Unreal 序列化和反序列化原理机制源码解析

Viewed 11

概要

  • Unreal 中 UObject 的序列化和反序列化流程和原理基本相同,都是基于 FArchive 的操作符重载 operator<< 对各种类型进行序列化。UObject 属性序列化主要在方法 SerializeScriptProperties 中进行,其底层调用各类型的 SerializeItem 进行对应类型属性的序列化,底层基于 FMemory::Memcpy 内存拷贝进行对象和流之间的读或写。

说明

  • 本文源码基于 UE4.27.2,不保证所有步骤流程源码等均与其它版本(尤其是UE5)一样,但原理和核心机制基本一致。
  • 本文基于个人测试和对源码的调试而来,主要用于学习和记录,存在不全面乃至错误的地方,如有发现,欢迎指出。

流程图

序列化

对象的序列化流程图


3ft6sga9qrm552ggm9bor4lglp.png

说明

  • 通过编辑器右键保存文件等触发package的序列化。
  • 基于文件等构建 FLinkerSave ,底层调用 FFileManagerGeneric::CreateFileWriter 来创建文件关联的 save Archive 。
  • 调用 UObject::Serialize 进行序列化
  • PackageFileSummary(unreal package 的“目录”, 存储在文件的顶部)的序列化
  • 序列化可搜索名称映射(SerializeSearchableNamesMap)
  • 保存指定包外层和链接器的缩略图数据(SaveThumbnails)
  • 保存世界浏览器使用的关卡信息(SaveWorldLevelInfo)
  • 其它信息(如ImportMap、ExportMap等)的序列化(略,详见UPackage::Save)
  • UObject 内部的 Serialize ,详见 UObject::Serialize。其中 LoadOuter、ObjClass、WasKill等信息的序列化略,此处详述属性的序列化流程。 #### 对象属性的序列化流程 * 主要基于 SerializeScriptProperties 进行属性的序列化,其序列化位于 Data 的脚本属性数据。 保存时,只保存那些与指定'DiffObject'(通常是对象的原型)中的对应值不同的属性。 * 调用 FArchive::MarkScriptSerializationStart,标记属性序列化开始。 * 调用 UStruct::SerializeTaggedProperties对属性列表进行序列化。 * 其中调用 UStruct::SerializeVersionedTaggedProperties ,基于 archive 的 ArUseUnversionedPropertySerialization (属性序列化是否使用更快的非版本序列化),使用不同的属性序列化函数。 * 调用 FPropertyTag::SerializeTaggedProperty进行属性序列化,在调用 SerializeItem 之前和之后做一些兼容性处理和收尾工作。 * 调用具体类型的 SerializeItem 进行具体属性的具体序列化(或反序列化),底层即是调用 FMemory::Memcpy 进行内存的拷贝。 * 调用 FArchive::MarkScriptSerializationEnd,标记属性序列化结束。 * 自此,对象的对应属性得到了序列化。

反序列化

对象的反序列化流程图


4filsj52bh2l79cpvg9mn580e2.png

说明

  • 首先把文件从磁盘上加载出来,得到特定类型的UObject(加载流程不在本文详述)
  • 调用 CreateLoader 创建 FLinkerLoad,其内部调用 FFileManagerGeneric::CreateFileReader 创建文件读取的 loader FArchive
  • 调用 FLinkerLoad::SerializePackageFileSummary 进行 package 摘要的序列化(如需要)
  • 依次序列化package摘要(FLinkerLoad::SerializePackageFileSummary)、名称表(FLinkerLoad::SerializeNameMap)、可收集的文本数据容器(FLinkerLoad::SerializeGatherableTextDataMap)、导入映射(FLinkerLoad::SerializeImportMap)、导出映射(FLinkerLoad::SerializeExportMap)、加载文本资产时创建导入和导出表(FLinkerLoad::ReconstructImportAndExportMap)、修复导入映射(FLinkerLoad::FixupImportMap)、为向后兼容性执行重映射等(FLinkerLoad::PopulateInstancingContext)、为实例化上下文生成重映射(一个实例包、允许对象实例在加载包时转换为其他类)(FLinkerLoad::FixupExportMap)、序列化依赖映射(FLinkerLoad::SerializeDependsMap)、创建导出哈希(依赖于已经序列化的导入和导出映射)(FLinkerLoad::CreateExportHash)、找到内存中现有的导出并将它们与此链接器匹配( 这是 PIE 正常工作所必需的,也是脚本编译所必需的,因为保存包将重置其链接器,加载将重新加载/替换没有链接器的现有对象)(FLinkerLoad::FindExistingExports)、序列化依赖映射(FLinkerLoad::SerializePreloadDependencies)、完成链接器创建,将链接器添加到加载器数组并可能验证imports等(FLinkerLoad::FinalizeCreation)。
  • 对象属性的反序列化,与对象属性的序列化流程基本相同,此处略。

