1.探究初衷
最近在研究UE4引擎,不可避免的遇到垃圾回收机制的学习,在网上看了一堆的介绍之后,依旧云里雾里。主要的疑惑有以下几个:
- 都说UObject会自动回收,可是我们创建的UOBject是如何被引擎添加到管理队列里的;
- UObject是在何时进行回收的;
- AddToRoot为何能保证UObject不被自动回收;
- MarkPendingKill函数又干了什么可以让UObject销毁。
2.探究历程
我刚开始猜想应该是在NewObject的过程中进行了某些操作,所以进去看了下,为了方便阅读,直接把代码贴在这里:
// Epic Games\UE_4.26\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectGlobals.h Line 1123
/**
* Convenience template for constructing a gameplay object
*
* @param Outer the outer for the new object. If not specified, object will be created in the transient package.
* @param Class the class of object to construct
* @param Name the name for the new object. If not specified, the object will be given a transient name via MakeUniqueObjectName
* @param Flags the object flags to apply to the new object
* @param Template the object to use for initializing the new object. If not specified, the class's default object will be used
* @param bCopyTransientsFromClassDefaults if true, copy transient from the class defaults instead of the pass in archetype ptr (often these are the same)
* @param InInstanceGraph contains the mappings of instanced objects and components to their templates
* @param ExternalPackage Assign an external Package to the created object if non-null
*
* @return a pointer of type T to a new object of the specified class
*/
template< class T >
FUNCTION_NON_NULL_RETURN_START
T* NewObject(UObject* Outer, const UClass* Class, FName Name = NAME_None, EObjectFlags Flags = RF_NoFlags, UObject* Template = nullptr, bool bCopyTransientsFromClassDefaults = false, FObjectInstancingGraph* InInstanceGraph = nullptr, UPackage* ExternalPackage = 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.CreateDefaultSubobject<> instead."));
}
#if DO_CHECK
// Class was specified explicitly, so needs to be validated
CheckIsClassChildOf_Internal(T::StaticClass(), Class);
#endif
FStaticConstructObjectParameters Params(Class);
Params.Outer = Outer;
Params.Name = Name;
Params.SetFlags = Flags;
Params.Template = Template;
Params.bCopyTransientsFromClassDefaults = bCopyTransientsFromClassDefaults;
Params.InstanceGraph = InInstanceGraph;
Params.ExternalPackage = ExternalPackage;
return static_cast<T*>(StaticConstructObject_Internal(Params));
}
我顺腾摸瓜查看了StaticConstructObject_Internal相关函数,发现里面只是一些创建代码,并没有关于对象垃圾回收相关的代码。巧合的是我在***StaticConstructObject_Internal(const FStaticConstructObjectParameters& Params)***函数里看到了MarkPendingKill函数,点进去看了一下,线索来了。看一下MarkPendingKill函数的代码:
// Epic Games\UE_4.26\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectBaseUtility.h Line171
/**
* Marks this object as RF_PendingKill.
*/
FORCEINLINE void MarkPendingKill()
{
check(!IsRooted());
GUObjectArray.IndexToObject(InternalIndex)->SetPendingKill();
}
我发现了GUObjectArray,很显然这是一个管理UOBject的数组,他很有可能就是我在开头提到的管理队列。我们看下他的声明
// Epic Games\UE_4.26\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectHash.cpp Line 24
// Global UObject array instance
FUObjectArray GUObjectArray;
接下来再看下FUObjectArray的声明,篇幅原因这里只贴部分代码:
// D:\Program Files\Epic Games\UE_4.26\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectArray.h Line 573
/***
*
* FUObjectArray replaces the functionality of GObjObjects and UObject::Index
*
* Note the layout of this data structure is mostly to emulate the old behavior and minimize code rework during code restructure.
* Better data structures could be used in the future, for example maybe all that is needed is a TSet<UObject *>
* One has to be a little careful with this, especially with the GC optimization. I have seen spots that assume
* that non-GC objects come before GC ones during iteration.
*
**/
class COREUOBJECT_API FUObjectArray
{
public:
/**
* Constructor, initializes to no permanent object pool
*/
FUObjectArray();
/**
* Adds a uobject to the global array which is used for uobject iteration
*
* @param Object Object to allocate an index for
*/
void AllocateUObjectIndex(class UObjectBase* Object, bool bMergingThreads = false);
}
我单独把AllocateUObjectIndex这个成员函数贴出来了,显然只要找到这个函数在哪里被引用的,自然可以解决开头提出的第一个问题。查找引用后得出引用的地方:
// Epic Games\UE_4.26\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectHash.cpp Line 1253
void AllocateUObjectIndexForCurrentThread(UObjectBase* Object)
{
GUObjectArray.AllocateUObjectIndex(Object);
}
OK,接近了,再查找一下这个函数的被引用情况:
// Epic Games\UE_4.26\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectBase.cpp Line 165
/**
* 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;
ObjectFlags &= ~RF_MarkAsRootSet;
}
if (ObjectFlags & RF_MarkAsNative)
{
InternalFlagsToSet |= EInternalObjectFlags::Native;
ObjectFlags &= ~RF_MarkAsNative;
}
AllocateUObjectIndexForCurrentThread(this);//看这里看这里看这里看这里看这里看这里
check(InName != NAME_None && InternalIndex >= 0);
if (InternalFlagsToSet != EInternalObjectFlags::None)
{
GUObjectArray.IndexToObject(InternalIndex)->SetFlags(InternalFlagsToSet);
}
HashObject(this);
check(IsValidLowLevel()<