首页 > 其他分享 >资源加载和序列化

资源加载和序列化

时间:2024-03-19 12:12:00浏览次数:22  
标签:Object const Linker LoadContext FLinkerLoad 序列化 资源 加载

一切加载最后都要跑到LoadPackageInternal:

  • 创建Linker
  • 序列化(LoadAllObjects)
{
	FUObjectSerializeContext* InOutLoadContext = LoadContext;
	Linker = GetPackageLinker(InOuter, PackagePath, LoadFlags, nullptr, InReaderOverride, &InOutLoadContext, ImportLinker, InstancingContext);
	if (ImportLinker)
	{
		TRACE_LOADTIME_ASYNC_PACKAGE_IMPORT_DEPENDENCY(ImportLinker, Linker);
	}
	else
	{
		TRACE_LOADTIME_ASYNC_PACKAGE_REQUEST_ASSOCIATION(Linker, 0);
	}
	if (InOutLoadContext != LoadContext && InOutLoadContext)
	{
		// The linker already existed and was associated with another context
		LoadContext->DecrementBeginLoadCount();
		LoadContext = InOutLoadContext;
		LoadContext->IncrementBeginLoadCount();
	}
}

FSerializedPropertyScope SerializedProperty(*Linker, ImportLinker ? ImportLinker->GetSerializedProperty() : Linker->GetSerializedProperty());
Linker->LoadAllObjects(GEventDrivenLoaderEnabled);

创建Linker

GetPackageLinker:

// 创建Package  (NewObject<UPackage>)
CreatedPackage = CreatePackage(*PackageNameToCreate);

// 创建Linker
TRefCountPtr<FUObjectSerializeContext> LoadContext(InExistingContext ? InExistingContext : FUObjectThreadContext::Get().GetSerializeContext());
FLinkerLoad* Result = FLinkerLoad::CreateLinker(LoadContext, TargetPackage, PackagePath, LoadFlags, InReaderOverride, InstancingContext);

CreateLinker

FLinkerLoad* FLinkerLoad::CreateLinker(FUObjectSerializeContext* LoadContext, UPackage* Parent, const FPackagePath& PackagePath, uint32 LoadFlags, FArchive* InLoader /*= nullptr*/, const FLinkerInstancingContext* InstancingContext /*= nullptr*/)
{
	FLinkerLoad* Linker = CreateLinkerAsync(LoadContext, Parent, PackagePath, LoadFlags, InstancingContext,
		TFunction<void()>([](){})
		);
	{
#if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING
		// the linker could already have the DeferDependencyLoads flag present 
		// (if this linker was already created further up the load chain, and 
		// we're re-entering this to further finalize its creation)... we want 
		// to make sure the DeferDependencyLoads flag is supplied (if it was 
		// specified) for the duration of the Tick() below, because its call to 
		// FinalizeCreation() could invoke further dependency loads
		TGuardValue<uint32> LinkerLoadFlagGuard(Linker->LoadFlags, Linker->LoadFlags | DeferredLoadFlag);
#endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING

		if (InLoader)
		{
			// The linker can't have an associated loader here if we have a loader override
			check(!Linker->Loader);
			Linker->SetLoader(InLoader, true /* bInLoaderNeedsEngineVersionChecks */);
			// Set the basic archive flags on the linker
			Linker->ResetStatusInfo();
		}

		TGuardValue<FLinkerLoad*> SerializedPackageLinkerGuard(LoadContext->SerializedPackageLinker, Linker);
		if (Linker->Tick(0.f, false, false, nullptr) == LINKER_Failed)
		{
			return nullptr;
		}
	}
}

FLinkerLoad* FLinkerLoad::CreateLinkerAsync(FUObjectSerializeContext* LoadContext, UPackage* Parent, const FPackagePath& PackagePath, uint32 LoadFlags, const FLinkerInstancingContext* InstancingContext
	, TFunction<void()>&& InSummaryReadyCallback
	)
{
		Linker = new FLinkerLoad(Parent, PackagePath, LoadFlags, InstancingContext ? *InstancingContext : FLinkerInstancingContext());
		Linker->SetSerializeContext(LoadContext);
		Parent->SetLinker(Linker);
		if (GEventDrivenLoaderEnabled && Linker)
		{
			Linker->CreateLoader(Forward<TFunction<void()>>(InSummaryReadyCallback));
		}
}

1 创建Linker对象(new FLinkerLoad),并设置Package的Linker。

2 Linker->Tick

Linker->Tick里做了主要的加载工作。

