[UE C++] Carregamento de recursos (4) Carregamento síncrono e carregamento assíncrono de FStreamableManager

[UE C++] Carregamento de recursos (4) FStreamableManager

FStreamableManager é outra classe de carregamento de recursos fornecida pelo UE, que pode ser carregada de forma síncrona ou assíncrona. Pode fornecer um método de gerenciamento de carregamento de recursos mais refinado. Os métodos de carregamento síncrono expostos pela camada externa incluem RequestSyncLoad, LoadSynchronouse os métodos de carregamento assíncronos RequestAsyncLoad. A implementação do carregamento síncrono é baseada no carregamento assíncrono RequestAsyncLoad. O fluxograma de execução pode ser referido da seguinte forma:
Insira a descrição da imagem aqui

FStreamableManager existe no AssetManager e é uma classe singleton global. O método para obtê-lo é o seguinte

FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();

1. SolicitarAsyncLoad

Existem quatro sobrecargas no total e a função é definida da seguinte forma:

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")
    );

A diferença está no primeiro parâmetro e no segundo parâmetro, mas em última análise, a execução final é TArray TargetsToStream e FStreamableDelegate DelegateToCall .Os outros três são apenas uma conversão dos parâmetros de entrada. A seguir está uma explicação dos parâmetros

  • TargetsToStream : recursos especificados carregados de forma assíncrona. FSoftObjectPath
  • DelegateToCall : Delegado executado após o carregamento do recurso. Se o recurso já existir na memória, ele será executado no próximo quadro. FstreamableDelegado
  • Prioridade : a prioridade de carregamento de recursos, alta prioridade será carregada primeiro, int32, padrão 0, máximo 100
  • bManageActiveHandle : Se é necessário gerenciar manualmente a referência do FStreamableHandle ao recurso. Se for verdade, a função ReleaseHandle ou CancelHandle deve ser chamada manualmente. false, será gerenciado automaticamente
  • bStartStalled : Se deve evitar o carregamento. Se for verdade, chamar esta função não iniciará diretamente o carregamento de recursos até que FStreamableHandle->StartStalledHandle() seja chamado.
  • DebugName : DebugName do recurso, usado para Log

A seguir são apresentadas RequestAsyncLoadas principais estruturas de dados utilizadas

1.1 FStreamableHandle

FStreamableHandle é RequestAsyncLoado valor de retorno, que corresponde ao recurso solicitado para ser carregado (mantendo uma referência e evitando GC).Você pode usar este identificador para obter informações relevantes (progresso, status) do carregamento do recurso, e também pode realizar a ligação de retorno de chamada.

As interfaces de consulta comumente usadas são as seguintes

//所有请求加载的资源是否全部加载完成,如果有一个资源加载失败,则会返回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;

As interfaces de gerenciamento comumente usadas são as seguintes

