cache leg info about boat farthest ahead and calculate and cache the leader by calculated time for the leg in RankingInfo

This commit is contained in:
Axel Uhl committed 2015-05-28 15:15:36 +02:00
1 parent f11c534892
commit c9a43c5781
4 files changed
+138 -103

No files matched your search

@@ -2,7 +2,9 @@ package com.sap.sailing.domain.ranking;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.StreamSupport;
@@ -10,9 +12,11 @@ import java.util.stream.StreamSupport;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -121,14 +125,77 @@ public abstract class AbstractRankingMetric implements RankingMetric {
}
}
public class RankingInfoImpl implements RankingMetric.RankingInfo {
private static final long serialVersionUID = -2390284312153324336L;
/**
* Helper instance used to encode <code>null</code> values in {@link ConcurrentHashMap} instances which do not accept
* <code>null</code> as key nor value.
*/
private final static Competitor NULL_COMPETITOR = new CompetitorImpl(null, null, null, null, null, null, null, null, null);
public abstract class AbstractRankingInfo implements RankingMetric.RankingInfo {
private static final long serialVersionUID = 6845168655725234325L;
/**
* The time point for which this ranking information is valid
*/
private final TimePoint timePoint;
/**
* Caches, on demand, the results of calls to {@link #getCompetitorFarthestAheadInLeg(Leg, TimePoint, WindLegTypeAndLegBearingCache)}.
* <code>null</code> values are encoded as the {@link #NULL_COMPETITOR} and must be translated back to <code>null</code> before returning
* to a caller outside of this class.
*/
private final ConcurrentHashMap<Leg, Competitor> competitorFarthestAheadInLeg;
public AbstractRankingInfo(final TimePoint timePoint) {
this.timePoint = timePoint;
this.competitorFarthestAheadInLeg = new ConcurrentHashMap<>();
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@Override
public Duration getActualTimeFromRaceStartToReachFarthestAheadInLeg(Competitor competitor, Leg leg, WindLegTypeAndLegBearingCache cache) {
final Duration result;
final TrackedLegOfCompetitor tloc = getTrackedRace().getTrackedLeg(competitor, getTimePoint());
final Duration raceDurationAtTimePoint = getTrackedRace().getStartOfRace().until(getTimePoint());
if (tloc.hasStartedLeg(getTimePoint())) {
final Competitor competitorFarthestAheadInLeg = getCompetitorFarthestAheadInLeg(leg, getTimePoint(), cache);
final TrackedLegOfCompetitor tlocOfCompetitorFarthestAheadInLeg = tloc.getTrackedLeg().getTrackedLeg(competitorFarthestAheadInLeg);
final Duration predictedDurationFromTimePointToReachFarthestAheadInLeg = getPredictedDurationToReachWindwardPositionOf(
tloc, tlocOfCompetitorFarthestAheadInLeg, getTimePoint(), cache);
final Duration cDurationFromRaceStartToReachFarthestInLeg = raceDurationAtTimePoint.plus(predictedDurationFromTimePointToReachFarthestAheadInLeg);
result = cDurationFromRaceStartToReachFarthestInLeg;
} else {
result = null;
}
return result;
}
@Override
public Competitor getCompetitorFarthestAheadInLeg(Leg leg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
Competitor result = competitorFarthestAheadInLeg.get(leg);
if (result == NULL_COMPETITOR) {
result = null;
} else if (result == null) {
result = AbstractRankingMetric.this.getCompetitorFarthestAheadInLeg(getTrackedRace().getTrackedLeg(leg), timePoint, cache);
competitorFarthestAheadInLeg.put(leg, result);
}
return result;
}
@Override
public Competitor getLeaderInLegByCalculatedTime(Leg leg, WindLegTypeAndLegBearingCache cache) {
final TrackedLeg trackedLeg = getTrackedRace().getTrackedLeg(leg);
return trackedLeg.getLeader(getTimePoint(), cache);
}
}
public class RankingInfoImpl extends AbstractRankingInfo {
private static final long serialVersionUID = -2390284312153324336L;
/**
* The basic information for each competitor, telling about actual and corrected times as well as information
* about actual and corrected times needed to reach the position of the competitor farthest ahead at
@@ -145,8 +212,8 @@ public abstract class AbstractRankingMetric implements RankingMetric {
private final Competitor leaderByCorrectedEstimatedTimeToCompetitorFarthestAhead;
public RankingInfoImpl(TimePoint timePoint, Map<Competitor, RankingMetric.CompetitorRankingInfo> competitorRankingInfo, Competitor competitorFarthestAhead) {
super(timePoint);
final Comparator<Duration> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
this.timePoint = timePoint;
this.competitorRankingInfo = c->competitorRankingInfo.get(c);
this.competitorFarthestAhead = competitorFarthestAhead;
leaderByCorrectedEstimatedTimeToCompetitorFarthestAhead = competitorRankingInfo.keySet().stream().sorted(
@@ -156,11 +223,6 @@ public abstract class AbstractRankingMetric implements RankingMetric {
findFirst().get();
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@Override
public Function<Competitor, CompetitorRankingInfo> getCompetitorRankingInfo() {
return competitorRankingInfo;
@@ -553,4 +615,61 @@ public abstract class AbstractRankingMetric implements RankingMetric {
}
return result;
}
/**
* @return <code>null</code> if no competitor has started the leg yet; the first competitor to finish the leg if any
* has already finished the leg at <code>timePoint</code>; or the competitor with the greatest windward
* distance traveled in the leg at <code>timePoint</code> otherwise
*/
protected Competitor getCompetitorFarthestAheadInLeg(TrackedLeg trackedLeg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
Competitor firstAroundMark = getFirstLegFinisherBefore(trackedLeg, timePoint);
final Competitor result;
if (firstAroundMark != null) {
result = firstAroundMark;
} else {
Iterable<MarkPassing> markPassingsForLegStart = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom());
Distance maxWindwardDistanceTraveled = new MeterDistance(Double.MIN_VALUE);
Competitor competitorFarthestAlong = null;
getTrackedRace().lockForRead(markPassingsForLegStart);
try {
for (MarkPassing mp : markPassingsForLegStart) {
if (mp.getTimePoint().after(timePoint)) {
break;
}
final Distance windwardDistanceTraveled = getWindwardDistanceTraveled(mp.getCompetitor(), mp.getWaypoint(),
timePoint, cache);
if (windwardDistanceTraveled.compareTo(maxWindwardDistanceTraveled) > 0) {
maxWindwardDistanceTraveled = windwardDistanceTraveled;
competitorFarthestAlong = mp.getCompetitor();
}
}
} finally {
getTrackedRace().unlockAfterRead(markPassingsForLegStart);
}
result = competitorFarthestAlong;
}
return result;
}
/**
* Determines the first competitor finishing the leg identified by <code>trackedLeg</code> at or before <code>timePoint</code>. If
* no such competitor exists, <code>null</code> is returned.
*/
private Competitor getFirstLegFinisherBefore(TrackedLeg trackedLeg, TimePoint timePoint) {
Iterable<MarkPassing> markPassingsForLegEnd = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getTo());
Competitor firstAroundMark = null;
getTrackedRace().lockForRead(markPassingsForLegEnd);
try {
final Iterator<MarkPassing> i = markPassingsForLegEnd.iterator();
if (i.hasNext()) {
MarkPassing markPassing = i.next();
if (!markPassing.getTimePoint().after(timePoint)) {
firstAroundMark = markPassing.getCompetitor();
}
}
} finally {
getTrackedRace().unlockAfterRead(markPassingsForLegEnd);
}
return firstAroundMark;
}
}
@@ -75,14 +75,9 @@ public class OneDesignRankingMetric extends AbstractRankingMetric {
@Override
public RankingInfo getRankingInfo(final TimePoint timePoint, final WindLegTypeAndLegBearingCache cache) {
return new RankingInfo() {
return new AbstractRankingInfo(timePoint) {
private static final long serialVersionUID = 25689357311324825L;
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@Override
public Function<Competitor, CompetitorRankingInfo> getCompetitorRankingInfo() {
return competitor -> getCompetitorRankingInfo(competitor);
@@ -122,7 +122,11 @@ public interface RankingMetric extends Serializable {
* is normalized to the {@link TrackedLeg#getWindwardDistance() windward distance of the leg} at the
* {@link TrackedLeg#getReferenceTimePoint() reference time point}.
*/
Competitor getLeaderInLegByCalculatedTime(Leg leg);
Competitor getLeaderInLegByCalculatedTime(Leg leg, WindLegTypeAndLegBearingCache cache);
Competitor getCompetitorFarthestAheadInLeg(Leg leg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache);
Duration getActualTimeFromRaceStartToReachFarthestAheadInLeg(Competitor competitor, Leg leg, WindLegTypeAndLegBearingCache cache);
}
public interface LegRankingInfo extends Timed, Serializable {
@@ -2,7 +2,6 @@ package com.sap.sailing.domain.ranking;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -14,7 +13,6 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Mile;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
@@ -160,63 +158,6 @@ public class TimeOnTimeAndDistanceRankingMetric extends AbstractRankingMetric {
compareTo(correctedTimesToReachFastestBoatsPositionAtTimePointOrEndOfLegMeasuredFromStartOfRace.get(tloc2.getCompetitor()));
}
/**
* @return <code>null</code> if no competitor has started the leg yet; the first competitor to finish the leg if any
* has already finished the leg at <code>timePoint</code>; or the competitor with the greatest windward
* distance traveled in the leg at <code>timePoint</code> otherwise
*/
private Competitor getCompetitorFarthestAheadInLeg(TrackedLeg trackedLeg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
Competitor firstAroundMark = getFirstLegFinisherBefore(trackedLeg, timePoint);
final Competitor result;
if (firstAroundMark != null) {
result = firstAroundMark;
} else {
Iterable<MarkPassing> markPassingsForLegStart = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom());
Distance maxWindwardDistanceTraveled = new MeterDistance(Double.MIN_VALUE);
Competitor competitorFarthestAlong = null;
getTrackedRace().lockForRead(markPassingsForLegStart);
try {
for (MarkPassing mp : markPassingsForLegStart) {
if (mp.getTimePoint().after(timePoint)) {
break;
}
final Distance windwardDistanceTraveled = getWindwardDistanceTraveled(mp.getCompetitor(), mp.getWaypoint(),
timePoint, cache);
if (windwardDistanceTraveled.compareTo(maxWindwardDistanceTraveled) > 0) {
maxWindwardDistanceTraveled = windwardDistanceTraveled;
competitorFarthestAlong = mp.getCompetitor();
}
}
} finally {
getTrackedRace().unlockAfterRead(markPassingsForLegStart);
}
result = competitorFarthestAlong;
}
return result;
}
/**
* Determines the first competitor finishing the leg identified by <code>trackedLeg</code> at or before <code>timePoint</code>. If
* no such competitor exists, <code>null</code> is returned.
*/
private Competitor getFirstLegFinisherBefore(TrackedLeg trackedLeg, TimePoint timePoint) {
Iterable<MarkPassing> markPassingsForLegEnd = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getTo());
Competitor firstAroundMark = null;
getTrackedRace().lockForRead(markPassingsForLegEnd);
try {
final Iterator<MarkPassing> i = markPassingsForLegEnd.iterator();
if (i.hasNext()) {
MarkPassing markPassing = i.next();
if (!markPassing.getTimePoint().after(timePoint)) {
firstAroundMark = markPassing.getCompetitor();
}
}
} finally {
getTrackedRace().unlockAfterRead(markPassingsForLegEnd);
}
return firstAroundMark;
}
@Override
public Duration getCorrectedTime(Competitor competitor, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
final Duration timeActuallySpent = getActualTimeSinceStartOfRace(competitor, timePoint);
@@ -379,47 +320,23 @@ public class TimeOnTimeAndDistanceRankingMetric extends AbstractRankingMetric {
public Duration getLegGapToLegLeaderInOwnTime(TrackedLegOfCompetitor trackedLegOfCompetitor, TimePoint timePoint,
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
final Duration result;
final Leg leg = trackedLegOfCompetitor.getLeg();
if (getTrackedRace().getStartOfRace() == null || !trackedLegOfCompetitor.hasStartedLeg(timePoint)) {
result = null;
} else {
final Duration raceDurationAtTimePoint = getTrackedRace().getStartOfRace().until(timePoint);
final Competitor farthestAheadOrEarliestLegFinisher = getCompetitorFarthestAheadInLeg(trackedLegOfCompetitor.getTrackedLeg(), timePoint, cache);
final TrackedLegOfCompetitor tlocOfFarthestAhead = trackedLegOfCompetitor.getTrackedLeg().getTrackedLeg(farthestAheadOrEarliestLegFinisher);
final boolean farthestAheadAlreadyFinishedLeg = tlocOfFarthestAhead.hasFinishedLeg(timePoint);
final Distance windwardDistanceFarthestTraveledUntilFinishingLeg = getWindwardDistanceTraveled(farthestAheadOrEarliestLegFinisher,
farthestAheadAlreadyFinishedLeg ? tlocOfFarthestAhead.getFinishTime() : timePoint, cache);
final Map<Competitor, Duration> correctedTimeToReachFarthestAheadInLeg = new HashMap<>();
final Map<Competitor, Duration> actualTimeFromRaceStartToReachFarthestAheadInLeg = new HashMap<>();
// Consider all competitors, regardless of whether they have started the leg or not; a competitor in a previous
// leg may well be the race leader. However, if a competitor has finished the leg already, use the finishing time
// point for that competitor.
// FIXME need to calculate the ranking stuff only once for requests for all competitors to this method, or else we'll end up with n^2 effort
// FIXME The competitor iteration is only necessary here because we need to know the "leader"; other than that, computing things for the leader and the trackedLegOfCompetitor's competitor is sufficient here
// FIXME Grab the leader information from RankingInfo; CAUTION: leg leader is not necessarily identical to race leader! RankingInfo may need another field for each leg leader
for (Competitor c : getTrackedRace().getRace().getCompetitors()) {
final TrackedLegOfCompetitor tloc = trackedLegOfCompetitor.getTrackedLeg().getTrackedLeg(c);
if (tloc.hasStartedLeg(timePoint)) {
final Duration predictedDurationFromTimePointToReachFarthestAheadInLeg = getPredictedDurationToReachWindwardPositionOf(tloc, tlocOfFarthestAhead, timePoint, cache);
final Duration cDurationFromRaceStartToReachFarthestInLeg = raceDurationAtTimePoint.plus(predictedDurationFromTimePointToReachFarthestAheadInLeg);
actualTimeFromRaceStartToReachFarthestAheadInLeg.put(c, cDurationFromRaceStartToReachFarthestInLeg);
final Duration correctedTimeWhenReachingFarthestAheadInLeg = getCorrectedTime(c, ()->tloc.getTrackedLeg().getLeg(),
()->farthestAheadAlreadyFinishedLeg?getTrackedRace().getApproximatePosition(tlocOfFarthestAhead.getTrackedLeg().getLeg().getTo(), tlocOfFarthestAhead.getFinishTime()):
getTrackedRace().getTrack(farthestAheadOrEarliestLegFinisher).getEstimatedPosition(timePoint, /* extrapolate */ true),
cDurationFromRaceStartToReachFarthestInLeg, windwardDistanceFarthestTraveledUntilFinishingLeg);
correctedTimeToReachFarthestAheadInLeg.put(c, correctedTimeWhenReachingFarthestAheadInLeg);
} else {
actualTimeFromRaceStartToReachFarthestAheadInLeg.put(c, null);
correctedTimeToReachFarthestAheadInLeg.put(c, null);
}
}
// leader in the leg is now the competitor with the least corrected time to reach the competitor farthest ahead in the leg
Comparator<Duration> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
final Competitor legLeader = rankingInfo.getLeaderInLegByCalculatedTime(trackedLegOfCompetitor.getTrackedLeg().getLeg());
final Competitor legLeader = correctedTimeToReachFarthestAheadInLeg.entrySet().stream()
.sorted((e1, e2) -> durationComparatorNullsLast.compare(e1.getValue(), e2.getValue())).findFirst().get().getKey();
final Competitor legLeader = rankingInfo.getLeaderInLegByCalculatedTime(trackedLegOfCompetitor.getTrackedLeg().getLeg(), cache);
result = getGapToCompetitorInOwnTime(trackedLegOfCompetitor.getCompetitor(), legLeader,
actualTimeFromRaceStartToReachFarthestAheadInLeg.get(trackedLegOfCompetitor.getCompetitor()),
actualTimeFromRaceStartToReachFarthestAheadInLeg.get(legLeader),
rankingInfo.getActualTimeFromRaceStartToReachFarthestAheadInLeg(trackedLegOfCompetitor.getCompetitor(), leg, cache),
rankingInfo.getActualTimeFromRaceStartToReachFarthestAheadInLeg(legLeader, leg, cache),
windwardDistanceFarthestTraveledUntilFinishingLeg);
}
return result;