hypertimer
v3.0.0
Published
Time control for simulations
Downloads
100
Maintainers
Readme
hypertimer
Hypertimer offers time control for simulations and animations. Hypertimer can be used to:
- Run in an unpaced mode for simulations: the time jumps from scheduled event to the next scheduled event, unrolling all events as fast as possible.
- Run in a paced mode for animations or real-time applications: the time proceeds at a continuous, configurable rate. Time can run at a faster or slower pace than real-time, and can run in the past, current, or future.
Hypertimer offers basic functionality to control time:
- Configure pacing, rate, and time on construction or via the function
config()
. - Get simulated time using functions
getTime()
ornow()
. - Schedule events using functions
setTimeout()
,setInterval()
, andsetTrigger()
.
These functions are compatible with JavaScript's built-in functions Date.now()
, setTimeout()
, and setInterval()
, with the difference that they use a simulated time rather than the system time.
Hypertimer enables writing code which can be used interchangeably in simulations as well as for real-time applications. For example, a process could predict its future state by running a simulation of itself, given a current state, its own behavior, and a model of the surrounding world.
Hypertimer runs on node.js and on any modern browser (Chrome, FireFox, Opera, Safari, IE9+).
Install
Install via npm:
npm install hypertimer
Install via bower:
bower install hypertimer
Load
node.js
var hypertimer = require('hypertimer');
var timer = hypertimer({
rate: 1,
time: new Date(2014, 0, 1)
});
browser
<!doctype html>
<html>
<head>
<script src="./dist/hypertimer.min.js"></script>
</head>
<body>
<script>
var timer = hypertimer({
rate: 1,
time: new Date(2014, 0, 1)
});
</script>
</body>
</html>
Use
Configuring a hypertimer
A hypertimer can run in two modes:
- paced: time proceeds at a continuous, configurable rate.
- unpaced: run as fast as possible, jumping from event to event.
Hypertimer as configuration options to set pacing, rate, time, and determinism of the timer.
// 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, same speed as real-time)
var timer2 = hypertimer();
// adjust the rate later on
timer2.config({rate: 1/2});
// create a hypertimer running discrete events as fast as possible (unpaced)
// (time will jump from scheduled event to scheduled event)
var timer3 = hypertimer({paced: false});
// create a hypertimer running discrete events with non-deterministic behavior
var timer4 = hypertimer({paced: false, deterministic: false});
Getting and setting time
Hypertimer offers functions to get and set time:
// create a hypertimer with the initial time at 14st of February 2015
var timer = hypertimer({time: '2015-01-14T12:00:00.000Z'});
// change the time to the 1st of January 2050
timer.config({time: '2050-01-01T12:00:00.000Z'});
// 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.
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, afirstTime
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 configuration, 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.
// create a hypertimer running ten times faster than real-time,
// start the timer at 1st of January 2050
var timer = hypertimer({
rate: 10,
time: 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);
Pacing
When running in paced mode, time proceeds at a continuous, configurable rate. Between events, the timer is just waiting until the clock reaches the time of the next event. This is needed for animations, but for simulations this is just wasted time (assuming that the state of the system does not change between events). When running in unpaced mode, the timer will jump from event to the first next event, as fast as possible.
To run in unpaced mode, configure hypertimer with paced: false
. Hypertimer ensures that all scheduled events are executed in a deterministic order by default. When non-deterministic order is desired, the configuration option deterministic
can be set to false
.
In the example below, the application will immediately output 'done!' and not after a delay of 10 seconds, because the timer is running in unpaced mode and immediately jumps to the first next event.
// create a hypertimer running discrete events
var timer = hypertimer({paced: false});
var delay = 10000;
timer.setTimeout(function () {
console.log('Timeout A');
timer.setTimeout(function () {
console.log('Timeout B');
}, delay);
}, delay);
// 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.
// asynchronous timeout
timer.setTimeout(function (done) {
// ... do something
done(); // call done when done
}, delay);
An example of using an asynchronous timeout:
// create a hypertimer running discrete events,
// jumping from event to the next event.
var timer = hypertimer({paced: false});
// 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 it can continue with the next event.
done();
});
}, 10000);
// Output:
// Timeout A
// (async action is executed here)
// Timeout B
Synchronization
Hypertimers running on multiple machines can be synchronized in a master/slave configuration. Multiple slaves can connect to a master via a WebSocket. The slaves will synchronize their time and configuration with that of the master.
WARNING: this is an experimental feature, and currently only supportes *paced* mode.
To create a master hypertimer, specify a port in the configuration. The hypertimer will open a websocket on this port.
// create a master hypertimer listening on port 8081
var masterTimer = hypertimer({port: 8081});
To connect to a master hypertimer, specify the url of the master in the property master
. The created timer will than act as a slave and synchronize retrieves configuration and time from the master.
// create a slave hypertimer connected to the master on port 8081
var slaveTimer = hypertimer({master: 'ws://localhost:8081'});
Use the destroy()
method to neatly shutdown master and slave timers:
// close all connections, clear all timeouts
masterTimer.destroy();
slaveTimer.destroy();
Examples
Examples can be found here:
https://github.com/enmasseio/hypertimer/tree/master/examples
API
Construction
A hypertimer is constructed as:
hypertimer([options])
By default, a new hypertimer runs with real-time speed and time.
Available options:
Name | Type | Default | Description
------------- | ----------------------------- | --------- | -----------
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.
master | string | null | The url of a master hypertimer, for example "ws://localhost:8081"
. If configured, the hypertimer will run as a slave, and synchronize it's configuration and time with its masters configuration and time.
paced | boolean | true
| Mode for pacing of time. When paced, the time proceeds at a continuous, configurable rate, useful for animation purposes. When unpaced, the time jumps immediately from scheduled event to the next scheduled event.
port | number | null | If provided, the hypertimer will open a websocket on the given port. The hypertimer will than act as a master. Multiple hypertimer slaves can connect to a master.
rate | number | 1 | The rate of progress of time with respect to real-time. Rate must be a positive number. For example when 2, the time of the hypertimer runs twice as fast as real-time. Only applicable when option paced is true.
time | number, Date, or ISO string | null
| Sets the simulation time. If not configured, a hypertimer is instantiated with the system time.
Example:
var timer = hypertimer({rate: 10});
Properties
running
True when the timer is running, false when paused. See also functions
pause()
andcontinue()
.
Methods
clear()
Clear all running timeouts.
clearTimeout(timeoutId: number)
Cancel a timeout.
clearInterval(intervalId: number)
Cancel an interval.
clearTrigger(triggerId: number)
Cancel a trigger.
config([options: Object]): Object
Change the configuration options of the hypertimer, and/or retrieve the current configuration. Available options:
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.
paced: boolean
Mode for pacing of time. When paced (default), the time proceeds at a continuous, configurable rate. When unpaced, the time jumps immediately from scheduled event to the next scheduled event.
rate: number
The rate of progress of time with respect to real-time. Rate must be a positive number, and is 1 by default. For example when 2, the time of the hypertimer runs twice as fast as real-time. Only applicable when option paced is true.
time: number | Date | String
Set a simulation time.
continue()
Continue the timer when paused. The state of the timer can be retrieved via the property
running
. See alsopause()
.destroy()
Destroy the hypertimer. Clears all timeouts, and closes any connection to master and slave hypertimers.
getTime(): Date
Returns the current time of the hypertimer as Date. See also
now()
. The time can be set usingconfig({time: ...})
.list()
Returns a list with the id's of all running timeouts.
now() : number
Returns the current time of the timer as a number. See also
getTime()
.pause()
Pause the timer. The state of the timer can be retrieved via the property
running
. See alsocontinue()
.setInterval(callback: Function, interval: number [, firstTime: Date | number | ISOString])
Trigger a callback every interval. Optionally, a start date can be provided to specify the first time the callback must be triggered. The function returns an intervalId which can be used to cancel the trigger using
clearInterval()
. See alsosetTimeout
andsetTrigger
. Parameters:callback: Function
Function executed when delay is exceeded.
interval: number
Interval in milliseconds. When interval is smaller than zero or is infinity, the interval will be set to zero and triggered with a maximum rate.
[firstTime: Date | number]
An optional absolute moment in time (Date) when the callback will be triggered the first time. By default,
firstTime = now() + interval
.
setTimeout(callback: Function, delay: number) : number
Set a timeout, which is triggered when the timeout occurs in hyper-time. See also
setTrigger
andsetInterval
. The function returns a timeoutId, which can be used to cancel the timeout usingclearTimeout(timeoutId)
. The parameters are:callback: Function
Function executed when the delay is exceeded
delay: number
The delay in milliseconds. When the delay is smaller or equal to zero, the callback is triggered immediately.
setTrigger(callback: Function, time: Date | number | ISOString ) : number
Set a trigger, which is triggered when the timeout occurs in hyper-time. See also
getTimeout
. The function returns a triggerId which can be used to cancel the trigger usingclearTrigger()
. The parameters are:callback: Function
Function executed when delay is exceeded.
time: Date | number
An absolute moment in time (Date) when the callback will be triggered. When the date is a Date in the past, the callback is triggered immediately.
toString() : String
Return a string representation of the current hyper-time, equal to
timer.getTime().toString()
.valueOf() : Date
Get the value of the hypertimer, returns the current time of the timer as Date.
Events
Available options:
Event | Description --------- | ----------- config | Triggered when the configuration is changed by the master timer. Called with the new configuration as first argument, and the previous configuration as second argument. error | Triggered when an error occurred, for example when one of the timout callbacks throws an Error.
Methods:
emit(event: string, ...args: *)
Emit an event.
on(event: string, callback: function)
Register a listener for an event
once(event: string, callback: function)
Register a listener for an event, which will be invoked only once and is removed after that.
off(event: string, callback: function)
Unregister a listener for an event
Example:
var timer = hypertimer();
timer.on('config', function (curr, prev)) {
console.log('config changed. old config:', prev, ', new config:', curr);
});
timer.on('error', function (err)) {
console.log('Error:', err);
});
Protocol
TODO: describe the protocol used to communicate between master and slaves.
Roadmap
- Implement a scalable solution to synchronize hypertimers running in different processes.
- Use high precision timers and time functions.
Build
To build the library from source, clone the project from github
git clone git://github.com/enmasseio/hypertimer.git
To install all dependencies and build the library, run npm install
in the
root of the project.
cd hypertimer
npm install
Then, the project can be build running:
npm run build
This generates the files ./dist/hypertimer.js
and ./dist/hypertimer.min.js
,
which can be used in the browser.
To automatically rebuild on changes in the source files, once can use
npm run watch
Test
To execute tests for the library, install the project dependencies once:
npm install
Then, the tests can be executed:
npm test
To test code coverage of the tests:
npm run coverage
To see the coverage results, open the generated report in your browser:
./coverage/lcov-report/index.html
License
Copyright (C) 2014-2015 Almende B.V., http://almende.com
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.