本篇文章通过调试代码进行跟踪对象的创建和查找,使用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(this, FName(TEXT("Jackson")));
check(PlayerData);
...
}
堆栈如下:
经过跟踪调试, 下面分析如下相关函数:
- 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(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(this, FName(TEXT("Jackson")));
check(PlayerData);
FString ObjPathname = PlayerData->GetPathName();
UPackage *Package = PlayerData->GetOutermost();
UPlayerData* FindResult = FindObject(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);
}
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::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(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时就没有理解),所以暂时放过。