如图:
假设有N个槽,时间轮已恒定速度顺时针转动,每转动一步槽指针就指向下一个槽,每转动一次的时间间隔叫做一个滴答间隔si,假设在T时间后到期,insertslot = (curslot + (T/si)) % N,计算出了insertslot就可以在O(1)的复杂度里完成。
//下面是简单的时间轮定时器代码
class tw_timer;
struct client_data
{
unsigned int uUin; //角色ID
unsigned int utype; //建筑类型
tw_timer* timer;
};
typedef void (*pFUNC)(client_data*);
class tw_timer
{
public:
//rot轮转几圈定时器到期
//ts 槽的索引
tw_timer( int rot, int ts ,pFUNC TimeOutCall)
: next( NULL ), prev( NULL ), rotation( rot ), time_slot( ts )
{
TimeOutfunc = TimeOutCall;
}
public:
//轮转几圈定时器到期
int rotation;
// 槽的索引
int time_slot;
//到时后的回调函数
//void (*cb_func)( client_data* );
pFUNC TimeOutfunc;
//自定义函数
client_data* user_data;
//链表的指针
tw_timer* next;
tw_timer* prev;
};
class time_wheel
{
public:
time_wheel() : cur_slot( 0 ))
{
//获得服务器时间
LastTickTime = GetCurTime();
for( int i = 0; i < N; ++i )
{
slots[i] = NULL;
}
}
~time_wheel()
{
for( int i = 0; i < N; ++i )
{
tw_timer* tmp = slots[i];
while( tmp )
{
slots[i] = tmp->next;
delete tmp;
tmp = slots[i];
}
}
}
tw_timer* add_timer( int timeout, pFUNC TimeOutCall)
{
if( timeout < 0 )
{
return NULL;
}
int ticks = 0;
//最少要一个滴答间隔
if( timeout < TI )
{
ticks = 1;
}
else
{
ticks = timeout / TI;
}
//rotation为0表示定时器到期
int rotation = ticks / N;
//计算槽索引
int ts = ( cur_slot + ( ticks % N ) ) % N;
tw_timer* timer = new tw_timer( rotation, ts ,TimeOutCall);
//当前的槽上没有定时器就放在head位置,否则放在插入在head位置
if( !slots[ts] )
{
printf( "add timer, rotation is %d, ts is %d, cur_slot is %d\n", rotation, ts, cur_slot );
slots[ts] = timer;
}
else
{
timer->next = slots[ts];
slots[ts]->prev = timer;
slots[ts] = timer;
}
return timer;
}
//删除一个定时器,主要是链表的删除的操作
void del_timer( tw_timer* timer )
{
if( !timer )
{
return;
}
int ts = timer->time_slot;
if( timer == slots[ts] )
{
slots[ts] = slots[ts]->next;
if( slots[ts] )
{
slots[ts]->prev = NULL;
}
delete timer;
}
else
{
timer->prev->next = timer->next;
if( timer->next )
{
timer->next->prev = timer->prev;
}
delete timer;
}
}
//每一个滴答间隔调用一次tick函数 time为当前服务器时间
void tick(unsigned int time)
{
//计算更新间隔进过了多少个滴答
unsigned int Ticount = (time - LastTickTime)/TI;
tw_timer* tmp = slots[cur_slot];
printf( "current slot is %d\n", cur_slot );
for(int i = 0;i < Ticount; ++i)
{
while( tmp )
{
printf( "tick the timer once\n" );
if( tmp->rotation > 0 )
{
tmp->rotation--;
tmp = tmp->next;
}
else
{
tmp->TimeOutfunc( tmp->user_data );
if( tmp == slots[cur_slot] )
{
printf( "delete header in cur_slot\n" );
slots[cur_slot] = tmp->next;
delete tmp;
if( slots[cur_slot] )
{
slots[cur_slot]->prev = NULL;
}
tmp = slots[cur_slot];
}
else
{
tmp->prev->next = tmp->next;
if( tmp->next )
{
tmp->next->prev = tmp->prev;
}
tw_timer* tmp2 = tmp->next;
delete tmp;
tmp = tmp2;
}
}
}
//移动到下一个槽,时间轮是环所以需要%N
cur_slot = ++cur_slot % N;
}
LastTickTime = time;
}
private:
//槽个数
static const int N = 60;
//滴答间隔(每移动一个槽的时间间隔)
static const int TI = 1;
//时间轮
tw_timer* slots[N];
//当前槽索引
int cur_slot;
//最后更新
unsigned int LastTickTime;
};
//假设在后台如何使用了
后台都会有一个主循环大概如下
bool update()
{
while(!stopserver)
{
//读网络IO
//读DB数据包
//处理事件
//处理定时器
timewhel.tick();
//处理逻辑
}
}
//就在住循环里驱动我们的定时器,在调用tick函数
比如我们现在有这么个个需求,就是玩家可以建造各式各样的建筑,比如房子,兵营,田地等,被建造的建筑会在一定时间后才能完成,并通知给前台,这样就需要一个定时器
。
//建造人口房屋
void BuilderHouse(client_data* clietdata)
{
//伪代码逻辑
/*
if (NULL == clietdata)
{
LOG("XXX");
return;
}
CRole* pRole = FindRole(clietdata->uUin);
if (NULL == pRole)
{
LOG("XXX");
return;
}
//调用角色建造人口接口,处理后台逻辑
pRole->BuilderHouse();
//通知给前台
Send(msg);
*/
}
//建造兵营
void BuilderCamp(client_data* clietdata)
{
//同上
}
//建造田地
void BuilderField(client_data* clietdata)
{
//同上
}
static time_wheel timewhel;
//假设玩家在游戏里场景里创建了一个房子,会执行下行代码
int CmdBuild()
{
//房子建造完成需要3分钟(180s) ,BuilderHouse为完成后的回调函数
timewhel.add_timer(180,BuilderHouse);
}