diff --git a/src/App/App.tsx b/src/App/App.tsx index 3c01e62..f37d420 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ConcreteState, emptyStatechart, isAncestorOf, Statechart, stateDescription, Transition } from "../VisualEditor/ast"; import { VisualEditor } from "../VisualEditor/VisualEditor"; @@ -8,6 +8,7 @@ import { Action, Expression } from "../VisualEditor/label_ast"; import "../index.css"; import "./App.css"; +import { getSimTime, setPaused, setRealtime, TimeMode } from "../VisualEditor/time"; export function ShowTransition(props: {transition: Transition}) { return <>➔ {stateDescription(props.transition.tgt)}; @@ -69,18 +70,21 @@ export function AST(props: {root: ConcreteState, transitions: Map(emptyStatechart); const [errors, setErrors] = useState<[string,string][]>([]); + const [rt, setRT] = useState([]); const [rtIdx, setRTIdx] = useState(null); - const [timeMs, setTimeMs] = useState(0); - const [paused, setPaused] = useState(true); + const [time, setTime] = useState({kind: "paused", simtime: 0}); const [timescale, setTimescale] = useState(1); + const [displayTime, setDisplayTime] = useState("0.000"); + function restart() { const rt = initialize(ast); console.log('runtime: ', rt); setRT([rt]); setRTIdx(0); + setTime({kind: "paused", simtime: 0}); } function stop() { @@ -97,6 +101,54 @@ export function App() { } } + function updateDisplayedTime() { + const now = performance.now(); + const timeMs = getSimTime(time, now); + const leadingZeros = "00" + timeMs % 1000; + const formatted = `${Math.floor(timeMs / 1000)}.${(leadingZeros).substring(leadingZeros.length-3)}`; + // console.log(now, timeMs, formatted); + setDisplayTime(formatted); + } + + useEffect(() => { + const interval = setInterval(() => { + updateDisplayedTime(); + }, 20); + return () => { + clearInterval(interval); + } + }, [time]); + + function onChangePaused(paused: boolean, wallclktime: number) { + setTime(time => { + if (paused) { + console.log('setPaused...'); + return setPaused(time, performance.now()); + } + else { + console.log('setRealtime...'); + return setRealtime(time, timescale, wallclktime); + } + }); + updateDisplayedTime(); + } + + function onTimeScaleChange(newValue: string, wallclktime: number) { + const asFloat = parseFloat(newValue); + if (Number.isNaN(asFloat)) { + return; + } + setTimescale(asFloat); + setTime(time => { + if (time.kind === "paused") { + return time; + } + else { + return setRealtime(time, asFloat, wallclktime); + } + }) + } + return
@@ -105,18 +157,23 @@ export function App() {   - raise + raise  {[...ast.inputEvents].map(event => )}   - setPaused(e.target.checked)}/> + onChangePaused(e.target.checked, performance.now())}/> - setPaused(!e.target.checked)}/> - + onChangePaused(!e.target.checked, performance.now())}/> +   - - +   + onTimeScaleChange(e.target.value, performance.now())}/>   - time is {timeMs} ms + {rtIdx !== null && + <> +   + + + }
@@ -142,7 +199,7 @@ function ShowEnvironment(props: {environment: Environment}) { 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(",")}
; + stateDescription(props.statechart.uid2State.get(uid)!)).join(", ")}
; } function getActiveLeafs(mode: Mode, sc: Statechart) { diff --git a/src/VisualEditor/runtime_types.ts b/src/VisualEditor/runtime_types.ts index 1f1c483..20a6907 100644 --- a/src/VisualEditor/runtime_types.ts +++ b/src/VisualEditor/runtime_types.ts @@ -1,11 +1,3 @@ -// modal configuration: maps child-uid to modal configuration of the child -// for OR-states, only the modal configuration of the current state is kept -// for AND-states, the modal configuration of every child is kept -// for basic states (= empty AND-states), the modal configuration is just an empty object -// export type Mode = {[uid:string]: Mode}; - -import { Transition } from "./ast"; - export type Timestamp = number; // milliseconds since begin of simulation export type Event = string; @@ -20,12 +12,7 @@ export type RT_Statechart = { // history: // TODO } -export type BigStep = { - from: RT_Statechart; - to: RT_Statechart; - inputEvent: string; - outputEvents: string[]; -} +export type BigStep = RT_Statechart & {outputEvents: string[]}; export type RaisedEvents = { internalEvents: string[]; diff --git a/src/VisualEditor/time.ts b/src/VisualEditor/time.ts new file mode 100644 index 0000000..76a3c5f --- /dev/null +++ b/src/VisualEditor/time.ts @@ -0,0 +1,61 @@ + +export type TimeMode = TimePaused | TimeRealTime; + +export type TimePaused = { + kind: "paused", + simtime: number, // the current simulated time +} + +export type TimeRealTime = { + kind: "realtime", + scale: number, // time scale relative to wall-clock time + since: { + 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 + } +} + +export function getSimTime(currentMode: TimeMode, wallclktime: number): number { + if (currentMode.kind === "paused") { + return currentMode.simtime; + } + else { + const elapsedWallclk = wallclktime - currentMode.since.wallclktime; + return currentMode.since.simtime + currentMode.scale * elapsedWallclk; + } +} + +export function setRealtime(currentMode: TimeMode, scale: number, wallclktime: number): TimeRealTime { + if (currentMode.kind === "paused") { + return { + kind: "realtime", + scale, + since: { + simtime: currentMode.simtime, + wallclktime, + }, + }; + } + else { + return { + kind: "realtime", + scale, + since: { + simtime: getSimTime(currentMode, wallclktime), + wallclktime, + }, + }; + } +} + +export function setPaused(currentMode: TimeMode, wallclktime: number): TimePaused { + if (currentMode.kind === "paused") { + return currentMode; // no change + } + else { + return { + kind: "paused", + simtime: getSimTime(currentMode, wallclktime), + }; + } +}