Fork me on GitHub

UE AssetRegistry GetAssetsByObjectPath 的实现

UE AssetRegistry GetAssetsByObjectPath 的实现

引擎版本 5.5.4

前置知识: FName 是用 Hash 存储的,见上一篇 https://www.cnblogs.com/icewalnut/p/18825426

这里以 GetAssetByObjectPath 为例讲解 UE 的资产注册表中根据 Object 路径获取资源是怎么实现的。

IAssetRegistry 的 函数 GetAssetByObjectPath 的实现是在 AssetRegistryImpl 实现类里面

image-20250806113626667

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。

FindObjectUObjectGlobals.h 中的函数

image-20250806113755717

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

image-20250806113851441

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

image-20250806113953841

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

image-20250806114139015

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

image-20250806114332468

真正的逻辑就在这里了,这里先看 ObjectPackagenullptr 的情况,也就是上面这张图

ObjectPackage 是要查找的 FindObject<UObject>(nullptr, *Builder) 里面的 nullptr,也就是 Outer

  • GetObjectOuterHash

    image-20250806153101808

    这里调用 GetTypeHash

    image-20250806153145625

    Name.GetComparisonIndex()Name.GetNumber() 看 "FName.md" 这里不再展开。

    image-20250806155849459

    image-20250806155904890

    这里可以认为是 FNameEntryIdFNameEntryHandle 的转换了。

  • FUObjectHashTables

    这个是用于加速对象查找的结构,里面维护了多个哈希表:

    image-20250806163012619

    这里用到的是 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

    image-20250806164042993

    TConstKeyIterator 是一个定向迭代器:

    • 只能遍历 Key == 指定值 的所有项
    • HashIt.Value() 会返回对应的 uint32 InternalIndex
  • GUObjectArray

    • 引擎内部用于存放所有已经注册(已创建)的 UObject 的数组(或者说是全局对象池)。
    • 每个 UObject 会被分配一个 InternalIndex (类似于在 GUObjectArray 中的下标)
posted @ 2025-08-13 17:15  icewalnut  阅读(36)  评论(0)    收藏  举报