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 @@ - +