[UE C++] 资源加载(二) 查找资源——FindObject

[UE C++] 资源加载(二) 查找资源——FindObject

上面文章最后提到要介绍同步和异步加载,但觉得在这之前还需介绍一下FindObjectXXXX等查找资源的API

常用的有FindObjectFindObjectFastFindObjectCheckedFindObjectSafeFSoftObjectPath::ResolveObject,它们的调用关系大致如下
[UE C++] 资源加载(二) 查找资源——FindObject_第1张图片

下面以FindObject为例来分析一下各阶段所做的工作(以下的顺序为它的调用过程),在文章末尾会给出使用示列

1. FindObject

作用: 在内存中查找对象,找到就会返回,找不到会返回nullptr,不会触发加载。

template< class T > 
inline T* FindObject( UObject* Outer, const TCHAR* Name, bool ExactClass=false )
{
	return (T*)StaticFindObject( T::StaticClass(), Outer, Name, ExactClass );
}

FindObject就是模板化的StaticFindObject,输入参数作用如下:

  • Outer:如果传入了Outer,就会在Outer所在的Package下面找对应的资源对象,如果没有Outer(ANY_PACKAGE)就会在全局找这个资源对象
  • Name: Object的唯一标识 NamePrivate ,可通过Object->GetFName()获取。(这个Name也可以是引用路径,后面做分析)
  • ExactClass:是否确切匹配,false会匹配子类

2. StaticFindObject

作用: 核心功能就是解析传入的OrigInName(Name),设置InObjectPackage(Outer),这个就是FindObject等执行流有StaticFindObject的函数支持传入引用路径的原因,核心代码如下

UObject* StaticFindObject( UClass* ObjectClass, UObject* InObjectPackage, const TCHAR* OrigInName, bool ExactClass )
{
	·······

	// Resolve the object and package name.
	const bool bAnyPackage = InObjectPackage==ANY_PACKAGE;
	UObject* ObjectPackage = bAnyPackage ? nullptr : InObjectPackage;

	········

	FName ObjectName;

	// Don't resolve the name if we're searching in any package
	if (!bAnyPackage)
	{
		FString InName = OrigInName;
		if (!ResolveName(ObjectPackage, InName, false, false))
		{
			return nullptr;
		}
		ObjectName = FName(*InName, FNAME_Add);
	}
	else
	{
		FString InName = OrigInName;
		ConstructorHelpers::StripObjectClass(InName);

		ObjectName = FName(*InName, FNAME_Add);
	}

	return StaticFindObjectFast(ObjectClass, ObjectPackage, ObjectName, ExactClass, bAnyPackage);
}

bAnyPackage = false ,会调用ResolveName函数来解析OrigInName,如果OrigInName内包含有效的PackageName,就会生成新的ObjectPackage和ObjectName。

UObject* ObjectPackage = nullptr;
FString InName{"/Game/Mannequin/Character/Materials/M_Male_Body.M_Male_Body"};
ResolveName(ObjectPackage, InName, false, false);

如上代码,经ResolveName后,ObjectPackage->GetFName() = “/Game/Mannequin/Character/Materials/M_Male_Body”,InName = “M_Male_Body”。ResolveName内部其实也是通过FindObject来查找ObjectPackage

3. StaticFindObjectFast

作用: 只是做了一些判断(GIsSavingPackage,GC,AsyncLoading),进而转到StaticFindObjectFastInternal

4. StaticFindObjectFastInternal

作用: 获得UObjectHashTables传递给线程安全StaticFindObjectFastInternalThreadSafe

FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();

以下引用来自用UObjectHashTables管理UObjectHash

UObject是UE对象系统的基础组成部分,游戏中通常有数十万个UObject,它们属于不同类型,有不同的Outer和Package,每个UObject也有自己的Name。UObjectHashTables就像一个关系型数据库,存储并管理了游戏中的UObject,对外提供更新与查询接口。查询是主要目的,有了UObjectHashTables,我们可以方便的查询某个UClass有多少实例UObject,还可以根据一个Name查询对应的UObject等等,都是些很实用的功能。使用FUObjectHashTables实现。

StaticFindObjectFastInternalThreadSafe会使用到UObjectHashTables的 HashHashOuter

/** Hash sets */
TMap Hash;
TMultiMap HashOuter;

一个Object的全名,包括了Object自己的名字(NamePrivate),以及外层所有Outer名字组成。

Hash是Object的NamePrivate映射的哈希表,HashOuter是Object的NamePrivate + OuterNames映射的哈希表

5. StaticFindObjectFastInternalThreadSafe

查找分为两种情况,ObjectPackage == nullptr

5.1 ObjectPackage != nullptr

