凡是稍微大一点的引擎框架,必然都要自己搞一套对象管理机制,如mfc、qt、glib等等,unreal自然也不例外。

究其原因,还是c++这种静态语言天生的不足,缺乏运行时类型操作功能,对于复杂庞大的逻辑层来说极为不便,查错和调优都很困难。

因此,这类框架自制对象管理模块的功能通常都包括:

1、运行时类型信息获取

a、总共有哪些类,各自之间的继承关系怎样?简单来说,能否在运行时print一个类的继承链图出来。

b、给你一个最基类指针,你能准确知道他实际是哪个子类的,有哪些方法、属性?

2、运行时类型创建及调用

a、给你一个字符串名字,能否创建该类的对象?

b、给你一些字符串方法或属性名,能否调用之?包括正确的参数和返回值处理

3、对象生命期的管理

a、所有已创建的对象,是否都能追踪遍历?

b、对象所占据内存,是否都有分门别类统计?

c、对象是否支持gc?

4、各种杂项

a、对象是否支持序列化?

b、对象是否支持由脚本创建调用?

c、对象是否支持设计时模式?也就是在编辑里呈现出另一种效果,可即时修改属性观察变化。

以上种种,通常都被各类框架不厌其烦的实现很多次了,即使在自己写的稍大一点的项目里,也或多或少做过类似的事,区别只是所谓商业级引擎,会做得更极致,更完善罢了。

而这些功能,其实都是java里的标配,在c++里却不得不各种重新发明轮子,这也说明c++在大工程架构方面先天不足,惟一的优势就是速度快,而游戏引擎极其注重性能,所以不得不在c++的基础上,重新添加大量类Java功能,以求性能和易用的结合。

在unreal3里,实现此模块的两个核心类分别是:

1、UObject:基本上是所有逻辑层类的祖基类,“一切皆是对象”,而每个对象必定属于一个类型,所以UObject里面有一个UClass* Class字段,就指向它的类型

2、UClass:这个就是表示一个类型的类,与UObject有大量的子类不同,UClass没有子类,它只是用不同的实例(携带不同属性)来表示各种类型。

举例来说,UObject的两个子类UCommandlet和UComponent,分别代表命令小工具和组件,它们各自有一个对应的UClass实例来描述自身类型信息。每一个Unrealscript类,也都有一个UClass实例与之对应。

这些UClass实例的创建过程也正好分类两类:

一是native类,即生成了C++头文件的,那么其中就包含相应的创建代码,相当于是手动写死链接的。

二是纯脚本类,它们的UClass实例是在加载Package时,由解析代码动态创建的。

UObject类型系统的实现:

在每个UObject子类的声明里,都有类似如下的宏:

DECLARE_ABSTRACT_CLASS(UCommandlet,UObject,0|CLASS_Transient,Core)

其中DECLARE_ABSTRACT_CLASS可能有DECLARE_CLASS、DECLARE_CASTED_CLASS、DECLARE_CLASS_INTRINSIC等变种,用于设定继承时的各类性质差别,其核心最后都转到【DECLARE_BASE_CLASS_LIGHTWEIGHT】:

#define DECLARE_BASE_CLASS_LIGHTWEIGHT( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage ) \
public: \
friend void AutoCheckNativeClassSizes##TPackage( UBOOL& Mismatch ); \
/* Identification */ \
enum {StaticClassFlags=TStaticFlags}; \
enum {StaticClassCastFlags=TStaticCastFlags}; \
private: \
static UClass* PrivateStaticClass; \
TClass & operator=(TClass const &); \
public: \
typedef TSuperClass Super;\
typedef TClass ThisClass;\
static UClass* GetPrivateStaticClass##TClass( const TCHAR* Package ); \
static void InitializePrivateStaticClass##TClass(); \
static UClass* StaticClass() \
{ \
if (!PrivateStaticClass) \
{ \
PrivateStaticClass = GetPrivateStaticClass##TClass( TEXT(#TPackage) ); \
InitializePrivateStaticClass##TClass(); \
} \
return PrivateStaticClass; \
} \
void* operator new( const size_t InSize, UObject* InOuter=(UObject*)GetTransientPackage(), FName InName=NAME_None, EObjectFlags InSetFlags= ) \
{ return StaticAllocateObject( StaticClass(), InOuter, InName, InSetFlags ); } \
void* operator new( const size_t InSize, EInternal* InMem ) \
{ return (void*)InMem; }

