/ proof-of-concept / steelsim21
Sign in
proof-of-concept / steelsim21
steelsim21 / src / cli / run.ts
74 lines · 2.5 KBCodeBlameHistory
c8442c5Command-line renderer (Node WebGPU via Dawn) and gallery generatorJeremy Magland 1// Runs one simulation to completion in Node (Dawn WebGPU) and returns the pressure signal.
3import { defaultParams, type PhysParams } from "../params.ts";
4import { Simulation } from "../sim/simulation.ts";
6let devicePromise: Promise<GPUDevice> | null = null;
8export function getDevice(): Promise<GPUDevice> {
9 if (!devicePromise) {
10 devicePromise = (async () => {
f8f7491CLI: import the optional Dawn binding by name so the type check passes without itJeremy Magland 11 // Loaded lazily and by name: the prebuilt binding is an optional dependency
12 // (it needs a recent glibc) and is only used by --engine dawn.
13 const moduleName = "webgpu";
14 const { create, globals } = (await import(moduleName)) as {
15 create: (args: string[]) => { requestAdapter(): Promise<GPUAdapter | null> };
16 globals: Record<string, unknown>;
17 };
d94838aLoad the Dawn binding lazily so the Chrome engine runs without itJeremy Magland 18 Object.assign(globalThis, globals);
c8442c5Command-line renderer (Node WebGPU via Dawn) and gallery generatorJeremy Magland 19 const gpu = create([]);
b640535CLI: Chrome engine (Playwright) as default, Dawn optional; gallery fixesJeremy Magland 20 const adapter = await gpu.requestAdapter();
c8442c5Command-line renderer (Node WebGPU via Dawn) and gallery generatorJeremy Magland 21 if (!adapter) throw new Error("No WebGPU adapter");
b640535CLI: Chrome engine (Playwright) as default, Dawn optional; gallery fixesJeremy Magland 22 const device = await adapter.requestDevice();
f8f7491CLI: import the optional Dawn binding by name so the type check passes without itJeremy Magland 23 device.addEventListener("uncapturederror", (ev: Event) => {
b640535CLI: Chrome engine (Playwright) as default, Dawn optional; gallery fixesJeremy Magland 24 console.error("WebGPU error:", (ev as GPUUncapturedErrorEvent).error.message);
25 });
26 return device;
28 }
29 return devicePromise;
32export interface RunResult {
33 params: PhysParams;
34 audio: Float32Array;
35 fs: number;
36 steps: number;
37 seconds: number;
38 grid: { nx: number; ny: number };
39 dt: number;
40 maxContactForce: number;
43export async function runSimulation(overrides: Partial<PhysParams>, log: (s: string) => void = () => {}): Promise<RunResult> {
44 const params: PhysParams = { ...defaultParams, ...overrides };
45 const device = await getDevice();
46 const sim = new Simulation(device, params);
47 const t0 = performance.now();
48 let lastLog = t0;
49 let maxContactForce = 0;
50 while (sim.phase !== "done") {
51 await sim.runBatch();
52 const st = sim.status();
53 if (st.stick) maxContactForce = Math.max(maxContactForce, st.stick.maxForce);
54 const now = performance.now();
55 if (now - lastLog > 5000) {
56 lastLog = now;
57 log(` ${st.phase} t=${st.t.toFixed(2)} s, ${(sim.totalSteps / ((now - t0) / 1000) / 1e3).toFixed(0)} k steps/s`);
58 }
59 }
c8442c5Command-line renderer (Node WebGPU via Dawn) and gallery generatorJeremy Magland 61 const seconds = (performance.now() - t0) / 1000;
62 const result: RunResult = {
63 params,
64 audio: sim.processedAudio().slice(),
65 fs: sim.fs,
66 steps: sim.totalSteps,
67 seconds,
68 grid: { nx: sim.grid.nx, ny: sim.grid.ny },
69 dt: sim.dt,
70 maxContactForce,
71 };
b640535CLI: Chrome engine (Playwright) as default, Dawn optional; gallery fixesJeremy Magland 72 // Buffers are left to the garbage collector: explicit destroy() aborts inside the Dawn bindings.
↑↓ move↵openescclose