【深入瞭解cocos2d-x 3.x】定時器(scheduler)的使用和原理探究(3)

上篇文章分析到了定時器的定義,這篇的重點就是定時器是如何運行起來的。

1.從main中尋找定時器的回調

講定時器的運行,就不得不觸及到cocos2dx的main函數了,因爲定時器是主線程上運行的,並不是單獨線程的,所以它的調用必然會在main函數中,每幀調用。

以下代碼就是win32平臺下的main函數

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // create the application instance
    AppDelegate app;
    return Application::getInstance()->run();
}

直接調用了run函數,直接進入到run中

int Application::run()
{
    while(!glview->windowShouldClose())
    {
        QueryPerformanceCounter(&nNow);
        if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
        {
            nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);
            
            director->mainLoop();		//看這裏
            glview->pollEvents();
        }
        else
        {
            Sleep(1);
        }
    }
	return 0;
}

run函數中其他不相關的調用我已經去掉了,可以看到mainLoop函數纔是真正的主循環

void DisplayLinkDirector::mainLoop()
{
    //做其他不相關的事情
	if (! _invalid)
    {
        drawScene();
    }
}
看到這裏實際上也是調用drawScene函數

void Director::drawScene()
{
    if (! _paused)
    {
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }
	//之後才進行繪製
}
drawScene要做的事情很多,我將繪製部分都去掉了,值得注意的是 繪製場景會在定時器之後才執行。

這裏可以看到,實際上執行的就是定時器的update函數,那麼這個update函數中究竟執行了什麼東西呢?

2.定時器的update函數

首先來看看Update的代碼

// main loop
void Scheduler::update(float dt)
{
    _updateHashLocked = true;

    if (_timeScale != 1.0f)
    {
        dt *= _timeScale;
    }

    //
    // 定時器回調
    //

    tListEntry *entry, *tmp;

    // Update定時器中優先級小於0的隊列先執行
    DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // 接下來是優先級等於0的
    DL_FOREACH_SAFE(_updates0List, entry, tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // 最後是大於0的
    DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // 這裏循環的是自定義定時器
    for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
    {
        _currentTarget = elt;
        _currentTargetSalvaged = false;

        if (! _currentTarget->paused)
        {
            // 遍歷當前對象附屬的所有定時器
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
                elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);
                elt->currentTimerSalvaged = false;

				//事實上在這裏執行真正的回調
                elt->currentTimer->update(dt);

                if (elt->currentTimerSalvaged)
                {
                    // 當定時器結束任務了,就應該釋放掉
                    elt->currentTimer->release();
                }

                elt->currentTimer = nullptr;
            }
        }

        // 指向鏈表的下一對象
        elt = (tHashTimerEntry *)elt->hh.next;

        // 當對象的所有定時器已經執行完成,並且對象附屬的定時器爲空,則將對象從哈希鏈表中移除
        if (_currentTargetSalvaged && _currentTarget->timers->num == 0)
        {
            removeHashElement(_currentTarget);
        }
    }

    // 移除所有標記爲刪除的優先級小於0的update定時器元素
    DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // 移除所有標記爲刪除的優先級等於0的update定時器元素
    DL_FOREACH_SAFE(_updates0List, entry, tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // 移除所有標記爲刪除的優先級大於0的update定時器元素
    DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    _updateHashLocked = false;
    _currentTarget = nullptr;
}


有三個部分值得注意的:

  1. update定時器優先調用
  2. update定時器中優先級越低的越優先調用
  3. 自定義定時器的回調在elt->currentTimer->update(dt);中執行
關於第三點,我們繼續分析這個update函數

void Timer::update(float dt)
{
	// 初次執行 會進入到這個if中初始化
    if (_elapsed == -1)
    {
        _elapsed = 0;			//已執行時間
        _timesExecuted = 0;		//初始化重複次數
    }
    else
    {
        if (_runForever && !_useDelay)
        {//循環延時函數
            _elapsed += dt;
            if (_elapsed >= _interval)
            {
                trigger();		//真正的回調

                _elapsed = 0;
            }
        }    
        else
        {//advanced usage
            _elapsed += dt;
            if (_useDelay)		//延時
            {
                if( _elapsed >= _delay )
                {
                    trigger();		//真正的回調
                    
                    _elapsed = _elapsed - _delay;
                    _timesExecuted += 1;
                    _useDelay = false;
                }
            }
            else				//每幀調用
            {
                if (_elapsed >= _interval)
                {
                    trigger();		//真正的回調
                    
                    _elapsed = 0;
                    _timesExecuted += 1;

                }
            }

			//回調完成,執行取消函數
            if (!_runForever && _timesExecuted > _repeat)
            {    //unschedule timer
                cancel();
            }
        }
    }
}

這一大段代碼的邏輯非常清晰,延時函數主要分爲,永遠循環的延時函數,有限循環的延遲函數;而有限循環的延遲函數裏面根據優化的不同可以分爲每幀調用的和固定時間調用的。上述代碼就是根據這個分類進行優化的。

實際上核心的函數在

trigger();
cancel();
第一個函數是真正的回調執行的函數,第二個函數是去掉執行的函數

void TimerTargetSelector::trigger()
{
    if (_target && _selector)
    {
        (_target->*_selector)(_elapsed);
    }
}

void TimerTargetSelector::cancel()
{
    _scheduler->unschedule(_selector, _target);
}

以上就是定時器的實現原理分析的全過程,定時器的實現在文章中我感覺還是說的不是很清楚。真正去代碼中自己走一遍應該會更加明瞭,對以後的應用也會更得心應手。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章