程序运行后每达到一帧的时间间隔就会执行一次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. Android 4.4 音量调节流程分析(二)

    之前在Android 4.4 音量调节流程分析(一)里已经有简单的分析音量控制的流程,今天想接着继续分析下音量大小计算的方法.对于任一播放文件而言其本身都有着固定大小的音量Volume_Max,而在A ...

  2. openstack 整合

  3. 第二百五十一天 how can I 坚持

    hadoop,namenote和datanode.namenode如果要是在启动时加载到内存,会不会对内存的要求比较高呢. edits-->fsimage. secondnamenode,那么n ...

  4. Java缓存学习之二:浏览器缓存机制

    浏览器端的九种缓存机制介绍 浏览器缓存是浏览器端保存数据用于快速读取或避免重复资源请求的优化机制,有效的缓存使用可以避免重复的网络请求和浏览器快速地读取本地数据,整体上加速网页展示给用户.浏览器端缓存 ...

  5. UIImage 相关操作

    修改UIImage大小 修改UISlider的最大值和最小值图片的时候,发现需要修改图片的大小,否则会导致UISlider变形.目前苹果还不支持直接修改UIImage类的大小,只能修改UIImageV ...

  6. HTTP常见错误 400/401/403/404/500及更多

    HTTP 错误 400 400 请求出错 由于语法格式有误,服务器无法理解此请求.不作修改,客户程序就无法重复此请求. HTTP 错误 401 401.1 未授权:登录失败 此错误表明传输给服务器的证 ...

  7. 如何把Excel数据转化成SQL语句-转

    问题背景 在我们实际的程序开发.维护的过程中,很多时候都要和Excel打交道. 因为用户的数据很多时候是Excel存储的. 公司维护项目的时候,经常要帮客户导入Excel数据,这些数据很多,零 碎,而 ...

  8. [Windows驱动开发](一)序言

    笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...

  9. 学习微软中间语言(MSIL)的绝佳工具 Dotnet IL Editor 推荐

    Dotnet IL Editor是一款.NET平台反编译工具,可以反编译.NET程序集文件为IL代码,并且可以执行,调试反编译后生成的IL代码.它的设计出发点比较直观,新建一个项目,添加程序集文件,设 ...

  10. 【转】安装Intel HAXM为Android 模拟器加速,30秒内启动完成

    http://www.cnblogs.com/Li-Cheng/p/4351966.html http://www.cnblogs.com/csulennon/p/4178404.html https ...