/**
* 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);

definição de identificador

DECLARE_DELEGATE(FStreamableDelegate);
DECLARE_DELEGATE_OneParam(FStreamableUpdateDelegate, TSharedRef<struct FStreamableHandle>);

FStreamableHandle manterá uma referência ao recurso solicitado para ser carregado.Se o identificador existir,o recurso não será GCed. Quando bManageActiveHandle = verdadeiro,este FStreamableHandle será adicionado à matriz ManagedActiveHandles de FStreamableManager.A menos que ReleaseHandle ou CancelHandle sejam explicitamente chamados,ManagedActiveHandles manterá sempre uma referência ao identificador,e o recurso estará sempre na memória (a menos que o GC seja forçado a forçar o recurso)

1.2 Fstreamível

FStreamable é uma estrutura usada internamente pelo FStreamableManager e não está aberta ao mundo exterior. Corresponde a uma instância de recurso. FStreamableManager gerencia recursos por meio de FStreamable. Todos os FStreamableHandles que fazem referência a este FStreamable existem dentro dele.

A estrutura de dados interna do StreamableManager corresponde a um recurso e também descreve o status de carregamento de um caminho de objeto. Uma solicitação conterá vários recursos e várias solicitações serão repetidas. Portanto, StreamableManager precisa usar FStreamable internamente para gerenciar o status de um único recurso para evitar o envio repetido de tarefas para AsyncLoadThread.

Seus dados principais internos são os seguintes

/** 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;

Vamos começar a análise RequestAsyncLoadda implementação interna, primeiro olhando o código fonte (algumas partes sem importância são omitidas)

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;
}

Internamente, um FStreamableHandle é criado e inicializado. Se bManageActiveHandle=true, será adicionado a ManagedActiveHandles.

2. IniciarHandleRequests

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);
		}
	}
}

Primeiro, é criado um TArray<FStreamable *> ExistingStreamables , que corresponderá um a um aos recursos solicitados por FStreamableHandle. Conforme mencionado anteriormente: FStreamableManager gerencia recursos por meio de FStreamable, e o processo de carregamento também é executado em um único objeto FStreamable. Durante um processo de carregamento assíncrono,FStreamableManager criará objetos FStreamable correspondentes de acordo com os recursos solicitados.Um recurso corresponde a um objeto FStreamable exclusivo, e todos os objetos FStreamable criados serão colocados em StreamableItems (um TMap). Se o recurso solicitado já tiver um objeto FStreamable correspondente, FStreamableManager não criará repetidamente um novo objeto FStreamable, mas apenas adicionará uma referência a este FStreamableHandle aos LoadHandles e ActiveHandles deste objeto FStreamable.

O relacionamento entre FStreamable e FStreamableHandle pode ser referido da seguinte forma (de: UE4 FStreamableManager )
Insira a descrição da imagem aqui

A primeira solicitação de envio criará FStreamable1 e FStreamable2. O objectpath1 na segunda solicitação de envio será repetido, portanto, apenas um registro Handle será adicionado a FStreamable1. FStreamableHandle registra o caminho do recurso solicitado e FStreamable registra o caminho do recurso, o Handle (referência fraca) que solicita o recurso, a instância UObject do recurso, etc., para que possam indexar um ao outro e facilitar o gerenciamento. Observe que FStreamable só é usado dentro do StreamableManager e está oculto do lado de fora. FStreamableHandle só pode ser mapeado através de caminhos de recursos.

O código-fonte de AddLoadingRequest é o seguinte: StreamablesLoading representa o número de objetos FStreamable que FStreamableHandle precisa carregar. 0 significa que o carregamento foi bem-sucedido.

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++;
}

StartHandleRequestsNo segundo loop for, o objeto FStreamable solicitado foi obtido neste momento, o que significa que o processo de carregamento assíncrono foi concluído. Ele será chamado CheckCompletedRequestspara objetos FStreamable (principalmente aqueles que já existem em StreamableItems e foram carregados). objetos FStreamable criados durante este processo de carregamento O objeto foi processado na função de retorno de chamada) para processamento, o código fonte é o seguinte

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. StreamInterno

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;
}

FindInMemoryStaticFindObjectA pesquisa é chamada internamente . AsyncLoadCallbackWrapperÉ a função de retorno de chamada de carregamento de FStreamableManager, empacotada AsyncLoadCallbacke definida como segue

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É o processo final de carregamento e é definido como segue

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. Solicitar Sincronização de Carga

RequestSyncLoadÉ um método de carregamento síncrono, mas a camada inferior depende do carregamento assíncrono RequestAsyncLoad. Existem duas sobrecargas de função.

TSharedPtr<FStreamableHandle> RequestSyncLoad(

    TArray<FSoftObjectPath> TargetsToStream, 
    //const FSoftObjectPath& TargetToStream

    bool bManageActiveHandle = false, 
    FString DebugName = TEXT("RequestSyncLoad Array")
    );

A chamada final é a versão TArray<FSoftObjectPath> TargetsToStream . A função é definida da seguinte forma

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. Carga síncrona

LoadSynchronousÉ outro método de carregamento síncrono, chamado internamente RequestSyncLoad. É uma operação de modelagem para conveniência de uso. Existem 4 sobrecargas, principalmente para satisfazer a modelagem de TSoftObjectPtr<T>, TSoftClassPtr<T>, eFSoftObjectPath

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;
}

A última chamada é a primeira versão não padronizada, que é definida da seguinte forma

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;
}

O parâmetro de entrada RequestHandlePointer aqui é na verdade o ponteiro de FStreamableHandle.Se passado, RequestSyncLoado identificador de gerenciamento de recursos FStreamableHandle será atribuído.

6.FStreamableManager

Finalmente, vamos analisar brevemente o FStreamableManager. FStreamableManager existe no AssetManager e é uma classe singleton global. O método para obtê-lo é o seguinte

FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();

Herdados de FGCObject , os recursos são gerenciados e referenciados internamente por meio de objetos FStreamable para evitar GC.

struct ENGINE_API FStreamableManager : public FGCObject

FGCObject é uma classe pai abstrata projetada para classes não-UObject fazer referência a objetos UObject e pode ser detectada pelo mecanismo GC (detecção de acessibilidade) (normalmente, quando classes não-UObject fazem referência a objetos UObject, o GC não pode detectá-lo. Este UObject é considerado como não sendo referenciado e pode ser reciclado no próximo GC), a subclasse deve implementá-lo AddReferencedObjects, e FStreamableManager é implementado da seguinte forma, que é adicionar referências aos Targets de todos os objetos FStreamable no contêiner StreamableItems.

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);
		}
	}
}

OnPreGarbageCollectFStreamableManager será vinculado a um delegado multicast antes da coleta de lixo quando for construído e será chamado antes de cada coleta de lixo.OnPreGarbageCollect

FStreamableManager::FStreamableManager()
{
	FCoreUObjectDelegates::GetPreGarbageCollectDelegate().AddRaw(this, &FStreamableManager::OnPreGarbageCollect);
	bForceSynchronousLoads = false;
	ManagerName = TEXT("StreamableManager");
}

OnPreGarbageCollectA definição é a seguinte, que consiste em detectar se o contêiner ActiveHandles de FStreamable contém todos os ponteiros fracos inválidos. Nesse caso, ele será excluído do contêiner StreamableItems e excluirá 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();
			}
		}
	}
}

Se carregado de forma assíncrona,bManageActiveHandle = true,este FStreamableHandle será adicionado ao array ManagedActiveHandles do FStreamableManager.A menos que ReleaseHandle ou CancelHandle sejam chamados explicitamente,ManagedActiveHandles sempre manterá uma referência ao identificador, portanto, o fraco correspondente a este identificador no contêiner ActiveHandles do FStreamable. a operação de conversão de um ponteiro em um ponteiro compartilhado é sempre legal, portanto, o recurso não será automaticamente GCed.

FStreamableHandle::ReleaseHandle e FStreamableHandle::CancelHandle excluirão FStreamableHandle do contêiner ActiveHandles de todos os objetos FStreamable e do contêiner ManagedActiveHandles de FStreamableManager. Esta é a chave para o recurso que está sendo GC. O método específico é chamar 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 é um método que pode liberar rapidamente o FStreamableHandle relacionado ao recurso especificado (este FStreamableHandle precisa estar no contêiner ManagedActiveHandles do FStreamableManager).Especificamente, ele atravessa e filtra o contêiner FStreamable ActiveHandles correspondente ao FSoftObjectPath especificado e chama FStreamableHandle ::ReleaseHandle. Observe que desta forma, mesmo que existam outras solicitações de recursos para FStreamableHandle, ele será liberado (causando facilmente exclusão acidental).

/** 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();
		}
	}
}

A função de FStreamableManager::GetActiveHandles é obter alguns FStreamableHandles válidos no contêiner ActiveHandles de FStreamable correspondente ao FSoftObjectPath especificado. O terceiro parâmetro bOnlyManagedHandles representa se o FStreamableHandle obtido deve existir no contêiner ManagedActiveHandles de FStreamableManager.

/** 
 * 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;
}

A função de FStreamableManager::IsAsyncLoadComplete é determinar se o recurso correspondente ao FSoftObjectPath especificado foi carregado e é julgado internamente por meio de seu FStreamable correspondente.

/** 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;
}

afinal

Este artigo apresenta o carregamento síncrono e o carregamento assíncrono no FStreamableManager, com foco no princípio de implementação. Se houver algum erro, você pode corrigi-lo na área de comentários. Versão do motor: 4.27

referência

Acho que você gosta

Origin blog.csdn.net/qq_52179126/article/details/130309900
Recomendado
Clasificación