creator 里面的计时器相信大家不陌生,但是了解它的原理是必要的,它的运行机制和setInterval有什么不同呢,

先简单说说setInterval的原理:setInterval是每隔一段时间向事件队列里面添加回调函数,如果主线程忙的话调用时间不确定特别容易出问题,由于setInterval只负责定时向队列中添加函数,而不考虑函数的执行,setInterval有一个原则:在向队列中添加回调函数时,如果队列中存在之前由其添加的回调函数,就放弃本次添加(不会影响之后的计时),如果主线程忙的话,之前添加的计时器回调没有触发紧接着又添加一个这个时候就会放弃本次添加,是很容易出问题的。另外计时是不准确的。

但是提前说好schedule的计时也是不准确的

首先看看schedule是什么时候出生的:

cc.Director = function () {EventTarget.call(this);// paused?this._paused = false;// purge?this._purgeDirectorInNextLoop = false;this._winSizeInPoints = null;// scenesthis._scene = null;this._loadingScene = '';// FPSthis._totalFrames = 0;this._lastUpdate = 0;this._deltaTime = 0.0;this._startTime = 0.0;// ParticleSystem max step delta timethis._maxParticleDeltaTime = 0.0;// Scheduler for user registration updatethis._scheduler = null;// Scheduler for life-cycle methods in componentthis._compScheduler = null;// Node activatorthis._nodeActivator = null;// Action managerthis._actionManager = null;var self = this;game.on(game.EVENT_SHOW, function () {self._lastUpdate = performance.now();});// 监听引擎初始化完毕,初始化定时器game.once(game.EVENT_ENGINE_INITED, this.init, this);
};
cc.Director.prototype = {constructor: cc.Director,init: function () {this._totalFrames = 0;this._lastUpdate = performance.now();this._startTime = this._lastUpdate;this._paused = false;this._purgeDirectorInNextLoop = false;this._winSizeInPoints = cc.size(0, 0);// 引擎初始化完毕之后初始化了一个Schedule实例this._scheduler = new Scheduler();if (cc.ActionManager) {// action的优先级比较高系统级别的优先级this._actionManager = new cc.ActionManager();this._scheduler.scheduleUpdate(this._actionManager, Scheduler.PRIORITY_SYSTEM, false);} else {this._actionManager = null;}this.sharedInit();return true;},
cc.Scheduler = function () {this._timeScale = 1.0;// 优先级 < 0的定时器列表 有ActionMangaer,CollisionManager,physicsManager,physics3dManagerthis._updatesNegList = [];  // list of priority < 0// 优先级 = 0 的定时器列表this._updates0List = [];    // list of priority == 0// 优先级 > 0 的定时器列表this._updatesPosList = [];  // list of priority > 0this._hashForUpdates = js.createMap(true);  // hash used to fetch quickly the list entries for pause, delete, etcthis._hashForTimers = js.createMap(true);   // Used for "selectors with interval"this._currentTarget = null;this._currentTargetSalvaged = false;this._updateHashLocked = false; // If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.this._arrayForTimers = [];  // Speed up indexing//this._arrayForUpdates = [];   // Speed up indexing
};

在CCDirector.js中的mainLoop每帧调用schedule,所以schedule是帧驱动的方式运行的,跟setInterval不用:

mainLoop: CC_EDITOR ? function (deltaTime, updateAnimate) {this._deltaTime = deltaTime;// Updateif (!this._paused) {this.emit(cc.Director.EVENT_BEFORE_UPDATE);this._compScheduler.startPhase();this._compScheduler.updatePhase(deltaTime);if (updateAnimate) {this._scheduler.update(deltaTime);}this._compScheduler.lateUpdatePhase(deltaTime);this.emit(cc.Director.EVENT_AFTER_UPDATE);}// Renderthis.emit(cc.Director.EVENT_BEFORE_DRAW);renderer.render(this._scene, deltaTime);// After drawthis.emit(cc.Director.EVENT_AFTER_DRAW);this._totalFrames++;} : function (now) {if (this._purgeDirectorInNextLoop) {this._purgeDirectorInNextLoop = false;this.purgeDirector();}else {// calculate "global" dtthis.calculateDeltaTime(now);// Updateif (!this._paused) {// before updatethis.emit(cc.Director.EVENT_BEFORE_UPDATE);// Call start for new added componentsthis._compScheduler.startPhase();// Update for componentsthis._compScheduler.updatePhase(this._deltaTime);// Engine update with scheduler 这里更新schedulethis._scheduler.update(this._deltaTime);// Late update for componentsthis._compScheduler.lateUpdatePhase(this._deltaTime);// User can use this event to do things after updatethis.emit(cc.Director.EVENT_AFTER_UPDATE);// Destroy entities that have been removed recentlyObj._deferredDestroy();}// Renderthis.emit(cc.Director.EVENT_BEFORE_DRAW);renderer.render(this._scene, this._deltaTime);// After drawthis.emit(cc.Director.EVENT_AFTER_DRAW);eventManager.frameUpdateListeners();this._totalFrames++;}

在看看组件里面使用schedule的具体流程:

schedule (callback, interval, repeat, delay) {cc.assertID(callback, 1619);interval = interval || 0;cc.assertID(interval >= 0, 1620);repeat = isNaN(repeat) ? cc.macro.REPEAT_FOREVER : repeat;delay = delay || 0;var scheduler = cc.director.getScheduler();// should not use enabledInHierarchy to judge whether paused,// because enabledInHierarchy is assigned after onEnable.// Actually, if not yet scheduled, resumeTarget/pauseTarget has no effect on component,// therefore there is no way to guarantee the paused state other than isTargetPaused.var paused = scheduler.isTargetPaused(this);// 在该组件上注册了一个计时器scheduler.schedule(callback, this, interval, repeat, delay, paused);},

接着看看CCSchedule.js中的schedule方法干了什么:

// target是Component对象
schedule: function (callback, target, interval, repeat, delay, paused) {'use strict';if (typeof callback !== 'function') {var tmp = callback;callback = target;target = tmp;}//selector, target, interval, repeat, delay, paused//selector, target, interval, pausedif (arguments.length === 4 || arguments.length === 5) {paused = !!repeat;repeat = cc.macro.REPEAT_FOREVER;delay = 0;}cc.assertID(target, 1502);var targetId = target._id;if (!targetId) {if (target.__instanceId) {cc.warnID(1513);targetId = target._id = target.__instanceId;}else {cc.errorID(1510);}}// 通过targetId获得对应的定时器实例(HashTimerEntry),有点拗口的感觉,里面保存了timers target paused等属性var element = this._hashForTimers[targetId];if (!element) {// Is this the 1st element ? Then set the pause level to all the callback_fns of this targetelement = HashTimerEntry.get(null, target, 0, null, null, paused);// 注意看这里的arrayForTimes push 了一个CallbackTimer对象this._arrayForTimers.push(element);this._hashForTimers[targetId] = element;} else if (element.paused !== paused) {cc.warnID(1511);}var timer, i;if (element.timers == null) {element.timers = [];}else {for (i = 0; i < element.timers.length; ++i) {timer = element.timers[i];if (timer && callback === timer._callback) {// 回调已存在,将更新interval属性cc.logID(1507, timer.getInterval(), interval);timer._interval = interval;return;}}}// 获得一个定时器实例(CallbackTimer),这里也有一个对象池(_timers)timer = CallbackTimer.get();timer.initWithCallback(this, callback, target, interval, repeat, delay);// 自定义的计时器被push到timers里面了element.timers.push(timer);// 修改_currentTargetSalvaged防止当前的HashTimerEntry被删除 这个在update函数中会有相关解释if (this._currentTarget === element && this._currentTargetSalvaged) {this._currentTargetSalvaged = false;}},

回过头来看Schedule.update干了什么怎么一步一步驱动定时器运作的:

 update: function (dt) {this._updateHashLocked = true;if(this._timeScale !== 1)dt *= this._timeScale;var i, list, len, entry;// 先遍历优先级比较高的for(i=0,list=this._updatesNegList, len = list.length; i<len; i++){entry = list[i];if (!entry.paused && !entry.markedForDeletion)entry.target.update(dt);}for(i=0, list=this._updates0List, len=list.length; i<len; i++){entry = list[i];if (!entry.paused && !entry.markedForDeletion)entry.target.update(dt);}for(i=0, list=this._updatesPosList, len=list.length; i<len; i++){entry = list[i];if (!entry.paused && !entry.markedForDeletion)entry.target.update(dt);}// Iterate over all the custom selectors 遍历所有自定义的计时器var elt, arr = this._arrayForTimers;// arr HashTimerEntry : currentTimer,paused: boolean,target: cc.Component,timerIndex: number,timers: CallbackTimer[]for(i=0; i<arr.length; i++){elt = arr[i];this._currentTarget = elt;this._currentTargetSalvaged = false;if (!elt.paused){// The 'timers' array may change while inside this loopfor (elt.timerIndex = 0; elt.timerIndex < elt.timers.length; ++(elt.timerIndex)){elt.currentTimer = elt.timers[elt.timerIndex];elt.currentTimerSalvaged = false;// 更新计时器elt.currentTimer.update(dt);elt.currentTimer = null;}}// only delete currentTarget if no actions were scheduled during the cycle (issue #481)if (this._currentTargetSalvaged && this._currentTarget.timers.length === 0) {this._removeHashElement(this._currentTarget);--i;}}// delete all updates that are marked for deletion// updates with priority < 0for(i=0,list=this._updatesNegList; i<list.length; ){entry = list[i];if(entry.markedForDeletion)this._removeUpdateFromHash(entry);elsei++;}for(i=0, list=this._updates0List; i<list.length; ){entry = list[i];if (entry.markedForDeletion)this._removeUpdateFromHash(entry);elsei++;}for(i=0, list=this._updatesPosList; i<list.length; ){entry = list[i];if (entry.markedForDeletion)this._removeUpdateFromHash(entry);elsei++;}this._updateHashLocked = false;this._currentTarget = null;},

最后就是主角 CallbackTimer了:它是一个池子方便复用,提升性能

function CallbackTimer () {// 是否锁定this._lock = false;// 计时器对象this._scheduler = null;// 流逝的时间this._elapsed = -1;// 是否是一直运行this._runForever = false;// 是否使用延时this._useDelay = false;// 执行的次数this._timesExecuted = 0;// 重复次数this._repeat = 0;// 延时时间this._delay = 0;// 间隔时间this._interval = 0;// 目标组件this._target = null;// 绑定到计时器身上的回调函数this._callback = null;
}var proto = CallbackTimer.prototype;proto.initWithCallback = function (scheduler, callback, target, seconds, repeat, delay) {this._lock = false;this._scheduler = scheduler;this._target = target;this._callback = callback;this._elapsed = -1;this._interval = seconds;this._delay = delay;this._useDelay = (this._delay > 0);this._repeat = repeat;this._runForever = (this._repeat === cc.macro.REPEAT_FOREVER);return true;
};
/*** @return {Number} returns interval of timer*/
proto.getInterval = function(){return this._interval;};
/*** @param {Number} interval set interval in seconds*/
proto.setInterval = function(interval){this._interval = interval;};/*** triggers the timer* @param {Number} dt delta time*/
proto.update = function (dt) {if (this._elapsed === -1) {this._elapsed = 0;this._timesExecuted = 0;} else {/** 流逝的时间 */this._elapsed += dt;if (this._runForever && !this._useDelay) {//standard timer usageif (this._elapsed >= this._interval) {this.trigger();this._elapsed = 0;}} else {//advanced usageif (this._useDelay) {if (this._elapsed >= this._delay) {// 触发组件定时器绑定的回调函数this.trigger();this._elapsed -= this._delay;// 定时器执行的次数this._timesExecuted += 1;this._useDelay = false;}} else {if (this._elapsed >= this._interval) {this.trigger();this._elapsed = 0;this._timesExecuted += 1;}}if (this._callback && !this._runForever && this._timesExecuted > this._repeat)this.cancel();}}
};proto.getCallback = function(){return this._callback;
};proto.trigger = function () {if (this._target && this._callback) {this._lock = true;// 触发回调函数传了个参数流逝的时间 往往是不准确的this._callback.call(this._target, this._elapsed);this._lock = false;}
};proto.cancel = function () {//overridethis._scheduler.unschedule(this._callback, this._target);
};var _timers = [];
CallbackTimer.get = function () {return _timers.pop() || new CallbackTimer();
};
CallbackTimer.put = function (timer) {if (_timers.length < MAX_POOL_SIZE && !timer._lock) {timer._scheduler = timer._target = timer._callback = null;_timers.push(timer);}
};

到此为止分析了schedule的出生到消亡的过程,

1:schedule是帧驱动的

2:schedule((t) => {},1.0) ,t时间是不准确的,自定义的计时器优先级比较低

Cocos Creator 源码解读之Schedule相关推荐

  1. Cocos Creator 源码解读:siblingIndex 与 zIndex

    前言 本文基于 Cocos Creator 2.4.5 撰写.

  2. [3D游戏开发实践] Cocos Cyberpunk 源码解读-一文搞定延迟渲染管线原理与实践

    Cocos Cyberpunk 是 Cocos 引擎官方团队以展示引擎重度 3D 游戏制作能力,提升社区学习动力而推出的完整开源 TPS 3D游戏,支持 Web, IOS, Android 多端发布. ...

  3. [3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略

    Cocos Cyberpunk 是 Cocos 引擎官方团队以展示引擎重度 3D 游戏制作能力,提升社区学习动力而推出的完整开源 TPS 3D游戏,支持 Web, IOS, Android 多端发布. ...

  4. Cocos Creator源码和教程分享

    由于受论坛修改时间限制,后续不能修改的情况下,开新帖分享新源码和教程. https://forum.cocos.com/t/cocoscreator/80131 https://forum.cocos ...

  5. xxl-job源码解读:调度器schedule

    xxl-job源码解读:调度器schedule 本文基于xxl-job的2.3.1版本 基本说明 基本原理概述 调用器主要的用于判断定时任务的执行时间,按时调用触发器(trigger),再由触发器去获 ...

  6. PyTorch 源码解读之即时编译篇

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者丨OpenMMLab 来源丨https://zhuanlan.zhihu.com/ ...

  7. k8s与日志--journalbeat源码解读 1

    前言 对于日志系统的重要性不言而喻,参照沪江的一 篇关于日志系统的介绍,基本上日志数据在以下几方面具有非常重要的作用: 数据查找:通过检索日志信息,定位相应的 bug ,找出解决方案 服务诊断:通过对 ...

  8. Zeus源码解读之定时任务执行与手动执行任务的过程分析

    Zeus源码解读之定时任务执行与手动执行任务的过程分析 zeus集群依赖任务执行模式  宙斯中任务出去任务独立调度之外,支持任务直接的复杂依赖调度,如下图一所示: 图1  A为根任务,B,C依赖A任务 ...

  9. mobx 源码解读(四):讲讲 autorun 和 reaction

    原文地址:mobx autorun 文本是 mobx 源码解读系列 第四篇 本系列文章全部采用 mobx 较新版本:v5.13.0 mobx 源码解读 issue,欢迎讨论 技术前提 在阅读之前,希望 ...

最新文章

  1. 使用Python和OpenCV在图像之间执行超快速的颜色转换
  2. Spark SQL基本操作以及函数的使用
  3. 程序员:今天你读了吗?
  4. sqlmap tamper脚本编写
  5. 数据库SQL基础知识点
  6. 类中匿名函数如何从 event 中去除
  7. Web中的鼠标自动移动
  8. 企业文化用品展示网页的开发
  9. zabbix 监控项自动发现过滤_Zabbix使用javascript+jsonpath预处理动态生成监控项
  10. 剑指offer 面试26题
  11. python数据透视表怎么存下来_大数据分析如何利用Python创建数据透视表?
  12. 亚马逊跨境商家会用的邮件管理软件—解孵
  13. 红帽linux系统内核版本7,如何查看Linux发行版内核版本及系统版本?
  14. 使用MATLAB2010实现AVI视频播放
  15. DS18B20数字温度计 (一) 电气特性, 寄生供电模式和远距离接线
  16. FPGA_硬件电路(自用)
  17. C语言中%s,%m.ns 和 %e,%m.ne 的意思
  18. 盐城北大青鸟:Java的四大就业方向,薪资也是一级棒
  19. [Duolingo]如何在PC版页面登录手机号注册的账号
  20. 科目二连续失败的反思

热门文章

  1. 超级计算机的等级,Azure超级计算机等级的Nvidia A100 GPU云计算服务正式上线
  2. 三七互娱陷入买量瓶颈
  3. OpenAI提交GPT-5商标申请;韩国室温超导团队称论文存在缺陷,已要求下架;Nim v2.0释出 | 极客头条
  4. TypeScript日期工具: date-fns日期工具的使用方法
  5. Nmap中NSE数据文件分析
  6. 做UDEV规则文件实现U盘自动挂载
  7. Word页眉、页码的使用:利用分隔符设置指定页显示页眉,解决页码显示{PAGE \* MERGEFORMAT}问题
  8. java rfc 二围数据_如何使用Java解析RFC 3339数据时间?
  9. 理解了KMP算法却不知道代码怎么写?看看这道题你会有收获!(jmu-ds-实现KMP)
  10. 计算机网络——MAC地址