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:

Limitations

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.

Solutions

Redwood provides two timing-related features to replace the use of native timing functions.

Redwood Timeout

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.

SynchronizedStopwatch Service

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).

⚠️ **GitHub.com Fallback** ⚠️