hypertimer
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -8,4 +8,4 @@ /** | ||
* | ||
* @version 1.0.0 | ||
* @date 2014-07-17 | ||
* @version 1.1.0 | ||
* @date 2014-07-22 | ||
* | ||
@@ -111,2 +111,5 @@ * @license | ||
* event to event). By default, rate is 1. | ||
* deterministic: boolean | ||
* If true (default), simultaneous events | ||
* are executed in a deterministic order. | ||
*/ | ||
@@ -116,2 +119,3 @@ function hypertimer(options) { | ||
var rate = 1; // number of milliseconds per milliseconds | ||
var deterministic = true; // run simultaneous events in a deterministic order | ||
@@ -141,2 +145,5 @@ // properties | ||
* event to event). By default, rate is 1. | ||
* deterministic: boolean | ||
* If true (default), simultaneous events | ||
* are executed in a deterministic order. | ||
* @return {Object} Returns the applied configuration | ||
@@ -155,2 +162,5 @@ */ | ||
} | ||
if ('deterministic' in options) { | ||
deterministic = options.deterministic ? true : false; | ||
} | ||
} | ||
@@ -163,3 +173,4 @@ | ||
return { | ||
rate: rate | ||
rate: rate, | ||
deterministic: deterministic | ||
}; | ||
@@ -500,2 +511,25 @@ }; | ||
/** | ||
* Remove all timeouts occurring before or on the provided time from the | ||
* queue and return them. | ||
* @param {number} time A timestamp | ||
* @returns {Array} returns an array containing all expired timeouts | ||
* @private | ||
*/ | ||
function _getExpiredTimeouts(time) { | ||
var i = 0; | ||
while (i < timeouts.length && ((timeouts[i].time <= time) || !isFinite(timeouts[i].time))) { | ||
i++; | ||
} | ||
var expired = timeouts.splice(0, i); | ||
if (deterministic == false) { | ||
// the array with expired timeouts is in deterministic order | ||
// shuffle them | ||
util.shuffle(expired); | ||
} | ||
return expired; | ||
} | ||
/** | ||
* Reschedule all queued timeouts | ||
@@ -534,7 +568,3 @@ * @private | ||
// 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); | ||
var expired = _getExpiredTimeouts(time); | ||
// note: expired.length can never be zero (on every change of the queue, we reschedule) | ||
@@ -614,5 +644,19 @@ | ||
/** | ||
* Shuffle an array | ||
* | ||
* + Jonas Raoni Soares Silva | ||
* @ http://jsfromhell.com/array/shuffle [v1.0] | ||
* | ||
* @param {Array} o Array to be shuffled | ||
* @returns {Array} Returns the shuffled array | ||
*/ | ||
exports.shuffle = function (o){ | ||
for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); | ||
return o; | ||
}; | ||
/***/ } | ||
/******/ ]) | ||
}) |
@@ -8,4 +8,4 @@ /** | ||
* | ||
* @version 1.0.0 | ||
* @date 2014-07-17 | ||
* @version 1.1.0 | ||
* @date 2014-07-22 | ||
* | ||
@@ -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(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()}}])}); | ||
!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(p.length>0){for(var t=p.length-1;t>=0&&p[t].time>e.time;)t--;p.splice(t+1,0,e)}else p.push(e)}function n(e,n){function r(){e.type===o.INTERVAL&&d[e.id]&&(e.occurrence++,e.time=e.firstTime+e.occurrence*e.interval,t(e)),delete d[e.id],"function"==typeof n&&n()}d[e.id]=e;try{0==e.callback.length?(e.callback(),r()):e.callback(r)}catch(i){r()}}function r(e){for(var t=0;t<p.length&&(p[t].time<=e||!isFinite(p[t].time));)t++;var n=p.splice(0,t);return 0==f&&i.shuffle(n),n}function a(){function e(){function e(){var r=t.shift();r?n(r,e):a()}c===u&&(m=i);var t=r(i);c===u?e():(t.forEach(n),a())}if(!(c===u&&Object.keys(d).length>0)){var t=p[0];if(v&&(clearTimeout(v),v=null),l&&t){var i=t.time,o=i-b.now(),f=c===u?0:o/c;v=setTimeout(e,f)}}}var c=1,f=!0,l=!1,s=null,m=null,p=[],d={},v=null,T=0,b={};return b.config=function(e){if(e){if("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"');m=b.now(),s=i.nowReal(),c=t}"deterministic"in e&&(f=e.deterministic?!0:!1)}return a(),{rate:c,deterministic:f}},b.setTime=function(e){if(e instanceof Date)m=e.valueOf();else{var t=Number(e);if(isNaN(t))throw new TypeError("time must be a Date or number");m=t}a()},b.now=function(){if(c===u)return m;if(l){var e=i.nowReal()-s,t=e*c;return m+t}return m},b["continue"]=function(){s=i.nowReal(),l=!0,a()},b.pause=function(){m=b.now(),s=null,l=!1,a()},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 r=T++,i=b.now()+n;if(isNaN(i))throw new TypeError("delay must be a number");return t({id:r,type:o.TIMEOUT,time:i,callback:e}),a(),r},b.setTrigger=function(e,n){var r=T++,i=Number(n);if(isNaN(i))throw new TypeError("time must be a Date or number");return t({id:r,type:o.TRIGGER,time:i,callback:e}),a(),r},b.setInterval=function(e,n,r){var i=T++,u=Number(n);if(isNaN(u))throw new TypeError("interval must be a number");(0>u||!isFinite(u))&&(u=0);var c;if(void 0!=r){if(c=Number(r),isNaN(c))throw new TypeError("firstTime must be a Date or number")}else c=b.now()+u;return t({id:i,type:o.INTERVAL,interval:u,time:c,firstTime:c,occurrence:0,callback:e}),a(),i},b.clearTimeout=function(e){if(d[e])return void delete d[e];for(var t=0;t<p.length;t++)if(p[t].id===e){p.splice(t,1),a();break}},b.clearTrigger=b.clearTimeout,b.clearInterval=b.clearTimeout,b.list=function(){return p.map(function(e){return e.id})},b.clear=function(){d={},p=[],a()},Object.defineProperty(b,"running",{get:function(){return l}}),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()},t.shuffle=function(e){for(var t,n,r=e.length;r;t=Math.floor(Math.random()*r),n=e[--r],e[r]=e[t],e[t]=n);return e}}])}); | ||
//# sourceMappingURL=hypertimer.map |
# History | ||
## 2014-07-22, version 1.1.0 | ||
- Added support for non-deterministic execution of events. | ||
## 2014-07-17, version 1.0.0 | ||
@@ -5,0 +10,0 @@ |
@@ -22,2 +22,5 @@ var util = require('./util'); | ||
* event to event). By default, rate is 1. | ||
* deterministic: boolean | ||
* If true (default), simultaneous events | ||
* are executed in a deterministic order. | ||
*/ | ||
@@ -27,2 +30,3 @@ function hypertimer(options) { | ||
var rate = 1; // number of milliseconds per milliseconds | ||
var deterministic = true; // run simultaneous events in a deterministic order | ||
@@ -52,2 +56,5 @@ // properties | ||
* event to event). By default, rate is 1. | ||
* deterministic: boolean | ||
* If true (default), simultaneous events | ||
* are executed in a deterministic order. | ||
* @return {Object} Returns the applied configuration | ||
@@ -66,2 +73,5 @@ */ | ||
} | ||
if ('deterministic' in options) { | ||
deterministic = options.deterministic ? true : false; | ||
} | ||
} | ||
@@ -74,3 +84,4 @@ | ||
return { | ||
rate: rate | ||
rate: rate, | ||
deterministic: deterministic | ||
}; | ||
@@ -411,2 +422,25 @@ }; | ||
/** | ||
* Remove all timeouts occurring before or on the provided time from the | ||
* queue and return them. | ||
* @param {number} time A timestamp | ||
* @returns {Array} returns an array containing all expired timeouts | ||
* @private | ||
*/ | ||
function _getExpiredTimeouts(time) { | ||
var i = 0; | ||
while (i < timeouts.length && ((timeouts[i].time <= time) || !isFinite(timeouts[i].time))) { | ||
i++; | ||
} | ||
var expired = timeouts.splice(0, i); | ||
if (deterministic == false) { | ||
// the array with expired timeouts is in deterministic order | ||
// shuffle them | ||
util.shuffle(expired); | ||
} | ||
return expired; | ||
} | ||
/** | ||
* Reschedule all queued timeouts | ||
@@ -445,7 +479,3 @@ * @private | ||
// 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); | ||
var expired = _getExpiredTimeouts(time); | ||
// note: expired.length can never be zero (on every change of the queue, we reschedule) | ||
@@ -452,0 +482,0 @@ |
@@ -21,1 +21,15 @@ | ||
} | ||
/** | ||
* Shuffle an array | ||
* | ||
* + Jonas Raoni Soares Silva | ||
* @ http://jsfromhell.com/array/shuffle [v1.0] | ||
* | ||
* @param {Array} o Array to be shuffled | ||
* @returns {Array} Returns the shuffled array | ||
*/ | ||
exports.shuffle = function (o){ | ||
for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); | ||
return o; | ||
}; |
{ | ||
"name": "hypertimer", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Time control for simulations", | ||
@@ -29,2 +29,3 @@ "author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)", | ||
"mocha": "^1.18.2", | ||
"seed-random": "^2.2.0", | ||
"uglify-js": "^2.4.15", | ||
@@ -31,0 +32,0 @@ "uglifyjs": "^2.3.6", |
hypertimer | ||
========== | ||
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 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). Hypertimer makes it possible to run in [hypertime](http://en.wikipedia.org/wiki/Hypertime) or in discrete time (a [discrete event simulation](http://en.wikipedia.org/wiki/Discrete_event_simulation)). | ||
@@ -11,3 +11,3 @@ Hypertimer offers basic functionality to control time: | ||
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. | ||
These functions are compatible with JavaScript's built-in functions `Date.now()`, `setTimeout()`, and `setInterval()`, but there is an important difference: they can run at a different moment in time and at a different rate. | ||
@@ -77,2 +77,5 @@ Hypertimer runs on node.js and on any modern browser (Chrome, FireFox, Opera, Safari, IE9+). | ||
var timer3 = hypertimer({rate: 'discrete'}); | ||
// create a hypertimer running in discrete time and non-deterministic behavior | ||
var timer4 = hypertimer({rate: 'discrete', deterministic: false}); | ||
``` | ||
@@ -193,3 +196,3 @@ | ||
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. | ||
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. When non-deterministic order is desired, the configuration option `deterministic` can be set to `false`. | ||
@@ -247,5 +250,6 @@ ```js | ||
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. | ||
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. | ||
deterministic | boolean | true | If true, (default) events taking place at the same time are executed in a deterministic order: in the same order they where created. If false, they are executed in a randomized order. | ||
@@ -280,4 +284,6 @@ Example: | ||
- `rate: Number | 'discrete'` | ||
- `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. | ||
- `deterministic: boolean` | ||
If true (default), events taking place at the same time are executed in a deterministic order: in the same order they where created. If false, they are executed in a randomized order. | ||
@@ -350,2 +356,3 @@ - **`continue()`** | ||
- Implement support for non-deterministic behavior. | ||
- Implement a scalable solution to synchronize hypertimers running in different processes. | ||
@@ -352,0 +359,0 @@ - Use high precision timers and time functions. |
@@ -5,2 +5,3 @@ // NOTE: all timeouts should have time differences of at least 50ms to be | ||
var async = require('async'); | ||
var seed = require('seed-random'); | ||
var hypertimer = require('../lib/hypertimer'); | ||
@@ -39,3 +40,3 @@ | ||
var timer = hypertimer(); | ||
assert.deepEqual(timer.config(), {rate: 1}); | ||
assert.deepEqual(timer.config(), {rate: 1, deterministic: true}); | ||
}); | ||
@@ -1051,2 +1052,75 @@ | ||
describe('determinism', function () { | ||
var originalRandom; | ||
before(function () { | ||
// replace the original Math.random with a reproducible one | ||
// FIXME: for util.shuffle, we should replace Math.random with a reproducible one but this seems not to work. | ||
originalRandom = Math.random; | ||
Math.random = seed('key'); | ||
}); | ||
after(function () { | ||
// restore the original random function | ||
Math.random = originalRandom; | ||
}); | ||
it('configure deterministic option', function () { | ||
var timer = hypertimer({deterministic: true}); | ||
assert.equal(timer.config().deterministic, true); | ||
timer.config({deterministic: false}); | ||
assert.equal(timer.config().deterministic, false); | ||
timer.config({deterministic: true}); | ||
assert.equal(timer.config().deterministic, true); | ||
}); | ||
it('should execute timeouts in deterministic order', function (done) { | ||
var timer = hypertimer({rate: 'discrete', deterministic: true}); | ||
var ids = []; | ||
var logs = []; | ||
for (var i = 0; i < 1000; i++) { | ||
(function () { | ||
var id = timer.setTimeout(function () { | ||
logs.push(id); | ||
}, 1000); | ||
ids.push(id); | ||
})(); | ||
} | ||
timer.setTimeout(function () { | ||
// the timeouts should have been executed in the order they where added | ||
assert.deepEqual(ids, logs); | ||
done(); | ||
}, 2000); | ||
}); | ||
it('should execute timeouts in non-deterministic order', function () { | ||
var timer = hypertimer({rate: 'discrete', deterministic: false}); | ||
var ids = []; | ||
var logs = []; | ||
for (var i = 0; i < 1000; i++) { | ||
(function () { | ||
var id = timer.setTimeout(function () { | ||
logs.push(id); | ||
}, 1000); | ||
ids.push(id); | ||
})(); | ||
} | ||
timer.setTimeout(function () { | ||
// the timeouts should have been executed in the order they where added | ||
assert.notDeepEqual(ids, logs); | ||
done(); | ||
}, 2000); | ||
}); | ||
}); | ||
// TODO: test with a numeric time instead of "real" Dates, timer.setTime(0), rate='discrete', and timeouts like timer.timeout(cb, 1) | ||
@@ -1053,0 +1127,0 @@ |
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
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
118634
2198
416
10