From 90b563eabe1ccce7df7561ca70d3b2e42a070c25 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 1 Apr 2015 19:53:13 +0200 Subject: [PATCH] added invalidation tests for WaypointPositionAndDistanceCache --- .../WaypointPositionAndDistanceCacheTest.java | 64 +++++++++++++++++-- .../WaypointPositionAndDistanceCache.java | 40 +++++++----- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassing/impl/WaypointPositionAndDistanceCacheTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassing/impl/WaypointPositionAndDistanceCacheTest.java index 7bdae317f03..459c9f40a78 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassing/impl/WaypointPositionAndDistanceCacheTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassing/impl/WaypointPositionAndDistanceCacheTest.java @@ -23,7 +23,9 @@ import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.MarkImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.markpassingcalculation.impl.WaypointPositionAndDistanceCache; import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -39,19 +41,26 @@ public class WaypointPositionAndDistanceCacheTest { private WaypointPositionAndDistanceCache cache; private int recalculations; private DynamicTrackedRace trackedRace; + /** + * A time point rouded to be at the center of a cache time range. + */ private TimePoint now; private Waypoint start; private Waypoint windwardWaypoint; private Waypoint finish; + private Duration timeRangeResolution; + private Mark pinEnd; + private Mark startBoat; + private Mark windward; @Before public void setUp() { final RaceDefinition race = mock(RaceDefinition.class); when(race.getCompetitors()).thenReturn(Collections.emptyList()); when(race.getBoatClass()).thenReturn(DomainFactory.INSTANCE.getOrCreateBoatClass("29er")); - Mark pinEnd = new MarkImpl("Pin End"); - Mark startBoat = new MarkImpl("Start Boat"); - Mark windward = new MarkImpl("Windward"); + pinEnd = new MarkImpl("Pin End"); + startBoat = new MarkImpl("Start Boat"); + windward = new MarkImpl("Windward"); ControlPointWithTwoMarks startFinish = new ControlPointWithTwoMarksImpl(UUID.randomUUID(), pinEnd, startBoat, "Start/Finish"); start = new WaypointImpl(startFinish); windwardWaypoint = new WaypointImpl(windward); @@ -66,12 +75,13 @@ public class WaypointPositionAndDistanceCacheTest { /* delayToLiveInMillis */ 8000, /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 15000, /* delayForCacheInvalidationOfWindEstimation */ 10000, /* useInternalMarkPassingAlgorithm */ false); - now = MillisecondsTimePoint.now(); + timeRangeResolution = Duration.ONE_MINUTE; + now = new MillisecondsTimePoint(MillisecondsTimePoint.now().asMillis() / timeRangeResolution.asMillis() * timeRangeResolution.asMillis()); trackedRace.getOrCreateTrack(pinEnd).addGPSFix(new GPSFixImpl(new DegreePosition(0, -0.0000001), now)); trackedRace.getOrCreateTrack(startBoat).addGPSFix(new GPSFixImpl(new DegreePosition(0, 0.0000001), now)); trackedRace.getOrCreateTrack(windward).addGPSFix(new GPSFixImpl(new DegreePosition(1, 0), now)); recalculations = 0; - cache = new WaypointPositionAndDistanceCache(trackedRace, /* timeRangeResolution */ Duration.ONE_MINUTE) { + cache = new WaypointPositionAndDistanceCache(trackedRace, timeRangeResolution) { @Override protected R computeResult(Function resultCalculator, TimePoint roundedToTimeRangeCenter) { recalculations++; @@ -83,7 +93,8 @@ public class WaypointPositionAndDistanceCacheTest { @Test public void simpleTest() { assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); - assertEquals(3, recalculations); // one for the position of each waypoint, one for the distance + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); + assertEquals(3, recalculations); // one for the position of each waypoint, one for the distance; second request coming from cache } @Test @@ -92,4 +103,45 @@ public class WaypointPositionAndDistanceCacheTest { assertEquals(60, cache.getApproximateDistance(windwardWaypoint, start, now).getNauticalMiles(), 0.01); assertEquals(3, recalculations); // one for the position of each waypoint, one for the distance which creates two symmetrical cache entries } + + @Test + public void testWithDifferentTimesStillInRange() { + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now.plus(timeRangeResolution.divide(4))).getNauticalMiles(), 0.01); + assertEquals(3, recalculations); // one for the position of each waypoint, one for the distance which creates two symmetrical cache entries + // the second query is expected to come from the cache because its time is still in range of the previous cache entries + } + + @Test + public void testNewMarkPositionAffectingEntry() { + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); + trackedRace.getOrCreateTrack(windward).add(new GPSFixImpl(new DegreePosition(1, 0).translateGreatCircle( + new DegreeBearingImpl(0), new KnotSpeedImpl(20).travel(now, now.plus(timeRangeResolution.divide(10)))), + now.plus(timeRangeResolution.divide(10)))); // does not affect the cache interval + trackedRace.getOrCreateTrack(windward).add(new GPSFixImpl(new DegreePosition(2, 0), now.plus(1 /* ms */))); // affects the cache interval + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); + assertEquals(5, recalculations); // one for each waypoint, one for the distance; second request recalculates except start/finish position + } + + @Test + public void testNewMarkPositionAffectingEntryOppositeOrder() { + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); + trackedRace.getOrCreateTrack(windward).add(new GPSFixImpl(new DegreePosition(1, 0).translateGreatCircle( + new DegreeBearingImpl(0), new KnotSpeedImpl(20).travel(now, now.plus(timeRangeResolution.divide(10)))), + now.plus(timeRangeResolution.divide(10)))); // does not affect the cache interval + trackedRace.getOrCreateTrack(windward).add(new GPSFixImpl(new DegreePosition(2, 0), now.plus(1 /* ms */))); // affects the cache interval + assertEquals(60, cache.getApproximateDistance(windwardWaypoint, start, now).getNauticalMiles(), 0.01); + assertEquals(5, recalculations); // one for each waypoint, one for the distance; second request recalculates except start/finish position + } + + @Test + public void testNewMarkPositionNotAffectingEntry() { + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); + trackedRace.getOrCreateTrack(windward).add(new GPSFixImpl(new DegreePosition(1, 0).translateGreatCircle( + new DegreeBearingImpl(0), new KnotSpeedImpl(20).travel(now, now.plus(timeRangeResolution))), + now.plus(timeRangeResolution))); // does not affect the cache interval + assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01); + assertEquals(60, cache.getApproximateDistance(windwardWaypoint, start, now).getNauticalMiles(), 0.01); + assertEquals(3, recalculations); // one for the position of each waypoint, one for the distance; second / third request from cache + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java index 422c5ebcf83..d1d739b54ce 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java @@ -2,10 +2,12 @@ package com.sap.sailing.domain.markpassingcalculation.impl; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NavigableMap; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Supplier; @@ -50,7 +52,7 @@ public class WaypointPositionAndDistanceCache { * Weak keys are the waypoints; values are time-ordered maps mapping the center of a time range * to the approximate waypoint position at that time. */ - private final ConcurrentWeakHashMap> waypointPositionCache; + private final ConcurrentWeakHashMap> waypointPositionCache; /** * Caches distances between the positions cached in {@link #waypointPositionCache}. As soon as an entry @@ -58,7 +60,7 @@ public class WaypointPositionAndDistanceCache { * symmetrically under the map's monitor being held, i.e., if (w1, w2) is a cache key then so is * (w2, w1), with equal value. */ - private final ConcurrentHashMap, Map> distanceCache; + private final ConcurrentHashMap, SortedMap> distanceCache; /** * The duration of the time ranges whose center time points serve as keys for the {@link NavigableMap}s used as @@ -121,24 +123,27 @@ public class WaypointPositionAndDistanceCache { * the additional key supplied by this object. If the supplier is null or supplies null, * no additional cache entry except one for the standard key will be produced */ - private R getApproximateResult(K cacheKey, TimePoint timePoint, Map> cacheMap, + private R getApproximateResult(K cacheKey, TimePoint timePoint, Map> cacheMap, Function resultCalculator, Supplier alternateKeySupplier) { - Map map = cacheMap.get(cacheKey); - if (map == null) { - map = Collections.synchronizedMap(new HashMap()); - cacheMap.put(cacheKey, map); - } + SortedMap map = cacheMap.get(cacheKey); + final R cachedResult; final TimePoint roundedToTimeRangeCenter = roundToResolution(timePoint); - final R cachedResult = map.get(roundedToTimeRangeCenter); + if (map == null) { + map = Collections.synchronizedSortedMap(new TreeMap()); + cacheMap.put(cacheKey, map); + cachedResult = null; + } else { + cachedResult = map.get(roundedToTimeRangeCenter); + } final R result; if (cachedResult == null) { result = computeResult(resultCalculator, roundedToTimeRangeCenter); map.put(roundedToTimeRangeCenter, result); K alternateKey; if (alternateKeySupplier != null && (alternateKey=alternateKeySupplier.get()) != null) { - Map mapForAlternateKey = cacheMap.get(alternateKey); + SortedMap mapForAlternateKey = cacheMap.get(alternateKey); if (mapForAlternateKey == null) { - mapForAlternateKey = Collections.synchronizedMap(new HashMap()); + mapForAlternateKey = Collections.synchronizedSortedMap(new TreeMap()); cacheMap.put(alternateKey, mapForAlternateKey); } mapForAlternateKey.put(roundedToTimeRangeCenter, result); @@ -180,11 +185,14 @@ public class WaypointPositionAndDistanceCache { } private void invalidate(Waypoint waypoint, TimeRange affectedTimeRange) { - Map map = waypointPositionCache.get(waypoint); + SortedMap map = waypointPositionCache.get(waypoint); if (map != null) { - for (TimePoint timePoint = roundToResolution(affectedTimeRange.from()); - !timePoint.after(affectedTimeRange.to()); - timePoint = timePoint.plus(timeRangeResolution)) { + final SortedMap tailMap = map.tailMap(roundToResolution(affectedTimeRange.from())); + for (Entry e : tailMap.entrySet()) { + final TimePoint timePoint = e.getKey(); + if (timePoint.after(affectedTimeRange.to())) { + break; + } assert timePoint.equals(roundToResolution(timePoint)); map.remove(timePoint); synchronized (waypoints) {