/**
* Experiment Handler
*
* @author Alain Pitiot
* @version 3.0.8
* @copyright (c) 2019 Ilixa Ltd. ({@link http://ilixa.com})
* @license Distributed under the terms of the MIT License
*/
import { PsychObject } from '../util/PsychObject';
import { MonotonicClock } from '../util/Clock';
import { PsychoJS } from '../core/PsychoJS';
import * as util from '../util/Util';
/**
*
An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful
* for generating a single data file from an experiment with many different loops (e.g. interleaved
* staircases or loops within loops.
*
* @name module:data.ExperimentHandler
* @class
* @extends PsychObject
* @param {Object} options
* @param {PsychoJS} options.psychoJS - the PsychoJS instance
* @param {string} options.name - name of the experiment
* @param {Object} options.extraInfo - additional information, such as session name, participant name, etc.
*/
export class ExperimentHandler extends PsychObject {
/**
* Getter for experimentEnded.
*
* @name module:core.Window#experimentEnded
* @function
* @public
*/
get experimentEnded() { return this._experimentEnded; }
/**
* Setter for experimentEnded.
*
* @name module:core.Window#experimentEnded
* @function
* @public
*/
set experimentEnded(ended) { this._experimentEnded = ended; }
constructor({
psychoJS,
name,
extraInfo
} = {}) {
super(psychoJS, name);
this._addAttributes(ExperimentHandler, extraInfo);
// loop handlers:
this._loops = [];
this._unfinishedLoops = [];
// data dictionaries (one per trial) and current data dictionary:
this._trialsKeys = [];
this._trialsData = [];
this._currentTrialData = {};
this._experimentEnded = false;
}
/**
* Add a loop.
*
*
The loop might be a {@link TrialHandler}, for instance.
*
Data from this loop will be included in the resulting data files.
*
* @name module:data.ExperimentHandler#addLoop
* @function
* @public
* @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler
*/
addLoop(loop) {
this._loops.push(loop);
this._unfinishedLoops.push(loop);
loop.experimentHandler = this;
}
/**
* Remove the given loop from the list of unfinished loops, e.g. when it has completed.
*
* @name module:data.ExperimentHandler#removeLoop
* @function
* @public
* @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler
*/
removeLoop(loop) {
const index = this._unfinishedLoops.indexOf(loop);
if (index !== -1)
this._unfinishedLoops.splice(index, 1);
}
/**
* Add the key/value pair.
*
*
Multiple key/value pairs can be added to any given entry of the data file. There are
* considered part of the same entry until a call to {@link nextEntry} is made.
*
* @name module:data.ExperimentHandler#addData
* @function
* @public
* @param {Object} key - the key
* @param {Object} value - the value
*/
addData(key, value) {
if (this._trialsKeys.indexOf(key) === -1) {
this._trialsKeys.push(key);
}
// turn arrays into their json equivalent:
if (Array.isArray(value))
value = JSON.stringify(value);
this._currentTrialData[key] = value;
}
/**
* Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData}
* will be associated with the next trial.
*
* @name module:data.ExperimentHandler#nextEntry
* @function
* @public
*/
nextEntry() {
// fetch data from each (potentially-nested) loop:
for (let loop of this._unfinishedLoops) {
const attributes = ExperimentHandler._getLoopAttributes(loop);
for (let a in attributes)
if (attributes.hasOwnProperty(a))
this._currentTrialData[a] = attributes[a];
}
// add the extraInfo dict to the data:
for (let a in this.extraInfo)
if (this.extraInfo.hasOwnProperty(a))
this._currentTrialData[a] = this.extraInfo[a];
this._trialsData.push(this._currentTrialData);
this._currentTrialData = {};
}
/**
* Save the results of the experiment.
*
*
*
For an experiment running locally, the results are offered for immediate download.
*
For an experiment running on the server, the results are uploaded to the server.