1// Shared run state per .sample file, connecting the runner (which drives a
2// run) with the form editor view (which shows the run button, per-chain
3// progress bars, and status). Keyed by the file URI.
5import type { Progress } from './protocol';
7export type RunPhase = 'idle' | 'compiling' | 'loading' | 'sampling' | 'writing' | 'done' | 'failed';
9export interface ChainProgress {
10 iteration: number;
11 totalIterations: number;
12 warmup: boolean;
13}
15export interface RunState {
16 phase: RunPhase;
17 /** Status detail or error message. */
18 message?: string;
19 /** Per-chain progress (index 0 = chain 1), while sampling. */
20 chains?: ChainProgress[];
21 computeTimeSec?: number;
22}
24type Listener = (uriKey: string, state: RunState) => void;
26const states = new Map<string, RunState>();
27const listeners = new Set<Listener>();
29export function getRunState(uriKey: string): RunState {
30 return states.get(uriKey) ?? { phase: 'idle' };
31}
33export function setRunState(uriKey: string, state: RunState): void {
34 states.set(uriKey, state);
35 for (const listener of listeners) {
36 listener(uriKey, state);
37 }
38}
40export function updateChainProgress(uriKey: string, numChains: number, report: Progress): void {
41 const state = getRunState(uriKey);
42 const chains = state.chains ?? Array.from({ length: numChains }, () => ({
43 iteration: 0,
44 totalIterations: report.totalIterations,
45 warmup: true,
46 }));
47 if (report.chain >= 1 && report.chain <= chains.length) {
48 chains[report.chain - 1] = {
49 iteration: report.iteration,
50 totalIterations: report.totalIterations,
51 warmup: report.warmup,
52 };
53 }
54 setRunState(uriKey, { ...state, phase: 'sampling', chains });
55}
57export function onDidChangeRunState(listener: Listener): { dispose(): void } {
58 listeners.add(listener);
59 return { dispose: () => listeners.delete(listener) };
60}