DOOM3 运行时型别判断与消息映射机制

Doom3的型别判断主要依靠idTypeInfo类提供信息,该类的主要成员有
   
class idTypeInfo {
public:
const char * classname; //当前类名
const char * superclass;    //父类名
idClass * ( *CreateInstance )( void );//指向类实例化函数的指针
void ( idClass::*Spawn )( void );//指向类Spawn(??生成)类成员函数指针
   void ( idClass::*Save )( idSaveGame *savefile ) const;//指向类持久化类成员函数指针
void ( idClass::*Restore )( idRestoreGame *savefile );//指向类恢复类成员函数指针


idEventFunc<idClass> * eventCallbacks;
eventCallback_t * eventMap; //消息映射表,分配在堆上的空间,整个引擎有多少消息就有多少个槽位
idTypeInfo * super; //指向父类型别信息指针
idTypeInfo * next; //指向下一个类型别信息指针


//当前类是否有自己的消息映射表(自己分配的eventMap),
//若有则该变量置为true这样在销毁该型别信息时会释放
bool freeEventMap;
int typeNum; //当前类类型在继承树中的索引
int lastChild; //当前类类型的最后一个后代在继承树中的索引


idHierarchy<idTypeInfo> node; //在继承图标里的节点

...
}

classname 类的类名,每个idClass的子类都有唯一类名
superclass 父类名称
CreateInstance 是指向子类实例化静态成员函数的指针,用于运行时动态实例化
Spwan 
Save   用于指向子类的保存静态成员函数。
Restore 用于指向子类的恢复静态成员函数。
eventCallbacks 指向idClass的eventCallbacks消息映射函数数组的指针。
eventMap 消息映射表,是分配在堆里的。该eventMap的大小是根据全局类的消息总数决定的这样
每idEventDef直接用其eventnum参数唯一对应于eventMap表中的一个槽位。若该类对该
消息有响应回调函数则该槽位不为空。
super     指向父类的idTypeInfo对象的指针。
next 指向字母表顺序的下一个类别idTypeInfo实例的指针。
freeEventMap 该布尔变量指明,eventMap消息映射表是否初始化。
typeNum     在继承图表中,前序遍历顺序的当前类类别索引。
lastChild   在继承图表中,前序遍历顺序下的该类最后一个后代的索引。
node 该idTypeInfo在继承图表里的节点对象。

为了实现每个类的运行时型别快速判断,需要在每个类的头文件声明处写上 
CLASS_PROTOTYPE(idClass的子类类名)
然后在类的cpp中写如下宏,并填入相应类名。
CLASS_DECLARATION( 父类类名, 当前类类名 )

CLASS_PROTOTYPE宏定义如下:
#define CLASS_PROTOTYPE( nameofclass )\
public: \
static idTypeInfo Type; \
static idClass *CreateInstance( void ); \
virtual idTypeInfo *GetType( void ) const; \
static idEventFunc<nameofclass> eventCallbacks[]

该宏一般在idClass的子类中声明,例如:
class idWeapon : public idAnimatedEntity 
{
public:
CLASS_PROTOTYPE( idWeapon );
...
};
该宏的作用是,为了在类中塞入运行时型别判断类实例Type,与eventCallbacks消息映射函数声明
数组,还有CreateInstance()该类的实例化函数,与GetType()用于获得该类的Type对象的地址。而在该
类的cpp文件中声明CLASS_DECLARATION则是为了实现这些由 CLASS_PROTOTYPE 声明的变量。以下是
CLASS_DECLARATION的展开:
#define CLASS_DECLARATION( nameofsuperclass, nameofclass )\
idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass,\
( idEventFunc<idClass> * )nameofclass::eventCallbacks, \
nameofclass::CreateInstance,\
(void( idClass::*)(void))&nameofclass::Spawn, \
(void( idClass::*)(idSaveGame*) const)&nameofclass::Save,\
(void( idClass::*)(idRestoreGame*))&nameofclass::Restore ); \
idClass *nameofclass::CreateInstance( void ) \
{\
try { \
nameofclass *ptr = new nameofclass; \
ptr->FindUninitializedMemory();\
return ptr; \
} \
catch( idAllocError & ) { \
return NULL;            \
} \
} \
idTypeInfo *nameofclass::GetType( void ) const \
{ \
return &( nameofclass::Type ); \
}   \
idEventFunc<nameofclass> nameofclass::eventCallbacks[] = {

该CLASS_DECLARATION宏的目的是构造idTypeInfo类型的变量Type。然后实现了CreateInstance()与GetType(),
最后是实现了nameofclass::eventCallbacks数组,该写法只写了消息数组的一半,而另一半需要用宏
#define END_CLASS{ NULL, NULL } }; 来填补。既应该如下书写
CLASS_DECLARATION( superclass , subclass )
EVENT( EV_GetName,idEntity::Event_GetName )
EVENT( EV_SetName,idEntity::Event_SetName )
...
END_CLASS

用户需要在CLASS_DECLARATION与 END_CLASS之间用EVENT宏声明消息及所对应的消息响应函数。

下面来分析下这些整个的消息系统与运行时型别判断系统是怎么初始化起来的。在为类声明CLASS_DECLARATION时就会
调用该类声明的型别对象idTypeInfo Type的构造函数。此构造函数会初始化当前类的idTypeInfo的类名、父类名称、当前类
的消息映射数组,Spawn、Save、Restore、CreateInstance函数指针,指定idTypeInfo的super指针使之关联到其父类的型别
对象。然后将该类的型别对象按类名大小插入到typelist中。

然后就等待系统调用idClass::Init()静态成员函数。Doom3引擎初始化时会在void idGameLocal::Init( void )中调用
该初始化函数。源码如下:
void idGameLocal::Init( void ) 
{
...
Clear();
idEvent::Init();
idClass::Init();
InitConsoleCommands();
...
}

这时idClass::Init()函数会遍历整个typelist既根据类名升序顺序排序的型别对象链表然后对每个idTypeInfo对象调用Init进行
初始化。代码如下:
// init the event callback tables for all the classes
for( c = typelist; c != NULL; c = c->next ) {
c->Init();
}
而idTypeInfo的Init的作用是,将该类型别对象关联到全局classHierarchy继承图表树中,然后更新该类的父类的在继承图表中后代数量
并记录在lastChild变量中。然后若当前类不存在任何自己独特的消息响应函数,则直接将自身的eventMap指向父类的eventMap,否则为该
型别对象分配一个总槽位为当前系统编译时定义的消息总量的消息函数回调表eventMap.然后继承父类的消息响应函数,若父类与当前类的
某些消息响应函数重复,则用当前类的消息响应函数覆盖父类的消息响应函数在子类自己的eventMap消息映射表中。
此时idClass::Init()中的型别对象已经基本初始化完成,idClass::Init紧接着会前序遍历整个classHierarchy然后将所有型别对象typeNum
更新为前序遍历顺序索引,将lastChild加上该typeNum(既该类子树的后代数加上当前类前序遍历索引数)然后得到该类的最后一个后代在
继承图表中的索引。该typeNum与lastChild非常有用,它可以快速判断出一个类到底是不是给定类的子类直接判断该类的型别对象的typeNum值
是不是在[typeNum , lastChild]的闭区间内,若是则说明是该类的子类。
然后idClass::Init()建立两个快速查找表,types与typenums其中types是按类名排序的查找表,而typenums是按类在前序遍历索引顺序
放置的查找表。然后设置initialized为true已防止多次调用idClass::Init()。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值