UE AssetRegistry GetAssetsByObjectPath 的实现
UE AssetRegistry GetAssetsByObjectPath 的实现
引擎版本 5.5.4
前置知识: FName 是用 Hash 存储的,见上一篇 https://www.cnblogs.com/icewalnut/p/18825426
这里以 GetAssetByObjectPath
为例讲解 UE 的资产注册表中根据 Object 路径获取资源是怎么实现的。
IAssetRegistry
的 函数 GetAssetByObjectPath
的实现是在 AssetRegistryImpl
实现类里面
FAssetData UAssetRegistryImpl::GetAssetByObjectPath(const FSoftObjectPath& ObjectPath, bool bIncludeOnlyOnDiskAssets, bool bSkipARFilteredAssets) const
{
if (!bIncludeOnlyOnDiskAssets)
{
TStringBuilder<FName::StringBufferSize> Builder;
ObjectPath.ToString(Builder);
UObject* Asset = FindObject<UObject>(nullptr, *Builder);
if (Asset)
{
if (!bSkipARFilteredAssets || !UE::AssetRegistry::FFiltering::ShouldSkipAsset(Asset))
{
return FAssetData(Asset, FAssetData::ECreationFlags::None /** Do not allow blueprint classes */,
EAssetRegistryTagsCaller::AssetRegistryQuery);
}
else
{
return FAssetData();
}
}
}
{
UE::AssetRegistry::FInterfaceReadScopeLock InterfaceScopeLock(InterfaceLock);
const FAssetRegistryState& State = GuardedData.GetState();
const FAssetData* FoundData = State.GetAssetByObjectPath(ObjectPath);
return (FoundData && !State.IsPackageUnmountedAndFiltered(FoundData->PackageName)
&& (!bSkipARFilteredAssets || !GuardedData.ShouldSkipAsset(FoundData->AssetClassPath, FoundData->PackageFlags))) ? *FoundData : FAssetData();
}
}
进入 FindObject
,传入的 Builder
是 ObjectPath。
FindObject
是 UObjectGlobals.h
中的函数
/**
* Find an optional object.
* @see StaticFindObject()
*/
template< class T >
inline T* FindObject( UObject* Outer, const TCHAR* Name, bool ExactClass=false )
{
return (T*)StaticFindObject( T::StaticClass(), Outer, Name, ExactClass );
}
进入 StaticFindObject
,传入的参数有 UClass
也就是这个 UObject 的类,Outer
是持有者,Name
是这个 UObject
的 Path,也就是前面传进来的 ObjectPath
转成的 TStringBuilder
。
//
// Find an optional object.
//
UObject* StaticFindObject( UClass* ObjectClass, UObject* InObjectPackage, const TCHAR* OrigInName, bool bExactClass )
{
INC_DWORD_STAT(STAT_FindObject);
// Resolve the object and package name.
const bool bAnyPackage = InObjectPackage == ANY_PACKAGE_DEPRECATED;
UObject* ObjectPackage = bAnyPackage ? nullptr : InObjectPackage;
UObject* MatchingObject = nullptr;
#if WITH_EDITOR
// If the editor is running, and T3D is being imported, ensure any packages referenced are fully loaded.
if ((GIsEditor == true) && (GIsImportingT3D == true))
{
MatchingObject = LoadObjectWhenImportingT3D(ObjectClass, OrigInName);
if (MatchingObject)
{
return MatchingObject;
}
}
#endif //#if !WITH_EDITOR
TStringBuilder<512> InName;
InName = OrigInName;
// Don't resolve the name if we're searching in any package
if (!bAnyPackage)
{
if (!ResolveName2(ObjectPackage, InName, false, false))
{
return nullptr;
}
}
else
{
ConstructorHelpers_StripObjectClass2(InName);
}
FName ObjectName(InName.ToView(), FNAME_Add);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return StaticFindObjectFast(ObjectClass, ObjectPackage, ObjectName, bExactClass, bAnyPackage);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
注意这里根据 OrigInName
构建了 FName ObjectName
,传入 StaticFindObjectFast
。
核心是 StaticFindObjectFast
//
// Find an object, path must unqualified
//
UObject* StaticFindObjectFast(UClass* ObjectClass, UObject* ObjectPackage, FName ObjectName, bool bExactClass, bool bAnyPackage, EObjectFlags ExclusiveFlags, EInternalObjectFlags ExclusiveInternalFlags)
{
UE_CLOG(UE::IsSavingPackage(nullptr), LogUObjectGlobals, Fatal, TEXT("Illegal call to StaticFindObjectFast() while serializing object data!"));
UE_CLOG(IsGarbageCollectingAndLockingUObjectHashTables(), LogUObjectGlobals, Fatal, TEXT("Illegal call to StaticFindObjectFast() while garbage collecting!"));
// We don't want to return any objects that are currently being background loaded unless we're using FindObject during async loading.
ExclusiveInternalFlags |= UE::GetAsyncLoadingInternalFlagsExclusion();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
UObject* FoundObject = StaticFindObjectFastInternal(ObjectClass, ObjectPackage, ObjectName, bExactClass, bAnyPackage, ExclusiveFlags, ExclusiveInternalFlags);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (!FoundObject)
{
FoundObject = StaticFindObjectWithChangedLegacyPath(ObjectClass, ObjectPackage, ObjectName, bExactClass, ExclusiveInternalFlags);
}
return FoundObject;
}
核心是 StaticFindObjectFastInternal
UObject* StaticFindObjectFastInternal(const UClass* ObjectClass, const UObject* ObjectPackage, FName ObjectName, bool bExactClass, bool bAnyPackage, EObjectFlags ExcludeFlags, EInternalObjectFlags ExclusiveInternalFlags)
{
UObject* Result = nullptr;
// Transactionally, a static find operation has to occur in the open as it touches shared state.
UE_AUTORTFM_OPEN
{
INC_DWORD_STAT(STAT_FindObjectFast);
check(ObjectPackage != ANY_PACKAGE_DEPRECATED); // this could never have returned anything but nullptr
// If they specified an outer use that during the hashing
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
Result = StaticFindObjectFastInternalThreadSafe(ThreadHash, ObjectClass, ObjectPackage, ObjectName, bExactClass, bAnyPackage, ExcludeFlags, ExclusiveInternalFlags);
};
return Result;
}
核心是 StaticFindObjectFastInternalThreadSafe
真正的逻辑就在这里了,这里先看 ObjectPackage
为 nullptr
的情况,也就是上面这张图
ObjectPackage
是要查找的FindObject<UObject>(nullptr, *Builder)
里面的nullptr
,也就是 Outer
-
GetObjectOuterHash
这里调用
GetTypeHash
Name.GetComparisonIndex()
和Name.GetNumber()
看 "FName.md" 这里不再展开。这里可以认为是
FNameEntryId
和FNameEntryHandle
的转换了。 -
FUObjectHashTables
这个是用于加速对象查找的结构,里面维护了多个哈希表:
这里用到的是
HashOuter
,他是一个TMultiMap
TMultiMap<OuterHash, ObjectIndex>
表示:
对于某个
(ObjectName, OuterPackage)
计算出来的哈希值Hash
,它能够对应多个对象,这些对象的索引都存储在这个哈希表中。
具体到
for (TMultiMap<int32, uint32>::TConstKeyIterator HashIt(ThreadHash.HashOuter, Hash); HashIt; ++HashIt)
这行代码
它遍历的是 ThreadHash.HashOuter
,他是一个 TMultiMap<int32, uint32>
类型。
前面的 int32
是 哈希值(由 ObjectName + OuterPackage 计算出来)。
后面的 uint32
是 UObject 的 InternalIndex (用于从 GUObjectArray 中索引实际的 UObject)。
-
为什么用
TMultiMap
-
因为可能不同的 UObject 可能恰好有相同名字和 Outer 的组合,或哈希碰撞。
所以一个 hash 值对应多个 InternalIndex,因此用的是
TMultiMap
(一对多)。
-
-
TMultiMap<int32, uint32>::TConstKeyIterator HashIt(ThreadHash.HashOuter, Hash)
-
这句代码创建了一个迭代器
HashIt
,用于- 只遍历
HashOuter
中 Key == Hash 的所有项
- 只遍历
-
并不像
TMap
的 iterator 那样遍历整个 map,它是只遍历匹配特定 key (Hash
)的条目感觉这是加速的核心之一了
-
-
TConstKeyIterator
TConstKeyIterator
是一个定向迭代器:- 只能遍历
Key == 指定值
的所有项 HashIt.Value()
会返回对应的uint32 InternalIndex
- 只能遍历
-
GUObjectArray
- 引擎内部用于存放所有已经注册(已创建)的 UObject 的数组(或者说是全局对象池)。
- 每个 UObject 会被分配一个 InternalIndex (类似于在 GUObjectArray 中的下标)