Files

473 lines
21 KiB
C++
Raw Permalink Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#if WITH_LOW_LEVEL_TESTS
#include "ObjectPtrTestClass.h"
#include "UObject/ObjectHandle.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/ObjectResource.h"
#include "UObject/MetaData.h"
#include "HAL/PlatformProperties.h"
#include "ObjectRefTrackingTestBase.h"
#include "IO/IoDispatcher.h"
#include "TestHarness.h"
#include "UObject/ObjectRef.h"
#include "UObject/ObjectPathId.h"
static_assert(sizeof(FObjectHandle) == sizeof(void*), "FObjectHandle type must always compile to something equivalent to a pointer size.");
class FObjectHandleTestBase : public FObjectRefTrackingTestBase
{
public:
protected:
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
void TestResolveFailure(UE::CoreUObject::Private::FPackedObjectRef PackedRef)
{
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
FObjectHandle TargetHandle = { PackedRef.EncodedRef };
UObject* ResolvedObject = FObjectPtr(TargetHandle).Get();
ObjectRefMetrics.TestNumResolves(TEXT("NumResolves should be incremented by one after a resolve attempt"), 1);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should be incremented by one after a resolve attempt"), 1);
CHECK(ResolvedObject == nullptr);
ObjectRefMetrics.TestNumFailedResolves(TEXT("NumFailedResolves should be incremented by one after a failed resolve attempt"), 1);
}
#endif
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE || UE_WITH_OBJECT_HANDLE_TRACKING
void TestResolvableNonNull(const ANSICHAR* PackageName, const ANSICHAR* ObjectName, bool bExpectSubRefReads)
{
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
FObjectRef TargetRef(FName(PackageName), NAME_None, NAME_None, UE::CoreUObject::Private::FObjectPathId(ObjectName));
UObject* ResolvedObject = TargetRef.Resolve();
FObjectPtr Ptr(ResolvedObject);
Ptr.Get();
TEST_TRUE(TEXT("expected not null"), ResolvedObject != nullptr);
ObjectRefMetrics.TestNumResolves(TEXT("NumResolves should be incremented by one after a resolve attempt"), 1);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should be incremented by one after a resolve attempt"), 1, bExpectSubRefReads /*bAllowAdditionalReads*/);
ObjectRefMetrics.TestNumFailedResolves(TEXT("NumFailedResolves should not change after a successful resolve attempt"), 0);
}
void TestResolveFailure(const ANSICHAR* PackageName, const ANSICHAR* ObjectName)
{
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
FObjectRef TargetRef(FName(PackageName), NAME_None, NAME_None, UE::CoreUObject::Private::FObjectPathId(ObjectName));
const UObject* ResolvedObject = TargetRef.Resolve();
ObjectRefMetrics.TestNumResolves(TEXT("NumResolves should be incremented by one after a resolve attempt"), 1);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should be incremented by one after a resolve attempt"), 1);
CHECK(ResolvedObject == nullptr);
ObjectRefMetrics.TestNumFailedResolves(TEXT("NumFailedResolves should be incremented by one after a failed resolve attempt"), 1);
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
}
#endif
};
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Null Behavior", "[CoreUObject][ObjectHandle]")
{
FObjectHandle TargetHandle = UE::CoreUObject::Private::MakeObjectHandle(nullptr);
TEST_TRUE(TEXT("Handle to target is null"), IsObjectHandleNull(TargetHandle));
TEST_TRUE(TEXT("Handle to target is resolved"), IsObjectHandleResolved(TargetHandle));
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
UObject* ResolvedObject = UE::CoreUObject::Private::ResolveObjectHandle(TargetHandle);
TEST_EQUAL(TEXT("Resolved object is equal to original object"), (UObject*)nullptr, ResolvedObject);
ObjectRefMetrics.TestNumFailedResolves(TEXT("NumFailedResolves should not change after a resolve attempt on a null handle"), 0);
ObjectRefMetrics.TestNumResolves(TEXT("NumResolves should not change after a resolve attempt on a null handle"), 0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should be incremented by one after a resolve attempt on a null handle"), 1);
}
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Pointer Behavior", "[CoreUObject][ObjectHandle]")
{
FObjectHandle TargetHandle = UE::CoreUObject::Private::MakeObjectHandle((UObject*)0x0042);
TEST_FALSE(TEXT("Handle to target is null"), IsObjectHandleNull(TargetHandle));
TEST_TRUE(TEXT("Handle to target is resolved"), IsObjectHandleResolved(TargetHandle));
FSnapshotObjectRefMetrics ObjectRefMetrics(*this);
UObject* ResolvedObject = UE::CoreUObject::Private::ResolveObjectHandle(TargetHandle);
TEST_EQUAL(TEXT("Resolved object is equal to original object"), (UObject*)0x0042, ResolvedObject);
ObjectRefMetrics.TestNumResolves(TEXT("NumResolves should not change after a resolve attempt on a pointer handle"), 0);
ObjectRefMetrics.TestNumFailedResolves(TEXT("NumFailedResolves should not change after a resolve attempt on a pointer handle"), 0);
ObjectRefMetrics.TestNumReads(TEXT("NumReads should be incremented by one after a resolve attempt on a pointer handle"),1);
}
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Resolve Engine Content Target", "[CoreUObject][ObjectHandle]")
{
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"));
UObject* TestSubObject = NewObject<UObjectPtrTestClass>(TestSoftObject, TEXT("SubObject"));
ON_SCOPE_EXIT{
TestPackage->RemoveFromRoot();
};
TestResolvableNonNull("/Engine/Test/ObjectPtrDefaultSerialize/Transient", "DefaultSerializeObject.SubObject", true);
TestResolvableNonNull("/Engine/Test/ObjectPtrDefaultSerialize/Transient", "DefaultSerializeObject", false);
}
// TODO: Disabled until warnings and errors related to loading a non-existent package have been fixed.
DISABLED_TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Resolve Non Existent Target", "[CoreUObject][ObjectHandle]")
{
// Confirm we don't successfully resolve an incorrect reference to engine content
TestResolveFailure("/Engine/EngineResources/NonExistentPackageName_0", "DefaultTexture");
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"));
ON_SCOPE_EXIT{
TestPackage->RemoveFromRoot();
};
TestResolveFailure("/Engine/Test/ObjectPtrDefaultSerialize/Transient", "DefaultSerializeObject_DoesNotExist");
}
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Resolve Script Target", "[CoreUObject][ObjectHandle]")
{
// Confirm we successfully resolve a correct reference to engine content
TestResolvableNonNull("/Script/CoreUObject", "MetaData", true);
}
#endif
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::TObjectPtr::HandleNullGetClass", "[CoreUObject][ObjectHandle]")
{
TObjectPtr<UObject> Ptr = nullptr;
TEST_TRUE(TEXT("TObjectPtr.GetClass should return null on a null object"), Ptr.GetClass() == nullptr);
}
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE("CoreUObject::FObjectHandle::Names")
{
const FName TestPackageName(TEXT("/Engine/Test/PackageResolve/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("DefaultSerializeObject"));
ON_SCOPE_EXIT{
TestPackage->RemoveFromRoot();
};
FObjectPtr Test;
FObjectPtr PackagePtr(MakeUnresolvedHandle(TestPackage));
FObjectPtr Obj1Ptr(MakeUnresolvedHandle(Obj1));
CHECK(!PackagePtr.IsResolved());
CHECK(TestPackage->GetPathName() == PackagePtr.GetPathName());
CHECK(TestPackage->GetFName() == PackagePtr.GetFName());
CHECK(TestPackage->GetName() == PackagePtr.GetName());
CHECK(TestPackage->GetFullName() == PackagePtr.GetFullName());
CHECK(!PackagePtr.IsResolved());
CHECK(!Obj1Ptr.IsResolved());
CHECK(Obj1->GetPathName() == Obj1Ptr.GetPathName());
CHECK(Obj1->GetFName() == Obj1Ptr.GetFName());
CHECK(Obj1->GetName() == Obj1Ptr.GetName());
CHECK(Obj1->GetFullName() == Obj1Ptr.GetFullName());
CHECK(!Obj1Ptr.IsResolved());
}
#endif
#if UE_WITH_OBJECT_HANDLE_TRACKING || UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE("CoreUObject::ObjectRef")
{
const FName TestPackageName(TEXT("/Engine/Test/ObjectRef/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("DefaultSerializeObject"));
UObject* Inner1 = NewObject<UObjectPtrTestClass>(Obj1, TEXT("Inner"));
ON_SCOPE_EXIT{
TestPackage->RemoveFromRoot();
};
{
FObjectImport ObjectImport(Obj1);
FObjectRef ObjectRef(Obj1);
CHECK(ObjectImport.ClassPackage == ObjectRef.ClassPackageName);
CHECK(ObjectImport.ClassName == ObjectRef.ClassName);
CHECK(TestPackage->GetFName() == ObjectRef.PackageName);
}
{
FObjectImport ObjectImport(Inner1);
FObjectRef ObjectRef(Inner1);
CHECK(ObjectImport.ClassPackage == ObjectRef.ClassPackageName);
CHECK(ObjectImport.ClassName == ObjectRef.ClassName);
CHECK(TestPackage->GetFName() == ObjectRef.PackageName);
}
}
#endif
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::TObjectPtr::Null Behavior", "[CoreUObject][ObjectHandle]")
{
TObjectPtr<UObject> Ptr = nullptr;
UObjectPtrTestClass* TestObject = nullptr;
uint32 ResolveCount = 0;
auto ResolveDelegate = [&ResolveCount](const FObjectRef& SourceRef, UPackage* ObjectPackage, UObject* Object)
{
++ResolveCount;
};
auto Handle = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback(ResolveDelegate);
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(Handle);
};
//compare against all flavours of nullptr, should not try and resolve this pointer
CHECK(Ptr == nullptr); CHECK(ResolveCount == 0u);
CHECK(nullptr == Ptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(Ptr != nullptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(nullptr != Ptr); CHECK(ResolveCount == 0u);
CHECK(!Ptr); CHECK(ResolveCount == 0u);
//using an if otherwise the macros try to convert to a pointer and not use the bool operator
if (Ptr)
{
CHECK(false);
}
else
{
CHECK(true);
}
CHECK(ResolveCount == 0u);
CHECK(Ptr == TestObject); CHECK(ResolveCount == 0u);
CHECK(TestObject == Ptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(Ptr != TestObject); CHECK(ResolveCount == 0u);
CHECK_FALSE(TestObject != Ptr); CHECK(ResolveCount == 0u);
FObjectRef TargetRef(FName("SomePackage"), FName("ClassPackageName"), FName("ClassName"), UE::CoreUObject::Private::FObjectPathId("ObjectName"));
UE::CoreUObject::Private::FPackedObjectRef PackedObjectRef = UE::CoreUObject::Private::MakePackedObjectRef(TargetRef);
FObjectPtr ObjectPtr({ PackedObjectRef.EncodedRef });
REQUIRE(!ObjectPtr.IsResolved()); //make sure not resolved
//an unresolved pointers compared against nullptr should still not resolve
Ptr = *reinterpret_cast<TObjectPtr<UObject>*>(&ObjectPtr);
CHECK_FALSE(Ptr == nullptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(nullptr == Ptr); CHECK(ResolveCount == 0u);
CHECK(Ptr != nullptr); CHECK(ResolveCount == 0u);
CHECK(nullptr != Ptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(!Ptr); CHECK(ResolveCount == 0u);
//using an if otherwise the macros try to convert to a pointer and not use the bool operator
if (Ptr)
{
CHECK(true);
}
else
{
CHECK(false);
}
CHECK(ResolveCount == 0u);
//test an unresolve pointer against a null raw pointer
CHECK_FALSE(Ptr == TestObject); CHECK(ResolveCount == 0u);
CHECK_FALSE(TestObject == Ptr); CHECK(ResolveCount == 0u);
CHECK(Ptr != TestObject); CHECK(ResolveCount == 0u);
CHECK(TestObject != Ptr); CHECK(ResolveCount == 0u);
//creating a real object for something that can resolve
const FName TestPackageName(TEXT("/Engine/Test/ObjectPtrDefaultSerialize/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
const FName TestObjectName(TEXT("MyObject"));
TestObject = NewObject<UObjectPtrTestClass>(TestPackage, TestObjectName, RF_Transient);
TObjectPtr<UObject> TestNotLazyObject = NewObject<UObjectPtrNotLazyTestClass>(TestPackage, TEXT("NotLazy"), RF_Transient);
//compare resolved ptr against nullptr
TObjectPtr<UObject> ResolvedPtr = TestObject;
CHECK(ResolvedPtr.IsResolved());
CHECK(Ptr != ResolvedPtr); CHECK(ResolveCount == 0u);
CHECK(ResolvedPtr != Ptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(Ptr == ResolvedPtr); CHECK(ResolveCount == 0u);
CHECK_FALSE(ResolvedPtr == Ptr); CHECK(ResolveCount == 0u);
//compare unresolved against nullptr
FObjectPtr FPtr(MakeUnresolvedHandle(TestObject));
TObjectPtr<UObject> UnResolvedPtr = *reinterpret_cast<TObjectPtr<UObject>*>(&FPtr);
CHECK(!UnResolvedPtr.IsResolved());
CHECK_FALSE(Ptr == UnResolvedPtr); CHECK(ResolveCount == 0u);
CHECK_FALSE(UnResolvedPtr == Ptr); CHECK(ResolveCount == 0u);
CHECK(Ptr != UnResolvedPtr); CHECK(ResolveCount == 0u);
CHECK(UnResolvedPtr != Ptr); CHECK(ResolveCount == 0u);
//compare unresolved against resolved not equal
CHECK_FALSE(TestNotLazyObject == UnResolvedPtr); CHECK(ResolveCount == 0u);
CHECK_FALSE(UnResolvedPtr == TestNotLazyObject); CHECK(ResolveCount == 0u);
CHECK(TestNotLazyObject != UnResolvedPtr); CHECK(ResolveCount == 0u);
CHECK(UnResolvedPtr != TestNotLazyObject); CHECK(ResolveCount == 0u);
//compare resolved against naked pointer
Ptr = TestObject;
REQUIRE(Ptr.IsResolved());
CHECK(Ptr == TestObject); CHECK(ResolveCount == 0u);
CHECK(TestObject == Ptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(Ptr != TestObject); CHECK(ResolveCount == 0u);
CHECK_FALSE(TestObject != Ptr); CHECK(ResolveCount == 0u);
//compare resolved pointer and unresolved of the same object
CHECK(Ptr == UnResolvedPtr); CHECK(ResolveCount == 0u);
CHECK(UnResolvedPtr == Ptr); CHECK(ResolveCount == 0u);
CHECK_FALSE(Ptr != UnResolvedPtr); CHECK(ResolveCount == 0u);
CHECK_FALSE(UnResolvedPtr != Ptr); CHECK(ResolveCount == 0u);
TestObject = nullptr;
CHECK_FALSE(Ptr == TestObject); CHECK(ResolveCount == 0u);
CHECK_FALSE(TestObject == Ptr); CHECK(ResolveCount == 0u);
CHECK(Ptr != TestObject); CHECK(ResolveCount == 0u);
CHECK(TestObject != Ptr); CHECK(ResolveCount == 0u);
TestObject = static_cast<UObjectPtrTestClass*>(Ptr.Get());
Ptr = nullptr;
CHECK_FALSE(Ptr == TestObject); CHECK(ResolveCount == 0u);
CHECK_FALSE(TestObject == Ptr); CHECK(ResolveCount == 0u);
CHECK(Ptr != TestObject); CHECK(ResolveCount == 0u);
CHECK(TestObject != Ptr); CHECK(ResolveCount == 0u);
support for TObjectPtr handling renames and GetTypeHash(TObjectPtr) no long has to resolve GetTypeHash will create a packaged object ref for hashing if the class is defined as lazy load otherwise raw pointer is hashed a map was added for moved objects and ObjectPathId can be a WeakObjectPtr the map allows UObjects to be mapped back to packed object refs #rb zousar.shaker https://p4-swarm.epicgames.net/reviews/23356491 #preflight 63b5de641c35d1cbdbccecf7 #preflight 63b70406e26e31879b8aa6d3 code cleanup of ObjectHandle made ObjectPathId to private trimmed down the public API as much as possible https://p4-swarm.epicgames.net/reviews/23658342 #rb zousar.shaker #preflight 63c5c8922a6acaf1625fcf25 #[robomerge] FNMain changed ObjectHandleTracking to use Functions instead of delegates as the performance of delegate is poor https://p4-swarm.epicgames.net/reviews/23751084 #rb zousar.shaker #preflight 63c9ae14d45afa2a8fc37d9d changed FObjectPropertyBase::CheckValidObject to not resolve FObjectPtr fields to do validation this is needed for feature work with validation of accessing asset pointers during serialization https://p4-swarm.epicgames.net/reviews/23782822 #rb zousar.shaker #[fyi] francis.hurteau #preflight 63cf06efd83c1837b14e2aeb fix for ObjectHandleTracking initialization. order of global variable initialization was problematic move globals into a struct added a singleton for the struct to do lazy initialization https://p4-swarm.epicgames.net/reviews/23877016 #rb zousar.shaker #preflight 63d308a7f626715201408881 changed `FClassProperty::Identical` to use TObjectPtr to avoid causing unnecessary reads #rb zousar.shaker #preflight 63d804493656ea96dc13d296 changed FObjectProperty::SerializeItem to not cause object handle reads #preflight 63d837e91f0aa8a2897477ee #ushell-cherrypick of 23589497 by joe.pribele #ushell-cherrypick of 23734208 by joe.pribele #ushell-cherrypick of 23781117 by joe.pribele #ushell-cherrypick of 23823212 by joe.pribele #ushell-cherrypick of 23878025 by joe.pribele #ushell-cherrypick of 23912609 by joe.pribele #ushell-cherrypick of 23916733 by joe.pribele [CL 24493715 by joe pribele in ue5-main branch]
2023-03-02 20:28:39 -05:00
}
#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(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Resolve Malformed Handle", "[CoreUObject][ObjectHandle]")
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
{
// make one packed ref guarantee something is in the object handle index
FObjectRef TargetRef(FName("/Test/DummyPackage"), FName("ClassPackageName"), FName("ClassName"), UE::CoreUObject::Private::FObjectPathId("DummyObjectName"));
UE::CoreUObject::Private::MakePackedObjectRef(TargetRef);
uint32 ObjectId = ~0u;
UPTRINT PackedId = ObjectId << 1 | 1;
UE::CoreUObject::Private::FPackedObjectRef PackedObjectRef = { PackedId };
TestResolveFailure(PackedObjectRef); // packed ref has a valid package id but invalid object id
TestResolveFailure(UE::CoreUObject::Private::FPackedObjectRef { 0xFFFF'FFFF'FFFF'FFFFull });
TestResolveFailure(UE::CoreUObject::Private::FPackedObjectRef { 0xEFEF'EFEF'EFEF'EFEFull });
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
}
#endif // UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Hash Object Without Index", "[CoreUObject][ObjectHandle]")
{
UObject DummyObjectWithInvalidIndex(EC_StaticConstructor, RF_NoFlags);
CHECK(DummyObjectWithInvalidIndex.GetUniqueID() == -1);
FObjectHandle DummyObjectHandle = UE::CoreUObject::Private::MakeObjectHandle(&DummyObjectWithInvalidIndex);
CHECK(GetTypeHash(DummyObjectHandle) == GetTypeHash(&DummyObjectWithInvalidIndex));
}
Create placeholder types/objects for package exports when type imports are missing/unresolved on editor map loads. Notes: - This allows the export to deserialize its tagged property stream (TPS) data into a property bag (when enabled), while also keeping references to the object in place. - The export can still be serialized to the same package on save with its TPS data still intact. In that case, we will serialize the property bag back into the package as a "normal" TPS data stream. - Serialized references to placeholder objects are resolved as NULL. For FObjectPtr types, the value is resolved as NULL when dereferenced (for type safety), but is still serialized as a non-NULL reference to the original object (even if placeholder). - Placeholders will not be created on load unless FUObjectSerializeContext::bSerializeUnknownProperty is set to TRUE in the serialization context. Otherwise, we immediately resolve the export as NULL, and its data is lost on load (previous behavior). Change summary: - Added FPropertyBagRepository::PropertyBagTypeRegistry (w/ thread-safe implementation). Currently using ARO to manage placeholder type object lifetime. - Added FPropertyBagRepository::AddPropertyBagPlaceholderType()/IsPropertyBagPlaceholderType(). - Modified FLinkerLoad::CreateExport() to instance a "placeholder" package/object if the export is missing its type import on load. Otherwise falls back to returning NULL (previous behavior). - Added UE_WITH_OBJECT_HANDLE_TYPE_SAFETY along with IsObjectHandleTypeSafe(). - Added UE::CoreUObject::Private::HasAnyFlags() to ObjectFwd.h (using int32 for the param here as the enum isn't defined at this level). Similar to internal APIs added for late resolve; avoid having to include Object.h. - Added RF_HasPlaceholderType (reclaimed an unused bit). This allows FObjectPtr to do a faster query at resolve time for type safety when referencing a placeholder object (i.e. it will return NULL). - Modified FObjectPtr::Get() and IsNull() variants to include an extra check for type safety. Right now this is only enabled in an editor build context. - Modified FLinkerLoad::Preload() to narrow the serialization scope to the export's TPS data *only* (since the original base type is unknown, we need to skip any natively-serialized data that might also exist in the stream). - Modified FLinkerLoad serialization of raw UObject* values to return NULL in the case of a placeholder object (for type safety at runtime). - Modified FLinkerLoad serialization of FObjectPtr values to return NULL in the case of a placeholder object only if type safety features are disabled on FObjectPtr/FObjectHandle. - Extended the LowLevel core NULL ptr tests to include additional tests for "unsafe type" objects wrapped by an FObjectPtr/TObjectPtr. #jira UE-197358 #rb Francis.Hurteau [CL 32476152 by phillip kavan in 5.4 branch]
2024-03-25 13:42:28 -04:00
#if UE_WITH_OBJECT_HANDLE_TYPE_SAFETY
[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 32477800 by phillip kavan in 5.4 branch]
2024-03-25 14:20:16 -04:00
TEST_CASE_METHOD(FObjectHandleTestBase, "CoreUObject::FObjectHandle::Type Safety", "[CoreUObject][ObjectHandle]")
Create placeholder types/objects for package exports when type imports are missing/unresolved on editor map loads. Notes: - This allows the export to deserialize its tagged property stream (TPS) data into a property bag (when enabled), while also keeping references to the object in place. - The export can still be serialized to the same package on save with its TPS data still intact. In that case, we will serialize the property bag back into the package as a "normal" TPS data stream. - Serialized references to placeholder objects are resolved as NULL. For FObjectPtr types, the value is resolved as NULL when dereferenced (for type safety), but is still serialized as a non-NULL reference to the original object (even if placeholder). - Placeholders will not be created on load unless FUObjectSerializeContext::bSerializeUnknownProperty is set to TRUE in the serialization context. Otherwise, we immediately resolve the export as NULL, and its data is lost on load (previous behavior). Change summary: - Added FPropertyBagRepository::PropertyBagTypeRegistry (w/ thread-safe implementation). Currently using ARO to manage placeholder type object lifetime. - Added FPropertyBagRepository::AddPropertyBagPlaceholderType()/IsPropertyBagPlaceholderType(). - Modified FLinkerLoad::CreateExport() to instance a "placeholder" package/object if the export is missing its type import on load. Otherwise falls back to returning NULL (previous behavior). - Added UE_WITH_OBJECT_HANDLE_TYPE_SAFETY along with IsObjectHandleTypeSafe(). - Added UE::CoreUObject::Private::HasAnyFlags() to ObjectFwd.h (using int32 for the param here as the enum isn't defined at this level). Similar to internal APIs added for late resolve; avoid having to include Object.h. - Added RF_HasPlaceholderType (reclaimed an unused bit). This allows FObjectPtr to do a faster query at resolve time for type safety when referencing a placeholder object (i.e. it will return NULL). - Modified FObjectPtr::Get() and IsNull() variants to include an extra check for type safety. Right now this is only enabled in an editor build context. - Modified FLinkerLoad::Preload() to narrow the serialization scope to the export's TPS data *only* (since the original base type is unknown, we need to skip any natively-serialized data that might also exist in the stream). - Modified FLinkerLoad serialization of raw UObject* values to return NULL in the case of a placeholder object (for type safety at runtime). - Modified FLinkerLoad serialization of FObjectPtr values to return NULL in the case of a placeholder object only if type safety features are disabled on FObjectPtr/FObjectHandle. - Extended the LowLevel core NULL ptr tests to include additional tests for "unsafe type" objects wrapped by an FObjectPtr/TObjectPtr. #jira UE-197358 #rb Francis.Hurteau [CL 32476152 by phillip kavan in 5.4 branch]
2024-03-25 13:42:28 -04:00
{
const FName TestPackageName(TEXT("/Engine/Test/ObjectHandle/TypeSafety/Transient"));
UPackage* TestPackage = NewObject<UPackage>(nullptr, TestPackageName, RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
// simulate an unsafe class type
UClass* TestClass = NewObject<UClass>(TestPackage, TEXT("TestClass"), RF_Transient);
TestClass->SetSuperStruct(UObject::StaticClass());
TestClass->Bind();
TestClass->StaticLink(/*bRelinkExistingProperties =*/ true);
UObject* TestClassDefaults = TestClass->GetDefaultObject();
TestClass->PostLoadDefaultObject(TestClassDefaults);
// validate helper method(s)
CHECK_FALSE(UE::CoreUObject::Private::HasAnyFlags(TestClassDefaults, RF_NoFlags));
[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 32477800 by phillip kavan in 5.4 branch]
2024-03-25 14:20:16 -04:00
CHECK(UE::CoreUObject::Private::HasAnyFlags(TestClassDefaults, RF_ClassDefaultObject));
Create placeholder types/objects for package exports when type imports are missing/unresolved on editor map loads. Notes: - This allows the export to deserialize its tagged property stream (TPS) data into a property bag (when enabled), while also keeping references to the object in place. - The export can still be serialized to the same package on save with its TPS data still intact. In that case, we will serialize the property bag back into the package as a "normal" TPS data stream. - Serialized references to placeholder objects are resolved as NULL. For FObjectPtr types, the value is resolved as NULL when dereferenced (for type safety), but is still serialized as a non-NULL reference to the original object (even if placeholder). - Placeholders will not be created on load unless FUObjectSerializeContext::bSerializeUnknownProperty is set to TRUE in the serialization context. Otherwise, we immediately resolve the export as NULL, and its data is lost on load (previous behavior). Change summary: - Added FPropertyBagRepository::PropertyBagTypeRegistry (w/ thread-safe implementation). Currently using ARO to manage placeholder type object lifetime. - Added FPropertyBagRepository::AddPropertyBagPlaceholderType()/IsPropertyBagPlaceholderType(). - Modified FLinkerLoad::CreateExport() to instance a "placeholder" package/object if the export is missing its type import on load. Otherwise falls back to returning NULL (previous behavior). - Added UE_WITH_OBJECT_HANDLE_TYPE_SAFETY along with IsObjectHandleTypeSafe(). - Added UE::CoreUObject::Private::HasAnyFlags() to ObjectFwd.h (using int32 for the param here as the enum isn't defined at this level). Similar to internal APIs added for late resolve; avoid having to include Object.h. - Added RF_HasPlaceholderType (reclaimed an unused bit). This allows FObjectPtr to do a faster query at resolve time for type safety when referencing a placeholder object (i.e. it will return NULL). - Modified FObjectPtr::Get() and IsNull() variants to include an extra check for type safety. Right now this is only enabled in an editor build context. - Modified FLinkerLoad::Preload() to narrow the serialization scope to the export's TPS data *only* (since the original base type is unknown, we need to skip any natively-serialized data that might also exist in the stream). - Modified FLinkerLoad serialization of raw UObject* values to return NULL in the case of a placeholder object (for type safety at runtime). - Modified FLinkerLoad serialization of FObjectPtr values to return NULL in the case of a placeholder object only if type safety features are disabled on FObjectPtr/FObjectHandle. - Extended the LowLevel core NULL ptr tests to include additional tests for "unsafe type" objects wrapped by an FObjectPtr/TObjectPtr. #jira UE-197358 #rb Francis.Hurteau [CL 32476152 by phillip kavan in 5.4 branch]
2024-03-25 13:42:28 -04:00
// construct objects for testing
UObject* TestSafeObject = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("TestSafeObject"), RF_Transient);
UObject* TestUnsafeObject = NewObject<UObject>(TestPackage, TestClass, TEXT("TestUnsafeObject"), RF_Transient | RF_HasPlaceholderType);
// construct object handles for testing
FObjectHandle NullObjectHandle = UE::CoreUObject::Private::MakeObjectHandle(nullptr);
FObjectHandle TestSafeObjectHandle = UE::CoreUObject::Private::MakeObjectHandle(TestSafeObject);
FObjectHandle TestUnsafeObjectHandle = UE::CoreUObject::Private::MakeObjectHandle(TestUnsafeObject);
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
// note that unresolved object handles are type safe by definition (since it implies the underlying type was not a placeholder)
FObjectRef TestSafeObjectRef(TestSafeObject);
UE::CoreUObject::Private::FPackedObjectRef PackedSafeObjectRef = UE::CoreUObject::Private::MakePackedObjectRef(TestSafeObjectRef);
FObjectHandle TestUnresolvedSafeObjectHandle = { PackedSafeObjectRef.EncodedRef };
#endif
// NULL/type-safe objects should report as being safe
CHECK(IsObjectHandleTypeSafe(NullObjectHandle));
CHECK(IsObjectHandleTypeSafe(TestSafeObjectHandle));
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
CHECK(IsObjectHandleTypeSafe(TestUnresolvedSafeObjectHandle));
CHECK(!IsObjectHandleResolved(TestUnresolvedSafeObjectHandle)); // the call above should not resolve the handle
#endif
// unsafe type object handles should report as being unsafe
CHECK_FALSE(IsObjectHandleTypeSafe(TestUnsafeObjectHandle));
// object handles should resolve the class to the unsafe type
CHECK(UE::CoreUObject::Private::ResolveObjectHandleClass(TestUnsafeObjectHandle) == TestClass);
// object handles should resolve/evaluate to the original type object
CHECK(UE::CoreUObject::Private::ResolveObjectHandle(TestUnsafeObjectHandle) == TestUnsafeObject);
[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 32477800 by phillip kavan in 5.4 branch]
2024-03-25 14:20:16 -04:00
// an unsafe type object handle should not equate to other unsafe type object handles except for itself (including NULL)
CHECK(NullObjectHandle != TestUnsafeObjectHandle); // note: this intentionally differs from object *pointers* (see below)
CHECK(TestUnsafeObjectHandle != NullObjectHandle); // see note directly above
CHECK(TestSafeObjectHandle != TestUnsafeObjectHandle);
CHECK(TestUnsafeObjectHandle != TestSafeObjectHandle);
CHECK(TestUnsafeObjectHandle == TestUnsafeObjectHandle);
Create placeholder types/objects for package exports when type imports are missing/unresolved on editor map loads. Notes: - This allows the export to deserialize its tagged property stream (TPS) data into a property bag (when enabled), while also keeping references to the object in place. - The export can still be serialized to the same package on save with its TPS data still intact. In that case, we will serialize the property bag back into the package as a "normal" TPS data stream. - Serialized references to placeholder objects are resolved as NULL. For FObjectPtr types, the value is resolved as NULL when dereferenced (for type safety), but is still serialized as a non-NULL reference to the original object (even if placeholder). - Placeholders will not be created on load unless FUObjectSerializeContext::bSerializeUnknownProperty is set to TRUE in the serialization context. Otherwise, we immediately resolve the export as NULL, and its data is lost on load (previous behavior). Change summary: - Added FPropertyBagRepository::PropertyBagTypeRegistry (w/ thread-safe implementation). Currently using ARO to manage placeholder type object lifetime. - Added FPropertyBagRepository::AddPropertyBagPlaceholderType()/IsPropertyBagPlaceholderType(). - Modified FLinkerLoad::CreateExport() to instance a "placeholder" package/object if the export is missing its type import on load. Otherwise falls back to returning NULL (previous behavior). - Added UE_WITH_OBJECT_HANDLE_TYPE_SAFETY along with IsObjectHandleTypeSafe(). - Added UE::CoreUObject::Private::HasAnyFlags() to ObjectFwd.h (using int32 for the param here as the enum isn't defined at this level). Similar to internal APIs added for late resolve; avoid having to include Object.h. - Added RF_HasPlaceholderType (reclaimed an unused bit). This allows FObjectPtr to do a faster query at resolve time for type safety when referencing a placeholder object (i.e. it will return NULL). - Modified FObjectPtr::Get() and IsNull() variants to include an extra check for type safety. Right now this is only enabled in an editor build context. - Modified FLinkerLoad::Preload() to narrow the serialization scope to the export's TPS data *only* (since the original base type is unknown, we need to skip any natively-serialized data that might also exist in the stream). - Modified FLinkerLoad serialization of raw UObject* values to return NULL in the case of a placeholder object (for type safety at runtime). - Modified FLinkerLoad serialization of FObjectPtr values to return NULL in the case of a placeholder object only if type safety features are disabled on FObjectPtr/FObjectHandle. - Extended the LowLevel core NULL ptr tests to include additional tests for "unsafe type" objects wrapped by an FObjectPtr/TObjectPtr. #jira UE-197358 #rb Francis.Hurteau [CL 32476152 by phillip kavan in 5.4 branch]
2024-03-25 13:42:28 -04:00
// construct object pointers for testing
TObjectPtr<UObject> NullObjectPtr(nullptr);
TObjectPtr<UObject> TestSafeObjectPtr(TestSafeObject);
TObjectPtr<UObject> TestUnsafeObjectPtr(TestUnsafeObject);
// unsafe type object pointers should evaluate to NULL/false (for type safety)
CHECK(!TestUnsafeObjectPtr);
CHECK_FALSE(!!TestUnsafeObjectPtr);
[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 32477800 by phillip kavan in 5.4 branch]
2024-03-25 14:20:16 -04:00
CHECK(NULL == TestUnsafeObjectPtr);
CHECK(TestUnsafeObjectPtr == NULL);
Create placeholder types/objects for package exports when type imports are missing/unresolved on editor map loads. Notes: - This allows the export to deserialize its tagged property stream (TPS) data into a property bag (when enabled), while also keeping references to the object in place. - The export can still be serialized to the same package on save with its TPS data still intact. In that case, we will serialize the property bag back into the package as a "normal" TPS data stream. - Serialized references to placeholder objects are resolved as NULL. For FObjectPtr types, the value is resolved as NULL when dereferenced (for type safety), but is still serialized as a non-NULL reference to the original object (even if placeholder). - Placeholders will not be created on load unless FUObjectSerializeContext::bSerializeUnknownProperty is set to TRUE in the serialization context. Otherwise, we immediately resolve the export as NULL, and its data is lost on load (previous behavior). Change summary: - Added FPropertyBagRepository::PropertyBagTypeRegistry (w/ thread-safe implementation). Currently using ARO to manage placeholder type object lifetime. - Added FPropertyBagRepository::AddPropertyBagPlaceholderType()/IsPropertyBagPlaceholderType(). - Modified FLinkerLoad::CreateExport() to instance a "placeholder" package/object if the export is missing its type import on load. Otherwise falls back to returning NULL (previous behavior). - Added UE_WITH_OBJECT_HANDLE_TYPE_SAFETY along with IsObjectHandleTypeSafe(). - Added UE::CoreUObject::Private::HasAnyFlags() to ObjectFwd.h (using int32 for the param here as the enum isn't defined at this level). Similar to internal APIs added for late resolve; avoid having to include Object.h. - Added RF_HasPlaceholderType (reclaimed an unused bit). This allows FObjectPtr to do a faster query at resolve time for type safety when referencing a placeholder object (i.e. it will return NULL). - Modified FObjectPtr::Get() and IsNull() variants to include an extra check for type safety. Right now this is only enabled in an editor build context. - Modified FLinkerLoad::Preload() to narrow the serialization scope to the export's TPS data *only* (since the original base type is unknown, we need to skip any natively-serialized data that might also exist in the stream). - Modified FLinkerLoad serialization of raw UObject* values to return NULL in the case of a placeholder object (for type safety at runtime). - Modified FLinkerLoad serialization of FObjectPtr values to return NULL in the case of a placeholder object only if type safety features are disabled on FObjectPtr/FObjectHandle. - Extended the LowLevel core NULL ptr tests to include additional tests for "unsafe type" objects wrapped by an FObjectPtr/TObjectPtr. #jira UE-197358 #rb Francis.Hurteau [CL 32476152 by phillip kavan in 5.4 branch]
2024-03-25 13:42:28 -04:00
CHECK(nullptr == TestUnsafeObjectPtr);
CHECK(TestUnsafeObjectPtr == 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 32477800 by phillip kavan in 5.4 branch]
2024-03-25 14:20:16 -04:00
// an unsafe type object pointer should not equate to other pointers except for NULL and itself
CHECK(NullObjectPtr == TestUnsafeObjectPtr); // note: this intentionally differs from object *handles* (see above)
CHECK(TestUnsafeObjectPtr == NullObjectPtr); // see note directly above
CHECK(TestSafeObjectPtr != TestUnsafeObjectPtr);
Create placeholder types/objects for package exports when type imports are missing/unresolved on editor map loads. Notes: - This allows the export to deserialize its tagged property stream (TPS) data into a property bag (when enabled), while also keeping references to the object in place. - The export can still be serialized to the same package on save with its TPS data still intact. In that case, we will serialize the property bag back into the package as a "normal" TPS data stream. - Serialized references to placeholder objects are resolved as NULL. For FObjectPtr types, the value is resolved as NULL when dereferenced (for type safety), but is still serialized as a non-NULL reference to the original object (even if placeholder). - Placeholders will not be created on load unless FUObjectSerializeContext::bSerializeUnknownProperty is set to TRUE in the serialization context. Otherwise, we immediately resolve the export as NULL, and its data is lost on load (previous behavior). Change summary: - Added FPropertyBagRepository::PropertyBagTypeRegistry (w/ thread-safe implementation). Currently using ARO to manage placeholder type object lifetime. - Added FPropertyBagRepository::AddPropertyBagPlaceholderType()/IsPropertyBagPlaceholderType(). - Modified FLinkerLoad::CreateExport() to instance a "placeholder" package/object if the export is missing its type import on load. Otherwise falls back to returning NULL (previous behavior). - Added UE_WITH_OBJECT_HANDLE_TYPE_SAFETY along with IsObjectHandleTypeSafe(). - Added UE::CoreUObject::Private::HasAnyFlags() to ObjectFwd.h (using int32 for the param here as the enum isn't defined at this level). Similar to internal APIs added for late resolve; avoid having to include Object.h. - Added RF_HasPlaceholderType (reclaimed an unused bit). This allows FObjectPtr to do a faster query at resolve time for type safety when referencing a placeholder object (i.e. it will return NULL). - Modified FObjectPtr::Get() and IsNull() variants to include an extra check for type safety. Right now this is only enabled in an editor build context. - Modified FLinkerLoad::Preload() to narrow the serialization scope to the export's TPS data *only* (since the original base type is unknown, we need to skip any natively-serialized data that might also exist in the stream). - Modified FLinkerLoad serialization of raw UObject* values to return NULL in the case of a placeholder object (for type safety at runtime). - Modified FLinkerLoad serialization of FObjectPtr values to return NULL in the case of a placeholder object only if type safety features are disabled on FObjectPtr/FObjectHandle. - Extended the LowLevel core NULL ptr tests to include additional tests for "unsafe type" objects wrapped by an FObjectPtr/TObjectPtr. #jira UE-197358 #rb Francis.Hurteau [CL 32476152 by phillip kavan in 5.4 branch]
2024-03-25 13:42:28 -04:00
CHECK(TestUnsafeObjectPtr != TestSafeObjectPtr);
CHECK(TestUnsafeObjectPtr == TestUnsafeObjectPtr);
// an unsafe type object should evaluate the object's attributes correctly
CHECK(TestUnsafeObjectPtr.GetName() == TestUnsafeObject->GetName());
CHECK(TestUnsafeObjectPtr.GetFName() == TestUnsafeObject->GetFName());
CHECK(TestUnsafeObjectPtr.GetPathName() == TestUnsafeObject->GetPathName());
CHECK(TestUnsafeObjectPtr.GetFullName() == TestUnsafeObject->GetFullName());
CHECK(TestUnsafeObjectPtr.GetOuter() == TestUnsafeObject->GetOuter());
CHECK(TestUnsafeObjectPtr.GetPackage() == TestUnsafeObject->GetPackage());
// an unsafe type object should not allow direct access to the underlying type
CHECK(TestUnsafeObjectPtr.GetClass() == nullptr);
// an unsafe type object pointer should resolve to NULL when dereferenced (for type safety)
CHECK(TestUnsafeObjectPtr.Get() == nullptr);
}
#endif
#endif