fixed the flickering issue with tail update, bug 3378

Change-Id: Ib52197191537d1dc8de1c704b6b23d9b2f3e1ff3
This commit is contained in:
Axel Uhl committed 2015-12-15 18:25:08 +01:00
1 parent dceee2da70
commit bb176b6790
2 files changed
+67 -17

No files matched your search

@@ -12,6 +12,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import com.google.gwt.maps.client.base.LatLng; 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.maps.client.overlays.Polyline;
import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -47,22 +48,23 @@ public class FixesAndTails {
private final Map<CompetitorDTO, List<GPSFixDTO>> fixes; private final Map<CompetitorDTO, List<GPSFixDTO>> 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
* <code>-1</code> for that competitor key.
*/ */
private final Map<CompetitorDTO, Polyline> tails; private final Map<CompetitorDTO, Polyline> 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 * 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 <code>-1</code> for that competitor.
*/ */
private final Map<CompetitorDTO, Integer> firstShownFix; private final Map<CompetitorDTO, Integer> 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 * {@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 <code>-1</code> for that competitor.
*/ */
private final Map<CompetitorDTO, Integer> lastShownFix; private final Map<CompetitorDTO, Integer> lastShownFix;
@@ -129,7 +131,9 @@ public class FixesAndTails {
* Creates a polyline for the competitor represented by <code>competitorDTO</code>, taking the fixes from * Creates a polyline for the competitor represented by <code>competitorDTO</code>, taking the fixes from
* {@link #fixes fixes.get(competitorDTO)} and using the fixes starting at time point <code>from</code> (inclusive) * {@link #fixes fixes.get(competitorDTO)} and using the fixes starting at time point <code>from</code> (inclusive)
* up to the last fix with time point before <code>to</code>. The polyline is returned. Updates are applied to * up to the last fix with time point before <code>to</code>. The polyline is returned. Updates are applied to
* {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}. * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}.<p>
*
* Precondition: <code>tails.containsKey(competitorDTO) == false</code>
*/ */
protected Polyline createTailAndUpdateIndices(final CompetitorDTO competitorDTO, Date from, Date to, TailFactory tailFactory) { protected Polyline createTailAndUpdateIndices(final CompetitorDTO competitorDTO, Date from, Date to, TailFactory tailFactory) {
List<LatLng> points = new ArrayList<LatLng>(); List<LatLng> points = new ArrayList<LatLng>();
@@ -199,10 +203,19 @@ public class FixesAndTails {
* longer tail itself), such that the second request that uses the <em>same</em> map will be considered * longer tail itself), such that the second request that uses the <em>same</em> map will be considered
* having an overlap now, not leading to a replacement of the previous update originating from the same * having an overlap now, not leading to a replacement of the previous update originating from the same
* request. * 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<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors, protected Map<CompetitorDTO, Runnable> updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) {
for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) { final Map<CompetitorDTO, Runnable> result = new HashMap<>();
final Runnable noOp = new Runnable() { @Override public void run() {} };
for (final Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()) { if (e.getValue() != null && !e.getValue().isEmpty()) {
List<GPSFixDTO> fixesForCompetitor = fixes.get(e.getKey()); List<GPSFixDTO> fixesForCompetitor = fixes.get(e.getKey());
if (fixesForCompetitor == null) { if (fixesForCompetitor == null) {
@@ -214,14 +227,18 @@ public class FixesAndTails {
fixesForCompetitor.clear(); fixesForCompetitor.clear();
// to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove // 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 // 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()); 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 overlapsWithKnownFixes.put(e.getKey(), true); // In case this was only one part of a split request, the next request *does* have an overlap
} else { } else {
mergeFixes(e.getKey(), e.getValue(), timeForPositionTransitionMillis); 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 { } else {
intoThis.add(intoThisIndex, mergeThisFix); intoThis.add(intoThisIndex, mergeThisFix);
if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) { 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)); tail.getPath().insertAt(intoThisIndex - indexOfFirstShownFix, coordinateSystem.toLatLng(mergeThisFix.position));
} }
if (intoThisIndex < indexOfFirstShownFix) { if (intoThisIndex < indexOfFirstShownFix) {
@@ -364,12 +382,19 @@ public class FixesAndTails {
* @param delayForTailChangeInMillis * @param delayForTailChangeInMillis
* the time in milliseconds after which to actually draw the tail update, or <code>-1</code> to perform * the time in milliseconds after which to actually draw the tail update, or <code>-1</code> to perform
* the update immediately * the update immediately
* @param tailPreparer
* will be {@link Runnable#run} just before the updates are applied to the <code>tail</code>. In
* particular, if a transition is active, the effect of running this preparer will be held back until the
* transition applies. May be <code>null</code> in which case simply no preparer is run.
*/ */
protected void updateTail(final Polyline tail, final CompetitorDTO competitorDTO, 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() { Timer delayedOrImmediateExecutor = new Timer() {
@Override @Override
public void run() { public void run() {
if (tailPreparer != null) {
tailPreparer.run();
}
int vertexCount = tail.getPath().getLength(); int vertexCount = tail.getPath().getLength();
final List<GPSFixDTO> fixesForCompetitor = getFixes(competitorDTO); final List<GPSFixDTO> fixesForCompetitor = getFixes(competitorDTO);
int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix
@@ -391,7 +416,7 @@ public class FixesAndTails {
tail.getPath().insertAt(0, coordinateSystem.toLatLng(fix.position)); tail.getPath().insertAt(0, coordinateSystem.toLatLng(fix.position));
vertexCount++; 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 int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix
.get(competitorDTO); .get(competitorDTO);
while (indexOfLastShownFix != -1 && vertexCount > 0 while (indexOfLastShownFix != -1 && vertexCount > 0
@@ -410,6 +435,9 @@ public class FixesAndTails {
indexOfLastShownFix++; indexOfLastShownFix++;
GPSFixDTO fix = fixesForCompetitor.get(indexOfLastShownFix); GPSFixDTO fix = fixesForCompetitor.get(indexOfLastShownFix);
tail.getPath().insertAt(vertexCount++, coordinateSystem.toLatLng(fix.position)); 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); firstShownFix.put(competitorDTO, indexOfFirstShownFix);
lastShownFix.put(competitorDTO, indexOfLastShownFix); lastShownFix.put(competitorDTO, indexOfLastShownFix);
@@ -438,6 +466,19 @@ public class FixesAndTails {
firstShownFix.remove(competitor); firstShownFix.remove(competitor);
lastShownFix.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 <code>competitor</code> are set to <code>-1</code>.
*/
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 * From {@link #fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the
@@ -1008,8 +1008,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis, private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis,
final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor, final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTO>> boatData, boolean updateTailsOnly) { final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTO>> boatData, boolean updateTailsOnly) {
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); final Map<CompetitorDTO, Runnable> tailPreparersPerCompetitor =
showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly); fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis);
showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly, tailPreparersPerCompetitor);
if (!updateTailsOnly) { if (!updateTailsOnly) {
showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); 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 // 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 <code>false</code>, tails of competitors not in <code>competitorsToShow</code> are removed from the map * @param updateTailsOnly
* if <code>false</code>, tails of competitors not in <code>competitorsToShow</code> 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<CompetitorDTO> competitorsToShow, boolean updateTailsOnly) { private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis,
final Iterable<CompetitorDTO> competitorsToShow, boolean updateTailsOnly,
Map<CompetitorDTO, Runnable> tailPreparersPerCompetitor) {
if (map != null) { if (map != null) {
Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds());
Date tailsToTime = newTime; Date tailsToTime = newTime;
@@ -1233,13 +1240,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet()); competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet());
} }
for (CompetitorDTO competitorDTO : competitorsToShow) { for (CompetitorDTO competitorDTO : competitorsToShow) {
Runnable tailPreparer = tailPreparersPerCompetitor.get(competitorDTO);
if (fixesAndTails.hasFixesFor(competitorDTO)) { if (fixesAndTails.hasFixesFor(competitorDTO)) {
Polyline tail = fixesAndTails.getTail(competitorDTO); Polyline tail = fixesAndTails.getTail(competitorDTO);
if (tail == null) { if (tail == null) {
tailPreparer.run(); // presumably a no-op, but you never know...
tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this); tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this);
} else { } else {
fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime, fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime,
(int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2)); (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2), tailPreparer);
if (!updateTailsOnly) { if (!updateTailsOnly) {
competitorDTOsOfUnusedTails.remove(competitorDTO); competitorDTOsOfUnusedTails.remove(competitorDTO);
} }