import { COMPUTE_LIMITS } from '../../../protocol/gpu-compute'; export type ComputeEndpoint = EventTarget & { postMessage(message: unknown): void; terminate(): void }; export class RemoteComputeEndpoint extends EventTarget implements ComputeEndpoint { private readonly id = crypto.randomUUID(); private closed = false; private readonly receive = (event: MessageEvent) => { const data = event.data; if(this.closed || data?.id !== this.id)return; if(data.type === 'ready')this.dispatchEvent(new MessageEvent('message', { data: { type: 'gpu-compute-ready' } })); else if(data.type === 'error')this.dispatchEvent(new ErrorEvent('error', { message: String(data.message) })); }; constructor(private readonly port: MessagePort) { super(); port.addEventListener('message', this.receive); port.start(); port.postMessage({ type: 'prepare', id: this.id }); } postMessage(message: unknown): void { if(this.closed)throw new Error('GPU compute endpoint closed'); this.port.postMessage({ type: 'dispatch', id: this.id, request: message }); } terminate(): void { if(this.closed)return; this.closed = true; this.port.postMessage({ type: 'close', id: this.id }); this.port.removeEventListener('message', this.receive); } } type PendingCompute = { control: SharedArrayBuffer; error: SharedArrayBuffer }; type ComputeEntry = { worker: Worker; ready: boolean; pending: Set }; export function attachPageComputeRelay(engine: Worker): void { const { port1, port2 } = new MessageChannel(), entries = new Map(); let closed = false; const fail = (pending: PendingCompute, message: string) => { const control = new Int32Array(pending.control), error = new Uint8Array(pending.error); if(Atomics.load(control, 0) !== 0)return; const bytes = new TextEncoder().encode(message).subarray(0, error.length); error.set(bytes); Atomics.store(control, 1, bytes.length); Atomics.compareExchange(control, 0, 0, -1); Atomics.notify(control, 0); }; const close = (id: string, message: string) => { const entry = entries.get(id); if(!entry)return; entries.delete(id); for(const pending of entry.pending)fail(pending, message); entry.worker.terminate(); }; port1.onmessage = ({ data }) => { if(closed || typeof data?.id !== 'string')return; const id = data.id; if(data.type === 'prepare'){ if(entries.has(id)){port1.postMessage({ type: 'error', id, message: 'GPU compute endpoint already exists' }); return;} try { const worker = new Worker(new URL('../workers/gpu-compute.worker.ts', import.meta.url), { type: 'module' }), entry: ComputeEntry = { worker, ready: false, pending: new Set() }; entries.set(id, entry); worker.onmessage = event => { if(closed || entries.get(id) !== entry)return; if(event.data?.type === 'gpu-compute-ready'){entry.ready = true; port1.postMessage({ type: 'ready', id });} }; worker.onerror = event => { event.preventDefault(); if(entries.get(id) !== entry)return; const message = event.message || 'GPU compute worker failed'; close(id, message); port1.postMessage({ type: 'error', id, message }); }; } catch(error) { close(id, String(error)); port1.postMessage({ type: 'error', id, message: String(error) }); } } else if(data.type === 'close'){close(id, 'GPU compute endpoint closed'); } else if(data.type === 'dispatch'){ const entry = entries.get(id), request = data.request; const pending: PendingCompute | null = request?.control instanceof SharedArrayBuffer && request.control.byteLength === 8 && request.error instanceof SharedArrayBuffer && request.error.byteLength === COMPUTE_LIMITS.errorBytes ? request : null; if(!entry?.ready){if(pending)fail(pending, 'GPU compute relay not ready'); return;} for(const item of entry.pending)if(Atomics.load(new Int32Array(item.control), 0) !== 0)entry.pending.delete(item); if(pending)entry.pending.add(pending); try{entry.worker.postMessage(request);}catch(error){close(id, String(error)); port1.postMessage({ type: 'error', id, message: String(error) });} } }; const terminate = engine.terminate.bind(engine); engine.terminate = () => { if(!closed){closed = true; for(const id of [...entries.keys()])close(id, 'Engine compute relay disposed'); port1.close();} terminate(); }; port1.start(); try{engine.postMessage({ type: 'native-gpu-compute-port', port: port2 }, [port2]);}catch(error){engine.terminate(); throw error;} }