这里的关键:

1、static UClass* PrivateStaticClass; 这就是每个类型对应的那个UClass*实例了

2、static UClass* StaticClass(); 这个就是初始化函数,在程序启动时,每个包每个(native)类的该函数都会被调用,相当于注册,其中通过GetPrivateStaticClasssXXX来创建UClass*实例,然后调用InitializePrivateStaticClassXXX来初始化其属性。这两个函数的实现在后面说明。

3、重载了两个new操作符,也就是设定了其内存分配函数到自己的StaticAllocateObject中,里面执行大量跟踪统计逻辑。

然后在每个UObject子类的实现中,都有如下宏:

IMPLEMENT_CLASS(UCommandlet);

最后展开为:

#define IMPLEMENT_CLASS_LIGHTWEIGHT(TClass) \
UClass* TClass::PrivateStaticClass = NULL; \
UClass* TClass::GetPrivateStaticClass##TClass( const TCHAR* Package ) \
{ \
UClass* ReturnClass; \
ReturnClass = ::new UClass \
( \
EC_StaticConstructor, \
sizeof(TClass), \
StaticClassFlags, \
StaticClassCastFlags, \
TEXT(#TClass) + + ((StaticClassFlags & CLASS_Deprecated) ? : ), \
Package, \
StaticConfigName(), \
RF_Public | RF_Standalone | RF_Transient | RF_Native | RF_RootSet | RF_DisregardForGC, \
(void(*)(void*))TClass::InternalConstructor, \
(void(UObject::*)())&TClass::StaticConstructor, \
(void(UObject::*)())&TClass::InitializeIntrinsicPropertyValues \
); \
check(ReturnClass); \
return ReturnClass; \
} \
/* Called from ::StaticClass after GetPrivateStaticClass */ \
void TClass::InitializePrivateStaticClass##TClass() \
{ \
InitializePrivateStaticClass( TClass::Super::StaticClass(), TClass::PrivateStaticClass, TClass::WithinClass::StaticClass() ); \
}

这里就是GetPrivateStaticClasssXXX和InitializePrivateStaticClassXXX的定义了。

GetPrivateStaticClasssXXX里面:以【EC_StaticConstructor模式】(一般静态链接都是这种,如使用dll动态链接则会走另一套流程)new了一个UClass出来,注意这里new前面的::,即全局默认new,并非上面被重载过的StaticAllocateObject,也就是说UClass是一类特殊的UObject,它的内存分配不需要走专用流程,最后其返回值会存在PrivateStaticClass静态变量,达到每个类一个UClass*实例的目的。

InitializePrivateStaticClassXXX里面:以父类的UClass、自己的UClass、外包类的UClass为参数,调用InitializePrivateStaticClass进行初始化,其内容也很简单:

void InitializePrivateStaticClass( class UClass* TClass_Super_StaticClass, class UClass* TClass_PrivateStaticClass, class UClass* TClass_WithinClass_StaticClass )
{
/* No recursive ::StaticClass calls allowed. Setup extras. */
if (TClass_Super_StaticClass != TClass_PrivateStaticClass)
{
TClass_PrivateStaticClass->SuperStruct = TClass_Super_StaticClass;
}
else
{
TClass_PrivateStaticClass->SuperStruct = NULL;
}
TClass_PrivateStaticClass->ClassWithin = TClass_WithinClass_StaticClass;
TClass_PrivateStaticClass->SetClass(UClass::StaticClass()); /* Perform UObject native registration. */
if( TClass_PrivateStaticClass->GetInitialized() && TClass_PrivateStaticClass->GetClass()==TClass_PrivateStaticClass->StaticClass() )
{
TClass_PrivateStaticClass->Register();
}
}

除了设置SuperStruct、ClassWithin等字段引用相应对象外,有趣的一句是setClass那里:它将自己设成了自己的Class。

前面说过“一切皆是对象”、“对象必有类型”,那么推出“类型也必是对象”,于是“类型对象也要有类型”,似乎要陷入死循环了,这里就用此种特殊的方式终止了循环:当一个对象的Class属性指向UClass::StaticClass()时,它自身必是一个UClass对象。

另外,注意InitializePrivateStaticClassXXX的实参:它本是在当前类的StaticClass中被调用,但又调用了另2个类的StaticClass()函数(加上UClass::StaticClass()就有3个),所以可能引发递归调用:

上例发生的原因是:首先调用根基类UObject::StaticClass(),在创建好它的UClass实例后调InitializePrivateStaticClass做初始化,其中又调用到UClass::StaticClass()(setClass那句),而UClass的WithinClass为UPackage,所以又进入了UPackage::StaticClass()……

直到各种基类初始化完成后,才逐步下降还原到当前层。

而顶层的UObject中,也声明了必要的边界条件以结束此递归:

class UObject
#if VTABLE_AT_END_OF_CLASS
: public UObjectBase
#endif
{
// Declarations.
DECLARE_BASE_CLASS(UObject,UObject,CLASS_Abstract|CLASS_NoExport,CASTCLASS_None,Core)
typedef UObject WithinClass;
……
}

在这里,TSuperClass和WithinClass都声明为自身,这样一旦PrivateStaticClass被赋值后,后续对StaticClass的调用就不会再转入InitializePrivateStaticClassXXX,而是直接返回已有的PrivateStaticClass了。

原理大致就如上,再从整体角度观察一下实际流程:

1、每个工程都有一个AutoInitializeRegistrantsXXX函数(XXX为工程名,如Core,Engine),它主要就是对本工程的所有UObject子类调用StaticClass()做初始化,这个函数是由编译解析脚本时自动生成的。(脚本相关内容后面再详说)

void AutoInitializeRegistrantsCore( INT& Lookup )
{
AUTO_INITIALIZE_REGISTRANTS_CORE
} #define AUTO_INITIALIZE_REGISTRANTS_CORE \
UObject::StaticClass(); \
GNativeLookupFuncs.Set(FName("Object"), GCoreUObjectNatives); \
UCommandlet::StaticClass(); \
UHelpCommandlet::StaticClass(); \
UComponent::StaticClass(); \
UDistributionFloat::StaticClass(); \
GNativeLookupFuncs.Set(FName("DistributionFloat"), GCoreUDistributionFloatNatives); \
UDistributionVector::StaticClass(); \
GNativeLookupFuncs.Set(FName("DistributionVector"), GCoreUDistributionVectorNatives); \
UExporter::StaticClass(); \

由于上面提到递归调用问题,各类初始的顺序不一定与代码所写一样,以AUTO_INITIALIZE_REGISTRANTS_CORE为例,它里面各类的实际顺序如下:(倒过来看)

第一个是UObject,第二个是UClass,可见其重要性。

2、这些AutoInitializeRegistrantsXXX函数在程序启动时被调用,它们为所包含的所有UObject子类调用StaticClass()函数

3、每个UObject子类的StaticClass()函数里会new出其对应的UClass*实例,存放在该类的PrivateStaticClass静态变量上。

以上就是各类及其UClass生成的过程了,至于UClass里面到底有什么,后续再分析。

unreal3对象管理模块分析的更多相关文章

  1. unreal3对象管理模块分析二

    上一篇主要记了UClass的创建,现在总结一下UObject的创建,可以从几个不同角度来理解. 从途径上看,可以根据UObject的构造函数来分个类: // Constructors. UObject ...

  2. [专业名词·硬件] 2、DC\DC、LDO电源稳压基本常识(包含基本原理、高效率模块设计、常见问题、基于nRF51822电源管理模块分析等)·长文

    综述先看这里 第一节的1.1简单介绍了DC/DC是什么: 第二节是关于DC/DC的常见的疑问答疑,非常实用: 第三节是针对nRF51822这款芯片电源管理部分的DC/DC.LDO.1.8的详细分析,对 ...

  3. slf4j+logback搭建超实用的日志管理模块

    文章转自http://www.2cto.com/kf/201702/536097.html slf4j+logback搭建超实用的日志管理模块(对日志有编号管理):日志功能在服务器端再常见不过了,我们 ...

  4. python模块分析之logging日志(四)

    前言 python的logging模块是用来设置日志的,是python的标准模块. 系列文章 python模块分析之random(一) python模块分析之hashlib加密(二) python模块 ...

  5. 如何使用 require.js ,实现js文件的异步加载,避免网页失去响应,管理模块之间的依赖性,便于代码的编写和维护。

    一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...

  6. spring - boot 监控管理模块搭建

    Spring-Actuator是Spring-boot对应用监控的集成模块,提供了我们对服务器进行监控的支持,使我们更直观的获取应用程序中加载的应用配置.环境变量.自动化配置报告等. 使用Spring ...

  7. 【转】python模块分析之collections(六)

    [转]python模块分析之collections(六) collections是Python内建的一个集合模块,提供了许多有用的集合类. 系列文章 python模块分析之random(一) pyth ...

  8. 【转】python模块分析之unittest测试(五)

    [转]python模块分析之unittest测试(五) 系列文章 python模块分析之random(一) python模块分析之hashlib加密(二) python模块分析之typing(三) p ...

  9. 【转】python模块分析之hashlib加密(二)

    [转]python模块分析之hashlib加密(二) hashlib模块是用来对字符串进行hash加密的模块,明文与密文是一一对应不变的关系:用于注册.登录时用户名.密码等加密使用.一.函数分析:1. ...

随机推荐

  1. [转]eclipse中的常用快捷键

    1.选中你要加注释的区域,用ctrl+shift+C 会加上//注释2.先把你要注释的东西选中,用shit+ctrl+/ 会加上注释3.要修改在eclispe中的命令的快捷键方式我们只需进入windo ...

  2. python3 mysql 多表查询

    python3 mysql 多表查询 一.准备表 创建二张表: company.employee company.department #建表 create table department( id ...

  3. HDU 之 City Game

                                                                                      City Game Time Lim ...

  4. Codeforces 461B Appleman and Tree:Tree dp

    题目链接:http://codeforces.com/problemset/problem/461/B 题意: 给你一棵树(编号从0到n-1,0为根节点),每个节点有黑白两种颜色,其中黑色节点有k+1 ...

  5. Ueditor基础使用

    感谢大家对我这个菜鸟的帮助,这是我第一次用.NET做网站.在这里向大家推荐个百度免费的文本编辑器Ueditor,是.NET版的,在http://ueditor.baidu.com/website/in ...

  6. BAT系列(一)— CNN

    1.CNN最成功的应用是在CV 那为什么NLP和Speech的很多问题也可以用CNN解出来?为什么AlphaGo里也用了CNN?这几个不相关的问题的相似性在哪里?CNN通过什么手段抓住了这个共性? 以 ...

  7. jQuery Cloud Zoom:图片放大镜插件 破解插件

    /* Cloud Zoom 10 Site License (CZ01-10). Version 3.1 rev 1312051822 */ (function(e) { function s(a) ...

  8. Saiku_学习_03_Saiku+Kylin构建多维分析OLAP平台

    一.技术选型 参见:Saiku+Kylin多维分析平台探索 1.saiku Saiku 作为分析平台,提供可视化的操作,能方便的对数据进行查询.分析,并提供图形化显示 2.kylin Kylin 作为 ...

  9. linux命令学习笔记(19):find 命令概览

    Linux下find命令在目录结构中搜索文件,并执行指定的操作.Linux下find命令提供了相当多的查找条件,功能 很强大.由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花 ...

  10. Java进阶07 嵌套类

    到现在为止,我们都是在Java文件中直接定义类.这样的类出现在包(package)的级别上.Java允许类的嵌套定义. 这里将讲解如何在一个类中嵌套定义另一个类. 嵌套 内部类 Java允许我们在类的 ...