自动删除的定时器队列
(金庆的专栏)
网游服务器中使用定时器队列代替角色遍历可以提高性能,同时简代逻辑。
游戏主循环中不必遍历角色,只需触发定时器动作。
主循环代码如:
RootTimerQueue & rTimerQueue = Singleton<RootTimerQueue>();
do
{
UpdateTimeNow(); // update m_rNow
bool bTimerActed = rTimerQueue.TryToTickOne(m_rNow);
size_t nCount = HandleMessages();
if (0 == nCount)
{
if (m_bStopped) break;
if (bTimerActed) continue;
boost::this_thread::sleep(boost::posix_time::milliseconds(1));
}
} while (true);
定时器运行与主循环线程。
RootTimerQueue为根队列,单件,各功能的定时器为RootTimerQueue的子队列。
定时器队列需要一个自动删除功能。
如角色相关的一大堆定时器动作需要在角色退出时自动删除。
不然定时器动作在角色不存在时或重新登录后触发,会造成错误。
如果采用角色临时ID(会话ID), 每次登录都不相同,
可以避免错误,定时器动作就成为一个空动作。
但是该动作一直存在于队列中会占用内存,无法清除。
解决方案是每个角色拥有一个自己的定时器队列,该队列为根队列的子队列。
角色退出时,子队列的删除会自动删除所有的定时器动作。
class TimerQueue : boost::noncopyable
{
public:
typedef boost::function<void ()> Action;
typedef UInt64 TimerId;
public:
TimerId Insert(time_t tStart, const Action & act, unsigned int nIntervalSec = 0);
void Erase(TiemrId id);
public:
void SetParent(TimerQueue * p);
public:
bool TryToTickOne(time_t tNow);
};
时间精度为秒,因为现在的应用只需秒即可,可以稍加改动替换成高精度时间。
定时器动作分为2类:
1. 只做一次,nIntervalSec为0表示只做一次。
2. 重复做,nIntervalSec非0.
添加定时器只有一个开始时间(有需要可以加个结束时间)。
开始时间可以是登录前的一个时间点,这样就可以计算离线期间所发生的改变。
通过SetParent()将自己设为某个队列的子队列。
实现部分如下:
class TimerQueue : boost::nonecopyable
{
private:
void TickOne();
void SetNextTime();
void EraseParentTimer();
private:
typedef UInt64 TimerSeq;
private:
struct TimerItem
{
time_t t;
TimerSeq seq;
Action act;
unsigned int nIntervalSec;
};
private:
void InsertItem(const TimerItem & itm);
TimerItem Pop();
private:
struct TimerOrder {};
typedef boost::multi_index::multi_index_container
<
TimerItem,
boost::multi_index::indexed_by
<
boost::multi_index::ordered_unique<
boost::multi_index::tag<TimerOrder>,
boost::multi_index::composite_key<
TimerItem,
boost::multi_index::member<TimerItem, time_t, &TimerItem::t>,
boost::multi_index::member<IimerItem, TimerSeq, &TimerItem::seq>
> // composite_key
>, // ordered_unique
boost::multi_index::hashed_unique<
boost::multi_index::tag<TimerId>,
boost::multi_index::member<TimerItem, TimerSeq, &TimerItem::seq>
> // hashed_unique
> // indexed_by
> TimerItemSet;
typedef TimerItemSet::index<TimerOrder>::type QueueByTime;
typedef TimerItemSet::index<TimerId>::type QueueById;
private:
TimerItemSet m_set;
private:
TimerSeq m_seq;
time_t m_tNext;
TimerQueue * m_pParent;
TimerId m_idParentTimer; // Used to erase timer from parent
};
TimerItemSet以时间和序号排序,允许时间相同,但序号是唯一的。
并且也能用ID号索引,用于删除定时器。
TimerQueue.cpp
TimerQueue::TimerQueue()
: m_seq(0) // 0 is illegal
, m_pParent(NULL)
, m_idParentTimer(0)
{
SetNextTime();
}
TimerQueue::~TimerQueue()
{
EraseParentTimer();
}
TimerQueue::TimerId TimerQueue::Insert(
time_t tStart, const Action & act, unsigned int nIntervalSec)
{
TimerItem itm = {tStart, ++m_seq, act, nIntervalSec};
InsertItem(itm);
return itm.seq;
}
TimerQueue::Erase(TimerId id)
{
SetById & rSet = mset.get<TimerId>();
SetById::iterator itr = rSet.find(id);
if (itr == rSet.end())
return;
time_t t = (*itr).t;
TimerSeq seqErase = (*itr).seq; // Used to set next time.
TimerSeq seqFront = m_set.get<TimerOrder>().begin()->seq;
rSet.erase(itr);
if (seqErase == seqFront)
SetNextTime();
}
void TimerQueue::SetParent(TimerQueue * p)
{
EraseParentTimer()
m_pParent = p
SetNextTime();
}
bool TimerQueue::TryToTickOne(time_t tNow)
{
if (tNow < m_tNext)
return false;
if (m_set.empty())
return false;
TickOne();
return true;
}
void TimerQueue::TickOne()
{
BOOST_ASSERT(!m_set.empty());
TimerItem itm = Pop();
itm.act();
if (0 == itm.nIntervalSec)
return;
itm.t += itm.nIntervalSec;
InsertItem(itm);
}
TimerQueue::TimerItem TimerQueue::Pop()
{
BOOST_ASSERT(!m_set.empty());
QueueByTime & rQ = m_set.get<TimerOrder>();
QueueByTime::iterator itr = rQ.begin();
TimerItem itm = *itr;
rQ.erase(itr);
BOOST_ASSERT(itm.t == m_tNext);
SetNextTime();
return itm;
}
void TimerQueue::SetNextTime()
{
EraseParentTimer();
if (m_set.empty())
{
m_tNext = std::numeric_limits<time_t>::max();
return;
}
time_t tNew = (*m_set.get<TimeOrder>().begin()).t;
m_tNext = tNew;
if (NULL == m_pParent)
return;
m_idParentTimer = m_pParent->Insert(m_tNext,
boost::bind(&TimerQueue::TickOne, this));
BOOST_ASSERT(m_idParentTimer); // 0 is illegal ID.
}
void TimerQueue::EraseParentTimer()
{
if (m_pParent && m_idParentTimer)
m_pParent->Erase(m_idParentTimer);
m_idParentTimer = 0;
}
void TimerQueue::InsertItem(const TimerIten & itm)
{
m_set.insert(itm);
if (itm.t < m_tNext)
SetNextTiem();
}
RootTimerQueue是TimerQueue的子类。
另外再派生子类
class LogicTimerQueue : public TimerQueue
{
public:
LogicTimerQueue();
};
LogicTimerQueue::LogicTimerQueue()
{
SetParent(&Singleton<RootTimerQueue>());
}
这样就让每个LogicTimerQueue成为根队列的子队列。
玩家类中就可以添加LogicTimerQueue成员,来执行定时动作。
class Player
{
public:
Player()
{
...
// Check mail every hour.
m_timer->Insert(time(NULL),
boost::bind(&Player::CheckMail, this),
3600);
...
}
...
private:
LogicTimerQueue m_timer;
...
};