Files
UnrealEngine/Engine/Source/Runtime/CoreUObject/Tests/ObjectPtrTest.cpp
2025-05-18 13:04:45 +08:00

1432 lines
62 KiB
C++

// 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"
#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 "Tests/Benchmark.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
#if !UE_OBJECT_PTR_GC_BARRIER
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");
#endif // !UE_OBJECT_PTR_GC_BARRIER
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");
// Ensure allowing comparison against NULL doesn't mean allowing comparison against int
static_assert(!TModels_V<CEqualityComparableWith, FConstObjectPtr, int>, "Should not be able to compare equality and inequality bidirectionally between TObjectPtr<const UObject> and int");
static_assert(!TModels_V<CEqualityComparableWith, FMutableObjectPtr, int>, "Should not be able to compare equality and inequality bidirectionally between TObjectPtr<UObject> and int");
#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("NULL should equal a null object pointer"), NULL == NullObjectPtr);
TEST_TRUE(TEXT("A null object pointer should equal NULL"), NullObjectPtr == NULL);
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"),
TestPackage,
0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should not change when initializing an FObjectPtr"),
TestSoftObject,
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"),
TestSoftObject,
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"),
TestSoftObject,
2);
TestPackage->RemoveFromRoot();
}
#endif // UE_WITH_OBJECT_HANDLE_LATE_RESOLVE || UE_WITH_OBJECT_HANDLE_TRACKING
#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"),
TestSoftObject,
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"),
TestSoftObject,
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 // UE_WITH_OBJECT_HANDLE_LATE_RESOLVE || UE_WITH_OBJECT_HANDLE_TRACKING
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]")
{
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"));
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
// 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);
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);
ObjectRefMetrics.TestNumFailedResolves(TEXT("Unexpected resolve failure after hashing an FObjectPtr"), 0);
(void)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);
//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);
//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);
}
TestPackage1->RemoveFromRoot();
}
#endif // UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
#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 // UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE_METHOD(FObjectPtrTestBase, "CoreUObject::TObjectPtr::Type Specialization", "[CoreUObject][ObjectPtr]")
{
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
const FName TestPackageName(TEXT("/Engine/Test/ObjectPtrTypeSafety/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
UObject* TestAsBaseUObject = NewObject<UObjectPtrTestClass>(TestPackage);
UObjectPtrTestClass* TestAsDerivedUObject = NewObject<UObjectPtrTestClass>(TestPackage);
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectPtr UntypedBaseUObjectPtr(MakeUnresolvedHandle(TestAsBaseUObject));
FObjectPtr UntypedDerivedUObjectPtr(MakeUnresolvedHandle(TestAsDerivedUObject));
#else
FObjectPtr UntypedBaseUObjectPtr(TestAsBaseUObject);
FObjectPtr UntypedDerivedUObjectPtr(TestAsDerivedUObject);
#endif
TObjectPtr<UObject> TestUObjectTypePtr(UntypedBaseUObjectPtr);
TObjectPtr<UObjectPtrTestClass> TestUObjectSubTypePtr(UntypedDerivedUObjectPtr);
// Evaluating the UObject base pointer type multiple times should not resolve the underlying handle.
TEST_TRUE(TEXT("UObject base pointer types should evaluate to true"), !!TestUObjectTypePtr);
TEST_TRUE(TEXT("UObject base pointer types should not equate to NULL"), TestUObjectTypePtr != nullptr);
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after UObject base pointer evaluation tests"), 0);
// Evaluating the UObject subtype pointer multiple times should also not resolve the underlying handle.
TEST_TRUE(TEXT("UObject subtype pointers should evaluate to true"), !!TestUObjectSubTypePtr);
TEST_TRUE(TEXT("UObject subtype pointers should not equate to NULL"), TestUObjectSubTypePtr != nullptr);
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after UObject subtype pointer evaluation tests"), 0);
// Dereferencing the UObject base pointer type multiple times should only increment the test's overall resolve count one time (if late resolve is enabled).
const UObject* ResolvedUObject = TestUObjectTypePtr;
TEST_TRUE(TEXT("UObject base pointer types should resolve to the original object"), ResolvedUObject == TestAsBaseUObject);
TEST_TRUE(TEXT("UObject base pointer types should resolve to a non-NULL reference"), Cast<UObject>(TestUObjectTypePtr) != nullptr);
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after dereferencing a UObject base pointer multiple times"), UE_WITH_OBJECT_HANDLE_LATE_RESOLVE ? 1 : 0);
// Dereferencing the UObject subtype pointer multiple times should only increment the test's overall resolve count one additional time (if late resolve is enabled).
const UObjectPtrTestClass* ResolvedUObjectSubType = TestUObjectSubTypePtr;
TEST_TRUE(TEXT("UObject subtype pointers should resolve to the original object"), ResolvedUObjectSubType == TestAsDerivedUObject);
TEST_TRUE(TEXT("UObject subtype pointers should resolve to a non-NULL reference"), Cast<UObjectPtrTestClass>(TestUObjectSubTypePtr) != nullptr);
ObjectRefMetrics.TestNumResolves(TEXT("Unexpected resolve count after dereferencing a UObject subtype pointer multiple times"), UE_WITH_OBJECT_HANDLE_LATE_RESOLVE ? 2 : 0);
}
void ObjectPtrStressTest(int32 NumTestNodes, bool bIsContiguousTest, bool bIsEvalOnlyTest)
{
const FName TestPackageName(TEXT("/Engine/Test/ObjectPtr/EvalStressTest/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
};
int32 NumObjectsAllocated = 0;
const int32 MaxAllowedAllocations = GUObjectArray.GetObjectArrayEstimatedAvailable();
TArray<TObjectPtr<UObjectPtrStressTestClass>*> TestNodePtrs;
TArray<TObjectPtr<UObjectPtrStressTestClass>> TestNodeStore;
if (bIsContiguousTest)
{
TestNodeStore.Reserve(NumTestNodes);
for (int32 NodeIdx = 0; NodeIdx < NumTestNodes; ++NodeIdx)
{
TestNodePtrs.Add(&TestNodeStore.AddDefaulted_GetRef());
}
}
else
{
for (int32 NodeIdx = 0; NodeIdx < NumTestNodes; ++NodeIdx)
{
TestNodePtrs.Add(new TObjectPtr<UObjectPtrStressTestClass>());
}
}
for (TObjectPtr<UObjectPtrStressTestClass>* TestNodePtr : TestNodePtrs)
{
// randomize the allocation of a test object
UObjectPtrStressTestClass* TestObject = nullptr;
if (NumObjectsAllocated < MaxAllowedAllocations && (FMath::Rand() % 2))
{
TestObject = NewObject<UObjectPtrStressTestClass>(TestPackage, NAME_None, RF_Transient);
++NumObjectsAllocated;
}
*TestNodePtr = TestObject;
}
for (TObjectPtr<UObjectPtrStressTestClass>* TestNodePtr : TestNodePtrs)
{
// fill out the remaining unallocated slots if we can
TObjectPtr<UObjectPtrStressTestClass>& TestNode = *TestNodePtr;
if (!TestNode && NumObjectsAllocated < MaxAllowedAllocations)
{
TestNode = NewObject<UObjectPtrStressTestClass>(TestPackage, NAME_None, RF_Transient);
++NumObjectsAllocated;
}
if (!bIsEvalOnlyTest)
{
// access the pointer (i.e. resolve to a raw UObject ptr) and do something with it
UObjectPtrStressTestClass* ResolvedObject = TestNode.Get();
if (ResolvedObject)
{
FMemory::Memzero(ResolvedObject->Data, PLATFORM_CACHE_LINE_SIZE);
}
}
if (!bIsContiguousTest)
{
if (TestNode)
{
TestNode->MarkAsGarbage();
}
delete TestNodePtr;
}
}
}
// Enable this method to add stress test benchmarks or to do A/B comparisons with features turned on/off.
DISABLED_TEST_CASE_METHOD(FObjectPtrTestBase, "CoreUObject::TObjectPtr::Stress Tests", "[CoreUObject][ObjectPtr]")
{
constexpr bool CONTIGUOUS = true;
constexpr bool NON_CONTIGUOUS = false;
constexpr bool EVAL_ONLY = true;
constexpr bool EVAL_AND_RESOLVE = false;
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
// contiguous object pointer blocks, eval-only
UE_BENCHMARK(5, [] { ObjectPtrStressTest(1000, CONTIGUOUS, EVAL_ONLY); } );
UE_BENCHMARK(5, [] { ObjectPtrStressTest(10000, CONTIGUOUS, EVAL_ONLY); });
UE_BENCHMARK(5, [] { ObjectPtrStressTest(100000, CONTIGUOUS, EVAL_ONLY); });
// non-contiguous list of object pointers, eval-only
UE_BENCHMARK(5, [] { ObjectPtrStressTest(1000, NON_CONTIGUOUS, EVAL_ONLY); });
UE_BENCHMARK(5, [] { ObjectPtrStressTest(10000, NON_CONTIGUOUS, EVAL_ONLY); });
UE_BENCHMARK(5, [] { ObjectPtrStressTest(100000, NON_CONTIGUOUS, EVAL_ONLY); });
// make sure that nothing was resolved above
ObjectRefMetrics.TestNumResolves(TEXT("Eval-only stress tests should not have triggered a resolve attempt"), 0);
// contiguous object pointer blocks, with resolve attempts
UE_BENCHMARK(5, [] { ObjectPtrStressTest(1000, CONTIGUOUS, EVAL_AND_RESOLVE); });
UE_BENCHMARK(5, [] { ObjectPtrStressTest(10000, CONTIGUOUS, EVAL_AND_RESOLVE); });
UE_BENCHMARK(5, [] { ObjectPtrStressTest(100000, CONTIGUOUS, EVAL_AND_RESOLVE); });
// non-contiguous list of object pointers, with resolve attempts
UE_BENCHMARK(5, [] { ObjectPtrStressTest(1000, NON_CONTIGUOUS, EVAL_AND_RESOLVE); });
UE_BENCHMARK(5, [] { ObjectPtrStressTest(10000, NON_CONTIGUOUS, EVAL_AND_RESOLVE); });
UE_BENCHMARK(5, [] { ObjectPtrStressTest(100000, NON_CONTIGUOUS, EVAL_AND_RESOLVE); });
}
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->GetPackage()->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());
}
#if !UE_DEPRECATE_MUTABLE_TOBJECTPTR
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);
}
#endif // !UE_DEPRECATE_MUTABLE_TOBJECTPTR
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 // UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
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 = [&](const 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 = 5;
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);
}
#if !UE_DEPRECATE_MUTABLE_TOBJECTPTR
//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);
}
}
#endif
#if !UE_DEPRECATE_MUTABLE_TOBJECTPTR
{
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);
}
#endif
{
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 = [&](const 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 = [&](const 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);
}
}
TEST_CASE("CoreUObject::TObjectPtr::GetOuter")
{
UPackage* TestPackage = NewObject<UPackage>(nullptr, "/Test/MyPackage", RF_Transient);
TestPackage->AddToRoot();
TObjectPtr<UObject> Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("Obj1"));
TObjectPtr<UObject> Obj2 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Obj2"));
int ResolveCount = 0;
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectPtr Ptr1(MakeUnresolvedHandle(TestPackage));
FObjectPtr Ptr2(MakeUnresolvedHandle(Obj1));
FObjectPtr Ptr3(MakeUnresolvedHandle(Obj2));
TObjectPtr<UPackage> PackagePtr = *reinterpret_cast<TObjectPtr<UPackage>*>(&Ptr1);
TObjectPtr<UObject> Obj1Ptr = *reinterpret_cast<TObjectPtr<UObject>*>(&Ptr2);
TObjectPtr<UObject> Obj2Ptr = *reinterpret_cast<TObjectPtr<UObject>*>(&Ptr3);
auto CallbackHandle = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback([&ResolveCount](const FObjectRef& SourceRef, UPackage* ObjectPackage, UObject* Object)
{
++ResolveCount;
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(CallbackHandle);
};
#else
TObjectPtr<UPackage> PackagePtr = TestPackage;
TObjectPtr<UObject> Obj1Ptr = Obj1;
TObjectPtr<UObject> Obj2Ptr = Obj2;
#endif
TObjectPtr<UObject> Obj1RawOuter = Obj1->GetOuter();
TObjectPtr<UObject> Obj2RawOuter = Obj2->GetOuter();
TObjectPtr<UObject> PackageOuter = PackagePtr.GetOuter();
TObjectPtr<UObject> Obj1Outer = Obj1Ptr.GetOuter();
TObjectPtr<UObject> Obj2Outer = Obj2Ptr.GetOuter();
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
CHECK(!Obj1Outer.IsResolved());
CHECK(!Obj2Outer.IsResolved());
//sanity check that the packed refs are identical
CHECK(Obj1Outer.GetHandle().PointerOrRef == PackagePtr.GetHandle().PointerOrRef);
CHECK(Obj2Outer.GetHandle().PointerOrRef == Obj1Ptr.GetHandle().PointerOrRef);
#endif
CHECK(Obj1Outer.GetHandle() == PackagePtr.GetHandle());
CHECK(Obj2Outer.GetHandle() == Obj1Ptr.GetHandle());
CHECK(PackageOuter == TestPackage->GetOuter());
CHECK(PackageOuter == nullptr);
CHECK(Obj1Outer == Obj1RawOuter);
CHECK(Obj1Outer.GetFName() == Obj1RawOuter->GetFName());
CHECK(Obj1Outer.GetPathName() == Obj1RawOuter->GetPathName());
CHECK(Obj1Outer.GetFullName() == Obj1RawOuter->GetFullName());
CHECK(Obj1Outer.GetClass() == Obj1RawOuter->GetClass());
CHECK(Obj2Outer == Obj2RawOuter);
CHECK(Obj2Outer.GetFName() == Obj2RawOuter->GetFName());
CHECK(Obj2Outer.GetPathName() == Obj2RawOuter->GetPathName());
CHECK(Obj2Outer.GetFullName() == Obj2RawOuter->GetFullName());
CHECK(Obj2Outer.GetClass() == Obj2RawOuter->GetClass());
TObjectPtr<UPackage> Package = PackagePtr.GetPackage();
TObjectPtr<UPackage> Obj1Package = Obj1Ptr.GetPackage();
TObjectPtr<UPackage> Obj2Package = Obj2Ptr.GetPackage();
CHECK(Package == PackagePtr);
CHECK(Obj1Package == PackagePtr);
CHECK(Obj2Package == PackagePtr);
CHECK(ResolveCount == 0);
}
// @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 // WITH_LOW_LEVEL_TESTS
class UForwardDeclaredObjDerived: public UObject {};
class FForwardDeclaredNotObjDerived {};
}
#if WITH_LOW_LEVEL_TESTS
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);
}
TEST_CASE("CoreUObject::TObjectPtr::DecayAndWrap")
{
const FName TestPackageName(TEXT("/Engine/Test/TestName/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* RawPtr1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("RawPtr1"));
UObject* RawPtr2 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("RawPtr2"));
{
TObjectPtr<UObject> ObjPtr{RawPtr1};
CHECK(RawPtr1 == ObjectPtrDecay(ObjPtr));
CHECK(ObjPtr == ObjectPtrWrap(RawPtr1));
CHECK(ObjectPtrDecay(ObjectPtrWrap(RawPtr1)) == RawPtr1);
CHECK(ObjectPtrWrap(ObjectPtrDecay(ObjPtr)) == ObjPtr);
}
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
{
FObjectPtr Unresolved{MakeUnresolvedHandle(RawPtr1)};
TObjectPtr<UObject> Ptr = reinterpret_cast<TObjectPtr<UObject>&>(Unresolved);
REQUIRE(!Unresolved.IsResolved());
CHECK(ObjectPtrDecay(Ptr) == RawPtr1);
CHECK(Ptr.IsResolved());
TArray<UObject*> RawArray = {RawPtr1, RawPtr2};
TArray<FObjectPtr> UnresolvedArray = {FObjectPtr(MakeUnresolvedHandle(RawPtr1)),
FObjectPtr(RawPtr2)};
REQUIRE(!UnresolvedArray[0].IsResolved());
REQUIRE(UnresolvedArray[1].IsResolved());
TArray<TObjectPtr<UObject>> ObjArray = reinterpret_cast<TArray<TObjectPtr<UObject>>&>(UnresolvedArray);
CHECK(ObjectPtrDecay(ObjArray) == RawArray);
CHECK(ObjArray[0].IsResolved());
CHECK(ObjArray[1].IsResolved());
}
#endif
};
#endif