[UE C++] 资源加载(四) FStreamableManager
FStreamableManager是UE提供的另一种资源加载类,即可以同步加载,也能异步加载。能够提供更加精细的资源加载管理方式。外层暴露的同步加载方式有RequestSyncLoad
、LoadSynchronous
,异步加载方式有RequestAsyncLoad
。同步加载的实现基于异步加载RequestAsyncLoad
,执行流程图可参考如下:
FStreamableManager存在于AssetManager中,是一个全局单例类,获取方式如下
FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
1. RequestAsyncLoad
一共有四种重载,函数定义如下:
TSharedPtr<FStreamableHandle> RequestAsyncLoad(
TArray<FSoftObjectPath> TargetsToStream,
//const FSoftObjectPath& TargetToStream,
FStreamableDelegate DelegateToCall = FStreamableDelegate(),
//TFunction<void()>&& Callback ,
TAsyncLoadPriority Priority = DefaultAsyncLoadPriority,
bool bManageActiveHandle = false,
bool bStartStalled = false,
FString DebugName = TEXT("RequestAsyncLoad ArrayDelegate")
);
差异在于第一个参数和第二个参数,但归根结底最后执行的是TArray TargetsToStream与FStreamableDelegate DelegateToCall,其他三种只是是对输入参数进行了一次转化。下面是参数解释
- TargetsToStream: 异步加载的指定资源。FSoftObjectPath
- DelegateToCall: 资源加载完成后执行的Delegate,若资源已存在与内存中,下一帧就会执行。FStreamableDelegate
- Priority:资源加载的优先级,高优先级的会被优先加载,int32,默认0,最大100
- bManageActiveHandle:是否需要手动管理FStreamableHandle对资源的引用,若为true,必须手动调用ReleaseHandle或CancelHandle函数。false,则会自动管理
- bStartStalled:是否阻止加载,若为true,调用此函数不会直接开始资源加载,直到调用 FStreamableHandle->StartStalledHandle()
- DebugName:资源的DebugName,用于Log
下面介绍RequestAsyncLoad
会用到的主要数据结构
1.1 FStreamableHandle
FStreamableHandle是RequestAsyncLoad
的返回值,与请求加载的资源相对应(持有引用,防止GC),可以通过该句柄来获得资源加载的相关信息(进度,状态),也可进行回调绑定。
常用的查询接口如下
//所有请求加载的资源是否全部加载完成,如果有一个资源加载失败,则会返回false。可能在回调函数执行之前返回true,因为回调函数可能处于等待队列之中
bool HasLoadCompleted();
//是否取消加载
bool WasCanceled();
//是否处于加载过程中(未被Cancel)
bool IsLoadingInProgress()
//句柄是否Active(未被Cancel和Released)
bool IsActive();
//加载是否被Stall
bool IsStalled();
/** 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<FSoftObjectPath>& AssetList) const;
/** Adds all loaded assets if load has succeeded. Some entries will be null if loading failed */
//获取已经加载的资源,加载失败的资源为null
void GetLoadedAssets(TArray<UObject *>& LoadedAssets) const;
/** Returns progress as a value between 0.0 and 1.0. */
//获取加载进度,从0.0-1.0
float GetProgress() const;
常用的管理接口如下
/**
* Release this handle. This can be called from normal gameplay code to indicate that the loaded assets are no longer needed
* This will be called implicitly if all shared pointers to this handle are destroyed
* If called before the completion delegate, the release will be delayed until after completion
*/
释放句柄对资源的引用。会在对此句柄的引用数量为0时隐式调用。如果在资源加载完成之前调用,此函数会在资源加载完成之后才会被真正执行(completion callback会触发)
void ReleaseHandle();
/**
* Cancel a request, callable from within the manager or externally
* This will immediately release the handle even if it is still in progress, and call the cancel callback if bound
* This stops the completion callback from happening, even if it is in the delayed callback queue
*/
//取消资源加载请求。立马释放对资源的引用即使资源已经在加载过程中,会触发cancel callback。如果completion callback未被触发,则会取消completion callback,如果已被触发则相当于执行ReleaseHandle()。但要注意,这个操作不会取消资源加载本身
void CancelHandle();
/** Tells a stalled handle to start its actual request. */
//如果资源加载被Stall,则会开始进行资源加载
void StartStalledHandle();
/** Bind delegate that is called when load completes, only works if loading is in progress. This will overwrite any already bound delegate! */
//绑定completion callback,会覆盖已经存在的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! */
//绑定cancel callback
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! */
//绑定Update callback
bool BindUpdateDelegate(FStreamableUpdateDelegate NewDelegate);
句柄的定义
DECLARE_DELEGATE(FStreamableDelegate);
DECLARE_DELEGATE_OneParam(FStreamableUpdateDelegate, TSharedRef<struct FStreamableHandle>);
FStreamableHandle会对请求加载的资源持有一份引用,句柄存在,资源就不会被GC。当bManageActiveHandle = true时,此FStreamableHandle会被添加进FStreamableManager的ManagedActiveHandles数组中,除非显式调用ReleaseHandle或CancelHandle,否则ManagedActiveHandles会一直保持对该句柄的引用,资源会一直在内存中(除非强制GC该资源)
1.2 FStreamable
FStreamable是FStreamableManager内部使用的结构,不对外开放。对应于一个资源实例。FStreamableManager通过FStreamable对资源进行管理。其内部存在了引用了该FStreamable的所有FStreamableHandle
StreamableManager的内部使用数据结构,对应一个资源,也描述一个objectpath的加载状态。一个Request会包含多个资源,而且多个Request会有重复,因此StreamableManager内部需要用FStreamable管理单个资源的状态,避免重复向AsyncLoadThread提交任务。
其内部核心数据如下
/** Hard Pointer to object */
UObject* Target;
/** If this object is currently being loaded */
bool bAsyncLoadRequestOutstanding;
/** If this object failed to load, don't try again */
bool bLoadFailed;
/** List of handles that are waiting for this to load. The same handle may be here multiple times with redirectors */
//正在等待该资源加载的FStreamableHandle,资源加载过程中会保持对FStreamableHandle的引用,加载完成会置空
TArray< TSharedRef< FStreamableHandle> > LoadingHandles;
/** List of handles that are keeping this streamable in memory. The same handle may be here multiple times */
//当前持有该FStreamable的FStreamableHandle,里面存在有效Handle,资源就不会被GC
TArray< TWeakPtr< FStreamableHandle> > ActiveHandles;
下面开始分析RequestAsyncLoad
的内部实现,先看源代码(省略了一部分不重要的)
TSharedPtr<FStreamableHandle> FStreamableManager::RequestAsyncLoad(TArray<FSoftObjectPath> TargetsToStream, FStreamableDelegate DelegateToCall, TAsyncLoadPriority Priority, bool bManageActiveHandle, bool bStartStalled, 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 = MoveTemp(TargetsToStream);
NewRequest->Priority = Priority;
int32 NumValidRequests = NewRequest->RequestedAssets.Num();
//对FSoftObjectPath进行检测,避免指向不存在的资源
TSet<FSoftObjectPath> TargetSet;
TargetSet.Reserve(NumValidRequests);
·······
if (TargetSet.Num() != NewRequest->RequestedAssets.Num())
{
·····
NewRequest->RequestedAssets = TargetSet.Array();
}
if (bManageActiveHandle)
{
// This keeps a reference around until explicitly released
ManagedActiveHandles.Add(NewRequest);
}
if (bStartStalled)
{
NewRequest->bStalled = true;
}
else
{
StartHandleRequests(NewRequest);
}
return NewRequest;
}
内部其实就是创建了一个FStreamableHandle并对其进行初始化,若bManageActiveHandle=true,则会将其添加进ManagedActiveHandles中
2. StartHandleRequests
void FStreamableManager::StartHandleRequests(TSharedRef<FStreamableHandle> Handle)
{
TArray<FStreamable *> ExistingStreamables;
ExistingStreamables.Reserve(Handle->RequestedAssets.Num());
for (int32 i = 0; i < Handle->RequestedAssets.Num(); i++)
{
FStreamable* Existing = StreamInternal(Handle->RequestedAssets[i], Handle->Priority, Handle);
check(Existing);
ExistingStreamables.Add(Existing);
Existing->AddLoadingRequest(Handle);
}
// Go through and complete loading anything that's already in memory, this may call the callback right away
for (int32 i = 0; i < Handle->RequestedAssets.Num(); i++)
{
FStreamable* Existing = ExistingStreamables[i];
if (Existing && (Existing->Target || Existing->bLoadFailed))
{
Existing->bAsyncLoadRequestOutstanding = false;
CheckCompletedRequests(Handle->RequestedAssets[i], Existing);
}
}
}
首先创建了一个 TArray<FStreamable *> ExistingStreamables,里面将会与FStreamableHandle请求的资源一一对应。前文提到过:FStreamableManager通过FStreamable对资源进行管理,加载过程也是对于单个FStreamable对象来进行的。一次异步加载过程中,FStreamableManager会根据请求的资源为之创建对应的FStreamable对象,一个资源对应唯一一个FStreamable对象,并将所有已经创建的FStreamable对象放进 StreamableItems 中(一个TMap)。若请求的资源已经存在相应的FStreamable对象,FStreamableManager不会重复创建新的FStreamable对象,只会在此FStreamable对象的LoadingHandles与ActiveHandles加入这个FStreamableHandle的引用。
FStreamable与FStreamableHandle的关系可参考如下(来自:UE4 FStreamableManager)
第一次提交请求会创建FStreamable1和FStreamable2,第二次提交请求中的objectpath1是重复的,因此只会在FStreamable1中添加一条Handle记录。FStreamableHandle记录请求的资源路径,FStreamable记录资源路径、请求该资源的Handle(弱引用)、该资源的UObject实例等,这样它们就能相互索引,方便管理。注意FStreamable只在StreamableManager内部使用,对外部隐藏,FStreamableHandle也只能通过资源路径进行对应。
AddLoadingRequest源码如下,StreamablesLoading代表FStreamableHandle需要加载的FStreamable对象数量,为0代表加载成功
void AddLoadingRequest(TSharedRef<FStreamableHandle> NewRequest)
{
// With redirectors we can end up adding the same handle multiple times, this is unusual but supported
ActiveHandles.Add(NewRequest);
LoadingHandles.Add(NewRequest);
NewRequest->StreamablesLoading++;
}
StartHandleRequests
第二个for循环,此时请求的FStreamable对象已经获得,代表此异步加载过程完成,会调用CheckCompletedRequests
来对FStreamable对象(主要是已经存在于StreamableItems中且加载完成的,此次加载过程中新创建的FStreamable对象在回调函数中已经处理了)进行处理,源码如下
void FStreamableManager::CheckCompletedRequests(const FSoftObjectPath& Target, struct FStreamable* Existing)
{
//加载完成的FStreamableHandle,会调用completion callback
TArray<TSharedRef<FStreamableHandle>> HandlesToComplete;
//需要Release的FStreamableHandle
TArray<TSharedRef<FStreamableHandle>> HandlesToRelease;
for (TSharedRef<FStreamableHandle>& Handle : Existing->LoadingHandles)
{
// Decrement related requests, and call delegate if all are done and request is still active
Handle->StreamablesLoading--;
//StreamablesLoading == 0,代表该FStreamableHandle已经完成资源加载
if (Handle->StreamablesLoading == 0)
{
if (Handle->bReleaseWhenLoaded)
{
HandlesToRelease.Add(Handle);
}
HandlesToComplete.Add(Handle);
}
}
//LoadingHandles置为空
Existing->LoadingHandles.Empty();
for (TSharedRef<FStreamableHandle>& Handle : HandlesToComplete)
{
Handle->CompleteLoad();
}
for (TSharedRef<FStreamableHandle>& Handle : HandlesToRelease)
{
Handle->ReleaseHandle();
}
}
3. StreamInternal
FStreamable* FStreamableManager::StreamInternal(const FSoftObjectPath& InTargetName, TAsyncLoadPriority Priority, TSharedRef<FStreamableHandle> Handle)
{
······
//在StreamableItems中查找,检测是否已经加载进内存中
FStreamable* Existing = StreamableItems.FindRef(TargetName);
if (Existing)
{
if (Existing->bAsyncLoadRequestOutstanding)
{
········
//处于异步加载过程中,不会返回
}
if (Existing->Target)
{
//已经存在于StreamableItems中,返回FStreamable
return Existing;
}
}
else
{
//StreamableItems不存在此FStreamable,创建新的FStreamable对象并放进StreamableItems中
Existing = StreamableItems.Add(TargetName, new FStreamable());
}
//希望在内存中能找到该FStreamable对应的资源对象
if (!Existing->bAsyncLoadRequestOutstanding)
{
FindInMemory(TargetName, Existing);
}
//内存中不存在需要的FStreamable对象,启动加载
if (!Existing->Target)
{
········
// 如果异步加载不安全或被强制开启,我们必须执行同步加载,这将刷新所有异步加载
if (GIsInitialLoad || ThreadContext.IsInConstructor > 0 || bForceSynchronousLoads)
{
······
//同步加载
Existing->Target = StaticLoadObject(UObject::StaticClass(), nullptr, *TargetName.ToString());
······
}
else
{
·······
//异步加载
Existing->bAsyncLoadRequestOutstanding = true;
Existing->bLoadFailed = false;
int32 RequestId = LoadPackageAsync(Package, FLoadPackageAsyncDelegate::CreateSP(Handle, &FStreamableHandle::AsyncLoadCallbackWrapper, TargetName), Priority);
}
}
return Existing;
}
FindInMemory
内部调用了StaticFindObject
进行查找。AsyncLoadCallbackWrapper
是FStreamableManager的加载回调函数,包裹了AsyncLoadCallback
,定义如下
void FStreamableHandle::AsyncLoadCallbackWrapper(const FName& PackageName, UPackage* Package, EAsyncLoadingResult::Type Result, FSoftObjectPath TargetName)
{
// Needed so we can bind with a shared pointer for safety
if (OwningManager)
{
//加载完成,调用completion callback
OwningManager->AsyncLoadCallback(TargetName);
//若未加载完成,调用update callback
if (!HasLoadCompleted())
{
CallUpdateDelegate();
}
}
}
AsyncLoadCallback
是加载的最后流程,定义如下
void FStreamableManager::AsyncLoadCallback(FSoftObjectPath TargetName)
{
//找到StreamableItems中的FStreamable对象,注意此时加载的UObject还未被写入FStreamable对象中
FStreamable* Existing = FindStreamable(TargetName);
if (Existing)
{
if (Existing->bAsyncLoadRequestOutstanding)
{
Existing->bAsyncLoadRequestOutstanding = false;
if (!Existing->Target)
{
//在内存中查找FStreamable对应的资源对象,并将其写入FStreamable对象的Target中
FindInMemory(TargetName, Existing);
}
//对FStreamable对象做处理,源码分析见上
CheckCompletedRequests(TargetName, Existing);
}
········
}
}
4. RequestSyncLoad
RequestSyncLoad
是一种同步加载方式,但底层依托的还是异步加载RequestAsyncLoad
,有两个函数重载
TSharedPtr<FStreamableHandle> RequestSyncLoad(
TArray<FSoftObjectPath> TargetsToStream,
//const FSoftObjectPath& TargetToStream
bool bManageActiveHandle = false,
FString DebugName = TEXT("RequestSyncLoad Array")
);
最后调用的是 TArray<FSoftObjectPath> TargetsToStream版本,函数定义如下
TSharedPtr<FStreamableHandle> FStreamableManager::RequestSyncLoad(TArray<FSoftObjectPath> TargetsToStream, bool bManageActiveHandle, FString DebugName)
{
//这里会进行判断,最后StreamInternal中进行同步加载还是进行异步加载(堵塞)实现同步加载
bForceSynchronousLoads = IsInAsyncLoadingThread() || IsEventDrivenLoaderEnabled() || !IsAsyncLoading();
// 执行异步加载并等待完成。在某些情况下,由于安全问题,这将执行同步加载
TSharedPtr<FStreamableHandle> Request = RequestAsyncLoad(MoveTemp(TargetsToStream), FStreamableDelegate(), AsyncLoadHighPriority, bManageActiveHandle, false, MoveTemp(DebugName));
//同步加载具有最高优先级 AsyncLoadHighPriority,所以加载完成后需要设为false,防止影响其它异步加载正常进行
bForceSynchronousLoads = false;
if (Request.IsValid())
{
//异步加载堵塞,直到加载完成,通常比LoadObject更快
EAsyncPackageState::Type Result = Request->WaitUntilComplete();
·····
}
return Request;
}
5. LoadSynchronous
LoadSynchronous
是另一种同步加载方式,内部调用了RequestSyncLoad
。是为了方便使用而进行的模板化操作,共有4种重载,主要是为了满足TSoftObjectPtr<T>
、TSoftClassPtr<T>
、FSoftObjectPath
的模板化
UObject* LoadSynchronous(const FSoftObjectPath& Target, bool bManageActiveHandle = false, TSharedPtr<FStreamableHandle>* RequestHandlePointer = nullptr);
/** Typed wrappers */
template< typename T >
T* LoadSynchronous(const FSoftObjectPath& Target, bool bManageActiveHandle = false, TSharedPtr<FStreamableHandle>* RequestHandlePointer = nullptr)
{
return Cast<T>(LoadSynchronous(Target, bManageActiveHandle, RequestHandlePointer) );
}
template< typename T >
T* LoadSynchronous(const TSoftObjectPtr<T>& Target, bool bManageActiveHandle = false, TSharedPtr<FStreamableHandle>* RequestHandlePointer = nullptr)
{
return Cast<T>(LoadSynchronous(Target.ToSoftObjectPath(), bManageActiveHandle, RequestHandlePointer));
}
template< typename T >
TSubclassOf<T> LoadSynchronous(const TSoftClassPtr<T>& Target, bool bManageActiveHandle = false, TSharedPtr<FStreamableHandle>* RequestHandlePointer = nullptr)
{
TSubclassOf<T> ReturnClass;
ReturnClass = Cast<UClass>(LoadSynchronous(Target.ToSoftObjectPath(), bManageActiveHandle, RequestHandlePointer));
return ReturnClass;
}
最后调用的是第一个非模板化的版本,其定义如下
UObject* FStreamableManager::LoadSynchronous(const FSoftObjectPath& Target, bool bManageActiveHandle, TSharedPtr<FStreamableHandle>* RequestHandlePointer)
{
TSharedPtr<FStreamableHandle> Request = RequestSyncLoad(Target, bManageActiveHandle, FString::Printf(TEXT("LoadSynchronous of %s"), *Target.ToString()));
if (RequestHandlePointer)
{
(*RequestHandlePointer) = Request;
}
if (Request.IsValid())
{
UObject* Result = Request->GetLoadedAsset();
if (!Result)
{
UE_LOG(LogStreamableManager, Verbose, TEXT("LoadSynchronous failed for load of %s! File is missing or there is a loading system problem"), *Target.ToString());
}
return Result;
}
return nullptr;
}
这里的输入参数RequestHandlePointer其实就是FStreamableHandle的指针,若传入则会被赋值RequestSyncLoad
的资源管理句柄FStreamableHandle
6. FStreamableManager
最后在简要分析一下FStreamableManager。FStreamableManager存在于AssetManager中,是一个全局单例类,获取方式如下
FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
继承于FGCObject,内部通过FStreamable对象对资源产生管理、引用,防止GC。
struct ENGINE_API FStreamableManager : public FGCObject
FGCObject是为非UObject类对UObject对象产生引用且能被GC机制检测(可达性检测)到设计的一种抽象父类(通常情况下,非UObject类对UObject对象产生引用,GC检测不到,视为此UObject没有被引用,可能在下次GC时被回收),子类必须实现AddReferencedObjects
,FStreamableManager实现如下,就是将StreamableItems容器内的所有FStreamable对象的Target加入引用。
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);
}
}
for (TStreamableRedirects::TIterator It(StreamableRedirects); It; ++It)
{
FRedirectedPath& Existing = It.Value();
if (Existing.LoadedRedirector)
{
Collector.AddReferencedObject(Existing.LoadedRedirector);
}
}
}
FStreamableManager在构造时会将OnPreGarbageCollect
绑定到垃圾回收前的一个多播委托,在每次垃圾回收前就会调用OnPreGarbageCollect
FStreamableManager::FStreamableManager()
{
FCoreUObjectDelegates::GetPreGarbageCollectDelegate().AddRaw(this, &FStreamableManager::OnPreGarbageCollect);
bForceSynchronousLoads = false;
ManagerName = TEXT("StreamableManager");
}
OnPreGarbageCollect
的定义如下,就是检测FStreamable的ActiveHandles容器是否包含的全是无效弱指针,若是,则会从StreamableItems容器将其删除,并delete FStreamable
void FStreamableManager::OnPreGarbageCollect()
{
TSet<FSoftObjectPath> RedirectsToRemove;
TArray<FStreamable*> StreamablesToDelete;
// 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());
StreamablesToDelete.Add(Existing);
It.RemoveCurrent();
}
}
for (FStreamable* StreamableToDelete : StreamablesToDelete)
{
delete StreamableToDelete;
}
if (RedirectsToRemove.Num() > 0)
{
for (TStreamableRedirects::TIterator It(StreamableRedirects); It; ++It)
{
if (RedirectsToRemove.Contains(It.Value().NewPath))
{
It.RemoveCurrent();
}
}
}
}
若异步加载,bManageActiveHandle = true,此FStreamableHandle会被添加进FStreamableManager的ManagedActiveHandles数组中,除非显式调用ReleaseHandle或CancelHandle,否则ManagedActiveHandles会一直保持对该句柄的引用,那么FStreamable的ActiveHandles容器中对应此句柄的弱指针转共享指针的操作一直是合法的,所以资源不会被自动GC。
FStreamableHandle::ReleaseHandle 与 FStreamableHandle::CancelHandle 会将FStreamableHandle从所有FStreamable对象的ActiveHandles容器和FStreamableManager的ManagedActiveHandles容器中删除,这是资源能被GC的关键,具体做法是调用了RemoveReferencedAsset
。
void FStreamableHandle::ReleaseHandle()
{
·······
if (bLoadCompleted)
{
······
// Remove from referenced list
for (const FSoftObjectPath& AssetRef : RequestedAssets)
{
OwningManager->RemoveReferencedAsset(AssetRef, SharedThis);
}
// 从ManagedActiveHandles容器内删除
OwningManager->ManagedActiveHandles.Remove(SharedThis);
······
}
}
void FStreamableManager::RemoveReferencedAsset(const FSoftObjectPath& Target, TSharedRef<FStreamableHandle> Handle)
{
if (Target.IsNull())
{
return;
}
//获得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()))
{
//从ActiveHandles容器内删除
ensureMsgf(Existing->ActiveHandles.Remove(Handle) > 0, TEXT("Failed to remove active handle for %s"), *Target.ToString());
// 尝试从LoadingHandles容器内删除, 不会调用completion callback,因为是被CancelHandle()调用
int32 LoadingRemoved = Existing->LoadingHandles.Remove(Handle);
if (LoadingRemoved > 0)
{
Handle->StreamablesLoading -= LoadingRemoved;
if (Existing->LoadingHandles.Num() == 0)
{
// All requests cancelled, remove loading flag
Existing->bAsyncLoadRequestOutstanding = false;
}
}
}
}
FStreamableManager::Unload 是提供的一种可以快速释放指定资源相关的FStreamableHandle的方法(这个FStreamableHandle需要在FStreamableManager的ManagedActiveHandles容器中),具体来说就是对指定的FSoftObjectPath对应的FStreamable的ActiveHandles容器进行遍历筛选,调用FStreamableHandle::ReleaseHandle。注意这种方式即使FStreamableHandle还存在其它资源请求,也会将其释放(容易造成误删)。
/** This will release any managed active handles pointing to the target soft object path, even if they include other requested assets in the same load */
void FStreamableManager::Unload(const FSoftObjectPath& Target)
{
check(IsInGameThread());
TArray<TSharedRef<FStreamableHandle>> HandleList;
if (GetActiveHandles(Target, HandleList, true))
{
for (TSharedRef<FStreamableHandle> Handle : HandleList)
{
Handle->ReleaseHandle();
}
}
}
FStreamableManager::GetActiveHandles 作用是获得指定FSoftObjectPath对应的FStreamable的ActiveHandles容器内的一些有效FStreamableHandle,第三个参数bOnlyManagedHandles代表获得的FStreamableHandle是否必须存在于FStreamableManager的ManagedActiveHandles容器内
/**
* Gets list of handles that are directly referencing this asset, returns true if any found.
* Combined Handles will not be returned by this function.
*
* @param Target Asset to get active handles for
* @param HandleList Fill in list of active handles
* @param bOnlyManagedHandles If true, only return handles that are managed by this manager, other active handles are skipped
*/
bool FStreamableManager::GetActiveHandles(const FSoftObjectPath& Target, TArray<TSharedRef<FStreamableHandle>>& HandleList, bool bOnlyManagedHandles) const
{
FStreamable* Existing = FindStreamable(Target);
if (Existing && Existing->ActiveHandles.Num() > 0)
{
for (TWeakPtr<FStreamableHandle> WeakHandle : Existing->ActiveHandles)
{
TSharedPtr<FStreamableHandle> Handle = WeakHandle.Pin();
if (Handle.IsValid())
{
ensure(Handle->OwningManager == this);
TSharedRef<FStreamableHandle> HandleRef = Handle.ToSharedRef();
if (!bOnlyManagedHandles || ManagedActiveHandles.Contains(HandleRef))
{
// Only add each handle once, we can have duplicates in the source list
HandleList.AddUnique(HandleRef);
}
}
}
return HandleList.Num() > 0;
}
return false;
}
FStreamableManager::IsAsyncLoadComplete 作用是判断指定FSoftObjectPath对应的资源是否加载完成,内部通过其对应的FStreamable来进行判断。
/** Returns true if all pending async loads have finished for this target */
bool FStreamableManager::IsAsyncLoadComplete(const FSoftObjectPath& Target) const
{
·······
FStreamable* Existing = FindStreamable(Target);
return !Existing || !Existing->bAsyncLoadRequestOutstanding;
}
最后
本文介绍了FStreamableManager内的同步加载和异步加载,侧重点在于实现原理,如果有错误,欢迎大家在评论区进行指正。引擎版本:4.27