【深入了解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);
}

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



原文地址:https://www.cnblogs.com/mengfanrong/p/5245663.html