Cut core/wire and shell/persist comment bloat ~46% (comments only, zero code change)

This commit is contained in:
2026-07-29 20:49:28 -04:00
parent 1f24c4b095
commit 8dac5b4a54
19 changed files with 638 additions and 1286 deletions
+40 -97
View File
@@ -1,65 +1,25 @@
// session.cpp — the ReaSamplerSession lifecycle half of the persist seam (Q-W5
// split of the former persist.cpp; see session.h for the TU map): the poll-driven
// identity-transition detection and the deferred undo/redo reload drain.
// session.cpp — the ReaSamplerSession lifecycle half of the persist seam (see
// session.h for the identity-transition design and the TU map).
//
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
// Compiled into the reaper_reasampler module. Includes reaper_plugin_functions.h
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp is the one TU that defines the API
// pointers; here they are extern (CLAUDE.md §contract).
//
// PROJECT-LOAD / SAVE-AS DETECTION (chosen mechanism):
// Driven by REAPER's "timer" register (main.cpp). Each poll() reads the active
// project (EnumProjects(-1)), its .rpp path, and the GUID we store in its ext
// state. Identity is layered GUID-PRIMARY, with the ReaProject* pointer as the
// secondary disambiguator (classifyProjectTransition owns the exact order):
// * different stored GUID -> a different project of record -> LOAD its index;
// NEVER relocate. Catches pointer RECYCLING (REAPER reuses a closed project's
// address, so a reopened/new project can present the previous pointer with a
// different GUID), new/unsaved<->saved, and switching between distinct saved
// projects.
// * SAME GUID, DIFFERENT object -> a forked sibling that copied our GUID via
// Save-As -> LOAD its index; NEVER relocate; re-GUID it so the siblings
// diverge going forward.
// * SAME GUID, SAME object, .rpp path changed -> genuine Save-As to a new
// location -> relocate the bank folder from the old dir to the new one, then
// re-GUID.
// Why GUID-primary (W12 fix): this layers the two prior designs. M4 (GUID-only)
// broke Save-As forks — Save-As copies the whole .rpp incl. our stored GUID, so a
// fork and its parent share a GUID on disk; switching between them read as a
// Save-As and clobbered a bank. W10 (pointer-primary, GUID voided) broke pointer
// RECYCLING — a reopened/new project reusing the previous project's address read
// as NoOp/SaveAsRelocate and the bank never reloaded. Checking the GUID first
// catches recycling; the pointer then separates a fork (same GUID, different
// object -> Load) from a Save-As (same GUID, same object, new path -> relocate).
// classifyProjectTransition (pure, capture_paths) takes a `sameProjectObject`
// bool (poll() computes `proj == lastProject_`) so the decision stays REAPER-free
// and testable; poll() executes the verdict.
// classifyProjectTransition (pure, capture_paths) takes a `sameProjectObject`
// bool so the decision stays REAPER-free and testable; poll() executes the
// verdict. REAPER exposes no stable per-project GUID, so we mint one (genGuid/
// guidToString) under kProjExtGuidKey; Save-As copies the whole .rpp including
// our ext state, so the new project initially shares the old GUID, and poll()
// re-GUIDs it after relocating (or on the forked-sibling Load branch).
//
// REAPER exposes no stable per-project GUID (GetSetProjectInfo_String has no
// PROJECT_GUID desc; GetProjectStateChangeCount is a session-local counter, not
// a cross-open identity), so we MINT one with genGuid/guidToString and store it
// under kProjExtGuidKey (ext_state_io.cpp owns the minting helpers). On Save-As
// REAPER copies the whole .rpp incl. our ext state, so the new project initially
// shares the old GUID; poll() re-GUIDs it (after relocating, or on the forked-
// sibling Load branch) so identities diverge.
//
// Rationale for the timer: the brief mandates ext-state storage (rules out the
// projectconfig .rpp-line hook for STORAGE), and the timer composes cleanly with
// ext-state while covering identity-transition load + Save-As detection in one
// place.
//
// DIVISION OF LABOUR (R-B undo):
// * Identity-transition poll (this file, classifyProjectTransition) owns
// open / tab-switch / new / forked-sibling / Save-As-relocation — every case
// where the project OF RECORD changes.
// * The `projectconfig` hook (main.cpp registers project_config_extension_t;
// BeginLoadProjectState with isUndo) owns UNDO/REDO — where the project
// identity is unchanged but its ext state rolled back/forward on disk. The
// identity poll sees NoOp there and would never re-read ext state, so the hook
// requests a reload (requestReload) that poll() drains on the next tick, once
// REAPER has restored the <EXTSTATE> block. See requestReload / the poll drain.
// The hook fires on undo AND redo (isUndo true for both), and on normal open
// (isUndo false) — but we set the reload flag ONLY for isUndo, so a normal open
// flows solely through the identity-transition Load path and never double-loads.
// Division of labour for undo/redo: the identity-transition poll (this file)
// owns open/tab-switch/new/forked-sibling/Save-As. The `projectconfig` hook
// (main.cpp, BeginLoadProjectState with isUndo) owns undo/redo, where identity
// is unchanged but ext state rolled back/forward on disk — the identity poll
// would see NoOp there, so the hook requests a reload that poll() drains next
// tick, once REAPER has restored the <EXTSTATE> block. The hook fires on
// undo, redo, AND normal open, but the reload flag is set only for isUndo, so
// a normal open never double-loads.
#include "shell/persist/session.h"
@@ -91,9 +51,8 @@ bool ReaSamplerSession::consumeLoadSignal() {
}
void ReaSamplerSession::requestReload() {
// Set-only; poll() drains it on the next tick (see the poll() drain block for why
// the read is deferred past the projectconfig callback). Cheap and idempotent —
// multiple undo/redo callbacks before the next tick collapse to one reload.
// Set-only; poll() drains it next tick. Idempotent — multiple undo/redo
// callbacks before the next tick collapse to one reload.
reloadRequested_ = true;
}
@@ -116,19 +75,13 @@ void ReaSamplerSession::poll() {
return;
}
// Undo/redo reload (owner: the projectconfig hook, NOT the identity classifier
// below). An undo/redo keeps the SAME project identity — same ReaProject*, GUID,
// and .rpp path — so classifyProjectTransition would return NoOp and never re-read
// ext state, leaving book_/view_ stale after the on-disk ext state rolled back.
// The projectconfig BeginLoadProjectState callback (isUndo) raised reloadRequested_
// one or more ticks ago; by NOW REAPER has finished restoring the project's
// <EXTSTATE> block, so GetProjExtState returns the POST-undo value. Reload from the
// current active project and identity-adopt it (no relocation — the path is
// unchanged), then return. loadFromProject raises loadPending_, so the existing
// consumeLoadSignal() glue re-baselines the panel detector and reapplies the active
// mode; bankPanelRefresh's fingerprint pass then repaints the restored book. This is
// the ONLY undo/redo reload path — the timer never polls ext-state CONTENT to detect
// an undo (Daniel's directive: the hook drives it, not a poll heuristic).
// Undo/redo reload, owned by the projectconfig hook, not the identity
// classifier below: an undo/redo keeps the same project identity, so
// classifyProjectTransition would return NoOp and never re-read ext
// state. By now REAPER has finished restoring the <EXTSTATE> block, so
// GetProjExtState returns the post-undo value. Reload and identity-adopt
// (no relocation — path unchanged). This is the ONLY undo/redo reload
// path — the timer never polls ext-state content to detect an undo.
if (reloadRequested_) {
reloadRequested_ = false;
loadFromProject(proj, projectDirOf(rppPath));
@@ -150,20 +103,14 @@ void ReaSamplerSession::poll() {
return;
case capture::ProjectTransition::Load: {
// A different project of record is active (open / tab switch / new /
// reopened / recycled pointer / forked sibling). Load ITS index; never
// relocate.
// A different project of record is active. Load its index; never relocate.
//
// Forked-sibling divergence: gate on `!sameProjectObject` so this fires
// ONLY for a step-2 Load (same GUID, different object) — a Save-As fork
// that copied our GUID and never re-saved (its fresh GUID was runtime-
// only on the sibling we came from). A recycled-pointer Load (step 1:
// currentGuid != lastGuid_) must NOT re-GUID — it is already a distinct
// identity. currentGuid == lastGuid_ can only hold here when step 1 did
// NOT fire, i.e. this is the fork case; the explicit !sameProjectObject
// makes that intent load-bearing rather than incidental. Do this BEFORE
// loadFromProject reads the index (order is irrelevant — GUID and
// bank_index are distinct keys — but self-contained is clearest).
// Forked-sibling re-GUID: gate on `!sameProjectObject` so this fires
// only for the fork case (same GUID, different object) — a Save-As
// fork that copied our GUID and never re-saved. A recycled-pointer
// Load (currentGuid != lastGuid_) must NOT re-GUID — it is already
// a distinct identity; currentGuid == lastGuid_ can only hold here
// when that case did not fire.
if (proj && !sameProjectObject && !currentGuid.empty() &&
currentGuid == lastGuid_ && !rppPath.empty()) {
const std::string fresh = genProjectGuidString();
@@ -186,12 +133,10 @@ void ReaSamplerSession::poll() {
}
case capture::ProjectTransition::SaveAsRelocate: {
// SAME project object + new .rpp path: a genuine Save-As (the pointer
// proves it — a fork tab-switch is a DIFFERENT object and took the Load
// branch above). Relocate the bank folder from the old dir to the new
// one so the wavs sit under the new .rpp and the index's relative paths
// still resolve. Keep the in-memory bank as-is (Save-As copied our ext
// state, the relative paths are unchanged) — do NOT reload.
// Same project object, new .rpp path: a genuine Save-As. Relocate
// the bank folder so the wavs sit under the new .rpp and the
// index's relative paths still resolve. Keep the in-memory bank
// as-is (Save-As copied our ext state) — do NOT reload.
const std::string oldDir = projectDirOf(lastRppPath_);
const std::string newDir = projectDirOf(rppPath);
const capture::BankRelocation plan =
@@ -200,11 +145,9 @@ void ReaSamplerSession::poll() {
relocateBankFolder(plan.oldBankDir, plan.newBankDir);
}
// Save-As duplicated our ext state, so the new project B currently
// shares A's GUID. Mint a FRESH GUID for B and write it, so A and B
// no longer collide on identity when reopened later. Adopt the fresh
// GUID as our last-seen identity. Mark dirty so the fresh GUID flushes
// to the new .rpp on the next normal save / close-prompt.
// Save-As duplicated our ext state, so the new project shares the
// old GUID; mint a fresh one and mark dirty so it flushes on the
// next save, and A/B no longer collide on identity when reopened.
const std::string fresh = genProjectGuidString();
if (proj) {
SetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),