类图

关键类类图


4nsgktijm4pdgnke9n21ekucji.png

关键类说明

UObject

  • UObject::SerializeScriptProperties * 序列化位于 Data 的脚本属性数据。 保存时,只保存那些与指定'DiffObject'(通常是对象的原型)中的对应值不同的属性。 * void UObject::SerializeScriptProperties( FStructuredArchive::FSlot Slot ) const


0t6kmq3ub7ukoaocukiierhrnb.png

  • UStruct::SerializeTaggedProperties * 序列化属性列表,使用属性标签处理不匹配 * virtual void SerializeTaggedProperties(FStructuredArchive::FSlot Slot, uint8 Data, UStruct DefaultsStruct, uint8 Defaults, const UObject BreakRecursionIfFullyLoad = nullptr) const; * virtual void SerializeTaggedProperties(FArchive& Ar, uint8 Data, UStruct DefaultsStruct, uint8 Defaults, const UObject BreakRecursionIfFullyLoad = nullptr) const final
  • UStruct::SerializeVersionedTaggedProperties * 基于 archive 的 ArUseUnversionedPropertySerialization (属性序列化是否使用更快的非版本序列化),使用不同的属性序列化函数。 * void SerializeVersionedTaggedProperties(FStructuredArchive::FSlot Slot, uint8 Data, UStruct DefaultsStruct, uint8 Defaults, const UObject BreakRecursionIfFullyLoad) const; * void SerializeUnversionedProperties(const UStruct Struct, FStructuredArchive::FSlot Slot, uint8 Data, UStruct DefaultsStruct, uint8 DefaultsData)
  • FPropertyTag::SerializeTaggedProperty * 属性序列化,调用 SerializeItem 之前和之后做一些兼容性处理和收尾工作。 * void SerializeTaggedProperty(FStructuredArchive::FSlot Slot, FProperty Property, uint8 Value, uint8 Defaults) const; * void SerializeTaggedProperty( FArchive& Ar, FProperty Property, uint8 Value, uint8 Defaults ) const;
  • FPropertyTag::SerializeItem * 调用具体类型的 SerializeItem 进行具体属性的具体序列化(或反序列化),底层即是调用 FMemory::Memcpy 进行内存的拷贝。 * virtual void SerializeItem(FStructuredArchive::FSlot Slot, void Value, void const Defaults = NULL) const PURE_VIRTUAL(FProperty::SerializeItem, );

FArchive

  • 存档的基类,可用于以字节有序方式加载、保存和垃圾回收。
  • virtual FArchive& operator<< * 有很多关于 << 的操作符重载函数 operator<< ,参数多种多样。用于把目标参数序列化到archive中或者从 archive 中读取数据给目标参数赋值。这些函数是 virtual 的,可以给各种各样的 FArchive 子类进行重载以便自定义自己的序列化和反序列化方式。 * virtual FArchive& operator<<(FText& Value)


