Scheduler——调度者、计划者。从字面上我们就可以看出,这个类负责的是程序和数据的调度。在上一篇的主循环分析中,( cocos2d-x 框架深入分析(一)—— MainLoop :)我们可以看到底层循环中:CCDirector::drawScene(void) 方法中,有这样的一句代码:m_pScheduler->update(m_fDeltaTime) ,这句代码中CCScheduler对象调用了成员函数update方法。
// calculate "global" dt
calculateDeltaTime();//计算时间差
//tick before glClear: issue #533
if (! m_bPaused) //如果不暂停,就更新数据
{
m_pScheduler->update(m_fDeltaTime);//调度者对象,是整个框架中,非常重要的东东,他负责者引擎中精灵、动作等的调度
//而里面所用的数据结构的组织,一定程度决定者引擎的效率。
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* to avoid flickr, nextScene MUST be here: after tick and before draw.
XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
if (m_pNextScene)
{
setNextScene();//如果有m_pNextScene对象不为空,就说明需要调用到新的场景中,
//在其中onEnter()、onEnterTransitionDidFinish()等函数被回调。
}
kmGLPushMatrix();//opengl:把当前矩阵放到栈中,开始绘图工作
// draw the scene
if (m_pRunningScene)
{
m_pRunningScene->visit();//通过访问方法,去绘制场景中包含的每个层和每个层中的每个节点的draw,
//这里面是一个递归的过程,其中transform()方法实现,opengl矩阵的变化,移动,旋转等。
}
float m_fTimeScale;
//
// "updates with priority" stuff
//
// 权限越小越先被调度
// 记录权限小于0的链表CCActionManager就是被装入的第一个节点
struct _listEntry *m_pUpdatesNegList; // list of priority < 0
// 记录权限等于0的链表,一般的节点是这个权限
struct _listEntry *m_pUpdates0List; // list priority == 0
// 记录权限大于0的链表,最后被调度
struct _listEntry *m_pUpdatesPosList; // list priority > 0
// 用于记录需要用Update方法的对象,这里是通过哈希表储存的,目的很明显,就是为了快速查找。
// 这里用的哈希表是在 ...\cocos2d-2.1rc0-x-2.1.2\cocos2dx\support\data_support\uthash.h
// uthash.h<span style="font-family: Arial;">头文件中,是用C语言实现的。</span>
// _hashUpdateEntry是哈希表项头地址,其中KEY是需要调度的对象的指针。VALUE就是_hashUpdateEntry结构体
struct _hashUpdateEntry *m_pHashForUpdates; // hash used to fetch quickly the list entries for pause,delete,etc
// Used for "selectors with interval"
// 结构同上,只是记录的是 SEL_SCHEDULE类型回调
struct _hashSelectorEntry *m_pHashForTimers;
// 结构同上,记录当前要调度的目标
struct _hashSelectorEntry *m_pCurrentTarget;
bool m_bCurrentTargetSalvaged;
// If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.
bool m_bUpdateHashLocked;
CCArray* m_pScriptHandlerEntries;
void CCScheduler::scheduleUpdateForTarget(CCObject *pTarget, int nPriority, bool bPaused)
{
// 申请一个用于记录Update的哈希项空指针
tHashUpdateEntry *pHashElement = NULL;
// 把KEY=pTarget 放到m_pHashForUpdates哈希链表中查找,找到为pHashElement赋值,没找到依然为空。
HASH_FIND_INT(m_pHashForUpdates, &pTarget, pHashElement);
if (pHashElement) // 存在这个值
{
#if COCOS2D_DEBUG >= 1
CCAssert(pHashElement->entry->markedForDeletion,"");
#endif
// TODO: check if priority has changed!
// 确保被调度对象不被删除,最后返回
pHashElement->entry->markedForDeletion = false;
return;
}
// most of the updates are going to be 0, that's way there
// is an special list for updates with priority 0
// 多数的更新操作的权限值为0。
if (nPriority == 0)
{
appendIn(&m_pUpdates0List, pTarget, bPaused);
}
// CCActionManager的更新权限是小于0的。
else if (nPriority < 0)
{
priorityIn(&m_pUpdatesNegList, pTarget, nPriority, bPaused);
}
else
{
// priority > 0
priorityIn(&m_pUpdatesPosList, pTarget, nPriority, bPaused);
}
}
void CCScheduler::appendIn(_listEntry **ppList, CCObject *pTarget, bool bPaused)
{
//申请一个链表节点
tListEntry *pListElement = (tListEntry *)malloc(sizeof(*pListElement));
pListElement->target = pTarget;
pListElement->paused = bPaused;
pListElement->markedForDeletion = false;
//把链表节点插入链表中。DL_APPEND这个方法是在utlist.h文件中,用C语言写的一个链表
DL_APPEND(*ppList, pListElement);
// update hash entry for quicker access
// 申请一个哈希项,用于更新操作,可以快速查找。
tHashUpdateEntry *pHashElement = (tHashUpdateEntry *)calloc(sizeof(*pHashElement), 1);
pHashElement->target = pTarget;
pTarget->retain();
pHashElement->list = ppList;
pHashElement->entry = pListElement;
// 把KEY为target,VALUE为pHashElement,插入哈希表中
HASH_ADD_INT(m_pHashForUpdates, target, pHashElement);
}
// main loop
void CCScheduler::update(float dt)
{
m_bUpdateHashLocked = true;
if (m_fTimeScale != 1.0f)
{
dt *= m_fTimeScale;
}
// Iterate over all the Updates' selectors
tListEntry *pEntry, *pTmp;
// 遍历每个权限小于0的链表,并调用他们的update方法
// updates with priority < 0
DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp)
{
// 不是暂定和删除状态就update
if ((! pEntry->paused) && (! pEntry->markedForDeletion))//如果不是暂停和删除标志
{
pEntry->target->update(dt);
}
}
// 遍历每个权限等于0的链表,并调用他们的update方法
// updates with priority == 0
DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp)
{
// 不是暂定和删除状态就update
if ((! pEntry->paused) && (! pEntry->markedForDeletion))
{
pEntry->target->update(dt);
}
}
// 遍历每个权限大于0的链表,并调用他们的update方法
// updates with priority > 0
DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp)
{
// 不是暂定和删除状态就update
if ((! pEntry->paused) && (! pEntry->markedForDeletion))
{
pEntry->target->update(dt);
}
}
// 遍历更新所有的定制的选择器,注意这里遍历的实际是哈希表,这是一个比较特殊的哈希表,在下次讲数据结构时会说
// 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))
{
// 当前索引的Timer
elt->currentTimer = (CCTimer*)(elt->timers->arr[elt->timerIndex]);
elt->currentTimerSalvaged = false;
elt->currentTimer->update(dt);
// 如果update方法调用后currentTimerSalvaged被改为true就取消当前Timer
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);
}
}
// 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);
}
}
}
// 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;
}