一切加载最后都要跑到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的信息,以便找到呢?
下面堆栈是保存蓝图时截的。
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