|
@@ -11,6 +11,8 @@ import {OnionContext} from "../onion_context";
|
|
|
import {mockUuid} from "onion/test_helpers";
|
|
|
import {PrimitiveRegistry, PrimitiveDelta} from "onion/primitive_delta";
|
|
|
import {INodeState, IValueState} from "onion/graph_state";
|
|
|
+import {embed, Version, VersionRegistry} from "onion/version";
|
|
|
+import {GraphState} from "onion/graph_state";
|
|
|
|
|
|
export const demo_Live_description = <>
|
|
|
<Title order={4}>
|
|
@@ -27,45 +29,58 @@ const getStateName = s => (s.getOutgoingEdges().get("name") as IValueState).valu
|
|
|
export function getDemoLive() {
|
|
|
const primitiveRegistry = new PrimitiveRegistry();
|
|
|
const generateUUID = mockUuid();
|
|
|
- const model = newVersionedModel({readonly: true});
|
|
|
- const modelId = generateUUID();
|
|
|
+ const versionRegistry = new VersionRegistry();
|
|
|
+ const model = newVersionedModel({readonly: true}, versionRegistry);
|
|
|
+
|
|
|
+ // To efficiently create a delta in the context of the design model only
|
|
|
+ const designModelGraphState = new GraphState();
|
|
|
+
|
|
|
+ // Mapping from run-time model to design model version
|
|
|
+ const rt2d = new Map([[versionRegistry.initialVersion, versionRegistry.initialVersion]]);
|
|
|
|
|
|
|
|
|
+ const modelId = generateUUID();
|
|
|
+
|
|
|
return function DemoSem() {
|
|
|
|
|
|
const [modelState, setDesignModelState] = React.useState<VersionedModelState>(model.initialState);
|
|
|
|
|
|
+ const gotoMatchingDesignVersion = (rtVersion: Version) => {
|
|
|
+ const currentDesignVersion = rt2d.get(modelState.version)!;
|
|
|
+ const newDesignVersion = rt2d.get(rtVersion)!;
|
|
|
+ const path = currentDesignVersion.findPathTo(newDesignVersion)!;
|
|
|
+ for (const [linkType, delta] of path) {
|
|
|
+ if (linkType === 'p') {
|
|
|
+ designModelGraphState.unexec(delta);
|
|
|
+ }
|
|
|
+ else if (linkType === 'c') {
|
|
|
+ designModelGraphState.exec(delta);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
const modelReducer = model.getReducer(setDesignModelState);
|
|
|
|
|
|
// We start out with the 'createList' delta already having occurred:
|
|
|
React.useEffect(() => {
|
|
|
// idempotent:
|
|
|
const modelCreation = primitiveRegistry.newNodeCreation(modelId);
|
|
|
- modelReducer.createAndGotoNewVersion([
|
|
|
+ const newVersion = modelReducer.createAndGotoNewVersion([
|
|
|
modelCreation,
|
|
|
primitiveRegistry.newEdgeCreation(modelCreation, "type", "DesignModel"),
|
|
|
], "createDesignModel", model.initialState.version);
|
|
|
+ rt2d.set(newVersion, newVersion);
|
|
|
+ gotoMatchingDesignVersion(newVersion);
|
|
|
}, []);
|
|
|
|
|
|
-
|
|
|
- const [dotGraph, setDotGraph] = React.useState("digraph {}");
|
|
|
-
|
|
|
- const [initialState, setInitialState] = React.useState<string|null>(null);
|
|
|
- const [currentState, setCurrentState] = React.useState<string|null>(null);
|
|
|
-
|
|
|
- // Whenever the current version changes, we calculate a bunch of values that are needed in the UI and in its callbacks
|
|
|
- const [states, transitions, modelNode, initial,
|
|
|
- runtimeModelNode, current]: [[string,INodeState][], [string,string,string,INodeState][], INodeState|null, INodeState|null, INodeState|null, INodeState|null] = React.useMemo(() => {
|
|
|
+ const getDesignModelStuff = graphState => {
|
|
|
const states: Array<[string, INodeState]> = [];
|
|
|
const transitions: Array<[string, string, string, INodeState]> = [];
|
|
|
let modelNode: INodeState|null = null;
|
|
|
- let runtimeModelNode: INodeState|null = null;
|
|
|
+ let initial: INodeState|undefined;
|
|
|
let initialStateName : string|null = null;
|
|
|
- let currentStateName : string|null = null;
|
|
|
- let initial;
|
|
|
- let current;
|
|
|
|
|
|
- for (const nodeState of model.graphState.nodes.values()) {
|
|
|
+ for (const nodeState of graphState.nodes.values()) {
|
|
|
if (nodeState.isDeleted) {
|
|
|
continue;
|
|
|
}
|
|
@@ -86,40 +101,92 @@ export function getDemoLive() {
|
|
|
modelNode = nodeState;
|
|
|
initial = nodeState.getOutgoingEdges().get("initial") as INodeState;
|
|
|
initialStateName = initial ? getStateName(initial) : null;
|
|
|
- setInitialState(initialStateName);
|
|
|
},
|
|
|
+ }[nodeType])?.();
|
|
|
+ }
|
|
|
+
|
|
|
+ const result: {
|
|
|
+ states: [string,INodeState][],
|
|
|
+ transitions: [string,string,string,INodeState][],
|
|
|
+ modelNode: INodeState|null,
|
|
|
+ initial: INodeState|undefined,
|
|
|
+ initialStateName: string|null,
|
|
|
+ } = {
|
|
|
+ states,
|
|
|
+ transitions,
|
|
|
+ modelNode,
|
|
|
+ initial,
|
|
|
+ initialStateName,
|
|
|
+ };
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ const getRuntimeModelStuff = graphState => {
|
|
|
+ const designModelStuff = getDesignModelStuff(graphState);
|
|
|
+ let runtimeModelNode: INodeState|null = null;
|
|
|
+ let currentStateName : string|null = null;
|
|
|
+ let current;
|
|
|
+
|
|
|
+ for (const nodeState of graphState.nodes.values()) {
|
|
|
+ if (nodeState.isDeleted) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const nodeType = (nodeState.getOutgoingEdges().get("type") as IValueState)?.value as string;
|
|
|
+ ({
|
|
|
RuntimeModel: () => {
|
|
|
runtimeModelNode = nodeState;
|
|
|
current = nodeState.getOutgoingEdges().get("current") as INodeState;
|
|
|
currentStateName = current ? getStateName(current) : null;
|
|
|
- setCurrentState(currentStateName);
|
|
|
- }
|
|
|
- }[nodeType])();
|
|
|
+ },
|
|
|
+ }[nodeType])?.();
|
|
|
}
|
|
|
|
|
|
- setDotGraph(`
|
|
|
+ const dotGraph = `
|
|
|
digraph {
|
|
|
bgcolor="transparent";
|
|
|
rankdir="LR";
|
|
|
- ${states.map(([name])=>name
|
|
|
- + (name === initialStateName ? `[color=blue, style=filled, fontcolor=white]`:`[fillcolor=white, style=filled]`)
|
|
|
+ ${designModelStuff.states.map(([name])=>name
|
|
|
+ + (name === designModelStuff.initialStateName ? `[color=blue, style=filled, fontcolor=white]`:`[fillcolor=white, style=filled]`)
|
|
|
+ (name === currentStateName ? `[shape=doublecircle]`:`[shape=circle]`)
|
|
|
).join(' ')}
|
|
|
- ${transitions.map(([src,tgt,label])=> src + ' -> ' + tgt + ' [label='+label+']').join('\n')}
|
|
|
- }`);
|
|
|
+ ${designModelStuff.transitions.map(([src,tgt,label])=> src + ' -> ' + tgt + ' [label='+label+']').join('\n')}
|
|
|
+ }`;
|
|
|
+
|
|
|
+ const result: {
|
|
|
+ runtimeModelNode: INodeState|null,
|
|
|
+ current: INodeState|null,
|
|
|
+ currentStateName: string|null,
|
|
|
+ dotGraph: string
|
|
|
+ } = {
|
|
|
+ runtimeModelNode,
|
|
|
+ current,
|
|
|
+ currentStateName,
|
|
|
+ dotGraph};
|
|
|
+ return Object.assign(result, designModelStuff);
|
|
|
+ };
|
|
|
+
|
|
|
+ // Whenever the current version changes, we calculate a bunch of values that are needed in the UI and in its callbacks
|
|
|
+ const runtimeStuff = React.useMemo(() => getRuntimeModelStuff(model.graphState),
|
|
|
+ [modelState.version]); // memoize the values for each version
|
|
|
+
|
|
|
+ const designStuff = React.useMemo(() => getDesignModelStuff(designModelGraphState),
|
|
|
+ [rt2d.get(modelState.version)]); // memoize the values for each version
|
|
|
|
|
|
- return [states, transitions, modelNode, initial,
|
|
|
- runtimeModelNode, current];
|
|
|
- }, [modelState.version]); // memoize the values for each version
|
|
|
|
|
|
const modelComponents = model.getReactComponents(modelState, setDesignModelState, {
|
|
|
- onUserEdit: (deltas, description) => {
|
|
|
- const newVersion = modelReducer.createAndGotoNewVersion(deltas, description);
|
|
|
- },
|
|
|
- onUndoClicked: modelReducer.undo,
|
|
|
- onRedoClicked: modelReducer.redo,
|
|
|
- onVersionClicked: modelReducer.gotoVersion,
|
|
|
- onMerge: modelReducer.appendVersions,
|
|
|
+ onUndoClicked: (parentVersion, deltaToUndo) => {
|
|
|
+ gotoMatchingDesignVersion(parentVersion);
|
|
|
+ modelReducer.undo(parentVersion, deltaToUndo);
|
|
|
+ },
|
|
|
+ onRedoClicked: (childVersion, deltaToRedo) => {
|
|
|
+ gotoMatchingDesignVersion(childVersion);
|
|
|
+ modelReducer.redo(childVersion, deltaToRedo);
|
|
|
+ },
|
|
|
+ onVersionClicked: (version: Version) => {
|
|
|
+ gotoMatchingDesignVersion(version);
|
|
|
+ modelReducer.gotoVersion(version);
|
|
|
+ },
|
|
|
+ onMerge: modelReducer.appendVersions,
|
|
|
});
|
|
|
|
|
|
const [addStateName, setAddStateName] = React.useState<string>("A");
|
|
@@ -127,122 +194,158 @@ export function getDemoLive() {
|
|
|
const [addTransitionTgt, setAddTransitionTgt] = React.useState<string|null>(null);
|
|
|
const [addTransitionEvent, setAddTransitionEvent] = React.useState<string>("e");
|
|
|
|
|
|
- function addState() {
|
|
|
- if (modelNode !== null) {
|
|
|
- model.graphState.pushState();
|
|
|
+ const execTransaction = (graphState, modelNode, callback) => {
|
|
|
+ graphState.pushState();
|
|
|
+ const {compositeLabel} = callback(graphState, modelNode);
|
|
|
+ const deltas = graphState.popState();
|
|
|
+ return {compositeLabel, deltas};
|
|
|
+ }
|
|
|
|
|
|
- const nodeId = generateUUID();
|
|
|
- const nodeCreation = primitiveRegistry.newNodeCreation(nodeId);
|
|
|
- model.graphState.exec(nodeCreation);
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "type", "State"));
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "name", addStateName));
|
|
|
+ const editDesignModel = callback => {
|
|
|
+ if (runtimeStuff.modelNode !== null) {
|
|
|
+ const currentDesignVersion = rt2d.get(modelState.version)!;
|
|
|
|
|
|
- modelNode.getDeltasForSetEdge(primitiveRegistry, "contains-"+JSON.stringify(nodeId.value), nodeCreation).forEach(d => model.graphState.exec(d));
|
|
|
+ // perform edit on run-time model:
|
|
|
+ const {compositeLabel, deltas} = execTransaction(model.graphState, runtimeStuff.modelNode, callback);
|
|
|
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "addState:"+addStateName);
|
|
|
+ // see if the edit can be applied also on the design model:
|
|
|
+ let newDesignVersion;
|
|
|
+ try {
|
|
|
+ newDesignVersion = modelReducer.addDeltasAndVersion(deltas, "design:"+compositeLabel, currentDesignVersion.hash)!; // may throw
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ // alert("could not apply this exact same delta on the design model - probably there's a missing dependency:\n" + e.toString() + "\nTherefore, I will attempt to create a new delta with the same /intent/ on the design model.");
|
|
|
|
|
|
- // Auto-increment state names
|
|
|
- if (addStateName.match(/[A-Z]/i)) {
|
|
|
- setAddStateName(String.fromCharCode(addStateName.charCodeAt(0) + 1));
|
|
|
+ const {compositeLabel, deltas} = execTransaction(designModelGraphState, designStuff.modelNode, callback);
|
|
|
+ newDesignVersion = modelReducer.addDeltasAndVersion(deltas, "design':"+compositeLabel, currentDesignVersion.hash)!; // won't throw this time
|
|
|
}
|
|
|
+
|
|
|
+ const newRtVersion = modelReducer.createAndGotoNewVersion(deltas, "design:"+compositeLabel);
|
|
|
+ rt2d.set(newRtVersion, newDesignVersion);
|
|
|
+ rt2d.set(newDesignVersion, newDesignVersion);
|
|
|
+ gotoMatchingDesignVersion(newRtVersion);
|
|
|
}
|
|
|
}
|
|
|
+ const editRuntimeModel = callback => {
|
|
|
+ const currentDesignVersion = rt2d.get(modelState.version)!;
|
|
|
|
|
|
- const getState = name => {
|
|
|
- const s = states.find(([n]) => n === name);
|
|
|
- if (s !== undefined) return s[1].creation;
|
|
|
+ // perform edit on run-time model:
|
|
|
+ const {compositeLabel, deltas} = execTransaction(model.graphState, runtimeStuff.modelNode, callback);
|
|
|
+ const newRtVersion = modelReducer.createAndGotoNewVersion(deltas, "runtime:"+compositeLabel);
|
|
|
+
|
|
|
+ // the run-time change did not change the design model:
|
|
|
+ rt2d.set(newRtVersion, currentDesignVersion);
|
|
|
}
|
|
|
|
|
|
- function addTransition() {
|
|
|
- if (modelNode !== null) {
|
|
|
- model.graphState.pushState();
|
|
|
+ const findState = (graphState: GraphState, stateName: string | null) => {
|
|
|
+ for (const nodeState of graphState.nodes.values()) {
|
|
|
+ if (nodeState.isDeleted) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const nodeType = (nodeState.getOutgoingEdges().get("type") as IValueState)?.value as string;
|
|
|
+ if (nodeType !== "State") {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const name = (nodeState.getOutgoingEdges().get("name") as IValueState)?.value as string;
|
|
|
+ if (name === stateName) {
|
|
|
+ return nodeState;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ function addState() {
|
|
|
+ editDesignModel((graphState, modelNode) => {
|
|
|
const nodeId = generateUUID();
|
|
|
const nodeCreation = primitiveRegistry.newNodeCreation(nodeId);
|
|
|
- model.graphState.exec(nodeCreation);
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "type", "Transition"));
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "event", addTransitionEvent));
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "src", getState(addTransitionSrc)!));
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "tgt", getState(addTransitionTgt)!));
|
|
|
+ graphState.exec(nodeCreation);
|
|
|
+ graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "type", "State"));
|
|
|
+ graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "name", addStateName));
|
|
|
|
|
|
- modelNode.getDeltasForSetEdge(primitiveRegistry, "contains-"+JSON.stringify(nodeId.value), nodeCreation).forEach(d => model.graphState.exec(d));
|
|
|
+ modelNode.getDeltasForSetEdge(primitiveRegistry, "contains-"+JSON.stringify(nodeId.value), nodeCreation).forEach(d => graphState.exec(d));
|
|
|
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "addTransition:"+addTransitionSrc+"--"+addTransitionEvent+"->"+addTransitionTgt);
|
|
|
- }
|
|
|
+ // Bonus feature: Auto-increment state names
|
|
|
+ if (addStateName.match(/[A-Z]/i)) {
|
|
|
+ setAddStateName(String.fromCharCode(addStateName.charCodeAt(0) + 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ return {compositeLabel: "addState:"+addStateName};
|
|
|
+ });
|
|
|
}
|
|
|
+ function addTransition() {
|
|
|
+ editDesignModel((graphState, modelNode) => {
|
|
|
+ const nodeId = generateUUID();
|
|
|
+ const nodeCreation = primitiveRegistry.newNodeCreation(nodeId);
|
|
|
+ graphState.exec(nodeCreation);
|
|
|
+ graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "type", "Transition"));
|
|
|
+ graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "event", addTransitionEvent));
|
|
|
+ graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "src", findState(graphState, addTransitionSrc!)!.creation));
|
|
|
+ graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "tgt", findState(graphState, addTransitionTgt!)!.creation));
|
|
|
|
|
|
- function onDeleteState(name: string, nodeState: INodeState) {
|
|
|
- if (modelNode !== null) {
|
|
|
- model.graphState.pushState();
|
|
|
+ modelNode.getDeltasForSetEdge(primitiveRegistry, "contains-"+JSON.stringify(nodeId.value), nodeCreation).forEach(d => graphState.exec(d));
|
|
|
+
|
|
|
+ return {compositeLabel: "addTransition:"+addTransitionSrc+"--"+addTransitionEvent+"->"+addTransitionTgt};
|
|
|
+ })
|
|
|
+ }
|
|
|
+ function onDeleteState(name: string) {
|
|
|
+ editDesignModel((graphState) => {
|
|
|
+ const nodeState = findState(graphState, name)!;
|
|
|
while (true) {
|
|
|
// Delete all incoming and outgoing transitions:
|
|
|
const found = nodeState.getIncomingEdges().find(([label]) => label === "src" || label === "tgt");
|
|
|
if (found === undefined) break;
|
|
|
const [_, from] = found;
|
|
|
const deltas = from.getDeltasForDelete(primitiveRegistry);
|
|
|
- console.log({deltas});
|
|
|
- deltas.forEach(d => model.graphState.exec(d));
|
|
|
+ deltas.forEach(d => graphState.exec(d));
|
|
|
}
|
|
|
- nodeState.getDeltasForDelete(primitiveRegistry).forEach(d => model.graphState.exec(d));
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- console.log({deltas});
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "deleteState:"+name);
|
|
|
- }
|
|
|
+ nodeState.getDeltasForDelete(primitiveRegistry).forEach(d => {
|
|
|
+ console.log(d);
|
|
|
+ graphState.exec(d);
|
|
|
+ });
|
|
|
+ return {compositeLabel: "deleteState:"+name};
|
|
|
+ });
|
|
|
}
|
|
|
-
|
|
|
function onInitialStateChange(value) {
|
|
|
- if (modelNode !== null) {
|
|
|
- model.graphState.pushState();
|
|
|
- modelNode.getDeltasForSetEdge(primitiveRegistry, "initial", getState(value) || null).forEach(d => model.graphState.exec(d));
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "setInitial:"+value);
|
|
|
- setInitialState(value);
|
|
|
- }
|
|
|
+ editDesignModel((graphState, modelNode) => {
|
|
|
+ modelNode.getDeltasForSetEdge(primitiveRegistry, "initial", findState(graphState, value)?.creation || null).forEach(d => graphState.exec(d));
|
|
|
+ return {compositeLabel: "setInitial:"+value};
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
function onInitialize() {
|
|
|
- if (modelNode !== null && initial != null) {
|
|
|
- model.graphState.pushState();
|
|
|
- const runtimeId = generateUUID();
|
|
|
- const nodeCreation = primitiveRegistry.newNodeCreation(runtimeId);
|
|
|
- model.graphState.exec(nodeCreation);
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "type", "RuntimeModel"));
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "design", modelNode.creation));
|
|
|
- model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "current", initial.creation));
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "initialize");
|
|
|
+ if (runtimeStuff.modelNode !== null && runtimeStuff.initial !== null) {
|
|
|
+ editRuntimeModel((graphState) => {
|
|
|
+ const runtimeId = generateUUID();
|
|
|
+ const nodeCreation = primitiveRegistry.newNodeCreation(runtimeId);
|
|
|
+ model.graphState.exec(nodeCreation);
|
|
|
+ model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "type", "RuntimeModel"));
|
|
|
+ model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "design", runtimeStuff.modelNode!.creation));
|
|
|
+ model.graphState.exec(primitiveRegistry.newEdgeCreation(nodeCreation, "current", runtimeStuff.initial!.creation));
|
|
|
+ return {compositeLabel: "initialize"};
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
function onAbort() {
|
|
|
- if (runtimeModelNode !== null) {
|
|
|
- model.graphState.pushState();
|
|
|
- runtimeModelNode.getDeltasForDelete(primitiveRegistry).forEach(d => model.graphState.exec(d));
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "abort");
|
|
|
- setCurrentState(null);
|
|
|
+ if (runtimeStuff.runtimeModelNode !== null) {
|
|
|
+ editRuntimeModel((graphState) => {
|
|
|
+ runtimeStuff.runtimeModelNode!.getDeltasForDelete(primitiveRegistry).forEach(d => graphState.exec(d));
|
|
|
+ return {compositeLabel: "abort"};
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
function onCurrentStateChange(value) {
|
|
|
- if (runtimeModelNode !== null) {
|
|
|
- model.graphState.pushState();
|
|
|
- runtimeModelNode.getDeltasForSetEdge(primitiveRegistry, "current", getState(value) || null).forEach(d => model.graphState.exec(d));
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "setCurrent:"+value);
|
|
|
- setCurrentState(value);
|
|
|
+ if (runtimeStuff.runtimeModelNode !== null) {
|
|
|
+ editRuntimeModel((graphState) => {
|
|
|
+ runtimeStuff.runtimeModelNode!.getDeltasForSetEdge(primitiveRegistry, "current", findState(graphState, value)?.creation || null).forEach(d => graphState.exec(d));
|
|
|
+ return {compositeLabel: "setCurrent:"+value}
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
function onExecuteStep(srcName, tgtName, label, transition: INodeState) {
|
|
|
- if (runtimeModelNode !== null) {
|
|
|
- model.graphState.pushState();
|
|
|
- runtimeModelNode.getDeltasForSetEdge(primitiveRegistry, "current", getState(tgtName) || null).forEach(d => model.graphState.exec(d));
|
|
|
- const deltas = model.graphState.popState();
|
|
|
- modelReducer.createAndGotoNewVersion(deltas, "executeStep:"+transitionKey([srcName, tgtName, label]));
|
|
|
- setCurrentState(tgtName);
|
|
|
+ if (runtimeStuff.runtimeModelNode !== null) {
|
|
|
+ editRuntimeModel((graphState) => {
|
|
|
+ runtimeStuff.runtimeModelNode!.getDeltasForSetEdge(primitiveRegistry, "current", findState(graphState, tgtName)?.creation || null).forEach(d => graphState.exec(d));
|
|
|
+ return {compositeLabel: "executeStep:"+transitionKey([srcName, tgtName, label])};
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -258,7 +361,7 @@ export function getDemoLive() {
|
|
|
</Group>
|
|
|
<Divider label="(Projectional) Model Editor" labelPosition="centered" />
|
|
|
{
|
|
|
- modelNode === null ?
|
|
|
+ runtimeStuff.modelNode === null ?
|
|
|
<Alert icon={<Icons.IconAlertCircle/>} title="No design model!" color="blue">
|
|
|
Did you undo its creation?
|
|
|
</Alert>
|
|
@@ -268,40 +371,40 @@ export function getDemoLive() {
|
|
|
<Paper shadow="xs" p="xs" withBorder>
|
|
|
<Group grow>
|
|
|
<TextInput value={addStateName} onChange={e => setAddStateName(e.currentTarget.value)} label="State Name" withAsterisk/>
|
|
|
- <Button onClick={addState} disabled={addStateName===""||modelNode===null||states.some(([name]) => name ===addStateName)} leftIcon={<Icons.IconPlus/>} color="green">State</Button>
|
|
|
+ <Button onClick={addState} disabled={addStateName===""||runtimeStuff.modelNode===null||runtimeStuff.states.some(([name]) => name ===addStateName)} leftIcon={<Icons.IconPlus/>} color="green">State</Button>
|
|
|
</Group>
|
|
|
</Paper>
|
|
|
- <Select disabled={modelNode===null} searchable clearable label="Initial State" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={initialState} onChange={onInitialStateChange}/>
|
|
|
+ <Select disabled={runtimeStuff.modelNode===null} searchable clearable label="Initial State" data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))} value={runtimeStuff.initialStateName} onChange={onInitialStateChange}/>
|
|
|
</Group>
|
|
|
<div style={{minHeight: 26}}>
|
|
|
<Group>{
|
|
|
- states.map(([stateName, stateNodeState]) => {
|
|
|
- return <Button compact color="red" key={stateName} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(stateName, stateNodeState)}>State {stateName}</Button>
|
|
|
+ runtimeStuff.states.map(([stateName]) => {
|
|
|
+ return <Button compact color="red" key={stateName} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(stateName)}>State {stateName}</Button>
|
|
|
})
|
|
|
}</Group>
|
|
|
</div>
|
|
|
<Paper shadow="xs" p="xs" withBorder>
|
|
|
<Group grow>
|
|
|
- <Select searchable withAsterisk clearable label="Source" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionSrc} onChange={setAddTransitionSrc}/>
|
|
|
- <Select searchable withAsterisk clearable label="Target" data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionTgt} onChange={setAddTransitionTgt}/>
|
|
|
+ <Select searchable withAsterisk clearable label="Source" data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionSrc} onChange={setAddTransitionSrc}/>
|
|
|
+ <Select searchable withAsterisk clearable label="Target" data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))} value={addTransitionTgt} onChange={setAddTransitionTgt}/>
|
|
|
<TextInput value={addTransitionEvent} onChange={e => setAddTransitionEvent(e.currentTarget.value)} label="Event" />
|
|
|
<Button disabled={addTransitionSrc === null || addTransitionTgt === null} onClick={addTransition} leftIcon={<Icons.IconPlus/>} color="green">Transition</Button>
|
|
|
</Group>
|
|
|
</Paper>
|
|
|
<div style={{minHeight: 26}}>
|
|
|
<Group>{
|
|
|
- transitions.map(([srcName, tgtName, label, tNodeState]) => {
|
|
|
+ runtimeStuff.transitions.map(([srcName, tgtName, label, tNodeState]) => {
|
|
|
const key = srcName+'--('+label+')-->'+tgtName;
|
|
|
- return <Button compact color="red" key={key} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(key, tNodeState)}>Transition {key}</Button>
|
|
|
+ return <Button compact color="red" key={key} leftIcon={<Icons.IconX/>} onClick={() => onDeleteState(key)}>Transition {key}</Button>
|
|
|
})
|
|
|
}</Group>
|
|
|
</div>
|
|
|
|
|
|
<Divider label="Execution" labelPosition="centered" />
|
|
|
<Group grow>
|
|
|
- <Button disabled={initialState === null || runtimeModelNode !== null} onClick={onInitialize} leftIcon={<Icons.IconPlayerPlay/>}>Init</Button>
|
|
|
- <Button color="red" disabled={runtimeModelNode === null} onClick={onAbort} leftIcon={<Icons.IconPlayerStop/>}>Abort</Button>
|
|
|
- <Select disabled={currentState === null || runtimeModelNode === null} data={transitions.filter(([srcName]) => srcName === currentState).map(t => {
|
|
|
+ <Button disabled={runtimeStuff.initialStateName === null || runtimeStuff.runtimeModelNode !== null} onClick={onInitialize} leftIcon={<Icons.IconPlayerPlay/>}>Init</Button>
|
|
|
+ <Button color="red" disabled={runtimeStuff.runtimeModelNode === null} onClick={onAbort} leftIcon={<Icons.IconPlayerStop/>}>Abort</Button>
|
|
|
+ <Select disabled={runtimeStuff.currentStateName === null || runtimeStuff.runtimeModelNode === null} data={runtimeStuff.transitions.filter(([srcName]) => srcName === runtimeStuff.currentStateName).map(t => {
|
|
|
// @ts-ignore:
|
|
|
const key = transitionKey(t);
|
|
|
return {
|
|
@@ -309,14 +412,14 @@ export function getDemoLive() {
|
|
|
label: key,
|
|
|
}
|
|
|
})} label="Execute Step" onChange={key => {
|
|
|
- const t = transitions.find(t =>
|
|
|
+ const t = runtimeStuff.transitions.find(t =>
|
|
|
// @ts-ignore:
|
|
|
transitionKey(t) == key);
|
|
|
onExecuteStep(...t!);
|
|
|
}}/>
|
|
|
- <Select disabled={runtimeModelNode === null} searchable clearable label={<Group>Current State <InfoHoverCard>
|
|
|
+ <Select disabled={runtimeStuff.runtimeModelNode === null} searchable clearable label={<Group>Current State <InfoHoverCard>
|
|
|
<Text>Current state updates when executing an execution step, but can also be overridden at any point in time ("god event").</Text>
|
|
|
- </InfoHoverCard></Group>} data={states.map(([stateName]) => ({value:stateName, label:stateName}))} value={currentState} onChange={onCurrentStateChange}/>
|
|
|
+ </InfoHoverCard></Group>} data={runtimeStuff.states.map(([stateName]) => ({value:stateName, label:stateName}))} value={runtimeStuff.currentStateName} onChange={onCurrentStateChange}/>
|
|
|
</Group>
|
|
|
|
|
|
<InfoHoverCardOverlay contents={<>
|
|
@@ -324,8 +427,8 @@ export function getDemoLive() {
|
|
|
<Text>Powered by GraphViz and WebAssembly.</Text>
|
|
|
</>}>
|
|
|
<div style={{backgroundColor:"#eee"}}>{
|
|
|
- states.length > 0 ?
|
|
|
- <Graphviz dot={dotGraph} options={{fit:false, width:null, height:null}} />
|
|
|
+ runtimeStuff.states.length > 0 ?
|
|
|
+ <Graphviz dot={runtimeStuff.dotGraph} options={{fit:false, width:null, height:null}} />
|
|
|
: <Center position="center" style={{padding:20}}>
|
|
|
<Text>FSA will appear here.</Text>
|
|
|
</Center>
|
|
@@ -335,6 +438,8 @@ export function getDemoLive() {
|
|
|
<Stack>
|
|
|
{modelComponents.makeTabs("state", ["state", "merge", "deltaL1", "deltaL0"])}
|
|
|
{modelComponents.makeTabs("deltaL1", ["state", "merge", "deltaL1", "deltaL0"])}
|
|
|
+ Run-time model version: {modelState.version.hash.toString('hex').substring(0,8)}<br/>
|
|
|
+ DesignModel version: {rt2d.get(modelState.version)!.hash.toString('hex').substring(0,8)}
|
|
|
</Stack>
|
|
|
</SimpleGrid>
|
|
|
</OnionContext.Provider>
|