Flow control

This part of the library provides components that control the sequence of events during the experiment. It is thus responsible for the flow of Components throughout the experiment. For example, a flow.Sequence() groups several components together to be run sequentially, a flow.Loop() repeats single components, and a flow.Parallel() runs multiple components in parallel.


Sequence

class flow.Sequence([options])

A flow.Sequence() runs a group of components one after another. These can be any type of component – screens or other stimuli, and even other sequences or loops.

A typical experiment will often, on the highest, most coarse level, consist of a single sequence that encompasses the entirety of the experiment – instructions, experimental task, and debriefing – and runs it in sequence.

Sequences are, however, also useful on a much more granular level – for example, a single trial can be built as a sequence of an inter-stimulus interval, a fixation dot, and the stimulus itself.

flow.Sequence.options.content

List of components to run in sequence ([])

When a flow.Sequence() is constructed, the most important option is the content, which is a list of ‘sub-components’ that the sequence is comprised of. A basic example might be the following [1]:

const proclaimers = new lab.flow.Sequence({
  content: [
    new lab.html.Screen({ content: 'And',   timeout: 500 }),
    new lab.html.Screen({ content: 'I',     timeout: 500 }),
    new lab.html.Screen({ content: 'will',  timeout: 500 }),
    new lab.html.Screen({ content: 'walk',  timeout: 500 }),
    new lab.html.Screen({ content: 'five',  timeout: 500 }),
    new lab.html.Screen({ content: 'hun-',  timeout: 500 }),
    new lab.html.Screen({ content: '-dred', timeout: 500 }),
    new lab.html.Screen({ content: 'miles', timeout: 500 }),
  ],
})

proclaimers.run()

When the sequence is prepared or run, the constituent parts are prepared and run in sequence.

flow.Sequence.options.shuffle

Run the content components in random order (false)

If this option is set to true, the content of the sequence is shuffled during the prepare phase.

flow.Sequence.options.handMeDowns

List of options passed to nested components (['datastore', 'el', 'debug'])

The options specified as handMeDowns are transferred to nested components during the prepare phase. This option is largely for convenience, and designed to decrease the amount of repetition when all nested components behave similarly – typically, nested components share the same data storage and output element, so these are passed on by default. Similarly, the debug mode is easiest to set on the topmost component, and will automatically propagate to include all other components.


Loop

class flow.Loop([options])

A flow.Loop() repeats the same (single) template component, while varying parameters between repetitions. Keeping with our example above:

const template = new lab.html.Screen({
  content: '${ parameters.lyrics      }', // parameters substituted ...
  timeout: '${ parameters.beats * 600 }', // ... during preparation
})

const spandauBallet = new lab.flow.Loop({
  template: template,
  templateParameters: [
    /* ... */
    { lyrics: 'So true, funny how it seems', beats: 7 },
    { lyrics: 'Always in time, but never in line for dreams', beats: 10 },
    { lyrics: 'Head over heels when toe to toe', beats: 8 },
    { lyrics: 'This is the sound of my soul', beats: 8 },
    /* ... */
  ]
})

In many cases, the template will not be a single html.Screen(), but rather a flow.Sequence(), so that multiple screens can be repeated on each iteration.

flow.Loop.options.template

Content for each repetition of the loop.

There are several ways in which this option can be used:

  • First it can be a single component of any type, an html.Screen(), (most likely) a flow.Sequence() or even another flow.Loop(). This component will be cloned for each iteration, and the parameters substituted on each copy so that the repetitions can differ from another.
  • Second, it can be a function that creates and returns the component for each iteration. This function will receive each set of templateParameters in turn as a first argument (and, optionally, the index as a second argument, and the loop component itself as a third). The advantage of this method is a greater flexibility: Additional logic can be used at every step to customize every iteration.
flow.Loop.options.templateParameters

Array of parameter sets for each individual repetition ([]).

This option defines the parameters for every repetition of the template. Each individual set of parameters is defined as an object with name/value pairs, and these objects are combined to an array:

const stroopTrials = [
  { color: 'red', word: 'red' },
  { color: 'red', word: 'blue' },
  /* ... */
]

const stroopTask = new lab.flow.Loop({
  template: /* ... */,
  templateParameters: stroopTrials,
})
flow.Loop.options.sample.n

The number of samples to draw from the templateParameters. If not specified, the number of samples defaults to the number of available parameter sets.

Thus, in sequential, draw and draw-shuffle mode, leaving this option unset ensures that all parameter sets are used.

flow.Loop.options.sample.mode

How to sample from the iterations while constructing the loop. Several distinct modes are available (the default is draw-shuffle):

  • sequential: Run through the parameter sets in order. If all parameter sets have been run through, but more samples are required, re-start from the top.
  • draw: Sample from all parameter sets without replacement. When all sets have been drawn, start over.
  • draw-shuffle: Like draw, but shuffle the result when oversampling.
  • draw-replace: Sample from the parameter sets with replacement.

The draw and draw-shuffle modes only differ when the number of samples, n exceeds the number of available parameter sets. In this case, the draw algorithm will lead to blocks of shuffled parameter sets. For example, given three parameter sets [1, 2, 3], over-sampling five sets might lead to a resulting order of [2, 3, 1, 2, 1] (notice that all sets are exhausted before the first repetition occurs). The draw-shuffle mode adds a shuffle step after sampling that breaks this restiction (which might lead to the result of [2, 3, 3, 2, 1]). Thus in draw-shuffle mode, the block structure is broken up, but the frequency of parameter sets will be roughly equal.

flow.Loop.options.handMeDowns

Options to pass to subordinate components (see flow.Sequence()).


Parallel

class flow.Parallel([options])

A flow.Parallel() component runs other components concurrently, in that they are started together. Browser engines do not currently support literally parallel processing, but an effort has been made to approximate parallel processing as closely as possible.

flow.Parallel.options.content

List of components to run in parallel ([])

flow.Parallel.options.mode

How to react to nested elements ending ('race')

If this option is set to 'race', the entire flow.Parallel() component ends as soon as the first nested component ends. In this case, any remaining components are shut down automatically (by calling end()). If the mode is set to 'all', it waits until all nested items have ended by themselves.

flow.Parallel.options.handMeDowns

Options passed to nested elements (see flow.Sequence()).


[1]In apology to our British colleagues: This is, obviously, a grossly distorted version of the classic anthem: According to XKCD, the song has 131.9 beats per minute; the appropriate adjustment, as well as the Scottish accent, are left as an exercise for our esteemed readers. We hereby also pledge to award special prizes to any colleagues who use the library for interdepartmental karaoke (video proof required).