From f62c09f821df41dc1559ab1718bb6755963d4e8a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 1 Dec 2015 21:39:21 +0100 Subject: [PATCH 01/13] working towards reducing the number of GetRaceMapData calls; however, this version is INCORRECT; see TODO comments --- .../gwt/ui/actions/GetRaceMapDataAction.java | 2 - .../client/shared/racemap/FixesAndTails.java | 13 +++-- .../gwt/ui/client/shared/racemap/RaceMap.java | 47 +++++++++++++------ 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java index ac35428be2a..fd6895658cf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java @@ -42,12 +42,10 @@ public class GetRaceMapDataAction implements AsyncAction { for (Map.Entry fromEntry : from.entrySet()) { fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue()); } - Map toByCompetitorIdAsString = new HashMap(); for (Map.Entry toEntry : to.entrySet()) { toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue()); } - sailingService.getRaceMapData(raceIdentifier, date, fromByCompetitorIdAsString, toByCompetitorIdAsString, extrapolate, simulationLegIdentifier, new AsyncCallback() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index 492a17635cb..626f0d60702 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -178,18 +179,23 @@ public class FixesAndTails { * overlap with those already known, the fixes are merged into the list of already known fixes for the competitor. * Otherwise, the fixes received in result replace those known so far for the respective competitor. * The {@link #tails} affected by these fixes are updated accordingly when modifications fall inside the interval - * shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails are, however, - * not trimmed according to the specification for the tail length. This has to happen elsewhere (see also + * shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails are, however, not + * trimmed according to the specification for the tail length. This has to happen elsewhere (see also * {@link #updateTail}). * * @param fixesForCompetitors * For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be * the last one in the list + * + * @return the competitors for which new fixes have been added; this will not include competitors appearing as keys + * in foxesForCompetitor but with a null or empty fixes list */ - protected void updateFixes(Map> fixesForCompetitors, + protected Iterable updateFixes(Map> fixesForCompetitors, Map overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { + final Set competitorsWithNewFixes = new HashSet<>(); for (Map.Entry> e : fixesForCompetitors.entrySet()) { if (e.getValue() != null && !e.getValue().isEmpty()) { + competitorsWithNewFixes.add(e.getKey()); List fixesForCompetitor = fixes.get(e.getKey()); if (fixesForCompetitor == null) { fixesForCompetitor = new ArrayList(); @@ -212,6 +218,7 @@ public class FixesAndTails { } } } + return competitorsWithNewFixes; } /** 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 b4657f0d566..ae3900b60d1 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 @@ -772,18 +772,25 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe // For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the // boat positions at newTime with zero tail length; and another one fetching everything else. + // TODO bug3378: two calls that both request simulation and mark data wastes bandwidth and CPU; one of them should only use getBoatPositions to request missing pieces of tails; showBoatsOnMap must not remove canvases / tails if one of the two calls delivers only partial results GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); + final boolean updateMarksAndLinesExceptAdvantageLineInCallback; if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) { asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); + getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID, + /* updateMarksAndLinesExceptAdvantageLineInCallback */ true)); + updateMarksAndLinesExceptAdvantageLineInCallback = false; // don't do this again in the second call that mainly fetches requestID = ++boatPositionRequestIDCounter; + } else { + updateMarksAndLinesExceptAdvantageLineInCallback = true; } // next, do the full thing; being the later call, if request throttling kicks in, the later call // supersedes the earlier call which may get dropped then GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); + getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID, + updateMarksAndLinesExceptAdvantageLineInCallback)); // draw the wind into the map, get the combined wind List windSourceTypeNames = new ArrayList(); @@ -868,11 +875,18 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return result; } + /** + * @param updateMarksAndLinesExceptAdvantageLineInCallback + * when true, mark positions, mark-related lines such as start line and course middle lines, + * three hull length circles and side lines are drawn; otherwise, only the boats, their info flags and + * the advantage line will be updated. + */ private AsyncCallback getRaceMapDataCallback( final Date oldTime, final Date newTime, final Map hasTailOverlapForCompetitor, - final Iterable competitorsToShow, final int requestID) { + final Iterable competitorsToShow, final int requestID, + final boolean updateMarksAndLinesExceptAdvantageLineInCallback) { return new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -895,9 +909,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe // Do boat specific actions Map> boatData = raceMapDataDTO.boatPositions; long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime); - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); - showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow()); - showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors()); + Iterable competitorsForWhichNewFixesWereReveiced = + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); + if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { + showBoatsOnMap(newTime, timeForPositionTransitionMillis, competitorsForWhichNewFixesWereReveiced); + showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors()); + // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to + // adjust to new boat positions + showAdvantageLine(competitorsToShow, newTime); + } if (douglasMarkers != null) { removeAllMarkDouglasPeuckerpoints(); } @@ -905,17 +925,16 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe removeAllManeuverMarkers(); } - // Do mark specific actions - showCourseMarksOnMap(raceMapDataDTO.coursePositions); if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) { updateCoordinateSystemFromSettings(); } - showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); - showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions); - showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); - // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to - // adjust to new boat positions - showAdvantageLine(competitorsToShow, newTime); + if (updateMarksAndLinesExceptAdvantageLineInCallback) { + // Do mark specific actions + showCourseMarksOnMap(raceMapDataDTO.coursePositions); + showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); + showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions); + showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); + } // Rezoom the map LatLngBounds zoomToBounds = null; From 445303f5c935d24cafb8105e979cf5bc14544952 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 11 Dec 2015 16:40:04 +0100 Subject: [PATCH 02/13] fixing merge issues Change-Id: I4ce8b348313004ecc8a74e668eacad07a5f30d9b --- .../sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 a7c50dc71a9..89e2044b94e 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 @@ -918,10 +918,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe Map> boatData = raceMapDataDTO.boatPositions; fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); Iterable competitorsForWhichNewFixesWereReveiced = - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { - showBoatsOnMap(newTime, timeForPositionTransitionMillis, competitorsForWhichNewFixesWereReveiced); - showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors()); + showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced); + showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to // adjust to new boat positions showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); From b53112afc5c87b4b50cdcfd10d2bfbef91a24805 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Dec 2015 09:32:36 +0100 Subject: [PATCH 03/13] fixed typo in comment Change-Id: I11625c56daddedd4136a4026dca6920571139fb5 --- .../sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index 626f0d60702..a169f5c8288 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -188,7 +188,7 @@ public class FixesAndTails { * the last one in the list * * @return the competitors for which new fixes have been added; this will not include competitors appearing as keys - * in foxesForCompetitor but with a null or empty fixes list + * in fixesForCompetitor whose fixes list is null or empty */ protected Iterable updateFixes(Map> fixesForCompetitors, Map overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { From 1ea90fcdf0ff272880424831efb7d2b09ec029f1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Dec 2015 09:34:37 +0100 Subject: [PATCH 04/13] Javadoc and TODO adjustments Change-Id: I02f59c561115a720239e31ec6461d6f3a1578d00 --- .../sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 89e2044b94e..7898e204279 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 @@ -781,7 +781,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the // boat positions at newTime with zero tail length; and another one fetching everything else. // TODO bug3378: two calls that both request simulation and mark data wastes bandwidth and CPU; one of them should only use getBoatPositions to request missing pieces of tails; showBoatsOnMap must not remove canvases / tails if one of the two calls delivers only partial results - GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); + final GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); final boolean updateMarksAndLinesExceptAdvantageLineInCallback; if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) { asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, @@ -853,13 +853,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe /** * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the - * first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some + * first request when no fix at all is known for the competitor yet, or when the user has radically moved the time slider to some * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full * load of the entire tail data for that competitor.

* * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and * mark positions for the zero-length interval at newTime, assuming that this will work fairly fast and in particular in * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors. + * + * TODO bug 3378: This shall produce two calls: one complete GetRaceMapDataAction and another fetching of missing "long tails" which only needs to be carried out if the first call does not deliver everything requested by fromAndToAndOverlap */ private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping( Triple, Map, Map> fromAndToAndOverlap, From 68a1c76529501cc70f37baf0b8647aebaa02eb8c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Dec 2015 11:07:41 +0100 Subject: [PATCH 05/13] made members of AsyncActionsExecutor.ExecutionJob final Change-Id: I952ebd310eaf6a58cffa2a5e4c84d9991b0cebfd --- .../sailing/gwt/ui/client/shared/racemap/RaceMap.java | 10 ++++++++++ .../sap/sse/gwt/client/async/AsyncActionsExecutor.java | 6 +++--- .../sap/sse/gwt/client/async/MarkedAsyncCallback.java | 7 +++---- 3 files changed, 16 insertions(+), 7 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 7898e204279..9c82136a5e7 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 @@ -862,6 +862,16 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors. * * TODO bug 3378: This shall produce two calls: one complete GetRaceMapDataAction and another fetching of missing "long tails" which only needs to be carried out if the first call does not deliver everything requested by fromAndToAndOverlap + * The second call of GetRaceMapDataAction is this: + * + * GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, + * useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), + * true, // extrapolate + * (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); + * asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, + * getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, requestID, + * updateMarksAndLinesExceptAdvantageLineInCallback)); + * */ private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping( Triple, Map, Map> fromAndToAndOverlap, diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java index ff024bfda05..6ac2bde5be8 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java @@ -19,9 +19,9 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; */ public class AsyncActionsExecutor { private class ExecutionJob implements AsyncCallback { - private AsyncAction action; - private String category; - private AsyncCallback callback; + private final AsyncAction action; + private final String category; + private final AsyncCallback callback; public ExecutionJob(AsyncAction action, String category, AsyncCallback callback) { this.action = action; diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java index 9313bef647f..c2d04861efd 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java @@ -51,8 +51,7 @@ public final class MarkedAsyncCallback implements AsyncCallback { public MarkedAsyncCallback(AsyncCallback callback, String category) { this.callback = callback; this.category = category; - - if(DebugInfo.isDebugIdEnabled()) { + if (DebugInfo.isDebugIdEnabled()) { PendingAjaxCallMarker.incrementPendingAjaxCalls(this.category); } } @@ -70,7 +69,7 @@ public final class MarkedAsyncCallback implements AsyncCallback { try { this.callback.onFailure(cause); } finally { - if(DebugInfo.isDebugIdEnabled()) { + if (DebugInfo.isDebugIdEnabled()) { PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category); } } @@ -89,7 +88,7 @@ public final class MarkedAsyncCallback implements AsyncCallback { try { this.callback.onSuccess(result); } finally { - if(DebugInfo.isDebugIdEnabled()) { + if (DebugInfo.isDebugIdEnabled()) { PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category); } } From 61d1c2da2e0c6e006bd719eef1a0a0e564f611bd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Dec 2015 13:34:00 +0100 Subject: [PATCH 06/13] compileable but untested refactored version of RaceMap.timeChanged, working towards a solution for bug3378 Change-Id: Idc67121b9c1931e66453def87da8243250b9c9d2 --- .../AbstractGetMapRelatedDataAction.java | 47 +++++ .../ui/actions/GetBoatPositionsAction.java | 43 ++++ .../gwt/ui/actions/GetRaceMapDataAction.java | 30 +-- .../sailing/gwt/ui/client/SailingService.java | 6 + .../gwt/ui/client/SailingServiceAsync.java | 6 + .../gwt/ui/client/shared/racemap/RaceMap.java | 185 +++++++++--------- .../gwt/ui/server/SailingServiceImpl.java | 13 +- .../ui/shared/CompactBoatPositionsDTO.java | 31 +++ .../gwt/ui/shared/CompactRaceMapDataDTO.java | 22 +-- 9 files changed, 251 insertions(+), 132 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/AbstractGetMapRelatedDataAction.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactBoatPositionsDTO.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/AbstractGetMapRelatedDataAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/AbstractGetMapRelatedDataAction.java new file mode 100644 index 00000000000..c2e4aeab9f8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/AbstractGetMapRelatedDataAction.java @@ -0,0 +1,47 @@ +package com.sap.sailing.gwt.ui.actions; + +import java.util.Date; +import java.util.Map; + +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sse.gwt.client.async.AsyncAction; + +public abstract class AbstractGetMapRelatedDataAction implements AsyncAction { + private final SailingServiceAsync sailingService; + private final RegattaAndRaceIdentifier raceIdentifier; + private final Map from; + private final Map to; + private final boolean extrapolate; + + public AbstractGetMapRelatedDataAction(SailingServiceAsync sailingService, + RegattaAndRaceIdentifier raceIdentifier, Map from, + Map to, boolean extrapolate) { + this.sailingService = sailingService; + this.raceIdentifier = raceIdentifier; + this.from = from; + this.to = to; + this.extrapolate = extrapolate; + } + + protected SailingServiceAsync getSailingService() { + return sailingService; + } + + protected RegattaAndRaceIdentifier getRaceIdentifier() { + return raceIdentifier; + } + + protected Map getFrom() { + return from; + } + + protected Map getTo() { + return to; + } + + protected boolean isExtrapolate() { + return extrapolate; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java new file mode 100644 index 00000000000..74bc77702d4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java @@ -0,0 +1,43 @@ +package com.sap.sailing.gwt.ui.actions; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; + +public class GetBoatPositionsAction extends AbstractGetMapRelatedDataAction { + public GetBoatPositionsAction(SailingServiceAsync sailingService, + RegattaAndRaceIdentifier raceIdentifier, Map from, + Map to, boolean extrapolate) { + super(sailingService, raceIdentifier, from, to, extrapolate); + } + + @Override + public void execute(final AsyncCallback callback) { + Map fromByCompetitorIdAsString = new HashMap(); + for (Map.Entry fromEntry : getFrom().entrySet()) { + fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue()); + } + Map toByCompetitorIdAsString = new HashMap(); + for (Map.Entry toEntry : getTo().entrySet()) { + toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue()); + } + getSailingService().getBoatPositions(getRaceIdentifier(), fromByCompetitorIdAsString, toByCompetitorIdAsString, + isExtrapolate(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + callback.onFailure(caught); + } + + @Override + public void onSuccess(CompactBoatPositionsDTO result) { + callback.onSuccess(result); + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java index fd6895658cf..244247e5216 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java @@ -11,43 +11,33 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO; -import com.sap.sse.gwt.client.async.AsyncAction; -public class GetRaceMapDataAction implements AsyncAction { - private final SailingServiceAsync sailingService; - private final Iterable allCompetitors; - private final RegattaAndRaceIdentifier raceIdentifier; - private final Map from; - private final Map to; - private final boolean extrapolate; +public class GetRaceMapDataAction extends AbstractGetMapRelatedDataAction { + private final Map competitorsByIdAsString; private final Date date; private final LegIdentifier simulationLegIdentifier; - public GetRaceMapDataAction(SailingServiceAsync sailingService, Iterable allCompetitors, + public GetRaceMapDataAction(SailingServiceAsync sailingService, Map competitorsByIdAsString, RegattaAndRaceIdentifier raceIdentifier, Date date, Map from, Map to, boolean extrapolate, LegIdentifier simulationLegIdentifier) { - this.allCompetitors = allCompetitors; - this.sailingService = sailingService; - this.raceIdentifier = raceIdentifier; + super(sailingService, raceIdentifier, from, to, extrapolate); + this.competitorsByIdAsString = competitorsByIdAsString; this.date = date; - this.from = from; - this.to = to; - this.extrapolate = extrapolate; this.simulationLegIdentifier = simulationLegIdentifier; } @Override public void execute(final AsyncCallback callback) { Map fromByCompetitorIdAsString = new HashMap(); - for (Map.Entry fromEntry : from.entrySet()) { + for (Map.Entry fromEntry : getFrom().entrySet()) { fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue()); } Map toByCompetitorIdAsString = new HashMap(); - for (Map.Entry toEntry : to.entrySet()) { + for (Map.Entry toEntry : getTo().entrySet()) { toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue()); } - sailingService.getRaceMapData(raceIdentifier, date, fromByCompetitorIdAsString, toByCompetitorIdAsString, - extrapolate, simulationLegIdentifier, new AsyncCallback() { + getSailingService().getRaceMapData(getRaceIdentifier(), date, fromByCompetitorIdAsString, toByCompetitorIdAsString, + isExtrapolate(), simulationLegIdentifier, new AsyncCallback() { @Override public void onFailure(Throwable caught) { callback.onFailure(caught); @@ -55,7 +45,7 @@ public class GetRaceMapDataAction implements AsyncAction { @Override public void onSuccess(CompactRaceMapDataDTO result) { - callback.onSuccess(result.getRaceMapDataDTO(allCompetitors)); + callback.onSuccess(result.getRaceMapDataDTO(competitorsByIdAsString)); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 594417853c8..f2b20fa0ef9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -45,6 +45,7 @@ import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; @@ -158,6 +159,10 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException; + CompactBoatPositionsDTO getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, + Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, + boolean extrapolate) throws NoWindException; + RaceTimesInfoDTO getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier); List getRaceTimesInfos(Collection raceIdentifiers); @@ -612,4 +617,5 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS */ Collection getCompetitorRegistrationsOnRaceLog(String leaderboardName, String raceColumnName, String fleetName); + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index ebcfba02b56..0282609e482 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -37,6 +37,7 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; @@ -368,6 +369,10 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier, AsyncCallback callback); + void getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, Map fromPerCompetitorIdAsString, + Map toPerCompetitorIdAsString, boolean extrapolate, + AsyncCallback callback); + void getReplicaInfo(AsyncCallback callback); void startReplicatingFromMaster(String messagingHost, String masterName, String exchangeName, int servletPort, @@ -753,4 +758,5 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void copyCourseToOtherRaceLogs(Triple fromTriple, Set> toTriples, AsyncCallback callback); + } 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 9c82136a5e7..53241dd1b08 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 @@ -83,6 +83,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; +import com.sap.sailing.gwt.ui.actions.GetBoatPositionsAction; import com.sap.sailing.gwt.ui.actions.GetPolarAction; import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction; import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; @@ -100,6 +101,7 @@ import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO; @@ -776,29 +778,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime); final com.sap.sse.common.Util.Triple, Map, Map> fromAndToAndOverlap = fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); - int requestID = ++boatPositionRequestIDCounter; - // For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) - // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the - // boat positions at newTime with zero tail length; and another one fetching everything else. - // TODO bug3378: two calls that both request simulation and mark data wastes bandwidth and CPU; one of them should only use getBoatPositions to request missing pieces of tails; showBoatsOnMap must not remove canvases / tails if one of the two calls delivers only partial results - final GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); - final boolean updateMarksAndLinesExceptAdvantageLineInCallback; - if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) { - asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, requestID, - /* updateMarksAndLinesExceptAdvantageLineInCallback */ true)); - updateMarksAndLinesExceptAdvantageLineInCallback = false; // don't do this again in the second call that mainly fetches - requestID = ++boatPositionRequestIDCounter; - } else { - updateMarksAndLinesExceptAdvantageLineInCallback = true; - } - // next, do the full thing; being the later call, if request throttling kicks in, the later call - // supersedes the earlier call which may get dropped then - GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, - useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); - asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, requestID, - updateMarksAndLinesExceptAdvantageLineInCallback)); + // Request map data update, possibly in two calls; see method details + callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow); // draw the wind into the map, get the combined wind List windSourceTypeNames = new ArrayList(); @@ -851,63 +832,79 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } /** - * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer - * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the - * first request when no fix at all is known for the competitor yet, or when the user has radically moved the time slider to some - * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full - * load of the entire tail data for that competitor.

+ * Requests updates for map data and, when received, updates the map structures accordingly. + *

* - * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and - * mark positions for the zero-length interval at newTime, assuming that this will work fairly fast and in particular in - * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors. + * The update can happen in one or two round trips. We assume that overlapping segments usually don't require a lot + * of loading time as the most typical case will be to update a longer tail with a few new fixes that were received + * since the last time tick. These supposedly fast requests are handled by a {@link GetRaceMapDataAction} which also + * requests updates for mark positions, sidelines and quick ranks. The same request also loads boat positions for + * the zero-length interval at newTime for the non-overlapping tails assuming that this will work + * fairly fast and in particular in O(1) time regardless of tail length, compared to fetching the entire tail for + * all competitors. This will at least provide quick feedback about those competitors' positions even if loading + * their entire tail may take a little longer. The {@link GetRaceMapDataAction} therefore is intended to be a rather + * quick call. + *

* - * TODO bug 3378: This shall produce two calls: one complete GetRaceMapDataAction and another fetching of missing "long tails" which only needs to be carried out if the first call does not deliver everything requested by fromAndToAndOverlap - * The second call of GetRaceMapDataAction is this: - * - * GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, - * useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), - * true, // extrapolate - * (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); - * asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, - * getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, requestID, - * updateMarksAndLinesExceptAdvantageLineInCallback)); - * + * Non-overlapping position requests typically occur for the first request when no fix at all is known for the + * competitor yet, or when the user has radically moved the time slider to some other time such that given the + * current tail length setting the new tail segment does not overlap with the old one, requiring a full load of the + * entire tail data for that competitor. For these non-overlapping requests, this method creates a + * {@link GetBoatPositionsAction} request loading the entire tail required, but not quick ranks, sidelines and mark + * positions. Updating the results of this call is done in {@link #updateBoatPositions(Date, long, Map, Iterable, Map)}. + *

*/ - private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping( - Triple, Map, Map> fromAndToAndOverlap, - RegattaAndRaceIdentifier race, Date newTime) { - Map fromTimes = new HashMap<>(); - Map toTimes = new HashMap<>(); + private void callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers( + final Triple, Map, Map> fromAndToAndOverlap, + RegattaAndRaceIdentifier race, final Date newTime, final long transitionTimeInMillis, final Iterable competitorsToShow) { + final Map fromTimesForQuickCall = new HashMap<>(); + final Map toTimesForQuickCall = new HashMap<>(); + final Map fromTimesForNonOverlappingTailsCall = new HashMap<>(); + final Map toTimesForNonOverlappingTailsCall = new HashMap<>(); for (Map.Entry e : fromAndToAndOverlap.getC().entrySet()) { - if (!e.getValue()) { - // no overlap; add competitor to request - fromTimes.put(e.getKey(), newTime); - toTimes.put(e.getKey(), newTime); + if (e.getValue()) { + // overlap: expect a quick response; add original request interval for the competitor + fromTimesForQuickCall.put(e.getKey(), fromAndToAndOverlap.getA().get(e.getKey())); + toTimesForQuickCall.put(e.getKey(), fromAndToAndOverlap.getB().get(e.getKey())); + } else { + // no overlap; add competitor to request with a zero-length interval asking only position at newTime, not the entire tail + fromTimesForQuickCall.put(e.getKey(), newTime); + toTimesForQuickCall.put(e.getKey(), newTime); + fromTimesForNonOverlappingTailsCall.put(e.getKey(), fromAndToAndOverlap.getA().get(e.getKey())); + toTimesForNonOverlappingTailsCall.put(e.getKey(), fromAndToAndOverlap.getB().get(e.getKey())); } } - final GetRaceMapDataAction result; - if (!fromTimes.isEmpty()) { - result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), - race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); - } else { - result = null; + final Map competitorsByIdAsString = new HashMap(); + for (CompetitorDTO competitor : competitorSelection.getAllCompetitors()) { + competitorsByIdAsString.put(competitor.getIdAsString(), competitor); } - return result; + + asyncActionsExecutor.execute(new GetRaceMapDataAction(sailingService, competitorsByIdAsString, + race, useNullAsTimePoint() ? null : newTime, fromTimesForQuickCall, toTimesForQuickCall, /* extrapolate */true, + (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)), GET_RACE_MAP_DATA_CATEGORY, + getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter)); + // next, if necessary, do the full thing; the two calls have different action classes, so throttling should not drop one for the other + asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), + /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, + new MarkedAsyncCallback<>(new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); + } + + @Override + public void onSuccess(CompactBoatPositionsDTO result) { + updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, result.getBoatPositionsForCompetitors(competitorsByIdAsString)); + } + })); } - /** - * @param updateMarksAndLinesExceptAdvantageLineInCallback - * when true, mark positions, mark-related lines such as start line and course middle lines, - * three hull length circles and side lines are drawn; otherwise, only the boats, their info flags and - * the advantage line will be updated. - */ private AsyncCallback getRaceMapDataCallback( final Date newTime, final long transitionTimeInMillis, final Map hasTailOverlapForCompetitor, - final Iterable competitorsToShow, final int requestID, - final boolean updateMarksAndLinesExceptAdvantageLineInCallback) { - return new AsyncCallback() { + final Iterable competitorsToShow, final int requestID) { + return new MarkedAsyncCallback<>(new AsyncCallback() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */); @@ -916,28 +913,19 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe @Override public void onSuccess(RaceMapDataDTO raceMapDataDTO) { if (map != null && raceMapDataDTO != null) { - quickRanks = raceMapDataDTO.quickRanks; - competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = - raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber; - if (showViewSimulation && settings.isShowSimulationOverlay()) { - lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber; - simulationOverlay.updateLeg(Math.max(lastLegNumber,1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion); - } // process response only if not received out of order if (startedProcessingRequestID < requestID) { startedProcessingRequestID = requestID; + quickRanks = raceMapDataDTO.quickRanks; + competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = + raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber; + if (showViewSimulation && settings.isShowSimulationOverlay()) { + lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber; + simulationOverlay.updateLeg(Math.max(lastLegNumber, 1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion); + } // Do boat specific actions Map> boatData = raceMapDataDTO.boatPositions; - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); - Iterable competitorsForWhichNewFixesWereReveiced = - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); - if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { - showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced); - showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); - // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to - // adjust to new boat positions - showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); - } + updateBoatPositions(newTime, transitionTimeInMillis, hasTailOverlapForCompetitor, competitorsToShow, boatData); if (douglasMarkers != null) { removeAllMarkDouglasPeuckerpoints(); } @@ -948,13 +936,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) { updateCoordinateSystemFromSettings(); } - if (updateMarksAndLinesExceptAdvantageLineInCallback) { - // Do mark specific actions - showCourseMarksOnMap(raceMapDataDTO.coursePositions); - showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); - showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions); - showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); - } + // Do mark specific actions + showCourseMarksOnMap(raceMapDataDTO.coursePositions); + showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); + showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions); + showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); // Rezoom the map LatLngBounds zoomToBounds = null; @@ -982,7 +968,22 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe lastTimeChangeBeforeInitialization = newTime; } } - }; + }); + } + + private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis, + final Map hasTailOverlapForCompetitor, + final Iterable competitorsToShow, Map> boatData) { + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); + Iterable competitorsForWhichNewFixesWereReveiced = + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); + if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { + showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced); + showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); + // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to + // adjust to new boat positions + showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); + } } private void showCourseSidelinesOnMap(List sidelinesDTOs) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 11cd2e1b1c5..eaad130a029 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -291,6 +291,7 @@ import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO; import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; @@ -1640,7 +1641,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S public CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException { - final Map> boatPositions = getBoatPositions(raceIdentifier, + final Map> boatPositions = getBoatPositionsInternal(raceIdentifier, fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate); final CoursePositionsDTO coursePositions = getCoursePositions(raceIdentifier, date); final List courseSidelines = getCourseSidelines(raceIdentifier, date); @@ -1670,6 +1671,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return competitorBoats; } + + @Override + public CompactBoatPositionsDTO getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, + Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, + boolean extrapolate) throws NoWindException { + return new CompactBoatPositionsDTO(getBoatPositionsInternal(raceIdentifier, fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate)); + } + /** * {@link LegType}s are cached within the method with a resolution of one minute. The cache key is a pair of * {@link TrackedLegOfCompetitor} and {@link TimePoint}. @@ -1688,7 +1697,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S * @return a map where for each competitor participating in the race the list of GPS fixes in increasing * chronological order is provided. The last one is the last position at or before date. */ - private Map> getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, + private Map> getBoatPositionsInternal(RegattaAndRaceIdentifier raceIdentifier, Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, boolean extrapolate) throws NoWindException { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactBoatPositionsDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactBoatPositionsDTO.java new file mode 100644 index 00000000000..6aaab8b0af5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactBoatPositionsDTO.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.dto.CompetitorDTO; + +public class CompactBoatPositionsDTO implements IsSerializable { + private Map> boatPositions; + + CompactBoatPositionsDTO() {} // for GWT serialization only + + public CompactBoatPositionsDTO(Map> boatPositions) { + super(); + this.boatPositions = new HashMap<>(); + for (final Entry> e : boatPositions.entrySet()) { + this.boatPositions.put(e.getKey().getIdAsString(), e.getValue()); + } + } + + public Map> getBoatPositionsForCompetitors(Map competitorsByIdsAsStrings) { + final Map> result = new HashMap<>(); + for (final Entry> e : boatPositions.entrySet()) { + result.put(competitorsByIdsAsStrings.get(e.getKey()), e.getValue()); + } + return result; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java index d62034481db..c2635fa64f4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java @@ -23,7 +23,7 @@ import com.sap.sse.common.Util; * */ public class CompactRaceMapDataDTO implements IsSerializable { - private Map> boatPositionsByCompetitorIdAsString; + private CompactBoatPositionsDTO boatPositionsByCompetitorIdAsString; private CoursePositionsDTO coursePositions; private List courseSidelines; @@ -35,10 +35,7 @@ public class CompactRaceMapDataDTO implements IsSerializable { public CompactRaceMapDataDTO(Map> boatPositions, CoursePositionsDTO coursePositions, List courseSidelines, QuickRanksDTO quickRanks, long simulationResultVersion) { - this.boatPositionsByCompetitorIdAsString = new HashMap>(); - for (Map.Entry> e : boatPositions.entrySet()) { - this.boatPositionsByCompetitorIdAsString.put(e.getKey().getIdAsString(), e.getValue()); - } + this.boatPositionsByCompetitorIdAsString = new CompactBoatPositionsDTO(boatPositions); this.quickRanks = new ArrayList(quickRanks == null ? 0 : Util.size(quickRanks.getQuickRanks())); this.competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber = new LinkedHashMap<>(); Map competitorIdAsStringToOneBasedLegNumber = new HashMap<>(); @@ -59,11 +56,7 @@ public class CompactRaceMapDataDTO implements IsSerializable { this.simulationResultVersion = simulationResultVersion; } - public RaceMapDataDTO getRaceMapDataDTO(Iterable competitors) { - Map competitorsByIdAsString = new HashMap(); - for (CompetitorDTO competitor : competitors) { - competitorsByIdAsString.put(competitor.getIdAsString(), competitor); - } + public RaceMapDataDTO getRaceMapDataDTO(Map competitorsByIdAsString) { RaceMapDataDTO result = new RaceMapDataDTO(); result.quickRanks = new LinkedHashMap(this.quickRanks.size()); for (CompactQuickRankDTO compactQuickRank : this.quickRanks) { @@ -74,14 +67,7 @@ public class CompactRaceMapDataDTO implements IsSerializable { } result.courseSidelines = courseSidelines; result.coursePositions = coursePositions; - result.boatPositions = new HashMap>(); - for (Map.Entry> e : boatPositionsByCompetitorIdAsString.entrySet()) { - final CompetitorDTO competitor = competitorsByIdAsString.get(e.getKey()); - if (competitor != null) { - // maybe null in case the competitor was added, e.g., by unsuppressing, while this call was underway - result.boatPositions.put(competitor, e.getValue()); - } - } + result.boatPositions = boatPositionsByCompetitorIdAsString.getBoatPositionsForCompetitors(competitorsByIdAsString); result.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = new LinkedHashMap<>(); for (Entry i : competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber.entrySet()) { CompetitorDTO c = competitorsByIdAsString.get(i.getKey()); From 9ddf4c7108336d2a020ebdc16a83eefe937d81ec Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Dec 2015 16:39:49 +0000 Subject: [PATCH 07/13] Updated howto (markdown) --- wiki/howto.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wiki/howto.md b/wiki/howto.md index c7b90c10326..0c1490c5158 100644 --- a/wiki/howto.md +++ b/wiki/howto.md @@ -1,17 +1,17 @@ # How to work with this Wiki ### General Information -This Wiki is being stored into a GIT repository on branch master that is tracking remote branch master of origin at `/home/trac/git`. The physical location is `/home/gollum/wiki/gitwiki`. The changes here are pushed to origin on a more or less regular base (every week) by one of the admins. +This Wiki is being stored into a GIT repository on branch master that is tracking remote branch master of origin at `/home/trac/git`. The physical location is `/home/wiki/gitwiki`. The changes here are pushed to origin on a more or less regular base (every week) by one of the admins. -The script `/home/gollum/wiki/serve.sh` can be executed to either start or restart the process being responsible for this wiki. The process is a Ruby webserver (Rack) that uses the handler defined in `/home/gollum/wiki/app.rb`. This handler embeds Gollum wiki software into Rack and also makes sure that only authenticated access is possible (see `/home/gollum/wiki/users.yml`). +The script `/home/wiki/serve.sh` can be executed to either start or restart the process being responsible for this wiki. The process is a Ruby webserver (Rack) that uses the handler defined in `/home/wiki/app.rb`. This handler embeds Gollum wiki software into Rack and also makes sure that only authenticated access is possible (see `/home/wiki/users.yml`). ### Making websites public -Accessing this wiki is normally only allowed for authenticated users. If you want to share your work with others not having an account here then you can just request making a given url publicly available. On the server this is done by simply putting a new line to `/home/gollum/wiki/public.txt`. +Accessing this wiki is normally only allowed for authenticated users. If you want to share your work with others not having an account here then you can just request making a given url publicly available. On the server this is done by simply putting a new line to `/home/wiki/public.txt`. ### Creating a New Account Send a request to Axel Uhl or Simon Marcel Pamies that includes the SHA1 hash of your desired password. Obtain such an SHA1 hash for your password here: http://www.sha1-online.com/. -A Wiki administrator can add a new account by adding a new entry to `/home/gollum/wiki/users.yml`. The password there is encrypted using sha1sum (`echo -n "password" | sha1sum`) or, for those who don't happen to have sha1sum available, using an online SHA1 generator such as [this one](http://www.sha1-online.com/). +A Wiki administrator can add a new account by adding a new entry to `/home/wiki/users.yml`. The password there is encrypted using sha1sum (`echo -n "password" | sha1sum`) or, for those who don't happen to have sha1sum available, using an online SHA1 generator such as [this one](http://www.sha1-online.com/). ### Editing You can edit information here freely but do not leave any nonsense here. From a2076240a39a73963a60155a70788629c741fa87 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 14 Dec 2015 18:20:00 +0100 Subject: [PATCH 08/13] don't remove tails/boats for competitors missing from long tail updates; but this still has severe issues with tail / boat updates Change-Id: Id8fc7a110c13b67a65759da5c15871bbd0bb8edd --- .../client/shared/racemap/FixesAndTails.java | 2 +- .../gwt/ui/client/shared/racemap/RaceMap.java | 53 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index a169f5c8288..fcd439a6dcd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -448,7 +448,7 @@ public class FixesAndTails { * @return a triple whose {@link Triple#getA() first} component contains the "from", and whose {@link Triple#getB() * second} component contains the "to" times for the competitors whose trails / positions to show; the * {@link Triple#getC() third} component tells whether the existing fixes can remain and be augmented by - * those requested or need to be replaced + * those requested (true) or need to be replaced (false) */ protected Util.Triple, Map, Map> computeFromAndTo( Date upTo, Iterable competitorsToShow, long effectiveTailLengthInMilliseconds) { 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 53241dd1b08..7a86f54b0e9 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 @@ -851,7 +851,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe * current tail length setting the new tail segment does not overlap with the old one, requiring a full load of the * entire tail data for that competitor. For these non-overlapping requests, this method creates a * {@link GetBoatPositionsAction} request loading the entire tail required, but not quick ranks, sidelines and mark - * positions. Updating the results of this call is done in {@link #updateBoatPositions(Date, long, Map, Iterable, Map)}. + * positions. Updating the results of this call is done in {@link #updateBoatPositions(Date, long, Map, Iterable, Map, boolean)}. *

*/ private void callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers( @@ -884,7 +884,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)), GET_RACE_MAP_DATA_CATEGORY, getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter)); // next, if necessary, do the full thing; the two calls have different action classes, so throttling should not drop one for the other - asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), + asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall, /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, new MarkedAsyncCallback<>(new AsyncCallback() { @Override @@ -894,7 +894,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe @Override public void onSuccess(CompactBoatPositionsDTO result) { - updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, result.getBoatPositionsForCompetitors(competitorsByIdAsString)); + updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, result.getBoatPositionsForCompetitors(competitorsByIdAsString), + /* updateTailsOnly */ true); } })); } @@ -925,7 +926,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } // Do boat specific actions Map> boatData = raceMapDataDTO.boatPositions; - updateBoatPositions(newTime, transitionTimeInMillis, hasTailOverlapForCompetitor, competitorsToShow, boatData); + updateBoatPositions(newTime, transitionTimeInMillis, hasTailOverlapForCompetitor, competitorsToShow, boatData, /* updateTailsOnly */ false); if (douglasMarkers != null) { removeAllMarkDouglasPeuckerpoints(); } @@ -971,18 +972,29 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe }); } + /** + * @param updateTailsOnly + * if true, only the tails are updated according to boatData and + * hasTailOverlapForCompetitor, but the advantage line is not updated, and neither are the + * competitor info bubbles moved. This assumes that the tips of these tails are loaded in a separate call + * which does update those structures. In particular, tails that do not appear in + * boatData are not removed from the map in case updateTailsOnly is + * true. + */ private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis, final Map hasTailOverlapForCompetitor, - final Iterable competitorsToShow, Map> boatData) { + final Iterable competitorsToShow, Map> boatData, boolean updateTailsOnly) { fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); Iterable competitorsForWhichNewFixesWereReveiced = fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { - showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced); - showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); - // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to - // adjust to new boat positions - showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); + showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced, updateTailsOnly); + if (!updateTailsOnly) { + showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); + // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to + // adjust to new boat positions + showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); + } } } @@ -1187,7 +1199,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return timeForPositionTransitionMillis; } - private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable competitorsToShow) { + /** + * @param updateTailsOnly if false, tails of competitors not in competitorsToShow are removed from the map + */ + private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable competitorsToShow, boolean updateTailsOnly) { if (map != null) { Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); Date tailsToTime = newTime; @@ -1211,13 +1226,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } } - for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) { - BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO); - boatCanvas.removeFromMap(); - boatOverlays.remove(unusedBoatCanvasCompetitorDTO); - } - for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) { - fixesAndTails.removeTail(unusedTailCompetitorDTO); + if (!updateTailsOnly) { + for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) { + BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO); + boatCanvas.removeFromMap(); + boatOverlays.remove(unusedBoatCanvasCompetitorDTO); + } + for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) { + fixesAndTails.removeTail(unusedTailCompetitorDTO); + } } } } From a292d852241bb1333872e14346487d34496c9dea Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Dec 2015 11:57:58 +0100 Subject: [PATCH 09/13] bug3378: fixing display by marking the second of two split calls as overlap=true, avoiding the incorrect removal of fixes/tails for the second update Change-Id: Ib1cdeb7b58455745a572c85fab954cfd0f14c31e --- .../client/shared/racemap/FixesAndTails.java | 48 +++++----- .../ui/client/shared/racemap/Hoverline.java | 6 +- .../gwt/ui/client/shared/racemap/RaceMap.java | 92 ++++++++++++------- 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index fcd439a6dcd..ebbdbae1171 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -174,28 +174,37 @@ public class FixesAndTails { } /** - * Adds the fixes received in result to {@link #fixes} and ensures they are still contiguous for each - * competitor. If overlapsWithKnownFixes indicates that the fixes received in result - * overlap with those already known, the fixes are merged into the list of already known fixes for the competitor. - * Otherwise, the fixes received in result replace those known so far for the respective competitor. - * The {@link #tails} affected by these fixes are updated accordingly when modifications fall inside the interval - * shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails are, however, not - * trimmed according to the specification for the tail length. This has to happen elsewhere (see also - * {@link #updateTail}). + * Adds the fixes received in fixesForCompetitors to {@link #fixes} and ensures they are still + * contiguous for each competitor. If overlapsWithKnownFixes indicates that the fixes received in + * result overlap with those already known, the fixes are merged into the list of already known fixes + * for the competitor. Otherwise, the fixes received in result replace those known so far for the + * respective competitor. The {@link #tails} affected by these fixes are updated accordingly when modifications fall + * inside the interval shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails + * are, however, not trimmed according to the specification for the tail length. This has to happen elsewhere (see + * also {@link #updateTail}). * * @param fixesForCompetitors * For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be * the last one in the list - * - * @return the competitors for which new fixes have been added; this will not include competitors appearing as keys - * in fixesForCompetitor whose fixes list is null or empty + * @param overlapsWithKnownFixes + * if for a competitor whose fixes are provided in fixesForCompetitors this holds + * false, any fixes previously stored for that competitor are removed, and the tail is + * deleted from the map (see {@link #removeTail(CompetitorDTO)}); the new fixes are then added to the + * {@link #fixes} map, and a new tail will have to be constructed as needed (does not happen here). If + * this map holds true, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the + * new fixes from fixesForCompetitors into the {@link #fixes} collection, and the tail is + * left unchanged. NOTE: When a non-overlapping set of fixes is updated (false), this + * map's record for the competitor is UPDATED to true after the tail deletion and + * {@link #fixes} replacement has taken place. This helps in cases where this update is only one of two + * into which an original request was split (one quick update of the tail's head and another one for the + * longer tail itself), such that the second request that uses the same map will be considered + * having an overlap now, not leading to a replacement of the previous update originating from the same + * request. */ - protected Iterable updateFixes(Map> fixesForCompetitors, + protected void updateFixes(Map> fixesForCompetitors, Map overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { - final Set competitorsWithNewFixes = new HashSet<>(); for (Map.Entry> e : fixesForCompetitors.entrySet()) { if (e.getValue() != null && !e.getValue().isEmpty()) { - competitorsWithNewFixes.add(e.getKey()); List fixesForCompetitor = fixes.get(e.getKey()); if (fixesForCompetitor == null) { fixesForCompetitor = new ArrayList(); @@ -206,19 +215,14 @@ public class FixesAndTails { fixesForCompetitor.clear(); // to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove // all points from the competitor's polyline and clear the entries in firstShownFix and lastShownFix - if (tails.containsKey(e.getKey())) { - Polyline removedTail = tails.remove(e.getKey()); - removedTail.setMap(null); - } - firstShownFix.remove(e.getKey()); - lastShownFix.remove(e.getKey()); + removeTail(e.getKey()); fixesForCompetitor.addAll(e.getValue()); + overlapsWithKnownFixes.put(e.getKey(), true); // In case this was only one part of a split request, the next request *does* have an overlap } else { mergeFixes(e.getKey(), e.getValue(), timeForPositionTransitionMillis); } } } - return competitorsWithNewFixes; } /** @@ -483,7 +487,7 @@ public class FixesAndTails { toDate = upTo; } // only request something for the competitor if we're missing information at all - if (fromDate.before(toDate) || fromDate.equals(toDate)) { + if (!fromDate.after(toDate)) { from.put(competitor, fromDate); to.put(competitor, toDate); overlapWithKnownFixes.put(competitor, overlap); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java index ffb6f83106f..d6287d5192c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java @@ -23,7 +23,11 @@ public class Hoverline { this.options.setMap(polyline.getMap()); this.options.setPath(polyline.getPath()); this.options.setStrokeColor(polylineOptions.getStrokeColor()); - this.options.setZindex(polylineOptions.getZindex()); + try { + this.options.setZindex(polylineOptions.getZindex()); + } catch (Exception e) { + // the Z-index of polylineOptions most likely was undefined and therefore cannot be copied (GWT DevMode problem, mostly) + } this.hoverline = Polyline.newInstance(this.options); this.hoverline.setVisible(false); polyline.addMouseOverHandler(new MouseOverMapHandler() { 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 7a86f54b0e9..e0c21ec2231 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 @@ -773,14 +773,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (selectedRaces != null && !selectedRaces.isEmpty()) { RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1); final Iterable competitorsToShow = getCompetitorsToShow(); - if (race != null) { final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime); final com.sap.sse.common.Util.Triple, Map, Map> fromAndToAndOverlap = fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); // Request map data update, possibly in two calls; see method details callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow); - // draw the wind into the map, get the combined wind List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); @@ -879,25 +877,37 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe competitorsByIdAsString.put(competitor.getIdAsString(), competitor); } + // only update the tails for these competitors + // Note: the fromAndToAndOverlap.getC() map will be UPDATED by the call to updateBoatPositions happening inside + // the callback provided by getRaceMapDataCallback(...) for those + // entries that are considered not overlapping; subsequently, fromAndToOverlap.getC() will contain true for + // all its entries so that the other response received for GetBoatPositionsAction will consider this an + // overlap if it happens after this update. asyncActionsExecutor.execute(new GetRaceMapDataAction(sailingService, competitorsByIdAsString, race, useNullAsTimePoint() ? null : newTime, fromTimesForQuickCall, toTimesForQuickCall, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)), GET_RACE_MAP_DATA_CATEGORY, getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter)); // next, if necessary, do the full thing; the two calls have different action classes, so throttling should not drop one for the other - asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall, - /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, - new MarkedAsyncCallback<>(new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); - } - - @Override - public void onSuccess(CompactBoatPositionsDTO result) { - updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, result.getBoatPositionsForCompetitors(competitorsByIdAsString), - /* updateTailsOnly */ true); - } - })); + if (!fromTimesForNonOverlappingTailsCall.keySet().isEmpty()) { + asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall, + /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, + new MarkedAsyncCallback<>(new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); + } + + @Override + public void onSuccess(CompactBoatPositionsDTO result) { + // Note: the fromAndToAndOverlap.getC() map will be UPDATED by the call to updateBoatPositions for those + // entries that are considered not overlapping; subsequently, fromAndToOverlap.getC() will contain true for + // all its entries so that the other response received for GetRaceMapDataAction will consider this an + // overlap if it happens after this update. + updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, + result.getBoatPositionsForCompetitors(competitorsByIdAsString), /* updateTailsOnly */ true); + } + })); + } } private AsyncCallback getRaceMapDataCallback( @@ -973,6 +983,20 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } /** + * @param hasTailOverlapForCompetitor + * if for a competitor whose fixes are provided in fixesForCompetitors this holds + * false, any fixes previously stored for that competitor are removed, and the tail is + * deleted from the map (see {@link #removeTail(CompetitorDTO)}); the new fixes are then added to the + * {@link #fixes} map, and a new tail will have to be constructed as needed (does not happen here). If + * this map holds true, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the + * new fixes from fixesForCompetitors into the {@link #fixes} collection, and the tail is + * left unchanged. NOTE: When a non-overlapping set of fixes is updated (false), this + * map's record for the competitor is UPDATED to true after the tail deletion and + * {@link #fixes} replacement has taken place. This helps in cases where this update is only one of two + * into which an original request was split (one quick update of the tail's head and another one for the + * longer tail itself), such that the second request that uses the same map will be considered + * having an overlap now, not leading to a replacement of the previous update originating from the same + * request. See also {@link FixesAndTails#updateFixes(Map, Map, TailFactory, long)}. * @param updateTailsOnly * if true, only the tails are updated according to boatData and * hasTailOverlapForCompetitor, but the advantage line is not updated, and neither are the @@ -985,16 +1009,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe final Map hasTailOverlapForCompetitor, final Iterable competitorsToShow, Map> boatData, boolean updateTailsOnly) { fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); - Iterable competitorsForWhichNewFixesWereReveiced = - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); - if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { - showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced, updateTailsOnly); - if (!updateTailsOnly) { - showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); - // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to - // adjust to new boat positions - showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); - } + showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly); + if (!updateTailsOnly) { + showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); + // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to + // adjust to new boat positions + showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); } } @@ -1206,8 +1226,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (map != null) { Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); Date tailsToTime = newTime; - Set competitorDTOsOfUnusedTails = new HashSet(fixesAndTails.getCompetitorsWithTails()); - Set competitorDTOsOfUnusedBoatCanvases = new HashSet(boatOverlays.keySet()); + Set competitorDTOsOfUnusedTails = new HashSet(); + Set competitorDTOsOfUnusedBoatCanvases = new HashSet(); + if (!updateTailsOnly) { + competitorDTOsOfUnusedTails.addAll(fixesAndTails.getCompetitorsWithTails()); + competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet()); + } for (CompetitorDTO competitorDTO : competitorsToShow) { if (fixesAndTails.hasFixesFor(competitorDTO)) { Polyline tail = fixesAndTails.getTail(competitorDTO); @@ -1216,12 +1240,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } else { fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime, (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2)); - competitorDTOsOfUnusedTails.remove(competitorDTO); + if (!updateTailsOnly) { + competitorDTOsOfUnusedTails.remove(competitorDTO); + } PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO)); tail.setOptions(newOptions); } boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis); - if (usedExistingBoatCanvas) { + if (usedExistingBoatCanvas && !updateTailsOnly) { competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO); } } @@ -1379,8 +1405,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe nextPath.push(advantageLinePos1); nextPath.push(advantageLinePos2); advantageTimer.setNextPositionAndTransitionMillis(nextPath, timeForPositionTransitionMillis); - advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); - advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + if (advantageLineMouseOverHandler != null) { + advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); + advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + } } drawAdvantageLine = true; } From b919cdbcd6b5259f4caf53170029438a269bcbc1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Dec 2015 11:59:29 +0100 Subject: [PATCH 10/13] consolidated imports Change-Id: Ief644074cfe442657accc19a7d9f1825e7de7ad0 --- .../sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index ebbdbae1171..d26b430ae65 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -5,7 +5,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; From 1bb52ece474d5092638410a703814691fba5f97b Mon Sep 17 00:00:00 2001 From: Jonas Dann Date: Tue, 15 Dec 2015 14:16:28 +0100 Subject: [PATCH 11/13] added comment in Hoverline regarding the zindex --- .../com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java index ffb6f83106f..73a0e4c761a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java @@ -23,7 +23,7 @@ public class Hoverline { this.options.setMap(polyline.getMap()); this.options.setPath(polyline.getPath()); this.options.setStrokeColor(polylineOptions.getStrokeColor()); - this.options.setZindex(polylineOptions.getZindex()); + this.options.setZindex(polylineOptions.getZindex()); // if the zindex is not set, this line throws an exception in dev mode this.hoverline = Polyline.newInstance(this.options); this.hoverline.setVisible(false); polyline.addMouseOverHandler(new MouseOverMapHandler() { From c4e63f2a1df3776f4906d86ac2e152e59481b75c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Dec 2015 16:37:28 +0100 Subject: [PATCH 12/13] fixed an issue with interpolated fixes shipped to client but not marked as such (bug3378) Change-Id: I618bb8956c1a7f711ee4465c72e034f89d68e3a7 --- .../sailing/gwt/ui/client/shared/racemap/RaceMap.java | 2 ++ .../sap/sailing/gwt/ui/server/SailingServiceImpl.java | 9 +++++++-- 2 files changed, 9 insertions(+), 2 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 e0c21ec2231..16c266d0bfc 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 @@ -2079,6 +2079,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return vPanel; } + // FIXME bug3378: only fetch those competitors relevant to the race displayed by this RaceMap; see http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3378#c20 + // could be a method on CompetitorSelectionProvider, parameterized by a RaceIdentifier, and if nothing is known about the race association, all competitors are returned as a safe default private Iterable getCompetitorsToShow() { Iterable result; Iterable selection = competitorSelection.getSelectedCompetitors(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index eaad130a029..18e68e5b736 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1733,6 +1733,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } finally { track.unlockAfterRead(); } + final Set extrapolatedFixes; if (fixes.isEmpty()) { // then there was no (smoothened) fix between fromTimePoint and toTimePointExcluding; estimate... TimePoint middle = new MillisecondsTimePoint((toTimePointExcluding.asMillis()+fromTimePoint.asMillis())/2); @@ -1744,7 +1745,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S logger.finest(""+competitor.getName()+": " + estimatedFix+" (estimated)"); } fixes.add(estimatedFix); + extrapolatedFixes = Collections.singleton(estimatedFix); + } else { + extrapolatedFixes = Collections.emptySet(); } + } else { + extrapolatedFixes = Collections.emptySet(); } Iterator fixIter = fixes.iterator(); if (fixIter.hasNext()) { @@ -1774,8 +1780,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S legType = null; } WindDTO windDTO = wind == null ? null : createWindDTOFromAlreadyAveraged(wind, toTimePointExcluding); - GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */ - false); + GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */ extrapolatedFixes.contains(fix)); fixesForCompetitor.add(fixDTO); if (fixIter.hasNext()) { fix = fixIter.next(); From bb176b67904a6025a4aeba1b6258f07c15e4fd7d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 15 Dec 2015 18:25:08 +0100 Subject: [PATCH 13/13] fixed the flickering issue with tail update, bug 3378 Change-Id: Ib52197191537d1dc8de1c704b6b23d9b2f3e1ff3 --- .../client/shared/racemap/FixesAndTails.java | 65 +++++++++++++++---- .../gwt/ui/client/shared/racemap/RaceMap.java | 19 ++++-- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index d26b430ae65..485bdb1062c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Set; import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.mvc.MVCArray; import com.google.gwt.maps.client.overlays.Polyline; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; @@ -47,22 +48,23 @@ public class FixesAndTails { private final Map> fixes; /** - * Tails of competitors currently displayed as overlays on the map. + * Tails of competitors currently displayed as overlays on the map. A tail may have an {@link MVCArray#getLength() + * empty} {@link Polyline#getPath()}. In this case, {@link #firstShownFix} and {@link #lastShownFix} will hold + * -1 for that competitor key. */ private final Map tails; - /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the first fix shown + * Key set is equal to that of {@link #tails} and tells what the index in {@link #fixes} of the first fix shown * in {@link #tails} is. If a key is contained in this map, it is also contained in {@link #lastShownFix} and vice - * versa. + * versa. If a tail is present but has an empty path, this map contains -1 for that competitor. */ private final Map firstShownFix; /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the last fix shown in + * Key set is equal to that of {@link #tails} and tells what the index in {@link #fixes} of the last fix shown in * {@link #tails} is. If a key is contained in this map, it is also contained in {@link #firstShownFix} and vice - * versa. + * versa. If a tail is present but has an empty path, this map contains -1 for that competitor. */ private final Map lastShownFix; @@ -129,7 +131,9 @@ public class FixesAndTails { * Creates a polyline for the competitor represented by competitorDTO, taking the fixes from * {@link #fixes fixes.get(competitorDTO)} and using the fixes starting at time point from (inclusive) * up to the last fix with time point before to. The polyline is returned. Updates are applied to - * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}. + * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}.

+ * + * Precondition: tails.containsKey(competitorDTO) == false */ protected Polyline createTailAndUpdateIndices(final CompetitorDTO competitorDTO, Date from, Date to, TailFactory tailFactory) { List points = new ArrayList(); @@ -199,10 +203,19 @@ public class FixesAndTails { * longer tail itself), such that the second request that uses the same map will be considered * having an overlap now, not leading to a replacement of the previous update originating from the same * request. + * @return + * + * @return a {@link Runnable} for each competitor that the caller must invoke before starting to create or update + * the key competitor's tail. The object returned may be a no-op, but it may also clear an existing tail if + * necessary. The reason why this is not executed immediately is that there may be a transition-based delay + * with which the tails are updated. In this case we would be producing severe flicker. By delaying the + * command to the point when the update is applied should avoid such flicker. */ - protected void updateFixes(Map> fixesForCompetitors, + protected Map updateFixes(Map> fixesForCompetitors, Map overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { - for (Map.Entry> e : fixesForCompetitors.entrySet()) { + final Map result = new HashMap<>(); + final Runnable noOp = new Runnable() { @Override public void run() {} }; + for (final Map.Entry> e : fixesForCompetitors.entrySet()) { if (e.getValue() != null && !e.getValue().isEmpty()) { List fixesForCompetitor = fixes.get(e.getKey()); if (fixesForCompetitor == null) { @@ -214,14 +227,18 @@ public class FixesAndTails { fixesForCompetitor.clear(); // to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove // all points from the competitor's polyline and clear the entries in firstShownFix and lastShownFix - removeTail(e.getKey()); + result.put(e.getKey(), new Runnable() { @Override public void run() { clearTail(e.getKey()); } }); fixesForCompetitor.addAll(e.getValue()); overlapsWithKnownFixes.put(e.getKey(), true); // In case this was only one part of a split request, the next request *does* have an overlap } else { mergeFixes(e.getKey(), e.getValue(), timeForPositionTransitionMillis); + result.put(e.getKey(), noOp); } + } else { + result.put(e.getKey(), noOp); } } + return result; } /** @@ -299,6 +316,7 @@ public class FixesAndTails { } else { intoThis.add(intoThisIndex, mergeThisFix); if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) { + // fix inserted at a position currently visualized by tail tail.getPath().insertAt(intoThisIndex - indexOfFirstShownFix, coordinateSystem.toLatLng(mergeThisFix.position)); } if (intoThisIndex < indexOfFirstShownFix) { @@ -364,12 +382,19 @@ public class FixesAndTails { * @param delayForTailChangeInMillis * the time in milliseconds after which to actually draw the tail update, or -1 to perform * the update immediately + * @param tailPreparer + * will be {@link Runnable#run} just before the updates are applied to the tail. In + * particular, if a transition is active, the effect of running this preparer will be held back until the + * transition applies. May be null in which case simply no preparer is run. */ protected void updateTail(final Polyline tail, final CompetitorDTO competitorDTO, - final Date from, final Date to, final int delayForTailChangeInMillis) { + final Date from, final Date to, final int delayForTailChangeInMillis, final Runnable tailPreparer) { Timer delayedOrImmediateExecutor = new Timer() { @Override public void run() { + if (tailPreparer != null) { + tailPreparer.run(); + } int vertexCount = tail.getPath().getLength(); final List fixesForCompetitor = getFixes(competitorDTO); int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix @@ -391,7 +416,7 @@ public class FixesAndTails { tail.getPath().insertAt(0, coordinateSystem.toLatLng(fix.position)); vertexCount++; } - // now adjust the polylines tail: remove excess vertices that are after "to" + // now adjust the polyline's tail: remove excess vertices that are after "to" int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix .get(competitorDTO); while (indexOfLastShownFix != -1 && vertexCount > 0 @@ -410,6 +435,9 @@ public class FixesAndTails { indexOfLastShownFix++; GPSFixDTO fix = fixesForCompetitor.get(indexOfLastShownFix); tail.getPath().insertAt(vertexCount++, coordinateSystem.toLatLng(fix.position)); + if (indexOfFirstShownFix < 0) { // empty tail before? + indexOfFirstShownFix = indexOfLastShownFix; // set to the first vertex inserted into tail + } } firstShownFix.put(competitorDTO, indexOfFirstShownFix); lastShownFix.put(competitorDTO, indexOfLastShownFix); @@ -438,6 +466,19 @@ public class FixesAndTails { firstShownFix.remove(competitor); lastShownFix.remove(competitor); } + + /** + * Leaves the tail on the map and empties its path {@link Polyline#getPath()}. Correspondingly, the + * {@link #firstShownFix} and {@link #lastShownFix} entries for competitor are set to -1. + */ + private void clearTail(CompetitorDTO competitor) { + Polyline tail = tails.get(competitor); + if (tail != null) { + tail.getPath().clear(); + firstShownFix.put(competitor, -1); + lastShownFix.put(competitor, -1); + } + } /** * From {@link #fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the 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 16c266d0bfc..2921e8c8851 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 @@ -1008,8 +1008,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis, final Map hasTailOverlapForCompetitor, final Iterable competitorsToShow, Map> boatData, boolean updateTailsOnly) { - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); - showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly); + final Map tailPreparersPerCompetitor = + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); + showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly, tailPreparersPerCompetitor); if (!updateTailsOnly) { showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to @@ -1220,9 +1221,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } /** - * @param updateTailsOnly if false, tails of competitors not in competitorsToShow are removed from the map + * @param updateTailsOnly + * if false, tails of competitors not in competitorsToShow are removed from the + * map + * @param tailPreparersPerCompetitor + * executed before creating or updating the respective competitor's tail */ - private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable competitorsToShow, boolean updateTailsOnly) { + private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, + final Iterable competitorsToShow, boolean updateTailsOnly, + Map tailPreparersPerCompetitor) { if (map != null) { Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); Date tailsToTime = newTime; @@ -1233,13 +1240,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet()); } for (CompetitorDTO competitorDTO : competitorsToShow) { + Runnable tailPreparer = tailPreparersPerCompetitor.get(competitorDTO); if (fixesAndTails.hasFixesFor(competitorDTO)) { Polyline tail = fixesAndTails.getTail(competitorDTO); if (tail == null) { + tailPreparer.run(); // presumably a no-op, but you never know... tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this); } else { fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime, - (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2)); + (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2), tailPreparer); if (!updateTailsOnly) { competitorDTOsOfUnusedTails.remove(competitorDTO); }