| export const abortError = () => new DOMException("Playback stopped", "AbortError"); |
|
|
| |
| |
| 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()); }, |
| }; |
| } |
|
|