定时器的设计和实现
张家旺 2005-02-26
开始想学习DX编程时,一个朋友介绍了这个网站。平时,在里面也学习了不少内容,自己经常听同事说起定时器。而自己第一次使用时,所设计的一个定时器,是问题多多。经过一些总结和思考,设计了一个新的定时器,拿出来和大家共享。也免了总是拿来,而不付出。:}
定时器,是一个应该来说游戏最基本常用的功能。它的易用与否,直接影响了开发的效率;功能的完备与否,直接导致实现的难易;性能的好坏,直接影响到了游戏的运行表现。那么,如何实现一个简单易用、功能完善、性能较好的定时器呢?
首先,先看功能的完善。
定时器,那么,第一点,我们希望他能有最基本的定时功能:指定一段时间以后,发生某个事件。
然后,如果所有事件都是一定时间以后发生,而,我们经常可能由于某个操作,会取消前面事件或无效前面的事件。比如:定时炸弹,一段时间以后会爆炸,在爆炸以前,是可以拆毁的。那么,就需要能取消或无效某个定时。
好,这就是一个最基本的定时器,能够设定时间,能够取消设定。就有如下接口:
setTime(int time,… 可能还不明确,改为:int timeEvent(int time,Event &e)
cancelTime(int timeID) 统一名字: void cancelEvent(int e)
但是,往往,我们很多游戏希望能暂停,然后,从暂停点继续。
void pauseTime()
void resumeTime()
怎么暂停,就有个游戏时间、系统时间(时间的获取,一般都是以系统时间为基础)了。
unsigned playTime()
unsigned sysTime()
功能上就有了:定时、取消、暂停、继续、查询。作为一般的游戏开发已经相对比较充分了。
再看,简单易用。
我们在前面引入了一个事件的概念Event,这样一个类。如果按照前面的思路,那么,就需要在时间到了,发生时间。
Event::onOcured()
实际的使用中,发现自己处理自己发生了,不是一个好的方式,我们再通过一个中转:
EventHandler::occurred(Event &e)
使用时,每个事件都要有一个对象,而且是临时创建,还要销毁,并且要处理,必需继承EventHandler,使用相当不方便。需要最好不要继承,也不需要创建对象。
设计模式里有个proxy,可以借鉴一下。我们改为从proxy去取发生了的事件。
bool EventProxy::fetchEvent(Event &e)
void EventProxy::addOccured(Evene &e)
我们再使用时只需要fetch看有没有了,有、处理。进一步,取消也用proxy:
void EventProxy::cancelEvent(int e)
干脆,更方便,易于理解,我们把所有的借口都放这个proxy里算了。不过,不能叫EventProxy了。
int Timer:: timeEvent(int time,Event &e)
void Timer::cancelEvent(int e)
void Timer::pauseTime()
void Timer::resumeTime()
bool Timer::fetchEvent(Event &e)
这样,所有操作都只需要通过Timer了。不过,为了防止多份事件,需要采用singleton(单件)模式。同时,把时间的获取也加进来。
typedef unsigned int U32;
class TimeOffice
{
public:
/**
* 定时邮递
* @return 邮递定时器
* @para ms 间隔毫秒
* @para receiver 接收对象
* @para packet 邮递的包裹
* @para count 邮递次数(< 0 每隔ms毫秒邮递一次包裹)
*/
U32 timePost( U32 ms,const void *receiver,
const Packet &packet,int count = 1 );
/**
* 取消邮递(取消指定的定时器)
* @return 定时器存在否
* @para timer 邮递定时器
*/
bool cancelPost( U32 timer );
/**
* 取消邮递(取消发往指定接收对象的所有定时器)
* @return 被取消的邮递定时器个数
* @para receiver 接收者
*/
U32 cancelPost( const void *receiver );
/**
* 邮递到了的包裹数
* @return 已到达接收者但尚未取出的包裹的数目
* @para receiver 包裹接收者
*/
U32 arrivedPackets( const void *receiver );
/**
* 取邮递包裹
* @return 有可取的包裹否
* @para receiver 接收者
* @para packet 取出的包裹
*/
bool fetchPacket( const void *receiver,Packet &packet);
/**
* 丢弃接收者所有已邮递的包裹
* @return 丢弃的包裹的数目
* @para receiver 接收者
*/
U32 discardPackets( const void *receiver );
/**
* 生成时间戳
* @return 基于操作系统或游戏的时间戳
* @para system 是否采用依赖操作系统的时间戳
*/
static U32 stampTime( bool system = true );
/**
* 暂停游戏时间的流逝
* @return 无
*/
static void pause( void );
/**
* 启动被暂停的游戏时间的流逝
* @return 无
*/
static void resume( void );
/**
* 申请定时器实例
* @return 定时器实例
*/
static TimeOffice *getInstance( void );
/**
* 释放定时器
* @return 无
*/
static void release( void );
private:
TimeOffice( void );
~TimeOffice( void );
void step( void );
};
最后是性能。这个问题,是一个比较难以象前面泛泛而谈的了。
我们学过《数据结构》。知道,空间和运算性能通常是互斥的。既要空间利用少,又要运算性能高,通常是比较困难的。幸好,我们这里的fetch、time、discard等基本都是列表操作。由于cancel可能从中间删除事件,那么我们可以选择std::list,舍弃std::vector了。具体的实现就看大家怎么写了。
通篇,没有什么内容,只是一个思考的过程,和最后的设计结果。如果对此定时器有什么疑问或讨论,或对文章字句不解者,或对文章描述很烦者,都可建议修改,敬请在论坛发表帖子。个人,会抽时间去论讨和大家一起讨论。比较遗憾的是,自己机器上没有UML工具,因此,也没有相关“协作图”可供大家图形感觉(实在太过粗浅,这里不敢擅用“理解”一词)。
如果本文能对大家以后的游戏开发学习或游戏实际开发有所帮助。个人将深感高兴,并抽空再和大家一起讨论一般的windows游戏基本流程改进。恳请宝贵意见于论坛,个人已准备好cn_zhangJW用户,接受大家的臭鸡蛋了。:}