diff --git a/Source/LyraGame/Workshop/RootedObject.cpp b/Source/LyraGame/Workshop/RootedObject.cpp new file mode 100644 index 00000000..706d0245 --- /dev/null +++ b/Source/LyraGame/Workshop/RootedObject.cpp @@ -0,0 +1,104 @@ +#include "RootedObject.h" + + +#if WITH_DEV_AUTOMATION_TESTS + +template +struct TIsAssignableFrom +{ + struct TrueType {}; + + template + static T DeclVal(); // intentionally not implemented + + enum + { + Value = TIsSame() = DeclVal(), DeclVal()), TrueType>::Value + }; +}; + +static_assert(TIsConstructible>::Value, "TRootedObject should be default constructible"); + +static_assert(TIsConstructible, TRootedObject&&>::Value, "TRootedObject should be move constructible"); +static_assert(TIsAssignableFrom, TRootedObject&&>::Value, "TRootedObject should be move assignable"); + +static_assert(TIsAssignableFrom>::Value, "TRootedObject should implicitly castable to its inner type"); + +BEGIN_DEFINE_SPEC(TRootedObjectSpec, "Workshop.RootedObject", EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask) + UObject* TheInnerObject; +END_DEFINE_SPEC(TRootedObjectSpec) + +void TRootedObjectSpec::Define() +{ + BeforeEach([this]() + { + TheInnerObject = NewObject(); + }); + + Describe("Construct", [this]() + { + It("Should add the rooted object to the root", [this]() + { + TRootedObject RootedObject(TheInnerObject); + TestTrue("TheInnerObject->IsRooted()", TheInnerObject->IsRooted()); + }); + + It("Should move correctly", [this]() + { + TRootedObject RootedObject(TheInnerObject); + TRootedObject OtherRootedObject; + + OtherRootedObject = MoveTemp(RootedObject); + + TestTrue("TheInnerObject->IsRooted()", TheInnerObject->IsRooted()); + TestFalse("RootedObject.IsValid()", RootedObject.IsValid()); + TestTrue("OtherRootedObject.IsValid()", OtherRootedObject.IsValid()); + }); + }); + + Describe("Destruct", [this]() + { + It("Should remove the rooted object from the root", [this]() + { + { + TRootedObject RootedObject(TheInnerObject); + TestTrue("TheInnerObject->IsRooted()", TheInnerObject->IsRooted()); + } + TestFalse("TheInnerObject->IsRooted()", TheInnerObject->IsRooted()); + }); + }); + + Describe("RetrieveObject", [this]() + { + It("Should invalidate the rooted object", [this]() + { + TRootedObject RootedObject(TheInnerObject); + RootedObject.RetrieveObject(); + TestFalse("RootedObject.IsValid()", RootedObject.IsValid()); + }); + + It("Should return the inner object", [this]() + { + TRootedObject RootedObject(TheInnerObject); + UObject* RetrievedObject = RootedObject.RetrieveObject(); + TestEqual("RootedObject.IsValid()", TheInnerObject, RetrievedObject); + }); + + It("Should remove the inner object from root", [this]() + { + TRootedObject RootedObject(TheInnerObject); + UObject* RetrievedObject = RootedObject.RetrieveObject(); + TestFalse("RetrievedObject->IsRooted()", RetrievedObject->IsRooted()); + }); + + It("Implicit Retrieve is same as explicit retrieve", [this]() + { + TRootedObject RootedObject(TheInnerObject); + UObject* RetrievedObject = RootedObject; + TestEqual("RootedObject.IsValid()", TheInnerObject, RetrievedObject); + TestFalse("RetrievedObject->IsRooted()", RetrievedObject->IsRooted()); + }); + }); +} + +#endif \ No newline at end of file diff --git a/Source/LyraGame/Workshop/RootedObject.h b/Source/LyraGame/Workshop/RootedObject.h new file mode 100644 index 00000000..4c507b6e --- /dev/null +++ b/Source/LyraGame/Workshop/RootedObject.h @@ -0,0 +1,64 @@ +#pragma once + +#include "CoreMinimal.h" + +template +class TRootedObject +{ +public: + using SelfType = TRootedObject; + + static_assert(TIsDerivedFrom::Value, "This class only works with UObjects"); + + TRootedObject() = default; + + explicit TRootedObject(UObjectType* Object) + : OwnedObject(Object) + { + OwnedObject->AddToRoot(); + } + + TRootedObject(const SelfType& Other) = delete; + SelfType& operator=(const SelfType& Other) = delete; + + + TRootedObject(SelfType&& Other) + { + *this = Other; + } + + SelfType& operator=(SelfType&& Other) + { + Swap(OwnedObject, Other.OwnedObject); + return *this; + } + + ~TRootedObject() + { + if (::IsValid(OwnedObject)) + { + OwnedObject->RemoveFromRoot(); + } + } + + operator UObjectType*() + { + return RetrieveObject(); + } + + UObjectType* RetrieveObject() + { + UObjectType* Temp = OwnedObject; + OwnedObject = nullptr; + Temp->RemoveFromRoot(); + return Temp; + } + + bool IsValid() const + { + return ::IsValid(OwnedObject); + } + +private: + UObjectType* OwnedObject = nullptr; +};