Cut shell/actions, bank_ops, app comment bloat ~48% (comments only, zero code change)

This commit is contained in:
2026-07-29 20:49:31 -04:00
parent 1f24c4b095
commit 58c6d49261
19 changed files with 514 additions and 1074 deletions
+24 -57
View File
@@ -1,16 +1,11 @@
// bank_ops.cpp — the promptless bank-verb seam (Q-W6 lift; see bank_ops.h for the
// contract). The ONE implementation home of the bank verbs (create / rename /
// delete / evacuate / activate / move / copy / remove): each mutates the given
// session's book() then persists via persistBankOp() (one bank op = one Ctrl-Z; a
// true index no-op opens NO undo point). It DOES mutate the bank BOOK — but only
// the index/model + ext-state, never the arrange, never a sample file on disk
// (bank ops are index-only; files stay put — CONTEXT.md §Multi-bank).
// REFERENCE-INVALIDATION GUARDRAIL: after a STRUCTURAL mutation any
// Bank*/BankModel& is invalid — verbs take ids and resolve fresh per model call.
// bank_ops.cpp — see bank_ops.h for the contract. The ONE implementation home of the
// bank verbs: each mutates the given session's book() then persists via
// persistBankOp() (one bank op = one Ctrl-Z; a true index no-op opens NO undo
// point). Index/model + ext-state only — never the arrange, never a file on disk.
// REFERENCE-INVALIDATION GUARDRAIL: after a STRUCTURAL mutation any Bank*/BankModel&
// is invalid — verbs take ids and resolve fresh per model call.
//
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are
// extern (CLAUDE.md §contract). DAW-verified, not unit tested.
// main.cpp owns the API pointers; this TU gets them extern. DAW-verified, not unit tested.
#include "shell/bank_ops/bank_ops.h"
@@ -31,9 +26,7 @@ namespace reasampler {
namespace {
// Mints a fresh, genuine REAPER GUID string as a stable bank id (the B2/model
// design: ids are caller-supplied and stable; the model stays pure and mints none).
// Distinct from a track GUID by origin only — both are canonical guidToString output.
// Ids are caller-supplied and stable; the model stays pure and mints none.
std::string mintBankId() {
GUID g{};
genGuid(&g);
@@ -44,35 +37,20 @@ std::string mintBankId() {
} // namespace
// Persists a completed bank-index verb as a SINGLE batched REAPER undo point (R-B) —
// one bank op = one Ctrl-Z.
// WHY THIS WRAPS: a bank verb mutates ONLY our project ext-state, which REAPER's
// undo system captures iff UNDO_STATE_MISCCFG is set (the SDK documents MISCCFG as
// covering extensions' project ext-state). We pass exactly UNDO_STATE_MISCCFG, not
// -1/UNDO_STATE_ALL — a bank verb touches no tracks/FX/items, so snapshotting them
// would be both heavier and wrong. Persist runs INSIDE the block so the post-mutation
// ext-state is the block's "after" image.
//
// WHY THIS WRAPS AND saveToActiveProject() DOES NOT: a bank verb mutates ONLY our
// project ext-state (SetProjExtState under "reasampler"), which REAPER's undo system
// captures iff UNDO_STATE_MISCCFG is set in the Undo_EndBlock2 flags — the SDK
// documents MISCCFG as covering "extensions!" project ext-state (reaper_plugin.h
// ~1544, ~1199). We pass exactly UNDO_STATE_MISCCFG (not -1 / UNDO_STATE_ALL as the
// item-move family does): a bank verb touches no tracks, FX, items, or envelopes, so
// snapshotting them would be both heavier and semantically wrong. The persist runs
// INSIDE the block so the post-mutation ext-state is the block's "after" image.
//
// UNSAVED-PROJECT GUARDRAIL: on an unsaved / no-active project saveToActiveProject()
// no-ops (nothing is written to ext state). We must still CLOSE the block we opened,
// but with an EMPTY label and a zero flag so REAPER DISCARDS the point instead of
// recording a no-effect undo entry — mirroring view.cpp's empty-plan close. The
// in-session model change stands and persists on the user's next save; it just earns
// no undo point until there is a project to persist into (undo of an unsaved bank op
// has nothing to roll back to anyway). The Begin/End must still be balanced, hence
// the close-either-way. (Quiet persist by design — mirrors the CAPTURE path, NOT the
// Design-View path; deliberately NO Save-As prompt.)
// UNSAVED-PROJECT GUARDRAIL: on an unsaved/no-active project saveToActiveProject()
// no-ops; we still CLOSE the block, but with an empty label + zero flag so REAPER
// discards the point instead of recording a no-effect undo entry. Quiet persist by
// design (mirrors capture, not Design-View) — deliberately no Save-As prompt.
void persistBankOp(ReaSamplerSession& session, const char* label,
bool bumpGeneration) {
Undo_BeginBlock2(nullptr);
// S9: bump the bank-generation counter INSIDE the block, before the persist, so the
// fresh generation rides the same ext-state write (saveToActiveProject() stamps
// bankGeneration()). Bumped only for content-changing verbs (the caller decides); a
// pure-organizational verb passes false and leaves the counter be, so a
// rename/activate does not needlessly refresh live instances.
if (bumpGeneration) session.bumpBankGeneration();
const bool persisted = session.saveToActiveProject();
if (persisted)
@@ -109,7 +87,6 @@ bool bankOpDelete(ReaSamplerSession& session, const std::string& bankId,
bool bankOpEvacuate(ReaSamplerSession& session, const std::string& bankId) {
if (!session.book().evacuate(bankId)) return false; // pool is a destination, not a source
// S9: evacuate moves members between banks (bank membership changes) -> bump.
persistBankOp(session, "ReaSampler: evacuate bank", /*bumpGeneration=*/true);
return true;
}
@@ -120,14 +97,10 @@ bool bankOpActivate(ReaSamplerSession& session, const std::string& bankId) {
return true;
}
// NO-OP GUARDRAIL — VERB-AWARE (a collapse means different things per verb):
// * MOVE collapse: the source entry WAS removed (bank_book moveSample removes
// unconditionally before the dest add collapses on hash), so the index DID
// mutate — it counts toward opening an undo point.
// * COPY collapse: the source is left intact AND the dest already held the hash,
// so NOTHING changed — a true index no-op. It must NOT open an undo point.
// Hence: copy counts only real gains; move counts gains OR collapses. Ids pass
// straight to the model op — no BankModel& cached across the loop's mutations.
// NO-OP GUARDRAIL, verb-aware: a MOVE collapse still removed the source entry (the
// index DID mutate), but a COPY collapse left the source intact AND the dest already
// held the hash (a true no-op) — so copy counts only real gains, move counts gains
// OR collapses.
bool bankOpTransfer(ReaSamplerSession& session,
const std::vector<std::string>& sampleIds,
const std::string& srcBankId, const std::string& destBankId,
@@ -151,18 +124,14 @@ bool bankOpTransfer(ReaSamplerSession& session,
}
const bool mutated = copy ? (ok > 0) : (ok > 0 || collapsed > 0);
if (!mutated) return false; // nothing changed — no persist, no undo point
// S9: a move/copy changes bank membership (a sample arrives in / leaves a bank an
// instance may reference) -> bump so assigned instances refresh hands-free.
persistBankOp(session,
copy ? "ReaSampler: copy sample(s)" : "ReaSampler: move sample(s)",
/*bumpGeneration=*/true);
return true;
}
// Index-only, this-bank scope (fork R-A: the sole surfaced verb; RemoveScope::AllBanks
// stays latent in the model). Non-destructive to the file: a last-reference remove
// leaves the file on disk, orphaned until Phase R prune — remove NEVER deletes bytes
// (the manifest is untouched). Silent: recoverability is the batched undo (R-B).
// Index-only, this-bank scope; non-destructive to the file (a last-reference remove
// leaves the file orphaned until prune). Silent: recoverability is the batched undo.
bool bankOpRemove(ReaSamplerSession& session,
const std::vector<std::string>& sampleIds,
const std::string& srcBankId) {
@@ -174,8 +143,6 @@ bool bankOpRemove(ReaSamplerSession& session,
RemoveResult::Removed)
++removed;
if (removed == 0) return false; // every id already absent — no undo point
// S9: a remove drops a sample from a bank (an instance referencing it must refresh —
// it will resolve to silence, per the stale-id policy) -> bump.
persistBankOp(session, "ReaSampler: remove sample(s)", /*bumpGeneration=*/true);
return true;
}
+28 -47
View File
@@ -1,22 +1,14 @@
#pragma once
// bank_ops — the promptless bank-verb seam (Q-W6 lift of the Q-W4 single-owner
// verbs out of shell/panel/panel_bank_ops into a NON-UI home). Each verb is a model
// op on the given session's BankBook + persistBankOp (undo-batched ext-state
// persist) — NO prompts, NO message boxes, NO panel-state nudges, NO panel-global
// reads. The two UX surfaces consume these as thin skins:
// bank_ops — the promptless bank-verb seam. Each verb is a model op on the given
// session's BankBook + persistBankOp (undo-batched ext-state persist) — NO prompts,
// NO message boxes, NO panel-state nudges. Two UX surfaces consume these as thin
// skins: shell/panel/panel_bank_ops (menu prompts/confirms/repaints) and
// shell/actions/bank_actions (bindable family, text prompts/console feedback).
//
// * shell/panel/panel_bank_ops — the panel's menu handlers (prompts / confirms /
// repaints), passing the panel's live session.
// * shell/actions/bank_actions — the bindable family (text prompts / console
// feedback), passing its registered session.
//
// The session arrives BY REFERENCE: there is exactly one session pointer question
// per call site (the caller's), so a missing session can never be half-reported as
// a model rejection from in here (the Q-W4 review's fail-safe-collapse concern).
// Every verb returns whether the model accepted the mutation — a rejected op
// persists nothing and opens no undo point.
//
// REAPER-facing (persist + undo blocks + GUID minting) but SDK-free in this header.
// The session arrives BY REFERENCE, so a missing session can never be
// half-reported as a model rejection from in here. Every verb returns whether the
// model accepted the mutation — a rejected op persists nothing and opens no undo
// point. REAPER-facing (persist + undo blocks + GUID minting) but SDK-free header.
#include <string>
#include <vector>
@@ -25,58 +17,47 @@ namespace reasampler {
class ReaSamplerSession;
// Mints a stable GUID bank id, creates `name` in the book. Returns the new bank id,
// or "" when the model rejects the name (duplicate, trimmed + case-insensitive).
// Create is purely organizational — no generation bump.
// Returns "" when the model rejects the name (duplicate, trimmed + case-insensitive).
// Purely organizational — no generation bump.
std::string bankOpCreate(ReaSamplerSession& session, const std::string& name);
// Renames `bankId`. False when the model rejects (pool un-renamable / name in use).
// False when the model rejects (pool un-renamable / name in use).
bool bankOpRename(ReaSamplerSession& session, const std::string& bankId,
const std::string& newName);
// Deletes `bankId`. False when the model rejects (pool un-deletable). The caller
// passes `bumpGeneration` from the member count it read BEFORE any evacuate/delete
// (an evacuate-then-delete flow must still bump on the ORIGINAL membership).
// False when the model rejects (pool un-deletable). `bumpGeneration` should be the
// member count read BEFORE any evacuate/delete (an evacuate-then-delete flow must
// still bump on the ORIGINAL membership).
bool bankOpDelete(ReaSamplerSession& session, const std::string& bankId,
bool bumpGeneration);
// Evacuates `bankId`'s members to the pool. False when the model rejects (the pool
// itself). Bumps the generation (membership changed).
// False when the model rejects (the pool itself). Bumps the generation.
bool bankOpEvacuate(ReaSamplerSession& session, const std::string& bankId);
// Activates `bankId` as the capture target. False on an unknown id. No bump.
// False on an unknown id. No bump.
bool bankOpActivate(ReaSamplerSession& session, const std::string& bankId);
// Moves (copy=false) or copies (copy=true) `sampleIds` from `srcBankId` to
// `destBankId` (index-only; files never relocate). Returns whether the index
// actually mutated — the verb-aware no-op guardrail: a COPY collapse changes
// nothing (no undo point); a MOVE collapse did remove the source entry (counts).
// Persists ONE undo point ("move/copy sample(s)") only when mutated.
// Index-only; files never relocate. Returns whether the index actually mutated — a
// COPY collapse changes nothing (no undo point), a MOVE collapse did remove the
// source entry (counts). Persists one undo point only when mutated.
bool bankOpTransfer(ReaSamplerSession& session,
const std::vector<std::string>& sampleIds,
const std::string& srcBankId, const std::string& destBankId,
bool copy);
// Removes `sampleIds` from `srcBankId` (index-only, this-bank scope; never deletes
// bytes). Returns whether anything was removed; persists one undo point when so.
// Index-only, this-bank scope; never deletes bytes. Persists one undo point when
// anything was removed.
bool bankOpRemove(ReaSamplerSession& session,
const std::vector<std::string>& sampleIds,
const std::string& srcBankId);
// Persists a completed bank-index verb as a single REAPER undo point (R-B).
// Wraps the session persist (SetProjExtState) in a Begin/End block with
// UNDO_STATE_MISCCFG so the bank op is one Ctrl-Z. On an unsaved / no-active project
// the persist no-ops and the block is closed with an empty label + zero flag (REAPER
// discards it). Callers must invoke this ONLY after a successful/effective mutation —
// rejected ops (duplicate name, un-deletable pool, etc.) must return before reaching
// here so no empty undo point is ever opened for a no-op.
// Wraps the session persist in a Begin/End undo block (UNDO_STATE_MISCCFG) so the
// bank op is one Ctrl-Z; on an unsaved/no-active project the block closes empty
// (REAPER discards it). Call ONLY after an effective mutation.
//
// S9 bank-generation bump: pass `bumpGeneration = true` for a verb that changes what a
// live instance would PLAY — move / copy / remove / evacuate / delete-with-members. Leave
// it false (the default) for a PURELY ORGANIZATIONAL verb — create / rename / activate /
// reorder. The bump (when requested) happens INSIDE the block, BEFORE the persist, so
// the stamped counter rides the same ext-state write and undo captures the pre/post
// generation with the rest of the blob.
// `bumpGeneration = true` for a verb that changes what a live instance would PLAY;
// leave false for a purely organizational verb. The bump happens INSIDE the block,
// before the persist, so the stamped counter rides the same ext-state write.
void persistBankOp(ReaSamplerSession& session, const char* label,
bool bumpGeneration = false);