hypertimer
Advanced tools
Comparing version 0.0.1 to 0.1.0
{ | ||
"name": "hypertimer", | ||
"version": "0.0.1", | ||
"description": "A timer running faster or slower than realtime, in continuous or discrete time.", | ||
"version": "0.1.0", | ||
"description": "A timer running faster or slower than real-time, in continuous or discrete time.", | ||
"homepage": "https://github.com/enmasseio/hypertimer", | ||
@@ -13,4 +13,2 @@ "repository": { | ||
"coverage", | ||
"lib", | ||
"test", | ||
".idea", | ||
@@ -17,0 +15,0 @@ "gulpfile.js", |
@@ -5,7 +5,7 @@ /** | ||
* | ||
* A timer running faster or slower than realtime, in continuous or discrete | ||
* time. | ||
* A timer running faster or slower than real-time, and in continuous or | ||
* discrete time. | ||
* | ||
* @version 0.0.1 | ||
* @date 2014-07-11 | ||
* @version 0.1.0 | ||
* @date 2014-07-15 | ||
* | ||
@@ -27,3 +27,2 @@ * @license | ||
*/ | ||
(function webpackUniversalModuleDefinition(root, factory) { | ||
@@ -92,13 +91,451 @@ if(typeof exports === 'object' && typeof module === 'object') | ||
var util = __webpack_require__(2); | ||
// enum for type of timeout | ||
var TYPE = { | ||
TIMEOUT: 0, | ||
INTERVAL: 1, | ||
TRIGGER: 2 | ||
}; | ||
/** | ||
* Create a hypertimer | ||
* @param {Object} options | ||
* Create a new hypertimer | ||
* @param {Object} [options] The following options are available: | ||
* rate: number The rate of speed of hyper time with | ||
* respect to real-time in milliseconds | ||
* per millisecond. By default, rate | ||
* is 1. Note that rate can even be a | ||
* negative number. | ||
*/ | ||
module.exports = function hypertimer (options) { | ||
throw new Error('Not yet implemented, coming soon...'); | ||
}; | ||
function hypertimer(options) { | ||
// options | ||
var rate = 1; // number of milliseconds per milliseconds | ||
// properties | ||
var running = false; // true when running | ||
var realTime = null; // timestamp. the moment in real-time when hyperTime was set | ||
var hyperTime = null; // timestamp. the start time in hyper-time | ||
var timeouts = []; // array with all running timeouts | ||
var timeoutId = null; // currently running timer | ||
var idSeq = 0; // counter for unique timeout id's | ||
// exported timer object with public functions and variables | ||
var timer = {}; | ||
/** | ||
* Change configuration options of the hypertimer, or retrieve current | ||
* configuration. | ||
* @param {Object} [options] The following options are available: | ||
* rate: number The rate (in milliseconds per | ||
* millisecond) at which the timer | ||
* runs, with respect to real-time | ||
* speed. By default, rate is 1. | ||
* Note that rate can even be a | ||
* negative number. | ||
* @return {Object} Returns the applied configuration | ||
*/ | ||
timer.config = function(options) { | ||
if (options) { | ||
if ('rate' in options) { | ||
var newRate = Number(options.rate); | ||
if (isNaN(newRate)) { | ||
throw new TypeError('rate must be a number'); | ||
} | ||
// TODO: add option rate='discrete' | ||
hyperTime = timer.now(); | ||
realTime = util.nowReal(); | ||
rate = newRate; | ||
} | ||
} | ||
// reschedule running timeouts | ||
_schedule(); | ||
// return a copy of the configuration options | ||
return { | ||
rate: rate | ||
}; | ||
}; | ||
/** | ||
* Set the time of the timer. To get the current time, use getTime() or now(). | ||
* @param {number | Date} time The time in hyper-time. | ||
*/ | ||
timer.setTime = function (time) { | ||
if (time instanceof Date) { | ||
hyperTime = time.valueOf(); | ||
} | ||
else { | ||
var newTime = Number(time); | ||
if (isNaN(newTime)) { | ||
throw new TypeError('time must be a Date or number'); | ||
} | ||
hyperTime = newTime; | ||
} | ||
// reschedule running timeouts | ||
_schedule(); | ||
}; | ||
/** | ||
* Returns the current time of the timer as a number. | ||
* See also getTime(). | ||
* @return {number} The time | ||
*/ | ||
timer.now = function () { | ||
if (running) { | ||
// TODO: implement performance.now() / process.hrtime(time) for high precision calculation of time interval | ||
var realInterval = util.nowReal() - realTime; | ||
var hyperInterval = realInterval * rate; | ||
return hyperTime + hyperInterval; | ||
} | ||
else { | ||
return hyperTime; | ||
} | ||
}; | ||
/** | ||
* Continue the timer. | ||
*/ | ||
timer['continue'] = function() { | ||
realTime = util.nowReal(); | ||
running = true; | ||
// reschedule running timeouts | ||
_schedule(); | ||
}; | ||
/** | ||
* Pause the timer. The timer can be continued again with `continue()` | ||
*/ | ||
timer.pause = function() { | ||
hyperTime = timer.now(); | ||
realTime = null; | ||
running = false; | ||
// reschedule running timeouts (pauses them) | ||
_schedule(); | ||
}; | ||
/** | ||
* Returns the current time of the timer as Date. | ||
* See also now(). | ||
* @return {Date} The time | ||
*/ | ||
// rename to getTime | ||
timer.getTime = function() { | ||
return new Date(timer.now()); | ||
}; | ||
/** | ||
* Get the value of the hypertimer. This function returns the result of getTime(). | ||
* @return {Date} current time | ||
*/ | ||
timer.valueOf = timer.getTime; | ||
/** | ||
* Return a string representation of the current hyper-time. | ||
* @returns {string} String representation | ||
*/ | ||
timer.toString = function () { | ||
return timer.getTime().toString(); | ||
}; | ||
/** | ||
* Set a timeout, which is triggered when the timeout occurs in hyper-time. | ||
* See also setTrigger. | ||
* @param {Function} callback Function executed when delay is exceeded. | ||
* @param {number} delay The delay in milliseconds. When the rate is | ||
* zero, or the delay is smaller or equal to | ||
* zero, the callback is triggered immediately. | ||
* @return {number} Returns a timeoutId which can be used to cancel the | ||
* timeout using clearTimeout(). | ||
*/ | ||
timer.setTimeout = function(callback, delay) { | ||
var id = idSeq++; | ||
var timestamp = timer.now() + delay; | ||
if (isNaN(timestamp)) { | ||
throw new TypeError('delay must be a number'); | ||
} | ||
// add a new timeout to the queue | ||
_queueTimeout({ | ||
id: id, | ||
type: TYPE.TIMEOUT, | ||
time: timestamp, | ||
callback: callback | ||
}); | ||
// reschedule the timeouts | ||
_schedule(); | ||
return id; | ||
}; | ||
/** | ||
* Set a trigger, which is triggered when the timeout occurs in hyper-time. | ||
* See also getTimeout. | ||
* @param {Function} callback Function executed when timeout occurs. | ||
* @param {Date | number} time An absolute moment in time (Date) when the | ||
* callback will be triggered. When the rate is | ||
* zero, or the date is a Date in the past, | ||
* the callback is triggered immediately. | ||
* @return {number} Returns a triggerId which can be used to cancel the | ||
* trigger using clearTrigger(). | ||
*/ | ||
timer.setTrigger = function (callback, time) { | ||
var id = idSeq++; | ||
var timestamp = Number(time); | ||
if (isNaN(timestamp)) { | ||
throw new TypeError('time must be a Date or number'); | ||
} | ||
// add a new timeout to the queue | ||
_queueTimeout({ | ||
id: id, | ||
type: TYPE.TRIGGER, | ||
time: timestamp, | ||
callback: callback | ||
}); | ||
// reschedule the timeouts | ||
_schedule(); | ||
return id; | ||
}; | ||
/** | ||
* Trigger a callback every interval. Optionally, a start date can be provided | ||
* to specify the first time the callback must be triggered. | ||
* See also setTimeout and setTrigger. | ||
* @param {Function} callback Function executed when delay is exceeded. | ||
* @param {number} interval Interval in milliseconds. When interval | ||
* is smaller than zero or is infinity, the | ||
* interval will be set to zero and triggered | ||
* with a maximum rate. | ||
* @param {Date | number} [firstTime] An absolute moment in time (Date) when the | ||
* callback will be triggered the first time. | ||
* By default, firstTime = now() + interval. | ||
* @return {number} Returns a intervalId which can be used to cancel the | ||
* trigger using clearInterval(). | ||
*/ | ||
timer.setInterval = function(callback, interval, firstTime) { | ||
var id = idSeq++; | ||
var _interval = Number(interval); | ||
if (isNaN(_interval)) { | ||
throw new TypeError('interval must be a number'); | ||
} | ||
if (_interval < 0 || !isFinite(_interval)) { | ||
_interval = 0; | ||
} | ||
var timestamp; | ||
if (firstTime != undefined) { | ||
timestamp = Number(firstTime); | ||
if (isNaN(timestamp)) { | ||
throw new TypeError('firstTime must be a Date or number'); | ||
} | ||
} | ||
else { | ||
// firstTime is undefined or null | ||
timestamp = (timer.now() + _interval); | ||
} | ||
// add a new timeout to the queue | ||
_queueTimeout({ | ||
id: id, | ||
type: TYPE.INTERVAL, | ||
time: timestamp, | ||
interval: _interval, | ||
//firstTime: timestamp, | ||
//occurrence: 0, | ||
callback: callback | ||
}); | ||
// reschedule the timeouts | ||
_schedule(); | ||
return id; | ||
}; | ||
/** | ||
* Cancel a timeout | ||
* @param {number} timeoutId The id of a timeout | ||
*/ | ||
timer.clearTimeout = function(timeoutId) { | ||
// find the timeout in the queue | ||
for (var i = 0; i < timeouts.length; i++) { | ||
if (timeouts[i].id === timeoutId) { | ||
// remove this timeout from the queue | ||
timeouts.splice(i, 1); | ||
// reschedule timeouts | ||
_schedule(); | ||
break; | ||
} | ||
} | ||
}; | ||
/** | ||
* Cancel a trigger | ||
* @param {number} triggerId The id of a trigger | ||
*/ | ||
timer.clearTrigger = timer.clearTimeout; | ||
timer.clearInterval = timer.clearTimeout; | ||
/** | ||
* Returns a list with the id's of all timeouts | ||
* @returns {number[]} Timeout id's | ||
*/ | ||
timer.list = function () { | ||
return timeouts.map(function (timeout) { | ||
return timeout.id; | ||
}); | ||
}; | ||
/** | ||
* Clear all timeouts | ||
*/ | ||
timer.clear = function () { | ||
// empty the queue | ||
timeouts = []; | ||
// reschedule | ||
_schedule(); | ||
}; | ||
/** | ||
* Add a timeout to the queue. After the queue has been changed, the queue | ||
* must be rescheduled by executing _reschedule() | ||
* @param {{type: number, time: number, callback: Function}} params | ||
* @private | ||
*/ | ||
function _queueTimeout(params) { | ||
// insert the new timeout at the right place in the array, sorted by time | ||
if (timeouts.length > 0) { | ||
var i = timeouts.length - 1; | ||
while (i >= 0 && timeouts[i].time > params.time) { | ||
i--; | ||
} | ||
// insert the new timeout in the queue. Note that the timeout is | ||
// inserted *after* existing timeouts with the exact *same* time, | ||
// so the order in which they are executed is deterministic | ||
timeouts.splice(i + 1, 0, params); | ||
} | ||
else { | ||
// queue is empty, append the new timeout | ||
timeouts.push(params); | ||
} | ||
} | ||
/** | ||
* Reschedule all queued timeouts | ||
* @private | ||
*/ | ||
function _schedule() { | ||
var timeout; | ||
var next = timeouts[0]; | ||
// cancel timer when running | ||
if (timeoutId) { | ||
clearTimeout(timeoutId); | ||
timeoutId = null; | ||
} | ||
if (running && next) { | ||
// schedule next timeout | ||
var time = next.time; | ||
var delay = time - timer.now(); | ||
var realDelay = delay / rate; | ||
function trigger() { | ||
// execute all expired timeouts | ||
var intervals = []; | ||
while (timeouts.length > 0 && | ||
((timeouts[0].time <= time) || !isFinite(timeouts[0].time))) { | ||
timeout = timeouts[0]; | ||
// execute the callback | ||
try { | ||
timeout.callback(); | ||
} catch (err) { | ||
// silently ignore errors thrown by the callback | ||
} | ||
// in case of an interval we have to reschedule on next cycle | ||
if (timeout.type === TYPE.INTERVAL && timeouts[0] === timeout) { | ||
// timeout is not removed inside the callback, reschedule it | ||
intervals.push(timeout); | ||
} | ||
timeouts.shift(); | ||
} | ||
// reschedule intervals | ||
for (var i = 0; i < intervals.length; i++) { | ||
timeout = intervals[i]; | ||
// FIXME: adding the interval each occurrence will give round-off errors. | ||
// however, when multliplying the firstTime with the number of occurrences, | ||
// we cannot easily swith the rate at any time. | ||
//timeout.occurrence++; | ||
//timeout.time = timeout.firstTime + timeout.interval * timeout.occurrence * (rate < 0 ? -1 : 1); | ||
timeout.time += timeout.interval * (rate < 0 ? -1 : 1); | ||
_queueTimeout(timeout); | ||
} | ||
// initialize next round of timeouts | ||
_schedule(); | ||
} | ||
timeoutId = setTimeout(trigger, realDelay); | ||
} | ||
} | ||
Object.defineProperty(timer, 'running', { | ||
get: function () { | ||
return running; | ||
} | ||
}); | ||
timer.config(options); // apply options | ||
timer.setTime(util.nowReal()); // set time as current real time | ||
timer.continue(); // start the timer | ||
return timer; | ||
} | ||
module.exports = hypertimer; | ||
/***/ }, | ||
/* 2 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/* istanbul ignore else */ | ||
if (typeof Date.now === 'function') { | ||
/** | ||
* Helper function to get the current time | ||
* @return {number} Current time | ||
*/ | ||
exports.nowReal = function () { | ||
return Date.now(); | ||
} | ||
} | ||
else { | ||
/** | ||
* Helper function to get the current time | ||
* @return {number} Current time | ||
*/ | ||
exports.nowReal = function () { | ||
return new Date().valueOf(); | ||
} | ||
} | ||
/***/ } | ||
/******/ ]) | ||
}) |
@@ -5,7 +5,7 @@ /** | ||
* | ||
* A timer running faster or slower than realtime, in continuous or discrete | ||
* time. | ||
* A timer running faster or slower than real-time, and in continuous or | ||
* discrete time. | ||
* | ||
* @version 0.0.1 | ||
* @date 2014-07-11 | ||
* @version 0.1.0 | ||
* @date 2014-07-15 | ||
* | ||
@@ -27,3 +27,3 @@ * @license | ||
*/ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):"object"==typeof exports?exports.hypertimer=t():e.hypertimer=t()}(this,function(){return function(e){function t(r){if(o[r])return o[r].exports;var n=o[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var o={};return t.m=e,t.c=o,t.p="",t(0)}([function(e,t,o){e.exports=o(1)},function(e){e.exports=function(){throw new Error("Not yet implemented, coming soon...")}}])}); | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):"object"==typeof exports?exports.hypertimer=t():e.hypertimer=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){function r(e){function t(e){if(c.length>0){for(var t=c.length-1;t>=0&&c[t].time>e.time;)t--;c.splice(t+1,0,e)}else c.push(e)}function n(){function e(){for(var e=[];c.length>0&&(c[0].time<=f||!isFinite(c[0].time));){i=c[0];try{i.callback()}catch(u){}i.type===o.INTERVAL&&c[0]===i&&e.push(i),c.shift()}for(var a=0;a<e.length;a++)i=e[a],i.time+=i.interval*(0>r?-1:1),t(i);n()}var i,a=c[0];if(l&&(clearTimeout(l),l=null),u&&a){var f=a.time,m=f-s.now(),p=m/r;l=setTimeout(e,p)}}var r=1,u=!1,a=null,f=null,c=[],l=null,m=0,s={};return s.config=function(e){if(e&&"rate"in e){var t=Number(e.rate);if(isNaN(t))throw new TypeError("rate must be a number");f=s.now(),a=i.nowReal(),r=t}return n(),{rate:r}},s.setTime=function(e){if(e instanceof Date)f=e.valueOf();else{var t=Number(e);if(isNaN(t))throw new TypeError("time must be a Date or number");f=t}n()},s.now=function(){if(u){var e=i.nowReal()-a,t=e*r;return f+t}return f},s["continue"]=function(){a=i.nowReal(),u=!0,n()},s.pause=function(){f=s.now(),a=null,u=!1,n()},s.getTime=function(){return new Date(s.now())},s.valueOf=s.getTime,s.toString=function(){return s.getTime().toString()},s.setTimeout=function(e,r){var i=m++,u=s.now()+r;if(isNaN(u))throw new TypeError("delay must be a number");return t({id:i,type:o.TIMEOUT,time:u,callback:e}),n(),i},s.setTrigger=function(e,r){var i=m++,u=Number(r);if(isNaN(u))throw new TypeError("time must be a Date or number");return t({id:i,type:o.TRIGGER,time:u,callback:e}),n(),i},s.setInterval=function(e,r,i){var u=m++,a=Number(r);if(isNaN(a))throw new TypeError("interval must be a number");(0>a||!isFinite(a))&&(a=0);var f;if(void 0!=i){if(f=Number(i),isNaN(f))throw new TypeError("firstTime must be a Date or number")}else f=s.now()+a;return t({id:u,type:o.INTERVAL,time:f,interval:a,callback:e}),n(),u},s.clearTimeout=function(e){for(var t=0;t<c.length;t++)if(c[t].id===e){c.splice(t,1),n();break}},s.clearTrigger=s.clearTimeout,s.clearInterval=s.clearTimeout,s.list=function(){return c.map(function(e){return e.id})},s.clear=function(){c=[],n()},Object.defineProperty(s,"running",{get:function(){return u}}),s.config(e),s.setTime(i.nowReal()),s.continue(),s}var i=n(2),o={TIMEOUT:0,INTERVAL:1,TRIGGER:2};e.exports=r},function(e,t){t.nowReal="function"==typeof Date.now?function(){return Date.now()}:function(){return(new Date).valueOf()}}])}); | ||
//# sourceMappingURL=hypertimer.map |
@@ -5,4 +5,4 @@ /** | ||
* | ||
* A timer running faster or slower than realtime, in continuous or discrete | ||
* time. | ||
* A timer running faster or slower than real-time, and in continuous or | ||
* discrete time. | ||
* | ||
@@ -26,2 +26,2 @@ * @version @@version | ||
* the License. | ||
*/ | ||
*/ |
@@ -0,7 +1,419 @@ | ||
var util = require('./util'); | ||
// enum for type of timeout | ||
var TYPE = { | ||
TIMEOUT: 0, | ||
INTERVAL: 1, | ||
TRIGGER: 2 | ||
}; | ||
/** | ||
* Create a hypertimer | ||
* @param {Object} options | ||
* Create a new hypertimer | ||
* @param {Object} [options] The following options are available: | ||
* rate: number The rate of speed of hyper time with | ||
* respect to real-time in milliseconds | ||
* per millisecond. By default, rate | ||
* is 1. Note that rate can even be a | ||
* negative number. | ||
*/ | ||
module.exports = function hypertimer (options) { | ||
throw new Error('Not yet implemented, coming soon...'); | ||
}; | ||
function hypertimer(options) { | ||
// options | ||
var rate = 1; // number of milliseconds per milliseconds | ||
// properties | ||
var running = false; // true when running | ||
var realTime = null; // timestamp. the moment in real-time when hyperTime was set | ||
var hyperTime = null; // timestamp. the start time in hyper-time | ||
var timeouts = []; // array with all running timeouts | ||
var timeoutId = null; // currently running timer | ||
var idSeq = 0; // counter for unique timeout id's | ||
// exported timer object with public functions and variables | ||
var timer = {}; | ||
/** | ||
* Change configuration options of the hypertimer, or retrieve current | ||
* configuration. | ||
* @param {Object} [options] The following options are available: | ||
* rate: number The rate (in milliseconds per | ||
* millisecond) at which the timer | ||
* runs, with respect to real-time | ||
* speed. By default, rate is 1. | ||
* Note that rate can even be a | ||
* negative number. | ||
* @return {Object} Returns the applied configuration | ||
*/ | ||
timer.config = function(options) { | ||
if (options) { | ||
if ('rate' in options) { | ||
var newRate = Number(options.rate); | ||
if (isNaN(newRate)) { | ||
throw new TypeError('rate must be a number'); | ||
} | ||
// TODO: add option rate='discrete' | ||
hyperTime = timer.now(); | ||
realTime = util.nowReal(); | ||
rate = newRate; | ||
} | ||
} | ||
// reschedule running timeouts | ||
_schedule(); | ||
// return a copy of the configuration options | ||
return { | ||
rate: rate | ||
}; | ||
}; | ||
/** | ||
* Set the time of the timer. To get the current time, use getTime() or now(). | ||
* @param {number | Date} time The time in hyper-time. | ||
*/ | ||
timer.setTime = function (time) { | ||
if (time instanceof Date) { | ||
hyperTime = time.valueOf(); | ||
} | ||
else { | ||
var newTime = Number(time); | ||
if (isNaN(newTime)) { | ||
throw new TypeError('time must be a Date or number'); | ||
} | ||
hyperTime = newTime; | ||
} | ||
// reschedule running timeouts | ||
_schedule(); | ||
}; | ||
/** | ||
* Returns the current time of the timer as a number. | ||
* See also getTime(). | ||
* @return {number} The time | ||
*/ | ||
timer.now = function () { | ||
if (running) { | ||
// TODO: implement performance.now() / process.hrtime(time) for high precision calculation of time interval | ||
var realInterval = util.nowReal() - realTime; | ||
var hyperInterval = realInterval * rate; | ||
return hyperTime + hyperInterval; | ||
} | ||
else { | ||
return hyperTime; | ||
} | ||
}; | ||
/** | ||
* Continue the timer. | ||
*/ | ||
timer['continue'] = function() { | ||
realTime = util.nowReal(); | ||
running = true; | ||
// reschedule running timeouts | ||
_schedule(); | ||
}; | ||
/** | ||
* Pause the timer. The timer can be continued again with `continue()` | ||
*/ | ||
timer.pause = function() { | ||
hyperTime = timer.now(); | ||
realTime = null; | ||
running = false; | ||
// reschedule running timeouts (pauses them) | ||
_schedule(); | ||
}; | ||
/** | ||
* Returns the current time of the timer as Date. | ||
* See also now(). | ||
* @return {Date} The time | ||
*/ | ||
// rename to getTime | ||
timer.getTime = function() { | ||
return new Date(timer.now()); | ||
}; | ||
/** | ||
* Get the value of the hypertimer. This function returns the result of getTime(). | ||
* @return {Date} current time | ||
*/ | ||
timer.valueOf = timer.getTime; | ||
/** | ||
* Return a string representation of the current hyper-time. | ||
* @returns {string} String representation | ||
*/ | ||
timer.toString = function () { | ||
return timer.getTime().toString(); | ||
}; | ||
/** | ||
* Set a timeout, which is triggered when the timeout occurs in hyper-time. | ||
* See also setTrigger. | ||
* @param {Function} callback Function executed when delay is exceeded. | ||
* @param {number} delay The delay in milliseconds. When the rate is | ||
* zero, or the delay is smaller or equal to | ||
* zero, the callback is triggered immediately. | ||
* @return {number} Returns a timeoutId which can be used to cancel the | ||
* timeout using clearTimeout(). | ||
*/ | ||
timer.setTimeout = function(callback, delay) { | ||
var id = idSeq++; | ||
var timestamp = timer.now() + delay; | ||
if (isNaN(timestamp)) { | ||
throw new TypeError('delay must be a number'); | ||
} | ||
// add a new timeout to the queue | ||
_queueTimeout({ | ||
id: id, | ||
type: TYPE.TIMEOUT, | ||
time: timestamp, | ||
callback: callback | ||
}); | ||
// reschedule the timeouts | ||
_schedule(); | ||
return id; | ||
}; | ||
/** | ||
* Set a trigger, which is triggered when the timeout occurs in hyper-time. | ||
* See also getTimeout. | ||
* @param {Function} callback Function executed when timeout occurs. | ||
* @param {Date | number} time An absolute moment in time (Date) when the | ||
* callback will be triggered. When the rate is | ||
* zero, or the date is a Date in the past, | ||
* the callback is triggered immediately. | ||
* @return {number} Returns a triggerId which can be used to cancel the | ||
* trigger using clearTrigger(). | ||
*/ | ||
timer.setTrigger = function (callback, time) { | ||
var id = idSeq++; | ||
var timestamp = Number(time); | ||
if (isNaN(timestamp)) { | ||
throw new TypeError('time must be a Date or number'); | ||
} | ||
// add a new timeout to the queue | ||
_queueTimeout({ | ||
id: id, | ||
type: TYPE.TRIGGER, | ||
time: timestamp, | ||
callback: callback | ||
}); | ||
// reschedule the timeouts | ||
_schedule(); | ||
return id; | ||
}; | ||
/** | ||
* Trigger a callback every interval. Optionally, a start date can be provided | ||
* to specify the first time the callback must be triggered. | ||
* See also setTimeout and setTrigger. | ||
* @param {Function} callback Function executed when delay is exceeded. | ||
* @param {number} interval Interval in milliseconds. When interval | ||
* is smaller than zero or is infinity, the | ||
* interval will be set to zero and triggered | ||
* with a maximum rate. | ||
* @param {Date | number} [firstTime] An absolute moment in time (Date) when the | ||
* callback will be triggered the first time. | ||
* By default, firstTime = now() + interval. | ||
* @return {number} Returns a intervalId which can be used to cancel the | ||
* trigger using clearInterval(). | ||
*/ | ||
timer.setInterval = function(callback, interval, firstTime) { | ||
var id = idSeq++; | ||
var _interval = Number(interval); | ||
if (isNaN(_interval)) { | ||
throw new TypeError('interval must be a number'); | ||
} | ||
if (_interval < 0 || !isFinite(_interval)) { | ||
_interval = 0; | ||
} | ||
var timestamp; | ||
if (firstTime != undefined) { | ||
timestamp = Number(firstTime); | ||
if (isNaN(timestamp)) { | ||
throw new TypeError('firstTime must be a Date or number'); | ||
} | ||
} | ||
else { | ||
// firstTime is undefined or null | ||
timestamp = (timer.now() + _interval); | ||
} | ||
// add a new timeout to the queue | ||
_queueTimeout({ | ||
id: id, | ||
type: TYPE.INTERVAL, | ||
time: timestamp, | ||
interval: _interval, | ||
//firstTime: timestamp, | ||
//occurrence: 0, | ||
callback: callback | ||
}); | ||
// reschedule the timeouts | ||
_schedule(); | ||
return id; | ||
}; | ||
/** | ||
* Cancel a timeout | ||
* @param {number} timeoutId The id of a timeout | ||
*/ | ||
timer.clearTimeout = function(timeoutId) { | ||
// find the timeout in the queue | ||
for (var i = 0; i < timeouts.length; i++) { | ||
if (timeouts[i].id === timeoutId) { | ||
// remove this timeout from the queue | ||
timeouts.splice(i, 1); | ||
// reschedule timeouts | ||
_schedule(); | ||
break; | ||
} | ||
} | ||
}; | ||
/** | ||
* Cancel a trigger | ||
* @param {number} triggerId The id of a trigger | ||
*/ | ||
timer.clearTrigger = timer.clearTimeout; | ||
timer.clearInterval = timer.clearTimeout; | ||
/** | ||
* Returns a list with the id's of all timeouts | ||
* @returns {number[]} Timeout id's | ||
*/ | ||
timer.list = function () { | ||
return timeouts.map(function (timeout) { | ||
return timeout.id; | ||
}); | ||
}; | ||
/** | ||
* Clear all timeouts | ||
*/ | ||
timer.clear = function () { | ||
// empty the queue | ||
timeouts = []; | ||
// reschedule | ||
_schedule(); | ||
}; | ||
/** | ||
* Add a timeout to the queue. After the queue has been changed, the queue | ||
* must be rescheduled by executing _reschedule() | ||
* @param {{type: number, time: number, callback: Function}} params | ||
* @private | ||
*/ | ||
function _queueTimeout(params) { | ||
// insert the new timeout at the right place in the array, sorted by time | ||
if (timeouts.length > 0) { | ||
var i = timeouts.length - 1; | ||
while (i >= 0 && timeouts[i].time > params.time) { | ||
i--; | ||
} | ||
// insert the new timeout in the queue. Note that the timeout is | ||
// inserted *after* existing timeouts with the exact *same* time, | ||
// so the order in which they are executed is deterministic | ||
timeouts.splice(i + 1, 0, params); | ||
} | ||
else { | ||
// queue is empty, append the new timeout | ||
timeouts.push(params); | ||
} | ||
} | ||
/** | ||
* Reschedule all queued timeouts | ||
* @private | ||
*/ | ||
function _schedule() { | ||
var timeout; | ||
var next = timeouts[0]; | ||
// cancel timer when running | ||
if (timeoutId) { | ||
clearTimeout(timeoutId); | ||
timeoutId = null; | ||
} | ||
if (running && next) { | ||
// schedule next timeout | ||
var time = next.time; | ||
var delay = time - timer.now(); | ||
var realDelay = delay / rate; | ||
function trigger() { | ||
// execute all expired timeouts | ||
var intervals = []; | ||
while (timeouts.length > 0 && | ||
((timeouts[0].time <= time) || !isFinite(timeouts[0].time))) { | ||
timeout = timeouts[0]; | ||
// execute the callback | ||
try { | ||
timeout.callback(); | ||
} catch (err) { | ||
// silently ignore errors thrown by the callback | ||
} | ||
// in case of an interval we have to reschedule on next cycle | ||
if (timeout.type === TYPE.INTERVAL && timeouts[0] === timeout) { | ||
// timeout is not removed inside the callback, reschedule it | ||
intervals.push(timeout); | ||
} | ||
timeouts.shift(); | ||
} | ||
// reschedule intervals | ||
for (var i = 0; i < intervals.length; i++) { | ||
timeout = intervals[i]; | ||
// FIXME: adding the interval each occurrence will give round-off errors. | ||
// however, when multliplying the firstTime with the number of occurrences, | ||
// we cannot easily swith the rate at any time. | ||
//timeout.occurrence++; | ||
//timeout.time = timeout.firstTime + timeout.interval * timeout.occurrence * (rate < 0 ? -1 : 1); | ||
timeout.time += timeout.interval * (rate < 0 ? -1 : 1); | ||
_queueTimeout(timeout); | ||
} | ||
// initialize next round of timeouts | ||
_schedule(); | ||
} | ||
timeoutId = setTimeout(trigger, realDelay); | ||
} | ||
} | ||
Object.defineProperty(timer, 'running', { | ||
get: function () { | ||
return running; | ||
} | ||
}); | ||
timer.config(options); // apply options | ||
timer.setTime(util.nowReal()); // set time as current real time | ||
timer.continue(); // start the timer | ||
return timer; | ||
} | ||
module.exports = hypertimer; |
{ | ||
"name": "hypertimer", | ||
"version": "0.0.1", | ||
"description": "A timer running faster or slower than realtime, in continuous or discrete time", | ||
"version": "0.1.0", | ||
"description": "A timer running faster or slower than real-time, in continuous or discrete time", | ||
"author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)", | ||
@@ -10,6 +10,9 @@ "main": "./index", | ||
"time", | ||
"realtime", | ||
"simulation", | ||
"hypertime", | ||
"hyper-time", | ||
"real-time", | ||
"continuous", | ||
"discrete" | ||
"discrete", | ||
"event" | ||
], | ||
@@ -22,2 +25,3 @@ "repository": { | ||
"devDependencies": { | ||
"async": "^0.9.0", | ||
"browserify": "^4.2.0", | ||
@@ -24,0 +28,0 @@ "gulp": "^3.8.6", |
180
README.md
hypertimer | ||
========== | ||
A timer running faster or slower than realtime, in continuous or discrete time. | ||
Hypertimer is a timer running in [hypertime](http://en.wikipedia.org/wiki/Hypertime), at a different rate than [real-time](http://en.wikipedia.org/wiki/Real-time_clock), or in discrete time. Hypertimer is useful for controlling the time in simulations. | ||
Hypertimer offers basic functionality to get the time and set timeouts: | ||
- `getTime()`, `setTime()`, and `now()`, to get and set the time of the timer. | ||
- `setTimeout()`, `setInterval()`, and `setTrigger()` to set timeouts. | ||
These functions are similar to and compatible with JavaScripts built-in functions `Date.now()`, | ||
`setTimeout()`, and `setInterval()`, but there is an important difference: they can run with a different current time and at a different rate. | ||
## Install | ||
@@ -12,9 +21,176 @@ | ||
Install via bower: | ||
bower install hypertimer | ||
## Load | ||
### node.js | ||
```js | ||
var hypertimer = require('hypertimer'); | ||
var timer = hypertimer({rate: 1}); | ||
timer.setTime(new Date(2014, 0, 1)); | ||
``` | ||
### browser | ||
```html | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<script src="./dist/hypertimer.min.js"></script> | ||
</head> | ||
<body> | ||
<script> | ||
var timer = hypertimer({rate: 1}); | ||
timer.setTime(new Date(2014, 0, 1)); | ||
</script> | ||
</body> | ||
</html> | ||
``` | ||
## Use | ||
coming soon... | ||
The following example shows how to create a hypertimer and set a timeout. | ||
```js | ||
// create a hypertimer running ten times faster than real-time | ||
var timer = hypertimer({rate: 10}); | ||
// start the timer at 1st of January 2020 | ||
timer.setTime(new Date(2020, 0, 1, 12, 0, 0)); | ||
// set a timeout after a delay | ||
var delay = 10000; // milliseconds (hyper-time) | ||
timer.setTimeout(function () { | ||
console.log('Timeout!'); | ||
console.log('It is now ' + delay + ' ms later in hyper-time, ' + | ||
'the time is: ' + timer.getTime()); | ||
}, delay); | ||
console.log('The time is: ' + timer.getTime()); | ||
``` | ||
## API | ||
### Construction | ||
A hypertimer is constructed as: | ||
```js | ||
hypertimer([options]) | ||
``` | ||
By default, a new hypertimer runs with real-time speed and time. | ||
Available options: | ||
Name | Type | Default | Description | ||
---- | ------ | ------- | ----------- | ||
rate | Number | 1 | The rate (in milliseconds per millisecond) at which the timer runs, with respect to real-time speed. Can be a positive or negative value. | ||
Example: | ||
```js | ||
var timer = hypertimer({rate: 10}); | ||
``` | ||
### Properties | ||
- `running` | ||
True when the timer is running, false when paused. See also functions `pause()` and `continue()`. | ||
### Methods | ||
- **`clear()`** | ||
Clear all running timeouts. | ||
- **`clearTimeout(timeoutId: number)`** | ||
Cancel a timeout. | ||
- **`clearInterval(intervalId: number)`** | ||
Cancel an interval. | ||
- **`clearTrigger(triggerId: number)`** | ||
Cancel a trigger. | ||
- **`config([options: Object]): Object`** | ||
Change the configuration options of the hypertimer, and/or retrieve the current configuration. Available options: | ||
- `rate: Number` | ||
The rate (in milliseconds per millisecond) at which the timer runs, with respect to real-time speed. By default, rate is 1. Note that rate can even be a negative number. | ||
- **`continue()`** | ||
Continue the timer when paused. The state of the timer can be retrieved via the property `running`. See also `pause()`. | ||
- **`getTime(): Date`** | ||
Returns the current time of the timer as Date. See also `now()`. | ||
The time can be set using `setTime(time)`. | ||
- **`list()`** | ||
Returns a list with the id's of all running timeouts. | ||
- **`now() : number`** | ||
Returns the current time of the timer as a number. See also `getTime()`. | ||
- **`pause()`** | ||
Pause the timer. The state of the timer can be retrieved via the property `running`. See also `continue()`. | ||
- **`setInterval(callback: Function, interval: number [, firstTime: Date | number])`** | ||
Trigger a callback every interval. Optionally, a start date can be provided | ||
to specify the first time the callback must be triggered. The function returns an intervalId which can be used to cancel the trigger using `clearInterval()`. See also `setTimeout` and `setTrigger`. Parameters: | ||
- `callback: Function` | ||
Function executed when delay is exceeded. | ||
- `interval: number` | ||
Interval in milliseconds. When interval is smaller than zero or is infinity, the interval will be set to zero and triggered with a maximum rate. | ||
- `[firstTime: Date | number]` | ||
An optional absolute moment in time (Date) when the callback will be triggered the first time. By default, `firstTime = now() + interval`. | ||
- **`setTime(time: number | Date)`** | ||
Set the current time of the timer. Can be a Date or a timestamp. To get the current time, use `getTime()` or `now()`. | ||
- **`setTimeout(callback: Function, delay: number) : number`** | ||
Set a timeout, which is triggered when the timeout occurs in hyper-time. See also `setTrigger` and `setInterval`. The function returns a timeoutId, which can be used to cancel the timeout using `clearTimeout(timeoutId)`. The parameters are: | ||
- `callback: Function` | ||
Function executed when the delay is exceeded | ||
- `delay: number` | ||
The delay in milliseconds. When the rate is zero, or the delay is smaller or equal to zero, the callback is triggered immediately. | ||
- **`setTrigger(callback: Function, time: Date | number) : number`** | ||
Set a trigger, which is triggered when the timeout occurs in hyper-time. See also `getTimeout`. The function returns a triggerId which can be used to cancel the trigger using `clearTrigger()`. The parameters are: | ||
- `callback: Function` | ||
Function executed when delay is exceeded. | ||
- `time: Date | number` | ||
An absolute moment in time (Date) when the callback will be triggered. When the rate is zero, or the date is a Date in the past, the callback is triggered immediately. | ||
- **`toString() : String`** | ||
Return a string representation of the current hyper-time, equal to `timer.getTime().toString()`. | ||
- **`valueOf() : Date`** | ||
Get the value of the hypertimer, returns the current time of the timer as Date. | ||
## Examples | ||
Examples can be found here: | ||
https://github.com/enmasseio/hypertimer/tree/master/examples | ||
## Roadmap | ||
- Implement support for discrete time (discrete, deterministic events). | ||
- Implement a scalable solution to synchronize hypertimers running in different | ||
processes. | ||
## Build | ||
@@ -21,0 +197,0 @@ |
@@ -0,5 +1,842 @@ | ||
// NOTE: all timeouts should have time differences of at least 50ms to be | ||
// safely distinguishably | ||
var assert = require('assert'); | ||
var async = require('async'); | ||
var hypertimer = require('../lib/hypertimer'); | ||
/** | ||
* Assert whether two dates are approximately equal. | ||
* Throws an assertion error when the dates are not approximately equal. | ||
* @param {Date} date1 | ||
* @param {Date} date2 | ||
* @param {Number} [epsilon=25] maximum difference in milliseconds | ||
*/ | ||
function approx(date1, date2, epsilon) { | ||
assert(Math.abs(date1 - date2) < (epsilon === undefined ? 25 : epsilon), | ||
date1.toISOString() + ' ~= ' + date2.toISOString()); | ||
} | ||
describe('approx', function () { | ||
it ('should compare two dates', function () { | ||
var a = new Date(); | ||
var b = new Date(a.valueOf() + 0); | ||
var c = new Date(a.valueOf() + 30); | ||
var d = new Date(a.valueOf() + 300); | ||
approx(a, b); | ||
assert.throws(function() {approx(a, c)}); | ||
approx(a, c, 100); | ||
assert.throws(function() {approx(a, d, 100)}); | ||
}); | ||
}); | ||
describe('hypertimer', function () { | ||
describe('config', function () { | ||
it('should get configuration', function () { | ||
var timer = hypertimer(); | ||
assert.deepEqual(timer.config(), {rate: 1}); | ||
}); | ||
it('should set configuration', function () { | ||
var timer = hypertimer(); | ||
assert.equal(timer.config().rate, 1); | ||
timer.config({rate: 10}); | ||
assert.equal(timer.config().rate, 10); | ||
}); | ||
it('should set empty configuration', function () { | ||
var timer = hypertimer(); | ||
assert.equal(timer.config().rate, 1); | ||
timer.config({}); | ||
assert.equal(timer.config().rate, 1); | ||
}); | ||
it('should set configuration on creation', function () { | ||
var timer = hypertimer({rate: 10}); | ||
assert.equal(timer.config().rate, 10); | ||
}); | ||
it('should update configuration', function () { | ||
var timer = hypertimer({rate: 1}); | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,0)); | ||
timer.config({rate: 10}); | ||
assert.equal(timer.config().rate, 10); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,0)); | ||
}); | ||
it('should throw an error on invalid rate', function () { | ||
assert.throws(function () { | ||
hypertimer({rate: 'bla'}); | ||
}, /TypeError: rate must be a number/); | ||
}); | ||
}); | ||
describe('get/set time', function () { | ||
it ('should set the current hyper-time from a Date', function () { | ||
var timer = hypertimer({rate: 1}); | ||
timer.setTime(new Date(2050, 0, 1)); | ||
approx(timer.getTime(), new Date(2050, 0, 1)); | ||
}); | ||
it ('should set the current hyper-time from a number', function () { | ||
var timer = hypertimer({rate: 1}); | ||
timer.setTime(new Date(2050, 0, 1).valueOf()); | ||
approx(timer.getTime(), new Date(2050, 0, 1)); | ||
}); | ||
it ('should throw an error in case of invalid variable', function () { | ||
var timer = hypertimer({rate: 1}); | ||
assert.throws(function () {timer.setTime('bla')}, /time must be a Date or number/); | ||
assert.throws(function () {timer.setTime({})}, /time must be a Date or number/); | ||
}); | ||
it ('should get the current hyper-time as Date', function () { | ||
var timer = hypertimer({rate: 1}); | ||
assert(timer.getTime() instanceof Date, 'must return a Date'); | ||
approx(timer.getTime(), new Date()); | ||
}); | ||
it ('should get the current hyper-time as number', function () { | ||
var timer = hypertimer({rate: 1}); | ||
assert(typeof timer.now(), 'number'); | ||
approx(new Date(timer.now()), new Date()); | ||
}); | ||
}); | ||
describe('run', function () { | ||
it('should start running by default', function () { | ||
var timer = hypertimer({rate: 1}); | ||
assert.equal(timer.running, true); | ||
}); | ||
it('should test whether running', function () { | ||
var timer = hypertimer({rate: 1}); | ||
assert.equal(timer.running, true); | ||
timer.pause(); | ||
assert.equal(timer.running, false); | ||
timer.continue(); | ||
assert.equal(timer.running, true); | ||
}); | ||
it('start should continue where it was left off', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
timer.pause(); | ||
var a = timer.getTime(); | ||
approx(timer.getTime(), new Date()); | ||
setTimeout(function () { | ||
timer.continue(); // continue | ||
approx(timer.getTime(), a); | ||
setTimeout(function () { | ||
approx(timer.getTime(), new Date(a.valueOf() + 100)); | ||
done(); | ||
}, 100); | ||
}, 100); | ||
}); | ||
it('should set a new time via set', function () { | ||
var d = new Date(2050,0,1); | ||
var timer = hypertimer({rate: 1}); | ||
approx(timer.getTime(), new Date()); | ||
timer.setTime(d); | ||
approx(timer.getTime(), d); | ||
}); | ||
it('time should not change when paused', function (done) { | ||
var d = new Date(2050,0,1); | ||
var timer = hypertimer({rate: 1}); | ||
timer.setTime(d); | ||
approx(timer.getTime(), d); | ||
timer.pause(); | ||
approx(timer.getTime(), d); | ||
setTimeout(function () { | ||
approx(timer.getTime(), d); | ||
done(); | ||
}, 100); | ||
}); | ||
it('should run hyper-time with the configured rate', function (done) { | ||
// To keep the test fast, don't see a rate too large, else you have to | ||
// increase the delay to compensate for possible round-off errors and | ||
// inaccuracy of the real-time. | ||
var rates = [1, 2, 1/2, -1, -2, 0]; | ||
var epsilon = 20; | ||
async.map(rates, function (rate, cb) { | ||
var timer = hypertimer({rate: rate}); | ||
var started = new Date(); | ||
approx(timer.getTime(), started); | ||
var delay = 200; | ||
setTimeout(function () { | ||
approx(timer.getTime(), new Date(started.valueOf() + delay * rate), epsilon); | ||
cb(); | ||
}, delay); | ||
}, done); | ||
}); | ||
}); | ||
describe('timeout', function () { | ||
it('should set a timeout with rate=1', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
done(); | ||
}, 100); | ||
}); | ||
it('should set a timeout with rate=2', function (done) { | ||
var timer = hypertimer({rate: 2}); | ||
var start = new Date(); | ||
timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
done(); | ||
}, 200); | ||
}); | ||
it('should set a timeout with rate=1/2', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 200)); | ||
done(); | ||
}, 100); | ||
}); | ||
it('should set a timeout with a delay in the past', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTimeout(function () { | ||
approx(new Date(), start); | ||
done(); | ||
}, -10); | ||
}); | ||
it('should set a timeout with an infinite delay', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTimeout(function () { | ||
approx(new Date(), start); | ||
done(); | ||
}, Infinity); | ||
}); | ||
it('should execute multiple timeouts in the right order', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
var log = []; | ||
timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 200)); | ||
log.push('B'); | ||
assert.deepEqual(log, ['A', 'B']); | ||
}, 100); | ||
timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 300)); | ||
log.push('C'); | ||
assert.deepEqual(log, ['A', 'B', 'C']); | ||
done(); | ||
}, 150); | ||
timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
log.push('A'); | ||
assert.deepEqual(log, ['A']); | ||
}, 50); | ||
}); | ||
it('should pause a timeout when the timer is paused', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
var start = new Date(); | ||
var log = []; | ||
timer.setTimeout(function () { | ||
assert.deepEqual(log, ['A', 'B']); | ||
approx(new Date(), new Date(start.valueOf() + 400)); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,100)); | ||
done(); | ||
}, 100); | ||
// real-time timeout | ||
setTimeout(function () { | ||
log.push('A'); | ||
timer.pause(); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
setTimeout(function () { | ||
log.push('B'); | ||
timer.continue(); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
approx(new Date(), new Date(start.valueOf() + 300)); | ||
}, 200); | ||
}, 100); | ||
}); | ||
it('should adjust a timeout when the timers time is adjusted', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,200)); | ||
done(); | ||
}, 200); | ||
timer.setTime(new Date(2050,0,1,12,0,0,100)); | ||
}); | ||
it('should adjust a timeout when the timers rate is adjusted', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
var log = []; | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
timer.setTimeout(function () { | ||
assert.deepEqual(log, ['A']); | ||
approx(new Date(), new Date(start.valueOf() + 150)); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,100)); | ||
done(); | ||
}, 100); | ||
setTimeout(function () { | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
timer.config({rate: 1}); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
log.push('A'); | ||
}, 100) | ||
}); | ||
it('should cancel a timeout with clearTimeout', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var log = []; | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
var timeout1 = timer.setTimeout(function () { | ||
log.push('1'); | ||
}, 100); | ||
var timeout2 = timer.setTimeout(function () { | ||
log.push('2'); | ||
assert(false, 'should not trigger timeout1') | ||
}, 150); | ||
var timeout3 = timer.setTimeout(function () { | ||
log.push('3'); | ||
}, 200); | ||
setTimeout(function () { | ||
timer.clearTimeout(timeout2); | ||
}, 50); | ||
setTimeout(function () { | ||
assert.deepEqual(log, ['1', '3']); | ||
done(); | ||
}, 250) | ||
}); | ||
it('should be able to use setTimout from a different context', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
var mySetTimeout = timer.setTimeout; | ||
mySetTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 200)); | ||
done(); | ||
}, 100); | ||
}); | ||
}); | ||
describe('trigger', function () { | ||
it('should set a trigger with rate=1', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var time = new Date(new Date().valueOf() + 100); | ||
timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
done(); | ||
}, time); | ||
}); | ||
it('should set a trigger with rate=2', function (done) { | ||
var timer = hypertimer({rate: 2}); | ||
var start = new Date(); | ||
var time = new Date(new Date().valueOf() + 200); | ||
timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
done(); | ||
}, time); | ||
}); | ||
it('should set a trigger with rate=1/2', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
var time = new Date(new Date().valueOf() + 100); | ||
timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 200)); | ||
done(); | ||
}, time); | ||
}); | ||
it('should set a trigger with a time in the past', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var time = new Date(new Date().valueOf() - 100); | ||
timer.setTrigger(function () { | ||
approx(new Date(), start); | ||
done(); | ||
}, time); | ||
}); | ||
it('should set a trigger with a number as time', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var time = new Date().valueOf() + 100; | ||
timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
done(); | ||
}, time); | ||
}); | ||
it('should set a trigger with a number in the past as time', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var time = new Date().valueOf() - 100; | ||
timer.setTrigger(function () { | ||
approx(new Date(), start); | ||
done(); | ||
}, time); | ||
}); | ||
it('should set a trigger with an infinite number as time', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var time = Infinity; | ||
timer.setTrigger(function () { | ||
approx(new Date(), start); | ||
done(); | ||
}, time); | ||
}); | ||
it('should set a trigger with a start and end', function (done) { | ||
var timer = hypertimer({rate: 2}); | ||
var start = new Date(2050,0,1,12,0,0,0); | ||
var end = new Date(2050,0,1,12,0,0,200); | ||
timer.setTime(start); | ||
var a = new Date(); | ||
timer.setTrigger(function () { | ||
approx(new Date(), new Date(a.valueOf() + 100)); | ||
done(); | ||
}, end); | ||
}); | ||
it('should set a trigger with a start and an end in the past', function (done) { | ||
var timer = hypertimer({rate: 2}); | ||
var start = new Date(2050,0,1,12,0,0,0); | ||
var end = new Date(2050,0,1,11,0,0,0); | ||
timer.setTime(start); | ||
var a = new Date(); | ||
timer.setTrigger(function () { | ||
approx(new Date(), a); | ||
done(); | ||
}, end); | ||
}); | ||
it('should execute multiple triggers in the right order', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
var log = []; | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
var triggerB = timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 200)); | ||
log.push('B'); | ||
assert.deepEqual(log, ['A', 'B']); | ||
}, new Date(2050,0,1,12,0,0,100)); | ||
var triggerC = timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 300)); | ||
log.push('C'); | ||
assert.deepEqual(log, ['A', 'B', 'C']); | ||
done(); | ||
}, new Date(2050,0,1,12,0,0,150)); | ||
var triggerA = timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
log.push('A'); | ||
assert.deepEqual(log, ['A']); | ||
}, new Date(2050,0,1,12,0,0,50)); | ||
assert.deepEqual(timer.list(), [triggerA, triggerB, triggerC]); | ||
}); | ||
it('should pause a trigger when the timer is paused', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
var start = new Date(); | ||
var log = []; | ||
timer.setTrigger(function () { | ||
assert.deepEqual(log, ['A', 'B']); | ||
approx(new Date(), new Date(start.valueOf() + 400)); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,100)); | ||
done(); | ||
}, new Date(2050,0,1,12,0,0,100)); | ||
// real-time timeout | ||
setTimeout(function () { | ||
log.push('A'); | ||
timer.pause(); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
setTimeout(function () { | ||
log.push('B'); | ||
timer.continue(); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
approx(new Date(), new Date(start.valueOf() + 300)); | ||
}, 200); | ||
}, 100); | ||
}); | ||
it('should adjust a trigger when the timers time is adjusted', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
timer.setTrigger(function () { | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,200)); | ||
done(); | ||
}, new Date(2050,0,1,12,0,0,200)); | ||
timer.setTime(new Date(2050,0,1,12,0,0,100)); | ||
}); | ||
it('should adjust a trigger when the timers rate is adjusted', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
var log = []; | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
timer.setTrigger(function () { | ||
assert.deepEqual(log, ['A']); | ||
approx(new Date(), new Date(start.valueOf() + 150)); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,100)); | ||
done(); | ||
}, new Date(2050,0,1,12,0,0,100)); | ||
setTimeout(function () { | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
timer.config({rate: 1}); | ||
approx(timer.getTime(), new Date(2050,0,1,12,0,0,50)); | ||
log.push('A'); | ||
}, 100) | ||
}); | ||
it('should cancel a trigger with clearTrigger', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var log = []; | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
var trigger1 = timer.setTrigger(function () { | ||
log.push('1'); | ||
}, new Date(2050,0,1,12,0,0,100)); | ||
var trigger2 = timer.setTrigger(function () { | ||
log.push('2'); | ||
assert(false, 'should not trigger trigger1') | ||
}, new Date(2050,0,1,12,0,0,150)); | ||
var trigger3 = timer.setTrigger(function () { | ||
log.push('3'); | ||
}, new Date(2050,0,1,12,0,0,200)); | ||
setTimeout(function () { | ||
timer.clearTrigger(trigger2); | ||
}, 50); | ||
setTimeout(function () { | ||
assert.deepEqual(log, ['1', '3']); | ||
done(); | ||
}, 250) | ||
}); | ||
}); | ||
describe('interval', function () { | ||
it('should set an interval', function (done) { | ||
var timer = hypertimer({rate: 1/2}); | ||
var start = new Date(); | ||
var occurrence = 0; | ||
var interval = timer.setInterval(function () { | ||
occurrence++; | ||
approx(new Date(), new Date(start.valueOf() + occurrence * 100)); | ||
approx(timer.getTime(), new Date(start.valueOf() + occurrence * 50)); | ||
if (occurrence == 3) { | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
done(); | ||
} | ||
}, 50); | ||
}); | ||
it('should set an interval with firstTime', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
var firstTime = new Date(2050,0,1,12,0,0,300); | ||
var occurrence = 0; | ||
var interval = timer.setInterval(function () { | ||
occurrence++; | ||
approx(new Date(), new Date(start.valueOf() + 300 + (occurrence - 1) * 100)); | ||
approx(timer.getTime(), new Date(firstTime.valueOf() + (occurrence - 1) * 100)); | ||
if (occurrence == 3) { | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
done(); | ||
} | ||
}, 100, firstTime); | ||
}); | ||
it('should set an interval with a number as firstTime', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTime(new Date(2050,0,1,12,0,0,0)); | ||
var firstTime = new Date(2050,0,1,12,0,0,300); | ||
var occurrence = 0; | ||
var interval = timer.setInterval(function () { | ||
occurrence++; | ||
approx(new Date(), new Date(start.valueOf() + 300 + (occurrence - 1) * 100)); | ||
approx(timer.getTime(), new Date(firstTime.valueOf() + (occurrence - 1) * 100)); | ||
if (occurrence == 4) { | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
done(); | ||
} | ||
}, 100, firstTime.valueOf()); | ||
}); | ||
it('should clear an interval', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var interval = timer.setInterval(function () { | ||
assert(false, 'should not trigger interval') | ||
}, 100); | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
// wait until the time where the interval should have been triggered | ||
setTimeout(function () { | ||
done(); | ||
}, 200); | ||
}); | ||
it('should set a negative interval', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var occurrence = 0; | ||
var interval = timer.setInterval(function () { | ||
occurrence++; | ||
approx(new Date(), start); | ||
approx(timer.getTime(), start); | ||
if (occurrence == 4) { | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
done(); | ||
} | ||
}, -100); | ||
}); | ||
it('should set a negative interval with firstTime', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var timerStart = new Date(2050,0,1,12,0,0,0); | ||
var realStart = new Date(new Date().valueOf() + 200); | ||
var firstStart = new Date(2050,0,1,12,0,0,200); | ||
timer.setTime(timerStart); | ||
var occurrence = 0; | ||
var interval = timer.setInterval(function () { | ||
occurrence++; | ||
approx(new Date(), realStart); | ||
approx(timer.getTime(), firstStart); | ||
if (occurrence == 4) { | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
done(); | ||
} | ||
}, -100, firstStart); | ||
}); | ||
it('should set an infinite interval', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var occurrence = 0; | ||
var interval = timer.setInterval(function () { | ||
occurrence++; | ||
approx(new Date(), start); | ||
approx(timer.getTime(), start); | ||
if (occurrence == 4) { | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
done(); | ||
} | ||
}, Infinity); | ||
}); | ||
it('should correctly update interval when rate changes', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
timer.setTime(new Date(2050,0,1, 12,0,0, 0)); | ||
var plans = { | ||
'1': {realTime: new Date(start.valueOf() + 100), hyperTime: new Date(2050,0,1, 12,0,0, 100), newRate: 2}, | ||
'2': {realTime: new Date(start.valueOf() + 150), hyperTime: new Date(2050,0,1, 12,0,0, 200), newRate: 1/2}, | ||
'3': {realTime: new Date(start.valueOf() + 350), hyperTime: new Date(2050,0,1, 12,0,0, 300), newRate: -1}, | ||
'4': {realTime: new Date(start.valueOf() + 450), hyperTime: new Date(2050,0,1, 12,0,0, 200), newRate: 1}, | ||
'5': {realTime: new Date(start.valueOf() + 550), hyperTime: new Date(2050,0,1, 12,0,0, 300)} | ||
}; | ||
var occurrence = 0; | ||
timer.setInterval(function () { | ||
occurrence++; | ||
var plan = plans[occurrence]; | ||
try { | ||
approx(timer.getTime(), plan.hyperTime); | ||
approx(new Date(), plan.realTime); | ||
} | ||
catch (err) { | ||
done(err); | ||
} | ||
if ('newRate' in plan) { | ||
timer.config({rate: plan.newRate}); | ||
} | ||
if (!plans[occurrence + 1]) { | ||
timer.clear(); | ||
done(); | ||
} | ||
}, 100); | ||
}); | ||
}); | ||
// TODO: test with a numeric time instead of "real" Dates, timer.setTime(0), rate='discrete', and timeouts like timer.timeout(cb, 1) | ||
it('should get valueOf', function () { | ||
var timer = hypertimer(); | ||
assert(timer.valueOf() instanceof Date); | ||
approx(timer.valueOf(), timer.getTime()); | ||
}); | ||
it('should get toString', function () { | ||
var timer = hypertimer(); | ||
assert.strictEqual(typeof timer.toString(), 'string'); | ||
assert.strictEqual(timer.toString().toString(), timer.getTime().toString()); | ||
}); | ||
it('should list all timeouts', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var id1 = timer.setTimeout(function () {}, 1000); | ||
var id2 = timer.setTimeout(function () {}, 50); | ||
var id3 = timer.setTrigger(function () {}, new Date(Date.now() + 100)); | ||
assert.deepEqual(timer.list(), [id2, id3, id1]); | ||
timer.clearTimeout(id2); | ||
assert.deepEqual(timer.list(), [id3, id1]); | ||
setTimeout(function () { | ||
assert.deepEqual(timer.list(), [id1]); | ||
timer.clearTimeout(id1); | ||
assert.deepEqual(timer.list(), []); | ||
done(); | ||
}, 150); | ||
}); | ||
it('should clear all timeouts', function () { | ||
var timer = hypertimer({rate: 1}); | ||
var id1 = timer.setTimeout(function () {}, 1000); | ||
var id2 = timer.setTimeout(function () {}, 50); | ||
var id3 = timer.setTrigger(function () {}, new Date(Date.now() + 100)); | ||
assert.deepEqual(timer.list(), [id2, id3, id1]); | ||
timer.clear(); | ||
assert.deepEqual(timer.list(), []); | ||
}); | ||
// FIXME: interval stops when switching to negative rate and back | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
92639
22
1724
255
9
8
1