Files
UnrealEngineUWP/Engine/Source/Runtime/CoreUObject/Tests/ObjectPtrTest.cpp

1174 lines
51 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UObject/ObjectPtr.h"
#include "ObjectRefTrackingTestBase.h"
#include "ObjectPtrTestClass.h"
#include "Concepts/EqualityComparable.h"
#include "Serialization/ArchiveCountMem.h"
#include "Templates/Models.h"
#include "UObject/LinkerLoadImportBehavior.h"
#include "UObject/Interface.h"
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
#include "UObject/MetaData.h"
#include "UObject/ObjectPathId.h"
#include "UObject/ObjectRedirector.h"
#include "UObject/Package.h"
#include "UObject/SoftObjectPath.h"
#include "Misc/ScopeExit.h"
#include <type_traits>
namespace UE::CoreObject::Private::Tests
{
using FMutableObjectPtr = TObjectPtr<UObject>;
using FMutableInterfacePtr = TObjectPtr<UInterface>;
using FMutablePackagePtr = TObjectPtr<UPackage>;
using FConstObjectPtr = TObjectPtr<const UObject>;
using FConstInterfacePtr = TObjectPtr<const UInterface>;
using FConstPackagePtr = TObjectPtr<const UPackage>;
class UForwardDeclaredObjDerived;
class FForwardDeclaredNotObjDerived;
static_assert(sizeof(FObjectPtr) == sizeof(FObjectHandle), "FObjectPtr type must always compile to something equivalent to an FObjectHandle size.");
static_assert(sizeof(FObjectPtr) == sizeof(void*), "FObjectPtr type must always compile to something equivalent to a pointer size.");
static_assert(sizeof(TObjectPtr<UObject>) == sizeof(void*), "TObjectPtr<UObject> type must always compile to something equivalent to a pointer size.");
// Ensure that a TObjectPtr is trivially copyable, (copy/move) constructible, (copy/move) assignable, and destructible
static_assert(std::is_trivially_copyable<FMutableObjectPtr>::value, "TObjectPtr must be trivially copyable");
static_assert(std::is_trivially_copy_constructible<FMutableObjectPtr>::value, "TObjectPtr must be trivially copy constructible");
static_assert(std::is_trivially_move_constructible<FMutableObjectPtr>::value, "TObjectPtr must be trivially move constructible");
static_assert(std::is_trivially_copy_assignable<FMutableObjectPtr>::value, "TObjectPtr must be trivially copy assignable");
static_assert(std::is_trivially_move_assignable<FMutableObjectPtr>::value, "TObjectPtr must be trivially move assignable");
static_assert(std::is_trivially_destructible<FMutableObjectPtr>::value, "TObjectPtr must be trivially destructible");
// Ensure that raw pointers can be used to construct wrapped object pointers and that const-ness isn't stripped when constructing or converting with raw pointers
static_assert(std::is_constructible<FMutableObjectPtr, UObject*>::value, "TObjectPtr<UObject> must be constructible from a raw UObject*");
static_assert(!std::is_constructible<FMutableObjectPtr, const UObject*>::value, "TObjectPtr<UObject> must not be constructible from a const raw UObject*");
static_assert(std::is_convertible<FMutableObjectPtr, UObject*>::value, "TObjectPtr<UObject> must be convertible to a raw UObject*");
static_assert(std::is_convertible<FMutableObjectPtr, const UObject*>::value, "TObjectPtr<UObject> must be convertible to a const raw UObject*");
static_assert(std::is_constructible<FConstObjectPtr, UObject*>::value, "TObjectPtr<const UObject> must be constructible from a raw UObject*");
static_assert(std::is_constructible<FConstObjectPtr, const UObject*>::value, "TObjectPtr<const UObject> must be constructible from a const raw UObject*");
static_assert(!std::is_convertible<FConstObjectPtr, UObject*>::value, "TObjectPtr<const UObject> must not be convertible to a raw UObject*");
static_assert(std::is_convertible<FConstObjectPtr, const UObject*>::value, "TObjectPtr<const UObject> must be convertible to a const raw UObject*");
// Ensure that a TObjectPtr<const UObject> is constructible and assignable from a TObjectPtr<UObject> but not vice versa
static_assert(std::is_constructible<FConstObjectPtr, const FMutableObjectPtr&>::value, "Missing constructor (TObjectPtr<const UObject> from TObjectPtr<UObject>)");
static_assert(!std::is_constructible<FMutableObjectPtr, const FConstObjectPtr&>::value, "Invalid constructor (TObjectPtr<UObject> from TObjectPtr<const UObject>)");
static_assert(std::is_assignable<FConstObjectPtr, const FMutableObjectPtr&>::value, "Missing assignment (TObjectPtr<const UObject> from TObjectPtr<UObject>)");
static_assert(!std::is_assignable<FMutableObjectPtr, const FConstObjectPtr&>::value, "Invalid assignment (TObjectPtr<UObject> from TObjectPtr<const UObject>)");
static_assert(std::is_constructible<FConstObjectPtr, const FConstObjectPtr&>::value, "Missing constructor (TObjectPtr<const UObject> from TObjectPtr<const UObject>)");
static_assert(std::is_assignable<FConstObjectPtr, const FConstObjectPtr&>::value, "Missing assignment (TObjectPtr<const UObject> from TObjectPtr<const UObject>)");
// Ensure that a TObjectPtr<UObject> is constructible and assignable from a TObjectPtr<UInterface> but not vice versa
static_assert(std::is_constructible<FMutableObjectPtr, const FMutableInterfacePtr&>::value, "Missing constructor (TObjectPtr<UObject> from TObjectPtr<UInterface>)");
static_assert(!std::is_constructible<FMutableInterfacePtr, const FMutableObjectPtr&>::value, "Invalid constructor (TObjectPtr<UInterface> from TObjectPtr<UObject>)");
static_assert(std::is_constructible<FConstObjectPtr, const FConstInterfacePtr&>::value, "Missing constructor (TObjectPtr<const UObject> from TObjectPtr<const UInterface>)");
static_assert(std::is_constructible<FConstObjectPtr, const FMutableInterfacePtr&>::value, "Missing constructor (TObjectPtr<const UObject> from TObjectPtr<UInterface>)");
static_assert(!std::is_constructible<FConstInterfacePtr, const FConstObjectPtr&>::value, "Invalid constructor (TObjectPtr<const UInterface> from TObjectPtr<const UObject>)");
static_assert(!std::is_constructible<FConstInterfacePtr, const FMutableObjectPtr&>::value, "Invalid constructor (TObjectPtr<const UInterface> from TObjectPtr<UObject>)");
static_assert(std::is_assignable<FMutableObjectPtr, const FMutableInterfacePtr&>::value, "Missing assignment (TObjectPtr<UObject> from TObjectPtr<UInterface>)");
static_assert(std::is_assignable<FConstObjectPtr, const FMutableInterfacePtr&>::value, "Missing assignment (TObjectPtr<const UObject> from TObjectPtr<UInterface>)");
static_assert(std::is_assignable<FConstObjectPtr, const FConstInterfacePtr&>::value, "Missing assignment (TObjectPtr<const UObject> from TObjectPtr<const UInterface>)");
static_assert(!std::is_assignable<FMutableInterfacePtr, const FMutableObjectPtr&>::value, "Invalid assignment (TObjectPtr<UInterface> from TObjectPtr<UObject>)");
static_assert(!std::is_assignable<FConstInterfacePtr, const FMutableObjectPtr&>::value, "Invalid assignment (TObjectPtr<const UInterface> from TObjectPtr<UObject>)");
static_assert(!std::is_assignable<FConstInterfacePtr, const FConstObjectPtr&>::value, "Invalid assignment (TObjectPtr<const UInterface> from TObjectPtr<const UObject>)");
// Ensure that TObjectPtr<[const] UObject> is comparable with another TObjectPtr<[const] UObject> regardless of constness
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, FConstObjectPtr>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and TObjectPtr<const UObject>");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, FConstObjectPtr>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and TObjectPtr<const UObject>");
// Ensure that TObjectPtr<[const] UObject> is comparable with another TObjectPtr<[const] UInterface> regardless of constness
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, FConstInterfacePtr>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and TObjectPtr<const UInterface>");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, FConstInterfacePtr>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and TObjectPtr<const UInterface>");
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, FMutableInterfacePtr>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and TObjectPtr<UInterface>");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, FMutableInterfacePtr>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and TObjectPtr<UInterface>");
// Ensure that TObjectPtr<[const] UPackage> is not comparable with a TObjectPtr<[const] UInterface> regardless of constness
// TODO: This only ensures that at least one of the A==B,B==A,A!=B,B!=A operations fail, not that they all fail.
#if !(PLATFORM_MICROSOFT) || !defined(_MSC_EXTENSIONS) // MSVC static analyzer is run in non-conformance mode, and that causes these checks to fail.
static_assert(!TModels_V<CEqualityComparableWith, FConstPackagePtr, FConstInterfacePtr>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<const UPackage> and TObjectPtr<const UInterface>");
static_assert(!TModels_V<CEqualityComparableWith, FMutablePackagePtr, FConstInterfacePtr>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<UPackage> and TObjectPtr<const UInterface>");
static_assert(!TModels_V<CEqualityComparableWith, FConstPackagePtr, FMutableInterfacePtr>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<const UPackage> and TObjectPtr<UInterface>");
static_assert(!TModels_V<CEqualityComparableWith, FMutablePackagePtr, FMutableInterfacePtr>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<UPackage> and TObjectPtr<UInterface>");
#endif // #if !(PLATFORM_MICROSOFT) || !defined(_MSC_EXTENSIONS)
// Ensure that TObjectPtr<[const] UObject> is comparable with a raw pointer of the same referenced type regardless of constness
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, const UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and const UObject*");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, const UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and const UObject*");
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and UObject*");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and UObject*");
// Ensure that TObjectPtr<[const] UObject> is comparable with a UInterface raw pointer regardless of constness
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, const UInterface*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and const UInterface*");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, const UInterface*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and const UInterface*");
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, UInterface*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and UInterface*");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, UInterface*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and UInterface*");
// Ensure that TObjectPtr<[const] UInterface> is comparable with a UObject raw pointer regardless of constness
static_assert(TModels_V<CEqualityComparableWith, FConstInterfacePtr, const UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UInterface> and const UObject*");
static_assert(TModels_V<CEqualityComparableWith, FMutableInterfacePtr, const UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UInterface> and const UObject*");
static_assert(TModels_V<CEqualityComparableWith, FConstInterfacePtr, UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UInterface> and UObject*");
static_assert(TModels_V<CEqualityComparableWith, FMutableInterfacePtr, UObject*>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UInterface> and UObject*");
// Ensure that TObjectPtr<[const] UInterface> is not comparable with a UPackage raw pointer regardless of constness
// TODO: This only ensures that at least one of the A==B,B==A,A!=B,B!=A operations fail, not that they all fail.
static_assert(!TModels_V<CEqualityComparableWith, FConstInterfacePtr, const UPackage*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<const UInterface> and const UPackage*");
static_assert(!TModels_V<CEqualityComparableWith, FMutableInterfacePtr, const UPackage*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<UInterface> and const UPackage*");
static_assert(!TModels_V<CEqualityComparableWith, FConstInterfacePtr, UPackage*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<const UInterface> and UPackage*");
static_assert(!TModels_V<CEqualityComparableWith, FMutableInterfacePtr, UPackage*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<UInterface> and UPackage*");
// Ensure that TObjectPtr<[const] UInterface> is not comparable with a char raw pointer regardless of constness
// TODO: This only ensures that at least one of the A==B,B==A,A!=B,B!=A operations fail, not that they all fail.
static_assert(!TModels_V<CEqualityComparableWith, FConstObjectPtr, const char*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and const UObject*");
static_assert(!TModels_V<CEqualityComparableWith, FMutableObjectPtr, const char*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and const UObject*");
static_assert(!TModels_V<CEqualityComparableWith, FConstObjectPtr, char*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and UObject*");
static_assert(!TModels_V<CEqualityComparableWith, FMutableObjectPtr, char*>, "Must not be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and UObject*");
// Ensure that TObjectPtr<[const] UObject> is comparable with nullptr regardless of constness
static_assert(TModels_V<CEqualityComparableWith, FConstObjectPtr, TYPE_OF_NULLPTR>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and nullptr");
static_assert(TModels_V<CEqualityComparableWith, FMutableObjectPtr, TYPE_OF_NULLPTR>, "Must be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and nullptr");
#if !UE_OBJECT_PTR_NONCONFORMANCE_SUPPORT // Specialized NULL support causes these checks to fail.
static_assert(!TModels_V<CEqualityComparableWith, FConstObjectPtr, long>, "Should not be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and long");
static_assert(!TModels_V<CEqualityComparableWith, FMutableObjectPtr, long>, "Should not be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and long");
#endif // #if !UE_OBJECT_PTR_NONCONFORMANCE_SUPPORT
#if WITH_LOW_LEVEL_TESTS
// Ensure that the use of incomplete types doesn't provide a means to bypass type safety on TObjectPtr
// NOTE: This is disabled because we're permitting this operation with a deprecation warning.
//static_assert(!std::is_assignable<TObjectPtr<UForwardDeclaredObjDerived>, UForwardDeclaredObjDerived*>::value, "Should not be able to assign raw pointer of incomplete type that descends from UObject to exactly this type of TObjectPtr");
//static_assert(!std::is_assignable<TObjectPtr<FForwardDeclaredNotObjDerived>, FForwardDeclaredNotObjDerived*>::value, "Should not be able to assign raw pointer of incomplete type that does not descend from UObject to exactly this type of TObjectPtr");
class FObjectPtrTestBase : public FObjectRefTrackingTestBase
{
public:
protected:
};
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE("CoreUObject::TObjectPtr::FindLoadBehavior")
{
CHECK(UE::LinkerLoad::FindLoadBehavior(*UObjectPtrTestClass::StaticClass()) == UE::LinkerLoad::EImportBehavior::LazyOnDemand);
CHECK(::UE::LinkerLoad::FindLoadBehavior(*UObjectPtrDerrivedTestClass::StaticClass()) == ::UE::LinkerLoad::EImportBehavior::LazyOnDemand);
CHECK(::UE::LinkerLoad::FindLoadBehavior(*UObjectPtrNotLazyTestClass::StaticClass()) == ::UE::LinkerLoad::EImportBehavior::Eager);
}
#endif
TEST_CASE("CoreUObject::TObjectPtr::Null", "[CoreUObject][ObjectPtr]")
{
TObjectPtr<UObject> NullObjectPtr(nullptr);
TEST_TRUE(TEXT("Nullptr should equal a null object pointer"), nullptr == NullObjectPtr);
TEST_TRUE(TEXT("A null object pointer should equal nullptr"), NullObjectPtr == nullptr);
TEST_FALSE(TEXT("A null object pointer should evaluate to false"), !!NullObjectPtr);
TEST_TRUE(TEXT("Negation of a null object pointer should evaluate to true"), !NullObjectPtr);
}
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE || UE_WITH_OBJECT_HANDLE_TRACKING
TEST_CASE_METHOD(FObjectPtrTestBase, "CoreUObject::TObjectPtr::Default Serialize", "[CoreUObject][ObjectPtr]")
{
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
const FName TestPackageName(TEXT("/Engine/Test/ObjectPtrDefaultSerialize/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* TestSoftObject = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("DefaultSerializeObject"));
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectPtr DefaultSerializeObjectPtr(MakeUnresolvedHandle(TestSoftObject));
#else
FObjectPtr DefaultSerializeObjectPtr(TestSoftObject);
#endif
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after initializing an FObjectPtr"), 0);
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after initializing an FObjectPtr"), 0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should not change when initializing an FObjectPtr"), 0);
FArchiveUObject Writer;
Writer << DefaultSerializeObjectPtr;
ObjectRefMetrics.TestNumResolves(TEXT("Serializing an FObjectPtr should force it to resolve"), UE_WITH_OBJECT_HANDLE_LATE_RESOLVE ? 1 : 0);
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after serializing an FObjectPtr"), 0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should increase after serializing an FObjectPtr"), 1);
Writer << DefaultSerializeObjectPtr;
ObjectRefMetrics.TestNumResolves(TEXT("Serializing an FObjectPtr twice should only require it to resolve once"), UE_WITH_OBJECT_HANDLE_LATE_RESOLVE ? 1 : 0);
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after serializing an FObjectPtr"), 0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should increase after serializing an FObjectPtr"), 2);
TestPackage->RemoveFromRoot();
}
#endif
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE || UE_WITH_OBJECT_HANDLE_TRACKING
TEST_CASE_METHOD(FObjectPtrTestBase, "CoreUObject::TObjectPtr::Soft Object Path", "[CoreUObject][ObjectPtr]")
{
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
const FName TestPackageName(TEXT("/Engine/Test/ObjectPtrSoftObjectPath/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* TestSoftObject = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("TestSoftObject"));
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectPtr DefaultSoftObjPtr(MakeUnresolvedHandle(TestSoftObject));
#else
FObjectPtr DefaultSoftObjPtr(TestSoftObject);
#endif
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after initializing an FObjectPtr"), 0);
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after initializing an FObjectPtr"), 0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should not change when initializing an FObjectPtr"), 0);
// Initializing a soft object path from a TObjectPtr that's unresolved should stay unresolved.
FSoftObjectPath DefaultSoftObjPath(DefaultSoftObjPtr);
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after initializing an FSoftObjectPath from an FObjectPtr"), 0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should have changed when initializing a FSoftObjectPath"), UE_WITH_OBJECT_HANDLE_LATE_RESOLVE ? 0 : 1);
TEST_EQUAL_STR(TEXT("Soft object path constructed from an FObjectPtr does not have the expected path value"), TEXT("/Engine/Test/ObjectPtrSoftObjectPath/Transient.TestSoftObject"), *DefaultSoftObjPath.ToString());
TestPackage->RemoveFromRoot();
}
#endif
TEST_CASE_METHOD(FObjectPtrTestBase, "CoreUObject::TObjectPtr::Forward Declared", "[CoreUObject][ObjectPtr]")
{
UForwardDeclaredObjDerived* PtrFwd = nullptr;
TObjectPtr<UForwardDeclaredObjDerived> ObjPtrFwd(MakeObjectPtrUnsafe<UForwardDeclaredObjDerived>(reinterpret_cast<UObject*>(PtrFwd)));
TEST_TRUE(TEXT("Null forward declared pointer used to construct a TObjectPtr should result in a null TObjectPtr"), !ObjPtrFwd);
}
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE_METHOD(FObjectPtrTestBase, "CoreUObject::TObjectPtr::Hash Consistency", "[CoreUObject][ObjectPtr]")
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
{
const FName TestPackage1Name(TEXT("/Engine/Test/ObjectPtrHashConsistency1/Transient"));
UPackage* TestPackage1 = NewObject<UPackage>(nullptr, TestPackage1Name, RF_Transient);
TestPackage1->AddToRoot();
UObject* TestOuter1 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter1"));
UObject* TestOuter2 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter2"));
UObject* TestOuter3 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter3"));
UObject* TestOuter4 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter4"));
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
UObject* TestPublicObject = NewObject<UObjectPtrTestClass>(TestOuter1, TEXT("TestPublicObject"), RF_Public);
UE::CoreUObject::Private::MakePackedObjectRef(TestPublicObject); //construct a packed object ref the exported object. replicates linker load
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
// Perform hash consistency checks on public object reference
{
// Check that unresolved/resolved pointers produce the same hash
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectPtr TestPublicWrappedObjectPtr(MakeUnresolvedHandle(TestPublicObject));
#else
FObjectPtr TestPublicWrappedObjectPtr(TestPublicObject);
#endif
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after initializing an FObjectPtr"), 0);
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after initializing an FObjectPtr"), 0);
uint32 HashWrapped = GetTypeHash(TestPublicWrappedObjectPtr);
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after hashing an FObjectPtr"), 0);
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after hashing an FObjectPtr"), 0);
TestPublicWrappedObjectPtr.Get();
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after resolving an FObjectPtr"), 1);
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after resolving an FObjectPtr"), 0);
TObjectPtr<UObject> ObjectPtr = TestPublicObject;
uint32 ObjectPtrHash = GetTypeHash(ObjectPtr);
TEST_EQUAL(TEXT("Hash of FObjectPtr should equal hash of TObjectPtr"), ObjectPtrHash, HashWrapped);
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
//Check that renaming an object doesn't change its hash
TestPublicObject->Rename(TEXT("TestPublicObjectRenamed"));
uint32 HashWrappedAfterRename = GetTypeHash(TestPublicWrappedObjectPtr);
TEST_EQUAL(TEXT("Hash of resolved public FObjectPtr before rename should equal hash of resolved public FObjectPtr after rename"), HashWrappedAfterRename, HashWrapped);
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
//Check that reparenting an object doesn't change its hash
TestPublicObject->Rename(nullptr, TestOuter2);
uint32 HashWrappedAfterReparent = GetTypeHash(TestPublicWrappedObjectPtr);
TEST_EQUAL(TEXT("Hash of resolved public FObjectPtr before reparenting should equal hash of resolved public FObjectPtr after reparenting"), HashWrappedAfterReparent, HashWrapped);
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
}
TestPackage1->RemoveFromRoot();
}
#endif
Revision 2 (with handling for uninitialized object references): -Compile in lazy resolve functionality for editor binaries -Add "-LazyResolveAllImports" commandline switch to allow lazy RESOLVE of all wrapped object references -Functionality confirmed: load default map in ShooterGame Editor, cook ShooterGame, load LumenReflectiveTest in FortGPUTestbed, load default map and P_Construct in Frosty -Renamed "-DisableLoadingAllImports" to "-LazyLoadAllImports" commandline switch to allow lazy LOAD of all wrapped object references and ensured it works with AsyncLoading code path -Non functional when combined with lazy resolve - will be worked on afterwards -Added CPU timing scopes to measure performance impact of lazy resolve Fixed lazy resolve bugs: -Ensure null check on an unresolved object reference resolves the object reference (a non-null unresolved reference can become a null resolved reference) -Ensure hash of an unresolved object reference resolves the object reference and hashes the resultant address (otherwise we can't reliably ensure hash consistency in the face of object redirection or stale references) -Avoid using package name hash internally as it immediately manifested in hash collisions on a moderately sized project -Ensure StaticFindObjectFastInternal instead of StaticFindObjectFast to ensure we can find/resolve not fully loaded objects -Ensure UObjectRedirectors are handled when resolving wrapped object pointers -Ensure we handle the possibility that a package has been partially loaded and we may not find our target object within it because the object hasn't been created yet Automated testing: -Adding hash consistency and redirector resolve tests to ObjectPtr unit tests #rb devin.doucette [CL 15571874 by Zousar Shaker in ue5-main branch]
2021-03-02 14:39:53 -04:00
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE_METHOD(FObjectPtrTestBase, "CoreUObject::TObjectPtr::Long Path", "[CoreUObject][ObjectPtr]")
{
const FName TestPackage1Name(TEXT("/Engine/Test/FObjectPtrTestLongPath/Transient"));
UPackage* TestPackage1 = NewObject<UPackage>(nullptr, TestPackage1Name, RF_Transient);
TestPackage1->AddToRoot();
ON_SCOPE_EXIT{
TestPackage1->RemoveFromRoot();
};
UObject* TestObject1 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestObject1"));
UObject* TestObject2 = NewObject<UObjectPtrTestClass>(TestObject1, TEXT("TestObject2"));
UObject* TestObject3 = NewObject<UObjectPtrTestClass>(TestObject2, TEXT("TestObject3"));
UObject* TestObject4 = NewObject<UObjectPtrTestClass>(TestObject3, TEXT("TestObject4"));
UE::CoreUObject::Private::FObjectPathId LongPath(TestObject4);
UE::CoreUObject::Private::FObjectPathId::ResolvedNameContainerType ResolvedNames;
LongPath.Resolve(ResolvedNames);
TEST_EQUAL(TEXT("Resolved path from FObjectPathId should have 4 elements"), ResolvedNames.Num(), 4);
TEST_EQUAL(TEXT("Resolved path from FObjectPathId should have TestObject1 at element 0"), ResolvedNames[0], TestObject1->GetFName());
TEST_EQUAL(TEXT("Resolved path from FObjectPathId should have TestObject2 at element 1"), ResolvedNames[1], TestObject2->GetFName());
TEST_EQUAL(TEXT("Resolved path from FObjectPathId should have TestObject3 at element 2"), ResolvedNames[2], TestObject3->GetFName());
TEST_EQUAL(TEXT("Resolved path from FObjectPathId should have TestObject4 at element 3"), ResolvedNames[3], TestObject4->GetFName());
}
#endif
TEST_CASE("CoreUObject::TObjectPtr::GetPathName", "[CoreUObject][ObjectPtr]")
{
const FName TestPackage1Name(TEXT("/Engine/Test/FObjectPtrTestLongPath/Transient"));
UPackage* TestPackage1 = NewObject<UPackage>(nullptr, TestPackage1Name, RF_Transient);
TestPackage1->AddToRoot();
UObject* TestObject1 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestObject1"));
#if UE_WITH_OBJECT_HANDLE_TRACKING
int ResolveCount = 0;
auto ResolveDelegate = [&ResolveCount](const FObjectRef& SourceRef, UPackage* ObjectPackage, UObject* Object)
{
++ResolveCount;
};
auto Handle = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback(ResolveDelegate);
#endif
ON_SCOPE_EXIT
{
TestPackage1->RemoveFromRoot();
#if UE_WITH_OBJECT_HANDLE_TRACKING
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(Handle);
#endif
};
TObjectPtr<UObject> Ptr = nullptr;
CHECK(TEXT("None") == Ptr.GetPathName());
Ptr = TestObject1;
CHECK(TestObject1->GetPathName() == Ptr.GetPathName());
Ptr = TestPackage1;
CHECK(TestPackage1->GetPathName() == Ptr.GetPathName());
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
{
FObjectPtr ObjPtr(MakeUnresolvedHandle(TestObject1));
Ptr = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr);
REQUIRE(!Ptr.IsResolved());
CHECK(TestObject1->GetPathName() == Ptr.GetPathName());
CHECK(TestObject1->GetClass() == Ptr.GetClass());
}
{
FObjectPtr ObjPtr(MakeUnresolvedHandle(TestPackage1));
Ptr = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr);
REQUIRE(!Ptr.IsResolved());
CHECK(TestPackage1->GetPathName() == Ptr.GetPathName());
CHECK(TestPackage1->GetClass() == Ptr.GetClass());
}
#endif
#if UE_WITH_OBJECT_HANDLE_TRACKING
REQUIRE(ResolveCount == 0);
#endif
}
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE("CoreUObject::TObjectPtr::PackageRename")
{
const FName TestPackageName(TEXT("/Engine/Test/TestName/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
//register package with the object handle registry
UE::CoreUObject::Private::MakePackedObjectRef(TestPackage);
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj1"));
UObject* Inner1 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Inner1"));
UObject* Class = Obj1->GetClass();
Class->GetOutermost()->GetMetaData()->SetValue(Class, TEXT("LoadBehavior"), TEXT("LazyOnDemand"));
FObjectPtr ObjPtr(MakeUnresolvedHandle(Obj1));
FObjectPtr InnerPtr(MakeUnresolvedHandle(Inner1));
TObjectPtr<UObject> BeforeRename = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr);
TObjectPtr<UObject> BeforeRenameInner = *reinterpret_cast<TObjectPtr<UObject>*>(&InnerPtr);
REQUIRE(!BeforeRename.IsResolved());
CHECK(Obj1->GetPathName() == BeforeRename.GetPathName());
REQUIRE(!BeforeRenameInner.IsResolved());
CHECK(Inner1->GetPathName() == BeforeRenameInner.GetPathName());
TestPackage->Rename(TEXT("/Engine/Test/TestName/NewName"));
CHECK(Obj1->GetPathName() == BeforeRename.GetPathName());
CHECK(Inner1->GetPathName() == BeforeRenameInner.GetPathName());
TObjectPtr<UObject> AfterRenameResolved = Obj1;
TObjectPtr<UObject> AfterRenameInnerResolved = Inner1;
CHECK(BeforeRename == AfterRenameResolved);
CHECK(BeforeRenameInner == AfterRenameInnerResolved);
FObjectPtr ObjPtr2(MakeUnresolvedHandle(Obj1));
FObjectPtr InnerPtr2(MakeUnresolvedHandle(Inner1));
TObjectPtr<UObject> AfterRenameUnresolved = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr2);
TObjectPtr<UObject> AfterRenameInnerUnresolved = *reinterpret_cast<TObjectPtr<UObject>*>(&InnerPtr2);
CHECK(BeforeRename == AfterRenameUnresolved);
CHECK(BeforeRenameInner == AfterRenameInnerUnresolved);
Obj1->Rename(TEXT("RenamedObj"), nullptr);
CHECK(Obj1->GetPathName() == BeforeRename.GetPathName());
CHECK(Inner1->GetPathName() == BeforeRenameInner.GetPathName());
CHECK(BeforeRename == AfterRenameResolved);
CHECK(BeforeRenameInner == AfterRenameInnerResolved);
}
TEST_CASE("CoreUObject::TObjectPtr::InnerRename")
{
const FName TestPackageName(TEXT("/Engine/TestPackage"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj1"));
UObject* Inner1 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Inner1"));
UObject* Obj2 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj2"));
UObject* Inner2 = NewObject<UObjectPtrTestClass>(Obj2, TEXT("Inner2"));
FObjectPtr ObjPtr1(MakeUnresolvedHandle(Obj1));
FObjectPtr InnerPtr1(MakeUnresolvedHandle(Inner1));
FObjectPtr ObjPtr2(MakeUnresolvedHandle(Obj2));
FObjectPtr InnerPtr2(MakeUnresolvedHandle(Inner2));
TObjectPtr<UObject> BeforeRename1 = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr1);
TObjectPtr<UObject> BeforeRenameInner1 = *reinterpret_cast<TObjectPtr<UObject>*>(&InnerPtr1);
TObjectPtr<UObject> BeforeRename2 = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr2);
TObjectPtr<UObject> BeforeRenameInner2 = *reinterpret_cast<TObjectPtr<UObject>*>(&InnerPtr2);
REQUIRE(!BeforeRename1.IsResolved());
CHECK(Obj1->GetPathName() == BeforeRename1.GetPathName());
REQUIRE(!BeforeRenameInner1.IsResolved());
CHECK(Inner1->GetPathName() == BeforeRenameInner1.GetPathName());
REQUIRE(!BeforeRename2.IsResolved());
CHECK(Obj2->GetPathName() == BeforeRename2.GetPathName());
REQUIRE(!BeforeRenameInner2.IsResolved());
CHECK(Inner2->GetPathName() == BeforeRenameInner2.GetPathName());
Obj1->Rename(TEXT("RenamedObj"), nullptr);
FObjectPtr AfterRenameObjPtr1(MakeUnresolvedHandle(Obj1));
FObjectPtr AfterRenameInnerPtr1(MakeUnresolvedHandle(Inner1));
TObjectPtr<UObject> AfterRenameUnresolved1 = *reinterpret_cast<TObjectPtr<UObject>*>(&AfterRenameObjPtr1);
TObjectPtr<UObject> AfterRenameInnerUnresolved1 = *reinterpret_cast<TObjectPtr<UObject>*>(&AfterRenameInnerPtr1);
TObjectPtr<UObject> AfterRename1 = Obj1;
TObjectPtr<UObject> AfterRenameInner1 = Inner1;
CHECK(Obj1->GetPathName() == BeforeRename1.GetPathName());
REQUIRE(!BeforeRenameInner1.IsResolved());
CHECK(Inner1->GetPathName() == BeforeRenameInner1.GetPathName());
CHECK(BeforeRename1 == AfterRename1);
CHECK(BeforeRenameInner1 == AfterRenameInner1);
CHECK(BeforeRename1 == AfterRenameUnresolved1);
CHECK(BeforeRenameInner1 == AfterRenameInnerUnresolved1);
Obj1->Rename(nullptr, Inner2);
CHECK(Obj2->GetPathName() == BeforeRename2.GetPathName());
REQUIRE(!BeforeRenameInner2.IsResolved());
CHECK(Inner2->GetPathName() == BeforeRenameInner2.GetPathName());
CHECK(BeforeRename1 == AfterRename1);
CHECK(BeforeRename1.GetPathName() == AfterRename1.GetPathName());
CHECK(BeforeRenameInner1 == AfterRenameInner1);
TestPackage->RemoveFromRoot();
}
TEST_CASE("CoreUObject::TObjectPtr::Swap")
{
const FName TestPackageName(TEXT("/Engine/TestPackage"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj1"));
UObject* Inner1 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Inner1"));
UObject* RawPtrA = Obj1;
UObject* RawPtrB = Inner1;
FObjectPtr ObjPtr1(MakeUnresolvedHandle(RawPtrA));
FObjectPtr InnerPtr1(MakeUnresolvedHandle(RawPtrB));
TObjectPtr<UObject> PtrA = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr1);
TObjectPtr<UObject> PtrB = *reinterpret_cast<TObjectPtr<UObject>*>(&InnerPtr1);
CHECK(!PtrA.IsResolved());
CHECK(!PtrB.IsResolved());
Swap(PtrA, PtrB);
CHECK(!PtrA.IsResolved());
CHECK(!PtrB.IsResolved());
CHECK(PtrA != PtrB);
CHECK(PtrA != RawPtrA);
Swap(PtrA, RawPtrA);
CHECK(PtrA.IsResolved());
CHECK(RawPtrA == Inner1);
CHECK(PtrA == Obj1);
Swap(PtrB, PtrA);
CHECK(!PtrA.IsResolved());
CHECK(PtrB.IsResolved());
}
TEST_CASE("CoreUObject::TObjectPtr::SwapArray")
{
const FName TestPackageName(TEXT("/Engine/TestPackage"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj1"));
UObject* Inner1 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Inner1"));
UObject* RawPtrA = Obj1;
UObject* RawPtrB = Inner1;
FObjectPtr ObjPtr1(MakeUnresolvedHandle(RawPtrA));
FObjectPtr InnerPtr1(MakeUnresolvedHandle(RawPtrB));
TObjectPtr<UObject> PtrA = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjPtr1);
TObjectPtr<UObject> PtrB = *reinterpret_cast<TObjectPtr<UObject>*>(&InnerPtr1);
TArray<TObjectPtr<UObject>> ArrayPtr;
auto t = ArrayPtr.begin();
ArrayPtr.Add(PtrA);
TArray<UObject*> ArrayRaw;
ArrayRaw.Add(RawPtrB);
Swap(ArrayRaw, ArrayPtr);
CHECK(ArrayRaw[0] == RawPtrA);
CHECK(ArrayPtr[0] == RawPtrB);
}
TEST_CASE("CoreUObject::TObjectPtr::Move")
{
UPackage* TestPackageA = NewObject<UPackage>(nullptr, TEXT("/Engine/PackageA"), RF_Transient);
TestPackageA->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackageA, TEXT("Obj1"));
UObject* Inner1 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Inner1"));
UPackage* TestPackageB = NewObject<UPackage>(nullptr, TEXT("/Engine/PackageB"), RF_Transient);
TestPackageB->AddToRoot();
FObjectPtr FObjPtr(MakeUnresolvedHandle(Obj1));
FObjectPtr FInnerPtr(MakeUnresolvedHandle(Inner1));
TObjectPtr<UObject> BeforeRename = *reinterpret_cast<TObjectPtr<UObject>*>(&FObjPtr);
TObjectPtr<UObject> BeforeRenameInner = *reinterpret_cast<TObjectPtr<UObject>*>(&FInnerPtr);
TObjectPtr<UObject> ObjPtr1 = Obj1;
TObjectPtr<UObject> InnerPtr1 = Inner1;
REQUIRE(!BeforeRename.IsResolved());
CHECK(BeforeRename == ObjPtr1);
CHECK(Obj1->GetPathName() == BeforeRename.GetPathName());
REQUIRE(!BeforeRenameInner.IsResolved());
CHECK(InnerPtr1 == BeforeRenameInner);
CHECK(Inner1->GetPathName() == BeforeRenameInner.GetPathName());
Obj1->Rename(TEXT("Obj2"), TestPackageB);
bool e = BeforeRename == ObjPtr1;
REQUIRE(!BeforeRename.IsResolved());
CHECK(BeforeRename == ObjPtr1);
CHECK(Obj1->GetPathName() == BeforeRename.GetPathName());
CHECK(InnerPtr1 == BeforeRenameInner);
CHECK(Inner1->GetPathName() == BeforeRenameInner.GetPathName());
TObjectPtr<UObject> AfterRename = Obj1;
TObjectPtr<UObject> AfterRenameInner = Inner1;
CHECK(BeforeRename == AfterRename);
CHECK(BeforeRenameInner == AfterRenameInner);
TestPackageA->RemoveFromRoot();
TestPackageB->RemoveFromRoot();
}
TEST_CASE("CoreUObject::TObjectPtr::GetTypeHash")
{
int ResolveCount = 0;
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto ResolveDelegate = [&ResolveCount](const FObjectRef& SourceRef, UPackage* ObjectPackage, UObject* Object)
{
++ResolveCount;
};
auto Handle = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback(ResolveDelegate);
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(Handle);
};
#endif
UPackage* TestPackageA = NewObject<UPackage>(nullptr, TEXT("/Engine/PackageA"), RF_Transient);
TestPackageA->AddToRoot();
TObjectPtr<UObject> Obj1 = NewObject<UObjectPtrTestClass>(TestPackageA, TEXT("Obj1"));
TObjectPtr<UObject> Inner1 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Inner1"));
UE::CoreUObject::Private::MakePackedObjectRef(TestPackageA);
UE::CoreUObject::Private::MakePackedObjectRef(Obj1.Get());
UE::CoreUObject::Private::MakePackedObjectRef(Inner1.Get());
TMap<TObjectPtr<UObject>, int> TestMap;
int32 BeforeKey = GetTypeHash(Obj1);
int32 BeforeKeyInner = GetTypeHash(Inner1);
int32 BeforeKeyPackage = GetTypeHash(TestPackageA);
TestMap.Add(Obj1, 0);
TestMap.Add(Inner1, 1);
TestMap.Add(TestPackageA, 3);
//add a bunch of objects
for (int i = 0; i < 10; ++i)
{
TestMap.Add(NewObject<UObjectPtrTestClass>(TestPackageA), TestMap.Num());;
}
for (int i = 0; i < 10; ++i)
{
TestMap.Add(NewObject<UObjectPtrNotLazyTestClass>(TestPackageA), i);;
}
TObjectPtr<UObject> NotLazy = NewObject<UObjectPtrNotLazyTestClass>(TestPackageA);
int NotLazyValue = TestMap.Num();
TestMap.Add(NotLazy, NotLazyValue);
CHECK(GetTypeHash(NotLazy) == GetTypeHash(NotLazy.Get()));
CHECK(TestMap[NotLazy] == NotLazyValue);
FObjectPtr FPackagePtr(MakeUnresolvedHandle(TestPackageA));
FObjectPtr FObjPtr(MakeUnresolvedHandle(Obj1));
FObjectPtr FInnerPtr(MakeUnresolvedHandle(Inner1));
TObjectPtr<UObject> BeforeRename = *reinterpret_cast<TObjectPtr<UObject>*>(&FObjPtr);
TObjectPtr<UObject> BeforeRenameInner = *reinterpret_cast<TObjectPtr<UObject>*>(&FInnerPtr);
TObjectPtr<UObject> BeforeRenamePackage = *reinterpret_cast<TObjectPtr<UObject>*>(&FPackagePtr);
int32 BeforeRenameTypeHash = GetTypeHash(BeforeRename);
int32 BeforeRenameTypeHashInner = GetTypeHash(BeforeRenameInner);
int32 BeforeRenamePackageTypeHash = GetTypeHash(BeforeRenamePackage);
CHECK(BeforeKey == BeforeRenameTypeHash);
CHECK(BeforeKey == BeforeRenameTypeHash);
CHECK(BeforeKeyInner == BeforeRenameTypeHashInner);
CHECK(ResolveCount == 0);
CHECK(TestMap[Obj1] == 0);
CHECK(TestMap[BeforeRename] == 0);
CHECK(TestMap[Inner1] == 1);
CHECK(TestMap[BeforeRenameInner] == 1);
CHECK(TestMap[TestPackageA] == 3);
CHECK(TestMap[BeforeRenamePackage] == 3);
CHECK(!BeforeRename.IsResolved());
CHECK(!BeforeRenameInner.IsResolved());
CHECK(!BeforeRenamePackage.IsResolved());
auto TestMapFunc = [&]()
{
TMap<TObjectPtr<UObject>, bool> Map;
Map.Add(Obj1, false);
Map.Add(Inner1, true);
CHECK(ResolveCount == 0);
bool* Found = Map.Find(BeforeRename);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(!*Found);
Found = Map.Find(BeforeRenameInner);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(*Found);
CHECK(ResolveCount == 0);
Found = Map.Find(Obj1);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(!*Found);
CHECK(ResolveCount == 0);
Found = Map.Find(Inner1);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(*Found);
};
auto TestUnResolvedMapFunc = [&]()
{
TMap<TObjectPtr<UObject>, bool> Map;
Map.Add(BeforeRename, false);
Map.Add(BeforeRenameInner, true);
CHECK(ResolveCount == 0);
bool* Found = Map.Find(BeforeRename);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(!*Found);
Found = Map.Find(BeforeRenameInner);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(*Found);
CHECK(ResolveCount == 0);
Found = Map.Find(Obj1);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(!*Found);
CHECK(ResolveCount == 0);
Found = Map.Find(Inner1);
CHECK(ResolveCount == 0);
CHECK(Found != nullptr);
CHECK(*Found);
};
TestMapFunc();
TestUnResolvedMapFunc();
UPackage* TestPackageB = NewObject<UPackage>(nullptr, TEXT("/Engine/PackageB"), RF_Transient);
TestPackageB->AddToRoot();
Obj1->Rename(nullptr, TestPackageB);
TestMapFunc();
TestUnResolvedMapFunc();
TestPackageA->RemoveFromRoot();
TestPackageB->RemoveFromRoot();
}
#endif
template<typename TObj>
void TestArrayConversion()
{
const FName TestPackageName(TEXT("/Engine/TestPackage"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj1"));
#if UE_WITH_OBJECT_HANDLE_TRACKING
int ResolveCount = 0;
uint32 ObjCount = 0;
auto ResolveDelegate = [&](TArrayView<const UObject* const> Objects)
{
++ResolveCount;
ObjCount = Objects.Num();
for(const UObject* ReadObj : Objects)
{
CHECK(ReadObj == Obj1);
}
};
auto Handle = UE::CoreUObject::AddObjectHandleReadCallback(ResolveDelegate);
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReadCallback(Handle);
};
#endif
TArray<TObjectPtr<TObj>> PtrArray;
uint32 NumObjs = 1000000;
for (uint32 i = 0; i < NumObjs; ++i)
{
PtrArray.Add(Obj1);
}
{
TArray<TObj*> RawArray;
RawArray = PtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == NumObjs);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == NumObjs);
for (uint32 i = 0; i < NumObjs; ++i)
{
CHECK(RawArray[i] == Obj1);
}
}
{
TArray<TObjectPtr<TObj>> EmptyArray;
TArray<TObj*> RawArray;
RawArray = EmptyArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == 0);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == 0);
}
{
const TArray<TObjectPtr<TObj>>& ConstPtrArray = PtrArray;
TArray<TObj*> RawArray;
RawArray = ConstPtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == NumObjs);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == NumObjs);
for (uint32 i = 0; i < NumObjs; ++i)
{
CHECK(RawArray[i] == Obj1);
}
}
{
TArray<TObjectPtr<TObj>> EmptyArray;
const TArray<TObjectPtr<TObj>>& ConstPtrArray = EmptyArray;
TArray<TObj*> RawArray;
RawArray = ConstPtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == 0);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == 0);
}
{
const TArray<TObjectPtr<TObj>>& ConstPtrArray = PtrArray;
const TArray<TObj*>& RawArray = ConstPtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == NumObjs);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == NumObjs);
for (uint32 i = 0; i < NumObjs; ++i)
{
CHECK(RawArray[i] == Obj1);
}
}
{
TArray<TObjectPtr<TObj>> EmptyArray;
const TArray<TObjectPtr<TObj>>& ConstPtrArray = EmptyArray;
const TArray<TObj*>& RawArray = ConstPtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == 0);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == 0);
}
//ArrayView
{
TArrayView<TObj*> RawArray = PtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == NumObjs);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == NumObjs);
for (uint32 i = 0; i < NumObjs; ++i)
{
CHECK(RawArray[i] == Obj1);
}
}
{
TArray<TObjectPtr<TObj>> EmptyArray;
TArrayView<TObj*> RawArray = EmptyArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == 0);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == 0);
}
{
const TArray<TObjectPtr<TObj>>& ConstPtrArray = PtrArray;
const TArrayView<TObj* const> RawArray = ConstPtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == NumObjs);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == NumObjs);
for (uint32 i = 0; i < NumObjs; ++i)
{
CHECK(RawArray[i] == Obj1);
}
}
{
TArray<TObjectPtr<TObj>> EmptyArray;
const TArray<TObjectPtr<TObj>>& ConstPtrArray = EmptyArray;
const TArrayView<TObj* const> RawArray = ConstPtrArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == 0);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawArray.Num() == 0);
}
}
TEST_CASE("CoreUObject::TObjectPtr::ArrayConversion")
{
TestArrayConversion<UObject>();
TestArrayConversion<const UObject>();
}
TEST_CASE("CoreUObject::TObjectPtr::ArrayConversionReferenceForSet")
{
const FName TestPackageName(TEXT("/Engine/TestPackage"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj1"));
UObject* Obj2 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj2"));
#if UE_WITH_OBJECT_HANDLE_TRACKING
int ResolveCount = 0;
uint32 ObjCount = 0;
auto ResolveDelegate = [&](TArrayView<const UObject* const> Objects)
{
++ResolveCount;
ObjCount = Objects.Num();
if (ObjCount == 2)
{
CHECK(Objects[0] == Obj1);
CHECK(Objects[1] == Obj2);
}
};
auto Handle = UE::CoreUObject::AddObjectHandleReadCallback(ResolveDelegate);
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReadCallback(Handle);
};
#endif
{
TArray<TObjectPtr<UObject>> Array;
Array.Add(Obj1);
Array.Add(Obj2);
TSet<UObject*> RawSet(Array);
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == 2);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawSet.Num() == 2);
}
{
TArray<TObjectPtr<UObject>> Array;
TSet<UObject*> RawSet(Array);
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
CHECK(ObjCount == 0);
ResolveCount = 0;
ObjCount = 0;
#endif
CHECK(RawSet.Num() == 0);
}
}
TEST_CASE("CoreUObject::TObjectPtr::ConstArrayViewConversion")
{
TObjectPtr<UObject> ObjectArray[3];
TConstArrayView<TObjectPtr<UObject>> View(&ObjectArray[0], 3);
#if UE_WITH_OBJECT_HANDLE_TRACKING
int ResolveCount = 0;
auto ResolveDelegate = [&](TArrayView<const UObject* const> Objects)
{
++ResolveCount;
CHECK(Objects.Num() == 3);
CHECK(Objects[0] == nullptr);
CHECK(Objects[1] == nullptr);
CHECK(Objects[2] == nullptr);
};
auto Handle = UE::CoreUObject::AddObjectHandleReadCallback(ResolveDelegate);
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReadCallback(Handle);
};
#endif
{
TConstArrayView<UObject*> ConvertedArray = View;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
ResolveCount = 0;
#endif
CHECK(ConvertedArray.Num() == 3);
CHECK(ConvertedArray[0] == nullptr);
CHECK(ConvertedArray[1] == nullptr);
CHECK(ConvertedArray[2] == nullptr);
}
{
TArrayView<const TObjectPtr<UObject>> ConstArray(&ObjectArray[0], 3);
TArrayView<const UObject* const> ConvertedArray = ConstArray;
#if UE_WITH_OBJECT_HANDLE_TRACKING
CHECK(ResolveCount == 1);
#endif
CHECK(ConvertedArray.Num() == 3);
CHECK(ConvertedArray[0] == nullptr);
CHECK(ConvertedArray[1] == nullptr);
CHECK(ConvertedArray[2] == nullptr);
}
}
// @TODO: OBJPTR: We should have a test that ensures that lazy loading of an object with an external package is handled correctly.
// This should also include external packages in the outer chain of the target object.
// IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FObjectPtrTestExternalPackages, FObjectPtrTestBase, TEXT(TEST_NAME_ROOT ".ExternalPackages"), ObjectPtrTestFlags)
// bool FObjectPtrTestExternalPackages::RunTest(const FString& Parameters)
// {
// const FName TestExternalPackage1Name(TEXT("/Engine/Test/ObjectPtrExternalPackages1/Transient"));
// UPackage* TestExternalPackage1 = NewObject<UPackage>(nullptr, TestExternalPackage1Name, RF_Transient);
// TestExternalPackage1->SetPackageFlags(PKG_EditorOnly | PKG_ContainsMapData);
// TestExternalPackage1->AddToRoot();
// const FName TestPackage1Name(TEXT("/Engine/Test/ObjectPtrExternalPackages1/Transient"));
// UPackage* TestPackage1 = NewObject<UPackage>(nullptr, TestPackage1Name, RF_Transient);
// TestPackage1->AddToRoot();
// UObject* TestOuter1 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter1"));
// UObject* TestOuter2 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter2"));
// UObject* TestOuter3 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter3"));
// UObject* TestOuter4 = NewObject<UObjectPtrTestClass>(TestPackage1, TEXT("TestOuter4"));
// UObject* TestPublicObject = NewObject<UObjectPtrTestClass>(TestOuter4, TEXT("TestPublicObject"), RF_Public);
// TestPublicObject->SetExternalPackage(TestExternalPackage1);
// TestPackage1->RemoveFromRoot();
// TestExternalPackage1->RemoveFromRoot();
// return true;
// }
// @TODO: OBJPTR: We should have a test that ensures that we can (de)serialize an FObjectPtr to FLinkerSave/FLinkerLoad and that upon load the object
// pointer is not resolved if we are in a configuration that supports lazy load. This is proving difficult due to the restrictions around how
// FLinkerSave/FLinkerLoad is used.
// IMPLEMENT_CUSTOM_SIMPLE_AUTOMATION_TEST(FObjectPtrTestLinkerSerializeBehavior, FObjectPtrTestBase, TEXT(TEST_NAME_ROOT ".LinkerSerialize"), ObjectPtrTestFlags)
// bool FObjectPtrTestLinkerSerializeBehavior::RunTest(const FString& Parameters)
// {
// FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
// FObjectPtr DefaultTexturePtr(FObjectRef {FName("/Engine/EngineResources/DefaultTexture"), NAME_None, NAME_None, FObjectPathId("DefaultTexture")});
// TUniquePtr<FLinkerSave> Linker = TUniquePtr<FLinkerSave>(new FLinkerSave(nullptr /*InOuter*/, false /*bForceByteSwapping*/, true /*bSaveUnversioned*/));
// return true;
// }
#endif
class UForwardDeclaredObjDerived: public UObject {};
class FForwardDeclaredNotObjDerived {};
}
#if WITH_LOW_LEVEL_TESTS
//bunch of class to reproduce multiple inheritance issues.
//need to live outside of the namespace be IMPLEMENT_CORE_INTRINSIC_CLASS don't like namespaces
class FTestBaseClass
{
public:
virtual ~FTestBaseClass() = default;
virtual void VirtFunc() { };
};
class UMiddleClass : public UObject, public FTestBaseClass
{
DECLARE_CLASS_INTRINSIC(UMiddleClass, UObject, CLASS_MatchedSerializers, TEXT("/Script/CoreUObject"))
public:
virtual void VirtFunc() override { };
};
class FAnotherBaseClass
{
public:
virtual ~FAnotherBaseClass() = default;
virtual void AnotherVirtFunc() { };
};
class UDerrivedClass : public UMiddleClass, public FAnotherBaseClass
{
DECLARE_CLASS_INTRINSIC(UDerrivedClass, UMiddleClass, CLASS_MatchedSerializers, TEXT("/Script/CoreUObject"))
public:
virtual void AnotherVirtFunc() override { };
};
IMPLEMENT_CORE_INTRINSIC_CLASS(UMiddleClass, UObject,
{
}
);
IMPLEMENT_CORE_INTRINSIC_CLASS(UDerrivedClass, UMiddleClass,
{
}
);
TEST_CASE("CoreUObject::TObjectPtr::TestEquals")
{
const FName TestPackageName(TEXT("/Engine/Test/TestEquals/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UDerrivedClass* Obj = NewObject<UDerrivedClass>(TestPackage, TEXT("DefaultSerializeObject"));
FTestBaseClass* BasePtr = Obj;
TObjectPtr<UDerrivedClass> ObjPtr(Obj);
CHECK(BasePtr == ObjPtr);
}
#endif