Design View: no undo block may open over the reclaim span, and a reapply closes none
Every opener ends the span first, so a tag fired mid-drain no longer hijacks the switch's point. One stated criterion governs both lane sites; no-op actions discard.
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
#include "shell/capture/item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
|
#include "shell/capture/item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
|
||||||
#include "shell/capture/track_guid.h" // shared MediaTrack* -> canonical GUID key
|
#include "shell/capture/track_guid.h" // shared MediaTrack* -> canonical GUID key
|
||||||
#include "shell/panel/panel_window.h" // bankPanelInvalidate — footer toggle repaint
|
#include "shell/panel/panel_window.h" // bankPanelInvalidate — footer toggle repaint
|
||||||
|
#include "shell/view/fx_reclaim.h" // fxReclaimEndUndoSpan — no block opens over the span
|
||||||
#include "shell/view/view.h" // applyMode (D2 shell)
|
#include "shell/view/view.h" // applyMode (D2 shell)
|
||||||
#include "shell/view/view_lanes.h" // mintManagedLanes
|
#include "shell/view/view_lanes.h" // mintManagedLanes
|
||||||
#include "shell/view/view_park.h" // kViewUndoMask
|
#include "shell/view/view_park.h" // kViewUndoMask
|
||||||
@@ -105,12 +106,16 @@ std::vector<std::string> selectedTrackGuids() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reapplies the CURRENT active mode so a membership mutation takes visible effect
|
// Reapplies the CURRENT active mode so a membership mutation takes visible effect
|
||||||
// immediately (park/unpark/re-derive parents). The return is deliberately discarded: a
|
// immediately (park/unpark/re-derive parents), answering whether it wrote any project
|
||||||
// refused reapply leaves the membership edit standing and unapplied, and the user's next
|
// state — the caller's block mints only if something did. `applied` is deliberately
|
||||||
// edit or mode switch reapplies it. What must NOT be dropped with it is the reclaimer's
|
// discarded: a refused reapply leaves the membership edit standing and unapplied, and
|
||||||
// queue, which is why applyMode invalidates on its refusal path too (view.cpp).
|
// the user's next edit or mode switch reapplies it (a refusal writes nothing, so it
|
||||||
void reapplyActiveMode() {
|
// reports false here and the block discards). What must NOT be dropped with it is the
|
||||||
applyMode(g_session->view(), g_session->view().activeModeId(), nullptr);
|
// reclaimer's queue, which is why applyMode invalidates on its refusal path too
|
||||||
|
// (view.cpp).
|
||||||
|
bool reapplyActiveMode() {
|
||||||
|
return applyMode(g_session->view(), g_session->view().activeModeId(), nullptr)
|
||||||
|
.wroteProjectState;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int kFreeModeFixedLanes = 2; // I_FREEMODE value; mirrors the shell's constant
|
constexpr int kFreeModeFixedLanes = 2; // I_FREEMODE value; mirrors the shell's constant
|
||||||
@@ -189,15 +194,21 @@ void doToggleMode() {
|
|||||||
const std::string target =
|
const std::string target =
|
||||||
nextModeId(g_session->view().modes(), g_session->view().activeModeId());
|
nextModeId(g_session->view().modes(), g_session->view().activeModeId());
|
||||||
if (target.empty()) return; // no modes to cycle to (degenerate)
|
if (target.empty()) return; // no modes to cycle to (degenerate)
|
||||||
if (!applyMode(g_session->view(), target, nullptr)) { reportModeSwitchRefused(); return; }
|
if (!applyMode(g_session->view(), target, nullptr).applied) {
|
||||||
|
reportModeSwitchRefused();
|
||||||
|
return;
|
||||||
|
}
|
||||||
persistViewState(PersistScope::ViewOnly);
|
persistViewState(PersistScope::ViewOnly);
|
||||||
bankPanelInvalidate(); // repaint the footer [Arrange|Design] toggle immediately
|
bankPanelInvalidate(); // repaint the footer [Arrange|Design] toggle immediately
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct jump to a named mode. applyMode is a no-op (returns false, no mutation) if
|
// Direct jump to a named mode. applyMode is a no-op (`applied` false, no mutation) if
|
||||||
// the id is unregistered or the transport is running, so both fail safe.
|
// the id is unregistered or the transport is running, so both fail safe.
|
||||||
void doActivateMode(const std::string& modeId) {
|
void doActivateMode(const std::string& modeId) {
|
||||||
if (!applyMode(g_session->view(), modeId, nullptr)) { reportModeSwitchRefused(); return; }
|
if (!applyMode(g_session->view(), modeId, nullptr).applied) {
|
||||||
|
reportModeSwitchRefused();
|
||||||
|
return;
|
||||||
|
}
|
||||||
persistViewState(PersistScope::ViewOnly);
|
persistViewState(PersistScope::ViewOnly);
|
||||||
bankPanelInvalidate(); // repaint the footer [Arrange|Design] toggle immediately
|
bankPanelInvalidate(); // repaint the footer [Arrange|Design] toggle immediately
|
||||||
}
|
}
|
||||||
@@ -208,18 +219,34 @@ void doActivateMode(const std::string& modeId) {
|
|||||||
// actually fired rather than the reapply it happened to trigger. Every one of them
|
// actually fired rather than the reapply it happened to trigger. Every one of them
|
||||||
// closes the block before persistViewState, which can pop a modal Save-As. Stack RAII
|
// closes the block before persistViewState, which can pop a modal Save-As. Stack RAII
|
||||||
// so a throw between the two calls cannot leave the block open in the user's project.
|
// so a throw between the two calls cannot leave the block open in the user's project.
|
||||||
|
//
|
||||||
|
// ENDING THE RECLAIM SPAN IS PART OF OPENING, and every action here inherits it from
|
||||||
|
// this one constructor. A mode switch fired seconds ago has left the span open; a block
|
||||||
|
// opened over it makes each close pair with the other's open, collapsing the switch
|
||||||
|
// into this tag — mechanism and full rule in src/shell/view/CLAUDE.md.
|
||||||
|
//
|
||||||
|
// `markWrote` decides whether this block mints at all: re-tagging tracks that already
|
||||||
|
// sit in that mode writes nothing to the project, and the ("", 0) discard form is real
|
||||||
|
// from a hand-fired action (DAW-measured, same doc). The membership move itself is
|
||||||
|
// never in the point either way — persistViewState runs after the close.
|
||||||
class ActionUndoBlock {
|
class ActionUndoBlock {
|
||||||
public:
|
public:
|
||||||
ActionUndoBlock(std::string label, int mask) : label_(std::move(label)), mask_(mask) {
|
ActionUndoBlock(std::string label, int mask) : label_(std::move(label)), mask_(mask) {
|
||||||
|
fxReclaimEndUndoSpan();
|
||||||
Undo_BeginBlock2(nullptr);
|
Undo_BeginBlock2(nullptr);
|
||||||
}
|
}
|
||||||
~ActionUndoBlock() { Undo_EndBlock2(nullptr, label_.c_str(), mask_); }
|
~ActionUndoBlock() {
|
||||||
|
if (wrote_) Undo_EndBlock2(nullptr, label_.c_str(), mask_);
|
||||||
|
else Undo_EndBlock2(nullptr, "", 0);
|
||||||
|
}
|
||||||
|
void markWrote() { wrote_ = true; }
|
||||||
ActionUndoBlock(const ActionUndoBlock&) = delete;
|
ActionUndoBlock(const ActionUndoBlock&) = delete;
|
||||||
ActionUndoBlock& operator=(const ActionUndoBlock&) = delete;
|
ActionUndoBlock& operator=(const ActionUndoBlock&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string label_;
|
std::string label_;
|
||||||
int mask_;
|
int mask_;
|
||||||
|
bool wrote_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// tag() replaces any prior single-mode membership (a leaf lives in one mode; the
|
// tag() replaces any prior single-mode membership (a leaf lives in one mode; the
|
||||||
@@ -231,7 +258,7 @@ void doTag(const std::string& modeId) {
|
|||||||
{
|
{
|
||||||
ActionUndoBlock undo("ReaSampler: tag selected tracks -> " + modeId, kViewUndoMask);
|
ActionUndoBlock undo("ReaSampler: tag selected tracks -> " + modeId, kViewUndoMask);
|
||||||
for (const std::string& g : guids) g_session->view().membership().tag(g, modeId);
|
for (const std::string& g : guids) g_session->view().membership().tag(g, modeId);
|
||||||
reapplyActiveMode();
|
if (reapplyActiveMode()) undo.markWrote();
|
||||||
}
|
}
|
||||||
persistViewState(PersistScope::Full);
|
persistViewState(PersistScope::Full);
|
||||||
}
|
}
|
||||||
@@ -245,7 +272,7 @@ void doUntag() {
|
|||||||
{
|
{
|
||||||
ActionUndoBlock undo("ReaSampler: untag selected tracks", kViewUndoMask);
|
ActionUndoBlock undo("ReaSampler: untag selected tracks", kViewUndoMask);
|
||||||
for (const std::string& g : guids) g_session->view().membership().untag(g);
|
for (const std::string& g : guids) g_session->view().membership().untag(g);
|
||||||
reapplyActiveMode();
|
if (reapplyActiveMode()) undo.markWrote();
|
||||||
}
|
}
|
||||||
persistViewState(PersistScope::Full);
|
persistViewState(PersistScope::Full);
|
||||||
}
|
}
|
||||||
@@ -260,7 +287,7 @@ void doShowBoth() {
|
|||||||
ActionUndoBlock undo("ReaSampler: toggle show-both on selected tracks", kViewUndoMask);
|
ActionUndoBlock undo("ReaSampler: toggle show-both on selected tracks", kViewUndoMask);
|
||||||
MembershipIndex& m = g_session->view().membership();
|
MembershipIndex& m = g_session->view().membership();
|
||||||
for (const std::string& g : guids) m.setShowBoth(g, !m.isShowBoth(g));
|
for (const std::string& g : guids) m.setShowBoth(g, !m.isShowBoth(g));
|
||||||
reapplyActiveMode();
|
if (reapplyActiveMode()) undo.markWrote();
|
||||||
}
|
}
|
||||||
persistViewState(PersistScope::Full);
|
persistViewState(PersistScope::Full);
|
||||||
}
|
}
|
||||||
@@ -290,8 +317,11 @@ void doMoveItems(const std::string& targetMode) {
|
|||||||
}
|
}
|
||||||
// Re-drive the same minting/apply path auto-tag uses: mint/split lanes for any
|
// Re-drive the same minting/apply path auto-tag uses: mint/split lanes for any
|
||||||
// track whose items now span modes, then reassert active-mode lane visibility.
|
// track whose items now span modes, then reassert active-mode lane visibility.
|
||||||
mintManagedLanes(g_session->view(), nullptr);
|
// Either half writing is enough to mint; a retag that split no lanes and drove
|
||||||
reapplyActiveMode();
|
// no flag leaves nothing for a point to hold.
|
||||||
|
const bool minted = mintManagedLanes(g_session->view(), nullptr);
|
||||||
|
const bool reapplied = reapplyActiveMode();
|
||||||
|
if (minted || reapplied) undo.markWrote();
|
||||||
}
|
}
|
||||||
|
|
||||||
persistViewState(PersistScope::Full);
|
persistViewState(PersistScope::Full);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "shell/capture/track_guid.h" // guidString
|
#include "shell/capture/track_guid.h" // guidString
|
||||||
#include "shell/panel/panel_input.h" // bankPanelTailSetting
|
#include "shell/panel/panel_input.h" // bankPanelTailSetting
|
||||||
#include "shell/persist/session.h"
|
#include "shell/persist/session.h"
|
||||||
|
#include "shell/view/fx_reclaim.h" // fxReclaimEndUndoSpan
|
||||||
#include "shell/view/view.h" // applyMode
|
#include "shell/view/view.h" // applyMode
|
||||||
#include "shell/view/view_lanes.h" // mintManagedLanes
|
#include "shell/view/view_lanes.h" // mintManagedLanes
|
||||||
|
|
||||||
@@ -133,6 +134,11 @@ void RunRenderTrackInPlace(ReaSamplerSession& session) {
|
|||||||
// visible — never to choose a tag. Both tags below are absolute.
|
// visible — never to choose a tag. Both tags below are absolute.
|
||||||
const std::string activeMode = session.view().activeModeId();
|
const std::string activeMode = session.view().activeModeId();
|
||||||
|
|
||||||
|
// A mode switch fired seconds ago has left the FX reclaimer's undo span open, and
|
||||||
|
// no block may open over it — mechanism in src/shell/view/CLAUDE.md. Ending it here
|
||||||
|
// mints the switch's point, whole, before this block exists; idempotent otherwise.
|
||||||
|
fxReclaimEndUndoSpan();
|
||||||
|
|
||||||
Undo_BeginBlock2(nullptr);
|
Undo_BeginBlock2(nullptr);
|
||||||
|
|
||||||
// flags = 0, never 1: flags&1 adds default envelopes/FX, and a default chain
|
// flags = 0, never 1: flags&1 adds default envelopes/FX, and a default chain
|
||||||
|
|||||||
+74
-21
@@ -74,11 +74,34 @@ decide membership or mode rules.
|
|||||||
block per write, and ONE under a single block spanning all ten — block WIDTH is the
|
block per write, and ONE under a single block spanning all ten — block WIDTH is the
|
||||||
entire effect there. The `("", 0)` discard form does NOT discard outside an action,
|
entire effect there. 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
|
it coalesces; it discards only from a hand-fired action. A block opened on one timer
|
||||||
callback and closed on a LATER one holds. So: a path that writes only flags opens NO
|
callback and closed on a LATER one holds.
|
||||||
block (a repair or a reapply must not cost the user a Ctrl-Z), and a path whose act
|
**The four flags above are the whole of what was measured.** The lane parameters this
|
||||||
IS the user's opens one to make that act undoable at all — which is why the three
|
directory also writes with no block behind them — `C_LANEPLAYS`, `I_FREEMODE`,
|
||||||
membership-edit actions own their own (`design_view_actions`' `ActionUndoBlock`)
|
`C_LANESCOLLAPSED`, `C_LANESETTINGS` (`view_lanes`' `applyLaneOps`) — are assumed
|
||||||
rather than leaning on a reapply that now mints nothing.
|
silent BY INFERENCE from the four, never observed: `[verify — DAW]`. If they turn out
|
||||||
|
to mint unaided, a reapply that repairs a lane leaves a stray point the criterion
|
||||||
|
below did not intend; that is the failure mode to look for.
|
||||||
|
- **ONE CRITERION decides who opens an undo block: the path whose own act has to
|
||||||
|
survive as a point, and no other.** Two things make an act the path's own — it was
|
||||||
|
explicitly fired by the user (a real mode switch, the membership actions,
|
||||||
|
`render_in_place`), or it makes a STRUCTURAL project change no re-derivation
|
||||||
|
reproduces (`view_lanes`' `mintManagedLanes`, which creates lanes and re-homes items;
|
||||||
|
the panel's detect tick fires it, so it is nobody's action, and it opens a block
|
||||||
|
anyway because nothing else would ever put those lanes back). Everything else
|
||||||
|
RE-ASSERTS driven state the view model recomputes from membership on every apply —
|
||||||
|
the park/restore flags, parent visibility, and `applyLaneOps`' `C_LANEPLAYS` plus its
|
||||||
|
`I_FREEMODE` re-assert on a track that already owns managed lanes — and opens none,
|
||||||
|
because a block there could only manufacture the point a project open or a membership
|
||||||
|
edit must not cost. This is why the same `I_FREEMODE` write is blocked at one site and
|
||||||
|
not the other: minting SPLITS a track, `applyLaneOps` only re-states which lane plays.
|
||||||
|
Second clause, binding every site that does open one: **a block that wrote nothing
|
||||||
|
closes on the discard form** (`Undo_EndBlock2(proj, "", 0)` — real from a hand-fired
|
||||||
|
action, per the measurement above), so re-tagging tracks already in that mode leaves
|
||||||
|
no empty point. The one exception is a REAL SWITCH, which mints written state or not:
|
||||||
|
it is an explicitly fired action and has to stay undoable even where the plan found
|
||||||
|
nothing to write. "Wrote" means a REAPER write throughout — model moves persist
|
||||||
|
through `persistViewState`, outside every block, so no point could carry them
|
||||||
|
(`ModeApplyResult`, `view.h`).
|
||||||
- **ONE mode switch is ONE undo point — the background FX parking included.** The
|
- **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
|
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
|
minutes-of-plugin-loads later all roll back in a single Ctrl-Z, because they share
|
||||||
@@ -92,12 +115,37 @@ decide membership or mode rules.
|
|||||||
block swallows every edit the user makes afterwards, indefinitely, so every
|
block swallows every edit the user makes afterwards, indefinitely, so every
|
||||||
terminating path routes through ONE idempotent close (`fxReclaimEndUndoSpan`): the
|
terminating path routes through ONE idempotent close (`fxReclaimEndUndoSpan`): the
|
||||||
drain settling, aborting on a generation bump, or deferring on the transport; the
|
drain settling, aborting on a generation bump, or deferring on the transport; the
|
||||||
project retargeting or closing; the next apply superseding it; ANY PROJECT-STATE
|
project retargeting or closing; the next REAL SWITCH superseding it; ANY PROJECT-STATE
|
||||||
LOAD; a project save; extension unload. The span may stay open only across a wait it
|
LOAD; a project save; extension unload; and any path about to open a block of its own
|
||||||
|
(next invariant). 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
|
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
|
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
|
(it lasts as long as the playback pass), so it ends the span and the parking that
|
||||||
follows mints its own point (`spanEndsAfter`).
|
follows mints its own point (`spanEndsAfter`).
|
||||||
|
- **NOTHING MAY OPEN AN UNDO BLOCK WHILE THE SPAN IS OPEN — every opener ends it
|
||||||
|
first, as its first act.** REAPER's blocks are read as a DEPTH COUNTER, not a stack
|
||||||
|
of identities (`[verify — DAW]`, and the same undocumented nested-block behaviour the
|
||||||
|
Gotchas below name): `Undo_EndBlock2` at depth 2->1 mints nothing and drops the label
|
||||||
|
and mask it was handed, and the close that does reach depth 0 mints under whatever
|
||||||
|
label IT carries. So a block opened over the span leaves each close paired with the
|
||||||
|
other's open — the switch's point gone, its parking relabelled as the intruder's act,
|
||||||
|
and the `UNDO_STATE_FX` the drain widened the span to (`UndoSpan::widen`) dropped,
|
||||||
|
which is the exact loss the widen exists to prevent. The RULE does not rest on that
|
||||||
|
reading: ending the span first yields the switch's point and the opener's as two
|
||||||
|
correctly-labelled points under the counter reading AND under an identity-pairing one,
|
||||||
|
which is why it is stated as an unconditional discipline rather than a workaround for
|
||||||
|
a behaviour nobody has observed. Ending it first mints the switch's point whole, at
|
||||||
|
depth 0, and leaves the opener's block the only one in play. The
|
||||||
|
openers, all of them: `design_view_actions`' `ActionUndoBlock` (whose constructor
|
||||||
|
ends the span, so all four membership actions inherit it), `view_lanes`'
|
||||||
|
`mintManagedLanes`, and `shell/capture`'s `render_in_place`. The mirror half is
|
||||||
|
`applyMode`'s: a REAPPLY never closes a span, precisely because it is reachable from
|
||||||
|
inside one of those blocks — only a real switch supersedes, and a real switch is
|
||||||
|
always fired at depth 0. RESIDUAL, out of this rule's reach: an extension undo block
|
||||||
|
ELSEWHERE (a capture, a bank verb, an ingest) that pumps the message loop can re-enter
|
||||||
|
the timer, and a drain tick that settles in there closes the span at depth 2->1 with
|
||||||
|
the same result. Closing that needs every extension-owned `Undo_BeginBlock2` to route
|
||||||
|
through one opener that ends the span — not attempted here.
|
||||||
- **EVERY project-state load terminates the span itself, at the hook, before REAPER
|
- **EVERY project-state load terminates the span itself, at the hook, before REAPER
|
||||||
restores anything — `isUndo` does not narrow it.**
|
restores anything — `isUndo` does not narrow it.**
|
||||||
`fxReclaimAbandonForStateLoad` runs UNBRANCHED from `BeginLoadProjectState`
|
`fxReclaimAbandonForStateLoad` runs UNBRANCHED from `BeginLoadProjectState`
|
||||||
@@ -137,13 +185,13 @@ decide membership or mode rules.
|
|||||||
rather than resuming on the next tick. (That a revert leaves the view model stale at
|
rather than resuming on the next tick. (That a revert leaves the view model stale at
|
||||||
all is the identity poll's defect, not the reclaimer's — this invariant only keeps the
|
all is the identity poll's defect, not the reclaimer's — this invariant only keeps the
|
||||||
reclaimer from acting on it.)
|
reclaimer from acting on it.)
|
||||||
- **A reapply opens no undo block at all, so it can leave no point** — whether it
|
- **A reapply opens no undo block and CLOSES NO SPAN, so it can leave no point** —
|
||||||
wrote or not, and whoever drove it: a project open, a membership edit,
|
whether it wrote or not, and whoever drove it: a project open, a membership edit,
|
||||||
`render_in_place`'s inner reapply. Nothing a reapply writes mints unaided (see the
|
`render_in_place`'s inner reapply. The open half is the criterion above (a reapply
|
||||||
measurement above), so a block there could only manufacture the Ctrl-Z opening a
|
re-asserts derived state and is nobody's act); the close half is the nesting rule
|
||||||
project must not cost. A caller whose own act needs to stay undoable spans it
|
above (a reapply runs inside its driver's block, so a supersede-close from there
|
||||||
itself. A REAL switch still always mints, written state or not: it is an explicitly
|
would land at depth 2->1 and hijack it). It reports what it wrote instead, and the
|
||||||
fired action and has to stay undoable even when the plan found nothing to write.
|
driver holding the block decides — `ModeApplyResult::wroteProjectState`.
|
||||||
- **A rapid A→B→A flip moves zero plugins.** The reclaimer only starts a park's
|
- **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`
|
OFFLINE work once a track has read continuously parked for `kParkHoldMs`
|
||||||
(3000ms). A flip inside that window flips the flags twice but the debounce
|
(3000ms). A flip inside that window flips the flags twice but the debounce
|
||||||
@@ -186,7 +234,7 @@ applies the resulting lane state to live tracks.
|
|||||||
|
|
||||||
## Modules
|
## 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 on a REAL SWITCH opens the ONE undo span all of that rides (`fx_reclaim` is what closes it; a reapply opens none). 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 on a REAL SWITCH ends any prior span and opens the ONE undo span all of that rides (`fx_reclaim` is what closes it; a reapply opens none and closes none). Reports what it wrote (`ModeApplyResult`) so a caller holding its own block can discard an empty 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_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_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). It opens no undo block: it runs in the wake of a Ctrl-Z, and its writes are flags only.
|
- `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). It opens no undo block: it runs in the wake of a Ctrl-Z, and its writes are flags only.
|
||||||
@@ -288,12 +336,17 @@ applies the resulting lane state to live tracks.
|
|||||||
refusal leaves the membership tags it already wrote in place, so the next
|
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
|
reapply — not a user re-fire of render-in-place — is what brings the tracks
|
||||||
into sync.
|
into sync.
|
||||||
- `render_in_place` calls `applyMode` inside its own `Undo_BeginBlock2` pair, and
|
- **"Nothing nests on the reapply paths" was FALSE, and cost the switch its undo
|
||||||
that block is now the only one in play on that path — the reapply opens none of
|
point.** `render_in_place` and the membership actions each call `applyMode` inside
|
||||||
its own, so nothing nests and the SDK's undocumented nested-block coalescing
|
their own `Undo_BeginBlock2` pair; the reapply opens no block of its own, but a prior
|
||||||
stopped being load-bearing here. The same holds for the membership actions'
|
REAL SWITCH's span can still be open — for `kParkHoldMs` plus the whole drain — and
|
||||||
`ActionUndoBlock`; `doMoveItems` is the one that still nests, over
|
it is a block like any other. Two things nest there, and both are now handled by the
|
||||||
`mintManagedLanes`.
|
"nothing may open a block while the span is open" invariant: the caller's block over
|
||||||
|
the span (each caller ends the span first), and the reapply's former unconditional
|
||||||
|
supersede-close inside the caller's block (only a real switch closes now). What still
|
||||||
|
nests legitimately: `doMoveItems` and `render_in_place` over `mintManagedLanes`, both
|
||||||
|
properly paired inside a single outer block, so the SDK's undocumented nested-block
|
||||||
|
coalescing is load-bearing only for that shape.
|
||||||
- `fx_offline`'s identity keying (`TrackFX_GetFXGUID`) assumes the GUID stays
|
- `fx_offline`'s identity keying (`TrackFX_GetFXGUID`) assumes the GUID stays
|
||||||
attached to its plugin across a chain mutation while parked. That is
|
attached to its plugin across a chain mutation while parked. That is
|
||||||
`[verify — DAW]` (see `fxGuidString` in `view_park.cpp`) and SWS issue #802 is a
|
`[verify — DAW]` (see `fxGuidString` in `view_park.cpp`) and SWS issue #802 is a
|
||||||
|
|||||||
+28
-21
@@ -63,19 +63,20 @@ bool modeApplyInProgress() { return g_applying; }
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool applyModeImpl(ViewModeModel& model, const std::string& targetModeId, ReaProject* proj) {
|
ModeApplyResult applyModeImpl(ViewModeModel& model, const std::string& targetModeId,
|
||||||
if (g_applying) return false; // see ApplyLatch; same fail-closed shape as the guards below
|
ReaProject* proj) {
|
||||||
|
if (g_applying) return {}; // see ApplyLatch; same fail-closed shape as the guards below
|
||||||
ApplyLatch latch;
|
ApplyLatch latch;
|
||||||
|
|
||||||
if (!model.modes().contains(targetModeId)) {
|
if (!model.modes().contains(targetModeId)) {
|
||||||
return false; // reject before touching the project — no partial apply
|
return {}; // reject before touching the project — no partial apply
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pinned ONCE: resolving "current project" independently at the begin and the end
|
// Pinned ONCE: resolving "current project" independently at the begin and the end
|
||||||
// would let a tab switch mid-apply open the block on one project and close it on
|
// would let a tab switch mid-apply open the block on one project and close it on
|
||||||
// another.
|
// another.
|
||||||
ReaProject* const project = proj ? proj : EnumProjects(-1, nullptr, 0);
|
ReaProject* const project = proj ? proj : EnumProjects(-1, nullptr, 0);
|
||||||
if (!ValidatePtr2(nullptr, project, "ReaProject*")) return false;
|
if (!ValidatePtr2(nullptr, project, "ReaProject*")) return {};
|
||||||
|
|
||||||
// The discriminator behind both halves of the contract in view.h. A gate placed
|
// The discriminator behind both halves of the contract in view.h. A gate placed
|
||||||
// unconditionally here would break tagging and the project-load reapply during
|
// unconditionally here would break tagging and the project-load reapply during
|
||||||
@@ -85,7 +86,7 @@ bool applyModeImpl(ViewModeModel& model, const std::string& targetModeId, ReaPro
|
|||||||
const bool realSwitch = targetModeId != outgoingModeId;
|
const bool realSwitch = targetModeId != outgoingModeId;
|
||||||
|
|
||||||
if (realSwitch && transportBlocksModeSwitch(project)) {
|
if (realSwitch && transportBlocksModeSwitch(project)) {
|
||||||
return false; // same fail-closed shape as the mode-exists guard above
|
return {}; // same fail-closed shape as the mode-exists guard above
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackHandles handleByGuid;
|
TrackHandles handleByGuid;
|
||||||
@@ -115,17 +116,20 @@ bool applyModeImpl(ViewModeModel& model, const std::string& targetModeId, ReaPro
|
|||||||
(targetMode ? targetMode->displayName : targetModeId) + " view";
|
(targetMode ? targetMode->displayName : targetModeId) + " view";
|
||||||
|
|
||||||
bool laneModeChanged = false;
|
bool laneModeChanged = false;
|
||||||
|
bool wrote = false; // REAPER writes only — see ModeApplyResult
|
||||||
std::vector<std::string> exposedNames; // named while handles are fresh, reported after the writes
|
std::vector<std::string> exposedNames; // named while handles are fresh, reported after the writes
|
||||||
|
|
||||||
// A prior switch's span, if the reclaimer has not closed it yet: this apply
|
// ONLY A REAL SWITCH, both halves — end a prior switch's still-open span, then open
|
||||||
// supersedes it, so that parking lands as its own point rather than nesting under
|
// this one's. A REAPPLY must do neither: it opens none because nothing it writes
|
||||||
// ours. Idempotent — it reaches nothing when none is open.
|
// mints unaided, and it CLOSES none because it is reachable from inside a caller's
|
||||||
fxReclaimEndUndoSpan();
|
// own block (a membership action, render_in_place), where a close would land at
|
||||||
|
// depth 2->1 and hijack the switch's point instead of minting it. A caller that
|
||||||
// ONLY a real switch opens one. Nothing a reapply writes mints on its own, so a
|
// opens a block ends the span itself, first, at depth 0 — see this directory's
|
||||||
// block around it could only manufacture the undo point a project open must not
|
// CLAUDE.md. Both calls are idempotent; do not reintroduce either unconditionally.
|
||||||
// cost — do not reintroduce one here (see this directory's CLAUDE.md).
|
if (realSwitch) {
|
||||||
if (realSwitch) fxReclaimBeginUndoSpan(project);
|
fxReclaimEndUndoSpan();
|
||||||
|
fxReclaimBeginUndoSpan(project);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
UiRefreshHold uiHold; // every write below lands with the TCP/MCP frozen
|
UiRefreshHold uiHold; // every write below lands with the TCP/MCP frozen
|
||||||
|
|
||||||
@@ -144,6 +148,7 @@ bool applyModeImpl(ViewModeModel& model, const std::string& targetModeId, ReaPro
|
|||||||
MediaTrack* tr = resolve(trackByGuid, guid);
|
MediaTrack* tr = resolve(trackByGuid, guid);
|
||||||
if (!tr) continue; // stale/deleted GUID — prune
|
if (!tr) continue; // stale/deleted GUID — prune
|
||||||
const TrackParkResult r = parkTrack(model, guid, tr, tp.flags);
|
const TrackParkResult r = parkTrack(model, guid, tr, tp.flags);
|
||||||
|
if (r.wrote) wrote = true;
|
||||||
// Named here, not after the block: TrackList_AdjustWindows below can
|
// Named here, not after the block: TrackList_AdjustWindows below can
|
||||||
// invalidate the handle.
|
// invalidate the handle.
|
||||||
if (r.exposed) exposedNames.push_back(trackDisplayName(tr));
|
if (r.exposed) exposedNames.push_back(trackDisplayName(tr));
|
||||||
@@ -154,13 +159,14 @@ bool applyModeImpl(ViewModeModel& model, const std::string& targetModeId, ReaPro
|
|||||||
const std::string& guid = tp.flags.front().guid;
|
const std::string& guid = tp.flags.front().guid;
|
||||||
MediaTrack* tr = resolve(trackByGuid, guid);
|
MediaTrack* tr = resolve(trackByGuid, guid);
|
||||||
if (!tr) continue; // stale/deleted GUID — prune
|
if (!tr) continue; // stale/deleted GUID — prune
|
||||||
restoreTrack(model, guid, tr, tp.flags);
|
if (restoreTrack(model, guid, tr, tp.flags)) wrote = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MANAGED LANES: the active mode's lane plays+shows, every other managed lane
|
// MANAGED LANES: the active mode's lane plays+shows, every other managed lane
|
||||||
// is silenced+hidden. Empty on a D1-only project — byte-identical there.
|
// is silenced+hidden. Empty on a D1-only project — byte-identical there.
|
||||||
const LaneApplyResult lanes = applyLaneOps(trackByGuid, plan.lanes);
|
const LaneApplyResult lanes = applyLaneOps(trackByGuid, plan.lanes);
|
||||||
laneModeChanged = lanes.freeMode;
|
laneModeChanged = lanes.freeMode;
|
||||||
|
if (lanes.wrote) wrote = true;
|
||||||
|
|
||||||
// PARENT VISIBILITY (never parked): recomputed every toggle, never recorded.
|
// PARENT VISIBILITY (never parked): recomputed every toggle, never recorded.
|
||||||
// Only the two visibility flags — never mainSend/FX on a parent.
|
// Only the two visibility flags — never mainSend/FX on a parent.
|
||||||
@@ -170,8 +176,8 @@ bool applyModeImpl(ViewModeModel& model, const std::string& targetModeId, ReaPro
|
|||||||
MediaTrack* tr = resolve(trackByGuid, node.guid);
|
MediaTrack* tr = resolve(trackByGuid, node.guid);
|
||||||
if (!tr) continue; // stale GUID — prune
|
if (!tr) continue; // stale GUID — prune
|
||||||
const double show = visible.count(node.guid) ? 1.0 : 0.0;
|
const double show = visible.count(node.guid) ? 1.0 : 0.0;
|
||||||
writeIfChanged(tr, "B_SHOWINTCP", show);
|
if (writeIfChanged(tr, "B_SHOWINTCP", show)) wrote = true;
|
||||||
writeIfChanged(tr, "B_SHOWINMIXER", show);
|
if (writeIfChanged(tr, "B_SHOWINMIXER", show)) wrote = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.setActiveMode(targetModeId);
|
model.setActiveMode(targetModeId);
|
||||||
@@ -212,13 +218,14 @@ bool applyModeImpl(ViewModeModel& model, const std::string& targetModeId, ReaPro
|
|||||||
// an undo rolling the model back underneath it. In memory, never on disk.
|
// an undo rolling the model back underneath it. In memory, never on disk.
|
||||||
noteAppliedState(project, model);
|
noteAppliedState(project, model);
|
||||||
|
|
||||||
return true;
|
return ModeApplyResult{true, wrote};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool applyMode(ViewModeModel& model, const std::string& targetModeId, ReaProject* proj) {
|
ModeApplyResult applyMode(ViewModeModel& model, const std::string& targetModeId,
|
||||||
const bool applied = applyModeImpl(model, targetModeId, proj);
|
ReaProject* proj) {
|
||||||
|
const ModeApplyResult result = applyModeImpl(model, targetModeId, proj);
|
||||||
|
|
||||||
// Every membership, snapshot and active-mode mutation in the system reaches the
|
// Every membership, snapshot and active-mode mutation in the system reaches the
|
||||||
// project through this call, so this is the whole invalidation surface: the
|
// project through this call, so this is the whole invalidation surface: the
|
||||||
@@ -231,7 +238,7 @@ bool applyMode(ViewModeModel& model, const std::string& targetModeId, ReaProject
|
|||||||
// else would drop a queue that now converges toward the mode the user just left.
|
// else would drop a queue that now converges toward the mode the user just left.
|
||||||
// The cost when the refusal followed no mutation is one re-derivation on the timer.
|
// The cost when the refusal followed no mutation is one re-derivation on the timer.
|
||||||
invalidateFxReclaim();
|
invalidateFxReclaim();
|
||||||
return applied;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace reasampler
|
} // namespace reasampler
|
||||||
|
|||||||
+23
-4
@@ -27,6 +27,21 @@ bool transportBlocksModeSwitch(ReaProject* proj);
|
|||||||
// one-shot signal on a call that would be refused — see main.cpp's load glue.
|
// one-shot signal on a call that would be refused — see main.cpp's load glue.
|
||||||
bool modeApplyInProgress();
|
bool modeApplyInProgress();
|
||||||
|
|
||||||
|
// What one applyMode did.
|
||||||
|
//
|
||||||
|
// `wroteProjectState` counts REAPER writes ONLY — a driven flag, a lane parameter,
|
||||||
|
// a parent's visibility. Model moves (a snapshot stored, a record's flag half spent,
|
||||||
|
// the active mode set) are deliberately excluded: they reach disk through
|
||||||
|
// persistViewState, which every caller runs OUTSIDE its undo block, so no point can
|
||||||
|
// carry them and counting them would only mint an empty one. It is the answer a
|
||||||
|
// caller holding its own block needs for the discard decision (design_view_actions'
|
||||||
|
// ActionUndoBlock); the solo pair cannot affect it, since only a real switch drives
|
||||||
|
// solo and a real switch mints regardless.
|
||||||
|
struct ModeApplyResult {
|
||||||
|
bool applied = false;
|
||||||
|
bool wroteProjectState = false;
|
||||||
|
};
|
||||||
|
|
||||||
// Captures each about-to-park track's prior flag values into `model`, caches/clears
|
// Captures each about-to-park track's prior flag values into `model`, caches/clears
|
||||||
// the outgoing mode's solo state and replays the incoming mode's, runs planToggle,
|
// the outgoing mode's solo state and replays the incoming mode's, runs planToggle,
|
||||||
// applies the park/restore flag writes plus managed-lane and parent-visibility
|
// applies the park/restore flag writes plus managed-lane and parent-visibility
|
||||||
@@ -41,16 +56,20 @@ bool modeApplyInProgress();
|
|||||||
// A REAL switch therefore RETURNS WITH ITS UNDO BLOCK STILL OPEN (fx_reclaim's
|
// A REAL switch therefore RETURNS WITH ITS UNDO BLOCK STILL OPEN (fx_reclaim's
|
||||||
// UndoSpan): the reclaimer closes it once the parking has drained, which is what keeps
|
// UndoSpan): the reclaimer closes it once the parking has drained, which is what keeps
|
||||||
// the switch and its FX writes ONE point rather than two. A reapply opens none, so a
|
// the switch and its FX writes ONE point rather than two. A reapply opens none, so a
|
||||||
// caller that wants its own act undoable opens its own (design_view_actions does).
|
// caller that wants its own act undoable opens its own (design_view_actions does) —
|
||||||
|
// and, because REAPER's blocks are a depth counter, ENDS THE SPAN BEFORE IT OPENS
|
||||||
|
// THAT BLOCK. A reapply never closes a span, precisely because it can be running
|
||||||
|
// inside such a caller's block.
|
||||||
//
|
//
|
||||||
// Returns false (no mutation) if `targetModeId` isn't registered, if this is a real
|
// `applied` is false (no mutation) if `targetModeId` isn't registered, if this is a
|
||||||
// switch (target != active) while the transport is running, if it is re-entered
|
// real switch (target != active) while the transport is running, if it is re-entered
|
||||||
// (modeApplyInProgress), or if the project does not validate. A reapply (target ==
|
// (modeApplyInProgress), or if the project does not validate. A reapply (target ==
|
||||||
// active) is never gated and never touches solo. `proj` == nullptr means the current
|
// active) is never gated and never touches solo. `proj` == nullptr means the current
|
||||||
// project, resolved ONCE and used for every call this apply makes.
|
// project, resolved ONCE and used for every call this apply makes.
|
||||||
//
|
//
|
||||||
// Either way — applied or refused — this drops the FX reclaimer's derived queue, which
|
// Either way — applied or refused — this drops the FX reclaimer's derived queue, which
|
||||||
// is why callers may safely discard the return after mutating the model.
|
// is why callers may safely discard the return after mutating the model.
|
||||||
bool applyMode(ViewModeModel& model, const std::string& targetModeId, ReaProject* proj);
|
ModeApplyResult applyMode(ViewModeModel& model, const std::string& targetModeId,
|
||||||
|
ReaProject* proj);
|
||||||
|
|
||||||
} // namespace reasampler
|
} // namespace reasampler
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "core/view/lane_keys.h"
|
#include "core/view/lane_keys.h"
|
||||||
#include "core/view/view_tree.h"
|
#include "core/view/view_tree.h"
|
||||||
#include "shell/capture/item_read.h"
|
#include "shell/capture/item_read.h"
|
||||||
|
#include "shell/view/fx_reclaim.h" // fxReclaimEndUndoSpan — no block opens over the span
|
||||||
#include "shell/view/view_park.h" // writeIfChanged — the read-before-write every driven parm takes
|
#include "shell/view/view_park.h" // writeIfChanged — the read-before-write every driven parm takes
|
||||||
|
|
||||||
#define REAPERAPI_MINIMAL
|
#define REAPERAPI_MINIMAL
|
||||||
@@ -276,6 +277,11 @@ bool mintManagedLanes(ViewModeModel& model, ReaProject* proj) {
|
|||||||
|
|
||||||
const TrackByGuid trackByGuid = indexHandles(handleByGuid);
|
const TrackByGuid trackByGuid = indexHandles(handleByGuid);
|
||||||
|
|
||||||
|
// No block may open over a still-open reclaim span — mechanism in this directory's
|
||||||
|
// CLAUDE.md. Placed past the empty-plan return above deliberately: a detect tick
|
||||||
|
// that mints nothing must not shorten a span it never competes with.
|
||||||
|
fxReclaimEndUndoSpan();
|
||||||
|
|
||||||
// `proj` deliberately NOT pinned the way applyMode pins it: nothing between this
|
// `proj` deliberately NOT pinned the way applyMode pins it: nothing between this
|
||||||
// begin and its end pumps the message loop, so no tab switch can land between them.
|
// begin and its end pumps the message loop, so no tab switch can land between them.
|
||||||
Undo_BeginBlock2(proj);
|
Undo_BeginBlock2(proj);
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ class ReaProject;
|
|||||||
namespace reasampler {
|
namespace reasampler {
|
||||||
|
|
||||||
// `freeMode` alone decides whether UpdateTimeline is owed (SDK: required after an
|
// `freeMode` alone decides whether UpdateTimeline is owed (SDK: required after an
|
||||||
// I_FREEMODE change); `wrote` is any project write at all, which is what the apply's
|
// I_FREEMODE change); `wrote` is a REAPER write, the reading ModeApplyResult
|
||||||
// undo-point decision reads.
|
// (view.h) carries through to the caller's undo-point decision.
|
||||||
struct LaneApplyResult {
|
struct LaneApplyResult {
|
||||||
bool freeMode = false;
|
bool freeMode = false;
|
||||||
bool wrote = false;
|
bool wrote = false;
|
||||||
@@ -24,14 +24,22 @@ struct LaneApplyResult {
|
|||||||
|
|
||||||
// Reconciles each op's durable laneKey to the track's current ordinal (a
|
// Reconciles each op's durable laneKey to the track's current ordinal (a
|
||||||
// stale/renamed/deleted key is pruned, never mis-driven) and drives C_LANEPLAYS.
|
// stale/renamed/deleted key is pruned, never mis-driven) and drives C_LANEPLAYS.
|
||||||
|
//
|
||||||
|
// Opens NO undo block, where mintManagedLanes below opens one over the same
|
||||||
|
// parameters — the one criterion this directory's CLAUDE.md states, applied: this
|
||||||
|
// re-asserts derived state the view model recomputes from membership on every apply,
|
||||||
|
// so nothing here needs to survive as a point of its own; minting creates lanes and
|
||||||
|
// re-homes items, which no re-derivation reproduces.
|
||||||
LaneApplyResult applyLaneOps(const TrackByGuid& handleByGuid,
|
LaneApplyResult applyLaneOps(const TrackByGuid& handleByGuid,
|
||||||
const std::vector<LanePlayOp>& lanes);
|
const std::vector<LanePlayOp>& lanes);
|
||||||
|
|
||||||
// Splits any track visible in more than one mode while carrying its own media into
|
// Splits any track visible in more than one mode while carrying its own media into
|
||||||
// fixed lanes (one managed lane per involved mode), assigns items, and records
|
// fixed lanes (one managed lane per involved mode), assigns items, and records
|
||||||
// ownership in `model`. Wraps the mutation in one Undo block when non-empty. Returns
|
// ownership in `model`. Wraps the mutation in one Undo block when non-empty — see
|
||||||
// true if any lane was minted (repaint hint). `proj` == nullptr means the current
|
// applyLaneOps above for why this site opens one and that one does not — and ENDS THE
|
||||||
// project.
|
// RECLAIM SPAN first, since REAPER's blocks are a depth counter and this is reachable
|
||||||
|
// from a timer tick with a prior switch's span still open. Returns true if any lane
|
||||||
|
// was minted (repaint hint). `proj` == nullptr means the current project.
|
||||||
bool mintManagedLanes(ViewModeModel& model, ReaProject* proj);
|
bool mintManagedLanes(ViewModeModel& model, ReaProject* proj);
|
||||||
|
|
||||||
// Recovers managed-lane ownership from durable P_LANENAME on project open —
|
// Recovers managed-lane ownership from durable P_LANENAME on project open —
|
||||||
|
|||||||
@@ -144,7 +144,6 @@ TrackParkResult parkTrack(ViewModeModel& model, const std::string& guid, MediaTr
|
|||||||
snap.fxKeying = prior->fxKeying;
|
snap.fxKeying = prior->fxKeying;
|
||||||
}
|
}
|
||||||
model.storeSnapshot(guid, snap);
|
model.storeSnapshot(guid, snap);
|
||||||
result.wrote = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyFlags(tr, flags)) result.wrote = true;
|
if (applyFlags(tr, flags)) result.wrote = true;
|
||||||
@@ -153,9 +152,9 @@ TrackParkResult parkTrack(ViewModeModel& model, const std::string& guid, MediaTr
|
|||||||
|
|
||||||
bool restoreTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
|
bool restoreTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
|
||||||
const std::vector<TrackFlagOp>& flags) {
|
const std::vector<TrackFlagOp>& flags) {
|
||||||
applyFlags(tr, flags);
|
const bool wrote = applyFlags(tr, flags);
|
||||||
model.releaseSnapshotFlags(guid);
|
model.releaseSnapshotFlags(guid);
|
||||||
return true;
|
return wrote;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string trackDisplayName(MediaTrack* tr) {
|
std::string trackDisplayName(MediaTrack* tr) {
|
||||||
|
|||||||
@@ -93,11 +93,16 @@ extern const int kViewUndoMask;
|
|||||||
// Returns whether it wrote.
|
// Returns whether it wrote.
|
||||||
bool writeIfChanged(MediaTrack* tr, const char* parm, double value);
|
bool writeIfChanged(MediaTrack* tr, const char* parm, double value);
|
||||||
|
|
||||||
// What one parkTrack call did. `wrote` is any project write at all — a flag write
|
// What one parkTrack call did. `wrote` is a REAPER write — a driven flag moved —
|
||||||
// or a record write — and feeds the apply's undo-point fold. `exposed` is true iff
|
// and nothing else: storing the snapshot is a MODEL move, and the model reaches disk
|
||||||
// this call handed the track back visible instead of parking or adopting it; the
|
// outside every undo block, so counting it would mint a point carrying none of it
|
||||||
// caller (view.cpp) collects exposed tracks across the whole apply for the one
|
// (ModeApplyResult, view.h). The two readings part company on exactly one path — a
|
||||||
// batched console report.
|
// RE-PARK whose restore had already handed the flags back at park-shaped values, which
|
||||||
|
// stores a fresh snapshot and writes no flag — and the REAPER reading is the one that
|
||||||
|
// wants: nothing in the project moved, so nothing is there for a point to hold.
|
||||||
|
// `exposed` is true iff this call handed the track back visible instead of parking or
|
||||||
|
// adopting it; the caller (view.cpp) collects exposed tracks across the whole apply for
|
||||||
|
// the one batched console report.
|
||||||
struct TrackParkResult {
|
struct TrackParkResult {
|
||||||
bool wrote = false;
|
bool wrote = false;
|
||||||
bool exposed = false;
|
bool exposed = false;
|
||||||
@@ -170,8 +175,9 @@ inline bool shouldReport(bool sameOwnerAsLast, const std::vector<std::string>& l
|
|||||||
void reportExposedTracks(ReaProject* proj, const std::vector<std::string>& trackNames);
|
void reportExposedTracks(ReaProject* proj, const std::vector<std::string>& trackNames);
|
||||||
|
|
||||||
// Restores one returning leaf from its record and spends the record's flag half.
|
// Restores one returning leaf from its record and spends the record's flag half.
|
||||||
// Always true: the record is consumed here, so this apply changed state and is not
|
// Returns whether a FLAG actually moved — the same REAPER-writes-only reading
|
||||||
// repeatable, which is exactly what its one reader (the undo-point decision) asks.
|
// TrackParkResult::wrote carries, and for the same reason: spending the record is a
|
||||||
|
// model move no undo point can hold.
|
||||||
bool restoreTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
|
bool restoreTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
|
||||||
const std::vector<TrackFlagOp>& flags);
|
const std::vector<TrackFlagOp>& flags);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user