vault backup: 2024-10-12 17:19:45
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# MatchedSerializers
|
||||
|
||||
- **功能描述:** 指定类支持文本结构序列化
|
||||
- **引擎模块:** Serialization
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_MatchedSerializers](../../../../Flags/EClassFlags/CLASS_MatchedSerializers.md),在Meta中添加[MatchedSerializers](../../../../Meta/Serialization/MatchedSerializers.md)
|
||||
- **常用程度:** 0
|
||||
|
||||
该标识符只允许在NoExportTypes.h中使用,属于是引擎自用的内部标识符。
|
||||
|
||||
基本上大部分的类都拥有该标记,除了自身不导出的类,一般包括NoExportTypes.h定义的(除非手动加上MatchedSerializers,比如UObject),或者靠DECLARE_CLASS_INTRINSIC直接在源码里定义的元数据。
|
||||
|
||||
因此实际上大部分的类都拥有该标记。因为在UHT中只要不是NoExport的,就会自动的加上这个标记。
|
||||
|
||||
```cpp
|
||||
// Force the MatchedSerializers on for anything being exported
|
||||
if (!ClassExportFlags.HasAnyFlags(UhtClassExportFlags.NoExport))
|
||||
{
|
||||
ClassFlags |= EClassFlags.MatchedSerializers;
|
||||
}
|
||||
```
|
||||
|
||||
## 结构化序列化器:
|
||||
|
||||
如果一个类支持文本格式,则StructuredArchive的结构的意思是会把类里的字段树形展开来序列化展示出来,从而方便人类理解。而如果不支持文本格式,则会把所有的字段值压进一个二进制buffer里(Data字段),这也是runtime时候用的方式。
|
||||
|
||||
测试代码:
|
||||
|
||||
```cpp
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType,editinlinenew)
|
||||
class INSIDER_API UMyClass_MatchedSerializersSub :public UObject
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Default = 123;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_MatchedSerializersTestAsset:public UDataAsset
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Default = 123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,Instanced)
|
||||
UMyClass_MatchedSerializersSub* SubObject;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UStruct* MyStructType;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UClass* MyClassType;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UEnum* MyEnumType;
|
||||
};
|
||||
|
||||
void UMyClass_MatchedSerializers_Test::ApplyClassFlag()
|
||||
{
|
||||
UMyClass_MatchedSerializersTestAsset::StaticClass()->ClassFlags |= CLASS_MatchedSerializers;
|
||||
UMyClass_MatchedSerializersSub::StaticClass()->ClassFlags |= CLASS_MatchedSerializers;
|
||||
}
|
||||
|
||||
void UMyClass_MatchedSerializers_Test::RemoveClassFlag()
|
||||
{
|
||||
UMyClass_MatchedSerializersTestAsset::StaticClass()->ClassFlags &= ~CLASS_MatchedSerializers;
|
||||
UMyClass_MatchedSerializersSub::StaticClass()->ClassFlags &= ~CLASS_MatchedSerializers;
|
||||
}
|
||||
```
|
||||
|
||||
在编辑器中创建测试数据Asset
|
||||
|
||||

|
||||
|
||||
然后在Editor选项里打开TextAssetFormatSupport(UEditorExperimentalSettings::bTextAssetFormatSupport)
|
||||
|
||||

|
||||
|
||||
然后在资产上就出现3个菜单支持把资产导出为文本。
|
||||
|
||||

|
||||
|
||||
ExportToTextFormat会在蓝图资产的同目录生成一个.utxt的文件,格式为json。通过动态的增删CLASS_MatchedSerializers这个标记来对比这个标记产生的差异:
|
||||
|
||||

