Docs: reflect the Design View redesign — EXPOSE replaces the park refusal, FX offlining moved to the reclaimer, undo repair is ours
This commit is contained in:
@@ -45,6 +45,11 @@ settled 2026-07-23):
|
||||
`ViewModeModel::reconcile(liveGuids)`). The same rule binds one level down: a
|
||||
parked track's per-FX offline state keys on the FX's own identity
|
||||
(`TrackFX_GetFXGUID`), never its slot — see `fx_offline`.
|
||||
- **A park record's two halves release independently.** The flag half is spent
|
||||
the moment a restore runs (`releaseSnapshotFlags`); the FX half survives until
|
||||
the reclaimer has actually driven that chain back online (`releaseSnapshotFx`)
|
||||
— releasing both at restore would destroy the only record of which plugins the
|
||||
user had offline before the park, and a later online would blanket-enable them.
|
||||
- **Relative/portable state only** in the persisted view section (GUID strings,
|
||||
mode ids — no absolute paths, no index positions).
|
||||
- **Show-both semantics.** A per-track "pin visible across modes" flag that
|
||||
@@ -103,6 +108,8 @@ settled 2026-07-23):
|
||||
- `mode_switch` — REAPER-free segment layout + hit-test for the bank_panel's Design View mode switch.
|
||||
- `guid_diff` — the pure, REAPER-free core of the D2 Wave-2 new-content detection: `newGuids(previous, current)` computes the GUIDs present in `current` but absent from `previous` (empty GUIDs ignored); `GuidBaseline` tracks the live GUID set across polls for one project, implementing the first-poll-after-open guard (the first `observe()` after construction/`reset()` records a baseline and reports nothing new, so pre-existing content is never mass-tagged) and re-arms via `reset()` on a detected project switch so detection never diffs across two unrelated projects.
|
||||
- `lane_keys` — the pure, REAPER-free convention mapping a fixed lane's durable REAPER name (`P_LANENAME:n`) to the opaque lane-key `view_mode_model` keys by, plus the managed/manual heuristic both the toggle-apply path and the new-content/auto-tag exemption path share: `kManagedLanePrefix` ("reasampler:") stamps every lane the tool mints; `isManagedLaneName`/`managedLaneKey`/`laneNameForMode`/`modeIdFromLaneName` round-trip a lane name ↔ its owning mode id; `isOnManualLane` is the single predicate governing which lanes a toggle may drive and which items are exempt from auto-tag.
|
||||
- `fx_state` — what the FX chains SHOULD be, derived from view state alone: `desiredFxState` (the whole project's Online/Offline work, Online sorted first), `fxRestoreTargets` (one track's Online targets from its park record), and `resolveFxWork` (one item's move resolved by identity against a live chain). Derived and never persisted; the park record's captured FX half is the only durable half.
|
||||
- `applied_state` — a comparable fingerprint (`AppliedState`/`appliedState`) of what the tool's last apply actually drove, so a reconcile can ask "did our output drift from what a restore would drive it back to" rather than "did an undo happen" — membership, lane ownership and the solo cache are deliberately excluded.
|
||||
|
||||
## Gotchas
|
||||
|
||||
|
||||
+87
-129
@@ -27,28 +27,22 @@ decide membership or mode rules.
|
||||
lost, only parked with the mode it belongs to. A REAPPLY (target == active —
|
||||
tag/untag/show-both, project load) touches solo not at all.
|
||||
- **Parking a track** (inactive-mode leaf) drives `B_SHOWINTCP=0`, `B_SHOWINMIXER=0`
|
||||
(hide both panels), `B_MAINSEND=0` (out of mix), `I_FXEN=0` (FX bypassed), and
|
||||
`TrackFX_SetOffline(track, fx, true)` for each FX (reclaim CPU) — full CPU-park,
|
||||
not mix-removal-only.
|
||||
- **A snapshot is only ever taken from a chain no park has touched — BOTH halves
|
||||
of it.** The trust test reads the four driven flags AND the FX chain, and either
|
||||
reading parked alone is enough to refuse: each half is the only source of truth
|
||||
for its own half of the snapshot, so one of them lying poisons the whole record.
|
||||
The FX half asks "is ANY FX offline", not "every" — no read distinguishes a
|
||||
park's leftover from one the user set by hand, and only "any" errs toward
|
||||
committing nothing false. An absent snapshot is NOT evidence of a clean chain —
|
||||
a saved project, a hand-edited chain and an undo/redo can each present one; the
|
||||
Gotchas below enumerate the routes that still reach it. A park meeting that pair
|
||||
REFUSES: no flag write, no FX write, no snapshot, and a
|
||||
console report NAMING the refused tracks (they are hidden from both the TCP and
|
||||
the mixer, so a count points at nothing the user can go and find) plus the hand
|
||||
recovery, printed once per changed refused set per project rather than on every
|
||||
reapply. Leaving the track as found is the only non-destructive answer once the
|
||||
pre-park truth is gone. **The refusal's cost is deliberate and it is not
|
||||
symmetric with "hidden forever":** because it skips the FX half too, a track
|
||||
whose flags read parked but whose FX are still online keeps them online for
|
||||
good. Refusing guarantees nothing false is written; it does not promise to
|
||||
finish the park. Decision at `view_fx_park`'s `decidePark`.
|
||||
(hide both panels), `B_MAINSEND=0` (out of mix), and `I_FXEN=0` (FX bypassed) —
|
||||
flags only. Per-FX `TrackFX_SetOffline` is the reclaimer's job (`fx_reclaim`),
|
||||
off this path entirely.
|
||||
- **EXPOSE, not refuse.** When no record exists for a track
|
||||
(`model.snapshot(guid) == nullptr`) and the live chain already reads
|
||||
park-shaped (`chainReadsParked`, flags only — the FX half is excluded, see its
|
||||
own comment), parking now would adopt a park with no account behind it as the
|
||||
user's intent — an undo/redo or reload that dropped the snapshot, a hand/script
|
||||
edit, or simply a track the user keeps hidden. Instead the tool drives the
|
||||
track visible/in-mix/FX-enabled, stores no snapshot, and reports it once per
|
||||
changed set per project (`reportExposedTracks`/`describeExposedTracks`, which
|
||||
NAME the tracks — they were hidden from both the TCP and the mixer, so a bare
|
||||
count points at nothing the user can go and find). The next apply sees an
|
||||
honest chain and parks/captures it normally — self-healing, no user action
|
||||
required. Daniel's ruling: expose a broken track rather than hide it forever.
|
||||
Decision at `view_park`'s `parkTrack`.
|
||||
- **Non-destructive restore.** For every flag the tool drives, snapshot the prior
|
||||
value BEFORE parking; on toggle-back restore FROM the snapshot, never to a
|
||||
hardcoded "on." Round-trip (snapshot → park → restore) returns every driven flag
|
||||
@@ -66,29 +60,20 @@ decide membership or mode rules.
|
||||
active mode — stateful plugins (convolution, loaded samplers, tail-holding
|
||||
effects) re-initialize on return (load hitch, un-persisted internal state lost).
|
||||
Accepted cost of the CPU reclaim; surfaced at the toggle affordance (tooltip).
|
||||
**The hitch sits on the switch's synchronous path, deliberately.** Per-FX
|
||||
offline/online runs inside `applyMode`'s own undo block, between the flag
|
||||
writes and `Undo_EndBlock2`, so an action-driven switch does not return until
|
||||
every plugin has moved. That cost is the price of the invariant below, and it
|
||||
was accepted as such.
|
||||
- **ONE mode switch is ONE undo point.** Everything the switch wrote into the
|
||||
project — the driven flags, every per-FX offline/online, the lane state —
|
||||
rolls back in a single Ctrl-Z. (The `view_state` ext-state write is NOT among
|
||||
them; it lands after the block, see Gotchas.) What was MEASURED (before any
|
||||
fix): 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. That measurement is the whole reason
|
||||
the FX writes must sit inside a block. Which block is the design decision, and
|
||||
it is settled: the SWITCH'S OWN. The undo surface
|
||||
(`vendor/reaper-sdk/sdk/reaper_plugin_functions.h:7745-7822`) is
|
||||
begin/end/state-change and nothing else — no append-to-point, no coalescing
|
||||
flag, no undo-silent write — and a block cannot be held open across a return
|
||||
to the message loop, so ANY deferral of the writes costs a second undo point.
|
||||
Two points is a rejected design, not a lesser one: do not reintroduce a
|
||||
deferred queue, an idle drain, or a second block. That an explicit block
|
||||
suppresses the per-call implicit points is still the PREMISE the whole
|
||||
arrangement rests on — INFERENCE from the measurement above, not observed;
|
||||
`docs/VERIFICATION.md` §"Mode switching" is where it gets checked.
|
||||
The hitch no longer sits on the switch's synchronous path: per-FX
|
||||
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
|
||||
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
|
||||
@@ -97,20 +82,11 @@ decide membership or mode rules.
|
||||
not cost the user a Ctrl-Z. A REAL switch always mints, written state or not:
|
||||
it is an explicitly fired action and has to stay undoable even when the plan
|
||||
found nothing to write.
|
||||
- **Stated DEVIATION — the undo mask does not keep FX out of a real switch.** The
|
||||
apply mask (`kApplyUndoMask`) drops `UNDO_STATE_FX` and ORs it back in when
|
||||
`I_FXEN` or a per-FX offline state actually moved — which every park does. So
|
||||
any switch that parks at least one track still makes REAPER marshal the
|
||||
project-wide FX chunk into its undo record. The saving is real only on a
|
||||
reapply that wrote nothing and on a no-op switch. Narrowing it further would
|
||||
mean not carrying the FX domain in the undo record at all, which would break
|
||||
"one switch is one Ctrl-Z."
|
||||
- **A rapid A→B→A flip pays two full park/restore cycles.** The retired intent
|
||||
queue let an intent annihilate its own pending inverse, so a fast double-toggle
|
||||
moved no plugins at all. Synchronous writes have no such window: the first
|
||||
switch offlines the chain, the second brings it back, and both hitches are
|
||||
paid. Accepted — the flip was never free (the flag writes always ran twice),
|
||||
and the alternative costs the one-point invariant above.
|
||||
- **A rapid A→B→A flip moves zero plugins.** The reclaimer only starts a park's
|
||||
OFFLINE work once a track has read continuously parked for `kParkHoldMs`
|
||||
(3000ms). A flip inside that window flips the flags twice but the debounce
|
||||
never matures, so the reclaimer finds every chain already where the record
|
||||
wants it and writes nothing.
|
||||
- **Show-both semantics:** a per-track "pin visible across modes" flag re-enables
|
||||
processing whenever shown. A show-both leaf appears in every mode's visible set
|
||||
and is never parked — its driven flags stay at snapshot/restored values, FX
|
||||
@@ -148,20 +124,19 @@ 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`.**
|
||||
- `view_fx_park` — the park surface: ONE track's whole move, both halves together. `parkTrack` is the trust 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), the pre-park snapshot when it is owed, then flags before FX; `restoreTrack` mirrors it, FX before flags, and consumes the snapshot. Each reports back what the caller's block needs (`TrackApplyResult`: did anything move, does the mask owe `UNDO_STATE_FX`, was the park refused, what could a restore not place). Also the `TrackFX_GetFXGUID` identity read all three of snapshot/park/restore address FX through, `trackFlagParm` — the ONE `Flag` → REAPER-parameter mapping they likewise share — the `writeIfChanged` read-before-write every driven parameter goes through, `trackStillLive` (a per-track `ValidatePtr2`, because a plugin unloading is assumed to pump the message loop), and the two console reports (`reportRefusedParks`, which names the tracks and holds the last-reported set so a reapply does not reprint it; `reportFxRestoreDrops`). **The snapshot is consumed where the restore RUNS** — from that line the flags are back at their captured values, and a model still describing the track as parked would let a later persist or reapply replan a restore over whatever the user changed since.
|
||||
- `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_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.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The pure mode model (`ViewModeModel`, membership, `reconcile(liveGuids)`, the
|
||||
snapshot-based park/restore planner) lives in `core/view` — reference it, do not
|
||||
duplicate its spec here.
|
||||
- **`TrackPlan::fxOffline` is read on the RESTORE side only.** `planToggle` builds
|
||||
every park plan with `fxCount=0`, and this shell's park loop passes `tp.flags`
|
||||
alone to `parkTrack`, which expands the real writes itself from
|
||||
`TrackFX_GetCount` — so the park-side field is populated only by tests. A shell
|
||||
change that starts reading it on the park path is reading an empty vector.
|
||||
- The Two-canvas sub-phase (Phase D2/E)'s settled DAW-application rules
|
||||
(fixed-lane mechanics, mode-aware capture placement, hidden-AND-silenced) are
|
||||
reflected in Invariants above; the membership/lane-ownership model concepts
|
||||
@@ -172,37 +147,23 @@ applies the resulting lane state to live tracks.
|
||||
under whatever mode id is active at that point, not the one the user undid back
|
||||
to. Pre-existing: `snapshots_` already carries this same model-vs-undo split;
|
||||
the solo cache inherits it rather than introducing it. Not fixed here.
|
||||
- **Which routes reach the no-snapshot-plus-parked-chain pair, re-derived for the
|
||||
one-point design.** Three of the routes that used to reach it were artifacts of
|
||||
the retired deferral window, and are now NARROWED TO THE RE-ENTRANCY WINDOW
|
||||
rather than gone: the flags-written-FX-not moment still exists inside
|
||||
`parkTrack`, but nothing can observe it except code re-entered through the
|
||||
pump premise below, and `applyMode` fails closed when re-entered
|
||||
(`modeApplyInProgress`). The window that remains is what a call REACHABLE FROM
|
||||
a pumped message loop but not routed through `applyMode` could see — no save,
|
||||
no reload and no discard reaches it (`main.cpp`'s `OnTimer` defers
|
||||
`session.poll()` itself, the one caller of `loadFromProject` a re-entered timer
|
||||
could reach, under this same latch), and there is no queued intent for an undo
|
||||
to drop. What survives:
|
||||
- A `view_state` that PARSED but carries no snapshot for the track — a snapshot
|
||||
`reconcile` pruned while its track was out of the live enumeration, then
|
||||
undo/redo-restored with the track but not the snapshot. (Asserted by the
|
||||
branch that introduced the refusal; not re-observed since.)
|
||||
- A hand or script edit that leaves a chain park-shaped — including the single
|
||||
likeliest real case, a track with untouched flags and just ONE hand-offlined
|
||||
plugin (the FX half of the disjunction refuses alone; the flags never have to
|
||||
read parked too).
|
||||
- A track the USER keeps hidden from both panels, out of the mix and
|
||||
FX-bypassed. It reads identically to both of the above and is refused too —
|
||||
no lost state there at all, which is why the report asserts no cause.
|
||||
- A REDO of a mode switch, hypothetically — the mechanism and its two
|
||||
unverified links are in the `view_state` gotcha below, which owns that
|
||||
question; do not re-derive it here.
|
||||
|
||||
An UNDO of a mode switch (Ctrl-Z) is NOT a route, and no longer needs an
|
||||
ordering premise to say so: flags and FX moved inside ONE block, so they roll
|
||||
back together and no intermediate state exists for a park to misread. The redo
|
||||
direction is not symmetric with it — see below.
|
||||
- **Which routes reach the no-record-plus-parked-chain pair EXPOSE handles.**
|
||||
A `view_state` that PARSED but carries no snapshot for the track — a snapshot
|
||||
`reconcile` pruned while its track was out of the live enumeration, then
|
||||
undo/redo-restored with the track but not the snapshot; a hand or script edit
|
||||
that leaves a chain park-shaped, including the likeliest real case, a track
|
||||
with untouched flags and just ONE hand-offlined plugin (`chainReadsParked`
|
||||
never reads the FX half at all, so this route is flags-only); or a track the
|
||||
USER keeps hidden from both panels, out of the mix and FX-bypassed, which
|
||||
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
|
||||
@@ -210,7 +171,7 @@ applies the resulting lane state to live tracks.
|
||||
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
|
||||
model-vs-undo split the solo-cache gotcha describes: **a redo can plausibly
|
||||
produce a NEW refusal route.** As a HYPOTHESIS — the redo restores the parked
|
||||
produce a NEW EXPOSE route.** As a HYPOTHESIS — the redo restores the parked
|
||||
flags AND the ext state as the point closed over it, i.e. the PRE-switch
|
||||
`view_state`, carrying no snapshot for those tracks (`UNDO_STATE_MISCCFG`
|
||||
covers extension state, `reaper_plugin.h:1544`). The `projectconfig` reload
|
||||
@@ -218,7 +179,8 @@ applies the resulting lane state to live tracks.
|
||||
plans NEITHER a park (the track is a member of the restored active mode) nor a
|
||||
restore (no snapshot to restore from) — verified in `planToggle`, which pushes
|
||||
nothing at all for an active leaf without a snapshot. The tracks stay parked
|
||||
with no snapshot, so the next real switch parks them and `decidePark` refuses.
|
||||
with no snapshot, so the next real switch parks them and `parkTrack` EXPOSEs
|
||||
them instead.
|
||||
TWO links are unverified and the route dies if either fails: (1) that the
|
||||
point's stored state is captured at `Undo_EndBlock2` and therefore excludes the
|
||||
`persistViewState` write that follows it — the SDK documents no capture-time
|
||||
@@ -228,43 +190,39 @@ applies the resulting lane state to live tracks.
|
||||
keeps its snapshots and the redo is consistent. `[verify — DAW]`. Do not "fix"
|
||||
it by moving `persistViewState` inside the block — it can open a modal Save-As;
|
||||
whether to split the persist is a separate decision.
|
||||
- A DIFFERENT strand entirely, which the refusal report does NOT cover: an
|
||||
- A DIFFERENT strand entirely, which the EXPOSE report does NOT cover: an
|
||||
absent or malformed `view_state` (`loadViewModel`, `ext_state_io.cpp`) is not
|
||||
a parked-chain reading at all — it falls back to a DEFAULT model (active mode
|
||||
Arrange, empty membership), so nothing is planned to park the track,
|
||||
`decidePark` never runs, and a track saved parked stays stuck with no console
|
||||
`parkTrack` never runs, and a track saved parked stays stuck with no console
|
||||
line whatsoever.
|
||||
- **`applyMode`'s block can be nested, and now carries FX writes while it is.**
|
||||
`render_in_place` calls `applyMode` inside its own `Undo_BeginBlock2` pair, so
|
||||
that verb's single `UNDO_STATE_ALL` point now absorbs the reapply's FX moves
|
||||
instead of a separate point landing after it — the better outcome, but it rests
|
||||
on REAPER coalescing nested blocks, which the SDK header does not document.
|
||||
The inverse is also open: a plugin unloading is ASSUMED to pump the message
|
||||
loop, so an action fired mid-apply could open its own block inside this one.
|
||||
`applyMode` itself cannot be that action — it fails closed when re-entered
|
||||
(`modeApplyInProgress`, `view.cpp`'s `ApplyLatch`) — but nothing else is
|
||||
latched, `mintManagedLanes` included, and it opens a block of its own. Both are
|
||||
- **`applyMode` fails closed when re-entered — a model-consistency guard now,
|
||||
not a pump-during-FX-writes one.** Nothing `applyMode` calls is known to pump
|
||||
the message loop any more (the FX writes are gone from it), but a re-entrant
|
||||
apply would still plan against the same model the outer call is iterating and
|
||||
consume records out from under it — `modeApplyInProgress`
|
||||
(`view.cpp`'s `ApplyLatch`) drops the re-entrant call rather than risk that. A
|
||||
mode switch or membership reapply fired while another apply is on the stack is
|
||||
DROPPED, not queued; the action callers just no-op, silently
|
||||
(`reportModeSwitchRefused` speaks only for the transport gate) and the user
|
||||
re-fires. The one caller with nothing to fall back on is `main.cpp`'s load
|
||||
glue, which tests `modeApplyInProgress` AND `fxReclaimDrainInProgress` before
|
||||
spending its one-shot signal and retries on the next tick — the reclaimer's
|
||||
own drain (`fx_reclaim`'s `DrainLatch`) is latched for the same reason over
|
||||
the same window: `TrackFX_SetOffline` is what pumps now, and it holds the view
|
||||
model the drain reads, not `applyMode`. `render_in_place`'s own `applyMode`
|
||||
call is the exception to the drop's recovery path, not to the drop itself: its
|
||||
refusal leaves the membership tags it already wrote in place, so the next
|
||||
reapply — not a user re-fire of render-in-place — is what brings the tracks
|
||||
into sync.
|
||||
- `render_in_place` still calls `applyMode` inside its own `Undo_BeginBlock2`
|
||||
pair for the reapply — one point rather than two for the flags and lane state
|
||||
— but with no FX writes left inside `applyMode`'s block there is nothing left
|
||||
to coalesce beyond that; the SDK's nested-block coalescing itself is still
|
||||
`[verify — DAW]`.
|
||||
That same premise put a residual on track handles, now CLOSED by construction
|
||||
rather than by a partial guard. The park and restore loops re-`ValidatePtr2`
|
||||
per track (`trackStillLive`) because they are the ones interleaved with the FX
|
||||
writes; the whole enumeration is then re-validated ONCE where those loops end,
|
||||
so every pass after them — lane ops, parent visibility, the solo replay — sees
|
||||
only handles REAPER still recognizes. A dead track's handle is nulled in place
|
||||
rather than erased from the vector, which is what `view_solo`'s writers already
|
||||
skip on.
|
||||
The latch's own cost, stated: a mode switch or a membership reapply fired while
|
||||
another apply is on the stack is DROPPED, not queued. The one caller with
|
||||
nothing to fall back on is `main.cpp`'s load glue — it spends a one-shot signal
|
||||
— so it tests `modeApplyInProgress` before consuming and retries next tick. The
|
||||
action callers just no-op, silently (`reportModeSwitchRefused` speaks only for
|
||||
the transport gate); the user re-fires. `render_in_place`'s own `applyMode` call
|
||||
is the exception to that recovery path, not to the drop itself: its refusal
|
||||
leaves the membership tags it already wrote in place, so the next reapply — not
|
||||
a user re-fire of render-in-place — is what brings the tracks into sync.
|
||||
- `fx_offline`'s identity keying (`TrackFX_GetFXGUID`) assumes the GUID stays
|
||||
attached to its plugin across a chain mutation while parked. That is
|
||||
`[verify — DAW]` (see `fxGuidString` in `view_fx_park.cpp`) and SWS issue #802 is a
|
||||
`[verify — DAW]` (see `fxGuidString` in `view_park.cpp`) and SWS issue #802 is a
|
||||
known reason it might not hold: `SNM_MoveOrRemoveTrackFX` reportedly leaves
|
||||
the FXID lines behind on reorder rather than moving them with the plugin. If
|
||||
confirmed, an SWS-driven reorder of a parked track's chain — not a native
|
||||
|
||||
Reference in New Issue
Block a user