游戏Tick时间轮算法实现

这里写自定义目录标题

时间轮算法简述

在这里插入图片描述
在这里插入图片描述

基本结构

Hash表嵌套队列,管理不同Tick时间调度的程序出队列,实现不同间隔Tick的程序抢占,从而实现游戏中定时检测等功能的实现

  • Hash表嵌套队列 :hash表的key值表示时间轮片的位置,队列表示同一Tick位置的线程排队情况;
  • HashCode 的计算1- 从当前的Tick位置开始 加上 线程设置的定时Tick 2- 将得到的Tick数,与时间轮的长度进行求余数操作,得到时间轮上的Tick位置 3- 将该线程插入到该Tick位置的线程队列
  • 时间轮的工作流程
    在这里插入图片描述

1- 从进程开始的时候,设置起始位置Tick(初始化的时候,一般都是从Tick==0开始计算

//第一个tick时初始化m_tvLastTick
    if (0 == m_tvLastTick.tv_sec && 0 == m_tvLastTick.tv_usec)
    {
        //最后一次系统tick时间
        m_tvLastTick = tvNow;
    }

    //当前的时间和上一次tick的时间,获取当前所在的时间轮位置
    uint32_t ticks = GetTicks(tvNow, m_tvLastTick); //从系统执行程序开始初始化Tick

2- 添加执行任务的线程到时间轮队列中, HashCode的计算,主要是将MS转化为时间轮的Tick数

  • 按照线程的需要,多少毫秒Tick一次线程,在时间轮的队列中具体的任务线程poTimer中设置过期时间:ExpiredTime = 当前Tick数+(任务线程的Tick时间转化的Tick数)
  • MsToTick: (uiMs - 1) / TICK_INTERVAL + 1; 时间轮转动的速度,即:设计的每个Tick之间的时间间隔TICK_INTERVAL
  • 求余数 和 按位与&的操作
    • 按位与 :通常用来取某个数的低N位的数值,当 x = 2^n时, a % x == a & (x -1),所以设计上,TICK_INTERVAL设计为2的幂次

3- 进程执行过程中,
1-根据当前的时间戳,计算当前处于时间轮片的那个Tick位置
2-获取当前Tick位置的队列,遍历队列中** ExpireTime** 到期的线程,按照FIFO的原则出队列
3-如果该任务需要继续Tick执行,则作为新的计算对象,从当前的进程Tick开始,重新计算HashCode,重新加入到队列中

相关代码

//基类:Hash表和队列的工作流程
/*
1-哈希表嵌套队列
typedef std::list<CBaseTimer*> TimerList;
TimerList      m_aoTimerList[TIMER_LIST_NR];
2-将Tick程序添加在队列中:CBaseTimerMgr::AddTimer
*/
//时间单位转化为微秒
static inline int TimevalInterval(const struct timeval& tv1, const struct timeval& tv2)
{
    //秒tv_sec转化为微秒 毫秒tv_usec转为微秒
    return (tv1.tv_sec - tv2.tv_sec) * MILLISEC_PER_SEC 
                    + (tv1.tv_usec - tv2.tv_usec) / MICROSEC_PER_MS;
}

//微秒转化为以tick为单位的数值
static inline uint32_t MsToTick(uint32_t uiMs)
{
    //每个tick的时间长度TICK_INTERVAL
    return (uiMs - 1)  / TICK_INTERVAL + 1;
}

//获取两个时间之间的tick数
static inline uint32_t GetTicks(const struct timeval& tv1, const struct timeval& tv2)
{
    int uiMs = TimevalInterval(tv1, tv2);

    if (uiMs <= 0)
    {
        return 0;
    }

    return MsToTick(uiMs);
}

static inline uint32_t HashCode(uint32_t uiKey)
{
    return uiKey & TIMER_LIST_MASK;
}

int CBaseTimerMgr::UpdateLastTick(uint32_t uiTicks)
{
    if (0 == uiTicks)
    {
        return 0;
    }

    struct timeval tv;
    tv.tv_sec  = uiTicks * TICK_INTERVAL / MILLISEC_PER_SEC;
    tv.tv_usec = (uiTicks * TICK_INTERVAL - tv.tv_sec * MILLISEC_PER_SEC) 
                            * MICROSEC_PER_MS;

    timeradd(&m_tvLastTick, &tv, &m_tvLastTick);
    return 0;
}

int CBaseTimerMgr::AddTimer(CBaseTimer *poTimer)
{
    CHECK_IF_NULL_RET(poTimer, -1);
    uint32_t uiTicks  = MsToTick(poTimer->GetIntervalTime());    //pass

    if (0 == uiTicks)
    {
        LOG_ERR("add timer [%s] failed, tick=0", poTimer->GetName().c_str());
        return -1;
    }

    uint32_t uiExpire = m_uiTickCount + uiTicks;
    poTimer->SetExpireTick(uiExpire);
    uint32_t uHashCode = HashCode(uiExpire);

    m_aoTimerList[uHashCode].push_back(poTimer);
    return 0;
}

int CBaseTimerMgr::RunTimer(uint32_t uiTick)
{
    TimerList& list = m_aoTimerList[HashCode(uiTick)];
    TimerList::iterator iter = list.begin();

    while(iter != list.end())
    {
        CBaseTimer *timer = *iter;
        if (uiTick != timer->GetExpireTick())    //pass
        {
            ++iter;
            continue;
        }

        iter = list.erase(iter);

        STAT_FUNC_TIME_TICK(timer->m_szTimerName.c_str());

        timer->OnTick();

        if (timer->GetCycleFlag())
        {
            AddTimer(timer);
            continue;
        }
    }

    return 0;
}

int CBaseTimerMgr::TimerTrigger ()
{
    struct timeval tvNow;
    gettimeofday(&tvNow, NULL);

    //第一个tick时初始化m_tvLastTick
    if (0 == m_tvLastTick.tv_sec && 0 == m_tvLastTick.tv_usec)
    {
        //最后一次系统tick时间
        m_tvLastTick = tvNow;
    }

    //当前的时间和上一次tick的时间,获取当前所在的时间轮位置
    uint32_t ticks = GetTicks(tvNow, m_tvLastTick);

    uint32_t uiLastTick = m_uiTickCount;
    m_uiTickCount += ticks;
    for( uint32_t i = 1; i <= ticks; ++i)
    {
        //出队列操作
        RunTimer(uiLastTick + i);
    }
    
    UpdateLastTick(ticks);

    // if (NULL != m_pBaseMonitor)
    // {
    //     m_pBaseMonitor->TickLog();
    // }

    
    return 0;
}

int CBaseTimerMgr::DelTimer(const CBaseTimer *poTimer)
{
    int iCount = 0;

    for (int i = 0; i < TIMER_LIST_NR; ++i)
    {
        TimerList& list = m_aoTimerList[i];
        TimerList::iterator iter = list.begin();
        while(iter != list.end())
        {
            if (poTimer == *iter)
            {
                iter = list.erase(iter);
                iCount++;
            }
            else
            {
                ++iter;
            }
        }
    }

    return iCount;
}




//声明自定义的任务线程


项目中声明注册定时器
/**
 * 注册TICK的接口
 * _tick    tickname
 * _class   _obj的类名
 * _obj     对象
 * _func    tick触发函数
 * _name    tick name 用于统计
 * _interval    超时时长单位毫秒
 */ 
#define DECLARE_TICK(_tick, _class, _obj, _func, _name, _interval) \
    static CAppTick<_class> *_tick = NULL; \
    if (NULL == _tick) \
    { \
        _tick = new CAppTick<_class>; \
        _tick->SetApp(_obj); \
        _tick->SetFunc(_func); \
        _tick->SetName(_name); \
        _tick->SetCycleFlag(true); \
        _tick->SetIntervalTime(_interval); \
        CBaseTimerMgr::Instance().AddTimer(_tick); \
    }

模板注册定时器执行函数
template<class _CApp>
class CAppTick : public CBaseTimer
{
public:
    //声明了函数指针;(_CApp::*_Func)() 无参数类型
    typedef int (_CApp::*_Func)();

    CAppTick() :
        m_pApp(NULL),
        m_pFunc(NULL)
    {
    }

    /**
     * @brief   构造函数
     * @param   app         对应的App
     * @param   szName      定时器的名字
     */
    CAppTick(_CApp *pApp, _Func pFunc, const char *szName, uint32_t uiInterval):
        CBaseTimer(szName, uiInterval, true),
        m_pApp(pApp),
        m_pFunc(pFunc)
    {
    }

    void SetApp(_CApp *pApp)
    {
        m_pApp = pApp;
    }

    void SetFunc(_Func pFunc)
    {
        m_pFunc = pFunc;
    }

    /**
     * @brief   超时处理函数
     * @return  0 if success
     */

    //执行的是对应线程中的tick函数
    virtual int OnTick()
    {
        if (NULL != m_pApp && NULL != m_pFunc)
        {
            return (m_pApp->*m_pFunc)();
        }

        return 0;
    }

private:
    _CApp *m_pApp;
    _Func m_pFunc;
};
#endif



/
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值