/ proof-of-concept / plate-dynamics-lab
Sign in
proof-of-concept / plate-dynamics-lab
plate-dynamics-lab / packages / components / src / hooks / useSolve.ts
82 lines · 2.7 KBBlameHistoryRaw
1import { useCallback, useRef, useState } from "react";
2import type { PlainValue, RunFailure, RunRequest, RunResult, RunSuccess } from "@plate-dyn/core";
3import { useRunner } from "./RunnerContext";
5export type SolveState<T> =
6 | { status: "idle" }
7 | { status: "running"; startedAt: number; output: string[] }
8 | { status: "done"; run: RunSuccess; value: T; output: string[] }
9 | { status: "error"; run: RunFailure; output: string[] };
11export interface Solve<T> {
12 state: SolveState<T>;
13 /** Most recent successful value, kept while a new run is in progress */
14 last: T | null;
15 run: (req: RunRequest) => Promise<RunResult | undefined>;
16 cancel: () => void;
17 /** Forget the last value and go back to idle, so nothing stale is displayed */
18 reset: () => void;
21/**
22 * Drive one run at a time through the shared runner. `parse` types the raw
23 * result; a parse failure is reported like any other error so a script that
24 * returns the wrong shape never looks like a blank screen.
25 */
26export function useSolve<T>(parse: (v: PlainValue) => T): Solve<T> {
27 const runner = useRunner();
28 const [state, setState] = useState<SolveState<T>>({ status: "idle" });
29 const [last, setLast] = useState<T | null>(null);
30 const seq = useRef(0);
32 const run = useCallback(
33 async (req: RunRequest) => {
34 const id = ++seq.current;
35 const output: string[] = [];
36 setState({ status: "running", startedAt: performance.now(), output: [] });
37 const res = await runner.run(req, (text) => {
38 output.push(text);
39 if (seq.current === id)
40 setState((s) => (s.status === "running" ? { ...s, output: [...output] } : s));
41 });
42 if (seq.current !== id) return undefined;
43 if (res.ok) {
44 try {
45 const value = parse(res.value);
46 setLast(value);
47 setState({ status: "done", run: res, value, output: res.output });
48 } catch (e) {
49 setState({
50 status: "error",
51 run: {
52 ...res,
53 ok: false,
54 error: `the script ran, but its result has an unexpected shape: ${(e as Error).message}`,
55 },
56 output: res.output,
57 });
58 }
59 } else {
60 setState({ status: "error", run: res, output: res.output });
61 }
62 return res;
63 },
64 [runner, parse],
65 );
67 const cancel = useCallback(() => {
68 seq.current++;
69 runner.cancel();
70 setState({ status: "idle" });
71 }, [runner]);
73 // Local state only: nothing is terminated, so the worker (and the numbl module it
74 // has loaded) survives. Use `cancel` to stop a run that is actually in flight.
75 const reset = useCallback(() => {
76 seq.current++;
77 setLast(null);
78 setState({ status: "idle" });
79 }, []);
81 return { state, last, run, cancel, reset };
↑↓ move↵openescclose