/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* @module cc
*/
const js = require('./platform/js');
const IdGenerater = require('./platform/id-generater');
const MAX_POOL_SIZE = 20;
var idGenerater = new IdGenerater('Scheduler');
//data structures
/*
* A list double-linked list used for "updates with priority"
* @class ListEntry
* @param {Object} target not retained (retained by hashUpdateEntry)
* @param {Number} priority
* @param {Boolean} paused
* @param {Boolean} markedForDeletion selector will no longer be called and entry will be removed at end of the next tick
*/
var ListEntry = function (target, priority, paused, markedForDeletion) {
this.target = target;
this.priority = priority;
this.paused = paused;
this.markedForDeletion = markedForDeletion;
};
var _listEntries = [];
ListEntry.get = function (target, priority, paused, markedForDeletion) {
var result = _listEntries.pop();
if (result) {
result.target = target;
result.priority = priority;
result.paused = paused;
result.markedForDeletion = markedForDeletion;
}
else {
result = new ListEntry(target, priority, paused, markedForDeletion);
}
return result;
};
ListEntry.put = function (entry) {
if (_listEntries.length < MAX_POOL_SIZE) {
entry.target = null;
_listEntries.push(entry);
}
};
/*
* A update entry list
* @class HashUpdateEntry
* @param {Array} list Which list does it belong to ?
* @param {ListEntry} entry entry in the list
* @param {Object} target hash key (retained)
* @param {function} callback
*/
var HashUpdateEntry = function (list, entry, target, callback) {
this.list = list;
this.entry = entry;
this.target = target;
this.callback = callback;
};
var _hashUpdateEntries = [];
HashUpdateEntry.get = function (list, entry, target, callback) {
var result = _hashUpdateEntries.pop();
if (result) {
result.list = list;
result.entry = entry;
result.target = target;
result.callback = callback;
}
else {
result = new HashUpdateEntry(list, entry, target, callback);
}
return result;
};
HashUpdateEntry.put = function (entry) {
if (_hashUpdateEntries.length < MAX_POOL_SIZE) {
entry.list = entry.entry = entry.target = entry.callback = null;
_hashUpdateEntries.push(entry);
}
};
//
/*
* Hash Element used for "selectors with interval"
* @class HashTimerEntry
* @param {Array} timers
* @param {Object} target hash key (retained)
* @param {Number} timerIndex
* @param {Timer} currentTimer
* @param {Boolean} currentTimerSalvaged
* @param {Boolean} paused
*/
var HashTimerEntry = function (timers, target, timerIndex, currentTimer, currentTimerSalvaged, paused) {
var _t = this;
_t.timers = timers;
_t.target = target;
_t.timerIndex = timerIndex;
_t.currentTimer = currentTimer;
_t.currentTimerSalvaged = currentTimerSalvaged;
_t.paused = paused;
};
var _hashTimerEntries = [];
HashTimerEntry.get = function (timers, target, timerIndex, currentTimer, currentTimerSalvaged, paused) {
var result = _hashTimerEntries.pop();
if (result) {
result.timers = timers;
result.target = target;
result.timerIndex = timerIndex;
result.currentTimer = currentTimer;
result.currentTimerSalvaged = currentTimerSalvaged;
result.paused = paused;
}
else {
result = new HashTimerEntry(timers, target, timerIndex, currentTimer, currentTimerSalvaged, paused);
}
return result;
};
HashTimerEntry.put = function (entry) {
if (_hashTimerEntries.length < MAX_POOL_SIZE) {
entry.timers = entry.target = entry.currentTimer = null;
_hashTimerEntries.push(entry);
}
};
/*
* Light weight timer
* @extends cc.Class
*/
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);
}
};
/**
* !#en
* Scheduler is responsible of triggering the scheduled callbacks.
* You should not use NSTimer. Instead use this class.
*
* There are 2 different types of callbacks (selectors):
* - update callback: the 'update' callback will be called every frame. You can customize the priority.
* - custom callback: A custom callback will be called every frame, or with a custom interval of time
*
* The 'custom selectors' should be avoided when possible. It is faster,
* and consumes less memory to use the 'update callback'. *
* !#zh
* Scheduler 是负责触发回调函数的类。
* 通常情况下,建议使用 cc.director.getScheduler() 来获取系统定时器。
* 有两种不同类型的定时器:
* - update 定时器:每一帧都会触发。您可以自定义优先级。
* - 自定义定时器:自定义定时器可以每一帧或者自定义的时间间隔触发。
* 如果希望每帧都触发,应该使用 update 定时器,使用 update 定时器更快,而且消耗更少的内存。
*
* @class Scheduler
*/
cc.Scheduler = function () {
this._timeScale = 1.0;
this._updatesNegList = []; // list of priority < 0
this._updates0List = []; // list of priority == 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
};
cc.Scheduler.prototype = {
constructor: cc.Scheduler,
//-----------------------private method----------------------
_removeHashElement: function (element) {
delete this._hashForTimers[element.target._id];
var arr = this._arrayForTimers;
for (var i = 0, l = arr.length; i < l; i++) {
if (arr[i] === element) {
arr.splice(i, 1);
break;
}
}
HashTimerEntry.put(element);
},
_removeUpdateFromHash: function (entry) {
var targetId = entry.target._id;
var self = this, element = self._hashForUpdates[targetId];
if (element) {
// Remove list entry from list
var list = element.list, listEntry = element.entry;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i] === listEntry) {
list.splice(i, 1);
break;
}
}
delete self._hashForUpdates[targetId];
ListEntry.put(listEntry);
HashUpdateEntry.put(element);
}
},
_priorityIn: function (ppList, listElement, priority) {
for (var i = 0; i < ppList.length; i++){
if (priority < ppList[i].priority) {
ppList.splice(i, 0, listElement);
return;
}
}
ppList.push(listElement);
},
_appendIn: function (ppList, listElement) {
ppList.push(listElement);
},
//-----------------------public method-------------------------
/**
* !#en This method should be called for any target which needs to schedule tasks, and this method should be called before any scheduler API usage.
* This method will add a `_id` property if it doesn't exist.
* !#zh 任何需要用 Scheduler 管理任务的对象主体都应该调用这个方法,并且应该在调用任何 Scheduler API 之前调用这个方法。
* 这个方法会给对象添加一个 `_id` 属性,如果这个属性不存在的话。
* @method enableForTarget
* @param {Object} target
*/
enableForTarget: function (target) {
if (!target._id) {
if (target.__instanceId) {
cc.warnID(1513);
}
else {
target._id = idGenerater.getNewId();
}
}
},
/**
* !#en
* Modifies the time of all scheduled callbacks.
* You can use this property to create a 'slow motion' or 'fast forward' effect.
* Default is 1.0. To create a 'slow motion' effect, use values below 1.0.
* To create a 'fast forward' effect, use values higher than 1.0.
* Note:It will affect EVERY scheduled selector / action.
* !#zh
* 设置时间间隔的缩放比例。
* 您可以使用这个方法来创建一个 “slow motion(慢动作)” 或 “fast forward(快进)” 的效果。
* 默认是 1.0。要创建一个 “slow motion(慢动作)” 效果,使用值低于 1.0。
* 要使用 “fast forward(快进)” 效果,使用值大于 1.0。
* 注意:它影响该 Scheduler 下管理的所有定时器。
* @method setTimeScale
* @param {Number} timeScale
*/
setTimeScale: function (timeScale) {
this._timeScale = timeScale;
},
/**
* !#en Returns time scale of scheduler.
* !#zh 获取时间间隔的缩放比例。
* @method getTimeScale
* @return {Number}
*/
getTimeScale: function () {
return this._timeScale;
},
/**
* !#en 'update' the scheduler. (You should NEVER call this method, unless you know what you are doing.)
* !#zh update 调度函数。(不应该直接调用这个方法,除非完全了解这么做的结果)
* @method update
* @param {Number} dt delta time
*/
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
* If paused is YES, then it won't be called until it is resumed.
* If 'interval' is 0, it will be called every frame, but if so, it recommended to use 'scheduleUpdateForTarget:' instead.
* If the callback function is already scheduled, then only the interval parameter will be updated without re-scheduling it again.
* repeat let the action be repeated repeat + 1 times, use cc.macro.REPEAT_FOREVER to let the action run continuously
* delay is the amount of time the action will wait before it'll start. Unit: s.
*