这种情况下已知Object的Outer,那么直接在UObjectHashTables的HashOuter中查找即可,首先需要获取Hash值

int32 Hash = GetObjectOuterHash(ObjectName, (PTRINT)ObjectPackage);

然后会对UObjectHashTables上锁,防止冲突

FHashTableLock HashLock(ThreadHash);

接下来就是,在HashOuter中得到并筛选Hash值对应的所有UObjectbBase*,筛选条件如下

if
	(
	/* check that the name matches the name we're searching for */
	(Object->GetFName() == ObjectName)

	/* Don't return objects that have any of the exclusive flags set */
	&& !Object->HasAnyFlags(ExcludeFlags)

	/* check that the object has the correct Outer */
	&& Object->GetOuter() == ObjectPackage

	/** If a class was specified, check that the object is of the correct class */
	&& (ObjectClass == nullptr || (bExactClass ? Object->GetClass() == ObjectClass : Object->IsA(ObjectClass)))
	
	/** Include (or not) pending kill objects */
	&& !Object->HasAnyInternalFlags(ExclusiveInternalFlags)
	)

具体来说就是,来自UObject源码分析(三)—FindObject

  • 资源路径必须要匹配
  • 不能包含某些Flag,比如:某资源还没有准备好,需要先实例化它的子对象。
  • Outer需要正确
  • 确保我们要加载的类是合理的,不能加载一个不存在的类。
  • 不能加载某些即将被GC或已经被挂起的对象。

5.2 ObjectPackage == nullptr

当 ObjectPackage == nullptr时,存在两种情况,一种是标记了 ANY_PACKAGE,另一种是Object的Outer真的为nullptr(顶层UObject)

创建了一个辅助查询的结构体对象

FObjectSearchPath SearchPath(ObjectName);

FObjectSearchPath是为了对ObjectName进行解析,拆分包名,子对象等

Splits an object path into FNames representing an outer chain. 
Input path examples: "Object", "Package.Object", "Object:Subobject", "Object:Subobject.Nested", "Package.Object:Subobject", "Package.Object:Subobject.NestedSubobject"

存在两个成员变量

FName Inner;
TArray> Outers;

若ObjectName = “Package.Object”,那么Inner = “Object”,Outers[0] = “Package”

接下来就是获取Hash值,给UObjectHashTables上锁,防止冲突。但因为Outer未知,只知道Object的NamePrivate,所以是在UObjectHashTables的Hash哈希表中寻找

const int32 Hash = GetObjectHash(SearchPath.Inner);
FHashTableLock HashLock(ThreadHash);

FHashBucket* Bucket = ThreadHash.Hash.Find(Hash);

以下引用来自用UObjectHashTables管理UObjectHash

FHashBucket是一个比较有意思的数据结构,感觉是UE根据自己经验做的专门优化,目的是减少内存使用。由于Hash的key是Object的FName,因此会出现几个Object的Hash key相同情况,正常我们需要用TMap < int32, TSet < Uobject*>>数据结构,但是如果大部分Object没有重名,那么就会有大量TSet只有一个元素。由于TSet是散列表,因此会有很多的内存浪费。但这只是假设,到底多少Object会重名,需要视具体游戏而定。

而后就是遍历FHashBucket进行条件筛选,筛选条件只增加了一条,就是关于是否为 ANY_PACKAGE 的判断。同时在对Object->GetFName()和Outer匹配有了一点差别

if
	(
	/* check that the name matches the name we're searching for */
	(Object->GetFName() == SearchPath.Inner)

	/* Don't return objects that have any of the exclusive flags set */
	&& !Object->HasAnyFlags(ExcludeFlags)

	/*If there is no package (no InObjectPackage specified, and InName's package is "")
	and the caller specified any_package, then accept it, regardless of its package.
	Or, if the object is a top-level package then accept it immediately.*/
	&& (bAnyPackage || !Object->GetOuter())

	/** If a class was specified, check that the object is of the correct class */
	&& (ObjectClass == nullptr || (bExactClass ? Object->GetClass() == ObjectClass : Object->IsA(ObjectClass)))

	/** Include (or not) pending kill objects */
	&& !Object->HasAnyInternalFlags(ExclusiveInternalFlags)

	/** Ensure that the partial path provided matches the object found */
	&& SearchPath.MatchOuterNames(Object->GetOuter())
	)

6. FindObjectFast

上面介绍了FindObject的执行流程,FindObjectFast包括后面的API在流程上都和FindObject有很大的重合,上面的流程弄清楚之后,API的使用就是在实际开发中考虑不同的情况进行调用了

