Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

hypertimer

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hypertimer - npm Package Compare versions

Comparing version 0.1.0 to 1.0.0

examples/discrete_time_async.js

211

dist/hypertimer.js

@@ -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 @@ ],

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc