UObject
部分参考 InsideUE4 - UObject
UObject
是 UE 中所有 C++ 对象的根基类,它是 UE 对象系统的重要组成部分。通过 UObject,UE 支持以下功能:
- 垃圾回收
- 引用更新
- 映象
- 序列化
- 默认属性变化自动更新
- 自动属性初始化
- 自动编辑器整合
- 运行时类型信息可用
- 网络复制
- ...
它有以下五个继承自 UObjectBase
的关键变量。
/**
* 用于描述一个对象。比如
* - RF_ClassDefaultObject 表明它是 ClassDefaultObject;
* - RF_MarkAsRootSet 表明它不应被 GC,即使不被引用;
* - RF_NeedInitialization 表明该对象还未初始化完成状态;
* - ...
* 这些描述符体现为一个 32 位无符号整数的 bit
*/
EObjectFlags ObjectFlags;
/**
* GUObjectArray 的索引,用于实现「内存管理」与「垃圾回收」。
* 其中 GUObjectArray 是一个全局 UObject 数组。
* 每构造一个对象,都会在构造函数中调用 AddObject() 将其加入到数组中。
*/
int32 InternalIndex;
/** 该 Object 所属的 UClass,用于实现「反射」 */
ObjectPtr_Private::TNonAccessTrackedObjectPtr<UClass> ClassPrivate;
/** 该 Object 的名字,用于实现「反射」 */
FName NamePrivate;
/**
* 拥有该 Object 的 Object。
* 在运行时引用自己的对象可能有无穷个,但自己的 Outer 有且仅有一个。
* Outer 语义上就是真正拥有自己的那个对象。
*/
ObjectPtr_Private::TNonAccessTrackedObjectPtr<UObject> OuterPrivate;
后面四个对象都可以通过一个 public 接口 GetXXX()
获取。
UObject 的内存管理
UObject 与 UE 内存管理相关的所有逻辑是在其基类 UObjectBase
中实现的。
构造函数
首先观察 UObjectBase 的构造函数,发现执行了关键的一条函数 AddObject()
。
UObjectBase.cppUObjectBase::UObjectBase(...) { check(ClassPrivate); // Add to global table. AddObject(InName, InInternalFlags, InInternalIndex, InSerialNumber); } void UObjectBase::AddObject(FName InName, EInternalObjectFlags InSetInternalFlags, int32 InInternalIndex, int32 InSerialNumber) { ... EInternalObjectFlags InternalFlagsToSet = InSetInternalFlags; ... GUObjectArray.AllocateUObjectIndex(this, InternalFlagsToSet, InInternalIndex, InSerialNumber); HashObject(this); }
这里主要关注其中两步,即 AllocateUObjectIndex()
和 HashObject()
。
AllocateUObjectIndex()
这个函数的作用是将 Object 放到一个全局的名为 GUObjectArray
的对象中。顾名思义,该对象用于管理所有 UObject 的信息,之后要提的垃圾回收也是基于这个对象进行的。
UObjectArray.cppvoid FUObjectArray::AllocateUObjectIndex(UObjectBase* Object, EInternalObjectFlags InitialFlags, int32 AlreadyAllocatedIndex, int32 SerialNumber) { ... int32 Index = INDEX_NONE; LockInternalArray(); // 如果已经分配过了 if (AlreadyAllocatedIndex >= 0) { Index = AlreadyAllocatedIndex; } // 如果开启了忽略 GC 的优化 else if (OpenForDisregardForGC && DisregardForGCEnabled()) { Index = ++ObjLastNonGCIndex; // 如果超过 忽略 GC 的最大 Object 数量,则需要扩容。 if (ObjLastNonGCIndex >= MaxObjectsNotConsideredByGC) { Index = ObjObjects.AddSingle(); } MaxObjectsNotConsideredByGC = FMath::Max(MaxObjectsNotConsideredByGC, ObjLastNonGCIndex + 1); } // 剩下就是有 GC 的情况 else { // 这里用「空闲列表」是因为后续回收的 Object 索引是不确定的 // 要用空闲列表防止产生碎片 if (ObjAvailableList.Num() > 0) { Index = ObjAvailableList.Pop(); const int32 AvailableCount = ObjAvailableList.Num(); checkSlow(AvailableCount >= 0); } else // 如果空间不足,就扩容 { Index = ObjObjects.AddSingle(); } // 这个 check 信息量很大 // 可以得到 ObjFirstGCIndex > ObjLastNonGCIndex 总是成立的 // 这意味着 ObjectArray 划分为两部分,前面为 NonGC,后面为 NeedGC // 同时 NonGC 会优先于 NeedGC 创建 // GC 过程不会访问 NonGC Object,它们也不能有指向 NeedGC 的引用 // 这样可以加快 GC 速度,因为只需遍历较少 Object。 check(Index >= ObjFirstGCIndex && Index > ObjLastNonGCIndex); } FUObjectItem* ObjectItem = IndexToObject(Index); // 根据 Index 获取 ObjectArray 中的 Item,进行一些初始化工作 ... UnlockInternalArray(); // 最后挨个通知订阅了 ObjectCreated 事件的 Listener ... }
HashObject()
这个函数是利用 FName 的唯一性,求出 FName 的哈希值 Hash 后,建立 Hash 到 Object 的映射,从而实现根据名字访问对象。
void HashObject(UObjectBase* Object)
{
FName Name = Object->GetFName();
if (Name != NAME_None)
{
...
int32 Hash = 0;
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
// 因为一个 FName 唯一确定一个 Object
// 所以可以直接用 FName 进行 Hash
Hash = GetObjectHash(Name);
...
ThreadHash.AddToHash(Hash, Object);
if (PTRINT Outer = (PTRINT)Object->GetOuter())
{
// 这次 Hash 会加入 Outer 的信息
Hash = GetObjectOuterHash(Name, Outer);
...
ThreadHash.HashOuter.Add(Hash, Object->GetUniqueID());
// ObjectOuterMap(Outer).Add(Object)
AddToOuterMap(ThreadHash, Object);
}
// ClassToObjectListMap(Object.Class).Add(Object)
AddToClassMap( ThreadHash, Object );
}
}
FreeUObjectIndex()
该函数和 AllocateUObjectIndex()
对应,但是只在析构函数中调用,作用是回收 GUObjectArray
中的 item。
UObjectArray.cppvoid FUObjectArray::FreeUObjectIndex(UObjectBase* Object) { ... // This should only be happening on the game thread (GC runs only on game thread when it's freeing objects) check(IsInGameThread() || IsInGarbageCollectorThread()); // No need to call LockInternalArray(); here as it should already be locked by GC int32 Index = Object->InternalIndex; FUObjectItem* ObjectItem = IndexToObject(Index); // 将 ObjectItem 重置回默认值 ... if (Index > ObjLastNonGCIndex && !GExitPurge && bShouldRecycleObjectIndices) { ObjAvailableList.Add(Index); } }
UObject 的创建
因为 UObject 将其所有构造函数都设为了 private,所以我们只能通过以下两种方式构造一个 UObject:
NewObject<ObjectClass>()
CreateDefaultSubobject<ObjectClass>()
实际上内部都是将参数统一打包成一个 FStaticConstructObjectParameters
结构体,传入并调用 StaticConstructObject_Internal()
函数。
UObject* StaticConstructObject_Internal(const FStaticConstructObjectParameters& Params)
{
...
UObject* Result = NULL;
// 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 && (Params.bAssumeTemplateIsArchetype || InTemplate == UObject::GetArchetypeFromRequiredInfo(InClass, InOuter, InName, InFlags)))
);
const bool bCanRecycleSubobjects = bIsNativeFromCDO && (!(InFlags & RF_DefaultSubObject) || !FUObjectThreadContext::Get().IsInConstructor);
bool bRecycledSubobject = false;
Result = StaticAllocateObject(InClass, InOuter, InName, InFlags, Params.InternalSetFlags, bCanRecycleSubobjects, &bRecycledSubobject, Params.ExternalPackage);
// Don't call the constructor on recycled subobjects, they haven't been destroyed.
if (!bRecycledSubobject)
{
...
(*InClass->ClassConstructor)(FObjectInitializer(Result, Params));
}
...
return Result;
}
这里的逻辑相对来说比较复杂。首先看最开始的三个 const bool
。
首先看第一个 bIsNativeClass
,其内容比较好理解,就是判断当前的类是不是 CLASS_Native
/CLASS_Intrinsic
二者之一。前者代表这个类是在 C++ 而不是蓝图中定义的,而后者则表示该类是由 C++ 直接声明,未经过 UHT 生成反射代码,一般是引擎内部的类才会用到。
第二个 bIsNativeFromCDO
指的是是否根据类默认对象(Class Default Object, CDO)创建。
第三个 bCanRecycleSubobjects
指的是是否可以重复利用子对象(Subobject)。其中 RF_DefaultSubObject
标签只会在 CreateDefaultSubobject 时被赋予。也就是说如果一个对象不是通过 CDO 创建,且不是一个创建中的 Subobject,那么就可以对原来的 Subobject 进行重复利用。
至于为什么有「重复利用」一说,还得接着看后续的 StaticAllocateObject()
函数做了什么。
StaticAllocateObject()
这个函数实际做的工作是创建一个 Object 实例或者替换一个已经存在的 Class/Outer/Name 都一样的 Object。执行替换操作时,原 Object 将会被销毁,新 Object 将会占用其内存空间。
UObject* StaticAllocateObject(...)
{
...
UObject* Obj = NULL;
if(InName == NAME_None)
{ // 如果创建时没有设置 Name,则生成一个独一无二的 Name
...
InName = MakeUniqueObjectName(InOuter, InClass);
}
else
{
// 查找是否存在 Outer Name 一样的 Object
Obj = StaticFindObjectFastInternal( /*Class=*/ NULL, InOuter, InName, true );
// 类型必须一致,否则 fatal
if (Obj && !Obj->GetClass()->IsChildOf(InClass)) { ... }
}
...
if( Obj == nullptr )
{ // 如果不存在上述 Object,就调用 AllocateUObject() 进行内存分配
...
Obj = (UObject *)GUObjectAllocator.AllocateUObject(...);
}
else
{ // 否则,直接替换
if (!bCreatingCDO && (!bCanRecycleSubobjects || !Obj->IsDefaultSubobject()))
{ // 如果不是 CDO ,或者是蓝图类,那就要进行 Destory
if(!Obj->HasAnyFlags(RF_FinishDestroyed))
{
// 如果没完成 Destroy,则销毁原来的 Object
Obj->ConditionalBeginDestroy();
...
Obj->ConditionalFinishDestroy();
}
...
Obj->~UObject();
...
}
else
{
bSubObject = true;
}
}
...
if (!bSubObject)
{ // 在此之前已经在这块内存上调用过析构函数,故需要重新格式化一下
// 并用 placement new 构造一个 Object
FMemory::Memzero((void *)Obj, TotalSize);
new ((void *)Obj) UObjectBase(...);
}
else
{ // 反之,只需要修改原 Object 的一些变量即可,也就是进行了重用
Obj->SetFlags(InFlags);
Obj->SetInternalFlags(InternalSetFlags);
}
...
return Obj;
}
在 placement new 的过程中,就会执行 UObject
的构造函数,进而执行一些和 GUObjectArray
的交互行为。随着 return Obj;
的执行,至此,一个新的 UObject 就被创建好了。
UObject 的销毁
自动销毁
可以将指向 UObject 的指针置空,这样后续就会被 UE 自动回收掉
自动销毁Obj = NewObject<UObject>(this, TEXT("Obj")); Obj = nullptr;
主动销毁
UObject::ConditionalBeginDestroy()
异步执行且对象在当前帧内持续有效 等待下次GC Obj->ConditionalBeginDestroy(); Obj = nullptr; MarkPendingKill()
标记为PendingKill,等待回收。指向此实例的指针将设置为NULL,并在下一次GC时删除。 IsPendingKill 判断是否处于 PendingKill 状态 ClearPendingKill 清除 PendingKill 状态 Obj->MarkPendingKill(); Obj = nullptr; Engine.ini 更改下面参数,设置销毁时间间隔
gc.TimeBetweenPurgingPendingKillObjects=60 强制垃圾回收 UWorld::ForceGarbageCollection 弃用
GEngine->ForceGarbageCollection
GEngine->ForceGarbageCollection(true);