CreateLoader (Loader是Linker的成员,主要管文件相关的)

 FLinkerLoad::ELinkerStatus FLinkerLoad::CreateLoader(
	TFunction<void()>&& InSummaryReadyCallback
	)
{
		FOpenPackageResult OpenResult;
#if WITH_EDITOR
			if (FLinkerLoad::GetPreloadingEnabled() && FLinkerLoad::TryGetPreloadedLoader(GetPackagePath(), OpenResult))
			{
				// OpenResult set by TryGetPreloadedLoader
			}
			else
#endif
			{
				OpenResult = IPackageResourceManager::Get().OpenReadPackage(GetPackagePath());
			}

		Loader = OpenResult.Archive.Release();//FArchive*

}

OpenReadPackage返回一个FOpenPackageResult,里面存储了Loader,Loader的具体类型为FArchiveFileReaderGeneric。

这个类里保留了一个文件的IFileHandle(文件句柄),以后对文件的操作都可以通过这个来进行。通过Loader,建立起了访问文件的桥梁。

struct FOpenPackageResult
{
	/** Archive containing the bytes for the requested PackagePath segment */
	TUniquePtr<FArchive> Archive;
	/**
	 * Format of the archive, binary or text.
	 * Currently only header segments can have EPackageFormat::Text, all other segments have EPackageFormat::Binary
	 */
	EPackageFormat Format = EPackageFormat::Binary;
	/**
	 * True if the package is of unknown version and needs to check for version and corruption.
	 * False if the package was loaded from a repository specifically for the current binary's versions
	 * and has already been checked for corruption.
	 */
	bool bNeedsEngineVersionChecks = true;

	void CopyMetaData(const FOpenPackageResult& Other)
	{
		Format = Other.Format;
		bNeedsEngineVersionChecks = Other.bNeedsEngineVersionChecks;
	}
};

FOpenPackageResult FPackageResourceManagerFile::OpenReadPackage(const FPackagePath& PackagePath,
	EPackageSegment PackageSegment, FPackagePath* OutUpdatedPath)
{
	FOpenPackageResult Result{ nullptr, EPackageFormat::Binary, true /* bNeedsEngineVersionChecks */};

	IFileManager* FileManager = &IFileManager::Get();
	IteratePossibleFiles(PackagePath, PackageSegment, OutUpdatedPath,
		[FileManager, &Result](const TCHAR* Filename, EPackageExtension Extension)
		{
#if !WITH_TEXT_ARCHIVE_SUPPORT
			if (Extension <span style="font-weight: bold;" class="mark"> EPackageExtension::TextAsset || Extension </span> EPackageExtension::TextMap)
			{
				return false;
			}
#endif
			Result.Archive = TUniquePtr<FArchive>(FileManager->CreateFileReader(Filename));
			if (Result.Archive)
			{
#if WITH_TEXT_ARCHIVE_SUPPORT
				if (Extension <span style="font-weight: bold;" class="mark"> EPackageExtension::TextAsset || Extension </span> EPackageExtension::TextMap)
				{
					Result.Format = EPackageFormat::Text;
				}
				else
#endif
				{
					Result.Format = EPackageFormat::Binary;
				}
				return true;
			}
			return false;
		});
	return Result;
}

FArchive* FFileManagerGeneric::CreateFileReaderInternal( const TCHAR* InFilename, uint32 Flags, uint32 BufferSize )
{
	IFileHandle* Handle = GetLowLevel().OpenRead( InFilename, !!(Flags & FILEREAD_AllowWrite) );
	if( !Handle )
	{
		if( Flags & FILEREAD_NoFail )
		{
			UE_LOG( LogFileManager, Fatal, TEXT( "Failed to read file: %s" ), InFilename );
		}
		return NULL;
	}
	return new FArchiveFileReaderGeneric( Handle, InFilename, Handle->Size(), BufferSize, Flags );
}

Creatloader的最后调用Loader->Precache​进行了一个预分配。

ProcessPackageSummary

这块主要是从Asset文件中反序列化出头部信息。

解释下下面代码,这块一开始没看明白。

StructuredArchiveRootRecord.GetValue() << SA_VALUE(TEXT("Summary"), Summary);

代替宏其实是

FStructuredArchiveRecord << TNamedValue

template<typename T> FORCEINLINE FStructuredArchiveRecord& operator<<(UE::StructuredArchive::Private::TNamedValue<T> Item)
	{
		EnterField(Item.Name) << Item.Value;
		return *this;
	}

EnterField返回FStructuredArchiveSlot​,目前传入的这个Name其实是没啥用的,主要情况是后面传入的Value类型,影响重载函数的选择。

普通类型会走到正常的序列化。

void FStructuredArchiveSlot::operator<< (int32& Value)
{
	StructuredArchive.EnterSlot(*this);
	StructuredArchive.Formatter.Serialize(Value);
	StructuredArchive.LeaveSlot();
}

这里的Summary特殊一点,会走到这里。

void operator<<(FStructuredArchive::FSlot Slot, FPackageFileSummary& Sum)

