Introduces a single per-race primitive in RacingEventServiceImpl that
composes TrackedRace.runWhenPastLoading with
PolarDataService.runWhenPolarLoadingFinishedFor to install the
maneuver-based wind estimation on a race only once BOTH of these are
true: the race has reached a status strictly beyond LOADING (so
whatever loading was to happen has finished), AND the polar-data
mining pipeline has fully drained this race's loaded fixes.
The primitive is called from two places, replacing two separate
installation code paths with one:
1. RaceAdditionListener.raceAdded, for races added while
windEstimationFactoryService is already registered.
2. setWindEstimationFactoryService (via
setWindEstimationOnAllTrackedRaces), for races that were already
present when the wind-factory service arrives later.
Under either arrival order the same guard applies: the install only
runs if the wind-factory reference is still the one we were scheduled
with, the race hasn't been removed or errored, and no other estimator
has been installed on it in the meantime. This removes the ordering
sensitivity between OSGi service arrival and race creation that
comment #17 on issue 6241 identified as a root cause.
PolarFixCacheUpdater simplifies dramatically: its only remaining job is
to trigger ingestion of the race's fixes into the polar-mining loading
pipeline on the first transition to a status past LOADING. It no longer
holds an installation callback, and it now also fires ingestion for
races that go directly PREPARED->TRACKING without ever entering
LOADING (previously that fallback branch fired only the install
callback, not the ingestion). This ensures the polar-loading gate in
PolarDataMiner always opens for such races, so any pending
runWhenPolarLoadingFinishedFor callbacks can proceed.
Also adds TrackedRace.getWindEstimation() so the installation
primitive can guard against duplicate installation (it was previously
write-only).
MockedTrackedRace implements DynamicTrackedRace (which extends
TrackedRace), so it needs the same no-op stub the other two mock/dummy
classes got in the previous commit.
Introduces a listener-based primitive that fires a callback exactly once
when a tracked race reaches a status strictly beyond LOADING (TRACKING,
FINISHED, ERROR, or REMOVED via status transition), provided the race is
still a member of its regatta at that moment. If the race is removed
from its regatta before the status condition is met, the callback is
never fired and both the status listener and the regatta-removal
listener are torn down.
The 'strictly beyond LOADING' condition (rather than 'at or beyond
LOADING') is what makes this primitive work for RaceLogRaceTracker-tracked
races that transition directly from PREPARED to TRACKING without ever
entering the LOADING state.
This will be used to wait for the moment when a tracked race is ready
for maneuver-based wind estimation to be attached (i.e., loading is
done, whatever there was of it) without leaking listeners on races that
never make it there because the user removed them.
Implementations added on TrackedRaceImpl (real behavior), DummyTrackedRace
(no-op stub), and MockedTrackedRaceWithStartTimeAndRanks (no-op stub).
New test TrackedRaceRunWhenPastLoadingTest covers the fast path,
PREPARED->TRACKING skipping LOADING, exactly-once semantics across
multiple transitions, and the removal-before-firing cancellation path.
the get(key, waitForLatest) method now consistently throws a
RuntimeException, regardless of whether it originated from calling get()
on the FutureTask executing an ongoing re-calculation, or reading from
the cache instead of an ongoing re-calculation's FutureTask.
The key was to extend the queue length in FixWindow to the duration used
for validity/outlier detection. This uses the averaging interval in
*both* directions starting from a fix, other than COG/SOG estimation
which uses fixes only in half that interval in each direction.
There are still sporadic test failures, with maneuver candidates being
at slightly different time points between early and late initialization.
Another puzzle to solve?
the damping makes it necessary to turn to the opposite direction a bit
more than only 1.2 times the maneuver threshold when before that the
boat turned the other way. Damping reduces this to below the maneuver
threshold. Increasing this to 1.5 does the job for this test.