[UE C++] 资源加载(四) FStreamableManager同步加载和异步加载

[UE C++] 资源加载(四) FStreamableManager

FStreamableManager是UE提供的另一种资源加载类,即可以同步加载,也能异步加载。能够提供更加精细的资源加载管理方式。外层暴露的同步加载方式有RequestSyncLoadLoadSynchronous,异步加载方式有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 TargetsToStreamFStreamableDelegate 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::ReleaseHandleFStreamableHandle::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

参考

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
引用[1]:案例测试: //同步加载单个资源LoadSynchronous FSoftObjectPath Path =FString(TEXT("Texture2D'/Game/StarterContent/Textures/T_Brick_Clay_Beveled_D.T_Brick_Clay_Beveled_D'")); UTexture2D* Image = UAssetManager::GetStreamableManager().LoadSynchronous<UTexture2D>(Path,false,nullptr); if (Image) { UE_LOG(LogTemp,Warning,TEXT("Image is %s"),*Image->GetName()); } 。 引用[2]:案例测试: //同步加载资源组 TArray<FSoftObjectPath> Paths; Paths.AddUnique(FString(TEXT("Texture2D'/Game/StarterContent/Textures/T_Brick_Clay_Beveled_N.T_Brick_Clay_Beveled_N'"))); Paths.AddUnique(FString(TEXT("Texture2D'/Game/StarterContent/Textures/T_Brick_Clay_New_D.T_Brick_Clay_New_D'"))); //注意:在资源未完成加载之前代码会在这一行暂停运行以等待资源加载完成。 TSharedPtr<FStreamableHandle> SyncStreamableHandle3 = UAssetManager::GetStreamableManager().RequestSyncLoad(Paths); if (SyncStreamableHandle3) { TArray<UObject*>LoadedAssets; SyncStreamableHandle3->GetLoadedAssets(LoadedAssets); if (LoadedAssets.Num() > 0) { for (int32 i = 0; i < LoadedAssets.Num(); i ) { UTexture2D* Image3 = Cast<UTexture2D>(LoadedAssets[i]); if (Image3) { UE_LOG(LogTemp, Warning, TEXT("Image3 is %s"), *Image3->GetName()); } } } } 。 引用[3]:测试案例介绍: FSoftObjectPath Path2 = FString(TEXT("Texture2D'/Game/StarterContent/Textures/T_Brick_Clay_Beveled_M.T_Brick_Clay_Beveled_M'")); //注意:在资源未完成加载之前代码会在这一行暂停运行以等待资源加载完成。 TSharedPtr<FStreamableHandle> SyncStreamableHandle2 = UAssetManager::GetStreamableManager().RequestSyncLoad(Path2); if (SyncStreamableHandle2) { //使用RequestSyncLoad(方法加载单个资源的时候要用GetLoadedAsset来获得FStreamableHandle中返回的资源。 UTexture2D* Image2 = Cast<UTexture2D>(SyncStreamableHandle2->GetLoadedAsset()); if (Image2) { UE_LOG(LogTemp, Warning, TEXT("Image2 is %s"), *Image2->GetName()); } } 。 问题:UE异步加载image 回答: 在提供的案例中,使用了同步加载的方式来加载UE中的图像资源。通过调用UAssetManager的LoadSynchronous方法,可以同步加载单个资源。例如,在案例[1]中,通过指定资源的路径,可以加载指定的图像资源。如果加载成功,可以通过Image指针来访问图像资源的属性,如名称。类似地,在案例[2]和案例[3]中,也展示了如何同步加载资源组和单个资源。通过使用RequestSyncLoad方法和GetLoadedAsset方法,可以加载并访问相应的图像资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MustardJim

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值