an untested attempt to re-structure the PlayerModes

Change-Id: I7685f7018899551941f2d6a7b3297687b653fc9d
This commit is contained in:
Axel Uhl committed 2016-07-16 15:31:52 +02:00
1 parent 82d9b3cf63
commit dae3acd2e9
6 files changed
+386 -329

No files matched your search

@@ -1,27 +1,49 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.actions.GetLeaderboardByNameAction;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet.CompetitorsForRaceDefinedListener;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.gwt.client.player.Timer;
/**
* Abstract base class for implementing a {@link PlayerMode}. The {@link #applyTo(RaceBoardPanel)} method
* registers this object as a leaderboard update listener and as a race times info provider listener. The
* default implementations of the corresponding callback methods will simply unregister this object as a
* listener. If subclasses want to stick with this behavior they can call the {@code super} implementation
* in their overrides.
* Abstract base class for implementing a {@link RaceBoardMode}. The {@link #applyTo(RaceBoardPanel)} method registers
* this object as a leaderboard update listener and as a race times info provider listener, also as a
* {@link CompetitorsForRaceDefinedListener} in order to learn when the set of competitors for the race displayed by the
* player is known. The default implementations of the corresponding callback methods will simply unregister this object
* as a listener for the respective callback, enters the knowledge gained in the object's fields and calls the
* {@link #trigger} method each time (abstract in this class). Subclasses can and should do the following:
*
* <ul>
* <li>Redefine specific callback methods in case more processing is required or canceling the listener is desired only
* when certain preconditions are met</li>
* <li>Implement the {@link #trigger} method to check if all information required has been received by now and then use
* it to configure the {@link RaceBoard} and its components accordingly</li>
* </ul>
*
* Note that the {@link #updatedLeaderboard(LeaderboardDTO)} method will typically be called several times in more or
* less unpredictable order, asynchronously. Calling {@link Timer#setTime(long)} will trigger
* {@link #updatedLeaderboard(LeaderboardDTO)} eventually, but in between, other callbacks to the same method
* may occur, triggered by other, automatic adjustments or initialization of the {@link Timer}. To solve this
* problem, subclasses may decide to use a separate {@link GetLeaderboardByNameAction} action and execute
* it using the {@link #getLeaderboardPanel()}.{@link LeaderboardPanel#getExecutor() getExecutor()}. In the
* dedicated callback the subclass can then be sure that the {@link LeaderboardDTO} received matches the
* time previously set on the {@link Timer}.
*
* @author Axel Uhl (d043530)
*
@@ -33,6 +55,13 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI
private LeaderboardPanel leaderboardPanel;
private RaceBoardPanel raceBoardPanel;
private Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo;
private RaceTimesInfoDTO raceTimesInfoForRace;
private LeaderboardDTO leaderboard;
private LeaderboardDTO leaderboardForSpecificTimePoint;
private Iterable<CompetitorDTO> competitorsInRace;
private RaceColumnDTO raceColumn;
@Override
public void applyTo(RaceBoardPanel raceBoardPanel) {
this.raceBoardPanel = raceBoardPanel;
@@ -45,6 +74,12 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI
raceBoardPanel.getMap().addCompetitorsForRaceDefinedListener(this);
}
/**
* Called whenever new information of any sort has become available. Subclasses can use this
* to decide whether everything they need has been received in order to carry out their actions.
*/
protected abstract void trigger();
protected Timer getTimer() {
return timer;
}
@@ -66,13 +101,21 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI
}
/**
* Called after the {@link RaceTimePanel} has reacted to this update. We assume that now the timing for the race has been
* received, and it should be clear by now whether we're talking about a live or a replay race. In case of a replay race
* the timer is set to
* Called after the {@link RaceTimePanel} has reacted to this update. We assume that now the timing for the race has
* been received, and it should be clear by now whether we're talking about a live or a replay race. The
* {@link #raceTimesInfo} field is set to the {@code raceTimesInfo} object received as parameter, the
* {@link #raceTimesInfoForRace} field is set to the race times info specific to the race identified by
* {@link #getRaceIdentifier()}.
*/
@Override
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo,
long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
this.raceTimesInfo = raceTimesInfo;
this.raceTimesInfoForRace = raceTimesInfo.get(getRaceIdentifier());
trigger();
}
protected void stopReceivingRaceTimesInfos() {
raceTimePanel.removeRaceTimesInfoProviderListener(this);
}
@@ -81,16 +124,83 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI
*/
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
this.leaderboard = leaderboard;
trigger();
}
protected void stopReceivingLeaderboard() {
leaderboardPanel.removeLeaderboardUpdateListener(this);
}
/**
* Fetches the leaderboard named {@code leaderboardName} for {@code timePoint} with details for race column
* {@code raceColumnName} and stores it in {@link #leaderboardForSpecificTimePoint}, then invokes {@link #trigger()}.
* If a {@link #getLeaderboard()} has been received already, it is passed on in the call in order to reduce the
* bandwidth required, based on a differential approach.
*/
protected void loadLeaderboardForSpecificTimePoint(String leaderboardName,
String raceColumnName, Date timePoint) {
final AsyncCallback<LeaderboardDTO> callback = new AsyncCallback<LeaderboardDTO>() {
@Override
public void onFailure(Throwable caught) {
GWT.log("Error trying to load leaderboard", caught);
}
@Override
public void onSuccess(LeaderboardDTO result) {
leaderboardForSpecificTimePoint = result;
trigger();
}
};
final ArrayList<String> raceColumnNameAsList = new ArrayList<>();
if (getRaceColumn() != null) {
raceColumnNameAsList.add(getRaceColumn().getName());
}
final GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(
getLeaderboardPanel().getSailingService(), getLeaderboardPanel().getLeaderboard().name,
getTimer().getTime(), raceColumnNameAsList, /* addOverallDetails */ false,
getLeaderboard(), /* fillTotalPointsUncorrected */ false,
/* timerToAdjustOffsetIn */ getTimer(), /* errorReporter */ null, StringMessages.INSTANCE);
getLeaderboardPanel().getExecutor().execute(getLeaderboardByNameAction, LeaderboardPanel.LOAD_LEADERBOARD_DATA_CATEGORY, callback);
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
// nothing to do
this.raceColumn = raceColumn;
trigger();
}
@Override
public void competitorsForRaceDefined(Iterable<CompetitorDTO> competitorsInRace) {
// nothing to do here
this.competitorsInRace = competitorsInRace;
trigger();
}
protected void stopReceivingCompetitorsInRace() {
raceBoardPanel.getMap().removeCompetitorsForRaceDefinedListener(this);
}
protected Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> getRaceTimesInfo() {
return raceTimesInfo;
}
protected RaceTimesInfoDTO getRaceTimesInfoForRace() {
return raceTimesInfoForRace;
}
protected LeaderboardDTO getLeaderboard() {
return leaderboard;
}
protected LeaderboardDTO getLeaderboardForSpecificTimePoint() {
return leaderboardForSpecificTimePoint;
}
protected Iterable<CompetitorDTO> getCompetitorsInRace() {
return competitorsInRace;
}
protected RaceColumnDTO getRaceColumn() {
return raceColumn;
}
}
@@ -1,16 +1,10 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
@@ -22,30 +16,33 @@ import com.sap.sse.gwt.client.player.Timer.PlayStates;
*
*/
public class FullAnalysisMode extends AbstractRaceBoardMode {
private boolean leaderboardSettingsAdjusted;
private boolean timerAdjusted;
/**
* Called after the {@link RaceTimePanel} has reacted to this update. We assume that now the timing for the race has been
* received, and it should be clear by now whether we're talking about a live or a replay race. In case of a replay race
* the timer is set to
* Listening for race times info continues until information about the end of the race has been received. The leaderboard
* settings are adjusted as soon as a valid leaderboard has been received.
*/
@Override
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo,
long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
if (getTimer().getPlayMode() != PlayModes.Live && !raceTimesInfo.isEmpty() && raceTimesInfo.containsKey(getRaceIdentifier())) {
final RaceTimesInfoDTO times = raceTimesInfo.get(getRaceIdentifier());
if (times.endOfRace != null) {
getTimer().setTime(times.endOfRace.getTime());
// we've done our adjustments; remove listener and let go
super.raceTimesInfosReceived(raceTimesInfo, clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
protected void trigger() {
if (!leaderboardSettingsAdjusted && getLeaderboard() != null) {
leaderboardSettingsAdjusted = true;
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (!timerAdjusted && getRaceTimesInfoForRace() != null && getRaceTimesInfoForRace().endOfRace != null) {
timerAdjusted = true;
stopReceivingRaceTimesInfos();
if (getTimer().getPlayMode() == PlayModes.Live) {
getTimer().setPlayMode(PlayModes.Replay);
}
getTimer().setTime(getRaceTimesInfoForRace().endOfRace.getTime());
}
}
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
super.updatedLeaderboard(leaderboard);
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS);
@@ -1,16 +1,10 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
@@ -26,30 +20,11 @@ import com.sap.sse.gwt.client.player.Timer.PlayStates;
public class PlayerMode extends AbstractRaceBoardMode {
private final Duration DURATION_BEFORE_START_TO_SET_TIMER_TO_FOR_REPLAY_RACES = Duration.ONE_SECOND.times(10);
/**
* Called after the {@link RaceTimePanel} has reacted to this update. We assume that now the timing for the race has been
* received, and it should be clear by now whether we're talking about a live or a replay race. In case of a replay race
* the timer is set to
*/
@Override
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo,
long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
if (getTimer().getPlayMode() != PlayModes.Live && !raceTimesInfo.isEmpty() && raceTimesInfo.containsKey(getRaceIdentifier())) {
final RaceTimesInfoDTO times = raceTimesInfo.get(getRaceIdentifier());
if (times.startOfRace != null) {
getTimer().setTime(new MillisecondsTimePoint(times.startOfRace).minus(DURATION_BEFORE_START_TO_SET_TIMER_TO_FOR_REPLAY_RACES).asMillis());
getTimer().play();
// we've done our adjustments; remove listener and let go
super.raceTimesInfosReceived(raceTimesInfo, clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
}
}
}
private boolean adjustedLeaderboardSettings;
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
super.updatedLeaderboard(leaderboard);
private boolean timerAdjusted;
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS);
@@ -69,4 +44,22 @@ public class PlayerMode extends AbstractRaceBoardMode {
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
}
@Override
protected void trigger() {
if (!adjustedLeaderboardSettings && getLeaderboard() != null) {
adjustedLeaderboardSettings = true;
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (!timerAdjusted && getTimer().getPlayMode() != PlayModes.Live && getRaceTimesInfoForRace() != null && getRaceTimesInfoForRace().startOfRace != null) {
timerAdjusted = true;
getTimer().setTime(new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).minus(DURATION_BEFORE_START_TO_SET_TIMER_TO_FOR_REPLAY_RACES).asMillis());
getTimer().play();
// we've done our adjustments; remove listener and let go
stopReceivingRaceTimesInfos();
}
}
}
@@ -20,7 +20,6 @@ public abstract class RaceBoardModeWithPerRaceCompetitors extends AbstractRaceBo
@Override
public void competitorsForRaceDefined(Iterable<CompetitorDTO> competitorsInRace) {
this.competitorsInRace = competitorsInRace;
updateCompetitorSelection();
}
protected Iterable<CompetitorDTO> getCompetitorsInRace() {
@@ -31,28 +30,19 @@ public abstract class RaceBoardModeWithPerRaceCompetitors extends AbstractRaceBo
return leaderboard;
}
protected void setLeaderboard(LeaderboardDTO leaderboard) {
this.leaderboard = leaderboard;
}
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
super.updatedLeaderboard(leaderboard);
setLeaderboard(leaderboard);
}
@Override
public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
this.raceColumn = raceColumn;
trigger();
}
protected RaceColumnDTO getRaceColumn() {
return raceColumn;
}
protected void updateCompetitorSelection(final int howManyTopCompetitorsInRaceToSelect) {
if (getLeaderboard() != null) {
final List<CompetitorDTO> competitorsFromBestToWorstInColumn = getLeaderboard().getCompetitorsFromBestToWorst(getRaceColumn());
protected void updateCompetitorSelection(final int howManyTopCompetitorsInRaceToSelect, LeaderboardDTO leaderboard) {
if (leaderboard != null) {
final List<CompetitorDTO> competitorsFromBestToWorstInColumn = leaderboard.getCompetitorsFromBestToWorst(getRaceColumn());
final Set<CompetitorDTO> competitorsToSelect = new HashSet<>();
int numberOfSelectedCompetitors = 0;
for (int i=0; numberOfSelectedCompetitors<howManyTopCompetitorsInRaceToSelect && i<competitorsFromBestToWorstInColumn.size(); i++) {
@@ -2,33 +2,22 @@ package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.maps.client.events.idle.IdleMapEvent;
import com.google.gwt.maps.client.events.idle.IdleMapHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.gwt.ui.actions.GetLeaderboardByNameAction;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.charts.ChartSettings;
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChartSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
@@ -47,10 +36,14 @@ public class StartAnalysisMode extends RaceBoardModeWithPerRaceCompetitors {
private static final Duration DURATION_AFTER_START_TO_SET_TIMER_TO_FOR_START_ANALYSIS = Duration.ONE_SECOND.times(10);
private static final Duration DURATION_BEFORE_START_TO_INCLUDE_IN_CHART_TIME_RANGE = Duration.ONE_SECOND.times(30);
private static final Duration DURATION_AFTER_START_TO_INCLUDE_IN_CHART_TIME_RANGE = Duration.ONE_SECOND.times(30);
private MultiCompetitorRaceChart competitorChart;
private boolean raceTimesInfoReceived;
private boolean leaderboardUpdateReveiced;
private RaceTimesInfoDTO raceTimesInfo;
private boolean leaderboardSettingsAdjusted;
private boolean competitorSelectionUpdated;
private boolean timerAdjusted;
private boolean mapSettingsAndTimeZoomAdjusted;
@Override
public void applyTo(RaceBoardPanel raceBoardPanel) {
@@ -58,168 +51,139 @@ public class StartAnalysisMode extends RaceBoardModeWithPerRaceCompetitors {
this.competitorChart = raceBoardPanel.getCompetitorChart();
}
/**
* Called after the {@link RaceTimePanel} has reacted to this update. We assume that now the timing for the race has been
* received, and it should be clear by now whether we're talking about a live or a replay race. In case of a replay race
* the timer is set to
*/
@Override
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo,
long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
if (!raceTimesInfo.isEmpty() && raceTimesInfo.containsKey(getRaceIdentifier())) {
final RaceTimesInfoDTO times = raceTimesInfo.get(getRaceIdentifier());
if (times.startOfRace != null) {
// make sure we're no longer in live/playing mode/state; the start analysis is supposed to be a "frozen" display at first
if (getTimer().getPlayMode() == PlayModes.Live) {
getTimer().setPlayMode(PlayModes.Replay);
}
// the following call will always trigger a leaderboard load and therefore a callback
// to updatedLeaderboard; therefore, updatedLeaderboard can decide when it's time to
// trigger the chart display and the map settings application
getTimer().setTime(new MillisecondsTimePoint(times.startOfRace).plus(DURATION_AFTER_START_TO_SET_TIMER_TO_FOR_START_ANALYSIS).asMillis());
// we've done our adjustments; remove listener and let go
super.raceTimesInfosReceived(raceTimesInfo, clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
this.raceTimesInfo = times;
raceTimesInfoReceived = true;
protected void updateCompetitorSelection() {
updateCompetitorSelection(/* howManyTopCompetitorsInRaceToSelect */ 3, getLeaderboard());
}
private void adjustMapSettings() {
final RaceMapSettings existingMapSettings = getRaceBoardPanel().getMap().getSettings();
final RaceMapSettings newMapSettings = new RaceMapSettings(
new RaceMapZoomSettings(Collections.singleton(ZoomTypes.BOATS), /* zoomToSelected */ false),
existingMapSettings.getHelpLinesSettings(),
existingMapSettings.getTransparentHoverlines(),
existingMapSettings.getHoverlineStrokeWeight(),
existingMapSettings.getTailLengthInMilliseconds(),
/* existingMapSettings.isWindUp() */ true,
existingMapSettings.getBuoyZoneRadiusInMeters(),
existingMapSettings.isShowOnlySelectedCompetitors(),
existingMapSettings.isShowSelectedCompetitorsInfo(),
existingMapSettings.isShowWindStreamletColors(),
existingMapSettings.isShowWindStreamletOverlay(),
existingMapSettings.isShowSimulationOverlay(),
existingMapSettings.isShowMapControls(),
existingMapSettings.getManeuverTypesToShow(),
existingMapSettings.isShowDouglasPeuckerPoints());
// try to update the settings once; the problem is the "wind up" display; it changes pan/zoom
// which is a major source of instability for the map. Going twice in the map idle event handler
// to ensure the settings are really applied.
final Object[] registration = new Object[1];
registration[0] = getRaceBoardPanel().getMap().getMap().addIdleHandler(new IdleMapHandler() {
@Override
public void onEvent(IdleMapEvent event) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
});
((HandlerRegistration) registration[0]).removeHandler();
}
});
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
@Override
protected void trigger() {
if (!leaderboardSettingsAdjusted && getLeaderboard() != null) {
leaderboardSettingsAdjusted = true;
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (!timerAdjusted && getRaceTimesInfoForRace() != null && getRaceTimesInfoForRace().startOfRace != null) {
timerAdjusted = true;
// we've done our adjustments; remove listener and let go
stopReceivingRaceTimesInfos();
// make sure we're no longer in live/playing mode/state; the start analysis is supposed to be a "frozen" display at first
if (getTimer().getPlayMode() == PlayModes.Live) {
getTimer().setPlayMode(PlayModes.Replay);
}
// the following call will always trigger a leaderboard load and therefore a callback
// to updatedLeaderboard; therefore, updatedLeaderboard can decide when it's time to
// trigger the chart display and the map settings application
getTimer().setTime(new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).plus(DURATION_AFTER_START_TO_SET_TIMER_TO_FOR_START_ANALYSIS).asMillis());
// no need to do anything if we don't have the timing info yet; after obtaining the timing
// info we can start waiting for the simulation overlay and then adjust the map settings and,
// if available, show the competitor chart
if (!mapSettingsAndTimeZoomAdjusted) {
mapSettingsAndTimeZoomAdjusted = true;
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
@Override
public boolean execute() {
// keep trying in 500ms steps until the simulation overlay is found; this way, changing the competitor chart
// settings is largely more successful
final boolean simulationOverlayFound = getRaceBoardPanel().getMap().getSimulationOverlay() != null;
if (simulationOverlayFound) {
if (isCompetitorChartEnabled()) {
final MultiCompetitorRaceChartSettings newCompetitorChartSettings = new MultiCompetitorRaceChartSettings(
new ChartSettings(/* stepSizeInMillis */ 1000), DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
/* no second series */ null);
competitorChart.updateSettings(newCompetitorChartSettings);
getRaceTimePanel().getTimeRangeProvider().setTimeZoom(
new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).minus(DURATION_BEFORE_START_TO_INCLUDE_IN_CHART_TIME_RANGE).asDate(),
new MillisecondsTimePoint(getRaceTimesInfoForRace().startOfRace).plus(DURATION_AFTER_START_TO_INCLUDE_IN_CHART_TIME_RANGE).asDate());
}
adjustMapSettings();
}
return !simulationOverlayFound;
}
}, /* delay in milliseconds */ 500);
}
}
if (getLeaderboardForSpecificTimePoint() == null &&
getRaceColumn() != null && getLeaderboard() != null
&& timerAdjusted) {
loadLeaderboardForSpecificTimePoint(getLeaderboard().name, getRaceColumn().getName(), getTimer().getTime());
}
if (!competitorSelectionUpdated && getLeaderboardForSpecificTimePoint() != null && getCompetitorsInRace() != null) {
competitorSelectionUpdated = true;
stopReceivingCompetitorsInRace();
updateCompetitorSelection();
if (isCompetitorChartEnabled()) {
getRaceBoardPanel().setCompetitorChartVisible(true);
}
}
}
@Override
protected void updateCompetitorSelection() {
updateCompetitorSelection(/* howManyTopCompetitorsInRaceToSelect */ 3);
}
private void showCompetitorChartIfAllDataReceived() {
if (getRaceColumn() != null && leaderboardUpdateReveiced && raceTimesInfoReceived && getLeaderboard() != null
&& getLeaderboard().getCompetitorsFromBestToWorst(getRaceColumn()) != null
&& raceTimesInfo != null && raceTimesInfo.startOfRace != null) {
ArrayList<String> raceColumnName = new ArrayList<>();
raceColumnName.add(getRaceColumn().getName());
// The problem with the leaderboard received in updatedLeaderboard(...) is that we don't know which request
// has caused it to be delivered; there is an early request for "now" and maybe even another request for start of race;
// therefore, we need to fire our own request for the time point desired and make sure we get the right
// LeaderboardDTO to determine the competitor ordering for the current race.
final GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(
getLeaderboardPanel().getSailingService(), getLeaderboardPanel().getLeaderboard().name,
getTimer().getTime(), raceColumnName, /* addOverallDetails */ false,
getLeaderboard(), /* fillTotalPointsUncorrected */ false,
/* timerToAdjustOffsetIn */ getTimer(), /* errorReporter */ null, StringMessages.INSTANCE);
getLeaderboardPanel().getExecutor().execute(getLeaderboardByNameAction, LeaderboardPanel.LOAD_LEADERBOARD_DATA_CATEGORY,
new AsyncCallback<LeaderboardDTO>() {
@Override
public void onFailure(Throwable caught) {
GWT.log("Error trying to load leaderboard", caught);
}
@Override
public void onSuccess(LeaderboardDTO result) {
setLeaderboard(result);
getLeaderboardPanel().updateLeaderboard(result);
updateCompetitorSelection();
if (isCompetitorChartEnabled()) {
getRaceBoardPanel().setCompetitorChartVisible(true);
}
}
});
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
@Override
public boolean execute() {
// keep trying in 500ms steps until the simulation overlay is found; this way, changing the competitor chart
// settings is largely more successful because
boolean result = getRaceBoardPanel().getMap().getSimulationOverlay() == null;
if (!result) {
if (isCompetitorChartEnabled()) {
final MultiCompetitorRaceChartSettings newCompetitorChartSettings = new MultiCompetitorRaceChartSettings(
new ChartSettings(/* stepSizeInMillis */ 1000), DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
/* no second series */ null);
competitorChart.updateSettings(newCompetitorChartSettings);
getRaceTimePanel().getTimeRangeProvider().setTimeZoom(
new MillisecondsTimePoint(raceTimesInfo.startOfRace).minus(DURATION_BEFORE_START_TO_INCLUDE_IN_CHART_TIME_RANGE).asDate(),
new MillisecondsTimePoint(raceTimesInfo.startOfRace).plus(DURATION_AFTER_START_TO_INCLUDE_IN_CHART_TIME_RANGE).asDate());
}
final RaceMapSettings existingMapSettings = getRaceBoardPanel().getMap().getSettings();
final RaceMapSettings newMapSettings = new RaceMapSettings(
new RaceMapZoomSettings(Collections.singleton(ZoomTypes.BOATS), /* zoomToSelected */ false),
existingMapSettings.getHelpLinesSettings(),
existingMapSettings.getTransparentHoverlines(),
existingMapSettings.getHoverlineStrokeWeight(),
existingMapSettings.getTailLengthInMilliseconds(),
/* existingMapSettings.isWindUp() */ true,
existingMapSettings.getBuoyZoneRadiusInMeters(),
existingMapSettings.isShowOnlySelectedCompetitors(),
existingMapSettings.isShowSelectedCompetitorsInfo(),
existingMapSettings.isShowWindStreamletColors(),
existingMapSettings.isShowWindStreamletOverlay(),
existingMapSettings.isShowSimulationOverlay(),
existingMapSettings.isShowMapControls(),
existingMapSettings.getManeuverTypesToShow(),
existingMapSettings.isShowDouglasPeuckerPoints());
// try to update the settings once; the problem is the "wind up" display; it changes pan/zoom
// which is a major source of instability for the map. Going twice in the map idle event handler
// to ensure the settings are really applied.
final Object[] registration = new Object[1];
registration[0] = getRaceBoardPanel().getMap().getMap().addIdleHandler(new IdleMapHandler() {
@Override
public void onEvent(IdleMapEvent event) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
});
((HandlerRegistration) registration[0]).removeHandler();
}
});
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
return result;
}
}, /* delay in milliseconds */ 500);
}
}
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
// no need to do anything if we don't have the timing info yet; after obtaining the timing
// info there will always be at least one more leaderboard requested, triggering a callback
// to this method
if (raceTimesInfoReceived) {
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
super.updatedLeaderboard(leaderboard);
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START);
raceDetailsToShow.add(DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START);
raceDetailsToShow.add(DetailType.DISTANCE_TO_START_AT_RACE_START);
raceDetailsToShow.add(DetailType.DISTANCE_TO_START_LINE);
raceDetailsToShow.add(DetailType.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS);
raceDetailsToShow.add(DetailType.SPEED_OVER_GROUND_AT_RACE_START);
raceDetailsToShow.add(DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START);
raceDetailsToShow.add(DetailType.START_TACK);
raceDetailsToShow.remove(DetailType.DISPLAY_LEGS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
leaderboardUpdateReveiced = true;
showCompetitorChartIfAllDataReceived();
}
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START);
raceDetailsToShow.add(DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START);
raceDetailsToShow.add(DetailType.DISTANCE_TO_START_AT_RACE_START);
raceDetailsToShow.add(DetailType.DISTANCE_TO_START_LINE);
raceDetailsToShow.add(DetailType.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS);
raceDetailsToShow.add(DetailType.SPEED_OVER_GROUND_AT_RACE_START);
raceDetailsToShow.add(DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START);
raceDetailsToShow.add(DetailType.START_TACK);
raceDetailsToShow.remove(DetailType.DISPLAY_LEGS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
}
private boolean isCompetitorChartEnabled() {
return competitorChart != null;
}
}
@@ -3,16 +3,12 @@ package com.sap.sailing.gwt.ui.raceboard;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
import com.sap.sailing.gwt.ui.shared.MarkPassingTimesDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -30,93 +26,58 @@ import com.sap.sse.gwt.client.player.Timer.PlayModes;
public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors {
private Duration tailLength;
/**
* When set to {@code true}, when {@link #updatedLeaderboard(LeaderboardDTO)} is called the next time it will
* stop the leaderboard update notifications and will adjust the tail length to {@link #tailLength}. Must only
* be set to {@code true} if {@link #tailLength} has been set to a valid duration before.
*/
private boolean stopReceivingLeaderboardUpdatesAndAdjustTailLength;
@Override
public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfo,
long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
final Date startOfRace;
final RaceTimesInfoDTO raceTimesInfoForRace;
if (!raceTimesInfo.isEmpty() && raceTimesInfo.containsKey(getRaceIdentifier()) &&
(startOfRace=(raceTimesInfoForRace=raceTimesInfo.get(getRaceIdentifier())).startOfRace) != null) {
if (getTimer().getPlayMode() == PlayModes.Live) {
// adjust tail length such that for the leading boat the tail is shown since the start time point
tailLength = new MillisecondsTimePoint(startOfRace).until(getTimer().getLiveTimePoint());
stopReceivingLeaderboardUpdatesAndAdjustTailLength = true;
} else {
final List<MarkPassingTimesDTO> markPassingTimes = raceTimesInfoForRace.getMarkPassingTimes();
final Date firstPassingOfLastWaypointPassed = markPassingTimes == null || markPassingTimes.isEmpty() ? null :
markPassingTimes.get(markPassingTimes.size()-1).firstPassingDate;
final Date endOfRace = raceTimesInfoForRace.endOfRace;
final Date end = firstPassingOfLastWaypointPassed != null ? firstPassingOfLastWaypointPassed :
endOfRace != null ? endOfRace : raceTimesInfoForRace.endOfTracking;
if (end != null) {
tailLength = new MillisecondsTimePoint(startOfRace).until(new MillisecondsTimePoint(end));
stopReceivingLeaderboardUpdatesAndAdjustTailLength = true;
getTimer().setTime(end.getTime());
}
// we've done our adjustments; remove listener and let go
super.raceTimesInfosReceived(raceTimesInfo, clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
}
}
}
private boolean adjustedLeaderboardSettings;
private boolean adjustedCompetitorSelection;
@Override
protected void updateCompetitorSelection() {
final int howManyCompetitorsToSelect = getHowManyCompetitorsToSelect(getLeaderboardPanel().getCompetitors(getRaceIdentifier()));
updateCompetitorSelection(howManyCompetitorsToSelect);
updateCompetitorSelection(howManyCompetitorsToSelect, getLeaderboard());
}
@Override
public void updatedLeaderboard(LeaderboardDTO leaderboard) {
if (stopReceivingLeaderboardUpdatesAndAdjustTailLength) {
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
super.updatedLeaderboard(leaderboard);
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS);
raceDetailsToShow.add(DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS);
raceDetailsToShow.add(DetailType.RACE_DISTANCE_TRAVELED);
raceDetailsToShow.add(DetailType.RACE_TIME_TRAVELED);
raceDetailsToShow.remove(DetailType.DISPLAY_LEGS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
final RaceMapSettings existingMapSettings = getRaceBoardPanel().getMap().getSettings();
final RaceMapSettings newMapSettings = new RaceMapSettings(existingMapSettings.getZoomSettings(),
existingMapSettings.getHelpLinesSettings(),
existingMapSettings.getTransparentHoverlines(),
existingMapSettings.getHoverlineStrokeWeight(),
tailLength.asMillis(),
/* existingMapSettings.isWindUp() */ true,
existingMapSettings.getBuoyZoneRadiusInMeters(),
/* existingMapSettings.isShowOnlySelectedCompetitors() */ true, // show the top n competitors and their tails quickly
existingMapSettings.isShowSelectedCompetitorsInfo(),
existingMapSettings.isShowWindStreamletColors(),
existingMapSettings.isShowWindStreamletOverlay(),
existingMapSettings.isShowSimulationOverlay(),
existingMapSettings.isShowMapControls(),
existingMapSettings.getManeuverTypesToShow(),
existingMapSettings.isShowDouglasPeuckerPoints());
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
private void adjustLeaderboardSettings() {
final LeaderboardSettings existingSettings = getLeaderboardPanel().getSettings();
final List<DetailType> raceDetailsToShow = new ArrayList<>(existingSettings.getRaceDetailsToShow());
raceDetailsToShow.add(DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS);
raceDetailsToShow.add(DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS);
raceDetailsToShow.add(DetailType.RACE_DISTANCE_TRAVELED);
raceDetailsToShow.add(DetailType.RACE_TIME_TRAVELED);
raceDetailsToShow.remove(DetailType.DISPLAY_LEGS);
final LeaderboardSettings newSettings = new LeaderboardSettings(existingSettings.getManeuverDetailsToShow(),
existingSettings.getLegDetailsToShow(),
raceDetailsToShow, existingSettings.getOverallDetailsToShow(), existingSettings.getNamesOfRaceColumnsToShow(),
existingSettings.getNamesOfRacesToShow(),
existingSettings.getNumberOfLastRacesToShow(), /* auto-expand pre-selected race */ true,
existingSettings.getDelayBetweenAutoAdvancesInMilliseconds(),
existingSettings.getNameOfRaceToSort(), existingSettings.isSortAscending(),
existingSettings.isUpdateUponPlayStateChange(),
existingSettings.getActiveRaceColumnSelectionStrategy(),
existingSettings.isShowAddedScores(),
existingSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(),
existingSettings.isShowCompetitorSailIdColumn(),
existingSettings.isShowCompetitorFullNameColumn());
getLeaderboardPanel().updateSettings(newSettings);
}
private void adjustMapSettings() {
final RaceMapSettings existingMapSettings = getRaceBoardPanel().getMap().getSettings();
final RaceMapSettings newMapSettings = new RaceMapSettings(existingMapSettings.getZoomSettings(),
existingMapSettings.getHelpLinesSettings(),
existingMapSettings.getTransparentHoverlines(),
existingMapSettings.getHoverlineStrokeWeight(),
tailLength.asMillis(),
/* existingMapSettings.isWindUp() */ true,
existingMapSettings.getBuoyZoneRadiusInMeters(),
/* existingMapSettings.isShowOnlySelectedCompetitors() */ true, // show the top n competitors and their tails quickly
existingMapSettings.isShowSelectedCompetitorsInfo(),
existingMapSettings.isShowWindStreamletColors(),
existingMapSettings.isShowWindStreamletOverlay(),
existingMapSettings.isShowSimulationOverlay(),
existingMapSettings.isShowMapControls(),
existingMapSettings.getManeuverTypesToShow(),
existingMapSettings.isShowDouglasPeuckerPoints());
getRaceBoardPanel().getMap().updateSettings(newMapSettings);
}
/**
@@ -128,4 +89,46 @@ public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors {
final int numberOfCompetitors = Util.size(competitors);
return numberOfCompetitors==0 ? 0 : numberOfCompetitors<=4 ? 1 : numberOfCompetitors <=9 ? 2 : 3;
}
@Override
protected void trigger() {
final Date startOfRace;
if (getRaceTimesInfoForRace() != null && (startOfRace=getRaceTimesInfoForRace().startOfRace) != null) {
if (getTimer().getPlayMode() == PlayModes.Live) {
// adjust tail length such that for the leading boat the tail is shown since the start time point
tailLength = new MillisecondsTimePoint(startOfRace).until(getTimer().getLiveTimePoint());
} else {
final List<MarkPassingTimesDTO> markPassingTimes = getRaceTimesInfoForRace().getMarkPassingTimes();
final Date firstPassingOfLastWaypointPassed = markPassingTimes == null || markPassingTimes.isEmpty() ? null :
markPassingTimes.get(markPassingTimes.size()-1).firstPassingDate;
final Date endOfRace = getRaceTimesInfoForRace().endOfRace;
final Date end = firstPassingOfLastWaypointPassed != null ? firstPassingOfLastWaypointPassed :
endOfRace != null ? endOfRace : getRaceTimesInfoForRace().endOfTracking;
if (end != null) {
tailLength = new MillisecondsTimePoint(startOfRace).until(new MillisecondsTimePoint(end));
getTimer().setTime(end.getTime());
}
}
// we've done our adjustments; remove listener and let go
stopReceivingRaceTimesInfos();
}
if (!adjustedLeaderboardSettings && getLeaderboard() != null) {
adjustedLeaderboardSettings = true;
// it's important to first unregister the listener before updateSettings is called because
// updateSettings will trigger another leaderboard load, leading to an endless recursion otherwise
stopReceivingLeaderboard();
adjustLeaderboardSettings();
}
if (adjustedLeaderboardSettings && tailLength != null) {
adjustMapSettings();
}
if (getLeaderboardForSpecificTimePoint() == null && tailLength != null && getLeaderboard() != null && getRaceColumn() != null) {
loadLeaderboardForSpecificTimePoint(getLeaderboard().name, getRaceColumn().getName(), getTimer().getTime());
}
if (!adjustedCompetitorSelection && getLeaderboardForSpecificTimePoint() != null) {
adjustedCompetitorSelection = true;
updateCompetitorSelection();
}
}
}