Files

1477 lines
63 KiB
C++
Raw Permalink 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"
[UE]: More revisions to placeholder property bag type import/export serialization support during map asset loads in the editor. Change summary: - Modified ObjectPtr_Private::IsObjectPtrEqualToRawPtrOfRelatedType() to include a non-resolving NULL check for LHS. This allows unsafe type object pointers to equate to NULL object pointers (in addition to nullptr) - e.g. for compiled paths that do implicit type conversions from nullptr to TObjectPtr(nullptr). Also updated the unit test to reflect this behavior change. - Replaced FPropertyBagRepository::IsPropertyBagPlaceholderType() with IsPropertyBagPlaceholderObject(). - No longer setting RF_HasPlaceholderType on placeholder import type CDOs. This allows UObject initializers to evaluate/dereference the CDO ptr normally as a UObject*, even if created with a placeholder type. - Replaced direct RF_HasPlaceholderType flag queries with FPropertyBagRepository::IsPropertyBagPlaceholderObject() instead for placeholder export object queries in FLinkerLoad. This remains inclusive of the CDO. - Now appending RF_HasPlaceholderType onto the ObjectFlags member for export entries created from placeholder type imports. The flag will be cleared if/when the correct instance is patched back into the export table (e.g. at reinstancing time). - Modified FLinkerLoad::TryCreatePlaceholderTypeForExport() to remove the check for 'bSerializeUnknownProperty'. This does not get set until after we've created the placeholder type when we attempt to Preload() the export that's using it. - Modified FLinkerLoad::Serialize() to virtualize serialization when loading a property bag for an object with a missing type import that was serialized with an asset version older than EUnrealEngineObjectUE5Version::SCRIPT_SERIALIZATION_OFFSET. - Modified FLinkerLoad::Preload() to include an asset version check for when serialization of placeholder exports can be safely narrowed to SerializeScriptProperties(). For older asset versions, any non-TPS data serialization is now virtualized instead. - A warning is now emitted by FLinkerLoad::operator<<() when returning NULL for placeholder export object refs in those cases where we are not able to enforce its type safety at runtime. This now includes reflected properties that might serialize a reference to a placeholder type's CDO, which should be an unlikely edge case that we'll now report on here. - Re-enabled unit tests for object handle/pointer type safety (ObjectHandleTests.cpp). - Added a "stress test" method for object pointers to assist with A/B testing and perf analysis (ObjectPtrTests.cpp). - Modified natvis to extend the TObjectPtr format to display as 'nullptr' for pointers to placeholder export types. Intent is to minimize confusion while debugging since object pointers don't allow access to unsafe type objects directly. - Added a CVar to control whether or not we will create placeholder exports as serialization targets when import types are missing on map load (SceneGraph.EnablePropertyBagPlaceholderObjectSupport). Also can be enabled at launch via command line (-EnablePropertyBagPlaceholderObjects) for iteration purposes. Currently the CVar/feature defaults to off (experimental/WiP). #jira UE-197358 #rb Francis.Hurteau [CL 31447968 by phillip kavan in ue5-main branch]
2024-02-13 16:53:52 -05:00
#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");
#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);
[UE]: More revisions to placeholder property bag type import/export serialization support during map asset loads in the editor. Change summary: - Modified ObjectPtr_Private::IsObjectPtrEqualToRawPtrOfRelatedType() to include a non-resolving NULL check for LHS. This allows unsafe type object pointers to equate to NULL object pointers (in addition to nullptr) - e.g. for compiled paths that do implicit type conversions from nullptr to TObjectPtr(nullptr). Also updated the unit test to reflect this behavior change. - Replaced FPropertyBagRepository::IsPropertyBagPlaceholderType() with IsPropertyBagPlaceholderObject(). - No longer setting RF_HasPlaceholderType on placeholder import type CDOs. This allows UObject initializers to evaluate/dereference the CDO ptr normally as a UObject*, even if created with a placeholder type. - Replaced direct RF_HasPlaceholderType flag queries with FPropertyBagRepository::IsPropertyBagPlaceholderObject() instead for placeholder export object queries in FLinkerLoad. This remains inclusive of the CDO. - Now appending RF_HasPlaceholderType onto the ObjectFlags member for export entries created from placeholder type imports. The flag will be cleared if/when the correct instance is patched back into the export table (e.g. at reinstancing time). - Modified FLinkerLoad::TryCreatePlaceholderTypeForExport() to remove the check for 'bSerializeUnknownProperty'. This does not get set until after we've created the placeholder type when we attempt to Preload() the export that's using it. - Modified FLinkerLoad::Serialize() to virtualize serialization when loading a property bag for an object with a missing type import that was serialized with an asset version older than EUnrealEngineObjectUE5Version::SCRIPT_SERIALIZATION_OFFSET. - Modified FLinkerLoad::Preload() to include an asset version check for when serialization of placeholder exports can be safely narrowed to SerializeScriptProperties(). For older asset versions, any non-TPS data serialization is now virtualized instead. - A warning is now emitted by FLinkerLoad::operator<<() when returning NULL for placeholder export object refs in those cases where we are not able to enforce its type safety at runtime. This now includes reflected properties that might serialize a reference to a placeholder type's CDO, which should be an unlikely edge case that we'll now report on here. - Re-enabled unit tests for object handle/pointer type safety (ObjectHandleTests.cpp). - Added a "stress test" method for object pointers to assist with A/B testing and perf analysis (ObjectPtrTests.cpp). - Modified natvis to extend the TObjectPtr format to display as 'nullptr' for pointers to placeholder export types. Intent is to minimize confusion while debugging since object pointers don't allow access to unsafe type objects directly. - Added a CVar to control whether or not we will create placeholder exports as serialization targets when import types are missing on map load (SceneGraph.EnablePropertyBagPlaceholderObjectSupport). Also can be enabled at launch via command line (-EnablePropertyBagPlaceholderObjects) for iteration purposes. Currently the CVar/feature defaults to off (experimental/WiP). #jira UE-197358 #rb Francis.Hurteau [CL 31447968 by phillip kavan in ue5-main branch]
2024-02-13 16:53:52 -05:00
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
#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
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_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);
}
[UE]: More revisions to placeholder property bag type import/export serialization support during map asset loads in the editor. Change summary: - Modified ObjectPtr_Private::IsObjectPtrEqualToRawPtrOfRelatedType() to include a non-resolving NULL check for LHS. This allows unsafe type object pointers to equate to NULL object pointers (in addition to nullptr) - e.g. for compiled paths that do implicit type conversions from nullptr to TObjectPtr(nullptr). Also updated the unit test to reflect this behavior change. - Replaced FPropertyBagRepository::IsPropertyBagPlaceholderType() with IsPropertyBagPlaceholderObject(). - No longer setting RF_HasPlaceholderType on placeholder import type CDOs. This allows UObject initializers to evaluate/dereference the CDO ptr normally as a UObject*, even if created with a placeholder type. - Replaced direct RF_HasPlaceholderType flag queries with FPropertyBagRepository::IsPropertyBagPlaceholderObject() instead for placeholder export object queries in FLinkerLoad. This remains inclusive of the CDO. - Now appending RF_HasPlaceholderType onto the ObjectFlags member for export entries created from placeholder type imports. The flag will be cleared if/when the correct instance is patched back into the export table (e.g. at reinstancing time). - Modified FLinkerLoad::TryCreatePlaceholderTypeForExport() to remove the check for 'bSerializeUnknownProperty'. This does not get set until after we've created the placeholder type when we attempt to Preload() the export that's using it. - Modified FLinkerLoad::Serialize() to virtualize serialization when loading a property bag for an object with a missing type import that was serialized with an asset version older than EUnrealEngineObjectUE5Version::SCRIPT_SERIALIZATION_OFFSET. - Modified FLinkerLoad::Preload() to include an asset version check for when serialization of placeholder exports can be safely narrowed to SerializeScriptProperties(). For older asset versions, any non-TPS data serialization is now virtualized instead. - A warning is now emitted by FLinkerLoad::operator<<() when returning NULL for placeholder export object refs in those cases where we are not able to enforce its type safety at runtime. This now includes reflected properties that might serialize a reference to a placeholder type's CDO, which should be an unlikely edge case that we'll now report on here. - Re-enabled unit tests for object handle/pointer type safety (ObjectHandleTests.cpp). - Added a "stress test" method for object pointers to assist with A/B testing and perf analysis (ObjectPtrTests.cpp). - Modified natvis to extend the TObjectPtr format to display as 'nullptr' for pointers to placeholder export types. Intent is to minimize confusion while debugging since object pointers don't allow access to unsafe type objects directly. - Added a CVar to control whether or not we will create placeholder exports as serialization targets when import types are missing on map load (SceneGraph.EnablePropertyBagPlaceholderObjectSupport). Also can be enabled at launch via command line (-EnablePropertyBagPlaceholderObjects) for iteration purposes. Currently the CVar/feature defaults to off (experimental/WiP). #jira UE-197358 #rb Francis.Hurteau [CL 31447968 by phillip kavan in ue5-main branch]
2024-02-13 16:53:52 -05:00
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->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());
}
#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
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 = [&](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
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);
}
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