Files
UnrealEngineUWP/Engine/Source/Runtime/CoreUObject/Tests/ObjectPtrTestClass.cpp
phillip kavan c8f19f844b [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

197 lines
6.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#if WITH_LOW_LEVEL_TESTS
#include "ObjectPtrTestClass.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "UObject/MetaData.h"
#include "UObject/UnrealType.h"
#include "UObject/UObjectGlobals.h"
//can not put the #if inside as the expansion of IMPLEMENT_CORE_INTRINSIC_CLASS fails
#if WITH_EDITORONLY_DATA
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectPtrTestClass, UObject,
{
auto MetaData = Class->GetOutermost()->GetMetaData();
if (MetaData)
{
MetaData->SetValue(Class, TEXT("LoadBehavior"), TEXT("LazyOnDemand"));
}
}
);
#else
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectPtrTestClass, UObject,
{
});
#endif
#if WITH_EDITORONLY_DATA
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectPtrTestClassWithRef, UObject,
{
//add reflection info for the two properties
{
UECodeGen_Private::FObjectPropertyParams Params = { };
Params.NameUTF8 = "ObjectPtr";
Params.Offset = STRUCT_OFFSET(UObjectPtrTestClassWithRef, ObjectPtr);
Params.PropertyFlags = CPF_TObjectPtrWrapper;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
auto Property = new FObjectProperty(Class, Params );
Property->PropertyClass = UObjectPtrTestClass::StaticClass();
}
{
UECodeGen_Private::FObjectPropertyParams Params = { };
Params.NameUTF8 = "ObjectPtrNonNullable";
Params.Offset = STRUCT_OFFSET(UObjectPtrTestClassWithRef, ObjectPtrNonNullable);
Params.PropertyFlags = EPropertyFlags::CPF_NonNullable | CPF_TObjectPtrWrapper;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
auto Property = new FObjectProperty(Class, Params);
Property->PropertyClass = UObjectPtrTestClass::StaticClass();
}
{
UECodeGen_Private::FArrayPropertyParams Params = { };
Params.NameUTF8 = "ArrayObjPtr";
Params.Offset = STRUCT_OFFSET(UObjectPtrTestClassWithRef, ArrayObjPtr);
Params.PropertyFlags = CPF_None;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
auto Property = new FArrayProperty(Class, Params);
auto InnerProperty = new FObjectProperty(Property, TEXT("Inner"), EObjectFlags::RF_NoFlags);
InnerProperty->PropertyClass = UObjectPtrTestClass::StaticClass();
Property->AddCppProperty(InnerProperty);
}
auto MetaData = Class->GetOutermost()->GetMetaData();
if (MetaData)
{
MetaData->SetValue(Class, TEXT("LoadBehavior"), TEXT("LazyOnDemand"));
}
}
);
#else
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectPtrTestClassWithRef, UObject,
{
{
UECodeGen_Private::FObjectPropertyParams Params = { };
Params.NameUTF8 = "ObjectPtr";
Params.Offset = STRUCT_OFFSET(UObjectPtrTestClassWithRef, ObjectPtr);
Params.PropertyFlags = CPF_TObjectPtrWrapper;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
auto Property = new FObjectProperty(Class, Params);
Property->PropertyClass = UObjectPtrTestClass::StaticClass();
}
{
UECodeGen_Private::FObjectPropertyParams Params = { };
Params.NameUTF8 = "ObjectPtrNonNullable";
Params.Offset = STRUCT_OFFSET(UObjectPtrTestClassWithRef, ObjectPtrNonNullable);
Params.PropertyFlags = EPropertyFlags::CPF_NonNullable | CPF_TObjectPtrWrapper;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
auto Property = new FObjectProperty(Class, Params);
Property->PropertyClass = UObjectPtrTestClass::StaticClass();
}
{
UECodeGen_Private::FArrayPropertyParams Params = { };
Params.NameUTF8 = "ArrayObjPtr";
Params.Offset = STRUCT_OFFSET(UObjectPtrTestClassWithRef, ArrayObjPtr);
Params.PropertyFlags = CPF_None;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
auto Property = new FArrayProperty(Class, Params);
auto InnerProperty = new FObjectProperty(Property, TEXT("Inner"), EObjectFlags::RF_NoFlags);
InnerProperty->PropertyClass = UObjectPtrTestClass::StaticClass();
Property->AddCppProperty(InnerProperty);
}
});
#endif
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectWithClassProperty, UObject,
{
{
UECodeGen_Private::FClassPropertyParams Params = { };
Params.NameUTF8 = "ClassPtr";
Params.Offset = STRUCT_OFFSET(UObjectWithClassProperty, ClassPtr);
Params.PropertyFlags = CPF_TObjectPtrWrapper;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
Params.MetaClassFunc = []()
{
return UClass::StaticClass();
};
auto Property = new FClassProperty(Class, Params);
Property->PropertyClass = UObject::StaticClass();
}
{
UECodeGen_Private::FClassPropertyParams Params = { };
Params.NameUTF8 = "ClassRaw";
Params.Offset = STRUCT_OFFSET(UObjectWithClassProperty, ClassRaw);
Params.PropertyFlags = EPropertyFlags::CPF_None;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
Params.MetaClassFunc = []()
{
return UClass::StaticClass();
};
auto Property = new FClassProperty(Class, Params);
Property->PropertyClass = UObject::StaticClass();
}
{
UECodeGen_Private::FClassPropertyParams Params = { };
Params.NameUTF8 = "SubClass";
Params.Offset = STRUCT_OFFSET(UObjectWithClassProperty, SubClass);
Params.PropertyFlags = EPropertyFlags::CPF_UObjectWrapper;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
Params.MetaClassFunc = []()
{
return UObjectPtrTestClass::StaticClass();
};
auto Property = new FClassProperty(Class, Params);
Property->PropertyClass = UObject::StaticClass();
}
}
);
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectWithRawProperty, UObject,
{
{
UECodeGen_Private::FObjectPropertyParams Params = { };
Params.NameUTF8 = "ObjectPtr";
Params.Offset = STRUCT_OFFSET(UObjectWithRawProperty, ObjectPtr);
Params.PropertyFlags = CPF_None;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
auto Property = new FObjectProperty(Class, Params);
Property->PropertyClass = UObjectPtrTestClass::StaticClass();
}
{
UECodeGen_Private::FObjectPropertyParams Params = { };
Params.NameUTF8 = "ObjectPtrNonNullable";
Params.Offset = STRUCT_OFFSET(UObjectWithRawProperty, ObjectPtrNonNullable);
Params.PropertyFlags = EPropertyFlags::CPF_NonNullable;
Params.ObjectFlags = RF_Public | RF_Transient | RF_MarkAsNative;
Params.ClassFunc = nullptr;
auto Property = new FObjectProperty(Class, Params);
Property->PropertyClass = UObjectPtrTestClass::StaticClass();
}
}
);
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectPtrDerrivedTestClass, UObjectPtrTestClass, {});
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectPtrNotLazyTestClass, UObject, {});
IMPLEMENT_CORE_INTRINSIC_CLASS(UObjectPtrStressTestClass, UObject, {});
#endif