microduck-reachy-simulator / src /game /simulationTimeline.js
laureen
Withdraw Code Training from published main pending fixes
99e34b4
Raw
History Blame Contribute Delete
1.64 kB
export const abortError = () => new DOMException("Playback stopped", "AbortError");
// Promises advance only when physics advances. No wall-clock callbacks are
// used to guess when a robot, posture transition, or recorded motion finishes.
export function createSimulationTimeline() {
const jobs = new Set();
function run({ duration, update = () => {}, until, maxDuration = Infinity, signal }) {
if (signal?.aborted) return Promise.reject(abortError());
return new Promise((resolve, reject) => {
const job = { elapsed: 0, duration, update, until, maxDuration };
const finish = (error) => {
jobs.delete(job);
signal?.removeEventListener("abort", abort);
error ? reject(error) : resolve();
};
const abort = () => finish(abortError());
job.finish = finish;
signal?.addEventListener("abort", abort, { once: true });
jobs.add(job);
});
}
return {
run,
wait: (duration, signal) => run({ duration, signal }),
until: (until, maxDuration, signal) => run({ until, maxDuration, signal }),
tick(dt) {
for (const job of [...jobs]) {
try {
job.elapsed += dt;
const t = job.duration === undefined ? job.elapsed : Math.min(job.elapsed, job.duration);
job.update(t);
if (job.until ? job.until(job.elapsed) : job.elapsed + 1e-9 >= job.duration) job.finish();
else if (job.elapsed >= job.maxDuration) job.finish(new Error("Robot did not finish its movement"));
} catch (e) { job.finish(e); }
}
},
cancel() { for (const job of [...jobs]) job.finish(abortError()); },
};
}