Fix the reclaimer stranding a tab-switched-into project: lift the pin and wakeup into FxReclaimQueue, retarget instead of going idle
Also validate the cached track in its project, and stop a drop-free tick resetting the report memo.
This commit is contained in:
@@ -30,12 +30,9 @@ namespace reasampler {
|
||||
|
||||
namespace {
|
||||
|
||||
// The derived queue and the project it was derived against. The pointer is
|
||||
// COMPARE-ONLY and never dereferenced beyond the ValidatePtr2 the tick already makes —
|
||||
// a tab switch has to be detectable without following a pointer that may name a closed
|
||||
// project.
|
||||
// The derived queue, the project it was derived against, and the timer's wakeup — see
|
||||
// FxReclaimQueue, which owns all three.
|
||||
FxReclaimQueue g_queue;
|
||||
ReaProject* g_project = nullptr;
|
||||
|
||||
// TrackFX_SetOffline is ASSUMED to pump the message loop, so a plugin load can re-enter
|
||||
// the timer and land back here mid-drain. Fail closed: a re-entered drain would derive a
|
||||
@@ -86,9 +83,11 @@ struct ReclaimHost {
|
||||
std::string cachedGuid_;
|
||||
MediaTrack* cachedTrack_ = nullptr;
|
||||
|
||||
// A track is validated IN a project (reaper_plugin_functions.h:7898); the null form
|
||||
// is defined only where the pointer is itself a project.
|
||||
MediaTrack* resolveForStep(const std::string& guid) {
|
||||
if (guid == cachedGuid_ && cachedTrack_ &&
|
||||
ValidatePtr2(nullptr, cachedTrack_, "MediaTrack*"))
|
||||
ValidatePtr2(project, cachedTrack_, "MediaTrack*"))
|
||||
return cachedTrack_;
|
||||
cachedTrack_ = resolveFresh(project, guid);
|
||||
cachedGuid_ = guid;
|
||||
@@ -171,24 +170,15 @@ void reportDrops(ReaProject* proj, const std::vector<TrackFxDrops>& drops) {
|
||||
if (!msg.empty()) ShowConsoleMsg(("!SHOW:" + msg).c_str());
|
||||
}
|
||||
|
||||
// Every path that stops scheduling ticks leaves the same state behind: no queue, no pin,
|
||||
// nothing pending. The debounce clock is NOT part of it — see fx_reclaim.h.
|
||||
void goIdle() {
|
||||
g_queue.discard();
|
||||
g_project = nullptr;
|
||||
g_fxReclaimPending = false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool g_fxReclaimPending = false;
|
||||
bool fxReclaimPending() { return g_queue.pending(); }
|
||||
|
||||
void invalidateFxReclaim() {
|
||||
// A generation bump and a flag, and deliberately nothing more. Discarding the queue
|
||||
// here is what let a re-entered apply free the element the outer drain was writing
|
||||
// through, and clear the very wakeup this call had just raised.
|
||||
g_queue.invalidate();
|
||||
g_fxReclaimPending = true;
|
||||
}
|
||||
|
||||
bool fxReclaimDrainInProgress() { return g_draining; }
|
||||
@@ -200,27 +190,17 @@ void fxReclaimTick(ReaSamplerSession& session, ReaProject* proj) {
|
||||
if (modeApplyInProgress()) return;
|
||||
DrainLatch latch;
|
||||
|
||||
// Belt-and-braces, not the tab-switch mechanism: OnTimer runs session.poll() before
|
||||
// this tick, and poll() already classifies a tab switch as a Load whose reapply
|
||||
// invalidates the queue — so by the time this test runs, a switch has normally
|
||||
// already been handled and g_project already matches. This still catches it when
|
||||
// poll() was itself skipped this tick (modeApplyInProgress/drain-in-progress
|
||||
// deferral) and the two go stale together. Both arms below mean the same thing —
|
||||
// no active project at all, or a project this queue no longer belongs to — so drop
|
||||
// the queue rather than write another project's chains; returning to it re-derives.
|
||||
// The clock goes too — a hold measured in a project we stopped watching is not
|
||||
// evidence about the one we come back to.
|
||||
ReaProject* const project = proj ? proj : EnumProjects(-1, nullptr, 0);
|
||||
if (!ValidatePtr2(nullptr, project, "ReaProject*") || (g_project && g_project != project)) {
|
||||
goIdle();
|
||||
g_queue.forgetHolds();
|
||||
return;
|
||||
}
|
||||
// beginTick owns what a pin/project mismatch means. What is local to here: the model
|
||||
// the re-derivation below reads is the ACTIVE project's, because reaching this line
|
||||
// means OnTimer ran session.poll() this tick — the two guards above are the same pair
|
||||
// that gate that poll.
|
||||
ReaProject* project = proj ? proj : EnumProjects(-1, nullptr, 0);
|
||||
if (!ValidatePtr2(nullptr, project, "ReaProject*")) project = nullptr;
|
||||
if (!g_queue.beginTick(project)) return;
|
||||
|
||||
// DERIVED, never persisted: recomputed from view state every time the queue was
|
||||
// invalidated, so a REAPER quit mid-drain simply re-derives and finishes on reopen.
|
||||
if (g_queue.stale()) {
|
||||
g_project = project;
|
||||
const ViewModeModel& model = session.view();
|
||||
g_queue.adopt(makeQueue(
|
||||
view::desiredFxState(model, model.activeModeId(), liveTrackGuids(project))));
|
||||
@@ -229,7 +209,7 @@ void fxReclaimTick(ReaSamplerSession& session, ReaProject* proj) {
|
||||
ReclaimHost host{project, &session};
|
||||
const ReclaimRunResult run = g_queue.run(host);
|
||||
reportDrops(project, run.drops);
|
||||
if (!run.aborted && run.settled) goIdle();
|
||||
if (!run.aborted && run.settled) g_queue.goIdle();
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
|
||||
Reference in New Issue
Block a user