// Web Worker that runs the Bloch simulation off the main thread. It parses the // .seq text, runs simulate(), streams throttled progress messages, and posts // the raw signal back. Cancellation is handled by the main thread terminating // the worker (no in-loop cancel needed). import { parseSeq } from '../seq/parseSeq.ts' import { simulate } from './simulate.ts' import type { SimProgress } from './simulate.ts' import type { Phantom } from '../phantom/phantomTypes.ts' export interface RunMessage { type: 'run' seqText: string phantom: Phantom } export type WorkerOut = | { type: 'progress'; progress: SimProgress; elapsedMs: number } | { type: 'done' re: Float64Array im: Float64Array numReadouts: number samplesPerReadout: Int32Array offsets: Int32Array maxSamplesPerReadout: number elapsedMs: number } | { type: 'error'; message: string } const ctx = self as unknown as Worker ctx.onmessage = (ev: MessageEvent) => { const msg = ev.data if (msg.type !== 'run') return const started = Date.now() try { const seq = parseSeq(msg.seqText) let lastPost = 0 const result = simulate(seq, msg.phantom, { progressEvery: 128, onProgress: (progress) => { const now = Date.now() if (now - lastPost >= 100 || progress.fraction >= 1) { lastPost = now const out: WorkerOut = { type: 'progress', progress, elapsedMs: now - started } ctx.postMessage(out) } }, }) const out: WorkerOut = { type: 'done', re: result.re, im: result.im, numReadouts: result.numReadouts, samplesPerReadout: result.samplesPerReadout, offsets: result.offsets, maxSamplesPerReadout: result.maxSamplesPerReadout, elapsedMs: Date.now() - started, } ctx.postMessage(out, [result.re.buffer, result.im.buffer]) } catch (err) { const out: WorkerOut = { type: 'error', message: err instanceof Error ? err.message : String(err) } ctx.postMessage(out) } }