Defer Design View's per-FX park to an idle tick; honest undo mask, compare-before-write, PreventUIRefresh bracket, O(1) handle resolve
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
// Design View's per-FX offline surface: the FX-identity read that snapshot,
|
||||
// park and restore all address FX through, the deferred intent queue that keeps
|
||||
// TrackFX_SetOffline off the mode switch's synchronous path, and the idle-tick
|
||||
// drain that applies it. See src/shell/view/CLAUDE.md's FX-parking caveat.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/view/fx_offline.h"
|
||||
|
||||
// Forward-declared to keep this header SDK-free; the .cpp includes the real SDK header.
|
||||
class MediaTrack;
|
||||
class ReaProject;
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
class ViewModeModel;
|
||||
|
||||
// The chain as it stands now: identity by current slot. Snapshot, park and
|
||||
// restore all address FX through this one plain 0..TrackFX_GetCount-1
|
||||
// enumeration — never the 0x1000000/0x2000000 input-FX or container forms — so
|
||||
// whatever it covers, all three cover identically.
|
||||
std::vector<std::string> liveFxGuids(MediaTrack* tr);
|
||||
|
||||
// One deferred per-FX intent for one track. A park carries no ops (every live
|
||||
// slot goes offline); a restore carries the planned ops verbatim.
|
||||
struct FxParkIntent {
|
||||
std::string guid;
|
||||
bool park = false;
|
||||
std::vector<FxOfflineOp> restoreOps;
|
||||
};
|
||||
|
||||
// The queue's re-entrancy rule, pure so it can be asserted without a DAW: at
|
||||
// most ONE intent per track GUID, and the latest one wins. Park and restore are
|
||||
// inverses, so an intent landing on its own pending inverse CANCELS it rather
|
||||
// than stacking — the queued work never ran, so the track already holds the
|
||||
// state the newcomer asks for, and replaying both would be both slower and
|
||||
// observably wrong.
|
||||
class FxParkQueue {
|
||||
public:
|
||||
void park(const std::string& guid) {
|
||||
FxParkIntent* held = find(guid);
|
||||
if (!held) {
|
||||
pending_.push_back(FxParkIntent{guid, true, {}});
|
||||
} else if (!held->park) {
|
||||
erase(held);
|
||||
}
|
||||
}
|
||||
|
||||
void restore(const std::string& guid, std::vector<FxOfflineOp> ops) {
|
||||
FxParkIntent* held = find(guid);
|
||||
if (!held) {
|
||||
pending_.push_back(FxParkIntent{guid, false, std::move(ops)});
|
||||
} else if (held->park) {
|
||||
erase(held);
|
||||
} else {
|
||||
held->restoreOps = std::move(ops);
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue order, which is apply order: park before restore within one
|
||||
// switch, as the synchronous body already orders them.
|
||||
const std::vector<FxParkIntent>& pending() const { return pending_; }
|
||||
bool empty() const { return pending_.empty(); }
|
||||
void clear() { pending_.clear(); }
|
||||
|
||||
private:
|
||||
FxParkIntent* find(const std::string& guid) {
|
||||
for (FxParkIntent& i : pending_)
|
||||
if (i.guid == guid) return &i;
|
||||
return nullptr;
|
||||
}
|
||||
void erase(FxParkIntent* held) {
|
||||
pending_.erase(pending_.begin() + (held - pending_.data()));
|
||||
}
|
||||
|
||||
std::vector<FxParkIntent> pending_;
|
||||
};
|
||||
|
||||
// Enqueue against `proj` (nullptr = current project). An enqueue naming a
|
||||
// different project than the pending intents discards those unapplied.
|
||||
void deferFxPark(ReaProject* proj, const std::string& guid);
|
||||
void deferFxRestore(ReaProject* proj, const std::string& guid, std::vector<FxOfflineOp> ops);
|
||||
|
||||
// Applies every pending intent, then clears each drained restore's snapshot
|
||||
// from `model` — the snapshot is consumed when the restore actually lands, not
|
||||
// when it was planned, so a second switch arriving first re-parks against the
|
||||
// still-true captured state instead of re-capturing parked values. Discards the
|
||||
// queue unapplied if the project it was enqueued against is no longer current
|
||||
// (close / switch); an intent whose track is gone is pruned. Idle cost is one
|
||||
// empty-queue test.
|
||||
void drainDeferredFxParks(ViewModeModel& model);
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user