admin管理员组文章数量:1568433
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;
// scenes
this._scene = null;
this._loadingScene = '';
// FPS
this._totalFrames = 0;
this._lastUpdate = 0;
this._deltaTime = 0.0;
this._startTime = 0.0;
// ParticleSystem max step delta time
this._maxParticleDeltaTime = 0.0;
// Scheduler for user registration update
this._scheduler = null;
// Scheduler for life-cycle methods in component
this._compScheduler = null;
// Node activator
this._nodeActivator = null;
// Action manager
this._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,physics3dManager
this._updatesNegList = []; // list of priority < 0
// 优先级 = 0 的定时器列表
this._updates0List = []; // list of priority == 0
// 优先级 > 0 的定时器列表
this._updatesPosList = []; // list of priority > 0
this._hashForUpdates = js.createMap(true); // hash used to fetch quickly the list entries for pause, delete, etc
this._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;
// Update
if (!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);
}
// Render
this.emit(cc.Director.EVENT_BEFORE_DRAW);
renderer.render(this._scene, deltaTime);
// After draw
this.emit(cc.Director.EVENT_AFTER_DRAW);
this._totalFrames++;
} : function (now) {
if (this._purgeDirectorInNextLoop) {
this._purgeDirectorInNextLoop = false;
this.purgeDirector();
}
else {
// calculate "global" dt
this.calculateDeltaTime(now);
// Update
if (!this._paused) {
// before update
this.emit(cc.Director.EVENT_BEFORE_UPDATE);
// Call start for new added components
this._compScheduler.startPhase();
// Update for components
this._compScheduler.updatePhase(this._deltaTime);
// Engine update with scheduler 这里更新schedule
this._scheduler.update(this._deltaTime);
// Late update for components
this._compScheduler.lateUpdatePhase(this._deltaTime);
// User can use this event to do things after update
this.emit(cc.Director.EVENT_AFTER_UPDATE);
// Destroy entities that have been removed recently
Obj._deferredDestroy();
}
// Render
this.emit(cc.Director.EVENT_BEFORE_DRAW);
renderer.render(this._scene, this._deltaTime);
// After draw
this.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, paused
if (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 target
element = 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 loop
for (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 < 0
for(i=0,list=this._updatesNegList; i<list.length; ){
entry = list[i];
if(entry.markedForDeletion)
this._removeUpdateFromHash(entry);
else
i++;
}
for(i=0, list=this._updates0List; i<list.length; ){
entry = list[i];
if (entry.markedForDeletion)
this._removeUpdateFromHash(entry);
else
i++;
}
for(i=0, list=this._updatesPosList; i<list.length; ){
entry = list[i];
if (entry.markedForDeletion)
this._removeUpdateFromHash(entry);
else
i++;
}
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 usage
if (this._elapsed >= this._interval) {
this.trigger();
this._elapsed = 0;
}
} else {//advanced usage
if (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 () {
//override
this._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 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1725779708a1042123.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论