zoukankan      html  css  js  c++  java
  • Cocos2d-x 3.2:定时器的使用和原理探究(3)

    Cocos2d-x 3.2:定时器的使用和原理探究(3)

    本文转载至【深入了解cocos2d-x 3.x】定时器(scheduler)的使用和原理探究(3)

    上篇文章分析到了定时器的定义,这篇的重点就是定时器是如何运行起来的。

    1.从main中寻找定时器的回调

    讲定时器的运行,就不得不触及到cocos2dx的main函数了,因为定时器是主线程上运行的,并不是单独线程的,所以它的调用必然会在main函数中,每帧调用。

    以下代码就是win32平台下的main函数

    1. int APIENTRY _tWinMain(HINSTANCE hInstance,  
    2.                        HINSTANCE hPrevInstance,  
    3.                        LPTSTR    lpCmdLine,  
    4.                        int       nCmdShow)  
    5. {  
    6.     UNREFERENCED_PARAMETER(hPrevInstance);  
    7.     UNREFERENCED_PARAMETER(lpCmdLine);  
    8.   
    9.     // create the application instance  
    10.     AppDelegate app;  
    11.     return Application::getInstance()->run();  
    12. }  


    直接调用了run函数,直接进入到run中

    1. int Application::run()  
    2. {  
    3.     while(!glview->windowShouldClose())  
    4.     {  
    5.         QueryPerformanceCounter(&nNow);  
    6.         if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)  
    7.         {  
    8.             nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);  
    9.               
    10.             director->mainLoop();        //看这里  
    11.             glview->pollEvents();  
    12.         }  
    13.         else  
    14.         {  
    15.             Sleep(1);  
    16.         }  
    17.     }  
    18.     return 0;  
    19. }  

    run函数中其他不相关的调用我已经去掉了,可以看到mainLoop函数才是真正的主循环

    1. void DisplayLinkDirector::mainLoop()  
    2. {  
    3.     //做其他不相关的事情  
    4.     if (! _invalid)  
    5.     {  
    6.         drawScene();  
    7.     }  
    8. }  

    看到这里实际上也是调用drawScene函数

    1. void Director::drawScene()  
    2. {  
    3.     if (! _paused)  
    4.     {  
    5.         _scheduler->update(_deltaTime);  
    6.         _eventDispatcher->dispatchEvent(_eventAfterUpdate);  
    7.     }  
    8.     //之后才进行绘制  
    9. }  

    drawScene要做的事情很多,我将绘制部分都去掉了,值得注意的是 绘制场景会在定时器之后才执行。

    这里可以看到,实际上执行的就是定时器的update函数,那么这个update函数中究竟执行了什么东西呢?

    2.定时器的update函数

    首先来看看Update的代码

    1. // main loop  
    2. void Scheduler::update(float dt)  
    3. {  
    4.     _updateHashLocked = true;  
    5.   
    6.     if (_timeScale != 1.0f)  
    7.     {  
    8.         dt *= _timeScale;  
    9.     }  
    10.   
    11.     //  
    12.     // 定时器回调  
    13.     //  
    14.   
    15.     tListEntry *entry, *tmp;  
    16.   
    17.     // Update定时器中优先级小于0的队列先执行  
    18.     DL_FOREACH_SAFE(_updatesNegList, entry, tmp)  
    19.     {  
    20.         if ((! entry->paused) && (! entry->markedForDeletion))  
    21.         {  
    22.             entry->callback(dt);  
    23.         }  
    24.     }  
    25.   
    26.     // 接下来是优先级等于0的  
    27.     DL_FOREACH_SAFE(_updates0List, entry, tmp)  
    28.     {  
    29.         if ((! entry->paused) && (! entry->markedForDeletion))  
    30.         {  
    31.             entry->callback(dt);  
    32.         }  
    33.     }  
    34.   
    35.     // 最后是大于0的  
    36.     DL_FOREACH_SAFE(_updatesPosList, entry, tmp)  
    37.     {  
    38.         if ((! entry->paused) && (! entry->markedForDeletion))  
    39.         {  
    40.             entry->callback(dt);  
    41.         }  
    42.     }  
    43.   
    44.     // 这里循环的是自定义定时器  
    45.     for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )  
    46.     {  
    47.         _currentTarget = elt;  
    48.         _currentTargetSalvaged = false;  
    49.   
    50.         if (! _currentTarget->paused)  
    51.         {  
    52.             // 遍历当前对象附属的所有定时器  
    53.             for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))  
    54.             {  
    55.                 elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);  
    56.                 elt->currentTimerSalvaged = false;  
    57.   
    58.                 //事实上在这里执行真正的回调  
    59.                 elt->currentTimer->update(dt);  
    60.   
    61.                 if (elt->currentTimerSalvaged)  
    62.                 {  
    63.                     // 当定时器结束任务了,就应该释放掉  
    64.                     elt->currentTimer->release();  
    65.                 }  
    66.   
    67.                 elt->currentTimer = nullptr;  
    68.             }  
    69.         }  
    70.   
    71.         // 指向链表的下一对象  
    72.         elt = (tHashTimerEntry *)elt->hh.next;  
    73.   
    74.         // 当对象的所有定时器已经执行完成,并且对象附属的定时器为空,则将对象从哈希链表中移除  
    75.         if (_currentTargetSalvaged && _currentTarget->timers->num == 0)  
    76.         {  
    77.             removeHashElement(_currentTarget);  
    78.         }  
    79.     }  
    80.   
    81.     // 移除所有标记为删除的优先级小于0的update定时器元素  
    82.     DL_FOREACH_SAFE(_updatesNegList, entry, tmp)  
    83.     {  
    84.         if (entry->markedForDeletion)  
    85.         {  
    86.             this->removeUpdateFromHash(entry);  
    87.         }  
    88.     }  
    89.   
    90.     // 移除所有标记为删除的优先级等于0的update定时器元素  
    91.     DL_FOREACH_SAFE(_updates0List, entry, tmp)  
    92.     {  
    93.         if (entry->markedForDeletion)  
    94.         {  
    95.             this->removeUpdateFromHash(entry);  
    96.         }  
    97.     }  
    98.   
    99.     // 移除所有标记为删除的优先级大于0的update定时器元素  
    100.     DL_FOREACH_SAFE(_updatesPosList, entry, tmp)  
    101.     {  
    102.         if (entry->markedForDeletion)  
    103.         {  
    104.             this->removeUpdateFromHash(entry);  
    105.         }  
    106.     }  
    107.   
    108.     _updateHashLocked = false;  
    109.     _currentTarget = nullptr;  
    110. }  



    有三个部分值得注意的:

    1. update定时器优先调用
    2. update定时器中优先级越低的越优先调用
    3. 自定义定时器的回调在elt->currentTimer->update(dt);中执行
    关于第三点,我们继续分析这个update函数

    1. void Timer::update(float dt)  
    2. {  
    3.     // 初次执行 会进入到这个if中初始化  
    4.     if (_elapsed == -1)  
    5.     {  
    6.         _elapsed = 0;           //已执行时间  
    7.         _timesExecuted = 0;     //初始化重复次数  
    8.     }  
    9.     else  
    10.     {  
    11.         if (_runForever && !_useDelay)  
    12.         {//循环延时函数  
    13.             _elapsed += dt;  
    14.             if (_elapsed >= _interval)  
    15.             {  
    16.                 trigger();      //真正的回调  
    17.   
    18.                 _elapsed = 0;  
    19.             }  
    20.         }      
    21.         else  
    22.         {//advanced usage  
    23.             _elapsed += dt;  
    24.             if (_useDelay)      //延时  
    25.             {  
    26.                 if( _elapsed >= _delay )  
    27.                 {  
    28.                     trigger();      //真正的回调  
    29.                       
    30.                     _elapsed = _elapsed - _delay;  
    31.                     _timesExecuted += 1;  
    32.                     _useDelay = false;  
    33.                 }  
    34.             }  
    35.             else                //每帧调用  
    36.             {  
    37.                 if (_elapsed >= _interval)  
    38.                 {  
    39.                     trigger();      //真正的回调  
    40.                       
    41.                     _elapsed = 0;  
    42.                     _timesExecuted += 1;  
    43.   
    44.                 }  
    45.             }  
    46.   
    47.             //回调完成,执行取消函数  
    48.             if (!_runForever && _timesExecuted > _repeat)  
    49.             {    //unschedule timer  
    50.                 cancel();  
    51.             }  
    52.         }  
    53.     }  
    54. }  


    这一大段代码的逻辑非常清晰,延时函数主要分为,永远循环的延时函数,有限循环的延迟函数;而有限循环的延迟函数里面根据优化的不同可以分为每帧调用的和固定时间调用的。上述代码就是根据这个分类进行优化的。

    实际上核心的函数在

    1. trigger();  
    2. cancel();  

    第一个函数是真正的回调执行的函数,第二个函数是去掉执行的函数

    1. void TimerTargetSelector::trigger()  
    2. {  
    3.     if (_target && _selector)  
    4.     {  
    5.         (_target->*_selector)(_elapsed);  
    6.     }  
    7. }  
    8.   
    9. void TimerTargetSelector::cancel()  
    10. {  
    11.     _scheduler->unschedule(_selector, _target);  
    12. }  

    以上就是定时器的实现原理分析的全过程,定时器的实现在文章中我感觉还是说的不是很清楚。真正去代码中自己走一遍应该会更加明了,对以后的应用也会更得心应手。

  • 相关阅读:
    MySQL用户管理
    linux下杀死进程(kill)的N种方法
    Windows查看某个端口被谁占用
    SQL语句
    CentOS6.5 安装mysql-5.7.9
    Linux服务器安全之用户密钥认证登录
    Linux添加/删除用户和用户组
    linux命令killall 、kill 、pkill 命令详解
    linux下cat命令详解
    linux之sed用法
  • 原文地址:https://www.cnblogs.com/dudu580231/p/4560365.html
Copyright © 2011-2022 走看看