hypertimer
Advanced tools
Comparing version 0.1.0 to 1.0.0
@@ -8,4 +8,4 @@ /** | ||
* | ||
* @version 0.1.0 | ||
* @date 2014-07-15 | ||
* @version 1.0.0 | ||
* @date 2014-07-17 | ||
* | ||
@@ -99,10 +99,14 @@ * @license | ||
var DISCRETE = 'discrete'; | ||
/** | ||
* 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. | ||
* rate: number | 'discrete' | ||
* The rate of speed of hyper time with | ||
* respect to real-time in milliseconds | ||
* per millisecond. Rate must be a | ||
* positive number, or 'discrete' to | ||
* run in discrete time (jumping from | ||
* event to event). By default, rate is 1. | ||
*/ | ||
@@ -118,2 +122,3 @@ function hypertimer(options) { | ||
var timeouts = []; // array with all running timeouts | ||
var current = {}; // the timeouts currently in progress (callback is being executed) | ||
var timeoutId = null; // currently running timer | ||
@@ -129,8 +134,9 @@ var idSeq = 0; // counter for unique timeout id's | ||
* @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. | ||
* rate: number | 'discrete' | ||
* The rate of speed of hyper time with | ||
* respect to real-time in milliseconds | ||
* per millisecond. Rate must be a | ||
* positive number, or 'discrete' to | ||
* run in discrete time (jumping from | ||
* event to event). By default, rate is 1. | ||
* @return {Object} Returns the applied configuration | ||
@@ -141,7 +147,6 @@ */ | ||
if ('rate' in options) { | ||
var newRate = Number(options.rate); | ||
if (isNaN(newRate)) { | ||
throw new TypeError('rate must be a number'); | ||
var newRate = (options.rate === DISCRETE) ? DISCRETE : Number(options.rate); | ||
if (newRate !== DISCRETE && (isNaN(newRate) || newRate <= 0)) { | ||
throw new TypeError('rate must be a positive number or the string "discrete"'); | ||
} | ||
// TODO: add option rate='discrete' | ||
hyperTime = timer.now(); | ||
@@ -188,10 +193,15 @@ realTime = util.nowReal(); | ||
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; | ||
if (rate === DISCRETE) { | ||
return hyperTime; | ||
} | ||
else { | ||
return hyperTime; | ||
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; | ||
} | ||
} | ||
@@ -251,5 +261,5 @@ }; | ||
* @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. | ||
* @param {number} delay The delay in milliseconds. When 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 | ||
@@ -284,5 +294,5 @@ * timeout using clearTimeout(). | ||
* @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. | ||
* callback will be triggered. When 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 | ||
@@ -355,6 +365,6 @@ * trigger using clearTrigger(). | ||
type: TYPE.INTERVAL, | ||
interval: _interval, | ||
time: timestamp, | ||
interval: _interval, | ||
//firstTime: timestamp, | ||
//occurrence: 0, | ||
firstTime: timestamp, | ||
occurrence: 0, | ||
callback: callback | ||
@@ -374,2 +384,8 @@ }); | ||
timer.clearTimeout = function(timeoutId) { | ||
// test whether timeout is currently being executed | ||
if (current[timeoutId]) { | ||
delete current[timeoutId]; | ||
return; | ||
} | ||
// find the timeout in the queue | ||
@@ -411,2 +427,3 @@ for (var i = 0; i < timeouts.length; i++) { | ||
// empty the queue | ||
current = {}; | ||
timeouts = []; | ||
@@ -421,10 +438,10 @@ | ||
* must be rescheduled by executing _reschedule() | ||
* @param {{type: number, time: number, callback: Function}} params | ||
* @param {{id: number, type: number, time: number, callback: Function}} timeout | ||
* @private | ||
*/ | ||
function _queueTimeout(params) { | ||
function _queueTimeout(timeout) { | ||
// 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) { | ||
while (i >= 0 && timeouts[i].time > timeout.time) { | ||
i--; | ||
@@ -436,7 +453,7 @@ } | ||
// so the order in which they are executed is deterministic | ||
timeouts.splice(i + 1, 0, params); | ||
timeouts.splice(i + 1, 0, timeout); | ||
} | ||
else { | ||
// queue is empty, append the new timeout | ||
timeouts.push(params); | ||
timeouts.push(timeout); | ||
} | ||
@@ -446,2 +463,46 @@ } | ||
/** | ||
* Execute a timeout | ||
* @param {{id: number, type: number, time: number, callback: function}} timeout | ||
* @param {function} [callback] | ||
* The callback is executed when the timeout's callback is | ||
* finished. Called without parameters | ||
* @private | ||
*/ | ||
function _execTimeout(timeout, callback) { | ||
// store the timeout in the queue with timeouts in progress | ||
// it can be cleared when a clearTimeout is executed inside the callback | ||
current[timeout.id] = timeout; | ||
function finish() { | ||
// in case of an interval we have to reschedule on next cycle | ||
// interval must not be cleared while executing the callback | ||
if (timeout.type === TYPE.INTERVAL && current[timeout.id]) { | ||
timeout.occurrence++; | ||
timeout.time = timeout.firstTime + timeout.occurrence * timeout.interval; | ||
_queueTimeout(timeout); | ||
} | ||
// remove the timeout from the queue with timeouts in progress | ||
delete current[timeout.id]; | ||
if (typeof callback === 'function') callback(); | ||
} | ||
// execute the callback | ||
try { | ||
if (timeout.callback.length == 0) { | ||
// synchronous timeout, like `timer.setTimeout(function () {...}, delay)` | ||
timeout.callback(); | ||
finish(); | ||
} else { | ||
// asynchronous timeout, like `timer.setTimeout(function (done) {...; done(); }, delay)` | ||
timeout.callback(finish); | ||
} | ||
} catch (err) { | ||
// silently ignore errors thrown by the callback | ||
finish(); | ||
} | ||
} | ||
/** | ||
* Reschedule all queued timeouts | ||
@@ -451,3 +512,9 @@ * @private | ||
function _schedule() { | ||
var timeout; | ||
// do not _schedule when there are timeouts in progress | ||
// this can be the case with async timeouts in discrete time. | ||
// _schedule will be executed again when all async timeouts are finished. | ||
if (rate === DISCRETE && Object.keys(current).length > 0) { | ||
return; | ||
} | ||
var next = timeouts[0]; | ||
@@ -465,43 +532,47 @@ | ||
var delay = time - timer.now(); | ||
var realDelay = delay / rate; | ||
var realDelay = (rate === DISCRETE) ? 0 : 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]; | ||
function onTimeout() { | ||
// when running in discrete time, update the hyperTime to the time | ||
// of the current event | ||
if (rate === DISCRETE) { | ||
hyperTime = time; | ||
} | ||
// execute the callback | ||
try { | ||
timeout.callback(); | ||
} catch (err) { | ||
// silently ignore errors thrown by the callback | ||
} | ||
// grab all expired timeouts from the queue | ||
var i = 0; | ||
while (i < timeouts.length && ((timeouts[i].time <= time) || !isFinite(timeouts[i].time))) { | ||
i++; | ||
} | ||
var expired = timeouts.splice(0, i); | ||
// note: expired.length can never be zero (on every change of the queue, we reschedule) | ||
// 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); | ||
// execute all expired timeouts | ||
if (rate === DISCRETE) { | ||
// in discrete time, we execute all expired timeouts serially, | ||
// and wait for their completion in order to guarantee deterministic | ||
// order of execution | ||
function next() { | ||
var timeout = expired.shift(); | ||
if (timeout) { | ||
_execTimeout(timeout, next); | ||
} | ||
else { | ||
// schedule the next round | ||
_schedule(); | ||
} | ||
} | ||
timeouts.shift(); | ||
next(); | ||
} | ||
else { | ||
// in continuous time, we fire all timeouts in parallel, | ||
// and don't await their completion (they can do async operations) | ||
expired.forEach(_execTimeout); | ||
// 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); | ||
// schedule the next round | ||
_schedule(); | ||
} | ||
// initialize next round of timeouts | ||
_schedule(); | ||
} | ||
timeoutId = setTimeout(trigger, realDelay); | ||
timeoutId = setTimeout(onTimeout, realDelay); | ||
} | ||
@@ -508,0 +579,0 @@ } |
@@ -8,4 +8,4 @@ /** | ||
* | ||
* @version 0.1.0 | ||
* @date 2014-07-15 | ||
* @version 1.0.0 | ||
* @date 2014-07-17 | ||
* | ||
@@ -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(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()}}])}); | ||
!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(s.length>0){for(var t=s.length-1;t>=0&&s[t].time>e.time;)t--;s.splice(t+1,0,e)}else s.push(e)}function n(e,n){function r(){e.type===o.INTERVAL&&m[e.id]&&(e.occurrence++,e.time=e.firstTime+e.occurrence*e.interval,t(e)),delete m[e.id],"function"==typeof n&&n()}m[e.id]=e;try{0==e.callback.length?(e.callback(),r()):e.callback(r)}catch(i){r()}}function r(){function e(){function e(){var t=o.shift();t?n(t,e):r()}a===u&&(l=i);for(var t=0;t<s.length&&(s[t].time<=i||!isFinite(s[t].time));)t++;var o=s.splice(0,t);a===u?e():(o.forEach(n),r())}if(!(a===u&&Object.keys(m).length>0)){var t=s[0];if(p&&(clearTimeout(p),p=null),c&&t){var i=t.time,o=i-b.now(),f=a===u?0:o/a;p=setTimeout(e,f)}}}var a=1,c=!1,f=null,l=null,s=[],m={},p=null,T=0,b={};return b.config=function(e){if(e&&"rate"in e){var t=e.rate===u?u:Number(e.rate);if(t!==u&&(isNaN(t)||0>=t))throw new TypeError('rate must be a positive number or the string "discrete"');l=b.now(),f=i.nowReal(),a=t}return r(),{rate:a}},b.setTime=function(e){if(e instanceof Date)l=e.valueOf();else{var t=Number(e);if(isNaN(t))throw new TypeError("time must be a Date or number");l=t}r()},b.now=function(){if(a===u)return l;if(c){var e=i.nowReal()-f,t=e*a;return l+t}return l},b["continue"]=function(){f=i.nowReal(),c=!0,r()},b.pause=function(){l=b.now(),f=null,c=!1,r()},b.getTime=function(){return new Date(b.now())},b.valueOf=b.getTime,b.toString=function(){return b.getTime().toString()},b.setTimeout=function(e,n){var i=T++,u=b.now()+n;if(isNaN(u))throw new TypeError("delay must be a number");return t({id:i,type:o.TIMEOUT,time:u,callback:e}),r(),i},b.setTrigger=function(e,n){var i=T++,u=Number(n);if(isNaN(u))throw new TypeError("time must be a Date or number");return t({id:i,type:o.TRIGGER,time:u,callback:e}),r(),i},b.setInterval=function(e,n,i){var u=T++,a=Number(n);if(isNaN(a))throw new TypeError("interval must be a number");(0>a||!isFinite(a))&&(a=0);var c;if(void 0!=i){if(c=Number(i),isNaN(c))throw new TypeError("firstTime must be a Date or number")}else c=b.now()+a;return t({id:u,type:o.INTERVAL,interval:a,time:c,firstTime:c,occurrence:0,callback:e}),r(),u},b.clearTimeout=function(e){if(m[e])return void delete m[e];for(var t=0;t<s.length;t++)if(s[t].id===e){s.splice(t,1),r();break}},b.clearTrigger=b.clearTimeout,b.clearInterval=b.clearTimeout,b.list=function(){return s.map(function(e){return e.id})},b.clear=function(){m={},s=[],r()},Object.defineProperty(b,"running",{get:function(){return c}}),b.config(e),b.setTime(i.nowReal()),b.continue(),b}var i=n(2),o={TIMEOUT:0,INTERVAL:1,TRIGGER:2},u="discrete";e.exports=r},function(e,t){t.nowReal="function"==typeof Date.now?function(){return Date.now()}:function(){return(new Date).valueOf()}}])}); | ||
//# sourceMappingURL=hypertimer.map |
# History | ||
## 2014-07-17, version 1.0.0 | ||
- Implemented support for (async) discrete time. | ||
- Removed support for negative rates. | ||
- Added more docs and examples. | ||
## 2014-07-15, version 0.1.0 | ||
- First functional release. Support for getting/setting time, and setting | ||
timeouts and intervals. | ||
## 2014-07-11, version 0.0.1 | ||
- Library name reserved at npm and bower. |
@@ -10,10 +10,14 @@ var util = require('./util'); | ||
var DISCRETE = 'discrete'; | ||
/** | ||
* 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. | ||
* rate: number | 'discrete' | ||
* The rate of speed of hyper time with | ||
* respect to real-time in milliseconds | ||
* per millisecond. Rate must be a | ||
* positive number, or 'discrete' to | ||
* run in discrete time (jumping from | ||
* event to event). By default, rate is 1. | ||
*/ | ||
@@ -29,2 +33,3 @@ function hypertimer(options) { | ||
var timeouts = []; // array with all running timeouts | ||
var current = {}; // the timeouts currently in progress (callback is being executed) | ||
var timeoutId = null; // currently running timer | ||
@@ -40,8 +45,9 @@ var idSeq = 0; // counter for unique timeout id's | ||
* @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. | ||
* rate: number | 'discrete' | ||
* The rate of speed of hyper time with | ||
* respect to real-time in milliseconds | ||
* per millisecond. Rate must be a | ||
* positive number, or 'discrete' to | ||
* run in discrete time (jumping from | ||
* event to event). By default, rate is 1. | ||
* @return {Object} Returns the applied configuration | ||
@@ -52,7 +58,6 @@ */ | ||
if ('rate' in options) { | ||
var newRate = Number(options.rate); | ||
if (isNaN(newRate)) { | ||
throw new TypeError('rate must be a number'); | ||
var newRate = (options.rate === DISCRETE) ? DISCRETE : Number(options.rate); | ||
if (newRate !== DISCRETE && (isNaN(newRate) || newRate <= 0)) { | ||
throw new TypeError('rate must be a positive number or the string "discrete"'); | ||
} | ||
// TODO: add option rate='discrete' | ||
hyperTime = timer.now(); | ||
@@ -99,10 +104,15 @@ realTime = util.nowReal(); | ||
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; | ||
if (rate === DISCRETE) { | ||
return hyperTime; | ||
} | ||
else { | ||
return hyperTime; | ||
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; | ||
} | ||
} | ||
@@ -162,5 +172,5 @@ }; | ||
* @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. | ||
* @param {number} delay The delay in milliseconds. When 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 | ||
@@ -195,5 +205,5 @@ * timeout using clearTimeout(). | ||
* @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. | ||
* callback will be triggered. When 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 | ||
@@ -266,6 +276,6 @@ * trigger using clearTrigger(). | ||
type: TYPE.INTERVAL, | ||
interval: _interval, | ||
time: timestamp, | ||
interval: _interval, | ||
//firstTime: timestamp, | ||
//occurrence: 0, | ||
firstTime: timestamp, | ||
occurrence: 0, | ||
callback: callback | ||
@@ -285,2 +295,8 @@ }); | ||
timer.clearTimeout = function(timeoutId) { | ||
// test whether timeout is currently being executed | ||
if (current[timeoutId]) { | ||
delete current[timeoutId]; | ||
return; | ||
} | ||
// find the timeout in the queue | ||
@@ -322,2 +338,3 @@ for (var i = 0; i < timeouts.length; i++) { | ||
// empty the queue | ||
current = {}; | ||
timeouts = []; | ||
@@ -332,10 +349,10 @@ | ||
* must be rescheduled by executing _reschedule() | ||
* @param {{type: number, time: number, callback: Function}} params | ||
* @param {{id: number, type: number, time: number, callback: Function}} timeout | ||
* @private | ||
*/ | ||
function _queueTimeout(params) { | ||
function _queueTimeout(timeout) { | ||
// 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) { | ||
while (i >= 0 && timeouts[i].time > timeout.time) { | ||
i--; | ||
@@ -347,7 +364,7 @@ } | ||
// so the order in which they are executed is deterministic | ||
timeouts.splice(i + 1, 0, params); | ||
timeouts.splice(i + 1, 0, timeout); | ||
} | ||
else { | ||
// queue is empty, append the new timeout | ||
timeouts.push(params); | ||
timeouts.push(timeout); | ||
} | ||
@@ -357,2 +374,46 @@ } | ||
/** | ||
* Execute a timeout | ||
* @param {{id: number, type: number, time: number, callback: function}} timeout | ||
* @param {function} [callback] | ||
* The callback is executed when the timeout's callback is | ||
* finished. Called without parameters | ||
* @private | ||
*/ | ||
function _execTimeout(timeout, callback) { | ||
// store the timeout in the queue with timeouts in progress | ||
// it can be cleared when a clearTimeout is executed inside the callback | ||
current[timeout.id] = timeout; | ||
function finish() { | ||
// in case of an interval we have to reschedule on next cycle | ||
// interval must not be cleared while executing the callback | ||
if (timeout.type === TYPE.INTERVAL && current[timeout.id]) { | ||
timeout.occurrence++; | ||
timeout.time = timeout.firstTime + timeout.occurrence * timeout.interval; | ||
_queueTimeout(timeout); | ||
} | ||
// remove the timeout from the queue with timeouts in progress | ||
delete current[timeout.id]; | ||
if (typeof callback === 'function') callback(); | ||
} | ||
// execute the callback | ||
try { | ||
if (timeout.callback.length == 0) { | ||
// synchronous timeout, like `timer.setTimeout(function () {...}, delay)` | ||
timeout.callback(); | ||
finish(); | ||
} else { | ||
// asynchronous timeout, like `timer.setTimeout(function (done) {...; done(); }, delay)` | ||
timeout.callback(finish); | ||
} | ||
} catch (err) { | ||
// silently ignore errors thrown by the callback | ||
finish(); | ||
} | ||
} | ||
/** | ||
* Reschedule all queued timeouts | ||
@@ -362,3 +423,9 @@ * @private | ||
function _schedule() { | ||
var timeout; | ||
// do not _schedule when there are timeouts in progress | ||
// this can be the case with async timeouts in discrete time. | ||
// _schedule will be executed again when all async timeouts are finished. | ||
if (rate === DISCRETE && Object.keys(current).length > 0) { | ||
return; | ||
} | ||
var next = timeouts[0]; | ||
@@ -376,43 +443,47 @@ | ||
var delay = time - timer.now(); | ||
var realDelay = delay / rate; | ||
var realDelay = (rate === DISCRETE) ? 0 : 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]; | ||
function onTimeout() { | ||
// when running in discrete time, update the hyperTime to the time | ||
// of the current event | ||
if (rate === DISCRETE) { | ||
hyperTime = time; | ||
} | ||
// execute the callback | ||
try { | ||
timeout.callback(); | ||
} catch (err) { | ||
// silently ignore errors thrown by the callback | ||
} | ||
// grab all expired timeouts from the queue | ||
var i = 0; | ||
while (i < timeouts.length && ((timeouts[i].time <= time) || !isFinite(timeouts[i].time))) { | ||
i++; | ||
} | ||
var expired = timeouts.splice(0, i); | ||
// note: expired.length can never be zero (on every change of the queue, we reschedule) | ||
// 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); | ||
// execute all expired timeouts | ||
if (rate === DISCRETE) { | ||
// in discrete time, we execute all expired timeouts serially, | ||
// and wait for their completion in order to guarantee deterministic | ||
// order of execution | ||
function next() { | ||
var timeout = expired.shift(); | ||
if (timeout) { | ||
_execTimeout(timeout, next); | ||
} | ||
else { | ||
// schedule the next round | ||
_schedule(); | ||
} | ||
} | ||
timeouts.shift(); | ||
next(); | ||
} | ||
else { | ||
// in continuous time, we fire all timeouts in parallel, | ||
// and don't await their completion (they can do async operations) | ||
expired.forEach(_execTimeout); | ||
// 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); | ||
// schedule the next round | ||
_schedule(); | ||
} | ||
// initialize next round of timeouts | ||
_schedule(); | ||
} | ||
timeoutId = setTimeout(trigger, realDelay); | ||
timeoutId = setTimeout(onTimeout, realDelay); | ||
} | ||
@@ -419,0 +490,0 @@ } |
{ | ||
"name": "hypertimer", | ||
"version": "0.1.0", | ||
"description": "A timer running faster or slower than real-time, in continuous or discrete time", | ||
"version": "1.0.0", | ||
"description": "Time control for simulations", | ||
"author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)", | ||
"main": "./index", | ||
"keywords": [ | ||
"timer", | ||
"time", | ||
"control", | ||
"simulation", | ||
"timer", | ||
"hypertime", | ||
"hyper-time", | ||
"real-time", | ||
"continuous", | ||
"discrete", | ||
"deterministic", | ||
"event" | ||
@@ -17,0 +16,0 @@ ], |
200
README.md
hypertimer | ||
========== | ||
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 time control for simulations. With simulations, it's important to be able to manipulate the time. Typically, simulations are run in discrete time, jumping from event to event in a deterministic manner. And afterwards, a simulation can be played back in continuous time at a faster or slower pace than real-time (depending on the time scale of the simulation). In short, one needs to be able to run a simulation in [hypertime](http://en.wikipedia.org/wiki/Hypertime). | ||
Hypertimer offers basic functionality to get the time and set timeouts: | ||
Hypertimer offers basic functionality to control time: | ||
- `getTime()`, `setTime()`, and `now()`, to get and set the time of the timer. | ||
- `setTimeout()`, `setInterval()`, and `setTrigger()` to set timeouts. | ||
- Get and set the time using functions `getTime()`, `setTime()`, and `now()`. | ||
- Schedule events using functions `setTimeout()`, `setInterval()`, and `setTrigger()`. | ||
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. | ||
These functions are compatible with JavaScript's 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. | ||
Hypertimer runs on node.js and on any modern browser (Chrome, FireFox, Opera, Safari, IE9+). | ||
## Install | ||
@@ -55,22 +56,176 @@ | ||
The following example shows how to create a hypertimer and set a timeout. | ||
### Configuring a hypertimer | ||
A hypertimer can run in continuous or discrete time, and can run faster or slower than real-time. The speed of a hypertimer can be configured via the option `rate`, which can be a positive number (to run in continuous time) or the string `'discrete'` (to run in discrete time). | ||
```js | ||
// create a hypertimer running ten times faster than real-time | ||
var timer1 = hypertimer({rate: 10}); | ||
// retrieve the current configuration | ||
console.log(timer1.config()); // returns an Object {rate: 10} | ||
// create a hypertimer with the default rate (1 by default) | ||
var timer2 = hypertimer(); | ||
// adjust the rate later on | ||
timer2.config({rate: 1/2}); | ||
// create a hypertimer running in discrete time (time will jump | ||
// from scheduled event to scheduled event) | ||
var timer3 = hypertimer({rate: 'discrete'}); | ||
``` | ||
### Getting and setting time | ||
Hypertimer offers functions to get and set time: | ||
```js | ||
var timer = hypertimer(); | ||
// set the time at 1st of January 2050 | ||
timer.setTime(new Date(2050, 0, 1, 12, 0, 0)); | ||
// get the time as Date | ||
console.log(timer.getTime()); // Returns a date, Sat Jan 01 2050 12:00:00 GMT+0100 (CET) | ||
// get the time as timestamp | ||
console.log(timer.now()); // Returns a number, 2524647600000 | ||
``` | ||
### Pausing the time | ||
The time in a hypertimer can be paused. All scheduled timeouts will be paused | ||
as well, and are automatically continued when the timer is running again. | ||
```js | ||
var timer = hypertimer(); | ||
// pause the timer | ||
timer.pause(); | ||
// time stands still here... | ||
console.log(timer.running); // false | ||
// continue again | ||
timer.continue(); | ||
// time is running again | ||
console.log(timer.running); // true | ||
``` | ||
### Schedule events | ||
Hypertimer has three functions to schedule events: | ||
- `setTimeout(callback, delay)` | ||
Schedule a callback after a delay in milliseconds. | ||
- `setTrigger(callback, time)` | ||
Schedule a callback at a specific time. Time can be a Date or a number (timestamp). | ||
- `setInterval(callback, interval [, firstTime])` | ||
Schedule a callback to be triggered once every interval. `interval` is a number in milliseconds. Optionally, a `firstTime` can be provided. | ||
On creation, the trigger time of the events is calculated from the `delay`, `interval`, and/or `firstTime`, and the event is scheduled to occur at that moment in time. When the time of the hypertimer is changed via `setTime()`, trigger time of running timeouts will not be adjusted. | ||
The functions `setTimeout()`, `setTrigger()`, and `setInterval()` return a timeout id, which can be used to cancel a timeout using the functions `clearTimeout()`, `clearTrigger()`, and `clearInterval()` respectively. To cancel all scheduled events at once, the function `clear()` can be called. | ||
```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)); | ||
// start the timer at 1st of January 2050 | ||
timer.setTime(new Date(2050, 0, 1, 12, 0, 0)); | ||
console.log('start', timer.getTime()); // start Sat Jan 01 2050 12:00:00 GMT+0100 (CET) | ||
// set a timeout after a delay | ||
var delay = 10000; // milliseconds (hyper-time) | ||
var id1 = timer.setTimeout(function () { | ||
console.log('timeout', timer.getTime()); // timeout Sat Jan 01 2050 12:00:10 GMT+0100 (CET) | ||
}, delay); | ||
// set a timeout at a specific time | ||
var time = new Date(2050, 0, 1, 12, 0, 20); // time (hyper-time) | ||
var id2 = timer.setTrigger(function () { | ||
console.log('trigger', timer.getTime()); // trigger Sat Jan 01 2050 12:00:20 GMT+0100 (CET) | ||
}, time); | ||
// set an interval | ||
var interval = 5000; // milliseconds (hyper-time) | ||
var firstTime = new Date(2050, 0, 1, 12, 0, 30); // optional first time (hyper-time) | ||
var counter = 0; | ||
var id3 = timer.setInterval(function () { | ||
console.log('interval', timer.getTime()); // interval, 12:00:30, 12:00:35, 12:00:40, etc ... | ||
// cancel the interval after 10 times | ||
counter++; | ||
if (counter > 10) { | ||
timer.clearInterval(id3); | ||
} | ||
}, interval, firstTime); | ||
``` | ||
### Discrete time | ||
When running in continuous time (rate is a positive number), scheduled events are triggered at their scheduled time (in hyper-time). When running a simulation, there is no need to wait and do nothing between scheduled events. It is much faster to jump from event to event in discrete time. To create a hypertimer running in discrete time, configure rate as `'discrete'`. Hypertimer ensures that all scheduled events are executed in a deterministic order. | ||
In the example below, the application will immediately output 'done!' and not after a delay of 10 seconds, because the timer is running in discrete time and immediately jumps to the first next event. | ||
```js | ||
// create a hypertimer running in discrete time, | ||
var timer = hypertimer({rate: 'discrete'}); | ||
var delay = 10000; | ||
timer.setTimeout(function () { | ||
console.log('Timeout!'); | ||
console.log('It is now ' + delay + ' ms later in hyper-time, ' + | ||
'the time is: ' + timer.getTime()); | ||
console.log('Timeout A'); | ||
timer.setTimeout(function () { | ||
console.log('Timeout B'); | ||
}, delay); | ||
}, delay); | ||
console.log('The time is: ' + timer.getTime()); | ||
// Will immediately output: | ||
// Timeout A | ||
// Timeout B | ||
``` | ||
When performing asynchronous tasks inside a timeout, one needs to create an asynchronous timeout, which calls `done()` when all asynchronous actions are finished. This is required in order to guarantee a deterministic order of execution. | ||
```js | ||
// asynchronous timeout | ||
timer.setTimeout(function (done) { | ||
// ... do something | ||
done(); // call done when done | ||
}, delay); | ||
``` | ||
An example of using an asynchronous timeout: | ||
```js | ||
// create a hypertimer running in discrete time, | ||
// jumping from scheduled event to scheduled event. | ||
var timer = hypertimer({rate: 'discrete'}); | ||
// create an asynchronous timeout, having a callback parameter done | ||
timer.setTimeout(function (done) { | ||
console.log('Timeout A'); | ||
// perform an asynchronous action inside the timeout | ||
someAsyncAction(param, function (err, result) { | ||
timer.setTimeout(function () { | ||
console.log('Timeout B'); | ||
}, 10000); | ||
// once we are done with our asynchronous event, call done() | ||
// so the hypertimer knows he can continue with a next event. | ||
done(); | ||
}); | ||
}, 10000); | ||
// Output: | ||
// Timeout A | ||
// (async action is executed here) | ||
// Timeout B | ||
``` | ||
## API | ||
@@ -90,5 +245,5 @@ | ||
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. | ||
Name | Type | Default | Description | ||
---- | -------------------- | ------- | ----------- | ||
rate | Number or 'discrete' | 1 | The rate (in milliseconds per millisecond) at which the timer runs, with respect to real-time speed. Can be a positive number, or the string 'discrete' to run in discrete time. | ||
@@ -123,4 +278,4 @@ Example: | ||
- `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. | ||
- `rate: Number | 'discrete'` | ||
The rate (in milliseconds per millisecond) at which the timer runs, with respect to real-time speed. Can be a positive number, or 'discrete' to run in discrete time. By default, rate is 1. | ||
@@ -166,3 +321,3 @@ - **`continue()`** | ||
- `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. | ||
The delay in milliseconds. When the delay is smaller or equal to zero, the callback is triggered immediately. | ||
@@ -176,3 +331,3 @@ - **`setTrigger(callback: Function, time: Date | number) : number`** | ||
- `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. | ||
An absolute moment in time (Date) when the callback will be triggered. When the date is a Date in the past, the callback is triggered immediately. | ||
@@ -195,5 +350,4 @@ - **`toString() : String`** | ||
- Implement support for discrete time (discrete, deterministic events). | ||
- Implement a scalable solution to synchronize hypertimers running in different | ||
processes. | ||
- Implement a scalable solution to synchronize hypertimers running in different processes. | ||
- Use high precision timers and time functions. | ||
@@ -200,0 +354,0 @@ |
@@ -73,3 +73,3 @@ // NOTE: all timeouts should have time differences of at least 50ms to be | ||
hypertimer({rate: 'bla'}); | ||
}, /TypeError: rate must be a number/); | ||
}, /TypeError: rate must be a positive number/); | ||
}); | ||
@@ -175,3 +175,3 @@ | ||
// inaccuracy of the real-time. | ||
var rates = [1, 2, 1/2, -1, -2, 0]; | ||
var rates = [1, 2, 4, 1/2]; | ||
var epsilon = 20; | ||
@@ -246,2 +246,21 @@ | ||
// TODO: | ||
it('should continue scheduling when the first timeout is cancelled', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var t1 = timer.setTimeout(function () { | ||
done(new Error('Should not trigger cancelled error')) | ||
}, 100); | ||
var t2 = timer.setTimeout(function () { | ||
approx(new Date(), new Date(start.valueOf() + 150)); | ||
done(); | ||
}, 150); | ||
// t1 is now scheduled as first next timeout | ||
timer.clearTimeout(t1); | ||
// t2 should now be scheduled as first next timeout | ||
}); | ||
it('should execute multiple timeouts in the right order', function (done) { | ||
@@ -687,3 +706,3 @@ var timer = hypertimer({rate: 1/2}); | ||
it('should clear an interval', function (done) { | ||
it('should clear an interval using clearInterval', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
@@ -693,3 +712,3 @@ | ||
assert(false, 'should not trigger interval') | ||
}, 100); | ||
}, 50); | ||
@@ -702,2 +721,55 @@ timer.clearInterval(interval); | ||
done(); | ||
}, 150); | ||
}); | ||
it('should clear an interval using clearInterval inside the intervals callback', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var counter = 0; | ||
var interval = timer.setInterval(function () { | ||
timer.clearInterval(interval); | ||
assert.deepEqual(timer.list(), []); | ||
counter++; | ||
}, 50); | ||
// wait until the time where the interval should have been triggered | ||
setTimeout(function () { | ||
assert.equal(counter, 1); | ||
done(); | ||
}, 150); | ||
}); | ||
it('should clear an interval using clear', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
timer.setInterval(function () { | ||
assert(false, 'should not trigger interval') | ||
}, 50); | ||
timer.clear(); | ||
assert.deepEqual(timer.list(), []); | ||
// wait until the time where the interval should have been triggered | ||
setTimeout(function () { | ||
done(); | ||
}, 150); | ||
}); | ||
it('should clear an interval using clear inside the intervals callback', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var counter = 0; | ||
timer.setInterval(function () { | ||
timer.clear(); | ||
assert.deepEqual(timer.list(), []); | ||
counter++; | ||
}, 50); | ||
// wait until the time where the interval should have been triggered | ||
setTimeout(function () { | ||
assert.equal(counter, 1); | ||
done(); | ||
}, 200); | ||
@@ -761,2 +833,37 @@ }); | ||
it('should correctly update interval when a timeout in the past is inserted', function (done) { | ||
var timer = hypertimer({rate: 1}); | ||
var start = new Date(); | ||
var logs = []; | ||
var first = true; | ||
timer.setInterval(function () { | ||
try { | ||
logs.push('A'); | ||
if (first) { | ||
first = false; | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
approx(timer.getTime(), new Date(start.valueOf() + 100)); | ||
timer.setTrigger(function () { | ||
logs.push('B'); | ||
approx(new Date(), new Date(start.valueOf() + 100)); | ||
approx(timer.getTime(), new Date(start.valueOf() + 100)); | ||
}, new Date(start.valueOf() -100)); | ||
} | ||
else { | ||
assert.deepEqual(logs, ['A', 'B', 'A']); | ||
approx(new Date(), new Date(start.valueOf() + 200)); | ||
approx(timer.getTime(), new Date(start.valueOf() + 200)); | ||
timer.clear(); | ||
done(); | ||
} | ||
} | ||
catch(err) { | ||
done(err); | ||
} | ||
}, 100); | ||
}); | ||
it('should correctly update interval when rate changes', function (done) { | ||
@@ -771,5 +878,4 @@ var timer = hypertimer({rate: 1}); | ||
'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)} | ||
'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, 400)} | ||
}; | ||
@@ -800,2 +906,153 @@ | ||
describe('discrete', function () { | ||
it('should run a series of events in discrete time', function (done) { | ||
var timer = hypertimer({rate: 'discrete'}); | ||
var start = new Date(); | ||
var logs = []; | ||
timer.setTime(new Date(2050,0,1, 12,0,0)); | ||
timer.setTimeout(function () { | ||
logs.push('A'); | ||
try { | ||
var time = timer.getTime(); | ||
assert.deepEqual(time, new Date(2050,0,1, 12,0,1)); | ||
} | ||
catch (err) { | ||
done(err); | ||
} | ||
timer.setTimeout(function () { | ||
logs.push('B'); | ||
try { | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,3)); | ||
} | ||
catch(err) { | ||
done(err) | ||
} | ||
}, 2000); | ||
timer.setTimeout(function () { | ||
logs.push('C'); | ||
try { | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,2)); | ||
} | ||
catch (err) { | ||
done(err); | ||
} | ||
timer.setTimeout(function () { | ||
logs.push('E'); | ||
try { | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,3)); | ||
assert.deepEqual(logs, ['A', 'C', 'D', 'B', 'E']); | ||
approx(new Date(), start); | ||
done(); | ||
} | ||
catch (err) { | ||
done(err); | ||
} | ||
}, 1000); | ||
}, 1000); | ||
timer.setTimeout(function () { | ||
logs.push('D'); | ||
try { | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,2)); | ||
} | ||
catch (err) { | ||
done(err); | ||
} | ||
}, 1000); | ||
}, 1000); | ||
}); | ||
it('should run an async timeout in discrete time', function (testDone) { | ||
var timer = hypertimer({rate: 'discrete'}); | ||
timer.setTime(new Date(2050,0,1, 12,0,0)); | ||
timer.setTimeout(function (done) { | ||
try { | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,5)); | ||
} | ||
catch (err) { | ||
done(err); | ||
} | ||
// here we do an async action inside a timeout's callback | ||
setTimeout(function () { | ||
timer.setTimeout(function () { | ||
try { | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,10)); | ||
} | ||
catch (err) { | ||
testDone(err); | ||
} | ||
testDone(); | ||
}, 5000); | ||
// we are done now with the first timeout | ||
done(); | ||
}, 100); | ||
}, 5000); | ||
}); | ||
it('should add timeouts in an async timeout in deterministic order', function (testDone) { | ||
// The real-time delay for creation of timeout C is larger than that for creating timeout D, | ||
// so the test would fail if A and B where executed in parallel | ||
var timer = hypertimer({rate: 'discrete'}); | ||
var logs = []; | ||
timer.setTime(new Date(2050,0,1, 12,0,0)); | ||
timer.setTimeout(function (done) { | ||
logs.push('A'); | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,5)); | ||
setTimeout(function () { | ||
timer.setTimeout(function () { | ||
logs.push('C'); | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,10)); | ||
}, 5000); | ||
done(); | ||
}, 100); | ||
}, 5000); | ||
timer.setTimeout(function (done) { | ||
logs.push('B'); | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,5)); | ||
// here we do an async action inside a timeout's callback | ||
setTimeout(function () { | ||
timer.setTimeout(function () { | ||
logs.push('D'); | ||
try { | ||
assert.deepEqual(logs, ['A', 'B', 'C', 'D']); | ||
assert.deepEqual(timer.getTime(), new Date(2050,0,1, 12,0,10)); | ||
testDone(); | ||
} | ||
catch (err) { | ||
testDone(err); | ||
} | ||
}, 5000); | ||
done(); | ||
}, 50); | ||
}, 5000); | ||
}); | ||
}); | ||
// TODO: test with a numeric time instead of "real" Dates, timer.setTime(0), rate='discrete', and timeouts like timer.timeout(cb, 1) | ||
@@ -849,4 +1106,2 @@ | ||
// FIXME: interval stops when switching to negative rate and back | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Network access
Supply chain riskThis module accesses the network.
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
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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
111750
2060
1
409
0
21