show execution history

This commit is contained in:
Joeri Exelmans 2025-10-09 16:25:03 +02:00
parent a8b522fdd8
commit d5272e30f3
6 changed files with 90 additions and 42 deletions

View file

@ -1,9 +1,9 @@
import { useState } from "react";
import { ConcreteState, emptyStatechart, Statechart, stateDescription, Transition } from "../VisualEditor/ast";
import { ConcreteState, emptyStatechart, isAncestorOf, Statechart, stateDescription, Transition } from "../VisualEditor/ast";
import { VisualEditor } from "../VisualEditor/VisualEditor";
import { RT_Statechart } from "../VisualEditor/runtime_types";
import { initialize, handleEvent } from "../VisualEditor/interpreter";
import { Environment, Mode, RT_Statechart } from "../VisualEditor/runtime_types";
import { initialize, handleEvent, handleInputEvent } from "../VisualEditor/interpreter";
import { Action, Expression } from "../VisualEditor/label_ast";
import "../index.css";
@ -69,7 +69,9 @@ export function AST(props: {root: ConcreteState, transitions: Map<string, Transi
export function App() {
const [ast, setAST] = useState<Statechart>(emptyStatechart);
const [errors, setErrors] = useState<[string,string][]>([]);
const [rt, setRT] = useState<RT_Statechart|null>(null);
const [rt, setRT] = useState<RT_Statechart[]>([]);
const [rtIdx, setRTIdx] = useState<number|null>(null);
const [timeMs, setTimeMs] = useState(0);
const [paused, setPaused] = useState(true);
const [timescale, setTimescale] = useState(1);
@ -77,61 +79,83 @@ export function App() {
function restart() {
const rt = initialize(ast);
console.log('runtime: ', rt);
setRT(rt);
setRT([rt]);
setRTIdx(0);
}
function stop() {
setRT(null);
setRT([]);
setRTIdx(null);
}
function raise(event: string) {
if (rt && ast.inputEvents.has(event)) {
const nextConfig = handleEvent(event, ast, ast.root, rt);
setRT(nextConfig);
// console.log({nextConfigs});
// if (nextConfigs.length > 0) {
// if (nextConfigs.length > 1) {
// console.warn('non-determinism, blindly selecting first next run-time state!');
// }
// setRT(nextConfigs[0]);
// }
console.log(rtIdx);
if (rt.length>0 && rtIdx!==null && ast.inputEvents.has(event)) {
const nextConfig = handleInputEvent(event, ast, rt[rtIdx]!);
setRT([...rt.slice(0, rtIdx+1), nextConfig]);
setRTIdx(rtIdx+1);
}
}
return <div className="layoutVertical">
<div className="panel">
</div>
<div className="panel">
<button onClick={restart}>(re)start</button>
<select disabled={rt===null} value="raise event..." onChange={e => raise(e.target.value)}>
<option value="">raise event...</option>
{[...ast.inputEvents].map(event =>
<option value={event}>{event}</option>
)}
</select>
<button onClick={stop} >stop</button>
<button onClick={stop} disabled={rt===null}>stop</button>
&emsp;
<input type="radio" name="paused" id="radio-paused" checked={paused} disabled={rt===null} onChange={e => setPaused(e.target.checked)}/>
raise
{[...ast.inputEvents].map(event => <button disabled={rtIdx===null} onClick={() => raise(event)}>{event}</button>)}
&emsp;
<input type="radio" name="paused" id="radio-paused" checked={paused} disabled={rtIdx===null} onChange={e => setPaused(e.target.checked)}/>
<label htmlFor="radio-paused">paused</label>
<input type="radio" name="realtime" id="radio-realtime" checked={!paused} disabled={rt===null} onChange={e => setPaused(!e.target.checked)}/>
<input type="radio" name="realtime" id="radio-realtime" checked={!paused} disabled={rtIdx===null} onChange={e => setPaused(!e.target.checked)}/>
<label htmlFor="radio-realtime">real-time</label>
&emsp;
<label htmlFor="number-timescale">timescale</label>
<input type="number" id="number-timescale" disabled={rt===null} value={timescale} style={{width:40}}/>
<input type="number" id="number-timescale" disabled={rtIdx===null} value={timescale} style={{width:40}}/>
&emsp;
time is {timeMs} ms
</div>
<div className="layout">
<main className="content">
<VisualEditor {...{ast, setAST, rt, setRT, errors, setErrors}}/>
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors}}/>
</main>
<aside className="sidebar">
<AST {...ast}/>
<hr/>
{rt &&
[...rt.environment.entries()].map(([variable,value]) => <>
{variable}: {value}<br/>
</>)
}
{rt.map((rt, idx) => <><hr/><div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => setRTIdx(idx)}>
<ShowEnvironment environment={rt.environment}/>
<br/>
<ShowMode mode={rt.mode} statechart={ast}/>
</div></>)}
</aside>
</div>
</div>;
}
function ShowEnvironment(props: {environment: Environment}) {
return <>{[...props.environment.entries()].map(([variable,value]) =>
`${variable}: ${value}`
).join(', ')}</>;
}
function ShowMode(props: {mode: Mode, statechart: Statechart}) {
const activeLeafs = getActiveLeafs(props.mode, props.statechart);
return <>{[...activeLeafs].map(uid =>
stateDescription(props.statechart.uid2State.get(uid)!)).join(",")}</>;
}
function getActiveLeafs(mode: Mode, sc: Statechart) {
const toDelete = [];
for (const stateA of mode) {
for (const stateB of mode) {
if (sc.uid2State.get(stateA)!.parent === sc.uid2State.get(stateB)) {
toDelete.push(stateB);
}
}
}
return mode.difference(new Set(toDelete));
}
export default App;