// Drives a simulator backend chunk by chunk with an adaptive chunk size, pausing and stopping on // request, and reports pressure chunks, diagnostics and progress to the UI. import type { Setup } from "./model/setup.ts"; import type { ChunkResult, Diagnostics, SimulatorBackend } from "./model/simulator.ts"; export interface RunCallbacks { onChunk(res: ChunkResult): void; onDiagnostics(d: Diagnostics): void; onProgress(samplesDone: number, wallSeconds: number): void; onDone(stopped: boolean): void; } export class Runner { private paused = false; private stopped = false; private resume: (() => void) | null = null; private chunk = 512; readonly backend: SimulatorBackend; readonly setup: Setup; private readonly cb: RunCallbacks; constructor(backend: SimulatorBackend, setup: Setup, cb: RunCallbacks) { this.backend = backend; this.setup = setup; this.cb = cb; } async run() { const { setup, backend } = this, t0 = performance.now(); let lastDiag = t0; while (!this.stopped && backend.samplesDone < setup.nSamples) { if (this.paused) await new Promise((r) => { this.resume = r; }); if (this.stopped) break; const n = Math.min(this.chunk, setup.nSamples - backend.samplesDone); const tc = performance.now(); const res = await backend.runChunk(n); const dtc = (performance.now() - tc) / 1000; // aim at about 0.4 s of wall time per chunk, in multiples of 128 samples, at most 8192 this.chunk = Math.max(128, Math.min(8192, Math.round((n * 0.4) / Math.max(dtc, 1e-3) / 128) * 128)); this.cb.onChunk(res); this.cb.onProgress(backend.samplesDone, (performance.now() - t0) / 1000); if (performance.now() - lastDiag > 1000 || backend.samplesDone >= setup.nSamples) { this.cb.onDiagnostics(await backend.diagnostics()); lastDiag = performance.now(); } await new Promise((r) => setTimeout(r, 0)); } this.cb.onDone(this.stopped); } pause() { this.paused = true; } unpause() { this.paused = false; this.resume?.(); this.resume = null; } stop() { this.stopped = true; this.unpause(); } get isPaused() { return this.paused; } }