/// /** * Web Worker that owns a numbl instance and, when the browser exposes WebGPU * to workers, a GPU device for the ported time loop. One run at a time; the * host cancels a run by terminating the worker, which is why nothing here * needs to be interruptible. */ import { numblVersion } from "./execute"; import { executeRequest, type GpuProvider } from "./executeGpu"; import { requestGpuDevice, type GpuHandle } from "../gpu/fdLoop"; import { collectBuffers } from "./plain"; import type { FromWorker, ToWorker } from "./protocol"; import type { BackendInfo } from "./types"; const backend: BackendInfo = { engine: "numbl", version: numblVersion(), optimization: "1", host: "web-worker", }; const post = (msg: FromWorker, transfer: ArrayBuffer[] = []) => self.postMessage(msg, transfer); // The device is requested once and kept; a lost device is requested again next time. let gpuHandle: Promise | null = null; const gpuProvider: GpuProvider = () => { if (!gpuHandle) { gpuHandle = requestGpuDevice().then((h) => { void h.device.lost.then(() => { gpuHandle = null; }); return h; }); gpuHandle.catch(() => { gpuHandle = null; }); } return gpuHandle; }; const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator && !!(navigator as { gpu?: unknown }).gpu; self.onmessage = async (e: MessageEvent) => { const msg = e.data; if (msg.type === "info") { post({ type: "info", id: msg.id, backend }); return; } const result = await executeRequest( { bundle: msg.bundle, inputs: msg.inputs, gpu: msg.gpu }, backend, (text) => post({ type: "output", id: msg.id, text }), hasWebGpu ? gpuProvider : undefined, ); const transfer = result.ok ? collectBuffers(result.value) : []; post({ type: "done", id: msg.id, result }, transfer); };