fix: layer GUID-primary project identity so reopened/new projects reload the bank
classifyProjectTransition now checks the stored GUID first, then the pointer, fixing the w10 regression where a recycled ReaProject* address stopped the bank reloading. poll()'s fork re-GUID gate is bound to !sameProjectObject. Full transition matrix pinned in tests.
This commit is contained in:
+39
-32
@@ -14,26 +14,30 @@
|
||||
// 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 rests on the live ReaProject* POINTER first, with the GUID as
|
||||
// a secondary signal:
|
||||
// * different project object (proj != lastProject_) -> a switch/open/new
|
||||
// project -> LOAD its index; NEVER relocate. If its stored GUID still equals
|
||||
// the one we just left (a forked sibling that copied our GUID via Save-As),
|
||||
// re-GUID it so the siblings diverge going forward.
|
||||
// * 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 the pointer is back (W10 fix): the M4 GUID-only scheme could not tell a
|
||||
// Save-As from a tab-switch between two FORKED projects. Save-As copies the whole
|
||||
// .rpp incl. our stored GUID, so a fork and its parent share a GUID on disk;
|
||||
// switching between them (same GUID, different paths) read as a Save-As and
|
||||
// clobbered a bank. The pointer is the only signal that separates "same object
|
||||
// saved elsewhere" (Save-As) from "different object sharing a copied GUID"
|
||||
// (a switch). classifyProjectTransition (pure, capture_paths) takes a
|
||||
// `sameProjectObject` bool (poll() computes `proj == lastProject_`) so the
|
||||
// decision stays REAPER-free and testable; poll() executes the verdict.
|
||||
// Pointer *reuse* (a recycled address for a genuinely different project) stays
|
||||
// correct: it is a different object at that moment, so it Loads, and its GUID
|
||||
// differs or gets re-diverged.
|
||||
// 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.
|
||||
//
|
||||
// REAPER exposes no stable per-project GUID (GetSetProjectInfo_String has no
|
||||
// PROJECT_GUID desc; GetProjectStateChangeCount is a session-local counter, not
|
||||
@@ -309,19 +313,22 @@ void ReaSamplerSession::poll() {
|
||||
return;
|
||||
|
||||
case ProjectTransition::Load: {
|
||||
// A DIFFERENT project object is active (open / tab switch / new project
|
||||
// / recycled pointer). Load ITS index; never relocate.
|
||||
// A different project of record is active (open / tab switch / new /
|
||||
// reopened / recycled pointer / forked sibling). Load ITS index; never
|
||||
// relocate.
|
||||
//
|
||||
// Forked-sibling divergence: if the now-active project's stored GUID
|
||||
// still equals the one we just left, it is a Save-As fork that copied
|
||||
// our GUID and never re-saved (its fresh GUID was runtime-only on the
|
||||
// sibling we came from). Left alone, the two keep colliding on identity.
|
||||
// Mint a fresh GUID for the now-active project so the siblings diverge
|
||||
// going forward. Do this BEFORE loadFromProject reads the index (order
|
||||
// is irrelevant to the index — GUID and bank_index are distinct keys —
|
||||
// but keeping the write self-contained is clearest).
|
||||
if (proj && !currentGuid.empty() && currentGuid == lastGuid_ &&
|
||||
!rppPath.empty()) {
|
||||
// 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).
|
||||
if (proj && !sameProjectObject && !currentGuid.empty() &&
|
||||
currentGuid == lastGuid_ && !rppPath.empty()) {
|
||||
const std::string fresh = genProjectGuidString();
|
||||
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
|
||||
kProjExtGuidKey, fresh.c_str());
|
||||
|
||||
Reference in New Issue
Block a user