Reconcile orphaned Design View snapshots on track delete
Add ViewModeModel::reconcile(liveGuids) to prune snapshots of deleted tracks; wire it into applyMode before planning. Keep membership so undo-delete (same GUID) preserves the Design tag.
This commit is contained in:
@@ -579,6 +579,99 @@ static void testTaggedLeafBehaviorUnchangedWithUntagged() {
|
||||
CHECK(!parkTargets(arrange, "{UNT}"));
|
||||
}
|
||||
|
||||
// -- 10. reconcile: prune orphaned snapshots on track delete -----------------
|
||||
//
|
||||
// Closes the "reconcile on delete/restructure" hardening item. reconcile prunes a
|
||||
// snapshot whose GUID is not in the live set (its track was deleted while parked),
|
||||
// preventing both the slow snapshot leak and an incorrect restore if REAPER reuses
|
||||
// the GUID. Membership is deliberately KEPT (undo-delete restores the same GUID, so
|
||||
// dropping the tag would silently lose it). A full live set is a no-op — this is why
|
||||
// folder RESTRUCTURE, which leaves every GUID live, needs no special handling.
|
||||
|
||||
static void testReconcilePrunesOrphanedSnapshots() {
|
||||
ViewModeModel vm;
|
||||
// Two parked tracks (both snapshotted + tagged); {DEL} is about to be deleted.
|
||||
vm.membership().tag("{LIVE}", kDesignModeId);
|
||||
vm.membership().tag("{DEL}", kDesignModeId);
|
||||
TrackSnapshot sLive; sLive.showInTcp = 1; sLive.fxOffline = {0, 1};
|
||||
TrackSnapshot sDel; sDel.showInTcp = 1; sDel.fxEnable = 1;
|
||||
vm.storeSnapshot("{LIVE}", sLive);
|
||||
vm.storeSnapshot("{DEL}", sDel);
|
||||
CHECK(vm.snapshots().size() == 2);
|
||||
|
||||
// {DEL} is deleted from the project ⇒ absent from the live GUID set.
|
||||
std::set<std::string> liveGuids{"{LIVE}"};
|
||||
std::size_t removed = vm.reconcile(liveGuids);
|
||||
|
||||
// The orphaned snapshot is pruned; the live one is retained verbatim.
|
||||
CHECK(removed == 1);
|
||||
CHECK(vm.snapshot("{DEL}") == nullptr);
|
||||
const TrackSnapshot* kept = vm.snapshot("{LIVE}");
|
||||
CHECK(kept != nullptr);
|
||||
if (kept) CHECK(kept->showInTcp == 1 && kept->fxOffline.size() == 2);
|
||||
|
||||
// Membership is NOT pruned — the deleted GUID keeps its Design tag so an
|
||||
// undo-delete (which restores the same GUID) brings the track back correctly
|
||||
// tagged. This is the load-bearing design call.
|
||||
CHECK(vm.membership().query("{DEL}") != nullptr);
|
||||
CHECK(vm.leafBelongsToMode("{DEL}", kDesignModeId));
|
||||
CHECK(vm.membership().query("{LIVE}") != nullptr);
|
||||
}
|
||||
|
||||
static void testReconcileFullLiveSetIsNoOp() {
|
||||
// The restructure case: tracks moved between folders but none deleted ⇒ every
|
||||
// GUID stays live ⇒ reconcile prunes nothing.
|
||||
ViewModeModel vm;
|
||||
vm.storeSnapshot("{A}", TrackSnapshot{});
|
||||
vm.storeSnapshot("{B}", TrackSnapshot{});
|
||||
vm.storeSnapshot("{C}", TrackSnapshot{});
|
||||
|
||||
std::set<std::string> liveGuids{"{A}", "{B}", "{C}"};
|
||||
std::size_t removed = vm.reconcile(liveGuids);
|
||||
|
||||
CHECK(removed == 0);
|
||||
CHECK(vm.snapshots().size() == 3);
|
||||
CHECK(vm.snapshot("{A}") && vm.snapshot("{B}") && vm.snapshot("{C}"));
|
||||
|
||||
// A superset of live GUIDs (tracks exist that were never parked) is also a no-op:
|
||||
// reconcile only ever removes, never adds.
|
||||
std::set<std::string> superset{"{A}", "{B}", "{C}", "{NEVER_PARKED}"};
|
||||
CHECK(vm.reconcile(superset) == 0);
|
||||
CHECK(vm.snapshots().size() == 3);
|
||||
|
||||
// Empty live set (whole project emptied) prunes everything.
|
||||
CHECK(vm.reconcile(std::set<std::string>{}) == 3);
|
||||
CHECK(vm.snapshots().empty());
|
||||
}
|
||||
|
||||
static void testReconcileThenReparkLifecycleIntact() {
|
||||
// No regression to the park/restore lifecycle: after reconcile prunes a deleted
|
||||
// track's snapshot, a still-live tagged leaf toggled back to its mode still
|
||||
// restores from its retained snapshot, and a re-park recaptures fresh state.
|
||||
ViewModeModel vm;
|
||||
FolderTree tree;
|
||||
tree.nodes.push_back(FolderNode{"{DES}", "", false});
|
||||
vm.membership().tag("{DES}", kDesignModeId);
|
||||
|
||||
TrackSnapshot snap; snap.showInTcp = 1; snap.mainSend = 0; snap.fxEnable = 1;
|
||||
vm.storeSnapshot("{DES}", snap); // parked while in Arrange
|
||||
vm.storeSnapshot("{ORPHAN}", TrackSnapshot{}); // a since-deleted parked track
|
||||
|
||||
// Reconcile with {DES} live, {ORPHAN} gone.
|
||||
CHECK(vm.reconcile(std::set<std::string>{"{DES}"}) == 1);
|
||||
CHECK(vm.snapshot("{ORPHAN}") == nullptr);
|
||||
|
||||
// Toggle back to Design: {DES} restores from its retained snapshot verbatim.
|
||||
auto toDesign = vm.planToggle(tree, kDesignModeId);
|
||||
const TrackPlan* r = restoreFor(toDesign, "{DES}");
|
||||
CHECK(r != nullptr);
|
||||
if (r) {
|
||||
CHECK(flagValue(*r, Flag::ShowInTcp) == 1);
|
||||
CHECK(flagValue(*r, Flag::MainSend) == 0);
|
||||
CHECK(flagValue(*r, Flag::FxEnable) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// -- 8. nextModeId cycle (D4 toggle helper) ----------------------------------
|
||||
|
||||
static void testNextModeIdCycles() {
|
||||
@@ -756,6 +849,9 @@ int main() {
|
||||
testUntaggedLeavesManagedByModeSystem();
|
||||
testTaggedLeafBehaviorUnchangedWithUntagged();
|
||||
testNestedToggleSnapshotSurvivesRepark();
|
||||
testReconcilePrunesOrphanedSnapshots();
|
||||
testReconcileFullLiveSetIsNoOp();
|
||||
testReconcileThenReparkLifecycleIntact();
|
||||
testNextModeIdCycles();
|
||||
|
||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||
|
||||
Reference in New Issue
Block a user