fix(persist): use project-object identity to stop forked-bank cross-contamination
M4's GUID-only classifier read a tab-switch between two Save-As forks (shared copied GUID, different paths) as a Save-As and clobbered a bank. Thread sameProjectObject into classifyProjectTransition: a different object always Loads, never relocates; a forked sibling gets re-GUID'd to diverge.
This commit is contained in:
+16
-10
@@ -48,15 +48,18 @@ inline constexpr const char* kProjExtGuidKey = "project_guid";
|
||||
// Owns the session's BankIndex and drives persistence against the active REAPER
|
||||
// project. One instance lives for the extension's lifetime (main.cpp). It tracks
|
||||
// the project identity it last saw so the timer tick can detect a project load
|
||||
// (identity changed) and a Save-As (same project, path changed) and react:
|
||||
// (a different project became active) and a Save-As (SAME project, path changed):
|
||||
//
|
||||
// * project load -> load the index from ext state, resolve bank paths
|
||||
// * Save-As (new dir) -> relocate the bank folder under the new .rpp
|
||||
//
|
||||
// Identity is CONTENT-BASED, not pointer-based: persist keys Load/Save-As off a
|
||||
// GUID it mints and stores in each project's ext state, not the ReaProject*
|
||||
// pointer (REAPER recycles pointer addresses across close/open, which let a
|
||||
// project switch masquerade as a Save-As and clobber a bank — the M4 defect).
|
||||
// Identity rests on the live ReaProject* pointer FIRST (a genuine Save-As is one
|
||||
// object saved to a new path — same pointer; a tab-switch/open is a different
|
||||
// object), with a minted GUID as a SECONDARY signal to survive pointer *reuse*
|
||||
// (REAPER recycles a closed project's address). The pointer is what distinguishes
|
||||
// a Save-As from a switch between two FORKED siblings that share a copied GUID on
|
||||
// disk (the W10 data-integrity defect: dropping the pointer let a fork tab-switch
|
||||
// masquerade as a Save-As and clobber a bank).
|
||||
//
|
||||
// The bank itself is exposed for the capture/action layer to mutate; persist
|
||||
// only reads it on save and replaces it on load.
|
||||
@@ -95,12 +98,15 @@ private:
|
||||
ViewModeModel view_;
|
||||
|
||||
// The project identity last observed by poll(), used to detect load/Save-As.
|
||||
// Identity is the GUID we mint per project (kProjExtGuidKey), NOT the raw
|
||||
// ReaProject* pointer — see the class comment for why. The .rpp path is
|
||||
// tracked alongside so a same-GUID path change (Save-As) is distinguishable
|
||||
// from a same-GUID same-path idle tick (Save in place / no change).
|
||||
// The pointer is the PRIMARY signal (same object across ticks = a candidate
|
||||
// Save-As; different object = a switch/open, never a relocate). The GUID and
|
||||
// path travel alongside: the GUID distinguishes pointer *reuse* and drives
|
||||
// forked-sibling re-divergence; the path tells a Save-As from an idle tick.
|
||||
// Held as void* so the header stays REAPER-free; it is a compared-only opaque
|
||||
// handle (never dereferenced), so a stale/recycled address is harmless.
|
||||
void* lastProject_ = nullptr; // last active ReaProject* (opaque; compare only)
|
||||
std::string lastGuid_; // "" until the first saved project is seen
|
||||
std::string lastRppPath_; // .rpp path last seen for lastGuid_
|
||||
std::string lastRppPath_; // .rpp path last seen for lastProject_
|
||||
bool primed_ = false; // false until the first poll() observes state
|
||||
|
||||
// Load the index from the given project's ext state and resolve bank paths
|
||||
|
||||
Reference in New Issue
Block a user