因为现在才刚开始读,Loader里的Pos是0,从开头位置开始反序列化。Summary很重要,记录了一些基础信息,包括一些段在Loader里的起始Pos,根据这些信息,后面可以直接跳过去开始反序列化。

接着里面还序列化了一些比较重要的结构,比如ImportMap和ExportMap。

目前创建的Linker可以访问文件,但是还是一个空壳,接下来通过LoadAllObjects​才会真正的反序列化填充Linker数据。

序列化

序列化的概念应该都比较清楚。比较常见的像protobuf的序列化,为了节省空间,对整数的编码都是变长的,按需存储,极尽压缩。UE并没看到这种压缩,毕竟这种操作本身是会带来效率上的影响。

回顾一般的序列化需要做些什么,我们做一些简单的思考。

  • 首先得考虑是大端还是小端,如果两端字节序不一致,需要注意大端小端的转换。
  • 两边对各种内部元素的字节化顺序需要保持一致,这样才能保持数据的有效。有时是靠我们手写的时候人工注意两边的顺序。
  • 指针怎么办,在一般系统里,存储指针这种往往无意义。但是UE因为管理自己的资源,指向内部资源的这种关联信息我们是有办法保存起来的,具体后面讲。
  • Map的信息序列化反序列化后如何保持。
  • 数据不能像protobuf那样压缩,那还有没有别的办法。UE里有CDO的概念,CDO的部分没必要重复序列化,处理差异部分即可。

简单源码结构

<span style="font-weight: bold;" data-type="strong">LoadAllObjects</span>​里主要调用了CreateExportAndPreload

UObject* FLinkerLoad::CreateExportAndPreload(int32 ExportIndex, bool bForcePreload /* = false */)
{
	UObject* Object = CreateExport(ExportIndex);
	if (Object && (bForcePreload || dynamic_cast<UClass*>(Object) || Object->IsTemplate() || dynamic_cast<UObjectRedirector*>(Object)))
	{
		Preload(Object);
	}

	return Object;
}

CreateExport

创建UObject对象,放到Export.Object里。根据FObjectExport,先Verify需要的Import,确保需要的UObject都已经载入,然后再分配空间并StaticConstructObject_Internal​ 创建UObject对象执行构造,但这是的UObject一个空壳。

Preload

这一步主要调用了对象的Serialize函数。在这函数里,我们可以自己控制序列化对象的哪些属性。UE一个比较好的办法处理了序列化元素的顺序问题,即采用访问者模式,序列化和反序列化用的同一个Serialize,只是传入的Archive(访问者)不一样。正常情况下分别采用的是FLinkerSave和FLinkerLoad。

UProperty标记的属性,因为建立了反射信息,类中是可以遍历访问的。通过调用UObject::Serialize进行反序列化填充,放在Export.Object里,具体跟进去主要函数是SerializeTaggedProperties​。主要就是遍历Class的各个属性,按顺序对不同类型的Property进行SerializeItem​。

首先看看和CDO差异这一块如何序列化的。

uint8* DataPtr      = Property->ContainerPtrToValuePtr           <uint8>(Data, Idx);
uint8* DefaultValue = Property->ContainerPtrToValuePtrForDefaults<uint8>(DefaultsStruct, Defaults, Idx);
if (StaticArrayContainer.IsSet() || CustomPropertyNode || !UnderlyingArchive.DoDelta() || UnderlyingArchive.IsTransacting() || (!Defaults && !dynamic_cast<const UClass*>(this)) || !Property->Identical(DataPtr, DefaultValue, UnderlyingArchive.GetPortFlags()))
{
	Tag.SerializeTaggedProperty(PropertySlot, Property, DataPtr, DefaultValue);
}

Data​和DefaultsStruct​分别指向具体Instance和其CDO。根据这两个指针,取出属性里对应的值,看是否一样。只有非Identical(即CDO属性值被修改)时,需要走下面的属性序列化。

这里的ContainerPtrToValuePtr是根据属性取值的,通过每个属性的反射信息,可以查到其偏移(Offset_Internal),从而算出具体的值。

FORCEINLINE void* ContainerVoidPtrToValuePtrInternal(void* ContainerPtr, int32 ArrayIndex) const
	{
		checkf((ArrayIndex >= 0) && (ArrayIndex < ArrayDim), TEXT("Array index out of bounds: %i from an array of size %i"), ArrayIndex, ArrayDim);
		check(ContainerPtr);

		if (0)
		{
			// in the future, these checks will be tested if the property is NOT relative to a UClass
			check(!GetOwner<UClass>()); // Check we are _not_ calling this on a direct child property of a UClass, you should pass in a UObject* in that case
		}

		return (uint8*)ContainerPtr + Offset_Internal + static_cast<size_t>(ElementSize) * ArrayIndex;
	}

指针数据如何存储

对UObjectA引用的UObjectB,如何存储B的信息,以便找到呢?

