view: assert the FX-park coalescing delay's debounce; correct four overclaiming doc/comment claims

Extracts parkReadyAt/parkIsReady as a tested pure fold per PLAN.md's phase
criterion; the rest is wording fixes — hitch bound, hazard width, forced-drain
scope, progressive CPU reclaim.
This commit is contained in:
2026-08-03 15:50:07 -04:00
parent 6e2128e937
commit 761125d0fe
4 changed files with 114 additions and 69 deletions
+15 -9
View File
@@ -33,14 +33,19 @@ namespace {
// swallow a rapid A→B→A flip whole — that flip's restore then cancels the park
// outright and the plugins never move at all — and short enough that the window
// in which a user could add an FX to the track the switch just hid, and so miss
// the pre-park snapshot, stays implausible. Seconds, not minutes: this delay is
// the width of the hazard documented at tickDeferredFxParks.
// the pre-park snapshot, stays implausible. Seconds, not minutes; this is ONE
// term of the hazard's width — see tickDeferredFxParks for the other.
constexpr double kParkCoalesceSeconds = 1.0;
FxParkQueue g_queue;
ReaProject* g_owner = nullptr; // the project the pending intents were enqueued against
bool g_draining = false;
double g_parkReadyAt = 0.0; // time_precise() stamp; re-armed by every enqueued park
// The gate is GLOBAL, not per-track: a park enqueued for track Z re-arms this
// same stamp even though it does nothing to an already-partial park on track A.
// Benign — that track's FX are already I_FXEN=0/B_MAINSEND=0 from the
// synchronous half, so only its CPU reclaim (not correctness) is delayed — but
// this is not a per-intent debounce.
double g_parkReadyAt = 0.0; // parkReadyAt() stamp; re-armed by every enqueued park
// Makes "one drain at a time" explicit rather than implied by the call sites.
// RAII because an apply can throw and a stuck flag would silence the queue for
@@ -81,10 +86,11 @@ int firstOnlineFx(MediaTrack* tr) {
return -1;
}
// One track by GUID. The park tick resolves ONE track and does so afresh every
// tick — a whole-project map would be rebuilt per tick for a single lookup, and
// caching the handle across ticks is exactly the dangle ValidatePtr2 is here to
// prevent.
// One track by GUID, resolved afresh every tick — caching the handle across
// ticks is exactly the dangle ValidatePtr2 is here to prevent. Still O(tracks),
// still one guidString() per track per tick; a per-tick unordered_map keyed by
// GUID would cost the same enumeration to build for a single lookup, so the only
// thing avoided by NOT building one is that allocation.
MediaTrack* findTrack(ReaProject* proj, const std::string& guid) {
const int count = CountTracks(proj);
for (int i = 0; i < count; ++i) {
@@ -169,7 +175,7 @@ std::vector<FxOfflineOp> deferFxPark(ReaProject* proj, const std::string& guid)
adoptOwner(proj);
// Re-armed, not set once: while switches keep arriving the park keeps
// waiting, which is the whole point of coalescing them.
g_parkReadyAt = time_precise() + kParkCoalesceSeconds;
g_parkReadyAt = parkReadyAt(time_precise(), kParkCoalesceSeconds);
return g_queue.park(guid);
}
@@ -239,7 +245,7 @@ void drainDeferredFxRestores() {
void tickDeferredFxParks() {
if (!drainGateOpen()) return;
if (time_precise() < g_parkReadyAt) return; // still coalescing
if (!parkIsReady(time_precise(), g_parkReadyAt)) return; // still coalescing
const std::string guid = g_queue.nextParkGuid();
if (guid.empty()) return; // restores only — not this half's work