Split FX out of the park plan: flag-only plans, derived online-first FX work list, two-half record release, applied-state fingerprint

This commit is contained in:
2026-08-06 16:31:59 -04:00
parent 3eae942625
commit 2423225f0c
14 changed files with 897 additions and 164 deletions
+91
View File
@@ -0,0 +1,91 @@
#include "core/view/fx_state.h"
#include <cstddef>
#include <utility>
namespace reasampler::view {
namespace {
// Everything up to and including the first op naming `resumeAfter` is already
// done. Not found ⇒ nothing dropped: the identity left the chain between ticks,
// and re-walking a compare-before-write list beats trusting a cursor that no
// longer points at anything.
std::vector<FxOfflineOp> remainingAfter(const std::vector<FxOfflineOp>& ops,
const std::string& resumeAfter) {
if (resumeAfter.empty()) return ops;
for (std::size_t i = 0; i < ops.size(); ++i) {
if (ops[i].fxGuid == resumeAfter)
return std::vector<FxOfflineOp>(ops.begin() + static_cast<std::ptrdiff_t>(i) + 1,
ops.end());
}
return ops;
}
// Slot keying, deliberately: the live chain IS the target set, so its positions
// are current by construction. Identity keying would drop an FX REAPER reports no
// GUID for, and reclaim has to reach those too — the fxGuid rides along only so
// the resume point has something to match.
std::vector<FxOfflineOp> offlineTargets(const std::string& trackGuid,
const std::vector<std::string>& liveFxGuids) {
std::vector<FxOfflineOp> ops;
ops.reserve(liveFxGuids.size());
for (std::size_t i = 0; i < liveFxGuids.size(); ++i)
ops.push_back(FxOfflineOp{trackGuid, FxKeying::Slot, liveFxGuids[i],
static_cast<int>(i), true});
return ops;
}
} // namespace
std::vector<FxOfflineOp> fxRestoreTargets(const ViewModeModel& model,
const std::string& trackGuid) {
std::vector<FxOfflineOp> ops;
if (!model.holdsSnapshotFx(trackGuid)) return ops;
const TrackSnapshot* snap = model.snapshot(trackGuid);
ops.reserve(snap->fxOffline.size());
for (std::size_t i = 0; i < snap->fxOffline.size(); ++i) {
const FxOfflineState& fx = snap->fxOffline[i];
ops.push_back(FxOfflineOp{trackGuid, snap->fxKeying, fx.fxGuid,
static_cast<int>(i), fx.offline != 0});
}
return ops;
}
std::vector<FxTrackWork> desiredFxState(const ViewModeModel& model,
const std::set<std::string>& liveTrackGuids) {
std::vector<FxTrackWork> online;
std::vector<FxTrackWork> offline;
const std::string& activeMode = model.activeModeId();
for (const auto& [guid, snap] : model.snapshots()) { // std::map ⇒ GUID order
if (liveTrackGuids.count(guid) == 0) continue; // dead track — nothing to write
if (!model.leafBelongsToMode(guid, activeMode)) {
offline.push_back(FxTrackWork{guid, FxWorkKind::Offline, {}, {}});
continue;
}
// Active with the FX half already released: the chain is back online and
// the record is only waiting on its flags. Nothing owed either way.
if (!snap.fxHeld) continue;
online.push_back(
FxTrackWork{guid, FxWorkKind::Online, fxRestoreTargets(model, guid), {}});
}
online.reserve(online.size() + offline.size());
for (FxTrackWork& w : offline) online.push_back(std::move(w));
return online;
}
FxRestoreResolution resolveFxWork(const FxTrackWork& work,
const std::vector<std::string>& liveFxGuids) {
if (work.kind == FxWorkKind::Offline) {
return resolveFxRestore(
remainingAfter(offlineTargets(work.trackGuid, liveFxGuids), work.resumeAfterFxGuid),
liveFxGuids);
}
return resolveFxRestore(remainingAfter(work.targets, work.resumeAfterFxGuid), liveFxGuids);
}
} // namespace reasampler::view