mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 19:20:55 +00:00
Merge pull request #2079 from bjoluc/preload
Implement `preload` flag for plugin parameters
This commit is contained in:
commit
5152f52977
@ -170,6 +170,13 @@ export class JsPsych {
|
|||||||
|
|
||||||
document.documentElement.setAttribute("jspsych", "present");
|
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();
|
this.startExperiment();
|
||||||
await this.finished;
|
await this.finished;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { JsPsych } from "./JsPsych";
|
import { JsPsych } from "./JsPsych";
|
||||||
|
import { parameterType } from "./modules/plugins";
|
||||||
import {
|
import {
|
||||||
repeat,
|
repeat,
|
||||||
sampleWithReplacement,
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,14 +47,15 @@ type ParameterTypeMap = {
|
|||||||
13: any; // TIMELINE
|
13: any; // TIMELINE
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ParameterInfo {
|
export interface ParameterInfo {
|
||||||
type: keyof ParameterTypeMap;
|
type: keyof ParameterTypeMap;
|
||||||
array?: boolean;
|
array?: boolean;
|
||||||
pretty_name?: string;
|
pretty_name?: string;
|
||||||
default?: any;
|
default?: any;
|
||||||
|
preload?: "image" | "video" | "audio";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParameterInfos {
|
export interface ParameterInfos {
|
||||||
[key: string]: ParameterInfo;
|
[key: string]: ParameterInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
|
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";
|
import { pressKey, startTimeline } from "../utils";
|
||||||
|
|
||||||
describe("loop function", () => {
|
describe("loop function", () => {
|
||||||
@ -508,3 +509,89 @@ describe("add node to end of timeline", () => {
|
|||||||
pressKey("a");
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user