Skip to content

Commit

Permalink
Add ability to show/hide messages/timers in viz
Browse files Browse the repository at this point in the history
Adds a toggle pane on the side that shows/hides the messages in each
node's inbox area.

This commit _might_ address #59, but I'm not sure.
  • Loading branch information
emichael committed Nov 4, 2023
1 parent 33a4eb6 commit 59213bf
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 = "<html>%1s";

private static final String HIDE_ALL = "Hide All", SHOW_ALL = "Show All";

private final Address[] addresses;

private final Map<Address, SingleNodePanel> statePanels = new HashMap<>();
Expand All @@ -152,6 +161,14 @@ private static boolean runningInWSL() {
private final List<Pair<StatePredicate, JLabel>> prunes = new ArrayList<>();
private final List<Pair<StatePredicate, JLabel>> goals = new ArrayList<>();

private final JXTaskPane messageVisibilityPane, timerVisibilityPane;
private final JButton messagesVisibilityToggle, timersVisibilityToggle;
private final SortedMap<Class<? extends Message>, JCheckBox>
messageCheckBoxes =
new TreeMap<>(Comparator.comparing(Class::getName));
private final SortedMap<Class<? extends Timer>, JCheckBox> timerCheckBoxes =
new TreeMap<>(Comparator.comparing(Class::getName));

private final Pair<JXTaskPane, JLabel> exceptionPanel;

private final JXMultiSplitPane splitPane;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -395,7 +446,7 @@ public void actionPerformed(ActionEvent e) {
public void actionPerformed(ActionEvent e) {
List<EventTreeState> p =
currentState.pathToBestDescendent();
if (p.size() > 0) {
if (!p.isEmpty()) {
setState(p.get(0));
}
}
Expand Down Expand Up @@ -582,6 +633,42 @@ private void updateExceptionPane() {
}
}

private void updateDeliverablesVisibilityPane() {
for (var m : currentState.network()) {
Class<? extends Message> 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<? extends Timer> 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
Expand Down Expand Up @@ -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));
}

Expand All @@ -667,14 +766,40 @@ void deliverEvent(Event e) {
void setState(@NonNull EventTreeState s) {
stateTreeCanvas.showEvent(s);
currentState = s;

ImmutableSet<Class<? extends Message>> hiddenMessageTypes =
messageCheckBoxes.entrySet().stream()
.filter(e -> !e.getValue().isSelected())
.map(Entry::getKey)
.collect(ImmutableSet.toImmutableSet());
ImmutableSet<Class<? extends Timer>> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
Expand All @@ -131,7 +136,9 @@ public void paint(Graphics g) {
}

void updateState(EventTreeState s, SearchSettings settings,
boolean viewDeliveredMessages) {
boolean viewDeliveredMessages,
ImmutableSet<Class<? extends Message>> hiddenMessageTypes,
ImmutableSet<Class<? extends Timer>> hiddenTimerTypes) {
boolean nodeIsDiffed = !s.isInitialState() &&
s.previousEvent().locationRootAddress().equals(address);
if (nodeIsDiffed) {
Expand All @@ -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<MessageEnvelope> ms = Sets.newHashSet(
viewDeliveredMessages ? s.network() : s.undeliveredMessages());
final boolean pruned,
ImmutableSet<Class<? extends Message>> hiddenMessageTypes) {
// The set of messages to display
final ImmutableSet<MessageEnvelope> ms;
{
Iterable<MessageEnvelope> 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)) {
Expand Down Expand Up @@ -183,7 +202,8 @@ private void updateMessages(final EventTreeState s,
messageBox.revalidate();
messageBox.repaint();
}
for (Entry<MessageEnvelope, Pair<JButton, ObjectJTree>> messageEntry : messages.entrySet()) {
for (Entry<MessageEnvelope, Pair<JButton, ObjectJTree>> messageEntry
: messages.entrySet()) {
MessageEnvelope message = messageEntry.getKey();
ObjectJTree tree = messageEntry.getValue().getRight();
if (!s.isInitialState() &&
Expand All @@ -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<Class<? extends Timer>> hiddenTimerTypes) {
boolean repaintTimerBox = false;

// Track numbers of times timers are seen to infer which ones are new
Map<TimerEnvelope, Integer> 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<TimerEnvelope, Integer> otf =
s.isInitialState() ? ImmutableMap.of() : ImmutableMap.copyOf(
s.parent().timerFrequencies(address));

// The current pane's timers' frequencies
HashMap<TimerEnvelope, Integer> ctf = new HashMap<>();

// This state's timers' frequencies
ImmutableMap<TimerEnvelope, Integer> 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<TimerEnvelope> 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,
Expand Down Expand Up @@ -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<Boolean> tIsDeliverable =
() -> timerSet.add(t) && s.canStepTimer(t);
Expand Down

0 comments on commit 59213bf

Please sign in to comment.