定时器介绍
是时候放出灵魂框架图了,KBEngine最重要的几个类
是的,这个图就说明了KBEngine引擎最重要的几个类。从程序来看,最重要的就是App,因为我是从BaseApp为例子说明的,就画了BaseApp,其他App大同小异,原理类似。
其实最核心的类还是EventDispatcher。
EventDispatcher
EventDispatcher是引擎的核心的核心,可以说引擎能跑起来几乎是因为EventDispatcher在起作用。
void EventDispatcher::processUntilBreak()
{
if(breakProcessing_ != EVENT_DISPATCHER_STATUS_BREAK_PROCESSING)
breakProcessing_ = EVENT_DISPATCHER_STATUS_RUNNING;
while(breakProcessing_ != EVENT_DISPATCHER_STATUS_BREAK_PROCESSING)
{
this->processOnce(true);
}
}
这While循环就是引擎的主循环,可以看到循环是一帧一帧的跑起来的,我们再看每一帧都在干什么。
int EventDispatcher::processOnce(bool shouldIdle)
{
if(breakProcessing_ != EVENT_DISPATCHER_STATUS_BREAK_PROCESSING)
breakProcessing_ = EVENT_DISPATCHER_STATUS_RUNNING;
this->processTasks();
if(breakProcessing_ != EVENT_DISPATCHER_STATUS_BREAK_PROCESSING){
this->processTimers();
}
this->processStats();
if(breakProcessing_ != EVENT_DISPATCHER_STATUS_BREAK_PROCESSING){
return this->processNetwork(shouldIdle);
}
return 0;
}
最重要的一个函数,处理任务,处理定时器,处理network,中间有一个处理状态的,是分析辅助用的,可以说是不是核心功能,我们在奇技淫巧三里面讨论过Task,这篇文章说说Timer定时器。
Timer 时间 定时器
可以说和时间相关的处理逻辑在这地方,和Task,操作处理设计理念类似。在EventDispatcher里面有一个管理器管理着所有的定时器,游戏循环里面检查定时器触发条件,如果达到就处理相关的定时事件。
我们看下标哥是如何组织代码的:
看的时候要看清楚是Time还是Timer,概念上来说Timer是比Time高一个层次的,Timer涉及到的就是处理逻辑一类,Time就是时间,涉及到的就是如何达到触发条件。代码有继承和内嵌类,稍微有点复杂。
试着可以说说,TimersT是管理所有定时器的管理类,它里面有两个比较关键的成员变量:
container_
typedef std::vector<KBEngine::TimeBase *> Container;
Container container_;
这个变量是一个Time的vector,主要是不需要排序类的定时器,没有优先级。TimeBase就是主要的定时器设置。
timeQueue_
PriorityQueue timeQueue_;
可以看出来,这个也是容器,是一个优先队列的容器,里面存放的是一些需要关注优先级,也就是谁先触发之类逻辑的定时器。为了看的明显点,贴上代码:
TimerT
class TimersBase
{
public:
virtual void onCancel() = 0;
};
template<class TIME_STAMP>
class TimersT : public TimersBase
{
public:
typedef TIME_STAMP TimeStamp;
TimersT();
virtual ~TimersT();
inline uint32 size() const { return timeQueue_.size(); }
inline bool empty() const { return timeQueue_.empty(); }
int process(TimeStamp now);
bool legal( TimerHandle handle ) const;
TIME_STAMP nextExp( TimeStamp now ) const;
void clear( bool shouldCallCancel = true );
bool getTimerInfo( TimerHandle handle,
TimeStamp& time,
TimeStamp& interval,
void *& pUser ) const;
TimerHandle add(TimeStamp startTime, TimeStamp interval,
TimerHandler* pHandler, void * pUser);
private:
typedef std::vector<KBEngine::TimeBase *> Container;
Container container_;
void purgeCancelledTimes();
void onCancel();
class Time : public TimeBase
{
public:
Time( TimersBase & owner, TimeStamp startTime, TimeStamp interval,
TimerHandler * pHandler, void * pUser );
TIME_STAMP time() const { return time_; }
TIME_STAMP interval() const { return interval_; }
void triggerTimer();
private:
TimeStamp time_;
TimeStamp interval_;
Time( const Time & );
Time & operator=( const Time & );
};
class Comparator
{
public:
bool operator()(const Time* a, const Time* b)
{
return a->time() > b->time();
}
};
class PriorityQueue
{
public:
typedef std::vector<Time *> Container;
typedef typename Container::value_type value_type;
typedef typename Container::size_type size_type;
bool empty() const { return container_.empty(); }
size_type size() const { return container_.size(); }
const value_type & top() const { return container_.front(); }
void push( const value_type & x )
{
container_.push_back( x );
std::push_heap( container_.begin(), container_.end(),
Comparator() );
}
void pop()
{
std::pop_heap( container_.begin(), container_.end(), Comparator() );
container_.pop_back();
}
Time * unsafePopBack()
{
Time * pTime = container_.back();
container_.pop_back();
return pTime;
}
Container & container() { return container_; }
void make_heap()
{
std::make_heap( container_.begin(), container_.end(),
Comparator() );
}
private:
Container container_;
};
PriorityQueue timeQueue_;
Time * pProcessingNode_;
TimeStamp lastProcessTime_;
int numCancelled_;
TimersT( const TimersT & );
TimersT & operator=( const TimersT & );
};
typedef TimersT<uint32> Timers;
typedef TimersT<uint64> Timers64;
}
最后的Timers64 是EventDispatcher里面变量类型,TimersT就是主要管理着两个定时器容器。
我们可以看Time的实现:
Time
class TimersBase
{
public:
virtual void onCancel() = 0;
};
class Time : public TimeBase
{
public:
Time( TimersBase & owner, TimeStamp startTime, TimeStamp interval,
TimerHandler * pHandler, void * pUser );
TIME_STAMP time() const { return time_; }
TIME_STAMP interval() const { return interval_; }
void triggerTimer();
private:
TimeStamp time_;
TimeStamp interval_;
Time( const Time & );
Time & operator=( const Time & );
};
虽然Time是Timer里面声明的内嵌类,但是他的父类是在TimerT外边声明的,为了方便我放到了一起。
在Time里面放着TimerHandler,这个就是引擎使用的具体的业务类了。比如:
class NetworkInterface : public TimerHandler
{
public:
typedef std::map<Address, Channel *> ChannelMap;
不管在哪儿实现的TimerHandler,想办法把它偷偷加入到我们刚说的两个容器里面,就可以执行了。梳理一下
可以这样说
App里面有一个EventDispatcher,EventDispatcher里面有一个Timer管理器,管理器有一个Time容器【队列,container_变量没有用,貌似标哥没有想好】,Time里面存放着定时器处理器,定时器处理提TimerHandler就是需要自己实现的业务逻辑。
读这块代码的时候需要看清Timer和Time的区别,TimerHandle和TimerHandler的区别。
Timer和Time的区别就是Timer是管理器,Time是存放TimerHandler的容器。
TimerHandle和TimerHandler区别是TimeHandle是一个暂时未知的设计,TimerHandler
是一个具体的业务定时器处理逻辑。
我们可以看一下定时器处理遍历:
process
template <class TIME_STAMP>
int TimersT< TIME_STAMP >::process(TimeStamp now)
{
int numFired = 0;
while ((!timeQueue_.empty()) && (
timeQueue_.top()->time() <= now ||
timeQueue_.top()->isCancelled()))
{
Time * pTime = pProcessingNode_ = timeQueue_.top();
timeQueue_.pop();
if (!pTime->isCancelled())
{
++numFired;
pTime->triggerTimer();
}
if (!pTime->isCancelled())
{
timeQueue_.push( pTime );
}
else
{
delete pTime;
KBE_ASSERT( numCancelled_ > 0 );
--numCancelled_;
}
}
pProcessingNode_ = NULL;
lastProcessTime_ = now;
return numFired;
}
这个循环也是一个很有意思的循环,他是把队列里面的所有元素都操作一遍的循环。
从头开始,拿出头来,看触发没,触发则触发,没触发就增加时间。
循环结束条件是队列头时间不小于当前时间。就是遍历了一遍。写的也是很精巧,不过就是有一个问题,如果定时事件过多,比如上千个,因为数量级到了1000在一个循环里面会造成性能下降,有可能卡帧,服务器还好些吧,而且也很难达到这量级。暂时是一个可以优化的地方。
再看一下添加逻辑
add
template <class TIME_STAMP>
TimerHandle TimersT< TIME_STAMP >::add( TimeStamp startTime,
TimeStamp interval, TimerHandler * pHandler, void * pUser )
{
Time * pTime = new Time( *this, startTime, interval, pHandler, pUser );
timeQueue_.push( pTime );
return TimerHandle( pTime );
}
添加逻辑也是很诡异,返回一个TimerHandle,我不是很理解,难道这个东西是句柄的意思吗,将来准备某种地方使用?我看使用的地方很少有接住这个返回的变量的,是一个疑问点。
总结一下
EventDispatcher最重要的三个成员变量我们就说了两个了,Task和Timer,还剩下的一个是EventPoller管网络的。
Task和Timer设计理念类似,都是找时机添加对应的逻辑,坐等游戏循环调用。在以后调试或者阅读这方面的代码的时候,寻找相关的逻辑准没错。
真是读的越多理解的越多!