diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index 7fb50bced..106c65521 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -11,10 +11,10 @@ bAddPacks = True InsertPack = (PackSource="StarterContent.upack",PackName="StarterContent") [/Script/Cloud9.Cloud9DeveloperSettings] -bIsDrawHitCursorLine=False -bIsDrawDeprojectedCursorLine=False -bIsShowMouseCursor=False -NetGraph=1 +r.IsDrawHitCursorLine=0 +r.IsDrawDeprojectedCursorLine=1 +r.IsShowMouseCursor=0 +r.NetGraph=1 UnUsedEnum=Everything UnUsedStruct=(IntField0=3,FloatField1=0.000000) diff --git a/Source/Cloud9/Character/Cloud9Character.cpp b/Source/Cloud9/Character/Cloud9Character.cpp index 0f2c01dda..bdb2ac498 100644 --- a/Source/Cloud9/Character/Cloud9Character.cpp +++ b/Source/Cloud9/Character/Cloud9Character.cpp @@ -131,7 +131,7 @@ void ACloud9Character::SetViewDirection(const FHitResult& HitResult, bool bIsHit SetCursorIsHidden(false); } - let Settings = UCloud9DeveloperSettings::GetCloud9DeveloperSettings(); + let Settings = UCloud9DeveloperSettings::Get(); if (Settings->bIsDrawHitCursorLine) { @@ -177,7 +177,7 @@ void ACloud9Character::SetViewDirection(const FHitResult& HitResult, bool bIsHit void ACloud9Character::SetCameraRotationYaw(float Angle) const { - auto Rotation = CameraBoom->GetRelativeRotation(); + var Rotation = CameraBoom->GetRelativeRotation(); Rotation.Yaw = Angle; UE_LOG(LogCloud9, Display, TEXT("SetRelativeRotation Pitch: %s"), *Rotation.ToString()); CameraBoom->SetRelativeRotation(Rotation); @@ -185,8 +185,7 @@ void ACloud9Character::SetCameraRotationYaw(float Angle) const void ACloud9Character::AddCameraRotationYaw(float Angle) const { - const FRotator Rotation = {0.0f, Angle, 0.0f}; - CameraBoom->AddRelativeRotation(Rotation); + CameraBoom->AddRelativeRotation({0.0f, Angle, 0.0f}); } float ACloud9Character::GetCameraRotationRoll() const diff --git a/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.cpp b/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.cpp index 33a389e81..9469139dd 100644 --- a/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.cpp +++ b/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.cpp @@ -38,19 +38,19 @@ void UCloud9SpringArmComponent::UpdateDesiredArmLocation( bool bDoRotationLag, float DeltaTime) { - FRotator DesiredRot = GetTargetRotation(); + var DesiredRot = GetTargetRotation(); // Apply 'lag' to rotation if desired if (bDoRotationLag) { if (bUseCameraLagSubstepping && DeltaTime > CameraLagMaxTimeStep && CameraRotationLagSpeed > 0.f) { - const FRotator ArmRotStep = (DesiredRot - PreviousDesiredRot).GetNormalized() * (1.f / DeltaTime); - FRotator LerpTarget = PreviousDesiredRot; - float RemainingTime = DeltaTime; + let ArmRotStep = (DesiredRot - PreviousDesiredRot).GetNormalized() * (1.f / DeltaTime); + var LerpTarget = PreviousDesiredRot; + var RemainingTime = DeltaTime; while (RemainingTime > KINDA_SMALL_NUMBER) { - const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime); + let LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime); LerpTarget += ArmRotStep * LerpAmount; RemainingTime -= LerpAmount; @@ -80,9 +80,9 @@ void UCloud9SpringArmComponent::UpdateDesiredArmLocation( PreviousDesiredRot = DesiredRot; // Get the spring arm 'origin', the target we want to look at - FVector ArmOrigin = GetComponentLocation() + TargetOffset; + var ArmOrigin = GetComponentLocation() + TargetOffset; // We lag the target, not the actual camera position, so rotating the camera around does not have lag - FVector DesiredLoc = ArmOrigin; + var DesiredLoc = ArmOrigin; if (bDoLocationLag) { let LocationLag = CameraLagSpeed * CameraLagVector; @@ -91,13 +91,13 @@ void UCloud9SpringArmComponent::UpdateDesiredArmLocation( && DeltaTime > CameraLagMaxTimeStep && LocationLag != FVector::ZeroVector) { - const FVector ArmMovementStep = (DesiredLoc - PreviousDesiredLoc) * (1.f / DeltaTime); - auto LerpTarget = PreviousDesiredLoc; + let ArmMovementStep = (DesiredLoc - PreviousDesiredLoc) * (1.f / DeltaTime); + var LerpTarget = PreviousDesiredLoc; - float RemainingTime = DeltaTime; + var RemainingTime = DeltaTime; while (RemainingTime > KINDA_SMALL_NUMBER) { - const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime); + let LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime); LerpTarget += ArmMovementStep * LerpAmount; RemainingTime -= LerpAmount; diff --git a/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.h b/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.h index 16ce19899..04bf5b476 100644 --- a/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.h +++ b/Source/Cloud9/Character/Components/Cloud9SpringArmComponent.h @@ -34,8 +34,6 @@ class CLOUD9_API UCloud9SpringArmComponent : public USpringArmComponent GENERATED_BODY() public: - // TODO: CameraLagRotator - /** * If bEnableCameraLag is true, controls how quickly camera reaches target position by each axis. * Values of this vector multiplies with @CameraLagSpeed. diff --git a/Source/Cloud9/Game/Cloud9DeveloperSettings.cpp b/Source/Cloud9/Game/Cloud9DeveloperSettings.cpp index d655a8a8b..984ab002d 100644 --- a/Source/Cloud9/Game/Cloud9DeveloperSettings.cpp +++ b/Source/Cloud9/Game/Cloud9DeveloperSettings.cpp @@ -25,18 +25,16 @@ #include "Cloud9/Cloud9.h" #include "Cloud9/Tools/Extensions/UObject.h" +#include "Cloud9/Tools/Extensions/WhenOrNone.h" -const UCloud9DeveloperSettings* UCloud9DeveloperSettings::GetCloud9DeveloperSettings() -{ - static var bIsInitialized = false; - static let Settings = GetDefault(); - - if (!bIsInitialized) - { - Cast(Settings)->Log(); - bIsInitialized = true; - } +// ReSharper disable once CppPossiblyUninitializedMember +UCloud9DeveloperSettings::UCloud9DeveloperSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) {} +const UCloud9DeveloperSettings* UCloud9DeveloperSettings::Get() +{ + static let Settings = StaticClass()->GetDefaultObject(); + Settings->InitializeCVars(); return Settings; } @@ -46,8 +44,91 @@ void UCloud9DeveloperSettings::Save() Log(); } +template +auto RegisterConsoleVariable(TObject* Object, TProperty* Property) +{ + using TCppType = typename TProperty::TCppType; + + let ConsoleVariable = Property->GetMetaData(TEXT("ConsoleVariable")); + let ToolTip = Property->GetMetaData(TEXT("ToolTip")); + + let ValuePtr = Property->template ContainerPtrToValuePtr(Object); + + let ConsoleManager = &IConsoleManager::Get(); + + // FAutoConsoleVariableSink + let CVar = ConsoleManager->RegisterConsoleVariableRef(*ConsoleVariable, *ValuePtr, *ToolTip); + + CVar->AsVariable()->SetOnChangedCallback( + FConsoleVariableDelegate::CreateLambda([Object](auto Arg) { Object->Save(); }) + ); + + return CVar; +} + +void UCloud9DeveloperSettings::InitializeCVars() +{ + static var bIsConsoleInitialized = false; + + if (!bIsConsoleInitialized) + { + bIsConsoleInitialized = true; + + for (TFieldIterator It(GetClass()); It; ++It) + { + if (let Property = *It; Property->HasMetaData(TEXT("ConsoleVariable"))) + { + // TODO: Refactor with EachOrNone + let CVar = Property | WhenOrNone{ + [this](FBoolProperty* Arg) { return RegisterConsoleVariable(this, Arg); }, + [this](FIntProperty* Arg) { return RegisterConsoleVariable(this, Arg); }, + [this](FFloatProperty* Arg) { return RegisterConsoleVariable(this, Arg); }, + }; + + if (!CVar) + { + UE_LOG( + LogCloud9, + Warning, + TEXT("Property with meta key 'ConsoleVariable' %s wasn't registered" + " as console variable due to unsupported type '%s'"), + *Property->GetName(), + *Property->GetCPPType() + ) + } + } + } + + Log(); + } +} + void UCloud9DeveloperSettings::Log() const { let String = this | EUObject::Stringify(); UE_LOG(LogCloud9, Display, TEXT("%s"), *String); } + +void UCloud9DeveloperSettings::PostInitProperties() +{ + InitializeCVars(); + Super::PostInitProperties(); +#if WITH_EDITOR + if (IsTemplate()) + { + ImportConsoleVariableValues(); + } +#endif +} + +#if WITH_EDITOR +void UCloud9DeveloperSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + InitializeCVars(); + Super::PostEditChangeProperty(PropertyChangedEvent); + if (PropertyChangedEvent.Property) + { + ExportValuesToConsoleVariables(PropertyChangedEvent.Property); + } +} +#endif diff --git a/Source/Cloud9/Game/Cloud9DeveloperSettings.h b/Source/Cloud9/Game/Cloud9DeveloperSettings.h index f75dd637d..af659ff4f 100644 --- a/Source/Cloud9/Game/Cloud9DeveloperSettings.h +++ b/Source/Cloud9/Game/Cloud9DeveloperSettings.h @@ -48,14 +48,48 @@ enum class EUnUsedEnum : int32 Whatever = 3, }; -UCLASS(Config=Game, defaultconfig, meta = (DisplayName="Save Game Settings")) +UCLASS(Config=Game, defaultconfig, meta = (DisplayName="Various Developer Settings")) class CLOUD9_API UCloud9DeveloperSettings : public UDeveloperSettings { - GENERATED_BODY() + GENERATED_UCLASS_BODY() + +public: // properties + UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category=Debug, meta=( + ConsoleVariable="r.IsDrawHitCursorLine", + DisplayName="IsDrawHitCursorLine", + ToolTip="Whether to draw line from character to GetHitUnderCursor point")) + int32 bIsDrawHitCursorLine; + + UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category=Debug, meta=( + ConsoleVariable="r.IsDrawDeprojectedCursorLine", + DisplayName="IsDrawDeprojectedCursorLine", + ToolTip="Whether to draw line from character to deprojected mouse cursor")) + int32 bIsDrawDeprojectedCursorLine; + + UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category=Debug, meta=( + ConsoleVariable="r.IsShowMouseCursor", + DisplayName="IsShowMouseCursor", + ToolTip="Whether to show mouse cursor on screen or not in game")) + int32 bIsShowMouseCursor; + + UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category=Debug, meta=( + ConsoleVariable="r.NetGraph", + DisplayName="NetGraph", + ToolTip="Whether to show FPS and other specific debug info")) + int32 NetGraph; + + UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category=Debug) + EUnUsedEnum UnUsedEnum; -public: - UFUNCTION(BlueprintCallable, Category=Settings) - static const UCloud9DeveloperSettings* GetCloud9DeveloperSettings(); + UPROPERTY(config, EditAnywhere, BlueprintReadWrite, Category=Debug) + FUnUsedStruct UnUsedStruct; + +public: // static + UFUNCTION(BlueprintCallable, Category=Settings, DisplayName=GetCloud9DeveloperSettings) + static const UCloud9DeveloperSettings* Get(); + +public: // function + void InitializeCVars(); UFUNCTION(BlueprintCallable) void Save(); @@ -63,23 +97,6 @@ class CLOUD9_API UCloud9DeveloperSettings : public UDeveloperSettings UFUNCTION(BlueprintCallable) void Log() const; - // Debug - - UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Debug") - bool bIsDrawHitCursorLine; - - UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Debug") - bool bIsDrawDeprojectedCursorLine; - - UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Debug") - bool bIsShowMouseCursor; - - UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Debug") - int NetGraph; - - UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Debug") - EUnUsedEnum UnUsedEnum; - - UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Debug") - FUnUsedStruct UnUsedStruct; + virtual void PostInitProperties() override; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; }; diff --git a/Source/Cloud9/Game/Cloud9GameMode.cpp b/Source/Cloud9/Game/Cloud9GameMode.cpp index 71ead80cb..4f5bea88e 100644 --- a/Source/Cloud9/Game/Cloud9GameMode.cpp +++ b/Source/Cloud9/Game/Cloud9GameMode.cpp @@ -46,7 +46,7 @@ void ACloud9GameMode::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); - let Settings = UCloud9DeveloperSettings::GetCloud9DeveloperSettings(); + let Settings = UCloud9DeveloperSettings::Get(); if (Settings->NetGraph > 0) { diff --git a/Source/Cloud9/Game/Cloud9PlayerController.cpp b/Source/Cloud9/Game/Cloud9PlayerController.cpp index 5a9fd2c15..a5ecd4f36 100644 --- a/Source/Cloud9/Game/Cloud9PlayerController.cpp +++ b/Source/Cloud9/Game/Cloud9PlayerController.cpp @@ -47,7 +47,7 @@ void ACloud9PlayerController::PlayerTick(float DeltaTime) { Super::PlayerTick(DeltaTime); - let Settings = UCloud9DeveloperSettings::GetCloud9DeveloperSettings(); + let Settings = UCloud9DeveloperSettings::Get(); bShowMouseCursor = Settings->bIsShowMouseCursor; } diff --git a/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassA.cpp b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassA.cpp new file mode 100644 index 000000000..cfcf67102 --- /dev/null +++ b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassA.cpp @@ -0,0 +1,28 @@ +// Copyright (c) 2023 Alexei Gladkikh +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +#include "UWhenOrNoneTestClassA.h" + +// ReSharper disable once CppPossiblyUninitializedMember +UWhenOrNoneTestClassA::UWhenOrNoneTestClassA(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) {} diff --git a/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassA.h b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassA.h new file mode 100644 index 000000000..8fa6b6ba3 --- /dev/null +++ b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassA.h @@ -0,0 +1,44 @@ +// Copyright (c) 2023 Alexei Gladkikh +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "UWhenOrNoneTestClassA.generated.h" + +UCLASS() +class UWhenOrNoneTestClassA : public UObject +{ + GENERATED_UCLASS_BODY() + +public: // properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Debug) + bool BoolField; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Debug) + int IntField; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Debug) + float FloatField; +}; diff --git a/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassB.cpp b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassB.cpp new file mode 100644 index 000000000..7f02500d0 --- /dev/null +++ b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassB.cpp @@ -0,0 +1,28 @@ +// Copyright (c) 2023 Alexei Gladkikh +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +#include "UWhenOrNoneTestClassB.h" + +// ReSharper disable once CppPossiblyUninitializedMember +UWhenOrNoneTestClassB::UWhenOrNoneTestClassB(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) {} diff --git a/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassB.h b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassB.h new file mode 100644 index 000000000..382dcd57a --- /dev/null +++ b/Source/Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassB.h @@ -0,0 +1,45 @@ +// Copyright (c) 2023 Alexei Gladkikh +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "UWhenOrNoneTestClassB.generated.h" + + +UCLASS() +class UWhenOrNoneTestClassB : public UObject +{ + GENERATED_UCLASS_BODY() + +public: // properties + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Debug) + bool BoolField; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Debug) + int IntField; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Debug) + float FloatField; +}; diff --git a/Source/Cloud9/Tests/Unit/Tools/TestWhenOrNone.spec.cpp b/Source/Cloud9/Tests/Unit/Tools/TestWhenOrNone.spec.cpp new file mode 100644 index 000000000..982f3fe11 --- /dev/null +++ b/Source/Cloud9/Tests/Unit/Tools/TestWhenOrNone.spec.cpp @@ -0,0 +1,139 @@ +// Copyright (c) 2023 Alexei Gladkikh +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + + +#include "Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassA.h" +#include "Cloud9/Tests/Unit/Fake/UWhenOrNoneTestClassB.h" +#include "Cloud9/Tools/Extensions/WhenOrNone.h" + +BEGIN_DEFINE_SPEC( + FWhenOrNoneSpec, + "WhenOrNone.Unit", + EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask) + +END_DEFINE_SPEC(FWhenOrNoneSpec) + +namespace FWhenOrNoneClasses +{ + class FBase + { + public: + virtual ~FBase() = default; + virtual int Hello() = 0; + }; + + class FDerived1 final : public FBase + { + public: + virtual int Hello() override { return 1; } + }; + + class FDerived2 final : public FBase + { + public: + virtual int Hello() override { return 2; } + }; + + class FDerived3 final : public FBase + { + public: + virtual int Hello() override { return 3; } + }; + + class FDerived4 final : public FBase + { + public: + virtual int Hello() override { return 4; } + }; +} + +void FWhenOrNoneSpec::Define() +{ + Describe("WhenOrNone", [this] + { + let Object = UWhenOrNoneTestClassA::StaticClass()->GetDefaultObject(); + + It("dynamic_cast", [this, Object] + { + // WARNING: Won't work without RTTI information (by default disabled in UE) + + // let Array = TArray{ + // new FWhenOrNoneClasses::FDerived1, + // new FWhenOrNoneClasses::FDerived2, + // new FWhenOrNoneClasses::FDerived3, + // new FWhenOrNoneClasses::FDerived4, + // }; + // + // TArray Types; + // + // for (let Iterator : Array) + // { + // let TypeName = Iterator | WhenOrNone{ + // [this](FWhenOrNoneClasses::FDerived1* Arg) { return Arg->Hello(); }, + // [this](FWhenOrNoneClasses::FDerived2* Arg) { return Arg->Hello(); }, + // [this](FWhenOrNoneClasses::FDerived3* Arg) { return Arg->Hello(); }, + // [this](FWhenOrNoneClasses::FDerived4* Arg) { return Arg->Hello(); } + // }; + // TestTrue("TypeName.IsSet()", TypeName.IsSet()); + // Types.Add(*TypeName); + // } + // + // TestEqual("FDerived1", Types[0], 1); + // TestEqual("FDerived2", Types[1], 2); + // TestEqual("FDerived3", Types[2], 3); + // TestEqual("FDerived4", Types[3], 4); + }); + + It("UObject", [this, Object] + { + UObject* AsUObject = Object; + + let TypeName = AsUObject | WhenOrNone{ + [this](UWhenOrNoneTestClassA* Arg) { return "UWhenOrNoneTestClassA"; }, + [this](UWhenOrNoneTestClassB* Arg) { return "UWhenOrNoneTestClassB"; } + }; + TestTrue("TypeName.IsSet()", TypeName.IsSet()); + TestEqual("UWhenOrNoneTestClassA", *TypeName, "UWhenOrNoneTestClassA"); + }); + + It("FProperty", [this, Object] + { + var Types = TArray(); + + for (TFieldIterator Iterator(Object->GetClass()); Iterator; ++Iterator) + { + let TypeName = *Iterator | WhenOrNone{ + [this](FBoolProperty* Arg) { return "FBoolProperty"; }, + [this](FIntProperty* Arg) { return "FIntProperty"; }, + [this](FFloatProperty* Arg) { return "FFloatProperty"; } + }; + TestTrue("TypeName.IsSet()", TypeName.IsSet()); + Types.Add(*TypeName); + } + + TestEqual("FBoolProperty", Types[0], "FBoolProperty"); + TestEqual("FIntProperty", Types[1], "FIntProperty"); + TestEqual("FFloatProperty", Types[2], "FFloatProperty"); + }); + }); +} diff --git a/Source/Cloud9/Tools/Extensions/EachOrNone.h b/Source/Cloud9/Tools/Extensions/EachOrNone.h new file mode 100644 index 000000000..236d8d6b5 --- /dev/null +++ b/Source/Cloud9/Tools/Extensions/EachOrNone.h @@ -0,0 +1,62 @@ +#pragma once + +#include "Cloud9/Cloud9.h" + +namespace Private_EachOrNone +{ + template + struct THelper {}; + + template + struct THelper + { + using ReturnType = typename TInvokeResult::Type; + + explicit THelper(TOperation Operation) : Operation(Operation) {} + + template + friend constexpr auto operator|(TValue Value, THelper&& Self) + { + return THelper::Call(Value, Self); + } + + template + static constexpr TOptional Call(TValue Value, THelper& Self) + { + using ArgType = typename TRemovePointer::Type; + if (let Casted = CastField(Value)) + { + return Self.Operation(Casted); + } + + return THelper::Call(Value, Self); + } + + template + static constexpr TOptional Call(TValue Value, THelper& When) + { + using ArgType = typename TRemovePointer::Type; + if (let Casted = CastField(Value)) + { + return When.Operation(Casted); + } + + return {}; + } + + TOperation Operation; + }; +} + +/** + * TODO: Fix EachOrNone + */ +template +struct EachOrNone +{ + template + static auto Exec(TOperation Operation) + { + return Private_EachOrNone::THelper(Operation); + } +}; diff --git a/Source/Cloud9/Tools/Extensions/TFirstArgumentOf.h b/Source/Cloud9/Tools/Extensions/TFirstArgumentOf.h new file mode 100644 index 000000000..3693cc71c --- /dev/null +++ b/Source/Cloud9/Tools/Extensions/TFirstArgumentOf.h @@ -0,0 +1,21 @@ +#pragma once + +namespace Private_TFirstArgumentOf +{ + // ReSharper disable once CppFunctionIsNotImplemented + template + A Helper(ReturnType (Function::*)(A, Rest...)); + + // ReSharper disable once CppFunctionIsNotImplemented + template + A Helper(ReturnType (Function::*)(A, Rest...) const); +} + +/** + * Trait for the type of the first argument of invoking function (don't gonna work with static function now). + */ +template +struct TFirstArgumentOf +{ + using Type = decltype(Private_TFirstArgumentOf::Helper(&F::operator())); +}; diff --git a/Source/Cloud9/Tools/Extensions/TResultOf.h b/Source/Cloud9/Tools/Extensions/TResultOf.h new file mode 100644 index 000000000..55fa6bb2e --- /dev/null +++ b/Source/Cloud9/Tools/Extensions/TResultOf.h @@ -0,0 +1,22 @@ +#pragma once + +namespace Private_TResultOf +{ + // ReSharper disable once CppFunctionIsNotImplemented + template + ReturnType ResultOfHelper(ReturnType (Function::*)(A, Rest...)); + + // ReSharper disable once CppFunctionIsNotImplemented + template + ReturnType ResultOfHelper(ReturnType (Function::*)(A, Rest...) const); +} + +/** + * Trait for the type of the result when invoking. + * Unlike TInvokeResult aren't using SFINAE to disable 'Type' if F can't be called with given args. + */ +template +struct TResultOf +{ + using Type = decltype(Private_TResultOf::ResultOfHelper(&F::operator())); +}; diff --git a/Source/Cloud9/Tools/Extensions/WhenOrNone.h b/Source/Cloud9/Tools/Extensions/WhenOrNone.h new file mode 100644 index 000000000..ee245b57e --- /dev/null +++ b/Source/Cloud9/Tools/Extensions/WhenOrNone.h @@ -0,0 +1,102 @@ +// Copyright (c) 2023 Alexei Gladkikh +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +#pragma once + +#include "TFirstArgumentOf.h" +#include "TResultOf.h" +#include "Cloud9/Cloud9.h" + +template +struct WhenOrNone {}; + +template +struct WhenOrNone +{ + WhenOrNone(TFirst First, TRest... Rest) : First(First), Rest(Rest...) {} + + template + friend constexpr auto operator|(TValue Value, WhenOrNone&& Self) + { + using ReturnType = typename TResultOf::Type; + return WhenOrNone::Call(Value, Self); + } + + template + static constexpr TOptional Call(TValue Value, WhenOrNone& Self) + { + if (let Result = WhenOrNone::template Call(Value, Self.First)) + { + return Result; + } + + return WhenOrNone::template Call(Value, Self.Rest); + } + + template + static constexpr TOptional Call(TValue Value, WhenOrNone& When) + { + return WhenOrNone::template Call(Value, When.First); + } + + template + static constexpr TOptional Call(TValue Value, TFunction Function) + { + using ReturnType = typename TResultOf::Type; + using ArgType = typename TRemovePointer::Type>::Type; + using ValueType = typename TRemovePointer::Type; + static_assert(TIsSame::Value, "All WhenOrNone branches must return same type"); + + ArgType* Casted; + + if constexpr (TIsSame::Value) + { + Casted = CastField(Value); + } + else if constexpr (TIsSame::Value) + { + Casted = Cast(Value); + } + // else + // { + // // https://forums.unrealengine.com/t/how-do-i-cast-between-polymorphic-classes-that-dont-extend-uobject/368660 + // COMPILE_WARNING("bUseRTTI = true; in your build.cs file to enable cast for raw pointers") + // Casted = dynamic_cast(Value); + // } + + if (Casted != nullptr) + { + return Function(Casted); + } + + return {}; + } + +private: + TFirst First; + + WhenOrNone Rest; +}; + +template +WhenOrNone(Ts...) -> WhenOrNone;