Scheduler——调度者、计划者。从字面上我们就可以看出,这个类负责的是程序和数据的调度。在上一篇的主循环分析中,( cocos2d-x 框架深入分析(一)—— MainLoop :http://cocos2d.cocoachina.com/bbs/forum.php?mod=viewthread&tid=10881&extra=page%3D1)我们可以看到底层循环中:CCDirector::drawScene(void) 方法中,有这样的一句代码:m_pScheduler->update(m_fDeltaTime) ,这句代码中CCScheduler对象调用了成员函数update方法。

  1.    // calculate "global" dt

  2.    calculateDeltaTime();//计算时间差


  3.    //tick before glClear: issue #533

  4.    if (! m_bPaused)    //如果不暂停,就更新数据

  5.    {

  6.        m_pScheduler->update(m_fDeltaTime);//调度者对象,是整个框架中,非常重要的东东,他负责者引擎中精灵、动作等的调度

  7.                                                                                   //而里面所用的数据结构的组织,一定程度决定者引擎的效率。

  8.    }


  9.    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


  10.    /* to avoid flickr, nextScene MUST be here: after tick and before draw.

  11.     XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */

  12.    if (m_pNextScene)

  13.    {

  14.        setNextScene();//如果有m_pNextScene对象不为空,就说明需要调用到新的场景中,

  15.                                           //在其中onEnter()、onEnterTransitionDidFinish()等函数被回调。

  16.    }


  17.    kmGLPushMatrix();//opengl:把当前矩阵放到栈中,开始绘图工作


  18.    // draw the scene

  19.    if (m_pRunningScene)

  20.    {

  21.        m_pRunningScene->visit();//通过访问方法,去绘制场景中包含的每个层和每个层中的每个节点的draw,

  22.                                                                 //这里面是一个递归的过程,其中transform()方法实现,opengl矩阵的变化,移动,旋转等。

  23.    }

复制代码
从代码上看,这段代码是在我们绘制图形前调用的。
更新?到底要更新什么?一个方法就可以实现了所有我们需要更新的数据?代者疑问,我们不妨来猜测一下设计者的思路:
1、既然是在绘图前的,那应该是更新游戏场景中的数据,比如:每层的显隐,精灵的坐标,旋转角度、纹理地图坐标、一些动画等等。
2、一个方法就可以实习,我们不妨大胆的猜测里面用上的设计模式和数据组织。如果我们把用调度的对象加入CCScheduler中,让CCScheduler来管理,和调度对象,这样的模式有一点像观察者设计模式(个人理解,不一定对!-。-!),对于观察者模式,我的理解为:有一个被观察者(类似于这个CCScheduler),在类中的内部中有0到多个的观察者接口(比如一个精灵),当被观察者的状态有所变化时,被观察者调用某个通知方法时,就会调用内部的所有的观察者接口的方法,这样就可以实现我们想要的效果。
      好了,我们带着自己的猜想去看看源代码。在进入update方法前,我们先看看这个类维持的是什么数据吧。
      找到...\cocos2d-2.1rc0-x-2.1.2\cocos2dx\CCScheduler.h
  1.    float m_fTimeScale;


  2.    //

  3.    // "updates with priority" stuff

  4.    //

  5.        // 权限越小越先被调度

  6.        // 记录权限小于0的链表CCActionManager就是被装入的第一个节点

  7.    struct _listEntry *m_pUpdatesNegList;        // list of priority < 0

  8.        // 记录权限等于0的链表,一般的节点是这个权限

  9.    struct _listEntry *m_pUpdates0List;            // list priority == 0

  10.        // 记录权限大于0的链表,最后被调度

  11.    struct _listEntry *m_pUpdatesPosList;        // list priority > 0



  12.        // 用于记录需要用Update方法的对象,这里是通过哈希表储存的,目的很明显,就是为了快速查找。

  13.        // 这里用的哈希表是在   ...\cocos2d-2.1rc0-x-2.1.2\cocos2dx\support\data_support\uthash.h

  14.        // uthash.h<span style="font-family: Arial;">头文件中,是用C语言实现的。</span>

  15.        // _hashUpdateEntry是哈希表项头地址,其中KEY是需要调度的对象的指针。VALUE就是_hashUpdateEntry结构体

  16.    struct _hashUpdateEntry *m_pHashForUpdates; // hash used to fetch quickly the list entries for pause,delete,etc


  17.    // Used for "selectors with interval"

  18.        // 结构同上,只是记录的是 SEL_SCHEDULE类型回调

  19.    struct _hashSelectorEntry *m_pHashForTimers;

  20.        // 结构同上,记录当前要调度的目标

  21.    struct _hashSelectorEntry *m_pCurrentTarget;


  22.    bool m_bCurrentTargetSalvaged;

  23.    // If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.

  24.    bool m_bUpdateHashLocked;

  25.    CCArray* m_pScriptHandlerEntries;

复制代码
这就是CCScheduler维持的数据。其中包涵3个链表和3个哈希项。由此可以看出,当我们向CCScheduler中添加一个调度对象时(实际也就是scheduleSelector(SEL_SCHEDULE pfnSelector, CCObject *pTarget, float fInterval, bool bPaused) 或者 scheduleUpdateForTarget(CCObject *pTarget, int nPriority, bool bPaused) 等方法)。我们现在看看scheduleUpdateForTarget这个方法,看看是怎么把调度对象加入CCScheduler中的。
进入...\cocos2d-2.1rc0-x-2.1.2\cocos2dx\CCScheduler.cpp
  1. void CCScheduler::scheduleUpdateForTarget(CCObject *pTarget, int nPriority, bool bPaused)

  2. {

  3.        // 申请一个用于记录Update的哈希项空指针

  4.    tHashUpdateEntry *pHashElement = NULL;

  5.        // 把KEY=pTarget 放到m_pHashForUpdates哈希链表中查找,找到为pHashElement赋值,没找到依然为空。

  6.    HASH_FIND_INT(m_pHashForUpdates, &pTarget, pHashElement);

  7.    if (pHashElement)   // 存在这个值

  8.    {

  9. #if COCOS2D_DEBUG >= 1

  10.        CCAssert(pHashElement->entry->markedForDeletion,"");

  11. #endif

  12.        // TODO: check if priority has changed!

  13.        // 确保被调度对象不被删除,最后返回

  14.        pHashElement->entry->markedForDeletion = false;

  15.        return;

  16.    }


  17.    // most of the updates are going to be 0, that's way there

  18.    // is an special list for updates with priority 0

  19.        // 多数的更新操作的权限值为0。

  20.    if (nPriority == 0)

  21.    {

  22.        appendIn(&m_pUpdates0List, pTarget, bPaused);

  23.    }

  24.        // CCActionManager的更新权限是小于0的。

  25.    else if (nPriority < 0)

  26.    {

  27.        priorityIn(&m_pUpdatesNegList, pTarget, nPriority, bPaused);

  28.    }

  29.    else

  30.    {

  31.        // priority > 0

  32.        priorityIn(&m_pUpdatesPosList, pTarget, nPriority, bPaused);

  33.    }

  34. }

复制代码
从上面的代码可以看出,如果在哈希表中已经存在对应的值就不在插入了,否则就进行插入方法。appendIn和priorityIn的方法实现的原理差不多,只是priorityIn方法在插入时,是按照权限的大小排序插入的。现在我们进入appendIn函数看看。
进入:...\cocos2d-2.1rc0-x-2.1.2\cocos2dx\CCScheduler.cpp
  1. void CCScheduler::appendIn(_listEntry **ppList, CCObject *pTarget, bool bPaused)

  2. {

  3.        //申请一个链表节点

  4.    tListEntry *pListElement = (tListEntry *)malloc(sizeof(*pListElement));


  5.    pListElement->target = pTarget;

  6.    pListElement->paused = bPaused;

  7.    pListElement->markedForDeletion = false;

  8.        //把链表节点插入链表中。DL_APPEND这个方法是在utlist.h文件中,用C语言写的一个链表

  9.    DL_APPEND(*ppList, pListElement);


  10.    // update hash entry for quicker access

  11.        // 申请一个哈希项,用于更新操作,可以快速查找。

  12.    tHashUpdateEntry *pHashElement = (tHashUpdateEntry *)calloc(sizeof(*pHashElement), 1);

  13.    pHashElement->target = pTarget;

  14.    pTarget->retain();

  15.    pHashElement->list = ppList;

  16.    pHashElement->entry = pListElement;

  17.        // 把KEY为target,VALUE为pHashElement,插入哈希表中

  18.    HASH_ADD_INT(m_pHashForUpdates, target, pHashElement);

  19. }

复制代码
这个方法就是简单的插入数据。具体的每个字段的含义,下次讲到CCScheduler用到的数据结构时,具体讲。。。
下面我们看看最重要的update方法:...\cocos2d-2.1rc0-x-2.1.2\cocos2dx\CCScheduler.cpp
  1. // main loop

  2. void CCScheduler::update(float dt)

  3. {

  4.    m_bUpdateHashLocked = true;


  5.    if (m_fTimeScale != 1.0f)

  6.    {

  7.        dt *= m_fTimeScale;

  8.    }


  9.    // Iterate over all the Updates' selectors

  10.    tListEntry *pEntry, *pTmp;


  11.        // 遍历每个权限小于0的链表,并调用他们的update方法

  12.    // updates with priority < 0

  13.    DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)

  14.    {

  15.                // 不是暂定和删除状态就update

  16.        if ((! pEntry->paused) && (! pEntry->markedForDeletion))//如果不是暂停和删除标志

  17.        {

  18.            pEntry->target->update(dt);

  19.        }

  20.    }


  21.        // 遍历每个权限等于0的链表,并调用他们的update方法

  22.    // updates with priority == 0

  23.    DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)

  24.    {

  25.                // 不是暂定和删除状态就update

  26.        if ((! pEntry->paused) && (! pEntry->markedForDeletion))

  27.        {

  28.            pEntry->target->update(dt);

  29.        }

  30.    }


  31.        // 遍历每个权限大于0的链表,并调用他们的update方法

  32.    // updates with priority > 0

  33.    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)

  34.    {

  35.                // 不是暂定和删除状态就update

  36.        if ((! pEntry->paused) && (! pEntry->markedForDeletion))

  37.        {

  38.            pEntry->target->update(dt);

  39.        }

  40.    }


  41.        // 遍历更新所有的定制的选择器,注意这里遍历的实际是哈希表,这是一个比较特殊的哈希表,在下次讲数据结构时会说

  42.    // Iterate over all the custom selectors

  43.    for (tHashTimerEntry *elt = m_pHashForTimers; elt != NULL; )

  44.    {

  45.        m_pCurrentTarget = elt;

  46.        m_bCurrentTargetSalvaged = false;


  47.        if (! m_pCurrentTarget->paused)

  48.        {

  49.                        // 一个周期回调

  50.            // The 'timers' array may change while inside this loop

  51.            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))

  52.            {

  53.                                // 当前索引的Timer

  54.                elt->currentTimer = (CCTimer*)(elt->timers->arr[elt->timerIndex]);

  55.                elt->currentTimerSalvaged = false;


  56.                elt->currentTimer->update(dt);


  57.                                // 如果update方法调用后currentTimerSalvaged被改为true就取消当前Timer

  58.                if (elt->currentTimerSalvaged)

  59.                {

  60.                    // The currentTimer told the remove itself. To prevent the timer from

  61.                    // accidentally deallocating itself before finishing its step, we retained

  62.                    // it. Now that step is done, it's safe to release it.

  63.                    elt->currentTimer->release();

  64.                }


  65.                elt->currentTimer = NULL;

  66.            }

  67.        }


  68.        // elt, at this moment, is still valid

  69.        // so it is safe to ask this here (issue #490)

  70.                // 哈希表的下一个位置

  71.        elt = (tHashTimerEntry *)elt->hh.next;


  72.        // only delete currentTarget if no actions were scheduled during the cycle (issue #481)

  73.                // 当前目标对象没有动作需要调度就把他从哈希表中移除

  74.        if (m_bCurrentTargetSalvaged && m_pCurrentTarget->timers->num == 0)

  75.        {

  76.            removeHashElement(m_pCurrentTarget);

  77.        }

  78.    }


  79.    // Iterate over all the script callbacks

  80.    if (m_pScriptHandlerEntries)

  81.    {

  82.        for (int i = m_pScriptHandlerEntries->count() - 1; i >= 0; i--)

  83.        {

  84.            CCSchedulerScriptHandlerEntry* pEntry = static_cast<CCSchedulerScriptHandlerEntry*>(m_pScriptHandlerEntries->objectAtIndex(i));

  85.            if (pEntry->isMarkedForDeletion())

  86.            {

  87.                m_pScriptHandlerEntries->removeObjectAtIndex(i);

  88.            }

  89.            else if (!pEntry->isPaused())

  90.            {

  91.                pEntry->getTimer()->update(dt);

  92.            }

  93.        }

  94.    }


  95.    // delete all updates that are marked for deletion

  96.    // updates with priority < 0

  97.        // 依据权限把当前被设置为删除状态的目标对象,从哈希表中移除

  98.    DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)

  99.    {

  100.        if (pEntry->markedForDeletion)

  101.        {

  102.            this->removeUpdateFromHash(pEntry);

  103.        }

  104.    }


  105.    // updates with priority == 0

  106.    DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)

  107.    {

  108.        if (pEntry->markedForDeletion)

  109.        {

  110.            this->removeUpdateFromHash(pEntry);

  111.        }

  112.    }


  113.    // updates with priority > 0

  114.    DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)

  115.    {

  116.        if (pEntry->markedForDeletion)

  117.        {

  118.            this->removeUpdateFromHash(pEntry);

  119.        }

  120.    }


  121.    m_bUpdateHashLocked = false;


  122.    m_pCurrentTarget = NULL;

  123. }

复制代码
现在我们基本都知道了引擎是怎么通过CCScheduler更新所有node的数据的了,其中引起注意的是Update方法是CCObject的虚函数。所以只要是继承CCObject实现Update方法的类,注册进CCScheduler中,我们就可以在Update方法中每帧更新自己的数据。甚至我们还可以注册自己的回调函数。可见CCScheduler设计的强大。