1// Hook owning the render worker: debounced auto-preview on parameter change,
2// explicit full-quality render, and a latest-wins request queue.
4import { useCallback, useEffect, useRef, useState } from 'react';
5import type { WoodblockParams } from '../params';
6import type { ProgressMessage, RenderResponse } from '../worker/renderWorker';
8export interface RenderState {
9 result: RenderResponse | null;
10 busy: boolean;
11 stage: string;
12 progress: number; // 0..1 of the current stage
13}
15export function useRenderer(params: WoodblockParams) {
16 const workerRef = useRef<Worker | null>(null);
17 const nextId = useRef(1);
18 const inFlight = useRef<number | null>(null);
19 const queued = useRef<{ params: WoodblockParams; quality: 'preview' | 'full' } | null>(null);
20 const [state, setState] = useState<RenderState>({
21 result: null,
22 busy: false,
23 stage: '',
24 progress: 0,
25 });
27 useEffect(() => {
28 const worker = new Worker(new URL('../worker/renderWorker.ts', import.meta.url), {
29 type: 'module',
30 });
31 workerRef.current = worker;
32 worker.onmessage = (ev: MessageEvent<RenderResponse | ProgressMessage>) => {
33 const msg = ev.data;
34 if (msg.type === 'progress') {
35 setState((s) => ({ ...s, stage: msg.stage, progress: msg.frac }));
36 return;
37 }
38 inFlight.current = null;
39 const q = queued.current;
40 if (q) {
41 queued.current = null;
42 // show the result we have, then immediately start the queued request
43 setState({ result: msg, busy: true, stage: 'queued', progress: 0 });
44 send(q.params, q.quality);
45 } else {
46 setState({ result: msg, busy: false, stage: '', progress: 0 });
47 }
48 };
49 return () => {
50 worker.terminate();
51 workerRef.current = null;
52 };
53 // eslint-disable-next-line react-hooks/exhaustive-deps
54 }, []);
56 const send = useCallback((p: WoodblockParams, quality: 'preview' | 'full') => {
57 const worker = workerRef.current;
58 if (!worker) return;
59 if (inFlight.current !== null) {
60 queued.current = { params: p, quality };
61 return;
62 }
63 const id = nextId.current++;
64 inFlight.current = id;
65 setState((s) => ({ ...s, busy: true, stage: 'starting', progress: 0 }));
66 worker.postMessage({ type: 'render', id, params: p, quality });
67 }, []);
69 const renderFull = useCallback(() => send(params, 'full'), [params, send]);
71 // Debounced auto-preview whenever parameters change (including on mount).
72 useEffect(() => {
73 const t = setTimeout(() => send(params, 'preview'), 250);
74 return () => clearTimeout(t);
75 }, [params, send]);
77 return { ...state, renderFull };
78}