UE4 AssetRegistry分析
https://zhuanlan.zhihu.com/p/76964514
简介
Asset Registry是editor的子系统,负责在editor加载时收集未加载的asset信息。这些信息储存在内存中,因此editor可以创建资源列表,而不需要真正加载这些资源。Content Browser是这个系统的主要消费者,但是editor的其他部分,以及editor外的模块也能访问它们。
详细介绍可见官方文档:https://docs.unrealengine.com/en-US/Programming/Assets/Registry/index.html
和AssetRegistry相关的主要实体,以及它们见的关系如下图所示:
简而言之,AssetRegisty可以搜寻uasset文件,并用FAssetData抽象表示,并在有需要时根据FAssetData中的路径线索加载UObject。
主要数据结构
- FAssetData
用于在AssetRegistry查找asset时,抽象的表示一个asset,其中包含了一些关于asset的重要信息,可以在不加载UObject,即不进行反序列化操作和UObject内存分配这些比较耗的操作情况下表示关于这个对象的关键信息。因此ContentBrowser中虽然可以看到所有asset,但它们包含的uobject并没有加载到内存中。
主要属性:
FName ObjectPath:这个asset的object path,形如PackageName.AssetName,一个package中只有顶层object才会有AssetData.
FName PackageName:这个asset所在Package的名称,形如/Game/Path/Package
FName AssetClass:asset类型的类名称。比如蓝图资源则为“Blueprint”
FAssetDataTagMapSharedView TagsAndValues:一个uproperty的名称和值组成的map,可以提供一些资源所对应对象的描述信息,这些property被标记为AssetRegistrySearchable,或者在所在类的GetAssetRegistryTags()函数中被添加。比如蓝图资源asset就提供了GeneratedClass,BlueprintType等信息。
TArray<int32> ChunkIDs:所在chunk的ID
- FAssetPackageData
是对磁盘上package的抽象描述,在package进行save/load操作时同步更新。
主要属性:
int64 DiskSize:asset在磁盘上所占空间
FGuid PackageGuid:package的guid,用于唯一标识一个package
- FAssetIdentifier
用于唯一标识一个在asset registry框架下可以被引用的“实体”,可以表示一个package,也可以表示一个object
主要属性:
FName PackageName:依赖的package名称
FPrimaryAssetType PrimaryAssetType:primary asset类型,如果被设置,ObjectName即为PrimaryAssetName
FName ObjectName:package中具体的object名称,如果为空,那么就表示默认asset
FName ValueName:被引用的具体值,比如ObjectName描述了类似UStruct的类型
- FARFilter
用作AssetRegistry进行查询时的过滤器,可以配置多个属性。
部分可配置属性:
TArray<FName> PackageNames:Package名称的白名单
TArray<FName> PackagePaths:Package路径的白名单
bool bRecursivePaths:Package路径是否为递归查询
TMultiMap<FName, TOptional<FString>> TagsAndValues:被标为AssetRegistrySearchable的属性的值白名单
FARFilter的属性大多为一个容器,可配置多个元素。每个容器属性内部多个元素间的逻辑关系为“OR”,而多个属性间的逻辑关系为“AND”。
- FAssetRegistryState
储存了读取asset后的相关信息,用作磁盘内容的cache数据,用于描述asset registry当前的状态,可以由IAssetRegistry进行使用和维护。把这些数据单独组成一个FAssetRegistryState类,是为了减少耦合性,因为引擎中除asset registry外的其他模块 也需要用到这些数据。
主要属性:
TMap<FName, FAssetData*> CachedAssetsByObjectPath:ObjectPath名称到对应asset的映射
TMap<FName, TArray<FAssetData*> > CachedAssetsByPackageName:package名称到其包含的asset映射
TMap<FName, TArray<FAssetData*> > CachedAssetsByClass:class name到磁盘上asset的映射
主要方法:
GetAssets():根据FARFilter获取已加载的符合条件的FAssetData
- IAssetRegistry
AssetRegistry使用了基于UInterface的模式,因此IAssetRegistry接口定义了AssetRegistry需要具备的操作。
主要方法:
Tick:通过Tick来实现异步asset搜索,每当有asset新被搜索到,tick中就会把资源加入
GetAssetsByClass:得到某个class类型的所有已加载asset
ScanPathsAndFilesSynchronous:搜索某些路径下的asset
SearchAllAssets:搜索所有的asset,可通过命令行调用,editor启动时也会调用
- UAssetRegistryImpl
是IAssetRegistry的实现,单例模式。
- FAssetDataGatherer
用于在文件中搜寻asset。是异步task,内部使用了FAssetDataDiscovery进行更底层的搜寻操作。同时也支持同步搜寻。
- FDependsNode
可以抽象的描述一个资源节点(package/object)的依赖与引用关系,储存于FAssetRegistryState中,也扮演着cache的角色。当我们要查询一个资源的引用视图,比如编辑器中的“reference viwer”功能,就可以使用该数据。
主要属性:
FAssetIdentifier Identifier:该节点对应asset实体的id
TArray<FDependsNode*> HardDependencies:该资源的直接引用资源列表(加载该资源时这些被引用的资源也会加载)
TArray<FDependsNode*> SoftDependencies:该资源的间接引用资源列表
TArray<FDependsNode*> Referencers:引用该资源的资源列表
- FPackageDependencyData
FAssetDataDiscovery扫描磁盘上的资源时得到的数据结构,继承自FLinkerTables,之后要把它转为FDependsNode。
- Asset Registry类图
如何搜寻asset文件
使用AssetRegistry,我们可以方便的从磁盘上读取asset文件并构建FAssetData描述。
- 同步方式
通过上层接口进行调用,通常只能通过同步的方式搜寻,通常使用以下接口:
ScanFilesSynchronous(const TArray<FString>& InFilePaths, bool bForceRescan = false)
搜寻InFilePaths路径下的所有asset,不需要返回值,因为搜寻后的结果会保存在FAssetRegistryState中,之后可以使用过滤器进行查询。具体方式为新建一个FAssetDataGatherer,把模式设置为同步,然后FAssetDataGatherer就会直接阻塞的调用run()方法,为我们搜寻asset了。
类似的,也可以使用ScanFilesSynchronous方法进行同步搜索。
SearchAllAssets(bool bSynchronousSearch)
一种更简单的方式:同步搜寻所有assets。SearchAllAssets方法可以以同步和异步方式运行,该方法的同步模式通常用于CommandLet中,因为此时对时间消耗并不是很敏感。此方法同步模式最终执行方式其实是和ScanFilesSynchronous相同的,只是为我们自动得到了所有要搜寻的路径。
- 异步方式
异步方式通常由编辑器内部调用,尚不清楚如何通过上层进行调用。
SearchAllAssets(bool bSynchronousSearch)
之前说了,SearchAllAssets可以以异步模式执行,其实现方法也比较直观,为新建了一个FAssetDataGatherer对象,然后在UAssetRegistryImpl::Tick中获取。直接调用异步SearchAllAssets的地方目前只有一处,就是UAssetRegistryImpl的构造函数中,当在编辑器中,且不通过CommandLet时,就会调用,其作用是初始化ContentBrowser。我们刚打开编辑器时的初始化加载过程就是在做异步资源发现操作。
让我们看一下Tick是如何执行的,调用过程为:
- EngineTick()
- UEditorEngine::Tick 编辑器EngineTick
- FAssetRegistryModule::TickAssetRegistry
- UAssetRegistryImpl::Tick
在UAssetRegistryImpl::Tick中,会不断通过FAssetDataGatherer类型的成员变量BackgroundAssetSearch获取新搜寻到的FAssetData,并重置BackgroundAssetSearch的搜寻结果。之后AssetRegistry就会更新自己和State的数据,并发送一些广播,通知搜寻到 了新的assetdata。通过调用过程可以看到,在引擎的循环中,如果是编辑器,那FAssetRegistry的Tick始终在执行,以此达到保持编辑器中assetdata始终于磁盘同步的目的。
AddPathToSearch(const FString& Path)
这个方法是私有方法,并不能被上层调用,作用为向异步扫描过程中加入待扫描路径,编辑器自身的一些功能模块会调用到这。
如何从FAssetData获取Uobject
既然FAssetData中已经包含了ObjectPath,那么当我们需要某个UObject实例时,简单的Load一下即可。不过FAssetData已经为我们包装好了一个函数:GetAsset(),使用起来更加方便。