Close the collapse's two dropped guarantees: latch applyMode against re-entrancy, pin its project once; correct three overclaiming doc lines
This commit is contained in:
+57
-21
@@ -157,6 +157,11 @@ applies the resulting lane state to live tracks.
|
||||
- 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
|
||||
@@ -169,10 +174,14 @@ applies the resulting lane state to live tracks.
|
||||
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 GONE BY CONSTRUCTION: there is no longer a
|
||||
moment in which a track's flags have been written but its FX have not, so no
|
||||
save, no reload and no discard can land between the two halves, and there is no
|
||||
queued intent for an undo to drop. What survives:
|
||||
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, 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
|
||||
@@ -184,21 +193,39 @@ applies the resulting lane state to live tracks.
|
||||
- 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
|
||||
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.
|
||||
back together and no intermediate state exists for a park to misread. The redo
|
||||
direction is not symmetric with it — see below.
|
||||
- **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
|
||||
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: the point never saw the new
|
||||
`view_state`, so a redo may re-park the tracks while restoring the model that
|
||||
describes the mode the user undid back to. `[verify — DAW]`; if it does bite,
|
||||
the result is the pre-existing model-vs-undo split the solo-cache gotcha above
|
||||
already describes, not a new refusal route. Do not "fix" it by moving
|
||||
`persistViewState` inside the block — it can open a modal Save-As.
|
||||
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
|
||||
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
|
||||
then rebuilds the model from that stale value, and the reapply it triggers
|
||||
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.
|
||||
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
|
||||
semantics for the state mask; (2) that a redo fires `BeginLoadProjectState`
|
||||
with `isUndo == true` so the session really does reload the stale ext state
|
||||
(asserted in `main.cpp`, never observed). If (2) is false the in-memory model
|
||||
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
|
||||
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
|
||||
@@ -212,15 +239,24 @@ applies the resulting lane state to live tracks.
|
||||
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.
|
||||
Nothing guards against that (the retired queue's `g_draining` never did either
|
||||
— it only stopped a second drain). Both are `[verify — DAW]`.
|
||||
That same premise leaves a STATED RESIDUAL on track handles. The park and
|
||||
restore loops re-`ValidatePtr2` per track (`trackStillLive`), because they are
|
||||
the ones interleaved with the FX writes. The passes that run AFTER them — lane
|
||||
ops, parent visibility, the solo replay — still use handles resolved before the
|
||||
first FX write, so a track deleted mid-apply would reach them stale. Not closed:
|
||||
the premise it depends on is itself unobserved, and a partial guard covering
|
||||
three of those four passes would read as complete.
|
||||
`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
|
||||
`[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.
|
||||
- `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
|
||||
|
||||
Reference in New Issue
Block a user