Collapse the FX-park drain's writes into one undo point instead of one per TrackFX_SetOffline
This commit is contained in:
+23
-13
@@ -71,17 +71,24 @@ decide membership or mode rules.
|
||||
so the new mode paints first. The deferral changes only WHEN the plugins move —
|
||||
they still unload and re-instantiate, and un-persisted internal state is still
|
||||
lost. What it does change is the undo record, and REAPER's behaviour there is
|
||||
now OBSERVED, not assumed: the drain's `TrackFX_SetOffline` calls (the only
|
||||
`TrackFX_SetOffline` in the tree) run outside any undo block, and REAPER mints
|
||||
ONE implicit undo point per call — a small project measured 10 points for one
|
||||
switch, 1 for the switch itself and 9 for its FX. Ctrl-Z walks them one at a
|
||||
time, each step re-onlining a single FX, so one Ctrl-Z after a switch does not
|
||||
bring the plugins back. It also puts the switch point BELOW every FX point: an
|
||||
undo deep enough to restore the driven flags and roll the `"reasampler"` ext
|
||||
state back has already re-onlined every FX, so flags, FX and snapshot move
|
||||
together. The per-FX fragmentation is itself a defect to be closed by collapsing
|
||||
the drain's writes into one undo point; nothing here depends on them staying
|
||||
separate. **The idle tick is not the only drain point.** Any path that
|
||||
now OBSERVED, not assumed. What was measured, before the fix below: a
|
||||
`TrackFX_SetOffline` made outside any undo block mints ONE implicit undo point
|
||||
per call, and a small project showed 10 points for one switch — 1 for the switch
|
||||
itself and 9 for its FX — which Ctrl-Z then walked one FX at a time. **The drain
|
||||
now opens its own undo block** (`fxParkUndoClose`, `UNDO_STATE_FX` only, no other
|
||||
domain), so one drain is one point and one switch costs two: the switch's, then
|
||||
the drain's. A drain that wrote no FX state closes with REAPER's discard form
|
||||
and mints nothing at all, so a plain project open costs no undo point
|
||||
(`fxParkUndoClose` owns why). The ordering property the observation established is DELIBERATELY PRESERVED: the
|
||||
drain's block is its own and never the switch's, so the FX point still sits ABOVE
|
||||
the switch point, and an undo deep enough to restore the driven flags and roll
|
||||
the `"reasampler"` ext state back has already re-onlined every FX — flags, FX and
|
||||
snapshot still move together. Folding the FX writes into the switch's own block,
|
||||
or making them undo-silent, would break that and is not an option here. So ONE
|
||||
Ctrl-Z after a switch now re-onlines every FX at once, and a second rolls the
|
||||
flags and the ext state back — two steps rather than the measured ten, and never
|
||||
one, which is the standing price of the drain being a separate act.
|
||||
**The idle tick is not the only drain point.** Any path that
|
||||
serializes the view model drains synchronously first (`persistViewState`,
|
||||
`render_in_place`), because a save landing between a restore's synchronous flag
|
||||
writes and its drain would record offline FX beside a model that no longer
|
||||
@@ -134,7 +141,7 @@ applies the resulting lane state to live tracks.
|
||||
## Modules
|
||||
|
||||
- `view` — Design View shell: snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (`B_SHOWINTCP`/`B_SHOWINMIXER`/`B_MAINSEND`/`I_FXEN`, with per-FX offline deferred to `view_fx_park`), restores from snapshot. 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`.**
|
||||
- `view_fx_park` — the park surface: the `TrackFX_GetFXGUID` identity read snapshot/park/restore share, the deferred intent queue that keeps `TrackFX_SetOffline` off the switch's synchronous path (at most one intent per track GUID, latest wins, an intent landing on its own pending inverse cancels it), the idle-tick drain `main.cpp`'s `OnTimer` calls, the per-track park decision (`decidePark` over "does the model hold a snapshot" × "does the chain already read parked", with `decideParkForTrack` owning the live reads BOTH halves of that second term needs, and `trackFlagParm` the ONE `Flag` → REAPER-parameter mapping snapshot's read, park's read and restore's write all address), and the refusal report (`reportRefusedParks`, which names the tracks and holds the last-reported set so a reapply does not reprint it). **The drain owns no model state.** A snapshot is dropped where the restore is PLANNED — the flags are back at their captured values from that moment, and a model that still described the track as parked would let a persist or a reapply inside the drain window replan a restore over whatever the user changed since. What the deferral costs instead is that the live FX chain stops being a trustworthy snapshot source while an intent is pending: a park that CANCELS a pending restore takes the pre-park FX states from that restore's ops (`preParkFxFromCancelledRestore`), because the chain still reads the parked values and the cancel means no drain will ever fix them.
|
||||
- `view_fx_park` — the park surface: the `TrackFX_GetFXGUID` identity read snapshot/park/restore share, the deferred intent queue that keeps `TrackFX_SetOffline` off the switch's synchronous path (at most one intent per track GUID, latest wins, an intent landing on its own pending inverse cancels it), the idle-tick drain `main.cpp`'s `OnTimer` calls and the ONE undo point it collapses its writes into (`fxParkUndoClose` — it opens that block itself, so no caller may drain while holding one), the per-track park decision (`decidePark` over "does the model hold a snapshot" × "does the chain already read parked", with `decideParkForTrack` owning the live reads BOTH halves of that second term needs, and `trackFlagParm` the ONE `Flag` → REAPER-parameter mapping snapshot's read, park's read and restore's write all address), and the refusal report (`reportRefusedParks`, which names the tracks and holds the last-reported set so a reapply does not reprint it). **The drain owns no model state.** A snapshot is dropped where the restore is PLANNED — the flags are back at their captured values from that moment, and a model that still described the track as parked would let a persist or a reapply inside the drain window replan a restore over whatever the user changed since. What the deferral costs instead is that the live FX chain stops being a trustworthy snapshot source while an intent is pending: a park that CANCELS a pending restore takes the pre-park FX states from that restore's ops (`preParkFxFromCancelledRestore`), because the chain still reads the parked values and the cancel means no drain will ever fix them.
|
||||
- `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`.
|
||||
|
||||
## Gotchas
|
||||
@@ -163,7 +170,10 @@ applies the resulting lane state to live tracks.
|
||||
FX enqueue too. Full contract at `discardDeferredFxParks`.
|
||||
- **Which routes actually reach that no-snapshot-plus-parked-chain pair.** An undo
|
||||
of a mode switch (Ctrl-Z) is NOT one of them: per the observed undo semantics
|
||||
above, the flags and the FX come back at the same step. Whether the ext-state
|
||||
above, the drain's point sits above the switch's, so every FX is back online by
|
||||
the step that rolls the flags and the ext state back — and the step in between
|
||||
(FX online, flags still parked) still holds the model's snapshot, which decides
|
||||
`ParkOnly`, not a refusal. Whether the ext-state
|
||||
snapshot comes back with them is INFERENCE, not observed, and untested in the
|
||||
REDO direction: `[verify — DAW]` does a redo that re-parks a track's flags/FX
|
||||
also restore a `view_state` that is snapshot-free for it (matching a fresh
|
||||
|
||||
Reference in New Issue
Block a user