UE4資源載入FStreamableManager

引擎版本:4.17

官方相關文檔地址:Asynchronous Asset Loading

轉載請註明出處

點贊是我最大的動力~

UML類圖

FStreamableManager是使用同步非同步載入功能時直接操作的類,全局有一個,直接聲明一個實例即可。

參見UAssetManager類中的聲明:

核心的函數方法如下:

/** * Request streaming of one or more target objects, and call a delegate on completion. * 非同步載入指定路徑的資源,載入結束回調代理函數* @param TargetsToStream Assets to load off disk* 資源路徑* @param DelegateToCall Delegate to call when load finishes. Will be called on the next tick if asset is already loaded, or many seconds later* 載入結束要回調的代理* @param Priority Priority to pass to the streaming system, higher priority will be loaded first* 載入優先順序* @param bManageActiveHandle If true, the manager will keep the streamable handle active until explicitly released* 是否保留(TSharedPtr<>引用計數)載入的Handle,如果保留,則需要手動釋放Handle,否則自動釋放* @param DebugName Name of this handle, will be reported in debug tools*/TSharedPtr<FStreamableHandle> RequestAsyncLoad(const TArray<FStringAssetReference>& TargetsToStream, FStreamableDelegate DelegateToCall = FStreamableDelegate(), TAsyncLoadPriority Priority = DefaultAsyncLoadPriority, bool bManageActiveHandle = false, const FString& DebugName = TEXT("RequestAsyncLoad ArrayDelegate"));TSharedPtr<FStreamableHandle> RequestAsyncLoad(const FStringAssetReference& TargetToStream, FStreamableDelegate DelegateToCall = FStreamableDelegate(), TAsyncLoadPriority Priority = DefaultAsyncLoadPriority, bool bManageActiveHandle = false, const FString& DebugName = TEXT("RequestAsyncLoad SingleDelegate"));/** * Synchronously load a set of assets, and return a handle.* This can be very slow and may stall the game thread for several seconds.* 同步載入指定路徑資源* @param TargetsToStream Assets to load off disk* 資源路徑* @param bManageActiveHandle If true, the manager will keep the streamable handle active until explicitly released* 是否保留(TSharedPtr<>引用計數)載入的Handle,如果保留,則需要手動釋放Handle,否則自動釋放* @param DebugName Name of this handle, will be reported in debug tools*/TSharedPtr<FStreamableHandle> RequestSyncLoad(const TArray<FStringAssetReference>& TargetsToStream, bool bManageActiveHandle = false, const FString& DebugName = TEXT("RequestSyncLoad Array"));TSharedPtr<FStreamableHandle> RequestSyncLoad(const FStringAssetReference& TargetToStream, bool bManageActiveHandle = false, const FString& DebugName = TEXT("RequestSyncLoad Single"));


FStreamableHandle由上述幾個函數調用返回,每調用一次上述幾個函數便產生一個Handle,同時將函數的參數信息(要載入的資源,回調代理等)保留到Handle中,如果bManageActiveHandle參數為true值,則將該Handle添加到FStreamableManager中的ManagedActiveHandles數組中,管理其生命周期,需要手動調用ReleaseHandle釋放資源,代碼如下:

