cocos2d-x调度器原理

程序运行后每达到一帧的时间间隔就会执行一次mainLoop

void CCDisplayLinkDirector::mainLoop(void)
{
//判断是否需要释放CCDirector,通常游戏结束才会执行这个步骤
    if (m_bPurgeDirecotorInNextLoop)
    {
        m_bPurgeDirecotorInNextLoop = false;
        purgeDirector();
    }
    else if (! m_bInvalid)
     {
 //绘制当前场景并执行其他必要的处理
         drawScene();

 //弹出自动回收池,使这一帧被放入回收池的对象全部执行release
           CCPoolManager::sharedPoolManager()->pop();
     }
}

那么程序的关键步奏就在这里在drawScene里面了

void CCDirector::drawScene(void)

{

    // 计算全局帧间时间差
    calculateDeltaTime();

    //1. 引发定时器事件
    if (! m_bPaused)
    {
        m_pScheduler->update(m_fDeltaTime);
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //2. 是否切换场景
    if (m_pNextScene)
    {
        setNextScene();
    }

    kmGLPushMatrix();

    // 3. 绘制当前场景
    if (m_pRunningScene)
    {
        m_pRunningScene->visit();
    }

    // draw the notifications node处理通知节点
    if (m_pNotificationNode)
    {
        m_pNotificationNode->visit();
    }

    if (m_bDisplayStats)
    {
        showStats();
    }

    kmGLPopMatrix();

    m_uTotalFrames++;

    // swap buffers
    if (m_pobOpenGLView)
    {
        m_pobOpenGLView->swapBuffers();
    }

    if (m_bDisplayStats)
    {
        calculateMPF();
    }
}

那么可以看出,在游戏的每一帧,都会调用CCScheduler的update来调度定时器;然后遍历渲染树,对游戏进行绘制。

调度器CCScheduler

在游戏中要显示的元素都继承于CCNode类,当继承于CCNode的节点调用schedule()添加一个定时器时,CCNode通过导演->getScheduler()获得定时器CCScheduler对象,然后将定时器交给该CCScheduler对象管理。

再来看CCScheduler内,定时器主要分为Update定时器 和 普通interval定时器。如下CCScheduler 中的主要存储变量。(为提高调度器效率,使用了链表 和 散列表保存定时器信息。)

//Update定时器

    struct _listEntry *m_pUpdatesNegList;        // list of priority < 0
    struct _listEntry *m_pUpdates0List;            // list priority == 0
    struct _listEntry *m_pUpdatesPosList;        // list priority > 0
    struct _hashUpdateEntry *m_pHashForUpdates; // hash used to fetch quickly the list entries for pause,delete,etc
 // 普通interval定时器
    struct _hashSelectorEntry *m_pHashForTimers;

在主循环的drawScene函数中调用了CCScheduler::update,下面来分析这个函数:

void CCScheduler::update(float dt)
{
    m_bUpdateHashLocked = true; //$

//1. 时间差*缩放系数 一改变游戏全局速度,可通过CCScheduler的TimeScale属性设置
    if (m_fTimeScale != 1.0f)
    {
        dt *= m_fTimeScale;
    } 

//2. 分别枚举优先级小于0、等于0、大于0的update定时器。如果定时器没有暂停也没有“标记为删除”,则触发定时器。
    // Iterate over all the Updates‘ selectors
    tListEntry *pEntry, *pTmp;

    // updates with priority < 0
    DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
    {
       if ((! pEntry->paused) && (! pEntry->markedForDeletion))
        {
            pEntry->target->update(dt);
        }
    }
    // updates with priority == 0
    DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))
        {
            pEntry->target->update(dt);
        }
    }
    // updates with priority > 0
    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
    {
        if ((! pEntry->paused) && (! pEntry->markedForDeletion))
        {
            pEntry->target->update(dt);
        }
    }

//3.  1枚举所有注册过的普通interval定时器节点;2在枚举该节点的定时器,调用定时器的更新方法,从而决定是否触发该定时器
    // Iterate over all the custom selectors
    for (tHashTimerEntry *elt = m_pHashForTimers; elt != NULL; )
    {
        m_pCurrentTarget = elt;

        m_bCurrentTargetSalvaged = false;
        if (! m_pCurrentTarget->paused)
        {
            // The ‘timers‘ array may change while inside this loop
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
                elt->currentTimer = (CCTimer*)(elt->timers->arr[elt->timerIndex]);
                elt->currentTimerSalvaged = false;
                elt->currentTimer->update(dt);
                if (elt->currentTimerSalvaged)
                {
                    // The currentTimer told the remove itself. To prevent the timer from
                    // accidentally deallocating itself before finishing its step, we retained
                    // it. Now that step is done, it‘s safe to release it.
                    elt->currentTimer->release();
                }
                elt->currentTimer = NULL;
            }
        }

        // elt, at this moment, is still valid
        // so it is safe to ask this here (issue #490)
        elt = (tHashTimerEntry *)elt->hh.next;

        // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
        if (m_bCurrentTargetSalvaged && m_pCurrentTarget->timers->num == 0)
        {
            removeHashElement(m_pCurrentTarget);
        }
    }

