事件管理器 (EventMgr)
Ascent 的每个事件是一个TimedEvent 对象,其中包含了一个回调函数对象、间隔时间、回调次数和标志信息。
EventMgr 只是封装了添加事件的操作(并不负责运行事件的回调函数),真正执行事件回调函数的是EventableObjectHolder 的update() 函数。当使用EventMgr::AddEvent 的时候,需要指定目标对象(一个EventableObject 对象)、一个回调函数和1 ~4 个参数,类型、间隔时间、回调次数(0 不限)和标志。函数原型:
void AddEvent(Class *obj, void (Class::*method)(P1), P1 p1, uint32 type, uint32 time, uint32 repeats, uint32 flags)
事件会加入到EventableObject 对象的m_events (multimap )中,并会加入到EventableObjectHolder 对象中。在EventableObjectHolder::update() 函数中将会执行到时间的事件回调函数。
EventableObjectHolder 中,活动事件都在表m_events 中,但是对事件的添加删除各添加了一个缓冲链表(m_insertPool ,m_deletePool ),并对两个链表单独加锁。当再次回调update() 的时候才真正的添加删除事件。因为活动的事件比较多,这样就可以避免对m_events 的频繁锁竞争,或者其他线程添加事件被阻塞的情况以及线程死锁的情况。
关系如图1 的上半部分。附两段注释:
/**
* @class EventableObject
* EventableObject means that the class inheriting this is able to take
* events. This 'base' class will store and update these events upon
* receiving the call from the instance thread / WorldRunnable thread.
*/
/**
* @class EventableObjectHolder
* EventableObjectHolder will store eventable objects, and remove/add them when they change
* from one holder to another (changing maps / instances).
*
* EventableObjectHolder also updates all the timed events in all of its objects when its
* update function is called.
*
*/
AI与脚本管理器 ( ScriptMgr ):
与大多管理器一样, ScriptMgr 也是一个 singleton 类。主要负责 AI 和脚本的加载注册及管理。
脚本加载 关于脚本,都是以动态库的形式实现,放在配置的脚本目录中(默认为 script_bin )。 ScriptMgr 加载脚本时会遍历该目录下的所有 dll 文件,并显式加载。
脚本动态库需要有三个导出函数: _exp_get_version
typedef void (* exp_script_register )( ScriptMgr * mgr );
typedef uint32 (* exp_get_script_type )();
typedef uint32 (* exp_get_version )();
这三个导出函数将被 ScriptMgr 用来判断该脚本动态库的版本和类型(脚本引擎,脚本函数。),并注册该脚本动态库。
exp_script_register 需要提供ScriptMgr 的对象指针给脚本动态库,由脚本动态库调用ScriptMgr 的注册函数注册EntryId 和对应的创建函数。注册的创建函数以EntryId 为Key 分类放在hashmap 中。
脚本使用 图 1 中的下半部分列出了脚本类与 Ascent 类的关系,针对不同的类(需要用到脚本的类)有一个对应的脚本类基类(图中只列出了相关的 3 种,还有一些 script 类目前没有涉及到,但思想是一样的),该基类提供了各种虚函数接口,会在对应的地方调用。当需要一个脚本对象时,会使用在 ScriptMgr 中注册的创建函数来生成一个脚本对象。而该脚本对象一般就是从脚本类基类派生实现的(可参考脚本目录中的 moon 等项目,有 c++ 和 lua 的实现)。
以类 Creature 为例, Creature 属性中有 CreatureAIScript 的对象指针 _myScriptClass ,该指针指向一个 CreatureAIScript 对象或一个其的派生类对象。脚本就是通过从 CreatureAIScript 派生并实现其相应接口来实现逻辑扩展。当脚本动态库加载后,脚本动态库会向 ScriptMgr 注册一个 EntryID 和一个创建函数指针。该创建函数可以创建并返回一个脚本中实现的派生类对象,并返回一个基类对象指针。当 Creature 对象加入到游戏世界时,会调用 Creature :: LoadScript 加载脚本对象:
void Creature :: LoadScript ()
{
_myScriptClass = sScriptMgr . CreateAIScriptClassForEntry ( this );
}
其中调用了 ScriptMgr::CreateAIScriptClassForEntry 创建脚本对象:
CreatureAIScrip t * ScriptMgr :: CreateAIScriptClassForEntry ( Creature * pCreature )
{
//查找注册的函数指针
CreatureCreateMap::iterator itr = _creatures . find ( pCreature -> GetEntry ());
if ( itr == _creatures . end ())
return NULL ;
//调用函数指针创建对象
exp_create_creature_ai function_ptr = itr -> second ;
return ( function_ptr )( pCreature );
}
CreateAIScriptClassForEntry 中以EntryID 查找注册的创建函数(该函数的一般实现就是return new CLASSNAME; ),并调用创建函数返回一个派生的脚本类对象。该对象指针将赋给_myScriptClass ,在合适的时候通过宏 CALL_SCRIPT_EVENT 调用其虚函数接口。
AI 实现 : 基本的 AI 实现都在类 AIInterface 中,AIInterface 是一个基于事件的有限状态机,在HandleEvent 中完成状态转换,在Update 时刷新当前状态的行为动作。每个Unit 对象都会有一个AIInterface 的指针(如图1 ),在需要的时候通过GetAIInterface 获取指针并调用相应的函数接口。将AI 单独分离出来而作为成员指针,降低了状态机的实现复杂度,也使脚本对象一样可以派生扩展,从而也可以动态替换AI 。