timestamps visible in execution history
This commit is contained in:
parent
3f095bb419
commit
446829c8ec
4 changed files with 55 additions and 31 deletions
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { ConcreteState, emptyStatechart, isAncestorOf, Statechart, stateDescription, Transition } from "../VisualEditor/ast";
|
import { ConcreteState, emptyStatechart, isAncestorOf, Statechart, stateDescription, Transition } from "../VisualEditor/ast";
|
||||||
import { VisualEditor } from "../VisualEditor/VisualEditor";
|
import { VisualEditor } from "../VisualEditor/VisualEditor";
|
||||||
import { Environment, Mode, RT_Statechart } from "../VisualEditor/runtime_types";
|
import { BigStepOutput, Environment, Mode, RT_Statechart } from "../VisualEditor/runtime_types";
|
||||||
import { initialize, handleEvent, handleInputEvent } from "../VisualEditor/interpreter";
|
import { initialize, handleEvent, handleInputEvent } from "../VisualEditor/interpreter";
|
||||||
import { Action, Expression } from "../VisualEditor/label_ast";
|
import { Action, Expression } from "../VisualEditor/label_ast";
|
||||||
|
|
||||||
|
|
@ -67,11 +67,18 @@ export function AST(props: {root: ConcreteState, transitions: Map<string, Transi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function formatTime(timeMs: number) {
|
||||||
|
const leadingZeros = "00" + timeMs % 1000;
|
||||||
|
const formatted = `${Math.floor(timeMs / 1000)}.${(leadingZeros).substring(leadingZeros.length-3)}`;
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
||||||
const [errors, setErrors] = useState<[string,string][]>([]);
|
const [errors, setErrors] = useState<[string,string][]>([]);
|
||||||
|
|
||||||
const [rt, setRT] = useState<RT_Statechart[]>([]);
|
const [rt, setRT] = useState<BigStep[]>([]);
|
||||||
const [rtIdx, setRTIdx] = useState<number|null>(null);
|
const [rtIdx, setRTIdx] = useState<number|null>(null);
|
||||||
|
|
||||||
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
||||||
|
|
@ -80,23 +87,24 @@ export function App() {
|
||||||
|
|
||||||
|
|
||||||
function restart() {
|
function restart() {
|
||||||
const rt = initialize(ast);
|
const config = initialize(ast);
|
||||||
console.log('runtime: ', rt);
|
console.log('runtime: ', rt);
|
||||||
setRT([rt]);
|
setRT([{inputEvent: null, simtime: 0, ...config}]);
|
||||||
setRTIdx(0);
|
setRTIdx(0);
|
||||||
setTime({kind: "paused", simtime: 0});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
setRT([]);
|
setRT([]);
|
||||||
setRTIdx(null);
|
setRTIdx(null);
|
||||||
|
setTime({kind: "paused", simtime: 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
function raise(event: string) {
|
function raise(inputEvent: string) {
|
||||||
console.log(rtIdx);
|
console.log(rtIdx);
|
||||||
if (rt.length>0 && rtIdx!==null && ast.inputEvents.has(event)) {
|
if (rt.length>0 && rtIdx!==null && ast.inputEvents.has(inputEvent)) {
|
||||||
const nextConfig = handleInputEvent(event, ast, rt[rtIdx]!);
|
const simtime = getSimTime(time, performance.now());
|
||||||
setRT([...rt.slice(0, rtIdx+1), nextConfig]);
|
const nextConfig = handleInputEvent(inputEvent, ast, rt[rtIdx]!);
|
||||||
|
setRT([...rt.slice(0, rtIdx+1), {inputEvent, simtime, ...nextConfig}]);
|
||||||
setRTIdx(rtIdx+1);
|
setRTIdx(rtIdx+1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,10 +112,7 @@ export function App() {
|
||||||
function updateDisplayedTime() {
|
function updateDisplayedTime() {
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
const timeMs = getSimTime(time, now);
|
const timeMs = getSimTime(time, now);
|
||||||
const leadingZeros = "00" + timeMs % 1000;
|
setDisplayTime(formatTime(timeMs));
|
||||||
const formatted = `${Math.floor(timeMs / 1000)}.${(leadingZeros).substring(leadingZeros.length-3)}`;
|
|
||||||
// console.log(now, timeMs, formatted);
|
|
||||||
setDisplayTime(formatted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -149,13 +154,18 @@ export function App() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gotoRt(idx: number, timestamp: number) {
|
||||||
|
setRTIdx(idx);
|
||||||
|
setTime({kind: "paused", simtime: timestamp});
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="layoutVertical">
|
return <div className="layoutVertical">
|
||||||
<div className="panel">
|
<div className="panel">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="panel">
|
<div className="panel">
|
||||||
<button onClick={restart}>(re)start</button>
|
<button onClick={restart}>(re)start</button>
|
||||||
<button onClick={stop} disabled={rt===null}>stop</button>
|
<button onClick={stop} disabled={rtIdx===null}>stop</button>
|
||||||
 
|
 
|
||||||
raise
|
raise
|
||||||
{[...ast.inputEvents].map(event => <button disabled={rtIdx===null} onClick={() => raise(event)}>{event}</button>)}
|
{[...ast.inputEvents].map(event => <button disabled={rtIdx===null} onClick={() => raise(event)}>{event}</button>)}
|
||||||
|
|
@ -168,12 +178,8 @@ export function App() {
|
||||||
<label htmlFor="number-timescale">timescale</label>
|
<label htmlFor="number-timescale">timescale</label>
|
||||||
<input type="number" min={0} id="number-timescale" disabled={rtIdx===null} value={timescale} style={{width:40}} onChange={e => onTimeScaleChange(e.target.value, performance.now())}/>
|
<input type="number" min={0} id="number-timescale" disabled={rtIdx===null} value={timescale} style={{width:40}} onChange={e => onTimeScaleChange(e.target.value, performance.now())}/>
|
||||||
 
|
 
|
||||||
{rtIdx !== null &&
|
<label htmlFor="time">time (s)</label>
|
||||||
<>
|
<input id="time" disabled={rtIdx===null} value={displayTime} readOnly={true} style={{width:56, backgroundColor:"#eee", textAlign: "right"}}/>
|
||||||
<label htmlFor="time">time (s)</label>
|
|
||||||
<input id="time" value={displayTime} readOnly={true} style={{width:56, backgroundColor:"#eee", textAlign: "right"}}/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="layout">
|
<div className="layout">
|
||||||
<main className="content">
|
<main className="content">
|
||||||
|
|
@ -181,9 +187,13 @@ export function App() {
|
||||||
</main>
|
</main>
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
<AST {...ast}/>
|
<AST {...ast}/>
|
||||||
{rt.map((rt, idx) => <><hr/><div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => setRTIdx(idx)}>
|
{rt.map((rt, idx) => <><hr/><div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => gotoRt(idx, rt.simtime)}>
|
||||||
<ShowEnvironment environment={rt.environment}/>
|
<div>({formatTime(rt.simtime)}, {rt.inputEvent || "<init>"})</div>
|
||||||
<ShowMode mode={rt.mode} statechart={ast}/>
|
<ShowMode mode={rt.mode} statechart={ast}/>
|
||||||
|
<ShowEnvironment environment={rt.environment}/>
|
||||||
|
{rt.outputEvents.length>0 && <div>
|
||||||
|
{rt.outputEvents.map((e:string) => '^'+e).join(', ')}
|
||||||
|
</div>}
|
||||||
</div></>)}
|
</div></>)}
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -198,7 +208,7 @@ function ShowEnvironment(props: {environment: Environment}) {
|
||||||
|
|
||||||
function ShowMode(props: {mode: Mode, statechart: Statechart}) {
|
function ShowMode(props: {mode: Mode, statechart: Statechart}) {
|
||||||
const activeLeafs = getActiveLeafs(props.mode, props.statechart);
|
const activeLeafs = getActiveLeafs(props.mode, props.statechart);
|
||||||
return <div>{[...activeLeafs].map(uid =>
|
return <div>mode: {[...activeLeafs].map(uid =>
|
||||||
stateDescription(props.statechart.uid2State.get(uid)!)).join(", ")}</div>;
|
stateDescription(props.statechart.uid2State.get(uid)!)).join(", ")}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { evalExpr } from "./actionlang_interpreter";
|
import { evalExpr } from "./actionlang_interpreter";
|
||||||
import { computeArena, ConcreteState, getDescendants, isAncestorOf, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./ast";
|
import { computeArena, ConcreteState, getDescendants, isAncestorOf, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./ast";
|
||||||
import { Action } from "./label_ast";
|
import { Action } from "./label_ast";
|
||||||
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStep } from "./runtime_types";
|
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStepOutput } from "./runtime_types";
|
||||||
|
|
||||||
export function initialize(ast: Statechart): BigStep {
|
export function initialize(ast: Statechart): BigStepOutput {
|
||||||
let {enteredStates, environment, ...raised} = enterDefault(ast.root, {
|
let {enteredStates, environment, ...raised} = enterDefault(ast.root, {
|
||||||
environment: new Map(),
|
environment: new Map(),
|
||||||
...initialRaised,
|
...initialRaised,
|
||||||
|
|
@ -204,7 +204,7 @@ export function handleEvent(event: string, statechart: Statechart, activeParent:
|
||||||
return {environment, mode, ...raised};
|
return {environment, mode, ...raised};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleInputEvent(event: string, statechart: Statechart, {mode, environment}: {mode: Mode, environment: Environment}): BigStep {
|
export function handleInputEvent(event: string, statechart: Statechart, {mode, environment}: {mode: Mode, environment: Environment}): BigStepOutput {
|
||||||
let raised = initialRaised;
|
let raised = initialRaised;
|
||||||
|
|
||||||
({mode, environment, ...raised} = handleEvent(event, statechart, statechart.root, {mode, environment, ...raised}));
|
({mode, environment, ...raised} = handleEvent(event, statechart, statechart.root, {mode, environment, ...raised}));
|
||||||
|
|
@ -212,7 +212,7 @@ export function handleInputEvent(event: string, statechart: Statechart, {mode, e
|
||||||
return handleInternalEvents(statechart, {mode, environment, ...raised});
|
return handleInternalEvents(statechart, {mode, environment, ...raised});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleInternalEvents(statechart: Statechart, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): BigStep {
|
export function handleInternalEvents(statechart: Statechart, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): BigStepOutput {
|
||||||
while (raised.internalEvents.length > 0) {
|
while (raised.internalEvents.length > 0) {
|
||||||
const [internalEvent, ...rest] = raised.internalEvents;
|
const [internalEvent, ...rest] = raised.internalEvents;
|
||||||
({mode, environment, ...raised} = handleEvent(internalEvent, statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents: raised.outputEvents}));
|
({mode, environment, ...raised} = handleEvent(internalEvent, statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents: raised.outputEvents}));
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,15 @@ export type RT_Statechart = {
|
||||||
// history: // TODO
|
// history: // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BigStep = RT_Statechart & {outputEvents: string[]};
|
export type BigStepOutput = RT_Statechart & {
|
||||||
|
outputEvents: string[],
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BigStep = {
|
||||||
|
inputEvent: string | null, // null if initialization
|
||||||
|
simtime: number,
|
||||||
|
} & BigStepOutput;
|
||||||
|
|
||||||
|
|
||||||
export type RaisedEvents = {
|
export type RaisedEvents = {
|
||||||
internalEvents: string[];
|
internalEvents: string[];
|
||||||
|
|
@ -24,4 +32,4 @@ export type Timers = Map<string, number>; // transition uid -> timestamp
|
||||||
export const initialRaised: RaisedEvents = {
|
export const initialRaised: RaisedEvents = {
|
||||||
internalEvents: [],
|
internalEvents: [],
|
||||||
outputEvents: [],
|
outputEvents: [],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
|
|
||||||
export type TimeMode = TimePaused | TimeRealTime;
|
export type TimeMode = TimePaused | TimeRealTime;
|
||||||
|
|
||||||
|
// When the simulation is paused, we only need to know the current simulated time.
|
||||||
export type TimePaused = {
|
export type TimePaused = {
|
||||||
kind: "paused",
|
kind: "paused",
|
||||||
simtime: number, // the current simulated time
|
simtime: number, // the current simulated time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When the simulation is running in real time, we need to know the time when the simulation was set to real time (both in simulated and wall-clock time), and the time scale. This allows us to compute the simulated time of every future event.
|
||||||
|
// Such a 'future event' may be:
|
||||||
|
// - raising an input event
|
||||||
|
// - changing of the time scale parameter
|
||||||
|
// - pausing the simulation
|
||||||
export type TimeRealTime = {
|
export type TimeRealTime = {
|
||||||
kind: "realtime",
|
kind: "realtime",
|
||||||
scale: number, // time scale relative to wall-clock time
|
|
||||||
since: {
|
since: {
|
||||||
simtime: number, // the simulated time at which the time was set to realtime
|
simtime: number, // the simulated time at which the time was set to realtime
|
||||||
wallclktime: number, // the wall-clock time at which the time was set to realtime
|
wallclktime: number, // the wall-clock time at which the time was set to realtime
|
||||||
}
|
}
|
||||||
|
scale: number, // time scale relative to wall-clock time
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSimTime(currentMode: TimeMode, wallclktime: number): number {
|
export function getSimTime(currentMode: TimeMode, wallclktime: number): number {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue