diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverhash/impl/ManeuverCacheDelegate.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverhash/impl/ManeuverCacheDelegate.java index b0a53288bad..69d3390ea1c 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverhash/impl/ManeuverCacheDelegate.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverhash/impl/ManeuverCacheDelegate.java @@ -176,23 +176,52 @@ public class ManeuverCacheDelegate implements SerializableManeuverCache { } /** - * Second phase of {@link #computeAndStore()}: waits for the wind estimator to drain, triggers - * per-competitor recalculation so maneuvers get re-typed with the estimator's wind fixes, - * then snapshots and persists. Invoked from - * {@link com.sap.sailing.domain.tracking.TrackedRace#runWhenWindEstimationInstalled(Runnable)}. + * Second phase of {@link #computeAndStore()}: sequences maneuver-cache re-typing so that the + * stored maneuvers reflect the wind fixes the estimator produces from the spots emitted by + * the detector. + *
+ *
+ * The choreography is two-round: the first round of {@link ManeuverCache#recalculate}
+ * triggers a fresh full-scan pass of the detector for each competitor (their per-competitor
+ * detectors were {@link TrackedRaceImpl#setWindEstimation cleared} when the wind estimator
+ * was installed, so the fresh detector emits spots into the now-non-null
+ * {@code WindEstimationInteraction}). The estimator asynchronously processes those spots and
+ * publishes wind fixes on its {@code MANEUVER_BASED_ESTIMATION} track. Once the estimator
+ * has drained (via {@code waitUntilDone}), a second round of recalculation re-types the
+ * spots using the newly-available wind and captures the classified maneuvers, which are then
+ * persisted. See bug6241; without this sequencing the initial recalc runs while the
+ * estimator has queued but not yet processed the spots, so typing still sees no wind and
+ * produces UNKNOWN maneuvers which then get persisted permanently.
*/
private void retypeAndStoreAfterWindEstimationSettled() {
+ // Round 1: trigger fresh detection per competitor. Their detector cache was cleared when
+ // setWindEstimation ran, so new detectors are built with a non-null wind-estimation
+ // interaction and emit spots to the estimator during this pass.
+ for (final Competitor competitor : race.getRace().getCompetitors()) {
+ cacheToUse.recalculate(competitor);
+ cacheToUse.get(competitor, /* waitForLatest */ true);
+ }
+ // Wait for the estimator to drain the spots emitted in Round 1 and produce its wind
+ // fixes. Those fixes flow into the tracked race's MANEUVER_BASED_ESTIMATION track and
+ // become visible to getWind(pos, at) lookups.
final IncrementalWindEstimation windEstimation = race.getWindEstimation();
+ boolean estimatorSettled = true;
if (windEstimation != null) {
try {
windEstimation.waitUntilDone();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
+ estimatorSettled = false;
logger.log(Level.WARNING, "Interrupted while waiting for wind estimation to finish inference for race "
+race.getRaceIdentifier()+"; skipping maneuver re-type and store");
}
}
- if (!Thread.currentThread().isInterrupted()) {
+ if (estimatorSettled) {
+ // Round 2: recalculate per competitor. This time the detector's
+ // lastManeuverDetectionResult is populated from Round 1 and no new raw fixes have
+ // arrived, so it takes the re-type branch of detectManeuverSpots (line 150 of
+ // IncrementalManeuverDetectorImpl) and re-types the existing spots using the current
+ // wind -- which now includes the estimator's fixes.
final Map