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

609 lines
23 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#if WITH_LOW_LEVEL_TESTS
#include "TestHarness.h"
#include "TestMacros/Assertions.h"
#include "ObjectPtrTestClass.h"
#include "UObject/Package.h"
#include "Logging/LogScopedVerbosityOverride.h"
#include "Misc/ScopeExit.h"
#include "UObject/UnrealType.h"
#include "UObject/LinkerPlaceholderExportObject.h"
#include "UObject/LinkerPlaceholderClass.h"
#include "UObject/ObjectHandleTracking.h"
#include "UObject/PropertyHelper.h"
#include "Tests/WarnFilterScope.h"
#include "Misc/AssetRegistryInterface.h"
#include "AssetRegistry/AssetData.h"
#include "ObjectRefTrackingTestBase.h"
TEST_CASE("UE::CoreUObject::FObjectProperty::CheckValidAddress")
{
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto CallbackHandle = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback([](const FObjectRef&, UPackage*, UObject*)
{
FAIL("Unexpected resolve during CheckValidObject");
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(CallbackHandle);
};
#endif
UClass* Class = UObjectPtrTestClassWithRef::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(Property != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("/Temp/TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
UObjectPtrTestClassWithRef* Obj = NewObject<UObjectPtrTestClassWithRef>(TestPackage, TEXT("Object"));
UObjectPtrTestClass* Other = NewObject<UObjectPtrTestClass>(Obj, TEXT("Other"));
Revise TObjectPtr to use pointer semantics for type safety checks (editor only). Notes: - This replaces the previous solution that relied on indirection. - This solution relies on packed ref handles which requires late resolve features to also be enabled. Change summary: - Modified FPropertyBagRepository::IsPropertyBagPlaceholderType()/IsPropertyBagPlaceholderObject() to make the parameter read-only. - Re-enabled UE_WITH_OBJECT_HANDLE_TYPE_SAFETY for the editor context. This will unblock testing of IDO w/ placeholder type features. - Modified UE::CoreUObject::Private::Pack() to include a new "type" trait bit in the packed object handle reference. If set, it will signal that the reference is also a placeholder-typed object. - Modified UE::CoreUObject::Private::MakePackedObjectRef() to include a check for whether or not the given object reference is also a placeholder-typed object, and allow this case to pass. - Added UE::CoreUObject::Private::IsObjectHandleTypeSafeNoReadNoCheck(). This allows the implementation of IsObjectHandleTypeSafe() to rely on packed reference semantics, which are implemented as internal inline definitions. - Modified FLinkerLoad serialization of FObjectPtr values to register a packed object reference mapping for placeholder-typed objects. Note: This requires late resolve features to also be enabled. - Modified the TObjectPtr equality test to handle the case when only the RHS side is NULL. This can occur if the RHS is a non-UObject-type bound ptr to a placeholder object while the LHS is not. - Revised all object handle/ptr unit tests for type safety. Ensured that TObjectPtr stress tests are still in line both with/without type safety enabled. - Added a GObjectIndexToPackedObjectRefDebug global ptr to assist with debugger visualization of resolved placeholder-typed object references to allow for removal of RF_HasPlaceholderType. - Updated VS natvis file to revise queries for IsPlaceholderObject() on non-UObject-typed TObjectPtr values (removed ObjectFlags check and replaced w/ GObjectIndexToPackedObjectRefDebug). - Deprecated/removed all usage of RF_HasPlaceholderType (no longer required). - Removed FPropertyBagRepository::PropertyBagPlaceholderTypeRegistry and decoupled the current (static) API from the repo singleton instance. - (Minor) Fixed up some handle tracking unit tests in ObjectPropertyTest.cpp that did not compile with UE_WITH_OBJECT_HANDLE_LATE_RESOLVE disabled. #jira UE-212508, UE-197358 #rb Zousar.Shaker [CL 33397676 by phillip kavan in ue5-main branch]
2024-05-02 11:25:44 -04:00
#if UE_WITH_OBJECT_HANDLE_TRACKING && UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectHandle Handle = MakeUnresolvedHandle(Other);
TObjectPtr<UObjectPtrTestClass> ObjectPtr = *reinterpret_cast<TObjectPtr<UObjectPtrTestClass>*>(&Handle);
#else
TObjectPtr<UObjectPtrTestClass> ObjectPtr = Other;
#endif
//verify nothing happens by default
CHECK(!Obj->ObjectPtr);
Property->CheckValidObject(&Obj->ObjectPtr, nullptr);
CHECK(!Obj->ObjectPtr);
//valid assignment
Obj->ObjectPtr = ObjectPtr;
Property->CheckValidObject(&Obj->ObjectPtr, nullptr);
CHECK(Obj->ObjectPtr == ObjectPtr);
//assign a bad value to the pointer
Obj->ObjectPtr = reinterpret_cast<UObjectPtrTestClass*>(Obj);
CHECK(Obj->ObjectPtr != nullptr);
FWarnFilterScope _([](const TCHAR* Message, ELogVerbosity::Type Verbosity, const FName& Category)
{
if (Category == TEXT("LogProperty") && FCString::Strstr(Message, TEXT("Reference will be nulled")) && Verbosity == ELogVerbosity::Type::Warning)
{
return true;
}
return false;
});
Property->CheckValidObject(&Obj->ObjectPtr, ObjectPtr);
CHECK(!Obj->ObjectPtr); //value should be nulled since the type was not compatible
}
TEST_CASE("UE::CoreUObject::FObjectProperty::CheckValidAddressNonNullable")
{
bool bAllowRead = false;
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto CallbackHandle = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback([&bAllowRead](const FObjectRef & SourceRef, UPackage * ClassPackage, UObject* Object)
{
if (!bAllowRead)
{
FAIL("Unexpected resolve during CheckValidObject");
}
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(CallbackHandle);
};
#endif
UClass* Class = UObjectPtrTestClassWithRef::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtrNonNullable")));
FObjectProperty* AbstractProperty = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtrAbstractNonNullable")));
REQUIRE(Property != nullptr);
REQUIRE(AbstractProperty != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("/Temp/TestPackageName"), RF_Transient);
UPackage* OtherTestPackage = NewObject<UPackage>(nullptr, TEXT("/Temp/CheckValidAddressNonNullableOther"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
UObjectPtrTestClassWithRef* Obj = NewObject<UObjectPtrTestClassWithRef>(TestPackage, TEXT("Object"));
UObjectPtrTestClass* Other = NewObject<UObjectPtrTestClass>(Obj, TEXT("Other"));
UObjectPtrAbstractDerivedTestClass* AbstractDerivedOther = NewObject<UObjectPtrAbstractDerivedTestClass>(Obj, TEXT("AbstractDerivedOther"));
Revise TObjectPtr to use pointer semantics for type safety checks (editor only). Notes: - This replaces the previous solution that relied on indirection. - This solution relies on packed ref handles which requires late resolve features to also be enabled. Change summary: - Modified FPropertyBagRepository::IsPropertyBagPlaceholderType()/IsPropertyBagPlaceholderObject() to make the parameter read-only. - Re-enabled UE_WITH_OBJECT_HANDLE_TYPE_SAFETY for the editor context. This will unblock testing of IDO w/ placeholder type features. - Modified UE::CoreUObject::Private::Pack() to include a new "type" trait bit in the packed object handle reference. If set, it will signal that the reference is also a placeholder-typed object. - Modified UE::CoreUObject::Private::MakePackedObjectRef() to include a check for whether or not the given object reference is also a placeholder-typed object, and allow this case to pass. - Added UE::CoreUObject::Private::IsObjectHandleTypeSafeNoReadNoCheck(). This allows the implementation of IsObjectHandleTypeSafe() to rely on packed reference semantics, which are implemented as internal inline definitions. - Modified FLinkerLoad serialization of FObjectPtr values to register a packed object reference mapping for placeholder-typed objects. Note: This requires late resolve features to also be enabled. - Modified the TObjectPtr equality test to handle the case when only the RHS side is NULL. This can occur if the RHS is a non-UObject-type bound ptr to a placeholder object while the LHS is not. - Revised all object handle/ptr unit tests for type safety. Ensured that TObjectPtr stress tests are still in line both with/without type safety enabled. - Added a GObjectIndexToPackedObjectRefDebug global ptr to assist with debugger visualization of resolved placeholder-typed object references to allow for removal of RF_HasPlaceholderType. - Updated VS natvis file to revise queries for IsPlaceholderObject() on non-UObject-typed TObjectPtr values (removed ObjectFlags check and replaced w/ GObjectIndexToPackedObjectRefDebug). - Deprecated/removed all usage of RF_HasPlaceholderType (no longer required). - Removed FPropertyBagRepository::PropertyBagPlaceholderTypeRegistry and decoupled the current (static) API from the repo singleton instance. - (Minor) Fixed up some handle tracking unit tests in ObjectPropertyTest.cpp that did not compile with UE_WITH_OBJECT_HANDLE_LATE_RESOLVE disabled. #jira UE-212508, UE-197358 #rb Zousar.Shaker [CL 33397676 by phillip kavan in ue5-main branch]
2024-05-02 11:25:44 -04:00
#if UE_WITH_OBJECT_HANDLE_TRACKING && UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectHandle Handle = MakeUnresolvedHandle(Other);
FObjectHandle AbstractDerivedHandle = MakeUnresolvedHandle(AbstractDerivedOther);
TObjectPtr<UObjectPtrTestClass> ObjectPtr = *reinterpret_cast<TObjectPtr<UObjectPtrTestClass>*>(&Handle);
TObjectPtr<UObjectPtrAbstractDerivedTestClass> AbstractDerivedObjectPtr = *reinterpret_cast<TObjectPtr<UObjectPtrAbstractDerivedTestClass>*>(&AbstractDerivedHandle);
#else
TObjectPtr<UObjectPtrTestClass> ObjectPtr = Other;
TObjectPtr<UObjectPtrAbstractDerivedTestClass> AbstractDerivedObjectPtr = AbstractDerivedOther;
#endif
//property is already null should stay null
CHECK(!Obj->ObjectPtrNonNullable);
CHECK(!Obj->ObjectPtrAbstractNonNullable);
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, ObjectPtr);
AbstractProperty->CheckValidObject(&Obj->ObjectPtrAbstractNonNullable, AbstractDerivedObjectPtr);
CHECK(!Obj->ObjectPtrNonNullable);
CHECK(!Obj->ObjectPtrAbstractNonNullable);
//valid assignment
Obj->ObjectPtrNonNullable = ObjectPtr;
Obj->ObjectPtrAbstractNonNullable = AbstractDerivedObjectPtr;
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, nullptr);
AbstractProperty->CheckValidObject(&Obj->ObjectPtrAbstractNonNullable, nullptr);
CHECK(Obj->ObjectPtrNonNullable == ObjectPtr);
CHECK(Obj->ObjectPtrAbstractNonNullable == AbstractDerivedObjectPtr);
// Disable property warnings that will fire because we're deliberately setting non-nullable properties to null
LOG_SCOPE_VERBOSITY_OVERRIDE(LogProperty, ELogVerbosity::NoLogging);
using UE::CoreUObject::Private::ENonNullableBehavior;
using UE::CoreUObject::Private::GetNonNullableBehavior;
ENonNullableBehavior NonNullableBehavior = GetNonNullableBehavior();
bAllowRead = true; //has resolve the old value to construct a new default value for the property
//assign a bad value to the pointer
Obj->ObjectPtrNonNullable = reinterpret_cast<UObjectPtrTestClass*>(OtherTestPackage);
Obj->ObjectPtrAbstractNonNullable = reinterpret_cast<UObjectPtrAbstractTestClass*>(OtherTestPackage);
CHECK(Obj->ObjectPtrNonNullable != nullptr);
CHECK(Obj->ObjectPtrAbstractNonNullable != nullptr);
FWarnFilterScope _([](const TCHAR* Message, ELogVerbosity::Type Verbosity, const FName& Category)
{
if (Category == TEXT("LogProperty") && FCString::Strstr(Message, TEXT("Reference will be defaulted to")) && Verbosity == ELogVerbosity::Type::Warning)
{
return true;
}
return false;
});
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, ObjectPtr);
AbstractProperty->CheckValidObject(&Obj->ObjectPtrAbstractNonNullable, AbstractDerivedObjectPtr);
if (NonNullableBehavior == ENonNullableBehavior::CreateDefaultObjectIfPossible)
{
CHECK(Obj->ObjectPtrNonNullable == ObjectPtr); //non nullable properties should be assigned the old value
CHECK(Obj->ObjectPtrAbstractNonNullable == AbstractDerivedObjectPtr); //abstract non nullable properties should be assigned the old value
}
else
{
CHECK(Obj->ObjectPtrNonNullable == nullptr); //non nullable properties should be nulled if invalid
CHECK(Obj->ObjectPtrAbstractNonNullable == nullptr); //abstract non nullable properties should be nulled
}
//assign a bad value to the pointer
Obj->ObjectPtrNonNullable = reinterpret_cast<UObjectPtrTestClass*>(Obj);
Obj->ObjectPtrAbstractNonNullable = reinterpret_cast<UObjectPtrAbstractTestClass*>(Obj);
CHECK(Obj->ObjectPtrNonNullable != nullptr);
CHECK(Obj->ObjectPtrAbstractNonNullable != nullptr);
if (NonNullableBehavior == ENonNullableBehavior::CreateDefaultObjectIfPossible)
{
//new value is required for non nullable properties
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, nullptr);
AbstractProperty->CheckValidObject(&Obj->ObjectPtrAbstractNonNullable, nullptr);
CHECK(Obj->ObjectPtrNonNullable != nullptr);
CHECK(Obj->ObjectPtrNonNullable->IsA(UObjectPtrTestClass::StaticClass()));
CHECK(Obj->ObjectPtrAbstractNonNullable == nullptr);
}
else
{
//null is required for invalid non nullable properties
Property->CheckValidObject(&Obj->ObjectPtrNonNullable, nullptr);
AbstractProperty->CheckValidObject(&Obj->ObjectPtrAbstractNonNullable, nullptr);
CHECK(Obj->ObjectPtrNonNullable == nullptr);
CHECK(Obj->ObjectPtrAbstractNonNullable == nullptr);
}
}
class FMockArchive : public FArchive
{
public:
FMockArchive(UObject* Obj)
: ArchiveValue(Obj)
{
}
union Value
{
FObjectPtr ObjectPtrValue;
UObject* ObjectValue;
Value(UObject* Obj) : ObjectValue(Obj) {}
} ArchiveValue;
virtual FArchive& operator<<(FObjectPtr& Value) override
{
Value = ArchiveValue.ObjectPtrValue;
return *this;
}
virtual FArchive& operator<<(UObject*& Value) override
{
Value = ArchiveValue.ObjectValue;
return *this;
}
};
template<typename T>
static void TestSerializeItem(FName ObjectName)
{
UClass* Class = T::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(Property != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("/Temp/TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
T* Obj = NewObject<T>(TestPackage, ObjectName);
UObjectPtrTestClass* Other = NewObject<UObjectPtrTestClass>(Obj, TEXT("Other"));
ULinkerPlaceholderExportObject* PlaceHolderExport = NewObject<ULinkerPlaceholderExportObject>(TestPackage, TEXT("PlaceHolderExport"));
ULinkerPlaceholderClass* PlaceHolderClass = NewObject<ULinkerPlaceholderClass>(TestPackage, TEXT("PlaceHolderClass"));
PlaceHolderClass->Bind(); //must call bind or crashes on shutdown
int ResolveCount = 0;
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto CallbackId = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback([&ResolveCount](const FObjectRef&, UPackage*, UObject*)
{
++ResolveCount;
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(CallbackId);
};
#endif
{
//verify that if the property is null no reads are triggered
FMockArchive MockArchive(PlaceHolderExport);
FBinaryArchiveFormatter Formatter(MockArchive);
FStructuredArchive Ar(Formatter);
FStructuredArchiveSlot Slot = Ar.Open();
Property->SerializeItem(Slot, &Obj->ObjectPtr, nullptr);
CHECK(ResolveCount == 0);
CHECK(*reinterpret_cast<ULinkerPlaceholderExportObject**>(&Obj->ObjectPtr) == PlaceHolderExport);
}
{
//verify that if the property is not null no reads are triggered
FMockArchive MockArchive(PlaceHolderClass);
FBinaryArchiveFormatter Formatter(MockArchive);
FStructuredArchive Ar(Formatter);
FStructuredArchiveSlot Slot = Ar.Open();
Obj->ObjectPtr = Other;
Property->SerializeItem(Slot, &Obj->ObjectPtr, nullptr);
CHECK(ResolveCount == 0);
CHECK(*reinterpret_cast<ULinkerPlaceholderClass**>(&Obj->ObjectPtr) == PlaceHolderClass);
}
}
TEST_CASE("UE::CoreUObject::FObjectPtrProperty::StaticSerializeItem")
{
TestSerializeItem<UObjectPtrTestClassWithRef>(TEXT("Object1"));
}
TEST_CASE("UE::CoreUObject::FObjectProperty::StaticSerializeItem")
{
TestSerializeItem<UObjectWithRawProperty>(TEXT("Object2"));
}
class MockAssetRegistryInterface : public IAssetRegistryInterface
{
public:
MockAssetRegistryInterface()
: Old(IAssetRegistryInterface::Default)
{
IAssetRegistryInterface::Default = this;
}
virtual ~MockAssetRegistryInterface()
{
IAssetRegistryInterface::Default = Old;
}
virtual void GetDependencies(FName InPackageName, TArray<FName>& OutDependencies, UE::AssetRegistry::EDependencyCategory Category = UE::AssetRegistry::EDependencyCategory::Package, const UE::AssetRegistry::FDependencyQuery& Flags = UE::AssetRegistry::FDependencyQuery()) override
{
}
virtual UE::AssetRegistry::EExists TryGetAssetByObjectPath(const FSoftObjectPath& ObjectPath, FAssetData& OutAssetData) const override
{
const FAssetData* Found = AssetData.Find(ObjectPath);
if (Found)
{
OutAssetData = *Found;
return UE::AssetRegistry::EExists::Exists;
}
return UE::AssetRegistry::EExists::DoesNotExist;
}
virtual UE::AssetRegistry::EExists TryGetAssetPackageData(FName PackageName, FAssetPackageData& OutPackageData) const override
{
return UE::AssetRegistry::EExists::Exists;
}
Improves DoesPackageExistEx by enabling it to use the AssetRegistry when it's available Before Boot -> PIE FPackageName::DoesPackageExistEx called 191284 times takes 16.45s in total After Boot -> PIE FPackageName::DoesPackageExistEx called 191743 times takes 1.61s in total The majority of the 1.6s (1s) is from calling DoesPackageExistEx before the AssetRegistry has initialized and can provide a fast path. The 180K calls afterwards consume a total of 600ms compared to before, which took 15s. The original DoesPackageExistEx code checked for package existence on the filesystem, and at the same time found the case matching package name (the casing the file system uses for the package name). This information is known to the AssetRegistry so we can avoid the duplicate work by deferring to the AssetRegistry. The AssetRegistry needs to initialize itself, and previously did not expose a means to get the Case Matching FNames for Packages which are being used as the keys to AssetPackageData. We add a new overload to IAssetRegistryInterface::TryGetAssetPackageData to allow CoreUObject code to access the another new overload in the AssetRegistry::GetAssetPackageData which returns the case matching FName for the package as an out parameter. Since IAssetRegistryInterface can be used while the AssetRegistry doesn't exist or is still intializing, IAssetRegistryInterface::TryGetAssetPackageData will return UE::AssetRegistry::EExists::Unknown when the asset registry is not yet available. In the Unknown case, we fallback to the original FPackageName::DoesPackageExistEx codepath which reads from disk. I've made the (Try)GetAssetPackageData methods that only return FAssetPackageData* to use the new implementation that returns a FAssetPackageData* and populates an FName in order to reduce code duplication, however I'm open to changing this if the extra store is a concern. All paths perform the key search in the AssetRegistry's CachedAssetPackageData map. #jira UE-204062 #rb Matt.Peters [CL 31055136 by kevin macaulayvacher in ue5-main branch]
2024-01-31 13:06:38 -05:00
virtual UE::AssetRegistry::EExists TryGetAssetPackageData(FName PackageName, FAssetPackageData& OutPackageData, FName& OutCorrectCasePackageName) const override
{
return UE::AssetRegistry::EExists::Exists;
}
virtual bool EnumerateAssets(const FARFilter& Filter, TFunctionRef<bool(const FAssetData&)> Callback,
UE::AssetRegistry::EEnumerateAssetsFlags InEnumerateFlags) const override
{
return true;
}
IAssetRegistryInterface* Old;
TMap<FSoftObjectPath, FAssetData> AssetData;
};
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
TEST_CASE("UE::CoreUObject::FObjectProperty::ParseObjectPropertyValue")
{
UClass* Class = UObjectPtrTestClassWithRef::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
//make a fake entry for the asset registry
//this allows lazy load to return an unresolved handle
MockAssetRegistryInterface MockAssetRegistry;
const TCHAR* Text = TEXT("/TestPackageName.Other");
FSoftObjectPath Path(Text);
FAssetData AssetData;
AssetData.PackageName = "/TestPackageName";
AssetData.PackagePath = "/";
AssetData.AssetName = "Other";
AssetData.AssetClassPath.TrySetPath(UObjectPtrTestClass::StaticClass());
MockAssetRegistry.AssetData.Add(Path, AssetData);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("/Temp/TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
};
UObjectPtrTestClassWithRef* Obj = NewObject<UObjectPtrTestClassWithRef>(TestPackage, "ObjectName");
TObjectPtr<UObject> Result;
FObjectPropertyBase::ParseObjectPropertyValue(Property, Obj, UObjectPtrTestClass::StaticClass(), 0, Text, Result);
CHECK(Result != nullptr);
CHECK(!Result.IsResolved());
{
const TCHAR * Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ObjectPtr != nullptr);
CHECK(!Obj->ObjectPtr.IsResolved());
}
{
const TCHAR* Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
FProperty* ObjProperty = Class->FindPropertyByName(TEXT("ObjectPtr"));
ObjProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ObjectPtr == nullptr); //TODO this should not resolve and not be null
CHECK(Obj->ObjectPtr.IsResolved());
}
{
const TCHAR * Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
{
const TCHAR* Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
FProperty* ArrayProperty = Class->FindPropertyByName(TEXT("ArrayObjPtr"));
ArrayProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
{
const TCHAR * Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ObjectPtr != nullptr);
CHECK(!Obj->ObjectPtr.IsResolved());
}
{
const TCHAR* Buffer = TEXT("ObjectPtr=/TestPackageName.Other");
FProperty* ObjProperty = Class->FindPropertyByName(TEXT("ObjectPtr"));
ObjProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ObjectPtr == nullptr); //TODO this should not resolve and not be null
CHECK(Obj->ObjectPtr.IsResolved());
}
{
const TCHAR * Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
TArray<FDefinedProperty> DefinedProperties;
Buffer = FProperty::ImportSingleProperty(Buffer, Obj, Class, Obj, PPF_Delimited, nullptr, DefinedProperties);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
{
const TCHAR* Buffer = TEXT("ArrayObjPtr=(/TestPackageName.Other)");
FProperty* ArrayProperty = Class->FindPropertyByName(TEXT("ArrayObjPtr"));
ArrayProperty->ImportText_InContainer(Buffer, Obj, Obj, 0);
CHECK(Obj->ArrayObjPtr.Num() == 1);
CHECK(!Obj->ArrayObjPtr[0].IsResolved());
}
}
#endif
TEST_CASE("UE::FObjectProperty::Identical::ObjectPtr")
{
int ResolveCount = 0;
#if UE_WITH_OBJECT_HANDLE_TRACKING
auto CallbackHandle = UE::CoreUObject::AddObjectHandleReferenceResolvedCallback([&ResolveCount](const FObjectRef&, UPackage*, UObject*)
{
++ResolveCount;
});
ON_SCOPE_EXIT
{
UE::CoreUObject::RemoveObjectHandleReferenceResolvedCallback(CallbackHandle);
};
#endif
UClass* Class = UObjectPtrTestClassWithRef::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(Property != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("Test/TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
UPackage* TestPackage2 = NewObject<UPackage>(nullptr, TEXT("Test/TestPackageName2"), RF_Transient);
TestPackage2->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
TestPackage2->RemoveFromRoot();
};
TObjectPtr<UObject> ObjWithRef = NewObject<UObjectPtrTestClassWithRef>(TestPackage, TEXT("UObjectWithClassProperty"));
TObjectPtr<UObject> Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("UObjectPtrTestClass"));
TObjectPtr<UObject> Obj2 = NewObject<UObjectPtrTestClass>(TestPackage2, TEXT("UObjectPtrTestClass"));
Revise TObjectPtr to use pointer semantics for type safety checks (editor only). Notes: - This replaces the previous solution that relied on indirection. - This solution relies on packed ref handles which requires late resolve features to also be enabled. Change summary: - Modified FPropertyBagRepository::IsPropertyBagPlaceholderType()/IsPropertyBagPlaceholderObject() to make the parameter read-only. - Re-enabled UE_WITH_OBJECT_HANDLE_TYPE_SAFETY for the editor context. This will unblock testing of IDO w/ placeholder type features. - Modified UE::CoreUObject::Private::Pack() to include a new "type" trait bit in the packed object handle reference. If set, it will signal that the reference is also a placeholder-typed object. - Modified UE::CoreUObject::Private::MakePackedObjectRef() to include a check for whether or not the given object reference is also a placeholder-typed object, and allow this case to pass. - Added UE::CoreUObject::Private::IsObjectHandleTypeSafeNoReadNoCheck(). This allows the implementation of IsObjectHandleTypeSafe() to rely on packed reference semantics, which are implemented as internal inline definitions. - Modified FLinkerLoad serialization of FObjectPtr values to register a packed object reference mapping for placeholder-typed objects. Note: This requires late resolve features to also be enabled. - Modified the TObjectPtr equality test to handle the case when only the RHS side is NULL. This can occur if the RHS is a non-UObject-type bound ptr to a placeholder object while the LHS is not. - Revised all object handle/ptr unit tests for type safety. Ensured that TObjectPtr stress tests are still in line both with/without type safety enabled. - Added a GObjectIndexToPackedObjectRefDebug global ptr to assist with debugger visualization of resolved placeholder-typed object references to allow for removal of RF_HasPlaceholderType. - Updated VS natvis file to revise queries for IsPlaceholderObject() on non-UObject-typed TObjectPtr values (removed ObjectFlags check and replaced w/ GObjectIndexToPackedObjectRefDebug). - Deprecated/removed all usage of RF_HasPlaceholderType (no longer required). - Removed FPropertyBagRepository::PropertyBagPlaceholderTypeRegistry and decoupled the current (static) API from the repo singleton instance. - (Minor) Fixed up some handle tracking unit tests in ObjectPropertyTest.cpp that did not compile with UE_WITH_OBJECT_HANDLE_LATE_RESOLVE disabled. #jira UE-212508, UE-197358 #rb Zousar.Shaker [CL 33397676 by phillip kavan in ue5-main branch]
2024-05-02 11:25:44 -04:00
#if UE_WITH_OBJECT_HANDLE_TRACKING && UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectHandle Handle1 = MakeUnresolvedHandle(ObjWithRef.Get());
FObjectHandle Handle2 = MakeUnresolvedHandle(Obj1.Get());
FObjectHandle Handle3 = MakeUnresolvedHandle(Obj2.Get());
TObjectPtr<UObjectPtrTestClass> ObjectPtr = *reinterpret_cast<TObjectPtr<UObjectPtrTestClass>*>(&Handle1);
ObjWithRef = *reinterpret_cast<TObjectPtr<UObject>*>(&Handle1);
Obj1 = *reinterpret_cast<TObjectPtr<UObject>*>(&Handle2);
Obj2 = *reinterpret_cast<TObjectPtr<UObject>*>(&Handle3);
#endif
CHECK(Property->Identical(&Obj1, &Obj1, 0u));
CHECK(!Property->Identical(&Obj1, nullptr, 0u));
CHECK(!Property->Identical(nullptr, &Obj1, 0u));
CHECK(!Property->Identical(&ObjWithRef, &Obj2, 0u));
CHECK(!Property->Identical(&ObjWithRef, &Obj2, 0u));
CHECK(!Property->Identical(&Obj1, &ObjWithRef, PPF_DeepComparison));
CHECK(ResolveCount == 0);
CHECK(Property->Identical(&Obj1, &Obj2, PPF_DeepComparison));
Revise TObjectPtr to use pointer semantics for type safety checks (editor only). Notes: - This replaces the previous solution that relied on indirection. - This solution relies on packed ref handles which requires late resolve features to also be enabled. Change summary: - Modified FPropertyBagRepository::IsPropertyBagPlaceholderType()/IsPropertyBagPlaceholderObject() to make the parameter read-only. - Re-enabled UE_WITH_OBJECT_HANDLE_TYPE_SAFETY for the editor context. This will unblock testing of IDO w/ placeholder type features. - Modified UE::CoreUObject::Private::Pack() to include a new "type" trait bit in the packed object handle reference. If set, it will signal that the reference is also a placeholder-typed object. - Modified UE::CoreUObject::Private::MakePackedObjectRef() to include a check for whether or not the given object reference is also a placeholder-typed object, and allow this case to pass. - Added UE::CoreUObject::Private::IsObjectHandleTypeSafeNoReadNoCheck(). This allows the implementation of IsObjectHandleTypeSafe() to rely on packed reference semantics, which are implemented as internal inline definitions. - Modified FLinkerLoad serialization of FObjectPtr values to register a packed object reference mapping for placeholder-typed objects. Note: This requires late resolve features to also be enabled. - Modified the TObjectPtr equality test to handle the case when only the RHS side is NULL. This can occur if the RHS is a non-UObject-type bound ptr to a placeholder object while the LHS is not. - Revised all object handle/ptr unit tests for type safety. Ensured that TObjectPtr stress tests are still in line both with/without type safety enabled. - Added a GObjectIndexToPackedObjectRefDebug global ptr to assist with debugger visualization of resolved placeholder-typed object references to allow for removal of RF_HasPlaceholderType. - Updated VS natvis file to revise queries for IsPlaceholderObject() on non-UObject-typed TObjectPtr values (removed ObjectFlags check and replaced w/ GObjectIndexToPackedObjectRefDebug). - Deprecated/removed all usage of RF_HasPlaceholderType (no longer required). - Removed FPropertyBagRepository::PropertyBagPlaceholderTypeRegistry and decoupled the current (static) API from the repo singleton instance. - (Minor) Fixed up some handle tracking unit tests in ObjectPropertyTest.cpp that did not compile with UE_WITH_OBJECT_HANDLE_LATE_RESOLVE disabled. #jira UE-212508, UE-197358 #rb Zousar.Shaker [CL 33397676 by phillip kavan in ue5-main branch]
2024-05-02 11:25:44 -04:00
#if UE_WITH_OBJECT_HANDLE_TRACKING && UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
CHECK(ResolveCount == 2);
#endif
}
TEST_CASE("UE::FObjectProperty::Identical::Object")
{
UClass* Class = UObjectWithRawProperty::StaticClass();
FObjectProperty* Property = CastField<FObjectProperty>(Class->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(Property != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("Test/TestPackageName"), RF_Transient);
TestPackage->AddToRoot();
UPackage* TestPackage2 = NewObject<UPackage>(nullptr, TEXT("Test/TestPackageName2"), RF_Transient);
TestPackage2->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
TestPackage2->RemoveFromRoot();
};
TObjectPtr<UObject> ObjWithRef = NewObject<UObjectWithRawProperty>(TestPackage, TEXT("UObjectWithRawProperty"));
TObjectPtr<UObject> Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("UObjectPtrTestClass"));
TObjectPtr<UObject> Obj2 = NewObject<UObjectPtrTestClass>(TestPackage2, TEXT("UObjectPtrTestClass"));
CHECK(Property->Identical(&Obj1, &Obj1, 0u));
CHECK(!Property->Identical(&Obj1, nullptr, 0u));
CHECK(!Property->Identical(nullptr, &Obj1, 0u));
CHECK(!Property->Identical(&ObjWithRef, &Obj2, 0u));
CHECK(!Property->Identical(&ObjWithRef, &Obj2, 0u));
CHECK(!Property->Identical(&Obj1, &ObjWithRef, PPF_DeepComparison));
CHECK(Property->Identical(&Obj1, &Obj2, PPF_DeepComparison));
}
TEST_CASE("UE::FObjectProperty::CopySingleValue")
{
FObjectProperty* RawProperty = CastField<FObjectProperty>(UObjectWithRawProperty::StaticClass()->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(RawProperty != nullptr);
FObjectProperty* PtrProperty = CastField<FObjectProperty>(UObjectPtrTestClassWithRef::StaticClass()->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(PtrProperty != nullptr);
UPackage* TestPackage = NewObject<UPackage>(nullptr, TEXT("Test/CopySingleValue"), RF_Transient);
TestPackage->AddToRoot();
UPackage* TestPackage2 = NewObject<UPackage>(nullptr, TEXT("Test/CopySingleValue2"), RF_Transient);
TestPackage2->AddToRoot();
ON_SCOPE_EXIT
{
TestPackage->RemoveFromRoot();
TestPackage2->RemoveFromRoot();
};
TObjectPtr<UObject> ObjWithRawRef = NewObject<UObjectWithRawProperty>(TestPackage, TEXT("UObjectWithRawProperty"));
TObjectPtr<UObject> ObjWithPtrRef = NewObject<UObjectPtrTestClassWithRef>(TestPackage, TEXT("UObjectWithPtrProperty"));
UObject* Obj1 = NewObject<UObjectPtrTestClass>(TestPackage, TEXT("UObjectPtrTestClass"));
UObject* Obj2 = NewObject<UObjectPtrTestClass>(TestPackage2, TEXT("UObjectPtrTestClass2"));
UObject* RawPtr = Obj1;
TObjectPtr<UObject> PtrObj2 = Obj2;
Revise TObjectPtr to use pointer semantics for type safety checks (editor only). Notes: - This replaces the previous solution that relied on indirection. - This solution relies on packed ref handles which requires late resolve features to also be enabled. Change summary: - Modified FPropertyBagRepository::IsPropertyBagPlaceholderType()/IsPropertyBagPlaceholderObject() to make the parameter read-only. - Re-enabled UE_WITH_OBJECT_HANDLE_TYPE_SAFETY for the editor context. This will unblock testing of IDO w/ placeholder type features. - Modified UE::CoreUObject::Private::Pack() to include a new "type" trait bit in the packed object handle reference. If set, it will signal that the reference is also a placeholder-typed object. - Modified UE::CoreUObject::Private::MakePackedObjectRef() to include a check for whether or not the given object reference is also a placeholder-typed object, and allow this case to pass. - Added UE::CoreUObject::Private::IsObjectHandleTypeSafeNoReadNoCheck(). This allows the implementation of IsObjectHandleTypeSafe() to rely on packed reference semantics, which are implemented as internal inline definitions. - Modified FLinkerLoad serialization of FObjectPtr values to register a packed object reference mapping for placeholder-typed objects. Note: This requires late resolve features to also be enabled. - Modified the TObjectPtr equality test to handle the case when only the RHS side is NULL. This can occur if the RHS is a non-UObject-type bound ptr to a placeholder object while the LHS is not. - Revised all object handle/ptr unit tests for type safety. Ensured that TObjectPtr stress tests are still in line both with/without type safety enabled. - Added a GObjectIndexToPackedObjectRefDebug global ptr to assist with debugger visualization of resolved placeholder-typed object references to allow for removal of RF_HasPlaceholderType. - Updated VS natvis file to revise queries for IsPlaceholderObject() on non-UObject-typed TObjectPtr values (removed ObjectFlags check and replaced w/ GObjectIndexToPackedObjectRefDebug). - Deprecated/removed all usage of RF_HasPlaceholderType (no longer required). - Removed FPropertyBagRepository::PropertyBagPlaceholderTypeRegistry and decoupled the current (static) API from the repo singleton instance. - (Minor) Fixed up some handle tracking unit tests in ObjectPropertyTest.cpp that did not compile with UE_WITH_OBJECT_HANDLE_LATE_RESOLVE disabled. #jira UE-212508, UE-197358 #rb Zousar.Shaker [CL 33397676 by phillip kavan in ue5-main branch]
2024-05-02 11:25:44 -04:00
#if UE_WITH_OBJECT_HANDLE_TRACKING && UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
FObjectHandle Handle = MakeUnresolvedHandle(Obj2);
PtrObj2 = TObjectPtr<UObject>(FObjectPtr(Handle) );
#endif
//copy an unresolved TObjectPtr to an UObject* pointer. this should resolve the pointer
RawProperty->CopySingleValue(&RawPtr, &PtrObj2);
CHECK(RawPtr == Obj2);
PtrObj2 = nullptr;
//copy an UObject* to a TObjectPtr
PtrProperty->CopySingleValue(&PtrObj2, &RawPtr);
CHECK(PtrObj2 == RawPtr);
CHECK(RawProperty->GetClass() == PtrProperty->GetClass());
}
TEST_CASE("UE::FObjectProperty::GetCPPType")
{
FObjectProperty* RawProperty = CastField<FObjectProperty>(UObjectWithRawProperty::StaticClass()->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(RawProperty != nullptr);
FObjectProperty* PtrProperty = CastField<FObjectProperty>(UObjectPtrTestClassWithRef::StaticClass()->FindPropertyByName(TEXT("ObjectPtr")));
REQUIRE(PtrProperty != nullptr);
FString RawType = RawProperty->GetCPPType(nullptr, 0u);
CHECK(RawType == TEXT("UObjectPtrTestClass*"));
RawType = RawProperty->GetCPPType(nullptr, EPropertyExportCPPFlags::CPPF_NoTObjectPtr);
CHECK(RawType == TEXT("UObjectPtrTestClass*"));
FString PtrType = PtrProperty->GetCPPType(nullptr, 0u);
CHECK(PtrType == TEXT("TObjectPtr<UObjectPtrTestClass>"));
PtrType = PtrProperty->GetCPPType(nullptr, EPropertyExportCPPFlags::CPPF_NoTObjectPtr);
CHECK(PtrType == TEXT("UObjectPtrTestClass*"));
}
TEST_CASE("UE::FObjectProperty::ArrayProperty")
{
FArrayProperty* PtrProperty = CastField<FArrayProperty>(UObjectPtrTestClassWithRef::StaticClass()->FindPropertyByName(TEXT("ArrayObjPtr")));
REQUIRE(PtrProperty != nullptr);
}
#endif