下面堆栈是保存蓝图时截的。

image

FArchive& FLinkerSave::operator<<( UObject*& Obj )
{
	FPackageIndex Save;
	if (Obj)
	{
		Save = MapObject(Obj);
	}
	return *this << Save;
}

可以看出这里序列化了一个FPackageIndex。在我们存储的时候,所需要用到的资源索引其实已经在ExportMap或ImportMap中了,这时我们只需要保存一个FPackageIndex指向就可以了。在反序列化的时候,根据FPackageIndex同样可在Map中找到对应资源。

标签:Object,const,Linker,LoadContext,FLinkerLoad,序列化,资源,加载
From: https://www.cnblogs.com/monstertang/p/18082495/resource-loading-and-serialization-z1b9aep

相关文章

  • NOI2024前训练-一些有趣的国内外比赛资源库 #2
    NOI2024前训练-一些有趣的国内外比赛资源库#2QOJ#4399.[CEOI2022]AbracadabraTin是一位著名的魔术师,他的一个经典魔术与洗牌有关。Tin会准备一套牌,总共\(n\)张(保证\(n\)为偶数),各编号为\(1\simn\),一开始的时候牌是乱的且倒扣在桌子上。紧接着他开始表演洗牌,在洗牌......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的人脸表情识别系统(附完整资源+PySide6界面+训练代码
    摘要:本篇博客呈现了一种基于深度学习的人脸表情识别系统,并详细展示了其实现代码。系统采纳了领先的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期版本进行了比较,展示了其在图像、视频、实时视频流及批量文件中识别人脸表情的高准确度。文章深入阐释了YOLOv8的工作机制,并配备了相应......
  • Nancy 过滤加载dll
    protectedoverridevoidConfigureApplicationContainer(TinyIoCContainercontainer){List<Func<Assembly,bool>>IgnoredAssemblies=DefaultNancyBootstrapper.DefaultAutoRegisterIgnoredAssemblies.ToList();//IgnoredAs......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的犬种识别系统(附完整代码资源+UI界面+PyTorch代码)
    摘要:本文介绍了一种基于深度学习的犬种识别系统系统的代码,采用最先进的YOLOv8算法并对比YOLOv7、YOLOv6、YOLOv5等算法的结果,能够准确识别图像、视频、实时视频流以及批量文件中的犬种。文章详细解释了YOLOv8算法的原理,并提供了相应的Python实现代码、训练数据集,以及基于PySide6的......
  • 【操作系统】线程、程序、进程死锁的必要条件?如何避免死锁?死锁的预防,死锁的避免(银行
    目录线程、程序、进程死锁的必要条件?如何避免死锁?死锁的预防死锁的避免(银行家)死锁的检测进程-资源分配图死锁检测步骤死锁的解除线程、程序、进程进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行......
  • Fastapi中Swagger UI加载缓慢的解决方案
    在国内网络经常遇到SwaggerUI加载缓慢的问题,这是由于SwaggerUI的CSS和JS代码源在国外导致的,所以我们的解决方法是更改SwaggerUI的CSS代码和JS代码源到国内的CND实现加速。这里以Fastapi框架的SwaggerUI加载缓慢举例:一、解决方法在main.py(入口代码)代码中新增如下代码:fromfa......
  • 自学IT技术平台(专业技能,技术讲解,企业实训项目)技术小白的福音,免费资源网站
    教育平台Coursera Coursera(Coursera.org)是一个知名的在线学习平台,为学生、专业人士和组织提供高质量的在线课程、证书项目和学位项目。以下是Coursera平台的一些简介:课程内容:Coursera上有来自全球顶尖大学和教育机构的数千门在线课程,涵盖各种领域,如计算机科学、数据科学......
  • 错误: 找不到或无法加载主类
    错误:找不到或无法加载主类springboot项目平时启动好的,这天突然启动异常了,只有一个服务启动正常,其他都报错,找不到或无法加载主类。问题排查:首先通过命令行运行mvninstall 显示出无法加载dataSource信息。确定数据库已经启动,并且配置也是正确的。发现是资源文件夹变成普......
  • three模型加载loader模块封装
    在three项目中需要加载很多的模型而且在很多地方需要使用loader加载模型回调的gltf上一个项目中遇到了初学three的我留下笔记简单版`import*asTHREEfrom'three';import{GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader.js';constloader=newGLTFLoa......
  • Walrus 0.6发布:预览资源变更、丰富公有云支持,满足企业多云需求
    近日,数澈软件Seal(以下简称“Seal”)宣布基于IaC的开源应用管理平台Walrus0.6正式发布! 在之前的版本中,Walrus引入应用模型并优化了应用部署体验,前者为屏蔽基础设施复杂度提供了抽象层(即资源定义和资源),运维人员可以在资源定义内配置匹配规则、UISchema,同时开发人员通过创建......