Skip to content

Commit fa9e6d9

Browse files
test: display branched events inside HashgraphGui (#18618)
Signed-off-by: Ivan Kavaldzhiev <[email protected]>
1 parent 107e8c5 commit fa9e6d9

File tree

12 files changed

+389
-13
lines changed

12 files changed

+389
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package com.swirlds.platform.gui;
3+
4+
/**
5+
* Metadata for an event that is part of a branched event.
6+
* <p>
7+
* This metadata contains the index of the branch and the generation of the event.
8+
*/
9+
public record BranchedEventMetadata(Integer branchIndex, Long generation) {
10+
11+
/**
12+
* Get the index of the branch that the linked event is on.
13+
*/
14+
public Integer getBranchIndex() {
15+
return branchIndex;
16+
}
17+
18+
/**
19+
* Get the generation of the event linked to this metadata.
20+
*/
21+
public Long getGeneration() {
22+
return generation;
23+
}
24+
}

platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java

+19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import static org.hiero.consensus.model.event.EventConstants.FIRST_GENERATION;
55

6+
import com.hedera.hapi.platform.event.GossipEvent;
67
import com.hedera.hapi.platform.state.ConsensusSnapshot;
78
import com.swirlds.common.context.PlatformContext;
89
import com.swirlds.config.api.Configuration;
@@ -17,7 +18,9 @@
1718
import com.swirlds.platform.system.address.AddressBook;
1819
import edu.umd.cs.findbugs.annotations.NonNull;
1920
import edu.umd.cs.findbugs.annotations.Nullable;
21+
import java.util.HashMap;
2022
import java.util.List;
23+
import java.util.Map;
2124
import java.util.Objects;
2225
import org.hiero.consensus.config.EventConfig;
2326
import org.hiero.consensus.model.event.PlatformEvent;
@@ -37,6 +40,7 @@ public class GuiEventStorage {
3740
private final SimpleLinker linker;
3841
private final Configuration configuration;
3942
private ConsensusRound lastConsensusRound;
43+
private Map<GossipEvent, BranchedEventMetadata> branchedEventsMetadata = new HashMap<>();
4044

4145
/**
4246
* Creates an empty instance
@@ -143,4 +147,19 @@ public synchronized List<EventImpl> getNonAncientEvents() {
143147
public synchronized @Nullable ConsensusRound getLastConsensusRound() {
144148
return lastConsensusRound;
145149
}
150+
151+
/**
152+
* Get map with a link between a branched event and its metadata
153+
*
154+
* @return the map
155+
*/
156+
@NonNull
157+
public Map<GossipEvent, BranchedEventMetadata> getBranchedEventsMetadata() {
158+
return branchedEventsMetadata;
159+
}
160+
161+
public void setBranchedEventsMetadata(
162+
@NonNull final Map<GossipEvent, BranchedEventMetadata> branchedEventsMetadata) {
163+
this.branchedEventsMetadata = branchedEventsMetadata;
164+
}
146165
}

platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/HashgraphPictureOptions.java

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ public interface HashgraphPictureOptions {
5555
*/
5656
boolean writeBirthRound();
5757

58+
/**
59+
* @return should branch numbers be written for every branched event (if any)
60+
*/
61+
boolean writeBranches();
62+
5863
/**
5964
* @return should simple colors be used in the picture
6065
*/

platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/EventSelector.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public void mouseClicked(final MouseEvent me) {
7676
final int xEvent = metadata.xpos(null, e);
7777
final int yEvent = metadata.ypos(e);
7878
final double distanceSquared = Math.pow(xEvent - xClicked, 2) + Math.pow(yEvent - yClicked, 2);
79-
if (distanceSquared <= rSquared) {
79+
if (distanceSquared <= rSquared - 20) {
8080
if (selectedEvent == e) {
8181
selectedEvent = null;
8282
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package com.swirlds.platform.gui.hashgraph.internal;
3+
4+
import com.hedera.hapi.platform.event.GossipEvent;
5+
import edu.umd.cs.findbugs.annotations.NonNull;
6+
import java.util.Map;
7+
8+
/**
9+
* Class holding X coordinates for branched events with a specific generation for a given node.
10+
* Note that it's normal the branch to contain events with different generations.
11+
*/
12+
public class GenerationCoordinates {
13+
14+
// the X coordinates of all branched events with the same generation
15+
private Map<GossipEvent, Integer> xCoordinates;
16+
// the X coordinate of the most right branched event for a given generation
17+
private Integer rightMostX = 0;
18+
19+
/**
20+
* Return the X coordinates of all branched events with the same generation.
21+
*
22+
* @return the coordinates
23+
*/
24+
public Map<GossipEvent, Integer> getXCoordinates() {
25+
return xCoordinates;
26+
}
27+
28+
/**
29+
* Set updated X coordinates of all branched events with the same generation.
30+
*
31+
* @param xCoordinates the coordinates to set
32+
*/
33+
public void setXCoordinates(@NonNull final Map<GossipEvent, Integer> xCoordinates) {
34+
this.xCoordinates = xCoordinates;
35+
}
36+
37+
/**
38+
* Return the X coordinate of the most right branched event for a given generation.
39+
*
40+
* @return the coordinate
41+
*/
42+
public Integer getRightMostX() {
43+
return rightMostX;
44+
}
45+
46+
/**
47+
* Set the X coordinate of the most right branched event for a given generation.
48+
*
49+
* @param rightMostX the coordinate to set
50+
*/
51+
public void setRightMostX(@NonNull final Integer rightMostX) {
52+
this.rightMostX = rightMostX;
53+
}
54+
}

platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphGuiControls.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public class HashgraphGuiControls implements HashgraphPictureOptions {
4949
private final Checkbox labelGenerationCheckbox;
5050
/** the birth round number for the event */
5151
private final Checkbox labelBirthroundCheckbox;
52+
/** the branch number for the event */
53+
private final Checkbox labelBranchNumberCheckbox;
5254
/** check to display the latest events available */
5355
private final Checkbox displayLatestEvents;
5456

@@ -71,6 +73,7 @@ public HashgraphGuiControls(final ItemListener freezeListener) {
7173
labelConsTimestampCheckbox = new Checkbox("Labels: Timestamp (consensus)");
7274
labelGenerationCheckbox = new Checkbox("Labels: Generation");
7375
labelBirthroundCheckbox = new Checkbox("Labels: Birth round");
76+
labelBranchNumberCheckbox = new Checkbox("Labels: Branch number");
7477
displayLatestEvents = new Checkbox("Display latest events");
7578
displayLatestEvents.setState(true);
7679

@@ -109,6 +112,7 @@ public HashgraphGuiControls(final ItemListener freezeListener) {
109112
labelConsTimestampCheckbox,
110113
labelGenerationCheckbox,
111114
labelBirthroundCheckbox,
115+
labelBranchNumberCheckbox,
112116
displayLatestEvents
113117
};
114118
}
@@ -196,7 +200,8 @@ public JPanel createPanel() {
196200
- Non-famous witnesses are yellow\s
197201
- Famous witnesses are green\s
198202
- Undecided witnesses are red\s
199-
- The selected event is magenta\s
203+
- The selected event is magenta with green border\s
204+
- The parents of the selected event have magenta borders\s
200205
- The events the selected event can strongly see are cyan\s""")),
201206
constr);
202207
constr.gridy++;
@@ -282,6 +287,11 @@ public boolean displayLatestEvents() {
282287
return displayLatestEvents.getState();
283288
}
284289

290+
@Override
291+
public boolean writeBranches() {
292+
return labelBranchNumberCheckbox.getState();
293+
}
294+
285295
@Override
286296
public void setStartGeneration(final long startGeneration) {
287297
this.startGeneration.setValue(startGeneration);

platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphPicture.java

+78-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static com.swirlds.logging.legacy.LogMarker.EXCEPTION;
55
import static com.swirlds.platform.gui.hashgraph.HashgraphGuiConstants.HASHGRAPH_PICTURE_FONT;
66

7+
import com.hedera.hapi.platform.event.GossipEvent;
78
import com.swirlds.platform.Consensus;
89
import com.swirlds.platform.consensus.CandidateWitness;
910
import com.swirlds.platform.gui.hashgraph.HashgraphGuiConstants;
@@ -27,12 +28,15 @@
2728
import java.awt.image.BufferedImage;
2829
import java.io.Serial;
2930
import java.time.Instant;
31+
import java.util.HashMap;
3032
import java.util.Iterator;
3133
import java.util.List;
34+
import java.util.Map;
3235
import javax.swing.JPanel;
3336
import org.apache.logging.log4j.LogManager;
3437
import org.apache.logging.log4j.Logger;
3538
import org.hiero.consensus.model.event.EventConstants;
39+
import org.hiero.consensus.model.node.NodeId;
3640

3741
/**
3842
* This panel has the hashgraph picture, and appears in the window to the right of all the settings.
@@ -52,6 +56,9 @@ public class HashgraphPicture extends JPanel {
5256
private AddressBookMetadata nonExpandedMetadata;
5357
private AddressBookMetadata expandedMetadata;
5458

59+
/** used to store coordinates for branched events with a given generation for each forking node */
60+
private final Map<Long, Map<Long, GenerationCoordinates>> nodeIdToBranchIndexToCoordinates = new HashMap<>();
61+
5562
public HashgraphPicture(final HashgraphGuiSource hashgraphSource, final HashgraphPictureOptions options) {
5663
this.hashgraphSource = hashgraphSource;
5764
this.options = options;
@@ -105,7 +112,8 @@ public void paintComponent(final Graphics g) {
105112
.filter(e -> addressBook.getIndexOfNodeId(e.getCreatorId()) < numMem)
106113
.toList();
107114

108-
pictureMetadata = new PictureMetadata(fm, this.getSize(), currentMetadata, events);
115+
pictureMetadata = new PictureMetadata(
116+
fm, this.getSize(), currentMetadata, events, hashgraphSource, nodeIdToBranchIndexToCoordinates);
109117

110118
selector.setMetadata(pictureMetadata);
111119
selector.setEventsInPicture(events);
@@ -128,6 +136,12 @@ public void paintComponent(final Graphics g) {
128136

129137
final int d = pictureMetadata.getD();
130138

139+
if (nodeIdToBranchIndexToCoordinates.isEmpty()) {
140+
for (final NodeId nodeId : hashgraphSource.getAddressBook().getNodeIdSet()) {
141+
nodeIdToBranchIndexToCoordinates.put(nodeId.id(), new HashMap<>());
142+
}
143+
}
144+
131145
// for each event, draw 2 downward lines to its parents
132146
for (final EventImpl event : events) {
133147
drawLinksToParents(g, event);
@@ -137,13 +151,42 @@ public void paintComponent(final Graphics g) {
137151
for (final EventImpl event : events) {
138152
drawEventCircle(g, event, options, d);
139153
}
154+
155+
final List<EventImpl> selectedEvents =
156+
events.stream().filter(selector::isSelected).toList();
157+
if (!selectedEvents.isEmpty()) {
158+
final EventImpl selectedEvent = selectedEvents.getFirst();
159+
if (selectedEvent != null) {
160+
// if we have a selected event draw the circle and its parent links last, so that all labels and
161+
// lines connected to it can be easily seen
162+
drawLinksToParents(g, selectedEvent);
163+
drawEventCircle(g, selectedEvent, options, d);
164+
}
165+
}
140166
} catch (final Exception e) {
141167
logger.error(EXCEPTION.getMarker(), "error while painting", e);
142168
}
143169
}
144170

171+
/**
172+
* Draws a border around the event circle.
173+
*
174+
* @param g the graphics context
175+
* @param d the diameter of the event circle
176+
* @param event the selected event
177+
* @param borderColor the color of the border
178+
*/
179+
private void drawBorderAroundEvent(final Graphics g, final int d, final EventImpl event, final Color borderColor) {
180+
final int xPos =
181+
pictureMetadata.xpos(event.getOtherParent() != null ? event.getOtherParent() : event, event) - d / 2;
182+
final int yPos = pictureMetadata.ypos(event) - d / 2;
183+
g.setColor(borderColor);
184+
g.fillOval(xPos - 5, yPos - 5, d + 10, d + 10);
185+
}
186+
145187
private void drawLinksToParents(final Graphics g, final EventImpl event) {
146-
Graphics2D g2d = (Graphics2D) g;
188+
final int d = pictureMetadata.getD();
189+
final Graphics2D g2d = (Graphics2D) g;
147190
Stroke savedStroke = null;
148191
g.setColor(HashgraphGuiUtils.eventColor(event, options));
149192
boolean selectedLines = selector.isSelected(event);
@@ -168,15 +211,29 @@ private void drawLinksToParents(final Graphics g, final EventImpl event) {
168211
g.drawLine(
169212
pictureMetadata.xpos(e2, event),
170213
pictureMetadata.ypos(event),
171-
pictureMetadata.xpos(e2, event),
214+
pictureMetadata.xpos(e2, e1),
172215
pictureMetadata.ypos(e1));
216+
217+
if (selectedLines) {
218+
final Color currentColor = g.getColor();
219+
drawBorderAroundEvent(g, d, e1, Color.MAGENTA);
220+
drawEventCircle(g, e1, options, d);
221+
g.setColor(currentColor);
222+
}
173223
}
174224
if (e2 != null && e2.getGeneration() >= pictureMetadata.getMinGen()) {
175225
g.drawLine(
176226
pictureMetadata.xpos(e2, event),
177227
pictureMetadata.ypos(event),
178228
pictureMetadata.xpos(event, e2),
179229
pictureMetadata.ypos(e2));
230+
231+
if (selectedLines) {
232+
final Color currentColor = g.getColor();
233+
drawBorderAroundEvent(g, d, e2, Color.MAGENTA);
234+
drawEventCircle(g, e2, options, d);
235+
g.setColor(currentColor);
236+
}
180237
}
181238

182239
if (selectedLines) {
@@ -204,11 +261,15 @@ private void drawEventCircle(
204261
} else {
205262
color = HashgraphGuiUtils.eventColor(event, options);
206263
}
207-
g.setColor(color);
208264

209265
final int xPos = pictureMetadata.xpos(e2, event) - d / 2;
210266
final int yPos = pictureMetadata.ypos(event) - d / 2;
211267

268+
if (selector.isSelected(event)) {
269+
drawBorderAroundEvent(g, d, event, Color.GREEN);
270+
}
271+
272+
g.setColor(color);
212273
g.fillOval(xPos, yPos, d, d);
213274
g.setFont(g.getFont().deriveFont(Font.BOLD));
214275

@@ -254,8 +315,21 @@ private void drawEventCircle(
254315
if (options.writeBirthRound()) {
255316
s += " " + event.getBirthRound();
256317
}
318+
319+
final GossipEvent gossipEvent = event.getBaseEvent().getGossipEvent();
320+
if (options.writeBranches()
321+
&& hashgraphSource.getEventStorage().getBranchedEventsMetadata().containsKey(gossipEvent)) {
322+
s += " " + "\\/ "
323+
+ hashgraphSource
324+
.getEventStorage()
325+
.getBranchedEventsMetadata()
326+
.get(gossipEvent)
327+
.branchIndex();
328+
}
329+
257330
if (!s.isEmpty()) {
258331
final Rectangle2D rect = fm.getStringBounds(s, g);
332+
259333
final int x = (int) (pictureMetadata.xpos(e2, event) - rect.getWidth() / 2. - fa / 4.);
260334
final int y = (int) (pictureMetadata.ypos(event) + rect.getHeight() / 2. - fd / 2);
261335
g.setColor(HashgraphGuiConstants.LABEL_OUTLINE);

0 commit comments

Comments
 (0)