// Hook owning the render worker: debounced auto-preview on parameter change, // explicit full-quality render, and a latest-wins request queue. import { useCallback, useEffect, useRef, useState } from 'react'; import type { WoodblockParams } from '../params'; import type { ProgressMessage, RenderResponse } from '../worker/renderWorker'; export interface RenderState { result: RenderResponse | null; busy: boolean; stage: string; progress: number; // 0..1 of the current stage } export function useRenderer(params: WoodblockParams) { const workerRef = useRef(null); const nextId = useRef(1); const inFlight = useRef(null); const queued = useRef<{ params: WoodblockParams; quality: 'preview' | 'full' } | null>(null); const [state, setState] = useState({ result: null, busy: false, stage: '', progress: 0, }); useEffect(() => { const worker = new Worker(new URL('../worker/renderWorker.ts', import.meta.url), { type: 'module', }); workerRef.current = worker; worker.onmessage = (ev: MessageEvent) => { const msg = ev.data; if (msg.type === 'progress') { setState((s) => ({ ...s, stage: msg.stage, progress: msg.frac })); return; } inFlight.current = null; const q = queued.current; if (q) { queued.current = null; // show the result we have, then immediately start the queued request setState({ result: msg, busy: true, stage: 'queued', progress: 0 }); send(q.params, q.quality); } else { setState({ result: msg, busy: false, stage: '', progress: 0 }); } }; return () => { worker.terminate(); workerRef.current = null; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const send = useCallback((p: WoodblockParams, quality: 'preview' | 'full') => { const worker = workerRef.current; if (!worker) return; if (inFlight.current !== null) { queued.current = { params: p, quality }; return; } const id = nextId.current++; inFlight.current = id; setState((s) => ({ ...s, busy: true, stage: 'starting', progress: 0 })); worker.postMessage({ type: 'render', id, params: p, quality }); }, []); const renderFull = useCallback(() => send(params, 'full'), [params, send]); // Debounced auto-preview whenever parameters change (including on mount). useEffect(() => { const t = setTimeout(() => send(params, 'preview'), 250); return () => clearTimeout(t); }, [params, send]); return { ...state, renderFull }; }