highlight fired transitions
This commit is contained in:
parent
b8bc977a8e
commit
a10bf9acc8
6 changed files with 80 additions and 34 deletions
|
|
@ -19,13 +19,10 @@ import { BottomPanel } from "./BottomPanel";
|
|||
|
||||
export function App() {
|
||||
const [mode, setMode] = useState<InsertMode>("and");
|
||||
|
||||
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
||||
const [errors, setErrors] = useState<TraceableError[]>([]);
|
||||
|
||||
const [rt, setRT] = useState<BigStep[]>([]);
|
||||
const [rtIdx, setRTIdx] = useState<number|undefined>();
|
||||
|
||||
const [time, setTime] = useState<TimeMode>({kind: "paused", simtime: 0});
|
||||
|
||||
const refRightSideBar = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -54,17 +51,22 @@ export function App() {
|
|||
function appendNewConfig(inputEvent: string, simtime: number, config: BigStepOutput) {
|
||||
setRT([...rt.slice(0, rtIdx!+1), {inputEvent, simtime, ...config}]);
|
||||
setRTIdx(rtIdx!+1);
|
||||
// console.log('new config:', config);
|
||||
console.log('new config:', config);
|
||||
if (refRightSideBar.current) {
|
||||
|
||||
const el = refRightSideBar.current;
|
||||
console.log('scrolling to:', el);
|
||||
setTimeout(() => {
|
||||
el.scrollIntoView({block: "end", behavior: "smooth"});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Welcome to StateBuddy!");
|
||||
() => {
|
||||
console.log("Goodbye!");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: NodeJS.Timeout | undefined;
|
||||
if (rtIdx !== undefined) {
|
||||
|
|
@ -108,6 +110,8 @@ export function App() {
|
|||
return state && state.parent?.kind !== "and";
|
||||
})) || new Set();
|
||||
|
||||
const highlightTransitions = (rtIdx === undefined) ? [] : rt[rtIdx].firedTransitions;
|
||||
|
||||
return <Stack sx={{height:'100vh'}}>
|
||||
{/* Top bar */}
|
||||
<Box
|
||||
|
|
@ -125,7 +129,7 @@ export function App() {
|
|||
<Stack direction="row" sx={{height:'calc(100vh - 64px)'}}>
|
||||
{/* main */}
|
||||
<Box sx={{flexGrow:1, overflow:'auto'}}>
|
||||
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors, mode, highlightActive}}/>
|
||||
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors, mode, highlightActive, highlightTransitions}}/>
|
||||
</Box>
|
||||
{/* right sidebar */}
|
||||
<Box
|
||||
|
|
|
|||
|
|
@ -3,18 +3,25 @@ import { ArcDirection, euclideanDistance } from "./geometry";
|
|||
import { CORNER_HELPER_RADIUS } from "./parameters";
|
||||
|
||||
|
||||
export function ArrowSVG(props: { arrow: Arrow; selected: string[]; errors: string[]; highlight: boolean; arc: ArcDirection; }) {
|
||||
export function ArrowSVG(props: { arrow: Arrow; selected: string[]; errors: string[]; highlight: boolean; fired: boolean; arc: ArcDirection; initialMarker: boolean }) {
|
||||
const { start, end, uid } = props.arrow;
|
||||
const radius = euclideanDistance(start, end) / 1.6;
|
||||
const largeArc = "1";
|
||||
const arcOrLine = props.arc === "no" ? "L" :
|
||||
let largeArc = "1";
|
||||
let arcOrLine = props.arc === "no" ? "L" :
|
||||
`A ${radius} ${radius} 0 ${largeArc} ${props.arc === "ccw" ? "0" : "1"}`;
|
||||
if (props.initialMarker) {
|
||||
// largeArc = "0";
|
||||
arcOrLine = `A ${radius*2} ${radius*2} 0 0 1`
|
||||
}
|
||||
return <g>
|
||||
<path
|
||||
className={"arrow"
|
||||
+ (props.selected.length === 2 ? " selected" : "")
|
||||
+ (props.errors.length > 0 ? " error" : "")
|
||||
+ (props.highlight ? " highlight" : "")}
|
||||
+ (props.highlight ? " highlight" : "")
|
||||
+ (props.fired ? " fired" : "")
|
||||
}
|
||||
markerStart={props.initialMarker ? 'url(#initialMarker)' : undefined}
|
||||
markerEnd='url(#arrowEnd)'
|
||||
d={`M ${start.x} ${start.y}
|
||||
${arcOrLine}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,11 @@
|
|||
/* fill-opacity: 0.2; */
|
||||
/* stroke: rgb(100, 149, 237); */
|
||||
/* stroke: */
|
||||
filter: drop-shadow( 0px 0px 6px rgba(128, 72, 0, 0.856));
|
||||
stroke: rgb(192, 125, 0);
|
||||
fill:rgb(255, 251, 244);
|
||||
/* fill: lightgrey; */
|
||||
/* color: white; */
|
||||
filter: drop-shadow( 0px 0px 3px rgba(192, 125, 0, 0.856));
|
||||
/* stroke-width: 3px; */
|
||||
}
|
||||
|
||||
|
|
@ -99,9 +103,16 @@ circle.helper:hover:not(:active) {
|
|||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.arrow::marker {
|
||||
fill: content-stroke;
|
||||
}
|
||||
|
||||
#arrowEnd {
|
||||
fill: context-stroke;
|
||||
}
|
||||
#initialMarker {
|
||||
fill: context-stroke;
|
||||
}
|
||||
|
||||
.arrow:hover {
|
||||
cursor: grab;
|
||||
|
|
@ -157,6 +168,11 @@ text.helper:hover {
|
|||
.arrow.error {
|
||||
stroke: rgb(230,0,0);
|
||||
}
|
||||
.arrow.fired {
|
||||
stroke: rgb(192, 125, 0);
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
text.error, tspan.error {
|
||||
fill: rgb(230,0,0);
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -68,9 +68,10 @@ type VisualEditorProps = {
|
|||
setErrors: Dispatch<SetStateAction<TraceableError[]>>,
|
||||
mode: InsertMode,
|
||||
highlightActive: Set<string>,
|
||||
highlightTransitions: string[],
|
||||
};
|
||||
|
||||
export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highlightActive}: VisualEditorProps) {
|
||||
export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highlightActive, highlightTransitions}: VisualEditorProps) {
|
||||
const [historyState, setHistoryState] = useState<HistoryState>({current: emptyState, history: [], future: []});
|
||||
|
||||
const state = historyState.current;
|
||||
|
|
@ -679,11 +680,8 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const active = rt?.mode || new Set();
|
||||
|
||||
console.log(highlightActive);
|
||||
|
||||
const rootErrors = errors.filter(({shapeUid}) => shapeUid === "root").map(({message}) => message);
|
||||
|
||||
return <svg width="4000px" height="4000px"
|
||||
|
|
@ -700,6 +698,16 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh
|
|||
onCut={onCut}
|
||||
>
|
||||
<defs>
|
||||
<marker
|
||||
id="initialMarker"
|
||||
viewBox="0 0 9 9"
|
||||
refX="4.5"
|
||||
refY="4.5"
|
||||
markerWidth="9"
|
||||
markerHeight="9"
|
||||
markerUnits="userSpaceOnUse">
|
||||
<circle cx={4.5} cy={4.5} r={4.5}/>
|
||||
</marker>
|
||||
<marker
|
||||
id="arrowEnd"
|
||||
viewBox="0 0 10 10"
|
||||
|
|
@ -709,7 +717,7 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh
|
|||
markerHeight="12"
|
||||
orient="auto-start-reverse"
|
||||
markerUnits="userSpaceOnUse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||
<path d="M 0 0 L 10 5 L 0 10 z"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
|
|
@ -752,6 +760,7 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh
|
|||
if (sides && sides[0]?.uid === sides[1]?.uid && sides[0]!.uid !== undefined) {
|
||||
arc = arcDirection(sides[0]!.part, sides[1]!.part);
|
||||
}
|
||||
const initialMarker = sides && sides[0] === undefined && sides[1] !== undefined;
|
||||
return <ArrowSVG
|
||||
key={arrow.uid}
|
||||
arrow={arrow}
|
||||
|
|
@ -760,7 +769,9 @@ export function VisualEditor({ast, setAST, rt, errors, setErrors, mode, highligh
|
|||
.filter(({shapeUid}) => shapeUid === arrow.uid)
|
||||
.map(({message}) => message)}
|
||||
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
||||
fired={highlightTransitions.includes(arrow.uid)}
|
||||
arc={arc}
|
||||
initialMarker={initialMarker}
|
||||
/>;
|
||||
}
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -94,10 +94,10 @@ export function exitActions(simtime: number, state: ConcreteState, {enteredState
|
|||
|
||||
// recursively enter the given state's default state
|
||||
export function enterDefault(simtime: number, state: ConcreteState, rt: ActionScope): EnteredScope {
|
||||
let actionScope = rt;
|
||||
let {firedTransitions, ...actionScope} = rt;
|
||||
|
||||
// execute entry actions
|
||||
actionScope = entryActions(simtime, state, actionScope);
|
||||
({firedTransitions, ...actionScope} = entryActions(simtime, state, {firedTransitions, ...actionScope}));
|
||||
|
||||
// enter children...
|
||||
let enteredStates = new Set([state.uid]);
|
||||
|
|
@ -105,7 +105,7 @@ export function enterDefault(simtime: number, state: ConcreteState, rt: ActionSc
|
|||
// enter every child
|
||||
for (const child of state.children) {
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, child, actionScope));
|
||||
({enteredStates: enteredChildren, firedTransitions, ...actionScope} = enterDefault(simtime, child, {firedTransitions, ...actionScope}));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
}
|
||||
|
|
@ -115,14 +115,16 @@ export function enterDefault(simtime: number, state: ConcreteState, rt: ActionSc
|
|||
if (state.initial.length > 1) {
|
||||
console.warn(state.uid + ': multiple initial states, only entering one of them');
|
||||
}
|
||||
const [arrowUid, toEnter] = state.initial[0];
|
||||
firedTransitions = [...firedTransitions, arrowUid];
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, state.initial[0][1], actionScope));
|
||||
({enteredStates: enteredChildren, firedTransitions, ...actionScope} = enterDefault(simtime, toEnter, {firedTransitions, ...actionScope}));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
// console.warn(state.uid + ': no initial state');
|
||||
}
|
||||
|
||||
return {enteredStates, ...actionScope};
|
||||
return {enteredStates, firedTransitions, ...actionScope};
|
||||
}
|
||||
|
||||
// recursively enter the given state and, if children need to be entered, preferrably those occurring in 'toEnter' will be entered. If no child occurs in 'toEnter', the default child will be entered.
|
||||
|
|
@ -281,14 +283,14 @@ export function handleInputEvent(simtime: number, event: RT_Event, statechart: S
|
|||
return handleInternalEvents(simtime, statechart, {mode, environment, history, ...raised});
|
||||
}
|
||||
|
||||
export function handleInternalEvents(simtime: number, statechart: Statechart, {mode, environment, history, ...raised}: RT_Statechart & RaisedEvents): BigStepOutput {
|
||||
while (raised.internalEvents.length > 0) {
|
||||
const [internalEvent, ...rest] = raised.internalEvents;
|
||||
({mode, environment, ...raised} = handleEvent(simtime,
|
||||
{kind: "input", ...internalEvent}, // internal event becomes input event
|
||||
statechart, statechart.root, {mode, environment, history, internalEvents: rest, outputEvents: raised.outputEvents}));
|
||||
export function handleInternalEvents(simtime: number, statechart: Statechart, {internalEvents, ...rest}: RT_Statechart & RaisedEvents): BigStepOutput {
|
||||
while (internalEvents.length > 0) {
|
||||
const [nextEvent, ...remainingEvents] = internalEvents;
|
||||
({internalEvents, ...rest} = handleEvent(simtime,
|
||||
{kind: "input", ...nextEvent}, // internal event becomes input event
|
||||
statechart, statechart.root, {internalEvents: remainingEvents, ...rest}));
|
||||
}
|
||||
return {mode, environment, history, outputEvents: raised.outputEvents};
|
||||
return rest;
|
||||
}
|
||||
|
||||
export function fireTransition(simtime: number, t: Transition, ts: Map<string, Transition[]>, label: TransitionLabel, arena: OrState, {mode, environment, history, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
|
|
@ -309,12 +311,14 @@ export function fireTransition(simtime: number, t: Transition, ts: Map<string, T
|
|||
|
||||
// assuming we've already exited the source state of the transition, now enter the target state
|
||||
// IF however, the target is a pseudo-state, DON'T enter it (pseudo-states are NOT states), instead fire the first pseudo-outgoing transition.
|
||||
export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: Map<string, Transition[]>, label: TransitionLabel, arena: OrState, {mode, environment, history, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: Map<string, Transition[]>, label: TransitionLabel, arena: OrState, {mode, environment, history, firedTransitions, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
// exec transition actions
|
||||
for (const action of label.actions) {
|
||||
({environment, history, ...raised} = execAction(action, {environment, history, ...raised}));
|
||||
({environment, history, firedTransitions, ...raised} = execAction(action, {environment, history, firedTransitions, ...raised}));
|
||||
}
|
||||
|
||||
firedTransitions = [...firedTransitions, t.uid];
|
||||
|
||||
if (t.tgt.kind === "pseudo") {
|
||||
const outgoing = ts.get(t.tgt.uid) || [];
|
||||
for (const nextT of outgoing) {
|
||||
|
|
@ -323,7 +327,7 @@ export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: M
|
|||
if (evalExpr(nextLabel.guard, environment)) {
|
||||
console.log('fire', transitionDescription(nextT));
|
||||
// found ourselves an enabled transition
|
||||
return fireSecondHalfOfTransition(simtime, nextT, ts, nextLabel, arena, {mode, environment, history, ...raised});
|
||||
return fireSecondHalfOfTransition(simtime, nextT, ts, nextLabel, arena, {mode, environment, history, firedTransitions, ...raised});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -346,11 +350,11 @@ export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: M
|
|||
|
||||
// enter tgt
|
||||
let enteredStates;
|
||||
({enteredStates, environment, history, ...raised} = enterStates(simtime, state, toEnter, {environment, history, ...raised}));
|
||||
({enteredStates, environment, history, ...raised} = enterStates(simtime, state, toEnter, {environment, history, firedTransitions, ...raised}));
|
||||
const enteredMode = mode.union(enteredStates);
|
||||
|
||||
// console.log({enteredMode});
|
||||
|
||||
return {mode: enteredMode, environment, history, ...raised};
|
||||
return {mode: enteredMode, environment, history, firedTransitions, ...raised};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ export type RT_Statechart = {
|
|||
|
||||
export type BigStepOutput = RT_Statechart & {
|
||||
outputEvents: RaisedEvent[],
|
||||
firedTransitions: string[],
|
||||
};
|
||||
|
||||
export type BigStep = {
|
||||
|
|
@ -117,6 +118,7 @@ export type RaisedEvent = {
|
|||
export type RaisedEvents = {
|
||||
internalEvents: RaisedEvent[];
|
||||
outputEvents: RaisedEvent[];
|
||||
firedTransitions: string[]; // list of UIDs
|
||||
};
|
||||
|
||||
// export type Timers = Map<string, number>; // transition uid -> timestamp
|
||||
|
|
@ -124,6 +126,8 @@ export type RaisedEvents = {
|
|||
export const initialRaised: RaisedEvents = {
|
||||
internalEvents: [],
|
||||
outputEvents: [],
|
||||
|
||||
firedTransitions: [],
|
||||
};
|
||||
|
||||
export type Timers = [number, TimerElapseEvent][];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue