Collapse a Design View mode switch and its background FX parking to ONE undo point
applyMode now returns with its undo block open; the reclaimer closes it when the drain settles. A transport deferral or a tab switch ends it early -- two points, never a leak.
This commit is contained in:
+49
-22
@@ -64,17 +64,35 @@ decide membership or mode rules.
|
||||
offline/online runs on the background reclaimer (`fx_reclaim`), in ticks
|
||||
bounded by `kOnlineBudgetMs`/`kOfflineBudgetMs`, off `applyMode`'s block
|
||||
entirely (measured: `TrackFX_SetOffline` was 1897ms of a 1942ms switch).
|
||||
- **ONE mode switch is ONE undo point.** The driven flags and the lane state
|
||||
written inside `applyMode`'s block roll back in a single Ctrl-Z. (The
|
||||
`view_state` ext-state write is NOT among them; it lands after the block, see
|
||||
Gotchas.) Per-FX offline/online moved off this block entirely, onto the
|
||||
reclaimer (`fx_reclaim`) — each write there runs inside its OWN discarded
|
||||
block (`Undo_EndBlock2(proj, "", 0)`), so it mints nothing. DAW-VERIFIED: ten
|
||||
unblocked `TrackFX_SetOffline` calls minted ten undo points; the same ten
|
||||
calls wrapped in `Undo_BeginBlock2`/`Undo_EndBlock2(proj, "", 0)` minted zero
|
||||
— confirming the discard-block premise the reclaimer's per-write block
|
||||
(`fx_reclaim.cpp`'s `writeOffline`) rests on.
|
||||
- **A reapply that wrote nothing leaves no undo point.** The load-tick reapply
|
||||
- **ONE mode switch is ONE undo point — the background FX parking included.** The
|
||||
driven flags, the lane state, AND the per-FX offline/online the reclaimer writes
|
||||
minutes-of-plugin-loads later all roll back in a single Ctrl-Z, because they share
|
||||
ONE undo block: `applyMode` opens it (`fx_reclaim`'s `UndoSpan`) and a REAL switch
|
||||
RETURNS WITH IT OPEN, for the reclaimer to close once the drain has converged. A
|
||||
reapply closes its own before returning. This does not move an FX write back onto
|
||||
the switch — the writes still run on the timer, off the 1897ms path.
|
||||
DAW-MEASURED, and the reason the design is shaped this way: block WIDTH is the
|
||||
entire effect. For ten `TrackFX_SetOffline` calls from a TIMER — unblocked, or
|
||||
under a block per write, by every dispatch route — REAPER minted TEN points; under
|
||||
ONE block spanning all ten it minted ONE. The `("", 0)` discard form does NOT
|
||||
discard outside an action, it coalesces; it discards only from a hand-fired action.
|
||||
A block opened on one timer callback and closed on a LATER one holds. Whether one
|
||||
held from an ACTION into a later timer callback holds is the same property but is
|
||||
`[verify — DAW]`; if it does not, the switch and its parking land as two points,
|
||||
which is the intended degradation.
|
||||
- **No path may return past a terminating condition with the span open.** A leaked
|
||||
block swallows every edit the user makes afterwards, indefinitely, so every
|
||||
terminating path routes through ONE idempotent close (`fxReclaimEndUndoSpan`): the
|
||||
drain settling, aborting on a generation bump, or deferring on the transport; the
|
||||
project retargeting or closing; the next apply superseding it; a project save;
|
||||
extension unload. The span may stay open only across a wait it can BOUND — the 3s
|
||||
debounce, which is the dead time between the switch and its first FX write, and
|
||||
the only reason the span exists. A TRANSPORT deferral is not bounded (it lasts as
|
||||
long as the playback pass), so it ends the span and the parking that follows mints
|
||||
its own point (`spanEndsAfter`).
|
||||
- **A reapply that wrote nothing leaves no undo point** — and a reapply never holds
|
||||
the span open, so a project open, a membership edit and `render_in_place`'s inner
|
||||
reapply all keep the block shape they had. The load-tick reapply
|
||||
re-plans a park for every inactive leaf, and over a project saved fully parked
|
||||
it finds every flag and every FX already where the plan wants them. The block
|
||||
then closes on the `("", 0)` discard form (`view.h`'s `applyMintsUndoPoint`;
|
||||
@@ -124,13 +142,13 @@ applies the resulting lane state to live tracks.
|
||||
|
||||
## Modules
|
||||
|
||||
- `view` — Design View shell: runs the pure planner over the live folder tree and iterates its park/restore plans, applies managed-lane and parent-visibility writes, and owns the ONE undo block all of that rides (`applyMintsUndoPoint` decides whether it leaves a point). Owns the one discriminator (`target != active`) that separates a real switch from a reapply, and with it both the playback gate (`transportBlocksModeSwitch`) and the solo cache/clear/restore seams. **Never touches master or `B_MUTE`.** FX are NOT written here — see `fx_reclaim`.
|
||||
- `view` — Design View shell: runs the pure planner over the live folder tree and iterates its park/restore plans, applies managed-lane and parent-visibility writes, and OPENS the ONE undo span all of that rides (`applyMintsUndoPoint` decides whether it leaves a point; on a real switch `fx_reclaim` is what closes it). Owns the one discriminator (`target != active`) that separates a real switch from a reapply, and with it both the playback gate (`transportBlocksModeSwitch`) and the solo cache/clear/restore seams. **Never touches master or `B_MUTE`.** FX are NOT written here — see `fx_reclaim`.
|
||||
- `view_park` — the park surface, FLAG DOMAIN ONLY. `parkTrack` captures the pre-park snapshot (flags AND the FX chain's identity-keyed offline state) when the model holds none, drives the four flags, and EXPOSEs a track that reads park-shaped with no record behind it rather than adopting it; `restoreTrack` writes the flags back from the snapshot and spends its flag half (`releaseSnapshotFlags`). Also `trackFlagParm` (the one `Flag` → REAPER-parameter mapping), `writeIfChanged` (the read-before-write every driven parameter goes through), `fxGuidString` (the one FX-identity read snapshot/reclaim both address FX through), `kViewUndoMask`, and `reportExposedTracks`/`describeExposedTracks` (named tracks, once per changed set per project).
|
||||
- `view_lanes` — managed fixed lanes: `mintManagedLanes` splits multi-mode content onto per-mode lanes, `applyLaneOps` drives `C_LANEPLAYS` for the active mode, `reconcileManagedLanes` recovers ownership from durable `P_LANENAME` on project open. Manual lanes are never minted, driven, renamed or reassigned.
|
||||
- `view_reconcile` — the repair pass for a project state REAPER reloaded under us: `noteAppliedState` records what an apply left driven; `reconcileAppliedFlags` diffs that fingerprint against the reloaded model and hands back any track the reload no longer records but still reads driven — the fix for `UNDO_STATE_TRACKCFG` not carrying `B_SHOWINTCP`/`B_SHOWINMIXER` (DAW-confirmed).
|
||||
- `view_tracks` — the scaffolding every write pass sets up: `TrackHandles`/`TrackByGuid`, `readFolderEntries`/`indexHandles`, and `UiRefreshHold` (the `PreventUIRefresh` RAII pair every write phase runs under).
|
||||
- `view_solo` — the `I_SOLO` read/write pair behind the per-mode solo surface, plus `clearTrackSolos`/`restoreTrackSolos`, the outgoing-clear and incoming-replay entry points `view` drives them through. Holds no policy: what to cache, clear, or replay is `core/view/solo_cache`.
|
||||
- `fx_reclaim` — the FX convergence loop, off the switch's synchronous path (`TrackFX_SetOffline` was 1897ms of a measured 1942ms switch). `drainTick` moves one track's FX per step against `core/view/fx_state`'s derived work list (Online before Offline; an Offline item waits out `kParkHoldMs` so a fast A→B→A flip moves nothing), budgeted per tick (`kOnlineBudgetMs`/`kOfflineBudgetMs`) and gated off the transport for the Offline direction only. `FxReclaimQueue` owns the derived queue's lifetime across re-entrancy (a plugin unload is assumed to pump); each `writeOffline` runs inside its own discarded undo block, DAW-verified to mint nothing. `ReleaseFlush`/`fxReclaimFlushRelease` make a completed record's spend durable on the project's next save, never on the drain's own initiative.
|
||||
- `fx_reclaim` — the FX convergence loop, off the switch's synchronous path (`TrackFX_SetOffline` was 1897ms of a measured 1942ms switch). `drainTick` moves one track's FX per step against `core/view/fx_state`'s derived work list (Online before Offline; an Offline item waits out `kParkHoldMs` so a fast A→B→A flip moves nothing), budgeted per tick (`kOnlineBudgetMs`/`kOfflineBudgetMs`) and gated off the transport for the Offline direction only. `FxReclaimQueue` owns the derived queue's lifetime across re-entrancy (a plugin unload is assumed to pump). `UndoSpan` owns the ONE undo block the whole drain writes under — opened by `applyMode` on a real switch, else lazily by the first `writeOffline`, and closed through the single idempotent `fxReclaimEndUndoSpan` that every terminating path routes through (`spanEndsAfter` decides which ticks terminate). `ReleaseFlush`/`fxReclaimFlushRelease` make a completed record's spend durable on the project's next save, never on the drain's own initiative.
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -158,15 +176,24 @@ applies the resulting lane state to live tracks.
|
||||
reads identically and is EXPOSEd too — no lost state there at all, which is
|
||||
why the report never asserts a cause. A REDO of a mode switch is a further,
|
||||
hypothetical route — see the `view_state` gotcha below.
|
||||
- **An UNDO of a mode switch no longer rolls back FX at all.** FX offline/online
|
||||
moved off `applyMode`'s block entirely onto the reclaimer, so a Ctrl-Z restores
|
||||
only the flags and lane state the block wrote — whatever the reclaimer had
|
||||
converged (or had not yet converged) survives the undo untouched. Whether this
|
||||
reads as correct in the DAW is `[verify — DAW]`, `docs/VERIFICATION.md`
|
||||
§"Mode switching".
|
||||
- **The `view_state` write is NOT inside `applyMode`'s block.** `persistViewState`
|
||||
(`design_view_actions.cpp`) runs after `applyMode` has returned, so
|
||||
`SetProjExtState` lands past `Undo_EndBlock2`. The UNDO direction still comes
|
||||
- **An UNDO of a mode switch rolls back the FX the span covered, and only those.**
|
||||
The span carries `UNDO_STATE_FX` only once the drain has actually written FX under
|
||||
it (`UndoSpan::widen`), so a switch whose parking never matured — a fast A→B→A
|
||||
flip — still closes on `kViewUndoMask` alone and costs no FX serialization. What a
|
||||
Ctrl-Z cannot restore is anything the reclaimer converges AFTER the span closed: a
|
||||
transport deferral, and a drain no switch opened a span for, each mint their own
|
||||
point. Whether that reads as correct in the DAW is `[verify — DAW]`,
|
||||
`docs/VERIFICATION.md` §"Mode switching".
|
||||
- **The `view_state` write is NOT inside `applyMode`'s block — but on a REAL switch
|
||||
it is now inside the SPAN.** `persistViewState` (`design_view_actions.cpp`) runs
|
||||
after `applyMode` has returned, and a real switch returns with the span open, so
|
||||
the `SetProjExtState` — and the modal Save-As `persistViewState` can pop on an
|
||||
unsaved project — land inside the block after all. That is a consequence of the
|
||||
span, not a decision made here; it does not resolve the redo hypothesis below,
|
||||
which turns on capture-time semantics the SDK does not document. `[verify — DAW]`
|
||||
that a Save-As dialog opening under an open undo block is harmless. The rest of
|
||||
this gotcha describes the reapply path, where nothing changed. The UNDO direction
|
||||
still comes
|
||||
out right — the point closed over the ext state as it stood BEFORE the switch,
|
||||
which is what a Ctrl-Z should restore — but by position, not by design. The
|
||||
REDO direction is what this leaves open, and the cost is worse than the
|
||||
|
||||
Reference in New Issue
Block a user