上面文章最后提到要介绍同步和异步加载,但觉得在这之前还需介绍一下FindObjectXXXX
等查找资源的API
常用的有FindObject
,FindObjectFast
,FindObjectChecked
,FindObjectSafe
,FSoftObjectPath::ResolveObject
,它们的调用关系大致如下
下面以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
,输入参数作用如下:
作用: 核心功能就是解析传入的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
作用: 只是做了一些判断(GIsSavingPackage,GC,AsyncLoading),进而转到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的 Hash 和 HashOuter
/** Hash sets */
TMap Hash;
TMultiMap HashOuter;
一个Object的全名,包括了Object自己的名字(NamePrivate),以及外层所有Outer名字组成。
Hash是Object的NamePrivate映射的哈希表,HashOuter是Object的NamePrivate + OuterNames映射的哈希表
查找分为两种情况,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
当 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())
)
上面介绍了FindObject的执行流程,FindObjectFast包括后面的API在流程上都和FindObject有很大的重合,上面的流程弄清楚之后,API的使用就是在实际开发中考虑不同的情况进行调用了
作用: 快速查找对象,由于后面直接调用的是StaticFindObjectFast
,没有调用StaticFindObject
,FindObjectFast
没有对输入的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。
作用: 内部调用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;
}
作用: 内部调用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;
}
}
作用: 这个函数在上一篇文章中提到过作用,判断FSoftObjectPath引用的资源是否已经载入在内存中,若载入则返还资源对象指针,否则返还空。
内部先调用了ResolveObjectInternal
进行一些条件判断,而后调用了FindObject
UObject* FoundObject = FindObject(nullptr, PathString);
假定内存中存在"/Game/Mannequin/Character/Materials/M_Male_Body.M_Male_Body"
资源
Success:
Failure: