Merge pull request #2079 from bjoluc/preload

Implement `preload` flag for plugin parameters
This commit is contained in:
bjoluc 2021-08-17 17:01:18 +02:00 committed by GitHub
commit 5152f52977
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 149 additions and 3 deletions

View File

@ -170,6 +170,13 @@ export class JsPsych {
document.documentElement.setAttribute("jspsych", "present");
// Register preloading for the plugins referenced in the timeline
for (const [pluginName, parameters] of this.timeline.extractPreloadParameters()) {
for (const [parameter, type] of Object.entries(parameters)) {
this.pluginAPI.registerPreload(pluginName, parameter, type);
}
}
this.startExperiment();
await this.finished;
}

View File

@ -1,4 +1,5 @@
import { JsPsych } from "./JsPsych";
import { parameterType } from "./modules/plugins";
import {
repeat,
sampleWithReplacement,
@ -533,4 +534,54 @@ export class TimelineNode {
);
}
}
/**
* Extracts a map that, for each of the timeline's (nested) plugins, maps the plugin name to an
* object that maps media parameters to their corresponding `preload` type, if not prevented by a
* `preload: false` flag in a parameter's description.
*/
extractPreloadParameters() {
type PreloadType = "audio" | "image" | "video";
const preloadMap = new Map<string, Record<string, PreloadType>>();
/** Maps parameter types to their corresponding preload type */
const parameterTypeMap = new Map<number, PreloadType>([
[parameterType.AUDIO, "audio"],
[parameterType.IMAGE, "image"],
[parameterType.VIDEO, "video"],
]);
function recurseTimeline(node: TimelineNode) {
const isTimeline = typeof node.timeline_parameters !== "undefined";
if (isTimeline) {
for (const childNode of node.timeline_parameters.timeline) {
recurseTimeline(childNode);
}
} else if (node.trial_parameters.type.info) {
// node is a trial with type.info set
// Get the plugin name and parameters object from the info object
const { name: pluginName, parameters } = node.trial_parameters.type.info;
if (!preloadMap.has(pluginName)) {
preloadMap.set(
pluginName,
Object.fromEntries(
Object.entries<any>(parameters)
// Filter out parameter entries with media types and a non-false `preload` option
.filter(
([_name, { type, preload }]) => parameterTypeMap.has(type) && (preload ?? true)
)
// Map each entry's value to its preload type
.map(([name, { type }]) => [name, parameterTypeMap.get(type)])
)
);
}
}
}
recurseTimeline(this);
return preloadMap;
}
}

View File

@ -47,14 +47,15 @@ type ParameterTypeMap = {
13: any; // TIMELINE
};
interface ParameterInfo {
export interface ParameterInfo {
type: keyof ParameterTypeMap;
array?: boolean;
pretty_name?: string;
default?: any;
preload?: "image" | "video" | "audio";
}
interface ParameterInfos {
export interface ParameterInfos {
[key: string]: ParameterInfo;
}

View File

@ -1,6 +1,7 @@
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
import { initJsPsych } from "../../src";
import { initJsPsych, parameterType } from "../../src";
import { TimelineNode } from "../../src/TimelineNode";
import { pressKey, startTimeline } from "../utils";
describe("loop function", () => {
@ -508,3 +509,89 @@ describe("add node to end of timeline", () => {
pressKey("a");
});
});
describe("TimelineNode", () => {
const createTimelineNode = (parameters) => new TimelineNode(initJsPsych(), parameters);
describe("extractPreloadParameters", () => {
it("works for a single trial", () => {
const preloadMap = createTimelineNode({
type: {
info: {
name: "my-plugin",
parameters: {
one: { type: parameterType.IMAGE },
two: { type: parameterType.VIDEO },
three: { type: parameterType.AUDIO },
four: { type: parameterType.IMAGE, preload: true },
five: { type: parameterType.IMAGE, preload: false },
six: {
type: parameterType.STRING,
// This is illegal! But it should still not be added
preload: true,
},
seven: {},
},
},
},
}).extractPreloadParameters();
expect(preloadMap.get("my-plugin")).toEqual({
one: "image",
two: "video",
three: "audio",
four: "image",
});
});
it("works for a nested timeline", () => {
const preloadMap = createTimelineNode({
timeline: [
{
type: {
info: {
name: "plugin1",
parameters: { one: { type: parameterType.STRING } },
},
},
},
{
type: {
info: {
name: "plugin2",
parameters: { one: { type: parameterType.AUDIO } },
},
},
},
{
timeline: [
{
type: {
info: {
name: "plugin3",
parameters: {
one: { type: parameterType.VIDEO },
two: { type: parameterType.IMAGE },
},
},
},
},
],
},
],
}).extractPreloadParameters();
expect(preloadMap.get("plugin1")).toEqual({});
expect(preloadMap.get("plugin2")).toEqual({ one: "audio" });
expect(preloadMap.get("plugin3")).toEqual({ one: "video", two: "image" });
});
it("ignores trials with a function type", () => {
const preloadMap = createTimelineNode({
type: () => ({ info: { name: "my-dynamic-trial-type" } }),
}).extractPreloadParameters();
expect(preloadMap.size).toEqual(0);
});
});
});