1// Web Worker entry: the two computations that are too slow for the main thread. "modes" is the
2// full eigensolve (seconds to minutes); "gains" is the listener sum, redone whenever the
3// listener or the material changes (a fraction of a second).
4import type { Listener, SimulationConfig } from "../config/schema.ts";
5import { parseModes, serializeModes, type ModesData } from "../modes.ts";
6import { computeGains } from "./gains.ts";
7import { computeModes, type PrecomputeOptions } from "./pipeline.ts";
9export type PrecomputeRequest =
10 | { type: "modes"; token: number; config: SimulationConfig; options: PrecomputeOptions }
11 | { type: "gains"; token: number; buffer?: ArrayBuffer; listener: Listener; freq: Float64Array; phiFactor: number };
13export type PrecomputeReply =
14 | { type: "progress"; token: number; phase: string; frac: number }
15 | { type: "modes"; token: number; buffer: ArrayBuffer; seconds: number }
16 | { type: "gains"; token: number; front: Float32Array; back: Float32Array; seconds: number }
17 | { type: "error"; token: number; message: string };
19/** The worker keeps the modes it was last given so that repeated gain requests, which happen
20 * while a listener slider is being dragged, do not re-send a 12 MB buffer every time. */
21let held: { buffer: ArrayBuffer; data: ModesData } | null = null;
23self.onmessage = (ev: MessageEvent<PrecomputeRequest>) => {
24 const msg = ev.data;
25 const post = (reply: PrecomputeReply, transfer?: Transferable[]) => (self as unknown as Worker).postMessage(reply, transfer ?? []);
26 const t0 = performance.now();
27 try {
28 if (msg.type === "modes") {
29 let lastPost = 0;
30 const data = computeModes(msg.config, msg.options, (phase, frac) => {
31 const t = performance.now();
32 if (t - lastPost > 100 || frac >= 1) {
33 lastPost = t;
34 post({ type: "progress", token: msg.token, phase, frac });
35 }
36 });
37 const buffer = serializeModes(data);
38 held = { buffer, data };
39 post({ type: "modes", token: msg.token, buffer: buffer.slice(0), seconds: (performance.now() - t0) / 1000 });
40 } else {
41 if (msg.buffer) held = { buffer: msg.buffer, data: parseModes(msg.buffer) };
42 if (!held) throw new Error("no modes in the worker: send the buffer with the first gains request");
43 const g = computeGains(held.data, msg.listener, msg.freq, msg.phiFactor);
44 post({ type: "gains", token: msg.token, front: g.front, back: g.back, seconds: (performance.now() - t0) / 1000 },
45 [g.front.buffer, g.back.buffer]);
46 }
47 } catch (err) {
48 post({ type: "error", token: msg.token, message: (err as Error).message ?? String(err) });
49 }
50};