1tfpkqrhbk20654qqlhbr77n3o.png

  • friend FArchive& operator<< * 还有很多非 virtual 但是是 friend 的操作符重载函数 operator<<,其参数除了用于序列化和反序列化的archive外,还包括一个类型各异的参数,表示要序列化或反序列化的目标。标记为 friend ,以便对应的类型可以直接访问。 * FORCEINLINE friend FArchive& operator<<(FArchive& Ar, ANSICHAR& Value);


0kjqmor2kc11o9i9a498g9r84u.png

  • MarkScriptSerializationStart * 当对象开始使用脚本序列化序列化属性数据时调用。以便做一些自定义操作,如 FCPFUOWriter 在 MarkScriptSerializationStart 中调用 OpenTaggedDataScope 对 TaggedDataScope 进行递增。
  • MarkScriptSerializationEnd * 当对象停止使用脚本序列化序列化属性数据时调用。以便做一些自定义操作,如 FCPFUOWriter 在 MarkScriptSerializationEnd 中调用 CloseTaggedDataScope 对 TaggedDataScope 进行递减。
  • ByteOrderSerialize * 序列化指定长度字节的数据,在需要考虑字节交换(ByteSwapping)时使用。

FLinkerSave

  • 继承自 FLinker,负责处理 Unreal package 文件的保存。
  • Constructor
  • 其有多个构造函数,支持根据文件、内存字节流及继承自 FArchive 的自定义saver 来构造。


280rikmv43sudhd8etgdg7mbf9.png

  • 如果是基于文件来序列化,如文件保存,则会调用 FFileManagerGeneric::CreateFileWriter 且得到有其句柄的 FArchive 作为 Saver ,序列化完毕后会调用 CloseAndDestroySaver 释放。

FLinkerLoad

  • 继承自 FLinker,负责处理Unreal package的加载,包括从磁盘读取 UObject 数据,以及package基本信息的序列化。
  • FLinkerLoad::CreateLoader * 创建用于序列化内容的加载器,支持异步。 * ELinkerStatus CreateLoader( TFunction&& InSummaryReadyCallback );
  • FLinkerLoad::Preload * void FLinkerLoad::Preload( UObject* Object ) * 从 Unreal package 文件中序列化指定对象的对象数据。 加载对象处于有效状态以接收加载数据所需的任何其他资源,例如对象的 Outer、Class 或 ObjectArchetype。 当此函数退出时,Object 保证包含存储在磁盘上的数据。 * 参数 Object: 要为其加载数据的对象。 如果此对象的数据未存储在此 FLinkerLoad 中,则将调用其 Linker 的Preload。 如果对象已经加载(如未为对象设置 RF_NeedLoad 标志所示),则跳过数据序列化,因此可以安全地调用已经加载的对象。请注意,此函数假定对象已根据其初始化 模板对象。 如果 Object 是一个 UClass 并且已经创建了类默认对象,则也为类默认对象调用 Preload。
  • FLinkerLoad::Tick * 各种反序列化操作 * FLinkerLoad::ELinkerStatus FLinkerLoad::Tick( float InTimeLimit, bool bInUseTimeLimit, bool bInUseFullTimeLimit, TMap, FPackageIndex>* ObjectNameWithOuterToExportMap)


07ml0b09ubr59a49g63ume8poe.png

FArchiveFileReaderGeneric

  • 继承自 FArchive,负责从数据流中读取数据赋值给目标。底层调用平台兼容性的 FPlatformMemory::Memcpy 进行内存拷贝。


2q0rv7f9n6tsr2gg61ljtbk46d.png

FArchiveFileWriterGeneric

  • 继承自 FArchive,负责把数据写到数据流中。底层调用平台兼容性的 FPlatformMemory::Memcpy 进行内存拷贝。


09j5jbio6o3uv0c2co1f3mvjtb.png

相关链接

**声明:**本文来自公众号:Unity 与Unreal 游戏开发(GameDevLearning),转载请附上原文链接(https://mp.weixin.qq.com/s/7_XdNUxfIBv1oZiNEMwNgg)及本声明。

0 Answers