/** * Main-thread handle on the render worker. Requests are coalesced: while one * render is running, only the most recent further request is kept, and any * request it replaces resolves to null. */ import type { RenderRequest, RenderResult } from './types' type Job = { req: RenderRequest; resolve: (r: RenderResult | null) => void; reject: (e: Error) => void } export class Renderer { private worker: Worker private busy: Job | null = null private queued: Job | null = null private nextId = 1 constructor() { this.worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' }) this.worker.onmessage = (e: MessageEvent) => { const job = this.busy this.busy = null job?.resolve(e.data) this.pump() } this.worker.onerror = (e) => { const job = this.busy this.busy = null job?.reject(new Error(e.message || 'render failed')) this.pump() } } request(req: Omit): Promise { return new Promise((resolve, reject) => { if (this.queued) this.queued.resolve(null) this.queued = { req: { ...req, id: this.nextId++ }, resolve, reject } this.pump() }) } private pump(): void { if (this.busy || !this.queued) return this.busy = this.queued this.queued = null this.worker.postMessage(this.busy.req) } terminate(): void { this.worker.terminate() } }