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

前端之家收集整理的这篇文章主要介绍了【深入了解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函数才是真正的主循环

@H_404_25@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,tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // 最后是大于0的
    DL_FOREACH_SAFE(_updatesPosList,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,tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // 移除所有标记删除的优先级等于0的update定时器元素
    DL_FOREACH_SAFE(_updates0List,tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // 移除所有标记删除的优先级大于0的update定时器元素
    DL_FOREACH_SAFE(_updatesPosList,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.f2er.com/cocos2dx/342770.html

猜你在找的Cocos2d-x相关文章