// 4. 处理脚本引擎相关事件
    // Iterate over all the script callbacks
    if (m_pScriptHandlerEntries)
    {
        for (int i = m_pScriptHandlerEntries->count() - 1; i >= 0; i--)
        {
            CCSchedulerScriptHandlerEntry* pEntry = static_cast<CCSchedulerScriptHandlerEntry*>(m_pScriptHandlerEntries->objectAtIndex(i));
            if (pEntry->isMarkedForDeletion())
            {
                m_pScriptHandlerEntries->removeObjectAtIndex(i);
            }
            else if (!pEntry->isPaused())
            {
                pEntry->getTimer()->update(dt);
            }
        }
    }

// 5. 再次枚举Update定时器,删除前面被“标记为删除”的定时器
    // delete all updates that are marked for deletion
    // updates with priority < 0
    DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
    {
        if (pEntry->markedForDeletion)
        {
            this->removeUpdateFromHash(pEntry);
        }
    }
    // updates with priority == 0
    DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
    {
        if (pEntry->markedForDeletion)
        {
            this->removeUpdateFromHash(pEntry);
        }
    }
    // updates with priority > 0
    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
    {
        if (pEntry->markedForDeletion)
        {
            this->removeUpdateFromHash(pEntry);
        }
    }

    m_bUpdateHashLocked = false; //$

    m_pCurrentTarget = NULL;
}

对于Update定时器,每个节点只能注册一个定时器,因此调度器中存储定时器数据的结构体主要保存了注册节点和优先级。每一帧通过迭代调用链表中节点的Update函数来实现Update定时器。

对于普通interval定时器,每个节点能注册多个定时器,引擎使用回调函数(选择器)来区分同一个节点的不同定时器。调度器为每一个定时器创建了一个CCTimer对象,它记录了定时器的目标、回调函数、触发周期、重复触发等属性。

程序首先枚举了每个注册了定时器的对象,然后再枚举对象中定时器对应的CCTimer对象,调用CCTimer对象的update方法来更新定时器的状态,以便触发定时器事件。(在CCTimer的update方法中会把每一次调用时接受的时间间隔dt积累下来,如果经历的时间达到一次定时触发周期,就会触发对应节点的定时器事件(回调函数)。如果是一次的定时器,update就会终止,否者会重新计时,从而反复触发定时事件)

//注:$  、“标记为删除”: Update定时器三个链表正在迭代过程中,开发者完全可能在一个定时器事件中停用另一个定时器,如果立刻停用,这样会导致Update方法的迭代破坏。所以当定时器在迭代时(m_bUpdateHashLocked = true),删除一个节点的Update定时器不会立刻删除,而是“标记为删除”,在迭代完成后第5步再来清理被标记了的定时器,这样就保证了迭代的正确性。

对于普通interval定时器,通过update方法获知currentTimerSalvaged为true时,就会执行release,所以在迭代过程中CCTimer数组会改变,需要小心处理。

前些天做一个项目的时候,注册的一个调度器没能执行,后来发现是该节点没有添加到场景中,在if ((! pEntry->paused) && (! pEntry->markedForDeletion))时将会为false。

那么要为一个不加入场景的节点(如:全局网络派发器)添加调度器,就需要自己调用它的以下两个函数:

onEnter();
onEnterTransitionDidFinish();

这样,该节点的调度器就不会被暂停了。

至此可知,指定定时器后均由定时调度器控制,每个定时器互不干扰,串行执行。

时间: 2024-08-24 22:11:48

cocos2d-x调度器原理的相关文章

【转】Go调度器原理浅析

