timers - RedwoodAdmin/RedwoodFramework GitHub Wiki
There is often a need to use timers in experiments for things like automatically advancing the period after a certain amount of time, or restricting input for a certain amount of time, etc. However, as mentioned in the Gotchas page, the use of timers can be problematic. The reason for this is as follows:
Redwood is based on a messaging framework where clients (subject and admin browsers) send messages to the router (running on the redwood server). The router then echoes these messages to all other clients. The state of a running experiment is defined by the set of messages that have been sent from clients to the server. When a client page is refreshed, the router will resend the entire message queue to that client and it is expected that this will be sufficient to restore the state of the client. Any functionality that executes in response to events other than messages (eg. user input events, JavaScript timer events, etc) will not be executed when the message queue is replayed. For this reason it is imperative that all functionality that involves a change of subject state should be executed in response to a message.
Redwood provides two timing-related features to replace the use of native timing functions.
The simpler of the two is the timeout function RedwoodSubject.timeout(...)
which behaves in the same way as the JavaScript setTimeout function except that it is integrated into the redwood framework so that things like page refreshes are handled safely. The timeout function is useful for adhoc delays and timing-related functions and does not include synchronization between subjects. A great example of this is on the "wait" page of the example experiment, Ultimatum, where subjects are given 5 seconds to look at an image before providing an estimate of the number of objects they see. They code snippet is provided below:
rs.on("ready", function() {
$scope.state.ready = true;
drawPlot();
rs.timeout(function() {
rs.trigger("time_up");
}, 5000);
});
If a normal, non-redwood timeout function had been used, subjects could simply refresh their browser to get another 5 seconds to look at the image. However the redwood timeout function handles these scenarios correctly.
For more demanding requirements such as a continuous time-based experiment, Redwood includes a SynchronizedStopwatch service. This service provides a stream of timed ticks that are synchronized between multiple subjects. SynchronizedStopwatch is a separate service which needs to be included in the experiment controller when required, as shown in the Continuous Matrix example:
Redwood.controller("SubjectCtrl", ["$rootScope", "$scope", "RedwoodSubject", 'SynchronizedStopWatch', function($rootScope, $scope, rs, SynchronizedStopWatch) {
An instance of a stopwatch is created using the getInstance function. The stopwatch can then be configured by setting a number of parameters as follows:
$scope.clock = SynchronizedStopWatch.instance()
.frequency(CLOCK_FREQUENCY)
.onTick(function(tickNumber, timeLapsed_s, timeRemaining_s) { /*do stuff*/ })
.duration(rs.config.period_length_s)
.onComplete(function() {
rs.trigger("simulation_complete");
});
-
frequency
takes a number in Hz, indicating the number of 'ticks' per second. -
onTick
takes a function which is called on every tick with the tick number as an argument. -
duration
takes a number indicating how many seconds the stopwatch should run for. -
onComplete
takes a function which is called when the stopwatch is complete (immediately after the final tick) -
subjects
takes an array of subject id's specifying which subjects to synchronize with.
Once the stopwatch has been configured it can be started using the start
method:
$scope.clock.start();
It can also be subsequently paused and resumed using:
$scope.clock.pause();
$scope.clock.resume();
The stopwatch starts at t = 0
meaning the first call to the onTick
callback will have arguments (0, 0, duration)
.
If the stopwatch is not running, it can be manually advanced by a single tick using the method doNextTick
. This will cause the next tick to be executed including synchronizing subjects and calling the onTick
callback. This is sometimes useful if the first tick (t = 0) needs to be executed for initialization purposes before the timer is actually started.
A convenience method, getDurationInTicks
is also available to provide the number of ticks in the given duration.
NOTE: The stopwatch will complete after an integer number of ticks. The number of ticks is calculated as: Math.floor(duration * frequency)
.