monstertang

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一切加载最后都要跑到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。

两个Map中主要记录的结构体 FObjectImport FObjectExport

Import.OuterIndex 记录需要Import的对象。

OuterIndex.Index < 0 在自己同Package中,从自身ImportMap中可以取到。(Index取反-1)

OuterIndex.Index > 0 在其他Package。

OuterIndex.Index = 0 当前引用的就是一个UPackage,根据当前FObjectImport记录的信息(PackageName),载入Pacakge

Import.SourceLinker 外来Package的Linker

VerifyImport:根据ImportMap,载入需要Import的外来Package。Verify的意思其实是保证需要的Importer是已经加载的。很多时候Export是依赖于Import的。

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

序列化

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

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

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

简单源码结构

LoadAllObjects​里主要调用了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中找到对应资源。

posted on 2024-03-19 12:07  狂暴食尸鬼  阅读(273)  评论(0)    收藏  举报