|
||||
|
||||
可以发现,序列化出来的内容有明显的差异,不带有CLASS_MatchedSerializers标记的产生的右侧结果,把所有的字段值压进一个二进制buffer里(Data字段)。
|
||||
|
||||
## 内部机制原理:
|
||||
|
||||
CLASS_MatchedSerializers这个标记在UClass::IsSafeToSerializeToStructuredArchives中被使用,标明采用结构序列化器。是否支持文本导入导出,只在编辑器情况下使用。
|
||||
|
||||
在发生作用的只有SavePackage2.cpp和LinkerLoad.cpp,因此是只发生在保存UPackage的时候,作为子类对象。所以不能用简单的内存里Archive序列化来进行测试。
|
||||
|
||||
```cpp
|
||||
bool UClass::IsSafeToSerializeToStructuredArchives(UClass* InClass)
|
||||
{
|
||||
while (InClass)
|
||||
{
|
||||
if (!InClass->HasAnyClassFlags(CLASS_MatchedSerializers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
InClass = InClass->GetSuperClass();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//LinkerLoad.cpp
|
||||
bool bClassSupportsTextFormat = UClass::IsSafeToSerializeToStructuredArchives(Object->GetClass());
|
||||
if (IsTextFormat())//如果Ar序列化是文本格式
|
||||
{
|
||||
FStructuredArchiveSlot ExportSlot = GetExportSlot(Export.ThisIndex);
|
||||
|
||||
if (bClassSupportsTextFormat) //如果类本身支持文本格式
|
||||
{
|
||||
Object->GetClass()->SerializeDefaultObject(Object, ExportSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
FStructuredArchiveChildReader ChildReader(ExportSlot);
|
||||
FArchiveUObjectFromStructuredArchive Adapter(ChildReader.GetRoot());
|
||||
Object->GetClass()->SerializeDefaultObject(Object, Adapter.GetArchive());
|
||||
}
|
||||
}
|
||||
|
||||
//SavePackage2.cpp
|
||||
#if WITH_EDITOR
|
||||
bool bSupportsText = UClass::IsSafeToSerializeToStructuredArchives(Export.Object->GetClass());
|
||||
#else
|
||||
bool bSupportsText = false;
|
||||
#endif
|
||||
|
||||
if (bSupportsText)
|
||||
{
|
||||
Export.Object->GetClass()->SerializeDefaultObject(Export.Object, ExportSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
FArchiveUObjectFromStructuredArchive Adapter(ExportSlot);
|
||||
Export.Object->GetClass()->SerializeDefaultObject(Export.Object, Adapter.GetArchive());
|
||||
Adapter.Close();
|
||||
}
|
||||
```
|
||||
|
||||
文本格式只在编辑器环境下生效。
|
||||
|
||||
可以从源码看到,如果类本身支持文本格式序列化,则在Ar是文本格式的时候,直接可以序列化,采用默认的SerializeTaggedProperties。否则得采用FArchiveUObjectFromStructuredArchive 来适配一下,把对象指针转换为object path+ int32 Index的组合。
|
||||
|
||||
在引擎中打印出所有包含或不包含CLASS_MatchedSerializers的类,发现UStruct继承链下面的类开始包含(但是UClass却不包含),而上面UField的类则不包含,比如各种Property。类列表见Doc下txt文件。
|
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,8 @@
|
||||
# NonTransient
|
||||
|
||||
- **功能描述:** 使继承自基类的Transient说明符无效。
|
||||
- **引擎模块:** Serialization
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中移除[CLASS_Transient](../../../Flags/EClassFlags/CLASS_Transient.md)
|
||||
- **关联项:** [Transient](Transient/Transient.md)
|
||||
- **常用程度:** ★★★
|
@@ -0,0 +1,275 @@
|
||||
# Optional
|
||||
|
||||
- **功能描述:** 标记该类的对象是可选的,在Cooking的时候可以选择是否要忽略保存它们。
|
||||
|
||||
- **引擎模块:** Serialization
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_Optional](../../../../Flags/EClassFlags/CLASS_Optional.md)
|
||||
- **常用程度:** ★
|
||||
|
||||
标记该类的对象是可选的,在Cooking的时候可以选择是否要忽略保存它们。
|
||||
|
||||
- 一般为EditorOnly的数据,如MetaData等,在游戏运行时不存在,保存在其他的特定文件中。
|
||||
- Optional的对象一般也包在WITH_EDITORONLY_DATA宏里,只在编辑器下使用。
|
||||
- 引擎在cook的时候,会根据EDITOROPTIONAL的配置来加上SAVE_Optional,从而选择是否一起序列化该对象值,比如metadata。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
//ClassFlags: CLASS_Optional | CLASS_MatchedSerializers | CLASS_Native | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
UCLASS(Optional)
|
||||
class INSIDER_API UMyClass_Optional :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyClass_NotOptional :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyClass_Optional_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
UPROPERTY()
|
||||
UMyClass_Optional* MyOptionalObject;
|
||||
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
UMyClass_NotOptional* MyNotOptionalObject;
|
||||
public:
|
||||
static void CreatePackageAndSave();
|
||||
static void LoadPackageAndTest();
|
||||
};
|
||||
|
||||
void UMyClass_Optional_Test::CreatePackageAndSave()
|
||||
{
|
||||
FString packageName = TEXT("/Game/MyOptionTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
|
||||
IFileManager::Get().Delete(*assetPath, false, true);
|
||||
|
||||
UPackage* package = CreatePackage(*packageName);
|
||||
FSavePackageArgs saveArgs{};
|
||||
//saveArgs.TopLevelFlags = EObjectFlags::RF_Public | EObjectFlags::RF_Standalone;
|
||||
saveArgs.Error = GError;
|
||||
saveArgs.SaveFlags=SAVE_NoError;
|
||||
|
||||
//SAVE_Optional = 0x00008000, ///< Indicate that we to save optional exports. This flag is only valid while cooking. Optional exports are filtered if not specified during cooking.
|
||||
|
||||
UMyClass_Optional_Test* testObject = NewObject<UMyClass_Optional_Test>(package, TEXT("testObject"));
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
testObject->MyOptionalObject = NewObject<UMyClass_Optional>(testObject, TEXT("MyOptionalObject"));
|
||||
testObject->MyOptionalObject->MyProperty = 456;
|
||||
#endif
|
||||
|
||||
testObject->MyNotOptionalObject = NewObject<UMyClass_NotOptional>(testObject, TEXT("MyNotOptionalObject"));
|
||||
|
||||
testObject->MyNotOptionalObject->MyProperty = 456;
|
||||
|
||||
FString str = UInsiderSubsystem::Get().PrintObject(package, EInsiderPrintFlags::All);
|
||||
FString str2 = UInsiderSubsystem::Get().PrintObject(testObject, EInsiderPrintFlags::All);
|
||||
FString str3 = UInsiderSubsystem::Get().PrintObject(UMyClass_Optional::StaticClass(), EInsiderPrintFlags::All);
|
||||
FString str4 = UInsiderSubsystem::Get().PrintObject(UMyClass_NotOptional::StaticClass(), EInsiderPrintFlags::All);
|
||||
|
||||
bool result = UPackage::SavePackage(package, testObject, *assetPath, saveArgs);
|
||||
|
||||
}
|
||||
|
||||
void UMyClass_Optional_Test::LoadPackageAndTest()
|
||||
{
|
||||
FString packageName = TEXT("/Game/MyOptionTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
|
||||
UPackage* package = LoadPackage(nullptr, *assetPath, LOAD_None);
|
||||
package->FullyLoad();
|
||||
|
||||
UMyClass_Optional_Test* newTestObject = LoadObject<UMyClass_Optional_Test>(package, TEXT("testObject"), *assetPath);
|
||||
//UMyClass_Transient_Test* newTestObject = nullptr;
|
||||
|
||||
/*const TArray<FObjectExport>& exportMap = package->GetLinker()->ExportMap;
|
||||
for (const auto& objExport : exportMap)
|
||||
{
|
||||
if (objExport.ObjectName == TEXT("testObject"))
|
||||
{
|
||||
newTestObject = Cast<UMyClass_Transient_Test>(objExport.Object);
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
FString str = UInsiderSubsystem::Get().PrintObject(package, EInsiderPrintFlags::All);
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
正常的SavePackage发现是没有作用的,依然会序列化保存。特殊的保存方式在Cook阶段,本例就没有专门测试了。
|
||||
|
||||

|
||||
|
||||
在源码里搜索Optional,可以看到一般是EditorOnlyData和CookedMetaData类在使用。
|
||||
|
||||
```cpp
|
||||
UCLASS(Optional, Within=Enum)
|
||||
class ENGINE_API UEnumCookedMetaData : public UObject
|
||||
UCLASS(Optional, Within=ScriptStruct)
|
||||
class ENGINE_API UStructCookedMetaData : public UObject
|
||||
UCLASS(Optional, Within=Class)
|
||||
class ENGINE_API UClassCookedMetaData : public UObject
|
||||
|
||||
UMaterialInterfaceEditorOnlyData* UMaterialInterface::CreateEditorOnlyData()
|
||||
{
|
||||
const UClass* EditorOnlyClass = GetEditorOnlyDataClass();
|
||||
check(EditorOnlyClass);
|
||||
check(EditorOnlyClass->HasAllClassFlags(CLASS_Optional));
|
||||
|
||||
const FString EditorOnlyName = MaterialInterface::GetEditorOnlyDataName(*GetName());
|
||||
const EObjectFlags EditorOnlyFlags = GetMaskedFlags(RF_PropagateToSubObjects);
|
||||
return NewObject<UMaterialInterfaceEditorOnlyData>(this, EditorOnlyClass, *EditorOnlyName, EditorOnlyFlags);
|
||||
}
|
||||
```
|
||||
|
||||
引擎里也有一些验证:
|
||||
|
||||
```cpp
|
||||
UnrealTypeDefinitionInfo.cpp:
|
||||
// Validate if we are using editor only data in a class or struct definition
|
||||
if (HasAnyClassFlags(CLASS_Optional))
|
||||
{
|
||||
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : GetProperties())
|
||||
{
|
||||
if (PropertyDef->GetPropertyBase().IsEditorOnlyProperty())
|
||||
{
|
||||
PropertyDef->LogError(TEXT("Cannot specify editor only property inside an optional class."));
|
||||
}
|
||||
else if (PropertyDef->GetPropertyBase().ContainsEditorOnlyProperties())
|
||||
{
|
||||
PropertyDef->LogError(TEXT("Do not specify struct property containing editor only properties inside an optional class."));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过源码发现:
|
||||
|
||||
//SAVE_Optional = 0x00008000, ///< Indicate that we to save optional exports. This flag is only valid while cooking. Optional exports are filtered if not specified during cooking.
|
||||
|
||||
这个SAVE_Optional 作用于UUserDefinedEnum,UUserDefinedStruct,UBlueprintGeneratedClass的MetaData对象上。
|
||||
|
||||
```cpp
|
||||
void UUserDefinedStruct::PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext)
|
||||
{
|
||||
Super::PreSaveRoot(ObjectSaveContext);
|
||||
|
||||
if (ObjectSaveContext.IsCooking() && (ObjectSaveContext.GetSaveFlags() & SAVE_Optional))
|
||||
{
|
||||
//这个对象是以this为Outer的,标记RF_Standalone | RF_Public,会造成该子对象被序列化下来
|
||||
UStructCookedMetaData* CookedMetaData = NewCookedMetaData();
|
||||
CookedMetaData->CacheMetaData(this);
|
||||
|
||||
if (!CookedMetaData->HasMetaData())
|
||||
{
|
||||
PurgeCookedMetaData();//清理掉这个CookedMetaData对象
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PurgeCookedMetaData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另外,在cook的时候,如果指定
|
||||
|
||||
bCookEditorOptional = Switches.Contains(TEXT("EDITOROPTIONAL")); // Produce the optional editor package data alongside the cooked data.
|
||||
|
||||
则会加上CookEditorOptional 的标识
|
||||
|
||||
CookFlags |= bCookEditorOptional ? ECookInitializationFlags::CookEditorOptional : ECookInitializationFlags::None;
|
||||
|
||||
再之后则会传达SAVE_Optional 给Package的SaveFlags
|
||||
|
||||
SaveFlags |= COTFS.IsCookFlagSet(ECookInitializationFlags::CookEditorOptional) ? SAVE_Optional : SAVE_None;
|
||||
|
||||
从而在包括Package的时候,IsSaveOptional()的判断会造成是否创建Optional的Realm,
|
||||
|
||||
```cpp
|
||||
TArray<ESaveRealm> FSaveContext::GetHarvestedRealmsToSave()
|
||||
{
|
||||
TArray<ESaveRealm> HarvestedContextsToSave;
|
||||
if (IsCooking())
|
||||
{
|
||||
HarvestedContextsToSave.Add(ESaveRealm::Game);
|
||||
if (IsSaveOptional())
|
||||
{
|
||||
HarvestedContextsToSave.Add(ESaveRealm::Optional);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HarvestedContextsToSave.Add(ESaveRealm::Editor);
|
||||
}
|
||||
return HarvestedContextsToSave;
|
||||
}
|
||||
```
|
||||
|
||||
还有如果发现Object有CLASS_Optional,则不把它当做Export(子对象),而是当做Import(引用的对象),Optional对象有可能放在外部独立的文件中。
|
||||
|
||||
```cpp
|
||||
ESavePackageResult HarvestPackage(FSaveContext& SaveContext)
|
||||
{
|
||||
// If we have a valid optional context and we are saving it,
|
||||
// transform any harvested non optional export into imports
|
||||
// Mark other optional import package as well
|
||||
if (!SaveContext.IsSaveAutoOptional() &&
|
||||
SaveContext.IsSaveOptional() &&
|
||||
SaveContext.IsCooking() &&
|
||||
SaveContext.GetHarvestedRealm(ESaveRealm::Optional).GetExports().Num() &&
|
||||
SaveContext.GetHarvestedRealm(ESaveRealm::Game).GetExports().Num())
|
||||
{
|
||||
bool bHasNonOptionalSelfReference = false;
|
||||
FHarvestedRealm& OptionalContext = SaveContext.GetHarvestedRealm(ESaveRealm::Optional);
|
||||
for (auto It = OptionalContext.GetExports().CreateIterator(); It; ++It)
|
||||
{
|
||||
if (!It->Obj->GetClass()->HasAnyClassFlags(CLASS_Optional))
|
||||
{
|
||||
// Make sure the export is found in the game context as well
|
||||
if (FTaggedExport* GameExport = SaveContext.GetHarvestedRealm(ESaveRealm::Game).GetExports().Find(It->Obj))
|
||||
{
|
||||
// Flag the export in the game context to generate it's public hash
|
||||
GameExport->bGeneratePublicHash = true;
|
||||
// Transform the export as an import
|
||||
OptionalContext.AddImport(It->Obj);
|
||||
// Flag the package itself to be an import
|
||||
bHasNonOptionalSelfReference = true;
|
||||
}
|
||||
// if not found in the game context and the reference directly came from an optional object, record an illegal reference
|
||||
else if (It->bFromOptionalReference)
|
||||
{
|
||||
SaveContext.RecordIllegalReference(nullptr, It->Obj, EIllegalRefReason::ReferenceFromOptionalToMissingGameExport);
|
||||
}
|
||||
It.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
// Also add the current package itself as an import if we are referencing any non optional export
|
||||
if (bHasNonOptionalSelfReference)
|
||||
{
|
||||
OptionalContext.AddImport(SaveContext.GetPackage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,184 @@
|
||||
# Transient
|
||||
|
||||
- **功能描述:** 指定该类的所有对象都略过序列化。
|
||||
|
||||
- **引擎模块:** Serialization
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_Transient](../../../../Flags/EClassFlags/CLASS_Transient.md)
|
||||
- **关联项:** [NonTransient](../NonTransient.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
指定该类的所有对象都略过序列化。
|
||||
|
||||
- 从不将属于此类的对象保存到磁盘。此说明符会传播到子类,但是可由NonTransient说明符覆盖。可以在子类被改写.
|
||||
- 会造成相应Object的RF_Transient标记。
|
||||
- 注意:UPROPERTY(Transient)只是指定这一个特定的属性不序列化。而UCLASS(Transient)是作用于该类的所有对象。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, Transient)
|
||||
class INSIDER_API UMyClass_Transient :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, NonTransient)
|
||||
class INSIDER_API UMyClass_NonTransient :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_Transient_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_Transient* MyTransientObject;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_NonTransient* MyNonTransientObject;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Normal=123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,Transient)
|
||||
int32 MyInt_Transient =123;
|
||||
};
|
||||
```
|
||||
|
||||
序列化对象指针关键是采用FObjectProperty里的SerializeItem方法Slot << ObjectValue;
|
||||
|
||||
不能采用Actor的Class Default来测试,因为:
|
||||
|
||||
Transient properties are serialized for (Blueprint) Class Default Objects but should not be serialized for any 'normal' instances of classes.
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyActor_Transient_Test :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_Transient* MyTransientObject;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_NonTransient* MyNonTransientObject;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Normal=123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,Transient)
|
||||
int32 MyInt_Transient =123;
|
||||
};
|
||||
```
|
||||
|
||||
也不能用FObjectAndNameAsStringProxyArchive测试,因为FObjectAndNameAsStringProxyArchive内部在序列化对象的时候,会先查找名字。
|
||||
|
||||
```cpp
|
||||
FArchive& FObjectAndNameAsStringProxyArchive::operator<<(UObject*& Obj)
|
||||
{
|
||||
if (IsLoading())
|
||||
{
|
||||
// load the path name to the object
|
||||
FString LoadedString;
|
||||
InnerArchive << LoadedString;
|
||||
// look up the object by fully qualified pathname
|
||||
Obj = FindObject<UObject>(nullptr, *LoadedString, false);
|
||||
// If we couldn't find it, and we want to load it, do that
|
||||
if(!Obj && bLoadIfFindFails)
|
||||
{
|
||||
Obj = LoadObject<UObject>(nullptr, *LoadedString);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// save out the fully qualified object name
|
||||
FString SavedString(Obj->GetPathName());
|
||||
InnerArchive << SavedString;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
```
|
||||
|
||||
因此采用Package的测试:
|
||||
|
||||
```cpp
|
||||
|
||||
FString packageName = TEXT("/Game/MyTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
UPackage* package = CreatePackage(*packageName);
|
||||
FSavePackageArgs saveArgs{};
|
||||
saveArgs.Error = GError;
|
||||
|
||||
//ObjectFlags: RF_NoFlags
|
||||
UMyClass_Transient_Test* testObject = NewObject<UMyClass_Transient_Test>(package, TEXT("testObject"));
|
||||
//ObjectFlags: RF_Transient
|
||||
testObject->MyTransientObject = NewObject<UMyClass_Transient>(testObject, TEXT("MyTransientObject"));
|
||||
//ObjectFlags: RF_NoFlags
|
||||
testObject->MyNonTransientObject = NewObject<UMyClass_NonTransient>(testObject, TEXT("MyNonTransientObject"));
|
||||
|
||||
testObject->MyTransientObject->MyProperty = 456;
|
||||
testObject->MyNonTransientObject->MyProperty = 456;
|
||||
testObject->MyInt_Normal = 456;
|
||||
testObject->MyInt_Transient = 456;
|
||||
|
||||
bool result = UPackage::SavePackage(package, testObject, *assetPath, saveArgs);
|
||||
```
|
||||
|
||||
在保存完成之后,重新加载Package:
|
||||
|
||||
```cpp
|
||||
FString packageName = TEXT("/Game/MyTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
|
||||
UPackage* package = LoadPackage(nullptr, *assetPath, LOAD_None);
|
||||
package->FullyLoad();
|
||||
|
||||
UMyClass_Transient_Test* newTestObject=LoadObject<UMyClass_Transient_Test>(package, TEXT("testObject"),*assetPath);
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
可以看到MyTransientObject 并没有被序列化到磁盘上,因此不会加载出来。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在SavePackage的时候:RF_Transient会导致这个对象不会被HarvestExport,不会被放进SaveContext里
|
||||
|
||||
```cpp
|
||||
void FPackageHarvester::TryHarvestExport(UObject* InObject)
|
||||
{
|
||||
// Those should have been already validated
|
||||
check(InObject && InObject->IsInPackage(SaveContext.GetPackage()));
|
||||
|
||||
// Get the realm in which we should harvest this export
|
||||
EIllegalRefReason Reason = EIllegalRefReason::None;
|
||||
ESaveRealm HarvestContext = GetObjectHarvestingRealm(InObject, Reason);
|
||||
if (!SaveContext.GetHarvestedRealm(HarvestContext).IsExport(InObject))
|
||||
{
|
||||
SaveContext.MarkUnsaveable(InObject);
|
||||
bool bExcluded = false;
|
||||
if (!InObject->HasAnyFlags(RF_Transient))
|
||||
{
|
||||
bExcluded = ConditionallyExcludeObjectForTarget(SaveContext, InObject, HarvestContext);
|
||||
}
|
||||
if (!InObject->HasAnyFlags(RF_Transient) && !bExcluded)
|
||||
{
|
||||
// It passed filtering so mark as export
|
||||
HarvestExport(InObject, HarvestContext);
|
||||
}
|
||||
|
||||
// If we have a illegal ref reason, record it
|
||||
if (Reason != EIllegalRefReason::None)
|
||||
{
|
||||
SaveContext.RecordIllegalReference(CurrentExportDependencies.CurrentExport, InObject, Reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Reference in New Issue
Block a user