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(),使用起來更加方便。