goroutine是golang的一大特色,或者可以说是最大的特色吧(据我了解),这篇文章主要翻译自Morsing的[这篇博客](http://morsmachine.dk/go-scheduler),我读这篇文章的时候不只是赞叹调度器设计的精巧,而且被Unix内核设计思想的影响和辐射所震撼,感觉好多好东西都带着它的影子. 绪论(Introduction)---------------------Go 1.1最大的特色之一就是这个新的调度器,由Dmitry Vyukov贡献.新调度器让并行的Go

cocos2d调度器(定时执行某函数)

调度器(scheduler) 继承关系 原理介绍 Cocos2d-x调度器为游戏提供定时事件和定时调用服务.所有Node对象都知道如何调度和取消调度事件,使用调度器有几个好处: 每当Node不再可见或已从场景中移除时,调度器会停止. Cocos2d-x暂停时,调度器也会停止.当Cocos2d-x重新开始时,调度器也会自动继续启动. Cocos2d-x封装了一个供各种不同平台使用的调度器,使用此调度器你不用关心和跟踪你所设定的定时对象的销毁和停止,以及崩溃的风险. 基础用法 游戏中我们经常会随时间

hadoop调度器的原理和应用场景解析

前置篇: 为从根本上解决旧 MapReduce 框架的性能瓶颈,促进 Hadoop 框架的更长远发展,从 0.23.0 版本开始,Hadoop 的 计算框架完全重构,发生了根本的变化.新的 Hadoop MapReduce 框架命名为 Yarn,重构根本的思想是将 JobTracker 两个主要的功能分离成单独的组件,这两个功能是计算资源管理和任务调度 / 监控.ResourceManager全局管理所有应用程序计算资源的分配,每一个应用的 ApplicationMaster 负责相应的调度和协

(5)调度器(scheduler)

继承关系 原理介绍 Cocos2d-x调度器为游戏提供定时事件和定时调用服务.所有Node对象都知道如何调度和取消调度事件,使用调度器有几个好处: 每当Node不再可见或已从场景中移除时,调度器会停止. Cocos2d-x暂停时,调度器也会停止.当Cocos2d-x重新开始时,调度器也会自动继续启动. Cocos2d-x封装了一个供各种不同平台使用的调度器,使用此调度器你不用关心和跟踪你所设定的定时对象的销毁和停止,以及崩溃的风险. 基础用法 游戏中我们经常会随时间的变化而做一些逻辑判断,如碰撞

Linux 调度器发展简述

引言 进程调度是操作系统的核心功能.调度器只是是调度过程中的一部分,进程调度是非常复杂的过程,需要多个系统协同工作完成.本文所关注的仅为调度器,它的主要工作是在所有 RUNNING 进程中选择最合适的一个.作为一个通用操作系统,Linux 调度器将进程分为三类: 交互式进程 此类进程有大量的人机交互,因此进程不断地处于睡眠状态,等待用户输入.典型的应用比如编辑器 vi.此类进程对系统响应时间要求比较高,否则用户会感觉系统反应迟缓. 批处理进程 此类进程不需要人机交互,在后台运行,需要占用大量的系

CFS 调度器

CFS调度器的原理明白了但是有个地方,搜遍了整个网络也没找到一个合理的解释: if (delta > ideal_runtime) resched_task(rq_of(cfs_rq)->curr); 这出代码是在patch:f685ceaca sched: Strengthen buddies and mitigate buddy induced latencies patch中描述和是LAST_BUDDY 和 wakeup preemption相关 buddy是什么意思?是调度队列中的而一

Linux IO 调度器

Linux IO Scheduler(Linux IO 调度器) 每个块设备或者块设备的分区,都对应有自身的请求队列(request_queue),而每个请求队列都可以选择一个I/O调度器来协调所递交的request.I/O调度器的基本目的是将请求按照它们对应在块设备上的扇区号进行排列,以减少磁头的移动,提高效率.每个设备的请求队列里的请求将按顺序被响应.实际上,除了这个队列,每个调度器自身都维护有不同数量的队列,用来对递交上来的request进行处理,而排在队列最前面的request将适时被移

quick_cocos2dx 调度器

定时器local sharedScheduler = CCDirector:sharedDirector():getScheduler() --自定义函数 i=1 function prForHimi() -- cocos2d.CCLuaLog("reFresh function") --取消选择器 print("定时器") pSlider:setValue(i) --更新 if i==100 then scheduler:unscheduleScriptEntry

监听器初始化Job、JobTracker相应TaskTracker心跳、调度器分配task源码级分析

JobTracker和TaskTracker分别启动之后(JobTracker启动流程源码级分析,TaskTracker启动过程源码级分析),taskTracker会通过心跳与JobTracker通信,并获取分配它的任务.用户将作业提交到JobTracker之后,放入相应的数据结构中,静等被分配.mapreduce job提交流程源码级分析(三)这篇文章已经分析了用户提交作业的最后步骤,主要是构造作业对应的JobInProgress并加入jobs,告知所有的JobInProgressListen