程序运行后每达到一帧的时间间隔就会执行一次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 = ; 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 == )
{
removeHashElement(m_pCurrentTarget);
}
} // 4. 处理脚本引擎相关事件
// Iterate over all the script callbacks
if (m_pScriptHandlerEntries)
{
for (int i = m_pScriptHandlerEntries->count() - ; i >= ; 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();

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

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

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

  1. Golang/Go goroutine调度器原理/实现【原】

    Go语言在2016年再次拿下TIBOE年度编程语言称号,这充分证明了Go语言这几年在全世界范围内的受欢迎程度.如果要对世界范围内的gopher发起一次“你究竟喜欢Go的哪一点”的调查,我相信很多Gop ...

  2. Kubernetes集群调度器原理剖析及思考

    简述 云环境或者计算仓库级别(将整个数据中心当做单个计算池)的集群管理系统通常会定义出工作负载的规范,并使用调度器将工作负载放置到集群恰当的位置.好的调度器可以让集群的工作处理更高效,同时提高资源利用 ...

  3. IO调度器原理介绍

    IO调度器(IO Scheduler)是操作系统用来决定块设备上IO操作提交顺序的方法.存在的目的有两个,一是提高IO吞吐量,二是降低IO响应时间.然而IO吞吐量和IO响应时间往往是矛盾的,为了尽量平 ...

  4. 【转】Go调度器原理浅析

    goroutine是golang的一大特色,或者可以说是最大的特色吧(据我了解),这篇文章主要翻译自Morsing的[这篇博客](http://morsmachine.dk/go-scheduler) ...

  5. Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析

    最近在做一些和 NIF 有关的事情,看到 OTP 团队发布的 17 rc1 引入了一个新的特性“脏调度器”,为的是解决 NIF 运行时间过长耗死调度器的问题.本文首先简单介绍脏调度器机制的用法,然后简 ...

  6. TKE 用户故事 | 作业帮 Kubernetes 原生调度器优化实践

    作者 吕亚霖,2019年加入作业帮,作业帮架构研发负责人,在作业帮期间主导了云原生架构演进.推动实施容器化改造.服务治理.GO微服务框架.DevOps的落地实践. 简介 调度系统的本质是为计算服务/任 ...

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

    调度器(scheduler) 继承关系 原理介绍 Cocos2d-x调度器为游戏提供定时事件和定时调用服务.所有Node对象都知道如何调度和取消调度事件,使用调度器有几个好处: 每当Node不再可见或 ...

  8. MapReduce多用户任务调度器——容量调度器(Capacity Scheduler)原理和源码研究

    前言:为了研究需要,将Capacity Scheduler和Fair Scheduler的原理和代码进行学习,用两篇文章作为记录.如有理解错误之处,欢迎批评指正. 容量调度器(Capacity Sch ...

  9. Java并发编程原理与实战三十八:多线程调度器(ScheduledThreadPoolExecutor)

    在前面介绍了java的多线程的基本原理信息:线程池的原理与使用 本文对这个java本身的线程池的调度器做一个简单扩展,如果还没读过上一篇文章,建议读一下,因为这是调度器的核心组件部分. 我们如果要用j ...

随机推荐

  1. 高精度+搜索+质数 BZOJ1225 [HNOI2001] 求正整数

    // 高精度+搜索+质数 BZOJ1225 [HNOI2001] 求正整数 // 思路: // http://blog.csdn.net/huzecong/article/details/847868 ...

  2. [POJ] #1001# Exponentiation : 大数乘法

    一. 题目 Exponentiation Time Limit: 500MS   Memory Limit: 10000K Total Submissions: 156373   Accepted: ...

  3. HDU ACM Eight

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043 解题背景: 看到八数码问题,没有任何的想法,偶然在翻看以前做的题的时候发现解决过类似的一道题,不 ...

  4. Linux里实用命令之添加行号、文本和语法高亮显示

    写在前面的话 本博主我,强烈建议,来看此博文的朋友们,都玩玩. 最好,在刚入门的时候呢,不加行号,不玩文本和语法高亮显示,以后会深有体会.磨炼自己! 步骤一:进入 /etc/virc配置文件 步骤二: ...

  5. UVALive 7327 Digit Division (模拟)

    Digit Division 题目链接: http://acm.hust.edu.cn/vjudge/contest/127407#problem/D Description We are given ...

  6. ADUM1201在隔离RS232中的应用 【瓦特芯收藏】

    ADUM1201在隔离RS232中的应用 引言: RS-232是PC机与工业通信中应用最广泛的一种串行接口.RS-232接口最初是由美国EIA(电子工业联合会)规定的用于计算机与终端设备之间通讯的一种 ...

  7. [转]ORA-00907: 缺失右括号

    转至:http://www.cnblogs.com/Olive116/p/5149680.html ORA-00907: 缺失右括号 前言 最近在开发过程中使用oracle数据库,在程序中进行查询数据 ...

  8. Winform开发框架之通用自动更新模块(转)

    在网络化的环境中,特别是基于互联网发布的Winform程序,程序的自动更新功能是比较重要的操作,这样可以避免挨个给使用者打电话.发信息通知或者发送软件等,要求其对应用程序进行升级.实现程序的自动更新, ...

  9. 数据访问层DAL(数据库访问抽象类DataProvider)

    晒晒数据访问层DAL,看看你的项目数据访问层使用的是什么形式,数据访问性能比较 采用什么样的数据访问形式是软件编码很重要的一个环节,良好的数据访问形式不仅能够提搞代码的执行效率,协作能力,更重要的是对 ...

  10. 算法效果AB测试中的PV-UV不对称性

    (转载请注明原创于潘多拉盒子) 算法效果的AB测试,是指在相同的应用场景下,对比不同算法的效果.通常的做法是,按照PV或UV随机分配流量到算法上,计算算法的CTR或转化率进行对比.为了表述简单,我们假 ...