File
Blob: src/lib/exportClient.ts
| 1 | import type { ExportRequest, ExportResult, WorkerInbound, WorkerOutbound } from "../types"; |
| 2 | |
| 3 | /** |
| 4 | * Main-thread handle to the export worker. The worker owns the heavy pixel work |
| 5 | * (decode → transform → encode); here we just correlate requests/responses by id |
| 6 | * and hand back a promise. The source bytes are transferred (not copied). |
| 7 | */ |
| 8 | |
| 9 | let worker: Worker | null = null; |
| 10 | let seq = 0; |
| 11 | const pending = new Map<number, { resolve: (r: ExportResult) => void; reject: (e: Error) => void }>(); |
| 12 | |
| 13 | function getWorker(): Worker { |
| 14 | if (worker) return worker; |
| 15 | worker = new Worker(new URL("../workers/export.worker.ts", import.meta.url), { |
| 16 | type: "module", |
| 17 | name: "wimp-export", |
| 18 | }); |
| 19 | worker.onmessage = (e: MessageEvent<WorkerOutbound>) => { |
| 20 | const { id, response } = e.data; |
| 21 | const entry = pending.get(id); |
| 22 | if (!entry) return; |
| 23 | pending.delete(id); |
| 24 | if (response.ok) entry.resolve(response); |
| 25 | else entry.reject(new Error(response.error)); |
| 26 | }; |
| 27 | worker.onerror = (e: ErrorEvent) => { |
| 28 | const message = e.message || "The export worker crashed."; |
| 29 | for (const [, entry] of pending) entry.reject(new Error(message)); |
| 30 | pending.clear(); |
| 31 | // Drop the broken worker; the next request spins up a fresh one. |
| 32 | worker?.terminate(); |
| 33 | worker = null; |
| 34 | }; |
| 35 | return worker; |
| 36 | } |
| 37 | |
| 38 | export function exportImage(req: ExportRequest): Promise<ExportResult> { |
| 39 | const w = getWorker(); |
| 40 | const id = ++seq; |
| 41 | const message: WorkerInbound = { id, request: req }; |
| 42 | return new Promise<ExportResult>((resolve, reject) => { |
| 43 | pending.set(id, { resolve, reject }); |
| 44 | w.postMessage(message, { transfer: [req.bytes] }); |
| 45 | }); |
| 46 | } |