diff --git a/framework/tst/dslabs/framework/testing/visualization/DebuggerWindow.java b/framework/tst/dslabs/framework/testing/visualization/DebuggerWindow.java index 6af65624..47818e47 100644 --- a/framework/tst/dslabs/framework/testing/visualization/DebuggerWindow.java +++ b/framework/tst/dslabs/framework/testing/visualization/DebuggerWindow.java @@ -22,8 +22,11 @@ package dslabs.framework.testing.visualization; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import dslabs.framework.Address; +import dslabs.framework.Message; +import dslabs.framework.Timer; import dslabs.framework.testing.Event; import dslabs.framework.testing.StatePredicate; import dslabs.framework.testing.StatePredicate.PredicateResult; @@ -51,13 +54,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.prefs.BackingStoreException; import java.util.stream.Collectors; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.ButtonGroup; import javax.swing.InputMap; +import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; @@ -142,6 +149,8 @@ private static boolean runningInWSL() { static final int WINDOW_DEFAULT_WIDTH = 1440, WINDOW_DEFAULT_HEIGHT = 810; static final String LINE_WRAPPING_FORMAT = "%1s"; + private static final String HIDE_ALL = "Hide All", SHOW_ALL = "Show All"; + private final Address[] addresses; private final Map statePanels = new HashMap<>(); @@ -152,6 +161,14 @@ private static boolean runningInWSL() { private final List> prunes = new ArrayList<>(); private final List> goals = new ArrayList<>(); + private final JXTaskPane messageVisibilityPane, timerVisibilityPane; + private final JButton messagesVisibilityToggle, timersVisibilityToggle; + private final SortedMap, JCheckBox> + messageCheckBoxes = + new TreeMap<>(Comparator.comparing(Class::getName)); + private final SortedMap, JCheckBox> timerCheckBoxes = + new TreeMap<>(Comparator.comparing(Class::getName)); + private final Pair exceptionPanel; private final JXMultiSplitPane splitPane; @@ -333,6 +350,40 @@ public void actionPerformed(ActionEvent e) { sideBar.add(exceptionPane); updateExceptionPane(); + messageVisibilityPane = new JXTaskPane("Show/Hide Messages"); + messagesVisibilityToggle = new JButton(HIDE_ALL); + messagesVisibilityToggle.addActionListener(__ -> { + if (messageCheckBoxes.values().stream() + .allMatch(AbstractButton::isSelected)) { + messageCheckBoxes.values() + .forEach(x -> x.setSelected(false)); + } else { + messageCheckBoxes.values() + .forEach(x -> x.setSelected(true)); + } + setState(currentState); + }); + messageVisibilityPane.add(messagesVisibilityToggle); + messageVisibilityPane.setVisible(false); + sideBar.add(messageVisibilityPane); + + timerVisibilityPane = new JXTaskPane("Show/Hide Timers"); + timersVisibilityToggle = new JButton(HIDE_ALL); + timersVisibilityToggle.addActionListener(__ -> { + if (timerCheckBoxes.values().stream() + .allMatch(AbstractButton::isSelected)) { + timerCheckBoxes.values().forEach(x -> x.setSelected(false)); + } else { + timerCheckBoxes.values().forEach(x -> x.setSelected(true)); + } + setState(currentState); + }); + timerVisibilityPane.add(timersVisibilityToggle); + timerVisibilityPane.setVisible(false); + sideBar.add(timerVisibilityPane); + + updateDeliverablesVisibilityPane(); + sideBar.setMinimumSize(new Dimension(20, 0)); // Don't let sidebar be too large on startup sideBar.setPreferredSize(new Dimension( @@ -395,7 +446,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { List p = currentState.pathToBestDescendent(); - if (p.size() > 0) { + if (!p.isEmpty()) { setState(p.get(0)); } } @@ -582,6 +633,42 @@ private void updateExceptionPane() { } } + private void updateDeliverablesVisibilityPane() { + for (var m : currentState.network()) { + Class messageType = m.message().getClass(); + if (!messageCheckBoxes.containsKey(messageType)) { + int position = messageCheckBoxes.headMap(messageType).size(); + JCheckBox checkBox = new JCheckBox(messageType.getSimpleName()); + checkBox.setToolTipText(messageType.getName()); + checkBox.setSelected(true); + checkBox.addActionListener(__ -> setState(currentState)); + messageVisibilityPane.add(checkBox, position); + messageCheckBoxes.put(messageType, checkBox); + if (!messageVisibilityPane.isVisible()) { + messageVisibilityPane.setVisible(true); + } + } + } + for (Address a : currentState.addresses()) { + for (var t : currentState.timers(a)) { + Class timerType = t.timer().getClass(); + if (!timerCheckBoxes.containsKey(timerType)) { + int position = timerCheckBoxes.headMap(timerType).size(); + JCheckBox checkBox = + new JCheckBox(timerType.getSimpleName()); + checkBox.setToolTipText(timerType.getName()); + checkBox.setSelected(true); + checkBox.addActionListener(__ -> setState(currentState)); + timerVisibilityPane.add(checkBox, position); + timerCheckBoxes.put(timerType, checkBox); + if (!timerVisibilityPane.isVisible()) { + timerVisibilityPane.setVisible(true); + } + } + } + } + } + private void layoutNodes() { /* * We must reset the JXMultiSplitPane every time. Unfortunately, hiding @@ -655,6 +742,18 @@ private void layoutNodes() { void reset() { stateTreeCanvas.reset(); eventsPanel.reset(); + + messageVisibilityPane.setVisible(false); + messageVisibilityPane.removeAll(); + messageVisibilityPane.add(messagesVisibilityToggle); + + timerVisibilityPane.setVisible(false); + timerVisibilityPane.removeAll(); + timerVisibilityPane.add(timersVisibilityToggle); + + messageCheckBoxes.clear(); + timerCheckBoxes.clear(); + setState(EventTreeState.convert(DebuggerWindow.this.initialState)); } @@ -667,14 +766,40 @@ void deliverEvent(Event e) { void setState(@NonNull EventTreeState s) { stateTreeCanvas.showEvent(s); currentState = s; + + ImmutableSet> hiddenMessageTypes = + messageCheckBoxes.entrySet().stream() + .filter(e -> !e.getValue().isSelected()) + .map(Entry::getKey) + .collect(ImmutableSet.toImmutableSet()); + ImmutableSet> hiddenTimerTypes = + timerCheckBoxes.entrySet().stream() + .filter(e -> !e.getValue().isSelected()) + .map(Entry::getKey) + .collect(ImmutableSet.toImmutableSet()); + + if (hiddenMessageTypes.isEmpty()) { + messagesVisibilityToggle.setText(HIDE_ALL); + } else { + messagesVisibilityToggle.setText(SHOW_ALL); + } + + if (hiddenTimerTypes.isEmpty()) { + timersVisibilityToggle.setText(HIDE_ALL); + } else { + timersVisibilityToggle.setText(SHOW_ALL); + } + for (Address a : currentState.addresses()) { assert statePanels.containsKey(a); statePanels.get(a).updateState(currentState, ignoreSearchSettings ? null : searchSettings, - viewDeliveredMessages); + viewDeliveredMessages, hiddenMessageTypes, + hiddenTimerTypes); } eventsPanel.update(currentState); updatePredicatePanes(); updateExceptionPane(); + updateDeliverablesVisibilityPane(); } } diff --git a/framework/tst/dslabs/framework/testing/visualization/SingleNodePanel.java b/framework/tst/dslabs/framework/testing/visualization/SingleNodePanel.java index d6661742..798a151a 100644 --- a/framework/tst/dslabs/framework/testing/visualization/SingleNodePanel.java +++ b/framework/tst/dslabs/framework/testing/visualization/SingleNodePanel.java @@ -22,9 +22,13 @@ package dslabs.framework.testing.visualization; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Streams; import dslabs.framework.Address; +import dslabs.framework.Message; +import dslabs.framework.Timer; import dslabs.framework.testing.Event; import dslabs.framework.testing.MessageEnvelope; import dslabs.framework.testing.TimerEnvelope; @@ -111,7 +115,8 @@ class SingleNodePanel extends JPanel { // XXX: why does this need w 100%, h 100%? Shouldn't grow handle it? add(mainSplitPane, "grow, h 100%"); - updateState(s, settings, viewDeliveredMessages); + updateState(s, settings, viewDeliveredMessages, ImmutableSet.of(), + ImmutableSet.of()); add(new JLabel(a.toString()), "center"); } @@ -131,7 +136,9 @@ public void paint(Graphics g) { } void updateState(EventTreeState s, SearchSettings settings, - boolean viewDeliveredMessages) { + boolean viewDeliveredMessages, + ImmutableSet> hiddenMessageTypes, + ImmutableSet> hiddenTimerTypes) { boolean nodeIsDiffed = !s.isInitialState() && s.previousEvent().locationRootAddress().equals(address); if (nodeIsDiffed) { @@ -145,16 +152,28 @@ void updateState(EventTreeState s, SearchSettings settings, final boolean pruned = settings != null && settings.shouldPrune(s.state()); - updateMessages(s, settings, viewDeliveredMessages, pruned); - updateTimers(s, settings, pruned); + updateMessages(s, settings, viewDeliveredMessages, pruned, + hiddenMessageTypes); + updateTimers(s, settings, pruned, hiddenTimerTypes); } private void updateMessages(final EventTreeState s, final SearchSettings settings, final boolean viewDeliveredMessages, - final boolean pruned) { - final Set ms = Sets.newHashSet( - viewDeliveredMessages ? s.network() : s.undeliveredMessages()); + final boolean pruned, + ImmutableSet> hiddenMessageTypes) { + // The set of messages to display + final ImmutableSet ms; + { + Iterable messagesWithoutFilter = + viewDeliveredMessages ? s.network() : + s.undeliveredMessages(); + ms = Streams.stream(messagesWithoutFilter) + .filter(m -> !hiddenMessageTypes.contains( + m.message().getClass())) + .collect(ImmutableSet.toImmutableSet()); + } + boolean repaintMessageBox = false; for (MessageEnvelope message : ms) { if (!message.to().rootAddress().equals(address)) { @@ -183,7 +202,8 @@ private void updateMessages(final EventTreeState s, messageBox.revalidate(); messageBox.repaint(); } - for (Entry> messageEntry : messages.entrySet()) { + for (Entry> messageEntry + : messages.entrySet()) { MessageEnvelope message = messageEntry.getKey(); ObjectJTree tree = messageEntry.getValue().getRight(); if (!s.isInitialState() && @@ -197,18 +217,35 @@ private void updateMessages(final EventTreeState s, private void updateTimers(final EventTreeState s, final SearchSettings settings, - final boolean pruned) { + final boolean pruned, + ImmutableSet> hiddenTimerTypes) { boolean repaintTimerBox = false; // Track numbers of times timers are seen to infer which ones are new - Map otf = null, ctf = new HashMap<>(), stf = - s.timerFrequencies(address); - if (!s.isInitialState()) { - otf = s.parent().timerFrequencies(address); - } + + // The previous state's timers' frequencies (no need to remove hidden + // timer types) + final ImmutableMap otf = + s.isInitialState() ? ImmutableMap.of() : ImmutableMap.copyOf( + s.parent().timerFrequencies(address)); + + // The current pane's timers' frequencies + HashMap ctf = new HashMap<>(); + + // This state's timers' frequencies + ImmutableMap stf = + s.timerFrequencies(address).entrySet().stream() + .filter(e -> !hiddenTimerTypes.contains( + e.getKey().timer().getClass())).collect( + ImmutableMap.toImmutableMap(Entry::getKey, + Entry::getValue)); // The new timer queue to materialize to the user - final var newTimers = Lists.newArrayList(s.timers(address).iterator()); + final ImmutableList newTimers = + Streams.stream(s.timers(address)) + .filter(t -> !hiddenTimerTypes.contains( + t.timer().getClass())) + .collect(ImmutableList.toImmutableList()); /* First, remove all the timers that aren't present in the new state, @@ -246,7 +283,7 @@ private void updateTimers(final EventTreeState s, while (i < newTimers.size()) { var t = newTimers.get(i); final int tf = ctf.getOrDefault(t, 0) + 1; - final boolean tIsNew = otf != null && tf > otf.getOrDefault(t, 0); + final boolean tIsNew = tf > otf.getOrDefault(t, 0); // This is too clever by half, but it works final Supplier tIsDeliverable = () -> timerSet.add(t) && s.canStepTimer(t);