feat(persist): reload book+view on undo/redo via projectconfig hook (R-B)
BeginLoadProjectState(isUndo) requests a deferred reload the timer drains next tick, once REAPER has restored ext-state. Hook owns undo/redo; identity poll still owns open/tab-switch/save-as. Fixes copy-collapse over-count (verb-aware guard) and dangling undo point on unsaved-project bank ops.
This commit is contained in:
@@ -241,6 +241,56 @@ static void OnTimer()
|
||||
reasampler::bankPanelRefresh();
|
||||
}
|
||||
|
||||
// --- projectconfig hook: reload the session on undo/redo (R-B) ---------------
|
||||
// A Ctrl-Z / Ctrl-Shift-Z rolls back / forward the "reasampler" project ext state on
|
||||
// disk but keeps the SAME project identity (ReaProject*/GUID/.rpp path), so the timer's
|
||||
// identity poll reads it as NoOp and never re-reads ext state — the in-memory book/view
|
||||
// would stay stale until close+reopen. REAPER's projectconfig extension fires
|
||||
// BeginLoadProjectState on every project-state (re)load, INCLUDING an undo/redo restore
|
||||
// (isUndo == true for both). We hook it to drive a session reload.
|
||||
//
|
||||
// TIMING (the crux): BeginLoadProjectState is documented (reaper_plugin.h ~1203) as
|
||||
// firing BEFORE any state restore. Reading GetProjExtState synchronously here would
|
||||
// return the PRE-undo value. So we do NOT read here — we raise a one-shot reload request
|
||||
// (g_session.requestReload()) that OnTimer's poll() drains on the NEXT tick, by which
|
||||
// point REAPER has finished restoring the <EXTSTATE> block and GetProjExtState returns
|
||||
// the POST-undo value. Deterministic, event-driven — NOT ext-state content polling.
|
||||
//
|
||||
// GATED ON isUndo: a normal project open also fires BeginLoadProjectState (isUndo=false);
|
||||
// we ignore that here so a normal open flows solely through the timer's identity-transition
|
||||
// Load path (no double load). Only undo/redo (isUndo=true) requests the reload.
|
||||
static void OnBeginLoadProjectState(bool isUndo, project_config_extension_t* /*reg*/)
|
||||
{
|
||||
if (isUndo)
|
||||
g_session.requestReload();
|
||||
}
|
||||
|
||||
// ProcessExtensionLine / SaveExtensionConfig are intentional no-ops: ReaSampler stores
|
||||
// its state via project EXT STATE (SetProjExtState/GetProjExtState under "reasampler"),
|
||||
// which REAPER persists in its own <EXTSTATE> RPP block — NOT via this extension's own
|
||||
// project lines. We register the struct ONLY for the BeginLoadProjectState undo/redo
|
||||
// notification. Returning false from ProcessExtensionLine means "not our line" so REAPER
|
||||
// keeps dispatching (we claim none). SaveExtensionConfig writes nothing.
|
||||
static bool OnProcessExtensionLine(const char* /*line*/, ProjectStateContext* /*ctx*/,
|
||||
bool /*isUndo*/, project_config_extension_t* /*reg*/)
|
||||
{
|
||||
return false; // we own no project lines — ext state carries our data
|
||||
}
|
||||
|
||||
static void OnSaveExtensionConfig(ProjectStateContext* /*ctx*/, bool /*isUndo*/,
|
||||
project_config_extension_t* /*reg*/)
|
||||
{
|
||||
// Nothing to write: our data rides in ext state, not project lines.
|
||||
}
|
||||
|
||||
// Storage must outlive registration — REAPER holds this pointer until we unregister it.
|
||||
static project_config_extension_t g_projectConfig{
|
||||
&OnProcessExtensionLine,
|
||||
&OnSaveExtensionConfig,
|
||||
&OnBeginLoadProjectState,
|
||||
nullptr, // userData
|
||||
};
|
||||
|
||||
// --- Scope-action source resolution -----------------------------------------
|
||||
// The three scope actions (item / track / master) each resolve to (1) an exact
|
||||
// render range in project seconds — razor-else-time, inferred here — and (2) the
|
||||
@@ -786,6 +836,7 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
}
|
||||
|
||||
g_rec->Register("-timer", (void*)&OnTimer);
|
||||
g_rec->Register("-projectconfig", (void*)&g_projectConfig);
|
||||
g_rec->Register("-toggleaction", (void*)&OnToggleAction);
|
||||
g_rec->Register("-hookcommand", (void*)&OnHookCommand);
|
||||
// Tear down the Design View action family (D4) — mirror-unregisters each
|
||||
@@ -957,6 +1008,12 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
|
||||
// state, on a Save-As it relocates the bank folder under the new .rpp.
|
||||
rec->Register("timer", (void*)&OnTimer);
|
||||
|
||||
// Register the projectconfig hook so an UNDO/REDO state restore reloads the
|
||||
// session's book + view from the restored ext state (R-B). The timer's identity
|
||||
// poll cannot see an undo (same project identity), so this hook owns undo/redo; it
|
||||
// requests a deferred reload that the next timer tick drains (see the hook comment).
|
||||
rec->Register("projectconfig", (void*)&g_projectConfig);
|
||||
|
||||
ShowConsoleMsg("ReaSampler loaded.\n");
|
||||
|
||||
return 1; // success — REAPER keeps us loaded
|
||||
|
||||
Reference in New Issue
Block a user