diff --git a/oomph/LinguaFranca.setup b/oomph/LinguaFranca.setup
index 30a5843e13..3e5685482b 100644
--- a/oomph/LinguaFranca.setup
+++ b/oomph/LinguaFranca.setup
@@ -355,7 +355,7 @@
+ url="http://download.eclipse.org/elk/updates/releases/0.8.1/"/>
T associateWith(T derived, Object source) {
return delegate.associateWith(derived, source);
diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java
index 3976443181..a47d8f5790 100644
--- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java
+++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java
@@ -52,6 +52,7 @@
import org.eclipse.elk.core.options.CoreOptions;
import org.eclipse.elk.core.options.Direction;
import org.eclipse.elk.core.options.PortConstraints;
+import org.eclipse.elk.core.options.PortLabelPlacement;
import org.eclipse.elk.core.options.PortSide;
import org.eclipse.elk.core.options.SizeConstraint;
import org.eclipse.elk.graph.properties.Property;
@@ -77,6 +78,7 @@
import org.lflang.diagram.synthesis.styles.ReactorFigureComponents;
import org.lflang.diagram.synthesis.util.CycleVisualization;
import org.lflang.diagram.synthesis.util.InterfaceDependenciesVisualization;
+import org.lflang.diagram.synthesis.util.LayoutPostProcessing;
import org.lflang.diagram.synthesis.util.ModeDiagrams;
import org.lflang.diagram.synthesis.util.NamedInstanceUtil;
import org.lflang.diagram.synthesis.util.ReactorIcons;
@@ -154,6 +156,7 @@ public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis {
@Inject @Extension private FilterCycleAction _filterCycleAction;
@Inject @Extension private ReactorIcons _reactorIcons;
@Inject @Extension private ModeDiagrams _modeDiagrams;
+ @Inject @Extension private LayoutPostProcessing _layoutPostProcessing;
// -------------------------------------------------------------------------
@@ -235,7 +238,9 @@ public List getDisplayedSynthesisOptions() {
SHOW_INSTANCE_NAMES,
REACTOR_PARAMETER_MODE,
SHOW_STATE_VARIABLES,
- REACTOR_BODY_TABLE_COLS
+ REACTOR_BODY_TABLE_COLS,
+ LayoutPostProcessing.LAYOUT_CATEGORY,
+ LayoutPostProcessing.MODEL_ORDER
);
}
@@ -292,6 +297,7 @@ public KNode transform(final Model model) {
_kRenderingExtensions.addInvisibleContainerRendering(child);
setLayoutOption(child, CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID);
setLayoutOption(child, CoreOptions.PADDING, new ElkPadding(0));
+ // Legacy ordering option.
setLayoutOption(child, CoreOptions.PRIORITY, reactorNodes.size() - index); // Order!
rootNode.getChildren().add(child);
index++;
@@ -402,6 +408,7 @@ private Collection createReactorNode(
setLayoutOption(node, CoreOptions.PADDING, new ElkPadding(-1, 6, 6, 6));
setLayoutOption(node, LayeredOptions.SPACING_COMPONENT_COMPONENT, LayeredOptions.SPACING_COMPONENT_COMPONENT.getDefault() * 0.5f);
}
+ _layoutPostProcessing.configureMainReactor(node);
} else {
ReactorInstance instance = reactorInstance;
@@ -521,7 +528,11 @@ private Collection createReactorNode(
// Create ports
Map inputPorts = new HashMap<>();
Map outputPorts = new HashMap<>();
- for (PortInstance input : ListExtensions.reverseView(instance.inputs)) {
+ List inputs = instance.inputs;
+ if (LayoutPostProcessing.LEGACY.equals((String) getObjectValue(LayoutPostProcessing.MODEL_ORDER))) {
+ inputs = ListExtensions.reverseView(instance.inputs);
+ }
+ for (PortInstance input : inputs) {
inputPorts.put(input, addIOPort(node, input, true, input.isMultiport(), reactorInstance.isBank()));
}
for (PortInstance output : instance.outputs) {
@@ -571,6 +582,7 @@ private Collection createReactorNode(
Iterables.addAll(nodes, createUserComments(reactor, node));
}
configureReactorNodeLayout(node);
+ _layoutPostProcessing.configureReactor(node);
}
// Find and annotate cycles
@@ -594,15 +606,12 @@ private KNode configureReactorNodeLayout(KNode node) {
setLayoutOption(node, CoreOptions.NODE_SIZE_CONSTRAINTS, SizeConstraint.minimumSizeWithPorts());
// Allows to freely shuffle ports on each side
setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE);
+ // Adjust port label spacing to be closer to edge but not overlap with port figure
+ setLayoutOption(node, CoreOptions.PORT_LABELS_PLACEMENT, EnumSet.of(PortLabelPlacement.ALWAYS_OTHER_SAME_SIDE, PortLabelPlacement.OUTSIDE, PortLabelPlacement.NEXT_TO_PORT_IF_POSSIBLE));
+ setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_HORIZONTAL, 2.0);
+ setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_VERTICAL, -3.0);
// Balanced placement with straight long edges.
setLayoutOption(node, LayeredOptions.NODE_PLACEMENT_STRATEGY, NodePlacementStrategy.NETWORK_SIMPLEX);
- // Otherwise nodes are not sorted if they are not connected
- setLayoutOption(node, CoreOptions.SEPARATE_CONNECTED_COMPONENTS, false);
- // Needed to enforce node positions.
- setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_SEMI_INTERACTIVE, true);
- // Costs a little more time but layout is quick, therefore, we can do that.
- setLayoutOption(node, LayeredOptions.THOROUGHNESS, 100);
- setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.TWO_SIDED);
if (!getBooleanValue(SHOW_HYPERLINKS)) {
setLayoutOption(node, CoreOptions.PADDING, new ElkPadding(2, 6, 6, 6));
setLayoutOption(node, LayeredOptions.SPACING_NODE_NODE, LayeredOptions.SPACING_NODE_NODE.getDefault() * 0.75f);
@@ -739,11 +748,11 @@ private Collection transformReactorNetwork(
Multimap actionSources = HashMultimap.create();
Map timerNodes = new HashMap<>();
KNode startupNode = _kNodeExtensions.createNode();
- boolean startupUsed = false;
+ TriggerInstance> startup = null;
KNode shutdownNode = _kNodeExtensions.createNode();
- boolean shutdownUsed = false;
+ TriggerInstance> shutdown = null;
KNode resetNode = _kNodeExtensions.createNode();
- boolean resetUsed = false;
+ TriggerInstance> reset = null;
// Transform instances
int index = 0;
@@ -768,6 +777,7 @@ private Collection transformReactorNetwork(
Iterables.addAll(nodes, createUserComments(timer.getDefinition(), node));
timerNodes.put(timer, node);
_linguaFrancaShapeExtensions.addTimerFigure(node, timer);
+ _layoutPostProcessing.configureTimer(node);
}
// Create reactions
@@ -781,6 +791,7 @@ private Collection transformReactorNetwork(
reactionNodes.put(reaction, node);
setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE);
+ _layoutPostProcessing.configureReaction(node);
setLayoutOption(node, LayeredOptions.POSITION, new KVector(0, idx + 1)); // try order reactions vertically if in one layer (+1 to account for startup)
var figure = _linguaFrancaShapeExtensions.addReactionFigure(node, reaction);
@@ -812,17 +823,17 @@ private Collection transformReactorNetwork(
connect(createDependencyEdge(((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition),
startupNode,
port);
- startupUsed = true;
+ startup = trigger;
} else if (trigger.isShutdown()) {
connect(createDelayEdge(((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition),
shutdownNode,
port);
- shutdownUsed = true;
+ shutdown = trigger;
} else if (trigger.isReset()) {
connect(createDependencyEdge(((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition),
resetNode,
port);
- resetUsed = true;
+ reset = trigger;
} else if (trigger instanceof ActionInstance) {
actionDestinations.put(((ActionInstance) trigger), port);
} else if (trigger instanceof PortInstance) {
@@ -913,6 +924,7 @@ private Collection transformReactorNetwork(
nodes.add(node);
Iterables.addAll(nodes, createUserComments(action.getDefinition(), node));
setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE);
+ _layoutPostProcessing.configureAction(node);
Pair ports = _linguaFrancaShapeExtensions.addActionFigureAndPorts(
node,
action.isPhysical() ? "P" : "L");
@@ -1010,14 +1022,16 @@ private Collection transformReactorNetwork(
}
// Add startup/shutdown
- if (startupUsed) {
+ if (startup != null) {
_linguaFrancaShapeExtensions.addStartupFigure(startupNode);
_utilityExtensions.setID(startupNode, reactorInstance.uniqueID() + "_startup");
+ NamedInstanceUtil.linkInstance(startupNode, startup);
startupNode.setProperty(REACTION_SPECIAL_TRIGGER, true);
nodes.add(0, startupNode); // add at the start (ordered first)
// try to order with reactions vertically if in one layer
setLayoutOption(startupNode, LayeredOptions.POSITION, new KVector(0, 0));
setLayoutOption(startupNode, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST);
+ _layoutPostProcessing.configureAction(startupNode);
if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) {
KPort port = addInvisiblePort(startupNode);
@@ -1026,12 +1040,14 @@ private Collection transformReactorNetwork(
});
}
}
- if (shutdownUsed) {
+ if (shutdown != null) {
_linguaFrancaShapeExtensions.addShutdownFigure(shutdownNode);
_utilityExtensions.setID(shutdownNode, reactorInstance.uniqueID() + "_shutdown");
+ NamedInstanceUtil.linkInstance(shutdownNode, shutdown);
shutdownNode.setProperty(REACTION_SPECIAL_TRIGGER, true);
nodes.add(shutdownNode); // add at the end (ordered last)
// try to order with reactions vertically if in one layer
+ _layoutPostProcessing.configureShutDown(shutdownNode);
setLayoutOption(shutdownNode, LayeredOptions.POSITION, new KVector(0, reactorInstance.reactions.size() + 1));
if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { // connect all edges to one port
@@ -1041,11 +1057,12 @@ private Collection transformReactorNetwork(
});
}
}
- if (resetUsed) {
+ if (reset != null) {
_linguaFrancaShapeExtensions.addResetFigure(resetNode);
_utilityExtensions.setID(resetNode, reactorInstance.uniqueID() + "_reset");
+ NamedInstanceUtil.linkInstance(resetNode, reset);
resetNode.setProperty(REACTION_SPECIAL_TRIGGER, true);
- nodes.add(startupUsed ? 1 : 0, resetNode); // after startup
+ nodes.add(startup != null ? 1 : 0, resetNode); // after startup
// try to order with reactions vertically if in one layer
setLayoutOption(resetNode, LayeredOptions.POSITION, new KVector(0, 0.5));
setLayoutOption(resetNode, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST);
@@ -1090,7 +1107,8 @@ private Collection transformReactorNetwork(
prevNode = node;
}
}
-
+
+ _layoutPostProcessing.orderChildren(nodes);
_modeDiagrams.handleModes(nodes, reactorInstance);
return nodes;
diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/LayoutPostProcessing.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/LayoutPostProcessing.java
new file mode 100644
index 0000000000..e6d171550d
--- /dev/null
+++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/LayoutPostProcessing.java
@@ -0,0 +1,370 @@
+/*************
+* Copyright (c) 2022, Kiel University.
+*
+* Redistribution and use in source and binary forms, with or without modification,
+* are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright notice,
+* this list of conditions and the following disclaimer in the documentation
+* and/or other materials provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***************/
+package org.lflang.diagram.synthesis.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import org.eclipse.elk.alg.layered.components.ComponentOrderingStrategy;
+import org.eclipse.elk.alg.layered.options.CrossingMinimizationStrategy;
+import org.eclipse.elk.alg.layered.options.CycleBreakingStrategy;
+import org.eclipse.elk.alg.layered.options.GreedySwitchType;
+import org.eclipse.elk.alg.layered.options.LayeredOptions;
+import org.eclipse.elk.alg.layered.options.OrderingStrategy;
+import org.eclipse.elk.core.options.CoreOptions;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
+import org.lflang.diagram.synthesis.AbstractSynthesisExtensions;
+import org.lflang.diagram.synthesis.LinguaFrancaSynthesis;
+import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable;
+
+import de.cau.cs.kieler.klighd.SynthesisOption;
+import de.cau.cs.kieler.klighd.kgraph.KNode;
+import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared;
+import de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses;
+
+/**
+ * Set layout configuration options for the Lingua Franca diagram synthesis.
+ *
+ * @author{Sören Domrös }
+ */
+@ViewSynthesisShared
+public class LayoutPostProcessing extends AbstractSynthesisExtensions {
+
+ /** The synthesis option category for layout options. */
+ public static final SynthesisOption LAYOUT_CATEGORY =
+ SynthesisOption.createCategory("Layout", false).setCategory(LinguaFrancaSynthesis.APPEARANCE);
+
+ /** Synthesis option to control the order of nodes and edges by model order. */
+ public static final String MODEL_ORDER_OPTION = "Model Order";
+ /** Uses semi-automatic layout. */
+ public static final String LEGACY = "Legacy";
+ /** Only reactions are strictly ordered by their model order. */
+ public static final String STRICT_REACTION_ONLY = "Reactions Only";
+ /** Reactions and reactor are strictly ordered by their model order. */
+ public static final String STRICT = "Reactions and Reactors";
+ /** Reactions and reactors are ordered by their model order if no additional crossing are created. */
+ public static final String TIE_BREAKER = "Optimize Crossings";
+ /**
+ * No crossing minimization is done at all. This requires that actions and timers are sorted based on their model
+ * order.
+ */
+ public static final String FULL_CONTROL = "Full Control";
+
+
+ public static final SynthesisOption MODEL_ORDER =
+ SynthesisOption.createChoiceOption(
+ MODEL_ORDER_OPTION,
+ Arrays.asList(TIE_BREAKER, STRICT_REACTION_ONLY, STRICT, FULL_CONTROL),
+ STRICT_REACTION_ONLY).setCategory(LAYOUT_CATEGORY);
+
+ /**
+ * Comparator to sort KNodes based on the textual order of their linked instances.
+ *
+ * Startup, reset and shutdown actions are not in the model and are handled separately:
+ * Startup actions will always be first.
+ * Reset actions follow after the startup action.
+ * Shutdown is always sorted last. However, shutdown actions will not have a model order set and are, therefore,
+ * implicitly ordered by their connection.
+ */
+ public static final Comparator TEXTUAL_ORDER = new Comparator() {
+
+ @Override
+ public int compare(KNode node1, KNode node2) {
+ var pos1 = getTextPosition(node1);
+ var pos2 = getTextPosition(node2);
+ if (pos1 >= 0 && pos1 >= 0) {
+ return Integer.compare(pos1, pos2); // textual order
+ } else if (pos1 >= 0) {
+ return -1; // unassociated elements last
+ } else if (pos2 >= 0) {
+ return 1; // unassociated elements last
+ }
+ return Integer.compare(node1.hashCode(), node2.hashCode()); // any stable order between unassociated elements
+ }
+
+ private int getTextPosition(KNode node) {
+ var instance = NamedInstanceUtil.getLinkedInstance(node);
+ if (instance != null) {
+ var definition = instance.getDefinition();
+ if (definition instanceof BuiltinTriggerVariable) {
+ // special handling for built-in triggers
+ switch(((BuiltinTriggerVariable)definition).type) {
+ case STARTUP: return 0; // first
+ case RESET: return 1; // second
+ case SHUTDOWN: return Integer.MAX_VALUE; // last
+ }
+ } else if (definition instanceof EObject) {
+ var ast = NodeModelUtils.getNode((EObject) definition);
+ if (ast != null) {
+ return ast.getOffset();
+ }
+ }
+ }
+ return -1;
+ }
+ };
+
+ /**
+ * Configures layout options for main reactor.
+ *
+ * @param node The KNode of the main reactor.
+ */
+ public void configureMainReactor(KNode node) {
+ configureReactor(node);
+ }
+
+ /**
+ * Configures layout options for a reactor.
+ *
+ * @param node The KNode of a reactor.
+ */
+ public void configureReactor(KNode node) {
+ String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER);
+
+ switch (modelOrderStrategy) {
+ case LEGACY:
+ // Otherwise nodes are not sorted if they are not connected
+ DiagramSyntheses.setLayoutOption(node, CoreOptions.SEPARATE_CONNECTED_COMPONENTS, false);
+ // Needed to enforce node positions.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_SEMI_INTERACTIVE, true);
+ // Costs a little more time but layout is quick, therefore, we can do that.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.THOROUGHNESS, 100);
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.TWO_SIDED);
+ break;
+ case STRICT_REACTION_ONLY:
+ // Only set model order for reactions.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true);
+ // Do tie-breaking model order cycle breaking.
+ // Minimize number of backward edges but make decisions based on the model order if no greedy best alternative exists.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER);
+ // Before crossing minimization sort all nodes and edges/ports but also consider the node model order.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES);
+ // Separate connected components should be drawn separately.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true);
+ // Component order is enforced by looking at the minimum element with respect to model order of each component.
+ // Remember that the startUp action is always the first node.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER);
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true);
+
+ // Node order should not change during crossing minimization.
+ // Since only reactions will have a model order set in this approach the order of reactions in their respective
+ // separate connected components always respects the model order.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER, true);
+ // Disable greedy switch since this does otherwise change the node order after crossing minimization.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF);
+ break;
+ case STRICT:
+ // Do tie-breaking model order cycle breaking.
+ // Minimize number of backward edges but make decisions based on the model order if no greedy best alternative exists.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER);
+ // Before crossing minimization sort all nodes and edges/ports but also consider the node model order.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES);
+ // Separate connected components should be drawn separately.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true);
+ // Component order is enforced by looking at the minimum element with respect to model order of each component.
+ // Remember that the startUp action is always the first node.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER);
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true);
+
+ // Node order should not change during crossing minimization.
+ // Since only reactions and reactors will have a model order set in this approach the order of reactions and reactors in their respective
+ // separate connected components always respects the model order.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER, true);
+ // Disable greedy switch since this does otherwise change the node order after crossing minimization.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF);
+ break;
+ case TIE_BREAKER:
+ // Do tie-breaking model order cycle breaking.
+ // Minimize number of backward edges but make decisions based on the model order if no greedy best alternative exists.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER);
+ // Before crossing minimization sort all nodes and edges/ports but also consider the node model order.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES);
+ // Separate connected components should be drawn separately.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true);
+ // Component order is enforced by looking at the minimum element with respect to model order of each component.
+ // Remember that the startUp action is always the first node.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER);
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true);
+ // During crossing minimization 10 node order violations are regarded as important as 1 edge crossing.
+ // In reality this chooses the best node order from all tries.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_CROSSING_COUNTER_NODE_INFLUENCE, 0.1);
+ // Increase the number of tries with different starting configurations.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.THOROUGHNESS, 100);
+
+ break;
+ case FULL_CONTROL:
+ // Do strict model order cycle breaking. This may introduce unnecessary backward edges.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER);
+ // Before crossing minimization sort all nodes and edges/ports but also consider the node model order.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES);
+ // Separate connected components should be drawn separately.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true);
+ // Component order is enforced by looking at the minimum element with respect to model order of each component.
+ // Remember that the startUp action is always the first node.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER);
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true);
+ // Disable all kinds of crossing minimization entirely. Just take what is in the model and just do it.
+ // This requires that the list of nodes is not ordered by type, e.g. first all reactions, then all reactors, then all actions, ...
+ // but by their model order. In other approaches ordering actions between the reactions has no effect.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE);
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF);
+
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Configures layout options for an action.
+ *
+ * @param node The KNode of an action.
+ */
+ public void configureAction(KNode node) {
+ String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER);
+
+ switch (modelOrderStrategy) {
+ case STRICT_REACTION_ONLY:
+ case STRICT:
+ case TIE_BREAKER:
+ // Actions have no model order since their ordering in the model cannot be compared to the order of
+ // for example reactions since they are generally defined below in inputs/outputs and above the reactions.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true);
+ break;
+ case FULL_CONTROL:
+ // Give actions a model order since they should be controllable too.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, false);
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Configures layout options for a timer.
+ *
+ * @param node The KNode of a timer.
+ */
+ public void configureTimer(KNode node) {
+ String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER);
+
+ switch (modelOrderStrategy) {
+ case STRICT_REACTION_ONLY:
+ case STRICT:
+ case TIE_BREAKER:
+ // Timers have no model order since their ordering in the model cannot be compared to the order of
+ // for example reactions since they are generally defined below in inputs/outputs and above the reactions.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true);
+ break;
+ case FULL_CONTROL:
+ // Give timers a model order since they should be controllable too.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, false);
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Configures layout options for a startup action.
+ *
+ * @param node The KNode of a startup action.
+ */
+ public void configureStartUp(KNode node) {
+ // Nothing should be done. Model order is considered per default value.
+ // The actual ordering of this node has to be done in the synthesis.
+ }
+
+ /**
+ * Configures layout options for a shutdown action.
+ *
+ * @param node The KNode of a shutdown action.
+ */
+ public void configureShutDown(KNode node) {
+ String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER);
+
+ switch (modelOrderStrategy) {
+ case STRICT_REACTION_ONLY:
+ case STRICT:
+ case TIE_BREAKER:
+ case FULL_CONTROL:
+ // The shutdown node cannot have a high model order, since this would confuse cycle breaking.
+ // It also cannot have a low model order.
+ // It should have none at all and the other nodes should define its position.
+ // This is no problem since the shutdown node has only outgoing edges.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true);
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Configures layout options for a reaction.
+ * Currently a reaction does not have internal behavior that is visualized and its order is always considered,
+ * therefore, nothing needs to be done.
+ *
+ * @param node The KNode of a reaction.
+ */
+ public void configureReaction(KNode node) {
+ // Has no internal behavior and model order is set by default.
+ }
+
+ /**
+ * Configures layout options for a dummy node.
+ *
+ * @param node The KNode of a dummy node.
+ */
+ public void configureDummy(KNode node) {
+ String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER);
+
+ switch (modelOrderStrategy) {
+ case STRICT_REACTION_ONLY:
+ case STRICT:
+ case TIE_BREAKER:
+ case FULL_CONTROL:
+ // A dummy node has no model order.
+ DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true);
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Orders a list of nodes by their corresponding linked instance if synthesis option for full control is enabled.
+ * Ordering is done by the {@link #TEXTUAL_ORDER} comparator.
+ *
+ * @param nodes List of KNodes to be ordered.
+ */
+ public void orderChildren(List nodes) {
+ String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER);
+ if (FULL_CONTROL.equals(modelOrderStrategy)) {
+ nodes.sort(TEXTUAL_ORDER);
+ }
+ }
+}
diff --git a/org.lflang.targetplatform/org.lflang.targetplatform.target b/org.lflang.targetplatform/org.lflang.targetplatform.target
index 54bca9ba33..e9639650b5 100644
--- a/org.lflang.targetplatform/org.lflang.targetplatform.target
+++ b/org.lflang.targetplatform/org.lflang.targetplatform.target
@@ -78,7 +78,7 @@
-
+