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 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<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;
/**
* 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 <code>-1</code> for that competitor.
*/
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
* 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;
@@ -129,7 +131,9 @@ public class FixesAndTails {
* 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)
* 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) {
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
* 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<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
protected Map<CompetitorDTO, Runnable> updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
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()) {
List<GPSFixDTO> 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 <code>-1</code> to perform
* 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,
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<GPSFixDTO> 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);
@@ -439,6 +467,19 @@ public class FixesAndTails {
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/to times for which to request GPS fixes from the server. No update is performed here to {@link #fixes}. The
@@ -1008,8 +1008,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis,
final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTO>> boatData, boolean updateTailsOnly) {
final Map<CompetitorDTO, Runnable> tailPreparersPerCompetitor =
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis);
showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly);
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 <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) {
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);
}