本篇文章通過調試代碼進行跟蹤對象的創建和查找,使用UE4 Example FirstPersonCPP工程。
官方文檔https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Objects/index.html
NewObject
新建一個UPlayerData類,定義如下:
UCLASS()
class FIRSTPERSONCPP_API UPlayerData : public UObject
{
GENERATED_BODY()
public:
UPlayerData();
FString PlayerName;
uint32_t BulletNum;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "PlayerData.h"
UPlayerData::UPlayerData()
{
PlayerName = TEXT("Tom");
BulletNum = 100;
}
在void AFirstPersonCPPCharacter::OnFire()中加入測試代碼
{
UPlayerData *PlayerData = NewObject<UPlayerData>(this, FName(TEXT("Jackson")));
check(PlayerData);
...
}
堆棧如下:
Paste_Image.png
經過跟蹤調試, 下面分析如下相關函數:
- NewObject
template< class T >
FUNCTION_NON_NULL_RETURN_START
T* NewObject(UObject* Outer, FName Name, EObjectFlags Flags = RF_NoFlags, UObject* Template = nullptr, bool bCopyTransientsFromClassDefaults = false, FObjectInstancingGraph* InInstanceGraph = nullptr)
FUNCTION_NON_NULL_RETURN_END
{
if (Name == NAME_None)
{
FObjectInitializer::AssertIfInConstructor(Outer, TEXT("NewObject with empty name can't be used to create default subobjects (inside of UObject derived class constructor) as it produces inconsistent object names. Use ObjectInitializer.CreateDefaultSuobject<> instead."));
}
return static_cast<T*>(StaticConstructObject_Internal(T::StaticClass(), Outer, Name, Flags, EInternalObjectFlags::None, Template, bCopyTransientsFromClassDefaults, InInstanceGraph));
}
- StaticConstructObject_Internal
UObject* StaticConstructObject_Internal
(
UClass* InClass,
UObject* InOuter /*=GetTransientPackage()*/,
FName InName /*=NAME_None*/,
EObjectFlags InFlags /*=0*/,
EInternalObjectFlags InternalSetFlags /*=0*/,
UObject* InTemplate /*=NULL*/,
bool bCopyTransientsFromClassDefaults /*=false*/,
FObjectInstancingGraph* InInstanceGraph /*=NULL*/,
bool bAssumeTemplateIsArchetype /*=false*/
)
{
SCOPE_CYCLE_COUNTER(STAT_ConstructObject);
UObject* Result = NULL;
#if WITH_EDITORONLY_DATA
UE_CLOG(GIsSavingPackage && InOuter != GetTransientPackage(), LogUObjectGlobals, Fatal, TEXT("Illegal call to StaticConstructObject() while serializing object data! (Object will not be saved!)"));
#endif
checkf(!InTemplate || InTemplate->IsA(InClass) || (InFlags & RF_ClassDefaultObject), TEXT("StaticConstructObject %s is not an instance of class %s and it is not a CDO."), *GetFullNameSafe(InTemplate), *GetFullNameSafe(InClass)); // template must be an instance of the class we are creating, except CDOs
// Subobjects are always created in the constructor, no need to re-create them unless their archetype != CDO or they're blueprint generated.
// If the existing subobject is to be re-used it can't have BeginDestroy called on it so we need to pass this information to StaticAllocateObject.
const bool bIsNativeClass = InClass->HasAnyClassFlags(CLASS_Native | CLASS_Intrinsic);
const bool bIsNativeFromCDO = bIsNativeClass &&
(
!InTemplate ||
(InName != NAME_None && (bAssumeTemplateIsArchetype || InTemplate == UObject::GetArchetypeFromRequiredInfo(InClass, InOuter, InName, InFlags)))
);
#if WITH_HOT_RELOAD
// Do not recycle subobjects when performing hot-reload as they may contain old property values.
const bool bCanRecycleSubobjects = bIsNativeFromCDO && !GIsHotReload;
#else
const bool bCanRecycleSubobjects = bIsNativeFromCDO;
#endif
bool bRecycledSubobject = false;
/*
* Create a new instance of an object or replace an existing object. If both an Outer and Name are specified, and there is an object already in memory with the same Class, Outer, and Name, the
* existing object will be destructed, and the new object will be created in its place.
*/
Result = StaticAllocateObject(InClass, InOuter, InName, InFlags, InternalSetFlags, bCanRecycleSubobjects, &bRecycledSubobject);
check(Result != NULL);
// Don't call the constructor on recycled subobjects, they haven't been destroyed.
if (!bRecycledSubobject)
{
// 調用構造函數
FScopeCycleCounterUObject ConstructorScope(InClass, GET_STATID(STAT_ConstructObject));
(*InClass->ClassConstructor)( FObjectInitializer(Result, InTemplate, bCopyTransientsFromClassDefaults, true, InInstanceGraph) );
}
if( GIsEditor && GUndo && (InFlags & RF_Transactional) && !(InFlags & RF_NeedLoad) && !InClass->IsChildOf(UField::StaticClass()) )
{
// Set RF_PendingKill and update the undo buffer so an undo operation will set RF_PendingKill on the newly constructed object.
Result->MarkPendingKill();
SaveToTransactionBuffer(Result, false);
Result->ClearPendingKill();
}
return Result;
}
- StaticAllocateObject
主要是分配對象內存,并且設定名字空間和注冊
UObject* StaticAllocateObject
(
UClass* InClass,
UObject* InOuter,
FName InName,
EObjectFlags InFlags,
EInternalObjectFlags InternalSetFlags,
bool bCanRecycleSubobjects,
bool* bOutRecycledSubobject
)
{
SCOPE_CYCLE_COUNTER(STAT_AllocateObject);
checkSlow(InOuter != INVALID_OBJECT); // not legal
check(!InClass || (InClass->ClassWithin && InClass->ClassConstructor));
#if WITH_EDITOR
if (GIsEditor)
{
if (StaticAllocateObjectErrorTests(InClass,InOuter,InName,InFlags))
{
return NULL;
}
}
#endif // WITH_EDITOR
bool bCreatingCDO = (InFlags & RF_ClassDefaultObject) != 0;
check(InClass);
check(GIsEditor || bCreatingCDO || !InClass->HasAnyClassFlags(CLASS_Abstract)); // this is a warning in the editor, otherwise it is illegal to create an abstract class, except the CDO
check(InOuter || (InClass == UPackage::StaticClass() && InName != NAME_None)); // only packages can not have an outer, and they must be named explicitly
check(bCreatingCDO || !InOuter || InOuter->IsA(InClass->ClassWithin)); // 判斷InOuter是否屬于ClassWithin類
if (bCreatingCDO)
{
check(InClass->GetClass());
if( !GIsDuplicatingClassForReinstancing )
{
InName = InClass->GetDefaultObjectName();
}
// never call PostLoad on class default objects
InFlags &= ~(RF_NeedPostLoad|RF_NeedPostLoadSubobjects);
}
UObject* Obj = NULL;
if(InName == NAME_None)
{
// 分配對象名字
#if WITH_EDITOR
if ( GOutputCookingWarnings && GetTransientPackage() != InOuter->GetOutermost() )
{
static const FName NAME_UniqueObjectNameForCooking(TEXT("UniqueObjectNameForCooking"));
InName = MakeUniqueObjectName(InOuter, InClass, NAME_UniqueObjectNameForCooking);
}
else
#endif
{
InName = MakeUniqueObjectName(InOuter, InClass);
}
}
else
{
// 判斷對象是否已經存在
// See if object already exists.
Obj = StaticFindObjectFastInternal( /*Class=*/ NULL, InOuter, InName, true );
// Temporary: If the object we found is of a different class, allow the object to be allocated.
// This breaks new UObject assumptions and these need to be fixed.
if (Obj && !Obj->GetClass()->IsChildOf(InClass))
{
UE_LOG(LogUObjectGlobals, Fatal,
TEXT("Objects have the same fully qualified name but different paths.\n")
TEXT("\tNew Object: %s %s.%s\n")
TEXT("\tExisting Object: %s"),
*InClass->GetName(), InOuter ? *InOuter->GetPathName() : TEXT(""), *InName.ToString(),
*Obj->GetFullName());
}
}
FLinkerLoad* Linker = NULL;
int32 LinkerIndex = INDEX_NONE;
bool bWasConstructedOnOldObject = false;
// True when the object to be allocated already exists and is a subobject.
bool bSubObject = false;
int32 TotalSize = InClass->GetPropertiesSize();
checkSlow(TotalSize);
if( Obj == NULL )
{ // 沒有可復用的對象,新分配內存
int32 Alignment = FMath::Max( 4, InClass->GetMinAlignment() );
Obj = (UObject *)GUObjectAllocator.AllocateUObject(TotalSize,Alignment,GIsInitialLoad);
}
else
{
// 復用已經存在的Object空間
// Replace an existing object without affecting the original's address or index.
check(!Obj->IsUnreachable());
check(!ObjectRestoreAfterInitProps); // otherwise recursive construction
ObjectRestoreAfterInitProps = Obj->GetRestoreForUObjectOverwrite();
// Remember linker, flags, index, and native class info.
Linker = Obj->GetLinker();
LinkerIndex = Obj->GetLinkerIndex();
InternalSetFlags |= (Obj->GetInternalFlags() & (EInternalObjectFlags::Native | EInternalObjectFlags::RootSet));
if ( bCreatingCDO )
{
check(Obj->HasAllFlags(RF_ClassDefaultObject));
Obj->SetFlags(InFlags);
Obj->SetInternalFlags(InternalSetFlags);
// never call PostLoad on class default objects
Obj->ClearFlags(RF_NeedPostLoad|RF_NeedPostLoadSubobjects);
}
else if(!InOuter || !InOuter->HasAnyFlags(RF_ClassDefaultObject))
{
#if !UE_BUILD_SHIPPING
// Handle nested DSOs
bool bIsOwnedByCDO = false;
UObject* Iter = InOuter;
while (Iter)
{
if (Iter->HasAnyFlags(RF_ClassDefaultObject))
{
bIsOwnedByCDO = true;
break;
}
Iter = Iter->GetOuter();
}
// Should only get in here if we're NOT creating a subobject of a CDO. CDO subobjects may still need to be serialized off of disk after being created by the constructor
// if really necessary there was code to allow replacement of object just needing postload, but lets not go there unless we have to
checkf(!Obj->HasAnyFlags(RF_NeedLoad|RF_NeedPostLoad|RF_ClassDefaultObject) || bIsOwnedByCDO,
*FText::Format(NSLOCTEXT("Core", "ReplaceNotFullyLoaded_f", "Attempting to replace an object that hasn't been fully loaded: {0} (Outer={1}, Flags={2})"),
FText::FromString(Obj->GetFullName()),
InOuter ? FText::FromString(InOuter->GetFullName()) : FText::FromString(TEXT("NULL")),
FText::FromString(FString::Printf(TEXT("0x%08x"), (int32)Obj->GetFlags()))).ToString());
#endif//UE_BUILD_SHIPPING
}
// Subobjects are always created in the constructor, no need to re-create them here unless their archetype != CDO or they're blueprint generated.
if (!bCreatingCDO && (!bCanRecycleSubobjects || !Obj->IsDefaultSubobject()))
{
// Destroy the object.
SCOPE_CYCLE_COUNTER(STAT_DestroyObject);
// Check that the object hasn't been destroyed yet.
if(!Obj->HasAnyFlags(RF_FinishDestroyed))
{
// Get the name before we start the destroy, as destroy renames it
FString OldName = Obj->GetFullName();
// Begin the asynchronous object cleanup.
Obj->ConditionalBeginDestroy();
// Wait for the object's asynchronous cleanup to finish.
while (!Obj->IsReadyForFinishDestroy())
{
// If we're not in the editor, and aren't doing something specifically destructive like reconstructing blueprints, this is fatal
if (!GIsEditor && FApp::IsGame() && !GIsReconstructingBlueprintInstances)
{
// Switching to warning, investigate why level duplication triggers this
UE_LOG(LogUObjectGlobals, Warning, TEXT("Gamethread hitch waiting for resource cleanup on a UObject (%s) overwrite. Fix the higher level code so that this does not happen."), *OldName );
}
FPlatformProcess::Sleep(0);
}
// Finish destroying the object.
Obj->ConditionalFinishDestroy();
}
Obj->~UObject();
bWasConstructedOnOldObject = true;
}
else
{
bSubObject = true;
}
}
// If class is transient, non-archetype objects must be transient.
bool const bCreatingArchetype = (InFlags & RF_ArchetypeObject) != 0;
if ( !bCreatingCDO && InClass->HasAnyClassFlags(CLASS_Transient) && !bCreatingArchetype )
{
InFlags |= RF_Transient;
}
if (!bSubObject)
{
FMemory::Memzero((void *)Obj, TotalSize);
// 在該Obj處調用UObjectBase構造函數
new ((void *)Obj) UObjectBase(InClass, InFlags|RF_NeedInitialization, InternalSetFlags, InOuter, InName);
}
else
{
// Propagate flags to subobjects created in the native constructor.
Obj->SetFlags(InFlags);
Obj->SetInternalFlags(InternalSetFlags);
}
if (bWasConstructedOnOldObject)
{
// Reassociate the object with it's linker.
Obj->SetLinker(Linker,LinkerIndex,false);
if(Linker)
{
check(Linker->ExportMap[LinkerIndex].Object == NULL);
Linker->ExportMap[LinkerIndex].Object = Obj;
}
}
if (IsInAsyncLoadingThread())
{
NotifyConstructedDuringAsyncLoading(Obj, bSubObject);
}
else
{
// Sanity checks for async flags.
// It's possible to duplicate an object on the game thread that is still being referenced
// by async loading code or has been created on a different thread than the main thread.
Obj->ClearInternalFlags(EInternalObjectFlags::AsyncLoading);
if (Obj->HasAnyInternalFlags(EInternalObjectFlags::Async) && IsInGameThread())
{
Obj->ClearInternalFlags(EInternalObjectFlags::Async);
}
}
// Let the caller know if a subobject has just been recycled.
if (bOutRecycledSubobject)
{
*bOutRecycledSubobject = bSubObject;
}
return Obj;
}
/**
* Constructor used by StaticAllocateObject
* @param InClass non NULL, this gives the class of the new object, if known at this time
* @param InFlags RF_Flags to assign
* @param InOuter outer for this object
* @param InName name of the new object
* @param InObjectArchetype archetype to assign
*/
UObjectBase::UObjectBase(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)
: ObjectFlags (InFlags)
, InternalIndex (INDEX_NONE)
, ClassPrivate (InClass)
, OuterPrivate (InOuter)
{
check(ClassPrivate);
// Add to global table. 這里登記對象
AddObject(InName, InInternalFlags);
}
創建流程小結
- 快速查找是否已經存在同名對象;
- 如果存在則析構掉,并利用該內存塊m;
- 如果不存在同名對象,則根據Class反射信息 分配內存塊m;
- 在內存塊m上調用構造函數ObjectBase。
- 最后在內存塊m上調用我們期望的類的構造函數
其中UObjectBase(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)函數完成了對象登記。下面通過分析該函數來了解對象管理。
全局對象表
/**
* Add a newly created object to the name hash tables and the object array
*
* @param Name name to assign to this uobject
*/
void UObjectBase::AddObject(FName InName, EInternalObjectFlags InSetInternalFlags)
{
NamePrivate = InName;
EInternalObjectFlags InternalFlagsToSet = InSetInternalFlags;
if (!IsInGameThread())
{
InternalFlagsToSet |= EInternalObjectFlags::Async; // 在其它線程創建標志, 主要用在異步加載
}
if (ObjectFlags & RF_MarkAsRootSet)
{
InternalFlagsToSet |= EInternalObjectFlags::RootSet; // 不可GC
ObjectFlags &= ~RF_MarkAsRootSet;
}
if (ObjectFlags & RF_MarkAsNative)
{
InternalFlagsToSet |= EInternalObjectFlags::Native; // C++代碼中創建的對象
ObjectFlags &= ~RF_MarkAsNative;
}
AllocateUObjectIndexForCurrentThread(this); // 在GlobalArray中分配對象信息槽位
check(InName != NAME_None && InternalIndex >= 0);
if (InternalFlagsToSet != EInternalObjectFlags::None)
{
GUObjectArray.IndexToObject(InternalIndex)->SetFlags(InternalFlagsToSet);
}
HashObject(this); // Hash該對象
check(IsValidLowLevel());
}
關鍵數據結構
- FUObjectArray
- FUObjectItem
/**
* Single item in the UObject array.
*/
struct FUObjectItem
{
// Pointer to the allocated object
class UObjectBase* Object;
// Internal flags
int32 Flags;
// UObject Owner Cluster Index
int32 ClusterRootIndex;
// Weak Object Pointer Serial number associated with the object
int32 SerialNumber; //該SerialNumber用于驗證WeakPointer指向的對象已經被回收,槽位被新對象占用
FUObjectItem()
: Object(nullptr)
, Flags(0)
, ClusterRootIndex(0)
, SerialNumber(0)
{
}
}
HashObject
void HashObject(UObjectBase* Object)
{
SCOPE_CYCLE_COUNTER( STAT_Hash_HashObject );
FName Name = Object->GetFName();
if (Name != NAME_None)
{
int32 Hash = 0;
auto& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
Hash = GetObjectHash(Name);
checkSlow(!ThreadHash.PairExistsInHash(Hash, Object)); // if it already exists, something is wrong with the external code
ThreadHash.AddToHash(Hash, Object);
Hash = GetObjectOuterHash( Name, (PTRINT)Object->GetOuter() );
checkSlow( !ThreadHash.HashOuter.FindPair( Hash, Object ) ); // if it already exists, something is wrong with the external code
ThreadHash.HashOuter.Add( Hash, Object );
AddToOuterMap( ThreadHash, Object );
AddToClassMap( ThreadHash, Object );
}
}
FindObject
編寫調試代碼如下:
void AFirstPersonCPPCharacter::OnFire()
{
UPlayerData *PlayerData = NewObject<UPlayerData>(this, FName(TEXT("Jackson")));
check(PlayerData);
FString ObjPathname = PlayerData->GetPathName();
UPackage *Package = PlayerData->GetOutermost();
UPlayerData* FindResult = FindObject<UPlayerData>(nullptr, ObjPathname, false);
check(FindResult);
}
ObjPathname值為/Game/FirstPersonCPP/Maps/UEDPIE_0_FirstPersonExampleMap.FirstPersonExampleMap:PersistentLevel.FirstPersonCharacter_C_0.Jackson
//
// Find an optional object.
//
UObject* StaticFindObject( UClass* ObjectClass, UObject* InObjectPackage, const TCHAR* OrigInName, bool ExactClass )
{
INC_DWORD_STAT(STAT_FindObject);
if (GIsSavingPackage)
{
UE_LOG(LogUObjectGlobals, Fatal,TEXT("Illegal call to StaticFindObject() while serializing object data!"));
}
if (IsGarbageCollectingOnGameThread())
{
UE_LOG(LogUObjectGlobals, Fatal,TEXT("Illegal call to StaticFindObject() while collecting garbage!"));
}
// Resolve the object and package name.
const bool bAnyPackage = InObjectPackage==ANY_PACKAGE;
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))// && (ObjectPackage != ANY_PACKAGE) && (ObjectPackage != NULL))
{
static bool s_bCurrentlyLoading = false;
if (s_bCurrentlyLoading == false)
{
FString NameCheck = OrigInName;
if (NameCheck.Contains(TEXT("."), ESearchCase::CaseSensitive) &&
!NameCheck.Contains(TEXT("'"), ESearchCase::CaseSensitive) &&
!NameCheck.Contains(TEXT(":"), ESearchCase::CaseSensitive) )
{
s_bCurrentlyLoading = true;
MatchingObject = StaticLoadObject(ObjectClass, nullptr, OrigInName, nullptr, LOAD_NoWarn, nullptr);
s_bCurrentlyLoading = false;
if (MatchingObject != nullptr)
{
return MatchingObject;
}
}
}
}
#endif //#if !WITH_EDITOR
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)) // 解析出最終對象shortname和它的Outer對象. 這個類似文件系統的文件查找,會遞歸調用FindObject
{
return nullptr;
}
ObjectName = FName(*InName, FNAME_Add);
}
else
{
ObjectName = FName(OrigInName, FNAME_Add);
}
// Find Object Fast
return StaticFindObjectFast(ObjectClass, ObjectPackage, ObjectName, ExactClass, bAnyPackage);
}
Paste_Image.png
UObject* StaticFindObjectFastInternal(UClass* ObjectClass, UObject* ObjectPackage, FName ObjectName, bool bExactClass, bool bAnyPackage, EObjectFlags ExcludeFlags, EInternalObjectFlags ExclusiveInternalFlags)
{
SCOPE_CYCLE_COUNTER( STAT_Hash_StaticFindObjectFastInternal );
INC_DWORD_STAT(STAT_FindObjectFast);
check(ObjectPackage != ANY_PACKAGE); // this could never have returned anything but nullptr
// If they specified an outer use that during the hashing
auto& ThreadHash = FUObjectHashTables::Get();
UObject* Result = StaticFindObjectFastInternalThreadSafe(ThreadHash, ObjectClass, ObjectPackage, ObjectName, bExactClass, bAnyPackage, ExcludeFlags | RF_NewerVersionExists, ExclusiveInternalFlags);
return Result;
}
UObject* StaticFindObjectFastInternalThreadSafe(FUObjectHashTables& ThreadHash, UClass* ObjectClass, UObject* ObjectPackage, FName ObjectName, bool bExactClass, bool bAnyPackage, EObjectFlags ExcludeFlags, EInternalObjectFlags ExclusiveInternalFlags)
{
// If they specified an outer use that during the hashing
UObject* Result = nullptr;
if (ObjectPackage != nullptr)
{
int32 Hash = GetObjectOuterHash(ObjectName, (PTRINT)ObjectPackage);
FHashTableLock HashLock(ThreadHash); // 加鎖
for (TMultiMap<int32, class UObjectBase*>::TConstKeyIterator HashIt(ThreadHash.HashOuter, Hash); HashIt; ++HashIt)
{
UObject *Object = (UObject *)HashIt.Value();
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))
{
checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName());
if (Result)
{
UE_LOG(LogUObjectHash, Warning, TEXT("Ambiguous search, could be %s or %s"), *GetFullNameSafe(Result), *GetFullNameSafe(Object));
}
else
{
Result = Object;
}
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
break;
#endif
}
}
}
else
{
// Find an object with the specified name and (optional) class, in any package; if bAnyPackage is false, only matches top-level packages
FName ActualObjectName = ObjectName;
const FString ObjectNameString = ObjectName.ToString();
const int32 DotIndex = FMath::Max<int32>(ObjectNameString.Find(TEXT("."), ESearchCase::CaseSensitive, ESearchDir::FromEnd),
ObjectNameString.Find(TEXT(":"), ESearchCase::CaseSensitive, ESearchDir::FromEnd));
if (DotIndex != INDEX_NONE)
{
ActualObjectName = FName(*ObjectNameString.Mid(DotIndex + 1));
}
const int32 Hash = GetObjectHash(ActualObjectName);
FHashTableLock HashLock(ThreadHash);
FHashBucket* Bucket = ThreadHash.Hash.Find(Hash);
if (Bucket)
{
for (FHashBucketIterator It(*Bucket); It; ++It)
{
UObject* Object = (UObject*)*It;
if
((Object->GetFName() == ActualObjectName)
/* 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 */
&& (Object->GetPathName().EndsWith(ObjectNameString)))
{
checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName());
if (Result)
{
UE_LOG(LogUObjectHash, Warning, TEXT("Ambiguous search, could be %s or %s"), *GetFullNameSafe(Result), *GetFullNameSafe(Object));
}
else
{
Result = Object;
}
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
break;
#endif
}
}
}
}
// Not found.
return Result;
}
小結
對象的查找就像操作系統中根據文件路徑查找文件一樣,一層一層剖洋蔥一樣。
關于HashObject的數據結構,暫不研究。
關于FObjectInstancingGraph這塊代碼太復雜了,本人還沒有理解(UE3時就沒有理解),所以暫時放過。