From 4c83d89a9b8e6e9a21aec44d2f32a23f346ec4bf Mon Sep 17 00:00:00 2001
From: "masha.kashirina"
Date: Wed, 18 Mar 2026 18:56:13 +0100
Subject: [PATCH 01/10] bug6214: racemap scheduler for avoiding per-competitor
refresh
---
.../gwt/ui/client/shared/racemap/RaceMap.java | 81 +++++++++++--------
1 file changed, 46 insertions(+), 35 deletions(-)
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
index a2a16455485..77cc052e2a3 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
@@ -3121,6 +3121,48 @@ public class RaceMap extends AbstractCompositeComponent impleme
public RaceMapSettings getSettings() {
return settings;
}
+ private boolean selectionDependentRefreshScheduled;
+ private boolean selectionDependentRefreshPending;
+
+ private void scheduleSelectionDependentTailAndMapStateRefresh() {
+ selectionDependentRefreshPending = true;
+
+ if (!selectionDependentRefreshScheduled) {
+ selectionDependentRefreshScheduled = true;
+ Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
+ @Override
+ public void execute() {
+ selectionDependentRefreshScheduled = false;
+
+ if (selectionDependentRefreshPending) {
+ selectionDependentRefreshPending = false;
+ refreshSelectionDependentTailAndMapState();
+ }
+ }
+ });
+ }
+ }
+ private void refreshSelectionDependentTailAndMapState() {
+ if (selectedDetailType != null && !selectedDetailTypeChanged) {
+ // assumes that the detail values have already been loaded, as the detail type hasn't changed
+ fixesAndTails.updateDetailValueBoundaries(competitorSelection.getSelectedCompetitors());
+ }
+ // update tails for all competitors because selection change may also affect all unselected competitors
+ for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) {
+ Colorline tail = fixesAndTails.getTail(oneOfAllCompetitors);
+ if (tail != null) {
+ ColorlineOptions newOptions =
+ createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors));
+ tail.setOptions(newOptions); // depends on the min/max boundaries computed above
+ }
+ }
+ // Trigger auto-zoom if needed
+ final RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
+ if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
+ zoomMapToNewBounds(zoomSettings.getNewBounds(this));
+ }
+ redraw();
+ }
@Override
public void addedToSelection(CompetitorDTO competitor) {
@@ -3148,24 +3190,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
}
}
- if (selectedDetailType != null && !selectedDetailTypeChanged) {
- // assumes that the detail values have already been loaded, as the detail type hasn't changed
- fixesAndTails.updateDetailValueBoundaries(competitorSelection.getSelectedCompetitors());
- }
- // update tails for all competitors because selection change may also affect all unselected competitors
- for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) {
- Colorline tail = fixesAndTails.getTail(oneOfAllCompetitors);
- if (tail != null) {
- ColorlineOptions newOptions = createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors));
- tail.setOptions(newOptions); // depends on the min/max boundaries computed above
- }
- }
- // Trigger auto-zoom if needed
- final RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
- if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
- zoomMapToNewBounds(zoomSettings.getNewBounds(this));
- }
- redraw();
+ scheduleSelectionDependentTailAndMapStateRefresh();
}
@Override
@@ -3173,13 +3198,13 @@ public class RaceMap extends AbstractCompositeComponent impleme
if (isShowAnyHelperLines()) {
// helper lines depend on which competitor is visible, because the *visible* leader is used for
// deciding which helper lines to show:
- redraw();
+ //redraw();
} else {
// try a more incremental update otherwise
if (settings.isShowOnlySelectedCompetitors()) {
// if selection is now empty, show all competitors
if (Util.isEmpty(competitorSelection.getSelectedCompetitors())) {
- redraw();
+ //redraw();
} else {
// otherwise remove only deselected competitor's boat images and tail
final BoatOverlay removedBoatOverlay = boatOverlaysByCompetitorIdsAsStrings.remove(competitor.getIdAsString());
@@ -3200,21 +3225,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
}
}
// Now update tails for all competitors because selection change may also affect all unselected competitors
- if (selectedDetailType != null && !selectedDetailTypeChanged) {
- fixesAndTails.updateDetailValueBoundaries(competitorSelection.getSelectedCompetitors());
- }
- for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) {
- Colorline tail = fixesAndTails.getTail(oneOfAllCompetitors);
- if (tail != null) {
- ColorlineOptions newOptions = createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors));
- tail.setOptions(newOptions);
- }
- }
- // Trigger auto-zoom if needed
- RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
- if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
- zoomMapToNewBounds(zoomSettings.getNewBounds(this));
- }
+ scheduleSelectionDependentTailAndMapStateRefresh();
}
private boolean isShowAnyHelperLines() {
From 1d4873651b4c7249188bfc6630f66cd971985a67 Mon Sep 17 00:00:00 2001
From: "masha.kashirina"
Date: Wed, 18 Mar 2026 19:58:54 +0100
Subject: [PATCH 02/10] bug6214: fixed raceMap update runtime
---
.../gwt/ui/client/shared/racemap/RaceMap.java | 101 ++++++++++++++----
1 file changed, 82 insertions(+), 19 deletions(-)
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
index 77cc052e2a3..74fa5e9bd8b 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
@@ -283,6 +283,26 @@ public class RaceMap extends AbstractCompositeComponent impleme
* The leeward of two Polylines representing a triangle between startline and first mark.
*/
private Polyline leewardStartLineMarkToFirstMarkLine;
+
+ /**
+ * True once a selection-dependent tail/map refresh has already been scheduled for the current
+ * event cycle. This is used to coalesce many add/remove selection callbacks into one final refresh
+ * instead of running the expensive refresh logic once per competitor.
+ */
+ private boolean selectionDependentRefreshScheduled;
+
+ /**
+ * True when at least one selection change occurred and a selection-dependent refresh is still
+ * needed. The scheduled command clears this flag after performing the consolidated refresh.
+ */
+ private boolean selectionDependentRefreshPending;
+
+ /**
+ * Remembers the last tail display mode applied per competitor id. This allows us to skip
+ * reapplying tail styles for competitors whose visual tail state did not change between two
+ * refreshes. The optimization is especially useful when no detail-based tail coloring is active.
+ */
+ private final Map lastAppliedTailDisplayModes = new HashMap<>();
private class AdvantageLineMouseOverMapHandler implements MouseOverMapHandler {
private double trueWindAngle;
@@ -3121,12 +3141,23 @@ public class RaceMap extends AbstractCompositeComponent impleme
public RaceMapSettings getSettings() {
return settings;
}
- private boolean selectionDependentRefreshScheduled;
- private boolean selectionDependentRefreshPending;
+ /**
+ * Schedules a single consolidated refresh of selection-dependent tail and map state.
+ *
+ * Selection changes are delivered per competitor, so bulk selection can trigger many
+ * added/removed callbacks in the same event cycle. Running the expensive tail/map refresh
+ * immediately in every callback caused repeated redraw and restyling work. This method batches
+ * those callbacks by marking a refresh as pending and scheduling exactly one final refresh via
+ * {@link Scheduler#scheduleFinally(Scheduler.ScheduledCommand)}.
+ *
+ * If multiple selection events arrive before the scheduled command runs, they only set the
+ * pending flag again; no additional scheduled refresh is created. When the scheduled command
+ * executes, it performs one call to {@code refreshSelectionDependentTailAndMapState()} if a
+ * refresh is still pending.
+ */
private void scheduleSelectionDependentTailAndMapStateRefresh() {
selectionDependentRefreshPending = true;
-
if (!selectionDependentRefreshScheduled) {
selectionDependentRefreshScheduled = true;
Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
@@ -3151,9 +3182,17 @@ public class RaceMap extends AbstractCompositeComponent impleme
for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) {
Colorline tail = fixesAndTails.getTail(oneOfAllCompetitors);
if (tail != null) {
- ColorlineOptions newOptions =
- createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors));
- tail.setOptions(newOptions); // depends on the min/max boundaries computed above
+ final DisplayMode newDisplayMode = displayHighlighted(oneOfAllCompetitors);
+ if (selectedDetailType == null) {
+ final String competitorId = oneOfAllCompetitors.getIdAsString();
+ final DisplayMode previousDisplayMode = lastAppliedTailDisplayModes.get(competitorId);
+ if (previousDisplayMode == newDisplayMode) {
+ continue;
+ }
+ lastAppliedTailDisplayModes.put(competitorId, newDisplayMode);
+ }
+ ColorlineOptions newOptions = createTailStyle(oneOfAllCompetitors, newDisplayMode);
+ tail.setOptions(newOptions);
}
}
// Trigger auto-zoom if needed
@@ -3176,6 +3215,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
CanvasOverlayV3 boatOverlay = next.getValue();
boatOverlay.removeFromMap();
fixesAndTails.removeTail(next.getKey());
+ lastAppliedTailDisplayModes.remove(next.getKey());
i.remove(); // only this way a ConcurrentModificationException while looping can be avoided
}
}
@@ -3212,6 +3252,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
removedBoatOverlay.removeFromMap();
}
fixesAndTails.removeTail(competitor.getIdAsString());
+ lastAppliedTailDisplayModes.remove(competitor.getIdAsString());
showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
}
} else {
@@ -3558,7 +3599,27 @@ public class RaceMap extends AbstractCompositeComponent impleme
}
}
}
-
+ /**
+ * Creates the visual style for one competitor tail based on the current display mode.
+ *
+ * The three display modes are:
+ *
+ * - DEFAULT: normal competitor-colored tail with standard stroke width
+ * - SELECTED: highlighted tail with thicker stroke
+ * - NOT_SELECTED: lowlighted grey tail for competitors that are currently not selected
+ *
+ *
+ * A selected tail uses {@link ColorlineMode#POLYCHROMATIC} only when detail-based coloring is
+ * actually active and detail values are available. Otherwise it stays
+ * {@link ColorlineMode#MONOCHROMATIC}. This avoids unnecessary switching between monochromatic and
+ * polychromatic rendering, which would force {@code Colorline.setOptions(...)} to rebuild the tail
+ * polyline structure via {@code setPath(...)} and was identified as a significant performance cost
+ * during selection updates.
+ *
+ * @param competitor the competitor whose tail is being styled
+ * @param displayMode the desired display mode for this competitor tail
+ * @return the configured {@link ColorlineOptions} for the tail
+ */
@Override
public ColorlineOptions createTailStyle(CompetitorDTO competitor, DisplayMode displayMode) {
final ColorlineOptions options = new ColorlineOptions();
@@ -3572,19 +3633,21 @@ public class RaceMap extends AbstractCompositeComponent impleme
options.setStrokeWeight(1);
break;
case SELECTED:
- options.setColorMode(ColorlineMode.POLYCHROMATIC);
- options.setColorProvider(fixIndexInTail -> {
- final String resultColor;
- final Double detailValue;
- // If a DetailType has been selected and we are not currently waiting for the first update with the new values
- if (selectedDetailType != null && !selectedDetailTypeChanged && (detailValue = fixesAndTails.getDetailValueAt(competitor, fixIndexInTail)) != null) {
- resultColor = tailColorMapper.getColor(detailValue);
- } else {
- resultColor = competitorSelection.getColor(competitor, raceIdentifier).getAsHtml();
- }
- return resultColor;
- });
options.setStrokeWeight(2);
+ if (selectedDetailType != null && !selectedDetailTypeChanged) {
+ options.setColorMode(ColorlineMode.POLYCHROMATIC);
+ options.setColorProvider(fixIndexInTail -> {
+ final Double detailValue = fixesAndTails.getDetailValueAt(competitor, fixIndexInTail);
+ if (detailValue != null) {
+ return tailColorMapper.getColor(detailValue);
+ }
+ return competitorSelection.getColor(competitor, raceIdentifier).getAsHtml();
+ });
+ } else {
+ options.setColorMode(ColorlineMode.MONOCHROMATIC);
+ final String selectedColor = competitorSelection.getColor(competitor, raceIdentifier).getAsHtml();
+ options.setColorProvider(fixIndexInTail -> selectedColor);
+ }
break;
case NOT_SELECTED:
options.setColorMode(ColorlineMode.MONOCHROMATIC);
From 85e17dccd8bb9709379fff0e95049ffe5bf273a4 Mon Sep 17 00:00:00 2001
From: Axel Uhl
Date: Thu, 19 Mar 2026 11:27:39 +0100
Subject: [PATCH 03/10] bug6214: always suppress tail style update when display
mode unchanged
---
.../shared/racemap/ColorlineOptions.java | 46 +++++++++++++------
.../gwt/ui/client/shared/racemap/RaceMap.java | 32 ++++++-------
2 files changed, 47 insertions(+), 31 deletions(-)
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ColorlineOptions.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ColorlineOptions.java
index 6bea1a7138f..fcd0fc013fe 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ColorlineOptions.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ColorlineOptions.java
@@ -5,24 +5,27 @@ import com.google.gwt.maps.client.overlays.PolylineOptions;
public class ColorlineOptions {
private ColorlineMode colorMode = ColorlineMode.MONOCHROMATIC;
-
+
private boolean clickable = true;
private boolean draggable = false;
private boolean editable = false;
private boolean geodesic = true;
private boolean visible = true;
-
+
private int strokeWeight = 1;
private double strokeOpacity = 1.0;
-
+
private int zIndex = 0;
-
+
private ColorlineColorProvider colorProvider;
-
- public ColorlineOptions() {}
+
+ public ColorlineOptions() {
+ }
+
public ColorlineOptions(ColorlineColorProvider colorProvider) {
this.colorProvider = colorProvider;
}
+
public ColorlineOptions(PolylineOptions options, ColorlineColorProvider colorProvider) {
clickable = options.getClickable();
geodesic = options.getGeodesic();
@@ -32,6 +35,7 @@ public class ColorlineOptions {
zIndex = options.getZindex();
this.colorProvider = colorProvider;
}
+
public ColorlineOptions(ColorlineOptions options) {
colorMode = options.getColorMode();
colorProvider = options.getColorProvider();
@@ -44,21 +48,20 @@ public class ColorlineOptions {
strokeOpacity = options.getStrokeOpacity();
zIndex = options.getZIndex();
}
-
-
+
public Polyline newPolylineInstance(int fixIndexInTail) {
if (colorProvider == null) {
throw new IllegalStateException("A ColorProvider must be set prior to creating new Polylines.");
}
return newPolylineInstance(colorProvider.getColor(fixIndexInTail));
}
-
+
public Polyline newPolylineInstance(String strokeColor) {
Polyline line = Polyline.newInstance(newPolylineOptionsInstance(strokeColor));
line.setEditable(editable);
return line;
}
-
+
public PolylineOptions newPolylineOptionsInstance(String strokeColor) {
PolylineOptions opt = PolylineOptions.newInstance();
opt.setStrokeColor(strokeColor);
@@ -70,66 +73,83 @@ public class ColorlineOptions {
opt.setZindex(zIndex);
return opt;
}
-
+
public ColorlineMode getColorMode() {
return colorMode;
}
+
public void setColorMode(ColorlineMode colorMode) {
this.colorMode = colorMode;
}
-
+
public ColorlineColorProvider getColorProvider() {
return colorProvider;
}
+
public void setColorProvider(ColorlineColorProvider colorProvider) {
this.colorProvider = colorProvider;
}
-
+
public boolean getClickable() {
return clickable;
}
+
public void setClickable(boolean clickable) {
this.clickable = clickable;
}
+
public boolean getDraggable() {
return draggable;
}
+
public void setDraggable(boolean draggable) {
this.draggable = draggable;
}
+
public boolean getEditable() {
return editable;
}
+
public void setEditable(boolean editable) {
this.editable = editable;
}
+
public boolean getGeodesic() {
return geodesic;
}
+
public void setGeodesic(boolean geodesic) {
this.geodesic = geodesic;
}
+
public boolean getVisible() {
return visible;
}
+
public void setVisible(boolean visible) {
this.visible = visible;
}
+
public int getZIndex() {
return zIndex;
}
+
public void setZIndex(int zIndex) {
this.zIndex = zIndex;
}
+
public int getStrokeWeight() {
return strokeWeight;
}
+
public void setStrokeWeight(int strokeWeight) {
this.strokeWeight = strokeWeight;
}
+
public double getStrokeOpacity() {
return strokeOpacity;
}
+
public void setStrokeOpacity(double strokeOpacity) {
this.strokeOpacity = strokeOpacity;
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
index e225985a86a..bb529bb3f9e 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
@@ -2658,22 +2658,22 @@ public class RaceMap extends AbstractCompositeComponent impleme
}
private DisplayMode displayHighlighted(CompetitorDTO competitorDTO) {
- boolean competitorisSelected = competitorSelection.isSelected(competitorDTO);
+ final DisplayMode result;
+ final boolean competitorisSelected = competitorSelection.isSelected(competitorDTO);
if (!settings.isShowOnlySelectedCompetitors()) {
if (competitorisSelected) {
- return DisplayMode.SELECTED;
+ result = DisplayMode.SELECTED;
} else {
if (isSomeOtherCompetitorSelected()) {
- return DisplayMode.NOT_SELECTED;
+ result = DisplayMode.NOT_SELECTED;
} else {
- return DisplayMode.DEFAULT;
+ result = DisplayMode.DEFAULT;
}
}
+ } else {
+ result = competitorSelection.isSelected(competitorDTO) ? DisplayMode.SELECTED : DisplayMode.DEFAULT;
}
- else{
- return competitorSelection.isSelected(competitorDTO) ? DisplayMode.SELECTED : DisplayMode.DEFAULT;
- }
-
+ return result;
}
private boolean isSomeOtherCompetitorSelected(){
@@ -3163,7 +3163,6 @@ public class RaceMap extends AbstractCompositeComponent impleme
@Override
public void execute() {
selectionDependentRefreshScheduled = false;
-
if (selectionDependentRefreshPending) {
selectionDependentRefreshPending = false;
refreshSelectionDependentTailAndMapState();
@@ -3172,6 +3171,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
});
}
}
+
private void refreshSelectionDependentTailAndMapState() {
if (selectedDetailType != null && !selectedDetailTypeChanged) {
// assumes that the detail values have already been loaded, as the detail type hasn't changed
@@ -3182,16 +3182,12 @@ public class RaceMap extends AbstractCompositeComponent impleme
Colorline tail = fixesAndTails.getTail(oneOfAllCompetitors);
if (tail != null) {
final DisplayMode newDisplayMode = displayHighlighted(oneOfAllCompetitors);
- if (selectedDetailType == null) {
- final String competitorId = oneOfAllCompetitors.getIdAsString();
- final DisplayMode previousDisplayMode = lastAppliedTailDisplayModes.get(competitorId);
- if (previousDisplayMode == newDisplayMode) {
- continue;
- }
+ final String competitorId = oneOfAllCompetitors.getIdAsString();
+ if (lastAppliedTailDisplayModes.get(competitorId) != newDisplayMode) {
lastAppliedTailDisplayModes.put(competitorId, newDisplayMode);
+ ColorlineOptions newOptions = createTailStyle(oneOfAllCompetitors, newDisplayMode);
+ tail.setOptions(newOptions);
}
- ColorlineOptions newOptions = createTailStyle(oneOfAllCompetitors, newDisplayMode);
- tail.setOptions(newOptions);
}
}
// Trigger auto-zoom if needed
@@ -3613,7 +3609,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
* {@link ColorlineMode#MONOCHROMATIC}. This avoids unnecessary switching between monochromatic and
* polychromatic rendering, which would force {@code Colorline.setOptions(...)} to rebuild the tail
* polyline structure via {@code setPath(...)} and was identified as a significant performance cost
- * during selection updates.
+ * during selection updates. See also bug 6214.
*
* @param competitor the competitor whose tail is being styled
* @param displayMode the desired display mode for this competitor tail
From a1b20fe18ebdcae4dee99287602147e8aae8218c Mon Sep 17 00:00:00 2001
From: Axel Uhl
Date: Thu, 19 Mar 2026 11:35:16 +0100
Subject: [PATCH 04/10] bug6214: moving tail removal into a method to stay
consistent
---
.../gwt/ui/client/shared/racemap/RaceMap.java | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
index bb529bb3f9e..5f7f89aca9f 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
@@ -2026,12 +2026,17 @@ public class RaceMap extends AbstractCompositeComponent impleme
boatCanvas.removeFromMap();
boatOverlaysByCompetitorIdsAsStrings.remove(unusedBoatCanvasCompetitorDTO);
}
- for (String unusedTailCompetitorDTO : competitorIdsAsStringOfUnusedTails) {
- fixesAndTails.removeTail(unusedTailCompetitorDTO);
+ for (String unusedTailCompetitorIdAsString : competitorIdsAsStringOfUnusedTails) {
+ removeTail(unusedTailCompetitorIdAsString);
}
}
}
}
+
+ private void removeTail(String competitorIdAsString) {
+ fixesAndTails.removeTail(competitorIdAsString);
+ lastAppliedTailDisplayModes.remove(competitorIdAsString);
+ }
/**
* This algorithm is limited to distances such that dlon < pi/2, i.e., those that extend around less than one
@@ -3209,8 +3214,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
if (!next.getKey().equals(competitor.getIdAsString())) {
CanvasOverlayV3 boatOverlay = next.getValue();
boatOverlay.removeFromMap();
- fixesAndTails.removeTail(next.getKey());
- lastAppliedTailDisplayModes.remove(next.getKey());
+ removeTail(next.getKey());
i.remove(); // only this way a ConcurrentModificationException while looping can be avoided
}
}
@@ -3246,8 +3250,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
if (removedBoatOverlay != null) {
removedBoatOverlay.removeFromMap();
}
- fixesAndTails.removeTail(competitor.getIdAsString());
- lastAppliedTailDisplayModes.remove(competitor.getIdAsString());
+ removeTail(competitor.getIdAsString());
showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
}
} else {
From ea493ec86db2a64591e1130a6f542fe58ebfbcde Mon Sep 17 00:00:00 2001
From: "masha.kashirina"
Date: Fri, 20 Mar 2026 19:27:30 +0100
Subject: [PATCH 05/10] bug6214: redraw() is called only when needed
---
.../gwt/ui/client/shared/racemap/RaceMap.java | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
index 5f7f89aca9f..8b983d121df 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
@@ -290,12 +290,13 @@ public class RaceMap extends AbstractCompositeComponent impleme
* instead of running the expensive refresh logic once per competitor.
*/
private boolean selectionDependentRefreshScheduled;
-
+
/**
- * True when at least one selection change occurred and a selection-dependent refresh is still
- * needed. The scheduled command clears this flag after performing the consolidated refresh.
+ * True once a selection-dependent tail/map refresh needs redraw.
+ * For example, we avoid redraw in this situation:
+ * no helper lines, all competitors shown, one is getting deselected.
*/
- private boolean selectionDependentRefreshPending;
+ private boolean selectionDependentRefreshNeedsRedraw;
/**
* Remembers the last tail display mode applied per competitor id. This allows us to skip
@@ -3161,17 +3162,13 @@ public class RaceMap extends AbstractCompositeComponent impleme
* refresh is still pending.
*/
private void scheduleSelectionDependentTailAndMapStateRefresh() {
- selectionDependentRefreshPending = true;
if (!selectionDependentRefreshScheduled) {
selectionDependentRefreshScheduled = true;
Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
@Override
public void execute() {
selectionDependentRefreshScheduled = false;
- if (selectionDependentRefreshPending) {
- selectionDependentRefreshPending = false;
- refreshSelectionDependentTailAndMapState();
- }
+ refreshSelectionDependentTailAndMapState();
}
});
}
@@ -3200,7 +3197,9 @@ public class RaceMap extends AbstractCompositeComponent impleme
if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
zoomMapToNewBounds(zoomSettings.getNewBounds(this));
}
- redraw();
+ if (selectionDependentRefreshNeedsRedraw) {
+ redraw();
+ }
}
@Override
@@ -3237,13 +3236,13 @@ public class RaceMap extends AbstractCompositeComponent impleme
if (isShowAnyHelperLines()) {
// helper lines depend on which competitor is visible, because the *visible* leader is used for
// deciding which helper lines to show:
- //redraw();
+ selectionDependentRefreshNeedsRedraw = true;
} else {
// try a more incremental update otherwise
if (settings.isShowOnlySelectedCompetitors()) {
// if selection is now empty, show all competitors
if (Util.isEmpty(competitorSelection.getSelectedCompetitors())) {
- //redraw();
+ selectionDependentRefreshNeedsRedraw = true;
} else {
// otherwise remove only deselected competitor's boat images and tail
final BoatOverlay removedBoatOverlay = boatOverlaysByCompetitorIdsAsStrings.remove(competitor.getIdAsString());
@@ -3252,6 +3251,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
}
removeTail(competitor.getIdAsString());
showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
+ selectionDependentRefreshNeedsRedraw = false;
}
} else {
// "lowlight" currently selected competitor
@@ -3261,6 +3261,7 @@ public class RaceMap extends AbstractCompositeComponent impleme
boatCanvas.draw();
}
showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
+ selectionDependentRefreshNeedsRedraw = false;
}
}
// Now update tails for all competitors because selection change may also affect all unselected competitors
From 1c3edb89bd08adab3389f4b949beab6a2ba5ae2f Mon Sep 17 00:00:00 2001
From: "masha.kashirina"
Date: Sun, 22 Mar 2026 17:40:21 +0100
Subject: [PATCH 06/10] bug6214: choose RefreshableMultiSelectionModel to be
the one that is handling the table selection and the entity comparison,
remove redunant listener/notifier structure
---
.../gwt/ui/leaderboard/LeaderboardPanel.java | 82 ++-----------------
1 file changed, 5 insertions(+), 77 deletions(-)
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
index 3676f7acf92..9239d77cd4d 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
@@ -29,7 +29,6 @@ import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safecss.shared.SafeStyles;
@@ -331,14 +330,6 @@ public abstract class LeaderboardPanel extends A
*/
private final Handler selectionChangeHandler;
- /**
- * While the {@link #selectionChangeHandler} is registered as a selection change handler on the
- * {@link #leaderboardTable}'s selection model, this field holds the registration which can be used to remove the
- * registration again. We'll use this to temporarily suspend selection events when actively modifying / adjusting
- * the table selection to match the {@link #competitorSelectionProvider}.
- */
- private HandlerRegistration leaderboardAsTableSelectionModelRegistration;
-
private final FlowPanel contentPanel = new FlowPanel();
protected final PaywallResolver paywallResolver;
@@ -524,7 +515,7 @@ public abstract class LeaderboardPanel extends A
leaderboardTable.ensureDebugId("LeaderboardCellTable");
selectionCheckboxColumn = new LeaderboardSelectionCheckboxColumn(competitorSelectionProvider);
leaderboardTable.setWidth("100%");
- leaderboardSelectionModel = new MultiSelectionModel();
+ leaderboardSelectionModel = selectionCheckboxColumn.getSelectionModel();
// remember handler registration so we can temporarily remove it and re-add it to suspend selection events while
// we're actively changing it
selectionChangeHandler = new Handler() {
@@ -542,8 +533,7 @@ public abstract class LeaderboardPanel extends A
}
}
};
- leaderboardAsTableSelectionModelRegistration = leaderboardSelectionModel
- .addSelectionChangeHandler(selectionChangeHandler);
+ leaderboardSelectionModel.addSelectionChangeHandler(selectionChangeHandler);
leaderboardTable.setSelectionModel(leaderboardSelectionModel, selectionCheckboxColumn.getSelectionManager());
SimplePanel mainPanel = new SimplePanel();
@@ -2115,8 +2105,7 @@ public abstract class LeaderboardPanel extends A
}
private class LeaderboardSelectionCheckboxColumn
- extends com.sap.sse.gwt.client.celltable.SelectionCheckboxColumn
- implements CompetitorSelectionChangeListener {
+ extends com.sap.sse.gwt.client.celltable.SelectionCheckboxColumn {
protected LeaderboardSelectionCheckboxColumn(final CompetitorSelectionProvider competitorSelectionProvider) {
super(style.getTableresources().cellTableStyle().cellTableCheckboxSelected(),
style.getTableresources().cellTableStyle().cellTableCheckboxDeselected(),
@@ -2132,49 +2121,13 @@ public abstract class LeaderboardPanel extends A
return t.competitor.getIdAsString().hashCode();
}
}, getData(), leaderboardTable);
- competitorSelectionProvider.addCompetitorSelectionChangeListener(this);
}
@Override
public Boolean getValue(LeaderboardRowDTO row) {
return competitorSelectionProvider.isSelected(row.competitor);
}
-
- @Override
- public void competitorsListChanged(Iterable competitors) {
- }
-
- @Override
- public void filterChanged(FilterSet> oldFilterSet,
- FilterSet> newFilterSet) {
- }
-
- @Override
- public void filteredCompetitorsListChanged(Iterable filteredCompetitors) {
- }
-
- /**
- * Ensure that the checkbox is redrawn when the competitor selection changes
- */
- @Override
- public void addedToSelection(CompetitorDTO competitor) {
- final LeaderboardRowDTO row = getRow(competitor.getIdAsString());
- if (row != null) {
- getSelectionModel().setSelected(row, true);
- }
- }
-
- /**
- * Ensure that the checkbox is redrawn when the competitor selection changes
- */
- @Override
- public void removedFromSelection(CompetitorDTO competitor) {
- final LeaderboardRowDTO row = getRow(competitor.getIdAsString());
- if (row != null) {
- getSelectionModel().setSelected(row, false);
- }
- }
-
+
@Override
public void updateMinMax() {
}
@@ -2642,15 +2595,10 @@ public abstract class LeaderboardPanel extends A
}
}
for (Entry updateEntry : rowsToUpdate.entrySet()) {
- LeaderboardRowDTO oldElement = getData().getList().set(updateEntry.getKey(),
- updateEntry.getValue());
- leaderboardSelectionModel.setSelected(oldElement, false); // make sure the old element is no longer
- // part of the selection
- updateSelection(updateEntry.getValue());
+ getData().getList().set(updateEntry.getKey(), updateEntry.getValue());
}
for (LeaderboardRowDTO rowToAdd : rowsToAdd) {
getData().getList().add(rowToAdd);
- updateSelection(rowToAdd);
}
}
RaceColumn> lastRaceColumn = null;
@@ -2700,26 +2648,6 @@ public abstract class LeaderboardPanel extends A
protected abstract void applyTop30FilterIfCompetitorSizeGreaterEqual40(LeaderboardDTO leaderboard);
- /**
- * Adjusts the row's selection in the {@link #leaderboardSelectionModel} so it matches its selection state in the
- * {@link #competitorSelectionProvider}.
- */
- private void updateSelection(LeaderboardRowDTO row) {
- final boolean shallBeSelected = competitorSelectionProvider.isSelected(row.competitor);
- if (leaderboardAsTableSelectionModelRegistration != null) {
- // suspend selection events while actively adjusting the leaderboardSelectionModel to match the
- // competitorSelectionProvider
- leaderboardAsTableSelectionModelRegistration.removeHandler();
- leaderboardAsTableSelectionModelRegistration = null;
- }
- if (leaderboardSelectionModel.isSelected(row) != shallBeSelected) {
- leaderboardSelectionModel.setSelected(row, shallBeSelected);
- }
- // register the selection change handler again
- leaderboardAsTableSelectionModelRegistration = leaderboardTable.getSelectionModel()
- .addSelectionChangeHandler(selectionChangeHandler);
- }
-
/**
* The race columns hold a now outdated copy of a {@link RaceColumnDTO} which needs to be updated from the
* {@link LeaderboardDTO} just received
From 1eeacf159b0ad969ac077170070f8568d5180fed Mon Sep 17 00:00:00 2001
From: Axel Uhl
Date: Wed, 25 Mar 2026 22:59:43 +0100
Subject: [PATCH 07/10] bug6214: disabled a SwissTiming competitor import test
temporarily
---
.../manage2sail/resultimport/test/CompetitorImportTest.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java b/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java
index d46a7868a5d..1ee86747077 100644
--- a/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java
+++ b/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java
@@ -15,6 +15,7 @@ import java.util.Arrays;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
@@ -40,6 +41,7 @@ import com.sap.sse.common.Util;
*
*/
public class CompetitorImportTest extends AbstractEventResultJsonServiceTest {
+ @Disabled("ongoing tests at SwissTiming 2026-03-25")
@Test
public void simpleCompetitorImportTest() throws FileNotFoundException, IOException, JAXBException, URISyntaxException, SAXException, ParserConfigurationException {
ResultUrlRegistry resultUrlRegistry = mock(ResultUrlRegistry.class);
From 883e96612e1b1da5c4866119ef021de8edd630ee Mon Sep 17 00:00:00 2001
From: Axel Uhl
Date: Wed, 25 Mar 2026 23:05:21 +0100
Subject: [PATCH 08/10] bug6214: revert disabling of SwissTiming test
---
.../manage2sail/resultimport/test/CompetitorImportTest.java | 2 --
1 file changed, 2 deletions(-)
diff --git a/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java b/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java
index 1ee86747077..d46a7868a5d 100644
--- a/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java
+++ b/java/com.sap.sailing.manage2sail.resultimport.test/src/com/sap/sailing/manage2sail/resultimport/test/CompetitorImportTest.java
@@ -15,7 +15,6 @@ import java.util.Arrays;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
@@ -41,7 +40,6 @@ import com.sap.sse.common.Util;
*
*/
public class CompetitorImportTest extends AbstractEventResultJsonServiceTest {
- @Disabled("ongoing tests at SwissTiming 2026-03-25")
@Test
public void simpleCompetitorImportTest() throws FileNotFoundException, IOException, JAXBException, URISyntaxException, SAXException, ParserConfigurationException {
ResultUrlRegistry resultUrlRegistry = mock(ResultUrlRegistry.class);
From bc3b08f513003166be0e1a5b93a50251e68f1db4 Mon Sep 17 00:00:00 2001
From: Axel Uhl
Date: Wed, 25 Mar 2026 23:02:55 +0100
Subject: [PATCH 09/10] bug6214: don't exclude all tests from
com.sap.sailing.landscape.test
---
java/com.sap.sailing.landscape.test/pom.xml | 32 ---------------------
1 file changed, 32 deletions(-)
diff --git a/java/com.sap.sailing.landscape.test/pom.xml b/java/com.sap.sailing.landscape.test/pom.xml
index d07ebaac1bd..1b5838c3fc2 100755
--- a/java/com.sap.sailing.landscape.test/pom.xml
+++ b/java/com.sap.sailing.landscape.test/pom.xml
@@ -8,37 +8,5 @@
1.0.0-SNAPSHOT
com.sap.sailing.landscape.test
-
- eclipse-plugin
-
- true
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- **/*.java
-
-
-
-
- org.eclipse.tycho
- tycho-surefire-plugin
- ${tycho-version}
-
-
- **/*
-
-
-
-
-
From aeadcc57d3d32060143aed91add10fd010be8dfc Mon Sep 17 00:00:00 2001
From: Axel Uhl
Date: Thu, 26 Mar 2026 09:41:59 +0100
Subject: [PATCH 10/10] bug6214: disable landscape tests requiring tokens
---
.../src/com/sap/sailing/landscape/test/TestProcedures.java | 2 ++
.../com/sap/sailing/landscape/test/TestReleaseRepository.java | 2 ++
2 files changed, 4 insertions(+)
diff --git a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java
index 17fe84cb646..e0d7b2e0edd 100755
--- a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java
+++ b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java
@@ -19,6 +19,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import com.jcraft.jsch.JSchException;
@@ -76,6 +77,7 @@ import software.amazon.awssdk.services.route53.model.RRType;
* @author Axel Uhl (D043530)
*
*/
+@Disabled("This requires an AWS token in the launch configuration")
public class TestProcedures {
private static final Logger logger = Logger.getLogger(TestProcedures.class.getName());
private static final Optional optionalTimeout = Optional.of(Duration.ONE_MINUTE.times(10));
diff --git a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestReleaseRepository.java b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestReleaseRepository.java
index 38fab1e16be..1e7704c137b 100755
--- a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestReleaseRepository.java
+++ b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestReleaseRepository.java
@@ -3,11 +3,13 @@ package com.sap.sailing.landscape.test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import com.sap.sailing.landscape.SailingReleaseRepository;
import com.sap.sse.common.Util;
+@Disabled("Don't go against our Github rate limit")
public class TestReleaseRepository {
@Test
public void testForAtLeastOneRelease() {