///
/**
* 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);
};