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:
+40
-37
@@ -89,30 +89,34 @@ struct BankRelocation {
|
||||
BankRelocation deriveRelocationPlan(const std::string& oldProjectDir,
|
||||
const std::string& newProjectDir);
|
||||
|
||||
// --- Project-identity transition (W10 forked-project fix) --------------------
|
||||
// --- Project-identity transition (W12 combined identity fix) -----------------
|
||||
//
|
||||
// What the persist timer must do on each tick. Identity now rests on TWO facts,
|
||||
// not the GUID alone:
|
||||
// 1. sameProjectObject — did the same live ReaProject* stay active across the
|
||||
// two ticks (computed in poll() as `proj == lastProject_`)? This is what a
|
||||
// genuine Save-As looks like: ONE project object saved to a new path. A
|
||||
// tab-switch or open is a DIFFERENT object.
|
||||
// 2. the minted GUID — content-based identity stored in ext state, kept to
|
||||
// survive pointer *reuse* (REAPER recycles a closed project's address).
|
||||
// What the persist timer must do on each tick. Identity rests on TWO facts,
|
||||
// layered GUID-PRIMARY:
|
||||
// 1. the minted GUID — content-based identity of record, stored in ext state.
|
||||
// It is IMMUNE to REAPER recycling a closed project's ReaProject* address,
|
||||
// so it is checked FIRST.
|
||||
// 2. sameProjectObject — did the same live ReaProject* stay active across the
|
||||
// two ticks (computed in poll() as `proj == lastProject_`)? Used ONLY to
|
||||
// disambiguate the same-GUID case: a forked sibling (Save-As copied our GUID
|
||||
// onto a distinct object) vs a genuine Save-As (one object, new path).
|
||||
//
|
||||
// The pointer was dropped in M4 (GUID-only), which broke FORKED projects: Save-As
|
||||
// copies the whole .rpp incl. our stored GUID, so a fork and its parent share a
|
||||
// GUID on disk. Tab-switching between two forked siblings (same GUID, different
|
||||
// paths) then read as a Save-As and clobbered one bank with the other's — the
|
||||
// data-integrity defect this fix closes. The pointer is the ONLY signal that
|
||||
// separates "same object saved elsewhere" (Save-As) from "different object that
|
||||
// happens to share a forked GUID" (a switch).
|
||||
// This fix layers both prior designs, GUID-primary. 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. W10 (pointer-primary, GUID voided) broke pointer
|
||||
// RECYCLING: REAPER reuses a closed project's address, so a reopened/new project
|
||||
// can present the previous project's pointer with a different stored GUID —
|
||||
// pointer-primary read that 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).
|
||||
//
|
||||
// The load-bearing rule: a DIFFERENT project object NEVER relocates a bank.
|
||||
// The load-bearing rule: a DIFFERENT record identity (GUID) is always a Load; a
|
||||
// DIFFERENT project object with the same GUID is a fork Load, never a relocate.
|
||||
enum class ProjectTransition {
|
||||
NoOp, // same object, same location — nothing to do
|
||||
NoOp, // same object, same GUID, same location — nothing to do
|
||||
Load, // a different project is active — load ITS index from ext state
|
||||
SaveAsRelocate, // SAME object, new .rpp location — relocate the bank folder
|
||||
SaveAsRelocate, // SAME object + SAME GUID, new .rpp location — relocate the bank
|
||||
};
|
||||
|
||||
// Classifies what a poll tick observed.
|
||||
@@ -126,24 +130,23 @@ enum class ProjectTransition {
|
||||
// unsaved or never written)
|
||||
// currentPath : the now-active project's .rpp path ("" if unsaved)
|
||||
//
|
||||
// Rules:
|
||||
// * sameProjectObject == false -> Load (a different project
|
||||
// object — tab-switch / open /
|
||||
// recycled pointer; NEVER a
|
||||
// relocate, even if the GUID
|
||||
// matches a forked sibling)
|
||||
// * same object, non-empty GUID, path unchanged -> NoOp (Save in place / idle)
|
||||
// * same object, non-empty GUID, path changed -> SaveAsRelocate
|
||||
// * same object, empty GUID, path unchanged -> NoOp (idle unsaved project)
|
||||
// * same object, empty GUID, path changed -> SaveAsRelocate (first save;
|
||||
// the relocation plan no-ops on
|
||||
// the empty old dir, so nothing
|
||||
// is physically relocated —
|
||||
// the empty-GUID safety holds —
|
||||
// and poll() mints a GUID)
|
||||
// A different object never yields SaveAsRelocate: that is the whole fix. The empty-
|
||||
// GUID safety (unsaved projects never physically relocate) is preserved because an
|
||||
// empty old project dir makes deriveRelocationPlan's `needed` false.
|
||||
// Rules (evaluated in EXACTLY this order):
|
||||
// 1. currentGuid != lastGuid -> Load (different record identity:
|
||||
// recycled pointer w/ different GUID,
|
||||
// new/unsaved<->saved, or two distinct
|
||||
// saved projects)
|
||||
// 2. !sameProjectObject -> Load (same GUID, different object:
|
||||
// forked sibling, or two unsaved projects)
|
||||
// 3. currentPath != lastPath -> SaveAsRelocate (same object + same GUID,
|
||||
// new path: genuine Save-As, or first save
|
||||
// of an unsaved project — relocate no-ops
|
||||
// on the empty old dir, poll() mints a GUID)
|
||||
// 4. otherwise -> NoOp (same object, same GUID, same path)
|
||||
//
|
||||
// The GUID (identity of record) leads; the pointer only disambiguates the same-GUID
|
||||
// case (fork-Load in step 2 vs Save-As in step 3). The empty-GUID safety (unsaved
|
||||
// projects never physically relocate) is preserved because an empty old project dir
|
||||
// makes deriveRelocationPlan's `needed` false.
|
||||
ProjectTransition classifyProjectTransition(bool sameProjectObject,
|
||||
const std::string& lastGuid,
|
||||
const std::string& lastPath,
|
||||
|
||||
Reference in New Issue
Block a user