1// Runs one simulation to completion in Node (Dawn WebGPU) and returns the pressure signal.
3import { create, globals } from "webgpu";
4import { defaultParams, type PhysParams } from "../params.ts";
5import { Simulation } from "../sim/simulation.ts";
7let devicePromise: Promise<GPUDevice> | null = null;
9export function getDevice(): Promise<GPUDevice> {
10 if (!devicePromise) {
11 Object.assign(globalThis, globals);
12 devicePromise = (async () => {
13 const gpu = create([]);
14 const adapter = await gpu.requestAdapter();
15 if (!adapter) throw new Error("No WebGPU adapter");
16 const device = await adapter.requestDevice();
17 device.addEventListener("uncapturederror", (ev) => {
18 console.error("WebGPU error:", (ev as GPUUncapturedErrorEvent).error.message);
19 });
20 return device;
21 })();
22 }
23 return devicePromise;
24}
26export interface RunResult {
27 params: PhysParams;
28 audio: Float32Array;
29 fs: number;
30 steps: number;
31 seconds: number;
32 grid: { nx: number; ny: number };
33 dt: number;
34 maxContactForce: number;
35}
37export async function runSimulation(overrides: Partial<PhysParams>, log: (s: string) => void = () => {}): Promise<RunResult> {
38 const params: PhysParams = { ...defaultParams, ...overrides };
39 const device = await getDevice();
40 const sim = new Simulation(device, params);
41 const t0 = performance.now();
42 let lastLog = t0;
43 let maxContactForce = 0;
44 while (sim.phase !== "done") {
45 await sim.runBatch();
46 const st = sim.status();
47 if (st.stick) maxContactForce = Math.max(maxContactForce, st.stick.maxForce);
48 const now = performance.now();
49 if (now - lastLog > 5000) {
50 lastLog = now;
51 log(` ${st.phase} t=${st.t.toFixed(2)} s, ${(sim.totalSteps / ((now - t0) / 1000) / 1e3).toFixed(0)} k steps/s`);
52 }
53 }
54 log(" loop finished");
55 const seconds = (performance.now() - t0) / 1000;
56 const result: RunResult = {
57 params,
58 audio: sim.processedAudio().slice(),
59 fs: sim.fs,
60 steps: sim.totalSteps,
61 seconds,
62 grid: { nx: sim.grid.nx, ny: sim.grid.ny },
63 dt: sim.dt,
64 maxContactForce,
65 };
66 // Buffers are left to the garbage collector: explicit destroy() aborts inside the Dawn bindings.
67 return result;
68}