TSharedPtr<FStreamableHandle> FStreamableManager::RequestAsyncLoad(const TArray<FStringAssetReference>& TargetsToStream, FStreamableDelegate DelegateToCall, TAsyncLoadPriority Priority, bool bManageActiveHandle, const FString& DebugName){ // Schedule a new callback, this will get called when all related async loads are completed TSharedRef<FStreamableHandle> NewRequest = MakeShareable(new FStreamableHandle()); NewRequest->CompleteDelegate = DelegateToCall; NewRequest->OwningManager = this; NewRequest->RequestedAssets = TargetsToStream; NewRequest->DebugName = DebugName; // Remove null requests NewRequest->RequestedAssets.Remove(FStringAssetReference()); // ...創建FStreamable if (bManageActiveHandle) { // This keeps a reference around until explicitly released ManagedActiveHandles.Add(NewRequest); } return NewRequest;}

所以FStreamableHandle保存了要載入的資源信息,回調函數的代理,同時提供了以下函數可以查詢當前Handle載入的資源,資源載入的狀態,重新綁定代理等。

/** If this request has finished loading, meaning all available assets were loaded and delegate was called. If assets failed to load they will still be missing *//** 是否載入完畢 */bool HasLoadCompleted() const/** If this request was cancelled. Assets may still have been loaded, but delegate will not be called *//** 是否已經取消載入 */bool WasCanceled() const/** True if load is still ongoing and we havent been cancelled *//** 是否在載入途中 */bool IsLoadingInProgress() const/** If this handle is still active, meaning it wasnt canceled or released *//** 是否已經被釋放或者被取消 */bool IsActive() const/** Returns true if this is a combined handle that depends on child handles *//** 是否有子Handle,在UAssetManager中有用到 */bool IsCombinedHandle() const/** Release this handle, called from normal gameplay code to indicate that the loaded assets are no longer needed. If called before completion will release on completion *//** 釋放Handle資源 */void ReleaseHandle();/** Cancel a request, callable from within the manager or externally. Will stop delegate from being called *//** 取消載入,如果已經載入完畢則是ReleaseHandle */void CancelHandle();/** Bind delegate that is called when load completes, only works if loading is in progress. This will overwrite any already bound delegate! *//** 綁定載入結束代理 */bool BindCompleteDelegate(FStreamableDelegate NewDelegate);/** Bind delegate that is called if handle is canceled, only works if loading is in progress. This will overwrite any already bound delegate! *//** 綁定取消載入代理 */bool BindCancelDelegate(FStreamableDelegate NewDelegate);/** Bind delegate that is called periodically as delegate updates, only works if loading is in progress. This will overwrite any already bound delegate! *//** 綁定載入更新代理 */bool BindUpdateDelegate(FStreamableUpdateDelegate NewDelegate);/** Gets list of assets references this load was started with. This will be the paths before redirectors, and not all of these are guaranteed to be loaded *//** 獲取請求載入的資源 */void GetRequestedAssets(TArray<FStringAssetReference>& AssetList) const;/** Adds all loaded assets if load has succeeded. Some entries will be null if loading failed *//** 獲取已經載入的資源 */void GetLoadedAssets(TArray<UObject *>& LoadedAssets) const;

FStreamableHandle一直以TSharedPtr<FStreamableHandle>形式傳遞,當沒有強指針保存時被自動釋放。


FStreamable代表一個UObject資源實例,其定義核心內容如下:

struct FStreamable{ /** Hard Pointer to object */ UObject* Target; /** If this object is currently being loaded */ bool bAsyncLoadRequestOutstanding; /** If this object failed to load, dont try again */ bool bLoadFailed; /** List of handles that are waiting for this to load */ TArray< TSharedRef< FStreamableHandle> > LoadingHandles; /** List of handles that are keeping this streamable in memory */ TArray< TWeakPtr< FStreamableHandle> > ActiveHandles; // ....}

FStreamableHandle保存了一次請求載入的一批資源,而這一批資源中每一個資源會在FStreamable中保存(UObject* Target;),FStreamable同時也記錄了當前哪一些Handle同時請求了該資源。FStreamable被創建的代碼如下:

TSharedPtr<FStreamableHandle> FStreamableManager::RequestAsyncLoad(const TArray<FStringAssetReference>& TargetsToStream, FStreamableDelegate DelegateToCall, TAsyncLoadPriority Priority, bool bManageActiveHandle, const FString& DebugName){ // .... TArray<FStreamable *> ExistingStreamables; ExistingStreamables.Reserve(NewRequest->RequestedAssets.Num()); for (int32 i = 0; i < NewRequest->RequestedAssets.Num(); i++) { FStreamable* Existing = StreamInternal(NewRequest->RequestedAssets[i], Priority, NewRequest); if (!Existing) { // Requested an invalid asset path NewRequest->CancelHandle(); return nullptr; } ExistingStreamables.Add(Existing); Existing->AddLoadingRequest(NewRequest); } // ....}FStreamable* FStreamableManager::StreamInternal(const FStringAssetReference& InTargetName, TAsyncLoadPriority Priority, TSharedRef<FStreamableHandle> Handle){ // ... if (!Existing->Target) { // ... if (GIsInitialLoad || ThreadContext.IsInConstructor > 0 || bForceSynchronousLoads) { FRedirectedPath RedirectedPath; UE_LOG(LogStreamableManager, Verbose, TEXT(" Static loading %s"), *TargetName.ToString()); // 同步載入 Existing->Target = StaticLoadObject(UObject::StaticClass(), nullptr, *TargetName.ToString()); // ... } else { // ... // 非同步載入 LoadPackageAsync(Package, FLoadPackageAsyncDelegate::CreateSP(Handle, &FStreamableHandle::AsyncLoadCallbackWrapper, TargetName), Priority); } } return Existing;}

另外可以看到FStreamable中保存的僅僅是UObject* Target,如何被GC管理呢?

在FStreamableManager::AddReferencedObjects()中可以找到答案,這也是為什麼FStreamableManager繼承自FGCObject的原因,也是使用FGCObject的一個很好的參考用法。代碼如下:

void FStreamableManager::AddReferencedObjects(FReferenceCollector& Collector){ // If there are active streamable handles in the editor, this will cause the user to Force Delete, which is irritating but necessary because weak pointers cannot be used here for (TStreamableMap::TConstIterator It(StreamableItems); It; ++It) { FStreamable* Existing = It.Value(); if (Existing->Target) { Collector.AddReferencedObject(Existing->Target); } } // ...}


那資源的釋放是如何管理的?

首先FStreamableHandle::ReleaseHandle()時會調用FStreamableManager::RemoveReferencedAsset(),代碼如下:

void FStreamableManager::RemoveReferencedAsset(const FStringAssetReference& Target, TSharedRef<FStreamableHandle> Handle){ if (Target.IsNull()) { return; } ensureMsgf(Handle->OwningManager == this, TEXT("RemoveReferencedAsset called on wrong streamable manager for target %s"), *Target.ToString()); // 查找指定資源對應的FStreamable FStreamable* Existing = FindStreamable(Target); // This should always be in the active handles list if (ensureMsgf(Existing, TEXT("Failed to find existing streamable for %s"), *Target.ToString())) { ensureMsgf(Existing->ActiveHandles.Remove(Handle) > 0, TEXT("Failed to remove active handle for %s"), *Target.ToString()); // 去掉內部該Handle的標記 // Try removing from loading list if its still there, this wont call the callback as its being called from cancel if (Existing->LoadingHandles.Remove(Handle)) { Handle->StreamablesLoading--; if (Existing->LoadingHandles.Num() == 0) { // All requests cancelled, remove loading flag Existing->bAsyncLoadRequestOutstanding = false; } } }}

再者在FStreamableManager::OnPreGarbageCollect()中,即每次GC之前,檢查所有的FStreamable是否還有ActiveHandle,即檢查是否還有有效的Handle(沒有強指針指向Handle即無效了)存在FStreamable中,如果沒有則釋放FStreamable,代碼如下:

void FStreamableManager::OnPreGarbageCollect(){ TSet<FStringAssetReference> RedirectsToRemove; // Remove any streamables with no active handles, as GC may have freed them for (TStreamableMap::TIterator It(StreamableItems); It; ++It) { FStreamable* Existing = It.Value(); // Remove invalid handles, the weak pointers may be pointing to removed handles for (int32 i = Existing->ActiveHandles.Num() - 1; i >= 0; i--) { TSharedPtr<FStreamableHandle> ActiveHandle = Existing->ActiveHandles[i].Pin(); if (!ActiveHandle.IsValid()) { Existing->ActiveHandles.RemoveAtSwap(i, 1, false); } } if (Existing->ActiveHandles.Num() == 0) { RedirectsToRemove.Add(It.Key()); delete Existing; It.RemoveCurrent(); } } // ...}~FStreamable(){ // Clear the loading handles for (int32 i = 0; i < LoadingHandles.Num(); i++) { LoadingHandles[i]->StreamablesLoading--; } LoadingHandles.Empty(); // Cancel active handles, this list includes the loading handles for (int32 i = 0; i < ActiveHandles.Num(); i++) { TSharedPtr<FStreamableHandle> ActiveHandle = ActiveHandles[i].Pin(); if (ActiveHandle.IsValid()) { // Full cancel isnt safe any more ActiveHandle->bCanceled = true; ActiveHandle->OwningManager = nullptr; if (!ActiveHandle->bReleased && ActiveHandle->CancelDelegate.IsBound()) { FStreamableHandle::ExecuteDelegate(ActiveHandle->CancelDelegate, ActiveHandle); } } } // ...}

後期將要介紹內容預告

FAssetPtr,TAssetPtr,TAssetSubclassOf,FStringAssetReference,FAssetData,FAssetRegistryModule等資源相關的類或數據結構。(4.18上述幾個已改名)

UAssetManager,FPrimaryAssetId,FPrimaryAssetTypeData,FPrimaryAssetData,FPrimaryAssetTypeInfo等資源管理器相關類介紹以及如何和FStreamableManager聯繫起來。

...

點贊是我最大的動力~


推薦閱讀:

[DOD Series][OGRE] Pitfalls and Design proposal for Ogre 2.0
Matrix and Transform Conversion 1/3
[GDC16] Optimizing the Graphics Pipeline with Compute
[GDC16]Global Illumination in Tom Clancys The Division
[Siggraph15] GPU-Driven Rendering Pipelines

TAG:虛幻4遊戲引擎 | 遊戲引擎 | 遊戲編程 |