作用: 快速查找对象,由于后面直接调用的是StaticFindObjectFast,没有调用StaticFindObjectFindObjectFast没有对输入的Name进行解析,需要保证输入的Outer = Object->OuterPrivate,Name = Object->NamePrivate,才能查找成功。速度上会更快一些,适用于高性能场景

template< class T > 
inline T* FindObjectFast( UObject* Outer, FName Name, bool ExactClass=false, bool AnyPackage=false, EObjectFlags ExclusiveFlags=RF_NoFlags )
{
	return (T*)StaticFindObjectFast( T::StaticClass(), Outer, Name, ExactClass, AnyPackage, ExclusiveFlags );
}

需要注意Outer不能设置为ANY_PACKAGE,会触发Assert。

7. FindObjectChecked

作用: 内部调用StaticFindObjectChecked,找不到对象不会返回nullptr,会直接报Fatal,停止程序(Shipping和Test版不会)

template< class T > 
inline T* FindObjectChecked( UObject* Outer, const TCHAR* Name, bool ExactClass=false )
{
	return (T*)StaticFindObjectChecked( T::StaticClass(), Outer, Name, ExactClass );
}

UObject* StaticFindObjectChecked( UClass* ObjectClass, UObject* ObjectParent, const TCHAR* InName, bool ExactClass )
{
	UObject* Result = StaticFindObject( ObjectClass, ObjectParent, InName, ExactClass );
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	if( !Result )
	{
		UE_LOG(LogUObjectGlobals, Fatal, TEXT("Failed to find object '%s %s.%s'"), *ObjectClass->GetName(), ObjectParent==ANY_PACKAGE ? TEXT("Any") : ObjectParent ? *ObjectParent->GetName() : TEXT("None"), InName);
	}
#endif
	return Result;
}

8. FindObjectSafe

作用: 内部调用StaticFindObjectSafe,在 GIsSavingPackage = true 或 IsGarbageCollectingOnGameThread() = true 会直接返回nullptr,不会报Fatal

template< class T > 
inline T* FindObjectSafe( UObject* Outer, const TCHAR* Name, bool ExactClass=false )
{
	return (T*)StaticFindObjectSafe( T::StaticClass(), Outer, Name, ExactClass );
}

UObject* StaticFindObjectSafe( UClass* ObjectClass, UObject* ObjectParent, const TCHAR* InName, bool ExactClass )
{
	if (!GIsSavingPackage && !IsGarbageCollectingOnGameThread())
	{
		FGCScopeGuard GCAndSavepackageGuard;
		return StaticFindObject( ObjectClass, ObjectParent, InName, ExactClass );
	}
	else
	{
		return NULL;
	}
}

9. FSoftObjectPath::ResolveObject

作用: 这个函数在上一篇文章中提到过作用,判断FSoftObjectPath引用的资源是否已经载入在内存中,若载入则返还资源对象指针,否则返还空。

内部先调用了ResolveObjectInternal进行一些条件判断,而后调用了FindObject

UObject* FoundObject = FindObject(nullptr, PathString);

Examples

假定内存中存在"/Game/Mannequin/Character/Materials/M_Male_Body.M_Male_Body"资源

Success:

  • FindObject(nullptr, TEXT(“/Game/Mannequin/Character/Materials/M_Male_Body.M_Male_Body”))
  • FindObject(ANY_PACKAGE, TEXT(“/Game/Mannequin/Character/Materials/M_Male_Body.M_Male_Body”))
  • FindObject(nullptr, TEXT(“Material’/Game/Mannequin/Character/Materials/M_Male_Body.M_Male_Body’”))
  • FindObject(ANY_PACKAGE, TEXT(“M_Male_Body”))
  • FindObject(ANY_PACKAGE, TEXT(“M_Male_Body.M_Male_Body”))
  • FindObjectFast(FindObject(nullptr, TEXT(“/Game/Mannequin/Character/Materials/M_Male_Body”)), TEXT(“M_Male_Body”))

Failure:

  • FindObjectFast(ANY_PACKAGE, TEXT(“M_Male_Body.M_Male_Body”)) //崩溃
  • FindObjectFast(FindObject(nullptr, TEXT(“/Game/Mannequin/Character/Materials/M_Male_Body”)), TEXT(“M_Male_Body.M_Male_Body”))
  • FindObject(FindObject(nullptr, TEXT(“/Game/Mannequin/Character/Materials/M_Male_Body”)), TEXT(“M_Male_Body.M_Male_Body”))

参考

  • UE4的资源管理
  • 用UObjectHashTables管理UObjectHash
  • UObject源码分析(三)—FindObject

你可能感兴趣的:(UE,资源加载,c++,ue4,ue5)