grealy improved memoization
This commit is contained in:
parent
7994cd6eb0
commit
64aab1a6df
8 changed files with 217 additions and 119 deletions
|
|
@ -3,7 +3,7 @@ import "./App.css";
|
||||||
|
|
||||||
import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import { detectConnections } from "@/statecharts/detect_connections";
|
import { connectionsEqual, detectConnections, reducedConcreteSyntaxEqual } from "@/statecharts/detect_connections";
|
||||||
import { parseStatechart } from "../statecharts/parser";
|
import { parseStatechart } from "../statecharts/parser";
|
||||||
import { BottomPanel } from "./BottomPanel/BottomPanel";
|
import { BottomPanel } from "./BottomPanel/BottomPanel";
|
||||||
import { defaultSideBarState, SideBar, SideBarState } from "./SideBar/SideBar";
|
import { defaultSideBarState, SideBar, SideBarState } from "./SideBar/SideBar";
|
||||||
|
|
@ -18,6 +18,7 @@ import { plants } from "./plants";
|
||||||
import { emptyState } from "@/statecharts/concrete_syntax";
|
import { emptyState } from "@/statecharts/concrete_syntax";
|
||||||
import { ModalOverlay } from "./Overlays/ModalOverlay";
|
import { ModalOverlay } from "./Overlays/ModalOverlay";
|
||||||
import { FindReplace } from "./BottomPanel/FindReplace";
|
import { FindReplace } from "./BottomPanel/FindReplace";
|
||||||
|
import { useCustomMemo } from "@/hooks/useCustomMemo";
|
||||||
|
|
||||||
export type EditHistory = {
|
export type EditHistory = {
|
||||||
current: VisualEditorState,
|
current: VisualEditorState,
|
||||||
|
|
@ -59,7 +60,17 @@ export function App() {
|
||||||
|
|
||||||
// parse concrete syntax always:
|
// parse concrete syntax always:
|
||||||
const conns = useMemo(() => editorState && detectConnections(editorState), [editorState]);
|
const conns = useMemo(() => editorState && detectConnections(editorState), [editorState]);
|
||||||
const parsed = useMemo(() => editorState && conns && parseStatechart(editorState, conns), [editorState, conns]);
|
const parsed = useCustomMemo(() => editorState && conns && parseStatechart(editorState, conns),
|
||||||
|
[editorState, conns] as const,
|
||||||
|
// only parse again if anything changed to the connectedness / insideness...
|
||||||
|
// parsing is fast, BUT re-rendering everything that depends on the AST is slow, and it's difficult to check if the AST changed because AST objects have recursive structure.
|
||||||
|
([prevState, prevConns], [nextState, nextConns]) => {
|
||||||
|
if ((prevState === null) !== (nextState === null)) return false;
|
||||||
|
if ((prevConns === null) !== (nextConns === null)) return false;
|
||||||
|
// the following check is much cheaper than re-rendering everything that depends on
|
||||||
|
return connectionsEqual(prevConns!, nextConns!)
|
||||||
|
&& reducedConcreteSyntaxEqual(prevState!, nextState!);
|
||||||
|
});
|
||||||
const ast = parsed && parsed[0];
|
const ast = parsed && parsed[0];
|
||||||
|
|
||||||
const [appState, setAppState] = useState<AppState>(defaultAppState);
|
const [appState, setAppState] = useState<AppState>(defaultAppState);
|
||||||
|
|
@ -118,14 +129,6 @@ export function App() {
|
||||||
|
|
||||||
const simulator = useSimulator(ast, plant, plantConns, scrollDownSidebar);
|
const simulator = useSimulator(ast, plant, plantConns, scrollDownSidebar);
|
||||||
|
|
||||||
// console.log('render app', {ast, plant, appState});
|
|
||||||
// useDetectChange(ast, 'ast');
|
|
||||||
// useDetectChange(plant, 'plant');
|
|
||||||
// useDetectChange(scrollDownSidebar, 'scrollDownSidebar');
|
|
||||||
// useDetectChange(appState, 'appState');
|
|
||||||
// useDetectChange(simulator.time, 'simulator.time');
|
|
||||||
// useDetectChange(simulator.trace, 'simulator.trace');
|
|
||||||
|
|
||||||
const setters = makeAllSetters(setAppState, Object.keys(appState) as (keyof AppState)[]);
|
const setters = makeAllSetters(setAppState, Object.keys(appState) as (keyof AppState)[]);
|
||||||
|
|
||||||
const syntaxErrors = parsed && parsed[1] || [];
|
const syntaxErrors = parsed && parsed[1] || [];
|
||||||
|
|
@ -141,7 +144,9 @@ export function App() {
|
||||||
const highlightActive = (currentBigStep && currentBigStep.state.sc.mode) || new Set();
|
const highlightActive = (currentBigStep && currentBigStep.state.sc.mode) || new Set();
|
||||||
const highlightTransitions = currentBigStep && currentBigStep.state.sc.firedTransitions || [];
|
const highlightTransitions = currentBigStep && currentBigStep.state.sc.firedTransitions || [];
|
||||||
|
|
||||||
const plantState = currentBigStep && currentBigStep.state.plant || plant.execution.initial()[1];
|
const plantState = useMemo(() =>
|
||||||
|
currentBigStep && currentBigStep.state.plant || plant.execution.initial()[1],
|
||||||
|
[currentBigStep, plant]);
|
||||||
|
|
||||||
return <div style={{
|
return <div style={{
|
||||||
height:'100%',
|
height:'100%',
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import digitalFont from "./digital-font.ttf";
|
||||||
import "./DigitalWatch.css";
|
import "./DigitalWatch.css";
|
||||||
import imgNote from "./noteSmall.png";
|
import imgNote from "./noteSmall.png";
|
||||||
import imgWatch from "./watch.png";
|
import imgWatch from "./watch.png";
|
||||||
import { objectsEqual } from "@/util/util";
|
import { jsonDeepEqual } from "@/util/util";
|
||||||
|
|
||||||
export const [dwatchAbstractSyntax, dwatchErrors] = parseStatechart(dwatchConcreteSyntax as ConcreteSyntax, detectConnections(dwatchConcreteSyntax as ConcreteSyntax));
|
export const [dwatchAbstractSyntax, dwatchErrors] = parseStatechart(dwatchConcreteSyntax as ConcreteSyntax, detectConnections(dwatchConcreteSyntax as ConcreteSyntax));
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ export const DigitalWatch = memo(function DigitalWatch({state: {displayingTime,
|
||||||
}
|
}
|
||||||
</svg>
|
</svg>
|
||||||
</>;
|
</>;
|
||||||
}, objectsEqual);
|
}, jsonDeepEqual);
|
||||||
|
|
||||||
export const digitalWatchPlant = makeStatechartPlant({
|
export const digitalWatchPlant = makeStatechartPlant({
|
||||||
ast: dwatchAbstractSyntax,
|
ast: dwatchAbstractSyntax,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { ConcreteState, stateDescription, Transition, UnstableState } from "../.
|
||||||
import { Action, EventTrigger, Expression } from "../../statecharts/label_ast";
|
import { Action, EventTrigger, Expression } from "../../statecharts/label_ast";
|
||||||
import { KeyInfoHidden, KeyInfoVisible } from "../TopPanel/KeyInfo";
|
import { KeyInfoHidden, KeyInfoVisible } from "../TopPanel/KeyInfo";
|
||||||
import { useShortcuts } from '@/hooks/useShortcuts';
|
import { useShortcuts } from '@/hooks/useShortcuts';
|
||||||
|
import { arraysEqual, jsonDeepEqual } from '@/util/util';
|
||||||
|
|
||||||
export function ShowTransition(props: {transition: Transition}) {
|
export function ShowTransition(props: {transition: Transition}) {
|
||||||
return <>➝ {stateDescription(props.transition.tgt)}</>;
|
return <>➝ {stateDescription(props.transition.tgt)}</>;
|
||||||
|
|
@ -50,7 +51,7 @@ export const ShowAST = memo(function ShowASTx(props: {root: ConcreteState | Unst
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export function ShowInputEvents({inputEvents, onRaise, disabled}: {inputEvents: EventTrigger[], onRaise: (e: string, p: any) => void, disabled: boolean}) {
|
export const ShowInputEvents = memo(function ShowInputEvents({inputEvents, onRaise, disabled}: {inputEvents: EventTrigger[], onRaise: (e: string, p: any) => void, disabled: boolean}) {
|
||||||
const raiseHandlers = inputEvents.map(({event}) => {
|
const raiseHandlers = inputEvents.map(({event}) => {
|
||||||
return () => {
|
return () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -105,17 +106,22 @@ export function ShowInputEvents({inputEvents, onRaise, disabled}: {inputEvents:
|
||||||
|
|
||||||
</div>;
|
</div>;
|
||||||
})
|
})
|
||||||
}
|
}, (prevProps, nextProps) => {
|
||||||
|
console.log('onRaise changed:', prevProps.onRaise === nextProps.onRaise, prevProps.onRaise, nextProps.onRaise);
|
||||||
|
return prevProps.onRaise === nextProps.onRaise
|
||||||
|
&& prevProps.disabled === nextProps.disabled
|
||||||
|
&& jsonDeepEqual(prevProps.inputEvents, nextProps.inputEvents);
|
||||||
|
});
|
||||||
|
|
||||||
export function ShowInternalEvents(props: {internalEvents: EventTrigger[]}) {
|
export function ShowInternalEvents(props: {internalEvents: EventTrigger[]}) {
|
||||||
return [...props.internalEvents].map(({event, paramName}) => {
|
return [...props.internalEvents].map(({event, paramName}) => {
|
||||||
return <><div className="internalEvent">{event}{paramName===undefined?<></>:<>({paramName})</>}</div> </>;
|
return <div className="internalEvent" key={event}>{event}{paramName===undefined?<></>:<>({paramName})</>}</div>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function ShowOutputEvents(props: {outputEvents: Set<string>}) {
|
export function ShowOutputEvents(props: {outputEvents: Set<string>}) {
|
||||||
return [...props.outputEvents].map(eventName => {
|
return [...props.outputEvents].map(eventName => {
|
||||||
return <><div className="outputEvent">{eventName}</div> </>;
|
return <div className="outputEvent" key={eventName}>{eventName}</div>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||||
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
|
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
|
||||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
import { Conns } from '@/statecharts/timed_reactive';
|
import { Conns } from '@/statecharts/timed_reactive';
|
||||||
import { Dispatch, Ref, SetStateAction, useEffect, useRef, useState } from 'react';
|
import { Dispatch, memo, Ref, SetStateAction, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Statechart } from '@/statecharts/abstract_syntax';
|
import { Statechart } from '@/statecharts/abstract_syntax';
|
||||||
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from './ShowAST';
|
import { ShowAST, ShowInputEvents, ShowInternalEvents, ShowOutputEvents } from './ShowAST';
|
||||||
import { Plant } from '../Plant/Plant';
|
import { Plant } from '../Plant/Plant';
|
||||||
|
|
@ -17,6 +17,7 @@ import { plants, UniversalPlantState } from '../plants';
|
||||||
import { TimeMode } from '@/statecharts/time';
|
import { TimeMode } from '@/statecharts/time';
|
||||||
import { PersistentDetails } from '../Components/PersistentDetails';
|
import { PersistentDetails } from '../Components/PersistentDetails';
|
||||||
import "./SideBar.css";
|
import "./SideBar.css";
|
||||||
|
import { objectsEqual } from '@/util/util';
|
||||||
|
|
||||||
type SavedTraces = [string, BigStepCause[]][];
|
type SavedTraces = [string, BigStepCause[]][];
|
||||||
|
|
||||||
|
|
@ -76,20 +77,20 @@ type SideBarProps = SideBarState & {
|
||||||
time: TimeMode,
|
time: TimeMode,
|
||||||
} & Setters<SideBarState>;
|
} & Setters<SideBarState>;
|
||||||
|
|
||||||
export function SideBar({showExecutionTrace, showConnections, plantName, showPlantTrace, showProperties, activeProperty, autoConnect, autoScroll, plantConns, properties, savedTraces, refRightSideBar, ast, plant, setSavedTraces, trace, setTrace, setProperties, setShowPlantTrace, setActiveProperty, setPlantConns, setPlantName, setAutoConnect, setShowProperties, setAutoScroll, time, plantState, onReplayTrace, onRaise, setTime, setShowConnections, setShowExecutionTrace, showPlant, setShowPlant, showOutputEvents, setShowOutputEvents, setShowInternalEvents, showInternalEvents, setShowInputEvents, setShowStateTree, showInputEvents, showStateTree}: SideBarProps) {
|
export const SideBar = memo(function SideBar({showExecutionTrace, showConnections, plantName, showPlantTrace, showProperties, activeProperty, autoConnect, autoScroll, plantConns, properties, savedTraces, refRightSideBar, ast, plant, setSavedTraces, trace, setTrace, setProperties, setShowPlantTrace, setActiveProperty, setPlantConns, setPlantName, setAutoConnect, setShowProperties, setAutoScroll, time, plantState, onReplayTrace, onRaise, setTime, setShowConnections, setShowExecutionTrace, showPlant, setShowPlant, showOutputEvents, setShowOutputEvents, setShowInternalEvents, showInternalEvents, setShowInputEvents, setShowStateTree, showInputEvents, showStateTree}: SideBarProps) {
|
||||||
|
|
||||||
const [propertyResults, setPropertyResults] = useState<PropertyCheckResult[] | null>(null);
|
const [propertyResults, setPropertyResults] = useState<PropertyCheckResult[] | null>(null);
|
||||||
|
|
||||||
const speed = time.kind === "paused" ? 0 : time.scale;
|
const speed = time.kind === "paused" ? 0 : time.scale;
|
||||||
|
|
||||||
const onSaveTrace = () => {
|
const onSaveTrace = useCallback(() => {
|
||||||
if (trace) {
|
if (trace) {
|
||||||
setSavedTraces(savedTraces => [
|
setSavedTraces(savedTraces => [
|
||||||
...savedTraces,
|
...savedTraces,
|
||||||
["untitled", trace.trace.map((item) => item.cause)] as [string, BigStepCause[]],
|
["untitled", trace.trace.map((item) => item.cause)] as [string, BigStepCause[]],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}, [trace, setSavedTraces]);
|
||||||
|
|
||||||
// if some properties change, re-evaluate them:
|
// if some properties change, re-evaluate them:
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -115,6 +116,9 @@ export function SideBar({showExecutionTrace, showConnections, plantName, showPla
|
||||||
}
|
}
|
||||||
}, [ast, plant, autoConnect]);
|
}, [ast, plant, autoConnect]);
|
||||||
|
|
||||||
|
const raiseDebugEvent = useCallback((e,p) => onRaise("debug."+e,p), [onRaise]);
|
||||||
|
const raiseUIEvent = useCallback(e => onRaise("plant.ui."+e.name, e.param), [onRaise]);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div
|
<div
|
||||||
className={showExecutionTrace ? "shadowBelow" : ""}
|
className={showExecutionTrace ? "shadowBelow" : ""}
|
||||||
|
|
@ -132,7 +136,7 @@ export function SideBar({showExecutionTrace, showConnections, plantName, showPla
|
||||||
<summary>input events</summary>
|
<summary>input events</summary>
|
||||||
{ast && <ShowInputEvents
|
{ast && <ShowInputEvents
|
||||||
inputEvents={ast.inputEvents}
|
inputEvents={ast.inputEvents}
|
||||||
onRaise={(e,p) => onRaise("debug."+e,p)}
|
onRaise={raiseDebugEvent}
|
||||||
disabled={trace===null || trace.trace[trace.idx].kind === "error"}
|
disabled={trace===null || trace.trace[trace.idx].kind === "error"}
|
||||||
/>}
|
/>}
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
|
|
@ -153,14 +157,14 @@ export function SideBar({showExecutionTrace, showConnections, plantName, showPla
|
||||||
disabled={trace!==null}
|
disabled={trace!==null}
|
||||||
value={plantName}
|
value={plantName}
|
||||||
onChange={e => setPlantName(() => e.target.value)}>
|
onChange={e => setPlantName(() => e.target.value)}>
|
||||||
{plants.map(([plantName, p]) =>
|
{plants.map(([plantName]) =>
|
||||||
<option>{plantName}</option>
|
<option key={plantName}>{plantName}</option>
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
<br/>
|
<br/>
|
||||||
{/* Render plant */}
|
{/* Render plant */}
|
||||||
{<plant.render state={plant.cleanupState(plantState)} speed={speed}
|
{<plant.render state={plant.cleanupState(plantState)} speed={speed}
|
||||||
raiseUIEvent={e => onRaise("plant.ui."+e.name, e.param)}
|
raiseUIEvent={raiseUIEvent}
|
||||||
/>}
|
/>}
|
||||||
</PersistentDetails>
|
</PersistentDetails>
|
||||||
{/* Connections */}
|
{/* Connections */}
|
||||||
|
|
@ -251,7 +255,9 @@ export function SideBar({showExecutionTrace, showConnections, plantName, showPla
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
</>;
|
</>;
|
||||||
}
|
}, (prevProps, nextProps) => {
|
||||||
|
return objectsEqual(prevProps, nextProps);
|
||||||
|
});
|
||||||
|
|
||||||
function autoDetectConns(ast: Statechart, plant: Plant<any, any>, setPlantConns: Dispatch<SetStateAction<Conns>>) {
|
function autoDetectConns(ast: Statechart, plant: Plant<any, any>, setPlantConns: Dispatch<SetStateAction<Conns>>) {
|
||||||
for (const {event: a} of plant.uiEvents) {
|
for (const {event: a} of plant.uiEvents) {
|
||||||
|
|
@ -289,7 +295,7 @@ function ConnEditor(ast: Statechart, plant: Plant<any, any>, plantConns: Conns,
|
||||||
return <>
|
return <>
|
||||||
|
|
||||||
{/* SC output events can go to Plant */}
|
{/* SC output events can go to Plant */}
|
||||||
{[...ast.outputEvents].map(e => <div style={{width:'100%', textAlign:'right'}}>
|
{[...ast.outputEvents].map(e => <div key={e} style={{width:'100%', textAlign:'right'}}>
|
||||||
<label htmlFor={`select-dst-sc-${e}`} style={{width:'50%'}}>sc.{e} → </label>
|
<label htmlFor={`select-dst-sc-${e}`} style={{width:'50%'}}>sc.{e} → </label>
|
||||||
<select id={`select-dst-sc-${e}`}
|
<select id={`select-dst-sc-${e}`}
|
||||||
style={{width:'50%'}}
|
style={{width:'50%'}}
|
||||||
|
|
@ -302,7 +308,7 @@ function ConnEditor(ast: Statechart, plant: Plant<any, any>, plantConns: Conns,
|
||||||
</div>)}
|
</div>)}
|
||||||
|
|
||||||
{/* Plant output events can go to Statechart */}
|
{/* Plant output events can go to Statechart */}
|
||||||
{[...plant.outputEvents.map(e => <div style={{width:'100%', textAlign:'right'}}>
|
{[...plant.outputEvents.map(e => <div key={e.event} style={{width:'100%', textAlign:'right'}}>
|
||||||
<label htmlFor={`select-dst-plant-${e.event}`} style={{width:'50%'}}>plant.{e.event} → </label>
|
<label htmlFor={`select-dst-plant-${e.event}`} style={{width:'50%'}}>plant.{e.event} → </label>
|
||||||
<select id={`select-dst-plant-${e.event}`}
|
<select id={`select-dst-plant-${e.event}`}
|
||||||
style={{width:'50%'}}
|
style={{width:'50%'}}
|
||||||
|
|
@ -315,7 +321,7 @@ function ConnEditor(ast: Statechart, plant: Plant<any, any>, plantConns: Conns,
|
||||||
</div>)]}
|
</div>)]}
|
||||||
|
|
||||||
{/* Plant UI events typically go to the Plant */}
|
{/* Plant UI events typically go to the Plant */}
|
||||||
{plant.uiEvents.map(e => <div style={{width:'100%', textAlign:'right'}}>
|
{plant.uiEvents.map(e => <div key={e.event} style={{width:'100%', textAlign:'right'}}>
|
||||||
<label htmlFor={`select-dst-plant-ui-${e.event}`} style={{width:'50%', color: 'grey'}}>ui.{e.event} → </label>
|
<label htmlFor={`select-dst-plant-ui-${e.event}`} style={{width:'50%', color: 'grey'}}>ui.{e.event} → </label>
|
||||||
<select id={`select-dst-plant-ui-${e.event}`}
|
<select id={`select-dst-plant-ui-${e.event}`}
|
||||||
style={{width:'50%'}}
|
style={{width:'50%'}}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ export type TraceState = {
|
||||||
idx: number,
|
idx: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ignoreRaise = (inputEvent: string, param: any) => {};
|
||||||
|
|
||||||
|
|
||||||
export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPlantState>, plantConns: Conns, onStep: () => void) {
|
export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPlantState>, plantConns: Conns, onStep: () => void) {
|
||||||
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
||||||
|
|
@ -53,7 +55,7 @@ export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPl
|
||||||
}, {
|
}, {
|
||||||
...plantConns,
|
...plantConns,
|
||||||
...Object.fromEntries(ast.inputEvents.map(({event}) => ["debug."+event, ['sc',event] as [string,string]])),
|
...Object.fromEntries(ast.inputEvents.map(({event}) => ["debug."+event, ['sc',event] as [string,string]])),
|
||||||
}), [ast]);
|
}), [ast, plant, plantConns]);
|
||||||
|
|
||||||
const onInit = useCallback(() => {
|
const onInit = useCallback(() => {
|
||||||
if (cE === null) return;
|
if (cE === null) return;
|
||||||
|
|
@ -92,18 +94,51 @@ export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPl
|
||||||
setTime({kind: "paused", simtime: 0});
|
setTime({kind: "paused", simtime: 0});
|
||||||
}, [setTrace, setTime]);
|
}, [setTrace, setTime]);
|
||||||
|
|
||||||
|
const appendNewConfig = useCallback((simtime: number, cause: BigStepCause, computeNewState: () => [RaisedEvent[], CoupledState]) => {
|
||||||
|
let newItem: TraceItem;
|
||||||
|
const metadata = {simtime, cause}
|
||||||
|
try {
|
||||||
|
const [outputEvents, state] = computeNewState(); // may throw RuntimeError
|
||||||
|
newItem = {kind: "bigstep", ...metadata, state, outputEvents};
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof RuntimeError) {
|
||||||
|
newItem = {kind: "error", ...metadata, error};
|
||||||
|
// also pause the simulation, for dramatic effect:
|
||||||
|
setTime({kind: "paused", simtime});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
setTrace(trace => ({
|
||||||
|
trace: [
|
||||||
|
...trace!.trace.slice(0, trace!.idx+1), // remove everything after current item
|
||||||
|
newItem,
|
||||||
|
],
|
||||||
|
// idx: 0,
|
||||||
|
idx: trace!.idx+1,
|
||||||
|
}));
|
||||||
|
onStep();
|
||||||
|
}, [onStep, setTrace, setTime]);
|
||||||
|
|
||||||
// raise input event, producing a new runtime configuration (or a runtime error)
|
// raise input event, producing a new runtime configuration (or a runtime error)
|
||||||
const onRaise = (inputEvent: string, param: any) => {
|
const onRaise = useMemo(() => {
|
||||||
if (cE === null) return;
|
if (cE === null || currentTraceItem === null) {
|
||||||
if (currentTraceItem !== null /*&& ast.inputEvents.some(e => e.event === inputEvent)*/) {
|
return ignoreRaise; // this speeds up rendering of components that depend on onRaise if the model is being edited while there is no ongoing trace
|
||||||
|
}
|
||||||
|
else return (inputEvent: string, param: any) => {
|
||||||
if (currentTraceItem.kind === "bigstep") {
|
if (currentTraceItem.kind === "bigstep") {
|
||||||
const simtime = getSimTime(time, Math.round(performance.now()));
|
const simtime = getSimTime(time, Math.round(performance.now()));
|
||||||
appendNewConfig(simtime, {kind: "input", simtime, eventName: inputEvent, param}, () => {
|
appendNewConfig(simtime, {kind: "input", simtime, eventName: inputEvent, param}, () => {
|
||||||
return cE.extTransition(simtime, currentTraceItem.state, {kind: "input", name: inputEvent, param});
|
return cE.extTransition(simtime, currentTraceItem.state, {kind: "input", name: inputEvent, param});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}, [cE, currentTraceItem, time, appendNewConfig]);
|
||||||
|
|
||||||
|
console.log({onRaise});
|
||||||
|
|
||||||
// timer elapse events are triggered by a change of the simulated time (possibly as a scheduled JS event loop timeout)
|
// timer elapse events are triggered by a change of the simulated time (possibly as a scheduled JS event loop timeout)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -137,35 +172,6 @@ export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPl
|
||||||
}
|
}
|
||||||
}, [time, currentTraceItem]); // <-- todo: is this really efficient?
|
}, [time, currentTraceItem]); // <-- todo: is this really efficient?
|
||||||
|
|
||||||
function appendNewConfig(simtime: number, cause: BigStepCause, computeNewState: () => [RaisedEvent[], CoupledState]) {
|
|
||||||
let newItem: TraceItem;
|
|
||||||
const metadata = {simtime, cause}
|
|
||||||
try {
|
|
||||||
const [outputEvents, state] = computeNewState(); // may throw RuntimeError
|
|
||||||
newItem = {kind: "bigstep", ...metadata, state, outputEvents};
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (error instanceof RuntimeError) {
|
|
||||||
newItem = {kind: "error", ...metadata, error};
|
|
||||||
// also pause the simulation, for dramatic effect:
|
|
||||||
setTime({kind: "paused", simtime});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
setTrace(trace => ({
|
|
||||||
trace: [
|
|
||||||
...trace!.trace.slice(0, trace!.idx+1), // remove everything after current item
|
|
||||||
newItem,
|
|
||||||
],
|
|
||||||
// idx: 0,
|
|
||||||
idx: trace!.idx+1,
|
|
||||||
}));
|
|
||||||
onStep();
|
|
||||||
}
|
|
||||||
|
|
||||||
const onBack = useCallback(() => {
|
const onBack = useCallback(() => {
|
||||||
if (trace !== null) {
|
if (trace !== null) {
|
||||||
setTime(() => {
|
setTime(() => {
|
||||||
|
|
@ -182,9 +188,9 @@ export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPl
|
||||||
idx: trace.idx-1,
|
idx: trace.idx-1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [trace]);
|
}, [trace, setTime, setTrace]);
|
||||||
|
|
||||||
const onReplayTrace = (causes: BigStepCause[]) => {
|
const onReplayTrace = useCallback((causes: BigStepCause[]) => {
|
||||||
if (cE) {
|
if (cE) {
|
||||||
function run_until(simtime: number) {
|
function run_until(simtime: number) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -218,7 +224,7 @@ export function useSimulator(ast: Statechart|null, plant: Plant<any, UniversalPl
|
||||||
setTrace({trace: newTrace, idx: newTrace.length-1});
|
setTrace({trace: newTrace, idx: newTrace.length-1});
|
||||||
setTime({kind: "paused", simtime: lastSimtime});
|
setTime({kind: "paused", simtime: lastSimtime});
|
||||||
}
|
}
|
||||||
}
|
}, [setTrace, setTime, cE]);
|
||||||
|
|
||||||
return {trace, setTrace, plant, onInit, onClear, onBack, onRaise, onReplayTrace, time, setTime};
|
return {trace, setTrace, plant, onInit, onClear, onBack, onRaise, onReplayTrace, time, setTime};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
src/hooks/useCustomMemo.ts
Normal file
19
src/hooks/useCustomMemo.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
// author: ChatGPT
|
||||||
|
export function useCustomMemo<T, DepsType extends unknown[]>(
|
||||||
|
compute: () => T,
|
||||||
|
deps: DepsType,
|
||||||
|
isEqual: (a: DepsType, b: DepsType) => boolean
|
||||||
|
) {
|
||||||
|
const prev = useRef<{ deps: DepsType; value: T }>(null);
|
||||||
|
|
||||||
|
if (!prev.current || !isEqual(deps, prev.current.deps)) {
|
||||||
|
prev.current = {
|
||||||
|
deps,
|
||||||
|
value: compute(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev.current.value;
|
||||||
|
}
|
||||||
|
|
@ -1,60 +1,77 @@
|
||||||
import { VisualEditorState } from "@/App/VisualEditor/VisualEditor";
|
import { isEntirelyWithin, Rect2D } from "@/util/geometry";
|
||||||
import { ConcreteSyntax } from "./concrete_syntax";
|
import { ConcreteSyntax, Rountangle } from "./concrete_syntax";
|
||||||
import { findNearestArrow, findNearestHistory, findNearestSide, findRountangle, RectSide } from "./concrete_syntax";
|
import { findNearestArrow, findNearestHistory, findNearestSide, findRountangle, RectSide } from "./concrete_syntax";
|
||||||
|
import { arraysEqual, jsonDeepEqual, mapsEqual, setsEqual } from "@/util/util";
|
||||||
|
import { HISTORY_RADIUS } from "@/App/parameters";
|
||||||
|
|
||||||
export type Connections = {
|
export type Connections = {
|
||||||
arrow2SideMap: Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>,
|
arrow2SideMap: Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>,
|
||||||
side2ArrowMap: Map<string, Set<["start"|"end", string]>>,
|
side2ArrowMap: Map<string, ["start"|"end", string][]>,
|
||||||
text2ArrowMap: Map<string,string>,
|
text2ArrowMap: Map<string,string>,
|
||||||
arrow2TextMap: Map<string,string[]>,
|
arrow2TextMap: Map<string,string[]>,
|
||||||
arrow2HistoryMap: Map<string,string>,
|
arrow2HistoryMap: Map<string,string>,
|
||||||
text2RountangleMap: Map<string, string>,
|
text2RountangleMap: Map<string, string>,
|
||||||
rountangle2TextMap: Map<string, string[]>,
|
rountangle2TextMap: Map<string, string[]>,
|
||||||
history2ArrowMap: Map<string, string[]>,
|
history2ArrowMap: Map<string, string[]>,
|
||||||
|
insidenessMap: Map<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function detectConnections(state: ConcreteSyntax): Connections {
|
export function connectionsEqual(a: Connections, b: Connections) {
|
||||||
|
return mapsEqual(a.arrow2SideMap, b.arrow2SideMap, jsonDeepEqual)
|
||||||
|
&& mapsEqual(a.side2ArrowMap, b.side2ArrowMap, (a,b)=>arraysEqual(a,b,jsonDeepEqual))
|
||||||
|
&& mapsEqual(a.text2ArrowMap, b.text2ArrowMap)
|
||||||
|
&& mapsEqual(a.arrow2HistoryMap, b.arrow2HistoryMap)
|
||||||
|
&& mapsEqual(a.text2RountangleMap, b.text2RountangleMap)
|
||||||
|
&& mapsEqual(a.rountangle2TextMap, b.rountangle2TextMap, arraysEqual)
|
||||||
|
&& mapsEqual(a.history2ArrowMap, b.history2ArrowMap, arraysEqual)
|
||||||
|
&& mapsEqual(a.insidenessMap, b.insidenessMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function does the heavy lifting of parsing the concrete syntax:
|
||||||
|
// It detects insideness and connectedness relations based on the geometries of the shapes.
|
||||||
|
export function detectConnections(concreteSyntax: ConcreteSyntax): Connections {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
// detect what is 'connected'
|
// detect what is 'connected'
|
||||||
const arrow2SideMap = new Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>();
|
const arrow2SideMap = new Map<string,[{ uid: string; part: RectSide; } | undefined, { uid: string; part: RectSide; } | undefined]>();
|
||||||
const side2ArrowMap = new Map<string, Set<["start"|"end", string]>>();
|
const side2ArrowMap = new Map<string, ["start"|"end", string][]>();
|
||||||
const text2ArrowMap = new Map<string,string>();
|
const text2ArrowMap = new Map<string,string>();
|
||||||
const arrow2TextMap = new Map<string,string[]>();
|
const arrow2TextMap = new Map<string,string[]>();
|
||||||
const arrow2HistoryMap = new Map<string,string>();
|
const arrow2HistoryMap = new Map<string,string>();
|
||||||
const text2RountangleMap = new Map<string, string>();
|
const text2RountangleMap = new Map<string, string>();
|
||||||
const rountangle2TextMap = new Map<string, string[]>();
|
const rountangle2TextMap = new Map<string, string[]>();
|
||||||
const history2ArrowMap = new Map<string, string[]>();
|
const history2ArrowMap = new Map<string, string[]>();
|
||||||
|
const insidenessMap = new Map<string, string>();
|
||||||
|
|
||||||
// arrow <-> (rountangle | diamond)
|
// arrow <-> (rountangle | diamond)
|
||||||
for (const arrow of state.arrows) {
|
for (const arrow of concreteSyntax.arrows) {
|
||||||
// snap to history:
|
// snap to history:
|
||||||
const historyTarget = findNearestHistory(arrow.end, state.history);
|
const historyTarget = findNearestHistory(arrow.end, concreteSyntax.history);
|
||||||
if (historyTarget) {
|
if (historyTarget) {
|
||||||
arrow2HistoryMap.set(arrow.uid, historyTarget.uid);
|
arrow2HistoryMap.set(arrow.uid, historyTarget.uid);
|
||||||
history2ArrowMap.set(historyTarget.uid, [...(history2ArrowMap.get(historyTarget.uid) || []), arrow.uid]);
|
history2ArrowMap.set(historyTarget.uid, [...(history2ArrowMap.get(historyTarget.uid) || []), arrow.uid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// snap to rountangle/diamon side:
|
// snap to rountangle/diamon side:
|
||||||
const sides = [...state.rountangles, ...state.diamonds];
|
const sides = [...concreteSyntax.rountangles, ...concreteSyntax.diamonds];
|
||||||
const startSide = findNearestSide(arrow, "start", sides);
|
const startSide = findNearestSide(arrow, "start", sides);
|
||||||
const endSide = historyTarget ? undefined : findNearestSide(arrow, "end", sides);
|
const endSide = historyTarget ? undefined : findNearestSide(arrow, "end", sides);
|
||||||
if (startSide || endSide) {
|
if (startSide || endSide) {
|
||||||
arrow2SideMap.set(arrow.uid, [startSide, endSide]);
|
arrow2SideMap.set(arrow.uid, [startSide, endSide]);
|
||||||
}
|
}
|
||||||
if (startSide) {
|
if (startSide) {
|
||||||
const arrowConns = side2ArrowMap.get(startSide.uid + '/' + startSide.part) || new Set();
|
const arrowConns = side2ArrowMap.get(startSide.uid + '/' + startSide.part) || [];
|
||||||
arrowConns.add(["start", arrow.uid]);
|
arrowConns.push(["start", arrow.uid]);
|
||||||
side2ArrowMap.set(startSide.uid + '/' + startSide.part, arrowConns);
|
side2ArrowMap.set(startSide.uid + '/' + startSide.part, arrowConns);
|
||||||
}
|
}
|
||||||
if (endSide) {
|
if (endSide) {
|
||||||
const arrowConns = side2ArrowMap.get(endSide.uid + '/' + endSide.part) || new Set();
|
const arrowConns = side2ArrowMap.get(endSide.uid + '/' + endSide.part) || [];
|
||||||
arrowConns.add(["end", arrow.uid]);
|
arrowConns.push(["end", arrow.uid]);
|
||||||
side2ArrowMap.set(endSide.uid + '/' + endSide.part, arrowConns);
|
side2ArrowMap.set(endSide.uid + '/' + endSide.part, arrowConns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// text <-> arrow
|
// text <-> arrow
|
||||||
for (const text of state.texts) {
|
for (const text of concreteSyntax.texts) {
|
||||||
const nearestArrow = findNearestArrow(text.topLeft, state.arrows);
|
const nearestArrow = findNearestArrow(text.topLeft, concreteSyntax.arrows);
|
||||||
if (nearestArrow) {
|
if (nearestArrow) {
|
||||||
// prioritize text belonging to arrows:
|
// prioritize text belonging to arrows:
|
||||||
text2ArrowMap.set(text.uid, nearestArrow.uid);
|
text2ArrowMap.set(text.uid, nearestArrow.uid);
|
||||||
|
|
@ -64,7 +81,7 @@ export function detectConnections(state: ConcreteSyntax): Connections {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// text <-> rountangle
|
// text <-> rountangle
|
||||||
const rountangle = findRountangle(text.topLeft, state.rountangles);
|
const rountangle = findRountangle(text.topLeft, concreteSyntax.rountangles);
|
||||||
if (rountangle) {
|
if (rountangle) {
|
||||||
text2RountangleMap.set(text.uid, rountangle.uid);
|
text2RountangleMap.set(text.uid, rountangle.uid);
|
||||||
const texts = rountangle2TextMap.get(rountangle.uid) || [];
|
const texts = rountangle2TextMap.get(rountangle.uid) || [];
|
||||||
|
|
@ -74,6 +91,42 @@ export function detectConnections(state: ConcreteSyntax): Connections {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// figure out insideness...
|
||||||
|
|
||||||
|
const parentCandidates: Rountangle[] = [{
|
||||||
|
kind: "or",
|
||||||
|
uid: "root",
|
||||||
|
topLeft: {x: -Infinity, y: -Infinity},
|
||||||
|
size: {x: Infinity, y: Infinity},
|
||||||
|
}];
|
||||||
|
|
||||||
|
function findParent(geom: Rect2D): string {
|
||||||
|
// iterate in reverse:
|
||||||
|
for (let i = parentCandidates.length-1; i >= 0; i--) {
|
||||||
|
const candidate = parentCandidates[i];
|
||||||
|
if (candidate.uid === "root" || isEntirelyWithin(geom, candidate)) {
|
||||||
|
// found our parent
|
||||||
|
return candidate.uid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("impossible: should always find a parent state");
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORTANT ASSUMPTION: state.rountangles is sorted from big to small surface area:
|
||||||
|
for (const rt of concreteSyntax.rountangles) {
|
||||||
|
const parent = findParent(rt);
|
||||||
|
insidenessMap.set(rt.uid, parent);
|
||||||
|
parentCandidates.push(rt);
|
||||||
|
}
|
||||||
|
for (const d of concreteSyntax.diamonds) {
|
||||||
|
const parent = findParent(d);
|
||||||
|
insidenessMap.set(d.uid, parent);
|
||||||
|
}
|
||||||
|
for (const h of concreteSyntax.history) {
|
||||||
|
const parent = findParent({topLeft: h.topLeft, size: {x: HISTORY_RADIUS*2, y: HISTORY_RADIUS*2}});
|
||||||
|
insidenessMap.set(h.uid, parent);
|
||||||
|
}
|
||||||
|
|
||||||
const endTime = performance.now();
|
const endTime = performance.now();
|
||||||
|
|
||||||
// rather slow, about 10ms for a large model:
|
// rather slow, about 10ms for a large model:
|
||||||
|
|
@ -88,5 +141,35 @@ export function detectConnections(state: ConcreteSyntax): Connections {
|
||||||
text2RountangleMap,
|
text2RountangleMap,
|
||||||
rountangle2TextMap,
|
rountangle2TextMap,
|
||||||
history2ArrowMap,
|
history2ArrowMap,
|
||||||
|
insidenessMap,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ReducedConcreteSyntax = {
|
||||||
|
rountangles: {
|
||||||
|
kind: "and" | "or",
|
||||||
|
uid: string,
|
||||||
|
}[];
|
||||||
|
texts: {
|
||||||
|
text: string,
|
||||||
|
uid: string,
|
||||||
|
}[];
|
||||||
|
arrows: {
|
||||||
|
uid: string,
|
||||||
|
}[];
|
||||||
|
diamonds: {
|
||||||
|
uid: string,
|
||||||
|
}[];
|
||||||
|
history: {
|
||||||
|
kind: "deep" | "shallow",
|
||||||
|
uid: string,
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function reducedConcreteSyntaxEqual(a: ReducedConcreteSyntax, b: ReducedConcreteSyntax) {
|
||||||
|
return arraysEqual(a.rountangles, b.rountangles, (a,b)=>a.kind===b.kind&&a.uid===b.uid)
|
||||||
|
&& arraysEqual(a.texts, b.texts, (a,b)=>a.text===b.text&&a.uid===b.uid)
|
||||||
|
&& arraysEqual(a.arrows, b.arrows, (a,b)=>a.uid===b.uid)
|
||||||
|
&& arraysEqual(a.diamonds, b.diamonds, (a,b)=>a.uid===b.uid)
|
||||||
|
&& arraysEqual(a.history, b.history, (a,b)=>a.kind===b.kind&&a.uid===b.uid);
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import { ConcreteState, HistoryState, OrState, UnstableState, Statechart, stateDescription, Transition } from "./abstract_syntax";
|
import { ConcreteState, HistoryState, OrState, UnstableState, Statechart, stateDescription, Transition } from "./abstract_syntax";
|
||||||
import { Rountangle } from "./concrete_syntax";
|
|
||||||
import { isEntirelyWithin, Rect2D } from "../util/geometry";
|
|
||||||
import { Action, EventTrigger, Expression, ParsedText } from "./label_ast";
|
import { Action, EventTrigger, Expression, ParsedText } from "./label_ast";
|
||||||
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
||||||
import { Connections } from "./detect_connections";
|
import { Connections, ReducedConcreteSyntax } from "./detect_connections";
|
||||||
import { HISTORY_RADIUS } from "../App/parameters";
|
|
||||||
import { ConcreteSyntax } from "./concrete_syntax";
|
|
||||||
import { memoize } from "@/util/util";
|
import { memoize } from "@/util/util";
|
||||||
|
|
||||||
export type TraceableError = {
|
export type TraceableError = {
|
||||||
|
|
@ -34,7 +30,7 @@ function addEvent(events: EventTrigger[], e: EventTrigger, textUid: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Statechart, TraceableError[]] {
|
export function parseStatechart(concreteSyntax: ReducedConcreteSyntax, conns: Connections): [Statechart, TraceableError[]] {
|
||||||
const errors: TraceableError[] = [];
|
const errors: TraceableError[] = [];
|
||||||
|
|
||||||
// implicitly, the root is always an Or-state
|
// implicitly, the root is always an Or-state
|
||||||
|
|
@ -54,36 +50,14 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
const uid2State = new Map<string, ConcreteState|UnstableState>([["root", root]]);
|
const uid2State = new Map<string, ConcreteState|UnstableState>([["root", root]]);
|
||||||
const label2State = new Map<string, ConcreteState>();
|
const label2State = new Map<string, ConcreteState>();
|
||||||
const historyStates: HistoryState[] = [];
|
const historyStates: HistoryState[] = [];
|
||||||
|
|
||||||
// we will always look for the smallest parent rountangle
|
|
||||||
const parentCandidates: Rountangle[] = [{
|
|
||||||
kind: "or",
|
|
||||||
uid: root.uid,
|
|
||||||
topLeft: {x: -Infinity, y: -Infinity},
|
|
||||||
size: {x: Infinity, y: Infinity},
|
|
||||||
}];
|
|
||||||
|
|
||||||
const parentLinks = new Map<string, string>();
|
const parentLinks = new Map<string, string>();
|
||||||
|
|
||||||
function findParent(geom: Rect2D): ConcreteState {
|
|
||||||
// iterate in reverse:
|
|
||||||
for (let i=parentCandidates.length-1; i>=0; i--) {
|
|
||||||
const candidate = parentCandidates[i];
|
|
||||||
if (candidate.uid === "root" || isEntirelyWithin(geom, candidate)) {
|
|
||||||
// found our parent
|
|
||||||
return uid2State.get(candidate.uid)! as ConcreteState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error("impossible: should always find a parent state");
|
|
||||||
}
|
|
||||||
|
|
||||||
// step 1: figure out state hierarchy
|
// step 1: figure out state hierarchy
|
||||||
|
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
// IMPORTANT ASSUMPTION: state.rountangles is sorted from big to small surface area:
|
for (const rt of concreteSyntax.rountangles) {
|
||||||
for (const rt of state.rountangles) {
|
const parent = uid2State.get(conns.insidenessMap.get(rt.uid)!)! as ConcreteState;
|
||||||
const parent = findParent(rt);
|
|
||||||
const common = {
|
const common = {
|
||||||
kind: rt.kind,
|
kind: rt.kind,
|
||||||
uid: rt.uid,
|
uid: rt.uid,
|
||||||
|
|
@ -112,12 +86,11 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
parent.children.push(state as ConcreteState);
|
parent.children.push(state as ConcreteState);
|
||||||
parentCandidates.push(rt);
|
|
||||||
parentLinks.set(rt.uid, parent.uid);
|
parentLinks.set(rt.uid, parent.uid);
|
||||||
uid2State.set(rt.uid, state as ConcreteState);
|
uid2State.set(rt.uid, state as ConcreteState);
|
||||||
}
|
}
|
||||||
for (const d of state.diamonds) {
|
for (const d of concreteSyntax.diamonds) {
|
||||||
const parent = findParent(d);
|
const parent = uid2State.get(conns.insidenessMap.get(d.uid)!)! as ConcreteState;
|
||||||
const pseudoState = {
|
const pseudoState = {
|
||||||
kind: "pseudo",
|
kind: "pseudo",
|
||||||
uid: d.uid,
|
uid: d.uid,
|
||||||
|
|
@ -130,8 +103,8 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
uid2State.set(d.uid, pseudoState);
|
uid2State.set(d.uid, pseudoState);
|
||||||
parent.children.push(pseudoState);
|
parent.children.push(pseudoState);
|
||||||
}
|
}
|
||||||
for (const h of state.history) {
|
for (const h of concreteSyntax.history) {
|
||||||
const parent = findParent({topLeft: h.topLeft, size: {x: HISTORY_RADIUS*2, y: HISTORY_RADIUS*2}});
|
const parent = uid2State.get(conns.insidenessMap.get(h.uid)!)! as ConcreteState;
|
||||||
const historyState = {
|
const historyState = {
|
||||||
kind: h.kind,
|
kind: h.kind,
|
||||||
uid: h.uid,
|
uid: h.uid,
|
||||||
|
|
@ -152,7 +125,7 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
const transitions = new Map<string, Transition[]>();
|
const transitions = new Map<string, Transition[]>();
|
||||||
const uid2Transition = new Map<string, Transition>();
|
const uid2Transition = new Map<string, Transition>();
|
||||||
|
|
||||||
for (const arr of state.arrows) {
|
for (const arr of concreteSyntax.arrows) {
|
||||||
const srcUID = conns.arrow2SideMap.get(arr.uid)?.[0]?.uid;
|
const srcUID = conns.arrow2SideMap.get(arr.uid)?.[0]?.uid;
|
||||||
const tgtUID = conns.arrow2SideMap.get(arr.uid)?.[1]?.uid;
|
const tgtUID = conns.arrow2SideMap.get(arr.uid)?.[1]?.uid;
|
||||||
const historyTgtUID = conns.arrow2HistoryMap.get(arr.uid);
|
const historyTgtUID = conns.arrow2HistoryMap.get(arr.uid);
|
||||||
|
|
@ -243,7 +216,7 @@ export function parseStatechart(state: ConcreteSyntax, conns: Connections): [Sta
|
||||||
|
|
||||||
// step 3: figure out labels
|
// step 3: figure out labels
|
||||||
|
|
||||||
const textsSorted = state.texts.toSorted((a,b) => a.topLeft.y - b.topLeft.y);
|
const textsSorted = concreteSyntax.texts.toSorted((a,b) => a.topLeft.y - b.topLeft.y);
|
||||||
for (const text of textsSorted) {
|
for (const text of textsSorted) {
|
||||||
let parsed: ParsedText;
|
let parsed: ParsedText;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue