新手,有错请指出,大家共同进步
Unreal中UObject的基类
类型 | 名字 | 说明 |
---|---|---|
EObjectFlags | ObjectFlags | ObjectFlags是用于跟踪和记录对象的各种状态,它被引擎用于表示对象加载、保存、编辑、垃圾回收和对象作用标识时候使用。简单地说,就是一个标记信息。 |
int32 | InternalIndex | 全局对象数组的下标,unreal在运行时候会维护一个全局的对象数组,每当创建一个对象的时候便放到数组里面 |
UClass* | ClassPrivate | 类型信息 |
FName | NamePrivate | 对象名字 |
UObject* | OuterPrivate | Outer对象的指针,暂时没弄明白是啥意思。(update)一个对象A的"Outer"是拥有A的对象。例如,Component由其Actor或父组件拥有,Actors由其Levels拥有。 无论何时构造从UObject派生的类的对象,都要为它提供外部。 (CreateDefaultSubobject隐式提供当前对象作为外部。)可不可以这样说,人是一个对象,手是一个对象,而手的outer是人? 看这里解释 |
()
这个构造函数,只把NamePrivate
设置为NoInit
,其他啥事都没有做
( EObjectFlags InFlags )
Constructor used for bootstrapping
这时候对象并没有加入到全局对象数组中,感觉有特殊用途
(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)
UObjectBase::UObjectBase(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)
: ObjectFlags (InFlags)
, InternalIndex (INDEX_NONE)
, ClassPrivate (InClass)
, OuterPrivate (InOuter)
#if ENABLE_STATNAMEDEVENTS_UOBJECT
, StatIDStringStorage(nullptr)
#endif
{
check(ClassPrivate);
// Add to global table.
AddObject(InName, InInternalFlags);
}
这个构造函数是用于创建静态分配的对象,应该是一般的对象,构造函数把属性都赋值了,但是值得注意的是,InternalIndex
的值为INDEX_NONE
,也就是-1
,并没有马上就把对象加入了全局对象数组中。是不是说,创建该对象前,不能知道InternalIndex
? EInternalObjectFlags标记存放在全局对象管理数组的元素中(这是为了提高Cache命中率)
后面调用check(ClassPrivate)
来判断classPrivate是否为NULL
最后AddObject(InName, InInternalFlags)
把对象加入到全局对象数组中
~UObjectBase()
/**
* Final destructor, removes the object from the object array, and indirectly, from any annotations
**/
UObjectBase::~UObjectBase()
{
// If not initialized, skip out.
if( UObjectInitialized() && ClassPrivate && !GIsCriticalError )
{
// Validate it.
check(IsValidLowLevel());
LowLevelRename(NAME_None);
GUObjectArray.FreeUObjectIndex(this);
}
#if ENABLE_STATNAMEDEVENTS_UOBJECT
delete[] StatIDStringStorage;
StatIDStringStorage = nullptr;
#endif
}
通过LowLevelRename(NAME_None)
改掉名字,通过GUObjectArray.FreeUObjectIndex(this)
把对象从全局对象数组中去掉
(EObjectFlags FlagsToClear)
/**
* Atomically clears the specified flags.
* Do not use unless you know what you are doing.
* Designed to be used only by parallel GC and UObject loading thread.
*/
FORCENOINLINE void AtomicallyClearFlags( EObjectFlags FlagsToClear )
{
int32 OldFlags = 0;
int32 NewFlags = 0;
do
{
OldFlags = ObjectFlags;
NewFlags = OldFlags & ~FlagsToClear;
}
while( FPlatformAtomics::InterlockedCompareExchange( (int32*)&ObjectFlags, NewFlags, OldFlags) != OldFlags );
}
原子级别上清理flags,是不是能理解成在flags上的位上进行清理操作呢?为什么要这样操作?
(EObjectFlags FlagsToAdd)
/**
* Atomically adds the specified flags.
* Do not use unless you know what you are doing.
* Designed to be used only by parallel GC and UObject loading thread.
*/
FORCENOINLINE void AtomicallySetFlags( EObjectFlags FlagsToAdd )
{
int32 OldFlags = 0;
int32 NewFlags = 0;
do
{
OldFlags = ObjectFlags;
NewFlags = OldFlags | FlagsToAdd;
}
while( FPlatformAtomics::InterlockedCompareExchange( (int32*)&ObjectFlags, NewFlags, OldFlags) != OldFlags );
}
原子级别上清理flags,***是不是能理解成在flags上的位上进行清理操作呢?***为什么要这样操作?
(
UClass * UClassStaticClass,
const TCHAR * PackageName,
const TCHAR * Name
)
/**
* Convert a boot-strap registered class into a real one, add to uobject array, etc
*
* @param UClassStaticClass Now that it is known, fill in UClass::StaticClass() as the class
*/
void UObjectBase::DeferredRegister(UClass *UClassStaticClass,const TCHAR* PackageName,const TCHAR* InName)
{
//检查该对象是否已经初始化,当UObjectBaseInit()调用后,就初始化了。
check(Internal::GObjInitialized);
// Set object properties.
//根据PackageName找到Outer,然后设置本对象的Outer
UPackage* Package = CreatePackage(nullptr, PackageName);
check(Package);
Package->SetPackageFlags(PKG_CompiledIn);
OuterPrivate = Package;
check(UClassStaticClass);
check(!ClassPrivate);
ClassPrivate = UClassStaticClass;
// Add to the global object table.
AddObject(FName(InName), EInternalObjectFlags::None);
//这里是一些判断,确保该对象现在不会被垃圾回收,和已经放到root of set里面了
// Make sure that objects disregarded for GC are part of root set.
check(!GUObjectArray.IsDisregardForGC(this) || GUObjectArray.IndexToObject(InternalIndex)->IsRootSet());
}
此函数的目的是把通过构造函数UObjectBase( EObjectFlags InFlags )
创建的boot-strap对象转化为真实的对象,也就是转化为跟通过构造函数UObjectBase(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)
创建的对象一样
函数中保全了类的属性,然后通过AddObject
把对象加入全局对象数组中。
(
UClass * RootClass
)
Emit GC tokens for UObjectBase , this might be UObject::StaticClass or Default__Class
void UObjectBase::EmitBaseReferences(UClass *RootClass)
{
static const FName ClassPropertyName(TEXT("Class"));
static const FName OuterPropertyName(TEXT("Outer"));
RootClass->EmitObjectReference(STRUCT_OFFSET(UObjectBase, ClassPrivate), ClassPropertyName);
RootClass->EmitObjectReference(STRUCT_OFFSET(UObjectBase, OuterPrivate), OuterPropertyName, GCRT_PersistentObject);
}
FORCEINLINE uint32 GetUniqueID() const
{
return (uint32)InternalIndex;
}
FORCEINLINE UClass* GetClass() const
{
return ClassPrivate;
}
FORCEINLINE UObject* GetOuter() const
{
return OuterPrivate;
}
FORCEINLINE FName GetFName() const
{
return NamePrivate;
}
不多说。。。
()
/**
* Checks to see if the object appears to be valid
* @return true if this appears to be a valid object
*/
bool UObjectBase::IsValidLowLevel() const
{
if( this == nullptr )
{
UE_LOG(LogUObjectBase, Warning, TEXT("NULL object") );
return false;
}
if( !ClassPrivate )
{
UE_LOG(LogUObjectBase, Warning, TEXT("Object is not registered") );
return false;
}
return GUObjectArray.IsValid(this);
}
检查该对象是否为valid
(
bool bRecursive
)
一个能比 IsValidLowLevel
更快速检查是否为valid
的函数
(
FName NewName,
UObject * NewOuter
)
/**
* Just change the FName and Outer and rehash into name hash tables. For use by higher level rename functions.
*
* @param NewName new name for this object
* @param NewOuter new outer for this object, if NULL, outer will be unchanged
*/
void UObjectBase::LowLevelRename(FName NewName,UObject *NewOuter)
{
STAT(StatID = TStatId();) // reset the stat id since this thing now has a different name
UnhashObject(this);
check(InternalIndex >= 0);
NamePrivate = NewName;
if (NewOuter)
{
OuterPrivate = NewOuter;
}
HashObject(this);
}
改变FName和Outer。值得注意的是,name hash table也要改变。
FName在初始化时候,会根据字符串计算出hash值,然后把该hash值传入name hash table。然后要对比两个FName,就只需要对比两个hash值就行了。***这样会加快速度?***参照链接
(
const TCHAR * PackageName,
const TCHAR * Name
)
/** Enqueue the registration for this object. */
void UObjectBase::Register(const TCHAR* PackageName,const TCHAR* InName)
{
TMap& PendingRegistrants = FPendingRegistrantInfo::GetMap();
FPendingRegistrant* PendingRegistration = new FPendingRegistrant(this);
PendingRegistrants.Add(this, FPendingRegistrantInfo(InName, PackageName));
if(GLastPendingRegistrant)
{
GLastPendingRegistrant->NextAutoRegister = PendingRegistration;
}
else
{
check(!GFirstPendingRegistrant);
GFirstPendingRegistrant = PendingRegistration;
}
GLastPendingRegistrant = PendingRegistration;
}
排队注册
()
/** Force any base classes to be registered first */
virtual void RegisterDependencies() {}
先注册。。。***跟上面有什么不一样的?***函数体为空?
(
EObjectFlags NewFlags
)
/**
* Set the object flags directly
*
**/
FORCEINLINE void SetFlagsTo( EObjectFlags NewFlags )
{
checkfSlow((NewFlags & ~RF_AllFlags) == 0, TEXT("%s flagged as 0x%x but is trying to set flags to RF_AllFlags"), *GetFName().ToString(), (int)ObjectFlags);
ObjectFlags = NewFlags;
}
直接设置flags
()
/**
* Final phase of UObject initialization. all auto register objects are added to the main data structures.
*/
void UObjectBaseInit()
{
//下面四个初始化为0,但是迟点在.ini文件中读取(这个应该是开发游戏的程序员自行设定的)
// Zero initialize and later on get value from .ini so it is overridable per game/ platform...
int32 MaxObjectsNotConsideredByGC = 0;
int32 SizeOfPermanentObjectPool = 0;
int32 MaxUObjects = 2 * 1024 * 1024; // Default to ~2M UObjects
bool bPreAllocateUObjectArray = false;
// To properly set MaxObjectsNotConsideredByGC look for "Log: XXX objects as part of root set at end of initial load."
// in your log file. This is being logged from LaunchEnglineLoop after objects have been added to the root set.
// Disregard for GC relies on seekfree loading for interaction with linkers. We also don't want to use it in the Editor, for which
// FPlatformProperties::RequiresCookedData() will be false. Please note that GIsEditor and FApp::IsGame() are not valid at this point.
if (FPlatformProperties::RequiresCookedData())
{
FString Value;
bool bIsCookOnTheFly = FParse::Value(FCommandLine::Get(), TEXT("-filehostip="), Value);
if (bIsCookOnTheFly)
{
extern int32 GCreateGCClusters;
GCreateGCClusters = false;
}
else
{
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsNotConsideredByGC"), MaxObjectsNotConsideredByGC, GEngineIni);
// Not used on PC as in-place creation inside bigger pool interacts with the exit purge and deleting UObject directly.
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.SizeOfPermanentObjectPool"), SizeOfPermanentObjectPool, GEngineIni);
}
// Maximum number of UObjects in cooked game
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInGame"), MaxUObjects, GEngineIni);
// If true, the UObjectArray will pre-allocate all entries for UObject pointers
GConfig->GetBool(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.PreAllocateUObjectArray"), bPreAllocateUObjectArray, GEngineIni);
}
else
{
#if IS_PROGRAM
// Maximum number of UObjects for programs can be low
MaxUObjects = 100000; // Default to 100K for programs
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInProgram"), MaxUObjects, GEngineIni);
#else
// Maximum number of UObjects in the editor
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInEditor"), MaxUObjects, GEngineIni);
#endif
}
// Log what we're doing to track down what really happens as log in LaunchEngineLoop doesn't report those settings in pristine form.
UE_LOG(LogInit, Log, TEXT("%s for max %d objects, including %i objects not considered by GC, pre-allocating %i bytes for permanent pool."),
bPreAllocateUObjectArray ? TEXT("Pre-allocating") : TEXT("Presizing"),
MaxUObjects, MaxObjectsNotConsideredByGC, SizeOfPermanentObjectPool);
GUObjectAllocator.AllocatePermanentObjectPool(SizeOfPermanentObjectPool);
GUObjectArray.AllocateObjectPool(MaxUObjects, MaxObjectsNotConsideredByGC, bPreAllocateUObjectArray);
void InitAsyncThread();
InitAsyncThread();
//注意在这里表明初始化了
// Note initialized.
Internal::GObjInitialized = true;
UObjectProcessRegistrants();
}
这个函数主要做了4件事: 1. 初始化UObject的内存分配存储系统和对象的Hash系统。 2. 创建了异步加载线程,用来后续Package(uasset)的加载。 3. GObjInitialized=true,这样在后续就可以用bool UObjectInitialized()
来判断对象系统是否可用。 4. 继续转发到UObjectProcessRegistrants
来把注册项一一处理。
该类继承了UObjectBase,为UObject类定义了只与UObject类有关的函数,提供了一些辅助功能,Flag设置和查询、Mark设置与查询、Class查询、名字查询、Linker信息(Linker为uasset加载器)
无
空的,啥事都没做
(EObjectFlags InFlags)
只是设置了Inflags
返回值 | 参数 | 说明 |
---|---|---|
void | UObjectBaseUtility* ClusterRootOrObjectFromCluster, bool bAddAsMutableObject = false | 把对象放进集群里面,便于垃圾回收 |
返回值 | 参数 | 说明 |
---|---|---|
void | 无 | 把对象放到全局对象数组的根集合里面,其实应该就是放到全局数组对象的前面,可以防止对象被垃圾回收 |
返回值 | 参数 | 说明 |
---|---|---|
void | FString & ResultString | 帮对象增长名字 |
返回值 | 参数 | 说明 |
---|---|---|
bool | EInternalObjectFlags FlagsToClear | 帮对象清理掉过时的flag |
返回值 | 参数 | 说明 |
---|---|---|
bool | 无 | 判断该对象是否能作为一个cluster的根 |
返回值 | 参数 | 说明 |
---|---|---|
bool | 判断该对象是否能增加到一个cluster里面 |
返回值 | 参数 | 说明 |
---|---|---|
void | EobjectFlags newFlags | 清理掉该对象的某个flag |
返回值 | 参数 | 说明 |
---|---|---|
void | EInternalObjectFlags FlagsToClear | 清理掉该对象的internal flags |
返回值 | 参数 | 说明 |
---|---|---|
void | EInternalObjectFlags FlagsToClear | 清理掉flag中的PendingKill位,也就是说取消杀死该对象 |
返回值 | 参数 | 说明 |
---|---|---|
void | NULL | 创建一个新的对象Cluster |
返回值 | 参数 | 说明 |
---|---|---|
void | UObjectBaseUtility* ClusterRootObject, UObjectBaseUtility* ReferencingObject | 清理掉该对象的internal flags |
返回值 | 参数 | 说明 |
---|---|---|
const UClass* | const UClass * TestClass | 找到该类和TestClass共同的最近的基类 |
返回值 | 参数 | 说明 |
---|---|---|
EObjectMark | NULL | 返回该对象的所有marks |
返回值 | 参数 | 说明 |
---|---|---|
FString | bool bStartWithOuter | 返回最外面的包到该对象路径名(除去最外面的包名) |
返回值 | 参数 | 说明 |
---|---|---|
FString | const UObject * StopOuter | 返回最外面的包到该对象的对象名 |
返回值 | 参数 | 说明 |
---|---|---|
void * | UClass * InterfaceClass | 返回一个指向该对象的指针,该指针已经安全地转换成了特定接口类的指针类型 |
函数名 | 返回值 | 参数 | 说明 |
---|---|---|---|
GetInternalFlags | EInternalObj | 返回该对象的internal flags | |
GetLinker | GetInternalFlags | 返回对象的linker | |
GetLinkerCustomVersion | int32 | FGuid CustomVersionKey | 根据自定义版本的key,返回自定义版本的linker |
GetLinkerIndex | int32 | 返回该对象的linker索引 | |
GetLinkerLicenseeUE4Version() | int32 | 返回该函数的linker的证书的UE4版本 | |
GetMaskedFlags | EObjectFlags | EObjectFlags Mask | 将对象的flag与掩码(mask)作与运算,返回结果 |
GetName | FString & ResultString | 将该对象的名字写入ResultString,能够避免重复创建一个FString对象,达到省内存目的 | |
GetName | FString | 返回该对象的名字 | |
GetNativeInterfaceAddress | void* | UClass * InterfaceClass | 返回一个指向该对象实现的InterfaceClass本机接口对象的指针 |
GetNativeInterfaceAddress | const void* | UClass * InterfaceClass | 返回一个指向该对象实现的const InterfaceClass本机接口对象的指针 |
GetOutermost | UPackage* | 返回一个指向该对象最外面的非NULL outer | |
GetPathName | FString | const UObject * StopOuter | 返回该对象的路径名 |
GetPathName | void | const UObject * StopOuter, FString & ResultString | 返回该对象的路径名,将路径名写入ResultString里面,避免多一个FString |
GetTypedOuter | UObject * | UClass* Target | 往上遍历该对象的outer,返回第一个类型为Target的对象 |
GetTypedOuter | T * | 往上遍历该对象的outer,返回第一个类型为Target的对象 | |
HasAllFlags | bool | EObjectFlags FlagsToCheck | 判断该对象的flag是否含有FlagsToCheck里面的所有 |
HasAllMarks | bool | EObjectMark Marks | 判断是否含有所有的marks |
HasAnyFlags | bool | EObjectFlags FlagsToCheck | 检查是否含有FlagsToCheck里面的任何一个 |
函数名 | 返回值 | 参数 | 说明 |
---|---|---|---|
HasAnyInternalFlags | bool | EInternalObjectFlags FlagsToCheck | 判断是否含有FlagsToCheck里面的任何一个 |
HasAnyMarks | bool | EObjectMark Marks | 判断是否含有marks里面的任何一个 |
IsA | bool | OtherClassType SomeBase | 判断该对象是否为某一个类型 |
IsDefaultSubobject() | bool | 判断该对象是否是一个组件/子对象的模板或者模板的实例 | |
IsIn | bool | const UObject * SomeOuter | 判断该对象是否在SomeOuter的outer链中 |
IsInA | bool | const UClass * SomeBaseClass | 判断该对象的outer链中是否有SomeBaseClass类型的class |
IsNative | bool | 判断该对象是否是本地的 | |
IsPendingKill | bool | 判断该对象是否是已经被杀死,但是还在内存中 | |
IsPendingKillOrUnreachable | bool | 判断该对象是否是pandingKilling或者已经无法使用了 | |
IsRooted | bool | 判断该对象是否位于全局数组对象的根部集合 | |
IsTemplate | bool | EObjectFlags TemplateTypes | 判断该对象是否是TemplateType类型的模板对象 |
IsUnreachable | bool | 判断该对象是否是unreachable的 | |
Mark | void | EObjectMark Marks | 为该对象增加mark |
MarkPackageDirty | bool | 找到该对象最上的outer,并标记为脏 | |
MarkPendingKill | void | 标记该对象的为RF_PendingKill | |
OnClusterMarkedAsPendingKill | void | 如果该cluster已经被标记为PendingKill,那么当垃圾回收该cluster即将被摧毁时,该函数会被调用,以执行一些额外的清理工作 | |
RemoveFromRoot | void | 将该对象从全局对象数组的根集合上去掉 | |
RootPackageHasAnyFlags | bool | uint32 CheckFlagMask | 判断该对象的最上的包是否有CheckFlagMask上的任何一个flag |
SetFlags | void | EObjectFlags NewFlags | 为该对象设置新的flag |
SetInternalFlags | void | EInternalObjectFlags FlagsToSet | 清理掉过时的内部flags |
ThisThreadAtomicallyClearedRFUnreachable | bool | 清理掉没用的flag,如果在清理RF_Unreachable的线程中,就返回true | |
UnMark | void | EObjectMark Marks | 取消该对象的Mark标记 |
operator< | bool | const UObjectBaseUtility & Other | 判断该对象的名字与Other的名字是否按字典顺序小 |
该类派生于UObjectBaseUtility,是所有其他unreal对象的基类,提供了如下功能:创建子对象(SubObject),对象Destroy相关事件处理,对象编辑相关事件处理,序列化,执行脚本,从config文件读取或保存成员变量配置等等,本次因为函数众多(100+),就不一一描述了。
无
函数名 | 返回值 | 参数 | 说明 |
---|---|---|---|
AbortInsideMemberFunction | void | 在调用堆栈的顶部中止成员函数调用,帮助确保大多数平台将此对象的内存填充到生成的minidump中 | |
AddReferencedObjects | void | UObject * InThis, FReferenceCollector & Collector | 用于允许对象注册直接对象引用,该引用尚未被token stream覆盖 |
AreNativePropertiesIdenticalTo | bool | UObject * Other | 判断本地的属性是否与传入的属性相同 |
BeginDestroy() | void | 当该对象被摧毁前调用 |
本次实验从最底层开始,一步一步地抽象,从而逐步了解UE4。网上教程说UObject是UE4万物起源,但实际上UObject还是有其基类,UE4的一切其实从UObjectBase开始,UObjectBase其实只定义了极少数的功能,包括记录了对象的状态和名字等等,这些其实在直观上并没有与我们游戏中感受到的东西有太大关系,并没有场景管理、光照、碰撞等功能,一切都显得十分原始。但是UObjectBase并不是独立地完成它的工作,它也必须依赖其他的类来完成它的工作,例如GUObjectArray类。
UObjectBaseUtility继承自UObjectBase,它的功能又强大了一些,提供了一些辅助功能,Flag设置和查询、Mark设置与查询、Class查询、名字查询、Linker信息查询等等。这些功能更多是UObjectBase的一些补充,让UE4对象的功能更强。
而到了UObject,它是UE4其他所有对象的基类,提供了如下功能:创建子对象(SubObject),对象Destroy相关事件处理,对象编辑相关事件处理,序列化,执行脚本,从config文件读取或保存成员变量配置等等。这些功能相比它的基类又显得抽象一些,功能上更强大和复杂起来,而且离计算机底层的距离又远了一些。因为要实现的功能越来越多,UObject的代码也更复杂和臃肿,类之间的依赖关系越来越多,代码阅读起来也更难。
总的来说,本次源码阅读给我上了一堂十分有益的面向对象思想。以前编写代码的时候都比较偏向于面向过程,一个函数完成很多功能,当代码量比较少的时候这种方法并没有什么弊端,但是代码量大的时候弊端就很多。现在读了UE4的一点点源码,一开始时候觉得不能理解,为什么一个object基类要写成三个这么麻烦呢?其中的代价是否值得呢?后来当源码看多了后就慢慢觉得这是十分值得的,因为这降低了出错的几率,统一了通用的属性和接口,也方便其他人员理解。
作为一个完整的object基类,它基本上封装好了与对象有关的所有基本和共有的操作,因此后面由它派生的类就能很方便的用上它的功能,它的派生类,例如涉及场景管理和角色管理的类,相互协作,从而支撑起UE4庞大的系统。
以下是从网上大佬拷贝下来的引入object基类的作用:
https://www.cnblogs.com/fjz13/p/6164443.html | |
---|---|
那么引入一个Object的根基类设计到底有什么深远的影响,我们又付出了什么代价?
得到:
代价: