fx_reclaim: use GetCurrentProjectInLoadSave for the save hook, stop disarming ReleaseFlush on one write, validate project before cached-track lookup

This commit is contained in:
2026-08-06 22:52:57 -04:00
parent 02790f1270
commit c95203718a
5 changed files with 91 additions and 33 deletions
+36 -4
View File
@@ -110,11 +110,11 @@ struct FakeHost {
};
// What the project-save hook does (fxReclaimFlushRelease): write the in-memory model into
// this project's view state, but only when it is the project holding the release.
// this project's view state, but only when it is the project holding the release. Does
// NOT clear the arm — see ReleaseFlush — so calling this twice re-issues the same write.
void saveProject(FakeHost& host, ReleaseFlush& flush, ReaProject* active) {
if (!flush.pendingFor(active)) return;
host.persisted = host.model->serialize();
flush.clear();
}
// -- model helpers -------------------------------------------------------------
@@ -570,9 +570,10 @@ static void testACompletedOnlineReachesDiskOnTheSaveAndNotOnTheDrain() {
CHECK(flush.pendingFor(kProjA)); // ...and the save knows it is owed
// The user's next save is what spends it — and afterwards a reload plans no work,
// so nothing re-drives the chain.
// so nothing re-drives the chain. The arm itself survives the write (see
// testTheFlushArmSurvivesASaveAndCanBeReIssued) — this test is about the disk content.
saveProject(host, flush, kProjA);
CHECK(!flush.pendingFor(kProjA));
CHECK(flush.pendingFor(kProjA));
const std::optional<ViewModeModel> afterSave = ViewModeModel::deserialize(host.persisted);
CHECK(afterSave.has_value());
CHECK(afterSave->snapshot("{T}") == nullptr); // both halves spent ⇒ record erased
@@ -640,6 +641,36 @@ static void testATickThatSpendsNoRecordOwesNoSaveAnything() {
CHECK(host.persisted.empty());
}
static void testTheFlushArmSurvivesASaveAndCanBeReIssued() {
// Whether a write made from inside the save hook reaches the very file being written
// now, or lands one save late, is unsettled (ReleaseFlush's [verify — DAW]). The arm
// must not treat one issued write as proof of a durable spend: it stays armed, so an
// unfavourable answer still gets a retry on the project's NEXT save rather than
// stranding the record in memory for the rest of the session.
ViewModeModel vm = parkedDesignLeaf({"{FX-A}", "{FX-B}"});
vm.setActiveMode(kDesignModeId);
vm.releaseSnapshotFlags("{T}");
FakeHost host;
host.model = &vm;
ReleaseFlush flush;
flush.arm(kProjA);
saveProject(host, flush, kProjA);
CHECK(!host.persisted.empty());
CHECK(flush.pendingFor(kProjA)); // survives the flush — not disarmed by one write
// A second save re-issues the identical write rather than finding nothing to do.
host.persisted.clear();
saveProject(host, flush, kProjA);
CHECK(!host.persisted.empty());
CHECK(flush.pendingFor(kProjA));
// A different project's save must still leave this arm untouched.
saveProject(host, flush, kProjB);
CHECK(flush.pendingFor(kProjA));
}
// -- 7. captured FX state the online direction cannot place -------------------------
static void testADropIsReportedOnceAcrossRepeatedRederivations() {
@@ -930,6 +961,7 @@ int main() {
testACompletedOnlineReachesDiskOnTheSaveAndNotOnTheDrain();
testAReleaseKilledBeforeTheFlushIsSimplyReEarned();
testATickThatSpendsNoRecordOwesNoSaveAnything();
testTheFlushArmSurvivesASaveAndCanBeReIssued();
testADropIsReportedOnceAcrossRepeatedRederivations();
testAnOfflineTickReportsNoDrops();