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

void CCDisplayLinkDirector::mainLoop(void)
{
//判断是否需要释放CCDirector,通常游戏结束才会执行这个步骤if (m_bPurgeDirecotorInNextLoop){m_bPurgeDirecotorInNextLoop = false;purgeDirector();}else if (! m_bInvalid){//绘制当前场景并执行其他必要的处理    drawScene();//弹出自动回收池,使这一帧被放入回收池的对象全部执行releaseCCPoolManager::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 buffersif (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 < 0struct _listEntry *m_pUpdates0List;            // list priority == 0struct _listEntry *m_pUpdatesPosList;        // list priority > 0struct _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' selectorstListEntry *pEntry, *pTmp;// updates with priority < 0DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp){if ((! pEntry->paused) && (! pEntry->markedForDeletion)){pEntry->target->update(dt);}}// updates with priority == 0DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp){if ((! pEntry->paused) && (! pEntry->markedForDeletion)){pEntry->target->update(dt);}}// updates with priority > 0DL_FOREACH_SAFE(m_pUpdatesPosList, pEntry, pTmp){if ((! pEntry->paused) && (! pEntry->markedForDeletion)){pEntry->target->update(dt);}}//3.  1枚举所有注册过的普通interval定时器节点;2在枚举该节点的定时器,调用定时器的更新方法,从而决定是否触发该定时器// Iterate over all the custom selectorsfor (tHashTimerEntry *elt = m_pHashForTimers; elt != NULL; ){m_pCurrentTarget = elt;m_bCurrentTargetSalvaged = false;if (! m_pCurrentTarget->paused){// The 'timers' array may change while inside this loopfor (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 callbacksif (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 < 0DL_FOREACH_SAFE(m_pUpdatesNegList, pEntry, pTmp){if (pEntry->markedForDeletion){this->removeUpdateFromHash(pEntry);}}// updates with priority == 0DL_FOREACH_SAFE(m_pUpdates0List, pEntry, pTmp){if (pEntry->markedForDeletion){this->removeUpdateFromHash(pEntry);}}// updates with priority > 0DL_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调度器原理,mainloop的Update相关推荐

  1. 第十四课 k8s源码学习和二次开发原理篇-调度器原理

    第十四课 k8s源码学习和二次开发原理篇-调度器原理 tags: k8s 源码学习 categories: 源码学习 二次开发 文章目录 第十四课 k8s源码学习和二次开发原理篇-调度器原理 第一节 ...

  2. Golang的协程调度器原理及GMP设计思想

    一.Golang"调度器"的由来? (1) 单进程时代不需要调度器 我们知道,一切的软件都是跑在操作系统上,真正用来干活(计算)的是CPU.早期的操作系统每个程序就是一个进程,知道 ...

  3. Linux进程调度-CFS调度器原理分析及实现,懂了

    1. 概述 (1) Completely Fair Scheduler,完全公平调度器,用于Linux系统中普通进程的调度. (2) CFS采用了红黑树算法来管理所有的调度实体 sched_entit ...

  4. 实战:调度器原理-2022.2.17

    目录 文章目录 目录 实验环境 实验软件 1.调度器 2.调度流程 1.调度框架 1.扩展点(Extension Points) 2.示例 3.调度器调优 4.优先级调度 关于我 最后 实验环境 实验 ...

  5. Cocos2Dx之调度器-欧阳左至

    有的时候我们还需要使用其他的时间触发机制,比如一个重复性动作2秒之后再执行,并且重复间隔为3秒.怎么实现呢? 通过前面的分析,我们知道每个帧间隔时间到期后,都会调用CCDirector的mainLoo ...

  6. [转]Golang中goroutine的调度器详解

    Go调度器原理浅析 来源:https://www.douban.com/note/300631999/ goroutine是golang的一大特色,或者可以说是最大的特色吧(据我了解),这篇文章主要翻 ...

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

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

  8. Vue3 生命周期Hooks函数与调度器Scheduler的原理

    大厂技术  高级前端  Node进阶 点击上方 程序员成长指北,关注公众号 回复1,加入高级Node交流群 写在最前:本文章的目标 Vue3的生命周期的实现原理是比较简单的,但要理解整个Vue3的生命 ...

  9. xxl-job源码—调度器/执行器工作原理

    目录 一.架构图 1.1 功能架构图 2.2 任务调度工作原理 二.ER图 三.调度器 3.1 启动过程时序图 3.2 启动过程核心代码解析 3.2.1 启动初始化 3.2.2 执行器健康检查 3.2 ...

最新文章

  1. c++创建单级目录 多级目录,判断是否存在
  2. OSI与TCP/IP协议区别
  3. qemu模拟imx6用户态环境
  4. TDD测试驱动开发过程
  5. powerpc-linux-gcc,关于powerpc-linux-uclibc-gcc的使用
  6. kali安装python3.7_Debian服务器之安装Python3.7
  7. html+css 小案例(一)
  8. soidworks 生成PCD点云文件
  9. 图解交换机与路由器组网
  10. 无人机-2多翼无人机的结构与硬件
  11. IDEA导入Git项目后右键项目找不到Git选项的解决方法
  12. javaweb文件压缩下载
  13. 没有云服务器?内网穿透了解一下
  14. PS4蓝牙手柄分析之1
  15. N字霸气多空博弈大师能量潮拐点战法通达信 主图/副图/选股指标
  16. Windows 注册表(Registry) 学习
  17. CentOs 7.3 —— 使用rescue找回被删除的系统文件
  18. 怎样启动模拟器模拟鸿蒙系统,鸿蒙OS 模拟器运行
  19. EKL软件历史版本下载地址集合
  20. 本地mysql拒绝jdbc连接失败_jdbc连接MySQL数据库的诡异异常 java.net.ConnectException: 拒绝连接...

热门文章

  1. db文件 linux查看工具,Linux最大文件句柄数查看及修改
  2. linux脚本怎么获取参数,在Bash shell脚本编程中,如何正确无误获取到“脚本选项参数”和“脚本参数”呢?...
  3. 福建省计算机中职类高考400分多少名,重要参考!福建高职分类各院校近两年招生计划及分数线汇总来了,快收藏...
  4. linux bc安装的代码,BCLinux安装教程新篇
  5. php db类 应用实例,PHP封装类似thinkphp连贯操作数据库Db类与简单应用示例
  6. shell调用python脚本传递命名关键字参数_关于python:将字典作为关键字参数传递给函数...
  7. PHP编译参数 --prefix=/usr/local/php 的“深远”影响
  8. 如何修改游戏服务器端的数据,如何修改网络游戏服务器数据
  9. Spring写第一个程序HelloSpring
  10. NYOJ-括号配对问题(数据结构)