Fix undo-label misnomer, tighten drain-nesting/abort-path docs, mark inferred undo behavior as unverified
Renamed the drain's undo point to "Design View FX state" (it restores as often as it parks). Extended the nesting audit to cover a block opening inside the drain's own block. Marked post-fix undo behavior as inference pending DAW checks.
This commit is contained in:
+23
-22
@@ -70,24 +70,24 @@ decide membership or mode rules.
|
||||
offline/online is enqueued and applied on a later idle tick (`view_fx_park`),
|
||||
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. 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.
|
||||
lost. What it does change is the undo record. 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) on the PREMISE that an explicit block
|
||||
suppresses those per-call implicit points. That premise, and everything below
|
||||
that follows from it — one drain being one point, a switch costing two, a
|
||||
no-write drain minting nothing (`fxParkUndoClose` owns why), and ONE Ctrl-Z
|
||||
re-onlining every FX at once with a second rolling the flags and the ext state
|
||||
back — is INFERENCE from the code's design, not observed: `docs/VERIFICATION.md`
|
||||
§"Mode switching" (items 51–53) is where this gets checked against REAPER. The
|
||||
ordering property the pre-fix measurement established is DELIBERATELY
|
||||
PRESERVED BY DESIGN regardless: the drain's block is its own and never the
|
||||
switch's, so the FX point is meant to sit ABOVE the switch point, with flags,
|
||||
FX and snapshot moving together on undo. Folding the FX writes into the
|
||||
switch's own block, or making them undo-silent, would break that and is not an
|
||||
option here.
|
||||
**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
|
||||
@@ -169,10 +169,11 @@ applies the resulting lane state to live tracks.
|
||||
never falsely committed — and never finished either, since the refusal skips the
|
||||
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 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
|
||||
of a mode switch (Ctrl-Z) is NOT one of them, ON THE UNDO-ORDERING PREMISE
|
||||
above (`[verify — DAW]`, not yet observed — see Invariants): if the drain's
|
||||
point does sit above the switch's, 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
|
||||
|
||||
@@ -106,10 +106,16 @@ RestoreOutcome applyRestore(MediaTrack* tr, const std::vector<FxOfflineOp>& ops)
|
||||
// per-FX fragmentation this closes). The block is deliberately the drain's OWN
|
||||
// and never the switch's — the FX point has to stay ABOVE the switch's.
|
||||
//
|
||||
// Closing is unconditional on every exit, the abort path included: an unbalanced
|
||||
// block would swallow every later action into one point, which is far worse than
|
||||
// [verify — DAW] whatever Undo_EndBlock2 does with a ReaProject* that has closed
|
||||
// since Undo_BeginBlock2 took it.
|
||||
// Closing is unconditional on every exit, the abort path included. Which of two
|
||||
// undocumented models REAPER uses decides whether that is the safe call:
|
||||
// under a GLOBAL block-nesting counter, leaving this one unbalanced would
|
||||
// swallow every later action — in ANY project — into one point, far worse than
|
||||
// calling Undo_EndBlock2 on a ReaProject* that closed since Undo_BeginBlock2
|
||||
// took it. Under a PER-PROJECT counter, an unbalanced block on a now-dead
|
||||
// project costs nothing, and the closing call against a stale pointer is the
|
||||
// riskier of the two. Closing unconditionally is the safer bet under the
|
||||
// global model and merely unnecessary under the per-project one, hence the
|
||||
// choice. [verify — DAW] which model REAPER actually implements.
|
||||
class FxParkUndoBlock {
|
||||
public:
|
||||
explicit FxParkUndoBlock(ReaProject* proj) : proj_(proj) { Undo_BeginBlock2(proj_); }
|
||||
|
||||
@@ -283,7 +283,9 @@ struct FxParkUndoClose {
|
||||
|
||||
inline FxParkUndoClose fxParkUndoClose(bool wroteAnyFx) {
|
||||
if (!wroteAnyFx) return {};
|
||||
return FxParkUndoClose{"ReaSampler: Design View FX park", kFxParkUndoMask};
|
||||
// "FX state", not "FX park": the drain restores as often as it parks, and a
|
||||
// direction-coded label would misname the point that brings FX back online.
|
||||
return FxParkUndoClose{"ReaSampler: Design View FX state", kFxParkUndoMask};
|
||||
}
|
||||
|
||||
// Applies every pending intent. Touches NO model state — a restore's snapshot is
|
||||
@@ -307,6 +309,18 @@ inline FxParkUndoClose fxParkUndoClose(bool wroteAnyFx) {
|
||||
// header either way. All three call sites drain outside any block they open
|
||||
// (OnTimer holds none; persistViewState is reached only after applyMode's and
|
||||
// doMoveItems' blocks have closed; render_in_place drains after its EndBlock2).
|
||||
//
|
||||
// That audit is ONE-DIRECTIONAL — it covers the drain opening inside a caller's
|
||||
// block, not the inverse the pumping premise above (FxParkQueue::take) makes
|
||||
// possible: a block opening INSIDE the drain's own open block. Two routes are
|
||||
// live if that premise holds: a re-entrant applyMode (view.cpp) fired by an
|
||||
// action mid-drain, and a re-entrant OnTimer -> bankPanelRefresh ->
|
||||
// mintManagedLanes (view.cpp) — both open their OWN Undo_BeginBlock2/EndBlock2
|
||||
// pair. g_draining (view_fx_park.cpp) only blocks a SECOND drainDeferredFxParks
|
||||
// call; it does nothing to stop a different subsystem's block opening while
|
||||
// this one is still open. `[verify — DAW]` whether that nested pair (undocumented
|
||||
// either way, as above) does anything worse than the ordering already assumed —
|
||||
// see docs/VERIFICATION.md.
|
||||
void drainDeferredFxParks();
|
||||
|
||||
// Drops every pending intent without applying it. Called when the model the
|
||||
|
||||
Reference in New Issue
Block a user