Take the read-back back out of the persist; a write verdict belongs only where evidence crosses the plugin boundary
Its false gated six undo points, so an unverified byte-equality assumption could have silently removed Ctrl-Z for a bank mutation that landed.
This commit is contained in:
@@ -53,7 +53,7 @@ REAPER/filesystem-facing half only, and it gathers rather than decides.
|
||||
|
||||
## Modules
|
||||
|
||||
- `shell/persist` (`session` / `ext_state_io` / `prune_fs`) — the persist seam, split by responsibility (Q-W5; the former `persist.cpp` god-TU and its `persist.h` compatibility umbrella are both retired — callers include `shell/persist/session.h` / `ext_state_io.h` directly). `session` owns the `ReaSamplerSession` lifecycle: the poll identity-transition detection (load / Save-As / forked sibling / recycled pointer) and the `projectconfig`-driven deferred undo/redo reload. `ext_state_io` owns project ext state (`SetProjExtState`/`GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON, `ViewModeModel` JSON, `TailSetting` JSON, the tracking ledger JSON, the writing-version stamp, GUID minting, and bank-folder relocation. `saveToActiveProject` PROVES its `banks` write by reading that key back (`wire::extStateWriteLanded`) and returns that verdict — the sibling keys are written but not verified, so its `true` means "the bank state is in the project", never "everything persisted". `session` additionally owns `recordCreated` — **the one writer of a birth record**, called at the same point the `Sample` is added, deriving lineage from that `Sample`'s own provenance. `prune_fs` hosts the prune dry-run / full-set orphan queries (gathering `referencedPaths()` plus `tracking::pruneProtection`'s two inputs for the `prune_reconcile` pure core) — and, beside them, `tiedUsageFor`, the resample's replace-vs-add input, deliberately co-located so "both answers come out of one `TrackingState`" is structural rather than a rule two files must remember. It is also **the single file-deletion authority over user files in the bank folder** (`deleteOrphanFile` via `SHFileOperationW`); nothing else in the system deletes bank-folder bytes. Dry-run / orphan-set / reclaim each independently abort (delete nothing) when the authority reports a block.
|
||||
- `shell/persist` (`session` / `ext_state_io` / `prune_fs`) — the persist seam, split by responsibility (Q-W5; the former `persist.cpp` god-TU and its `persist.h` compatibility umbrella are both retired — callers include `shell/persist/session.h` / `ext_state_io.h` directly). `session` owns the `ReaSamplerSession` lifecycle: the poll identity-transition detection (load / Save-As / forked sibling / recycled pointer) and the `projectconfig`-driven deferred undo/redo reload. `ext_state_io` owns project ext state (`SetProjExtState`/`GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON, `ViewModeModel` JSON, `TailSetting` JSON, the tracking ledger JSON, the writing-version stamp, GUID minting, and bank-folder relocation. `saveToActiveProject` returns whether the writes were ISSUED — false means no active/saved project and NOTHING was written, which is the only reading its callers' discard-the-undo-point branch is safe under; it must never grow an observational third failure mode (the contract lives at its declaration in `session.h`). `session` additionally owns `recordCreated` — **the one writer of a birth record**, called at the same point the `Sample` is added, deriving lineage from that `Sample`'s own provenance. `prune_fs` hosts the prune dry-run / full-set orphan queries (gathering `referencedPaths()` plus `tracking::pruneProtection`'s two inputs for the `prune_reconcile` pure core) — and, beside them, `tiedUsageFor`, the resample's replace-vs-add input, deliberately co-located so "both answers come out of one `TrackingState`" is structural rather than a rule two files must remember. It is also **the single file-deletion authority over user files in the bank folder** (`deleteOrphanFile` via `SHFileOperationW`); nothing else in the system deletes bank-folder bytes. Dry-run / orphan-set / reclaim each independently abort (delete nothing) when the authority reports a block.
|
||||
- `usage_scan` — extension-side prune-scan shell: enumerates every `rsusage_*` ext-state key, decodes each `sample_usage` wire record, enumerates every ReaSampler 9000 FX instance across all tracks + master / normal + record chains / containers (recursive) / take FX, and returns the pure `sample_usage::foldUsageRecords` result verbatim. One of the two inputs `tracking::pruneProtection` reads; it decides nothing itself. Read-only: writes no ext-state.
|
||||
- `persist_internal.h` — internal-only shared helpers for the persist TU family (`session` / `ext_state_io` / `prune_fs`); included only by those three TUs, never a public seam (mirror of the panel's `panel_state.h` / the editor's `editor_internal.h` precedent). Holds the former anonymous-namespace helpers more than one split TU needs (active-project + `.rpp` path lookup, project-dir derivation, growing `GetProjExtState` read, project-GUID minting, bank-folder relocation) — all definitions live in `ext_state_io.cpp`. REAPER-free header: the project handle crosses this seam as the same opaque `void*` the public `session` header already uses.
|
||||
|
||||
|
||||
@@ -183,23 +183,10 @@ bool ReaSamplerSession::saveToActiveProject() {
|
||||
|
||||
MarkProjectDirty(static_cast<ReaProject*>(proj));
|
||||
|
||||
// Prove the ONE key a caller's answer hinges on. Each SetProjExtState above returns the
|
||||
// size of the whole extname's state, which the six writes here keep non-zero between
|
||||
// them, so no single one of those returns can speak for `banks`
|
||||
// (wire::extStateWriteLanded owns the reasoning). The sibling keys stay unobserved and
|
||||
// no caller claims otherwise; `banks` is the one whose absence would make a landed
|
||||
// capture vanish on reload.
|
||||
const wire::GrowingExtStateRead back = wire::readProjExtStateGrowing(
|
||||
[&](char* buf, int cap) {
|
||||
return GetProjExtState(static_cast<ReaProject*>(proj), projExtNamespace(),
|
||||
kProjExtBanksKey, buf, cap);
|
||||
});
|
||||
if (back.status == wire::GrowingExtStateRead::Status::Overflow)
|
||||
return false; // could not check -> do not claim; never folded in as an absence
|
||||
return wire::extStateWriteLanded(
|
||||
banksJson, back.status == wire::GrowingExtStateRead::Status::Complete
|
||||
? std::optional<std::string>(back.value)
|
||||
: std::nullopt);
|
||||
// The writes were ISSUED into a saved active project — all this call can observe, and
|
||||
// deliberately all it claims. Do not "prove" them with a read-back; session.h states
|
||||
// what a false has to keep meaning to its callers, and why.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReaSamplerSession::writeAssignmentRequest(const std::string& wire) {
|
||||
|
||||
+10
-10
@@ -108,16 +108,14 @@ public:
|
||||
void bumpBankGeneration() { ++bankGeneration_; }
|
||||
|
||||
// Serializes book/view/tail to ext state, clears the retired legacy
|
||||
// `bank_index` key. No-ops with no active/saved project.
|
||||
// `bank_index` key. No-ops with no active/saved project. Returns true iff
|
||||
// a persist happened, so a caller can skip an undo block when nothing was written.
|
||||
//
|
||||
// Returns true iff the `banks` key READ BACK as exactly what this call wrote —
|
||||
// the only per-key observation available under a shared extname
|
||||
// (wire::extStateWriteLanded owns why SetProjExtState's own return cannot
|
||||
// answer it). A false therefore covers four things without distinguishing
|
||||
// them: no active project, an unsaved project, a rejected write, and a
|
||||
// read-back that could not complete. The sibling keys (view/tail/ledger/
|
||||
// version/generation) are written but NOT verified, so no caller may read
|
||||
// this as "everything persisted" — only as "the bank state is in the project".
|
||||
// "Happened" is the writes being ISSUED, and a false means exactly two things —
|
||||
// no active project, or an unsaved one — with NOTHING written under either. SIX
|
||||
// callers turn a false straight into a discarded undo point, so this must never
|
||||
// grow a third, OBSERVATIONAL failure mode: a false negative would silently
|
||||
// remove the Ctrl-Z for a bank mutation that landed.
|
||||
bool saveToActiveProject();
|
||||
|
||||
// Report-only prune dry-run: feeds the pure core with (present, referenced,
|
||||
@@ -151,7 +149,9 @@ public:
|
||||
// Write the ingest assignment request (`assign_request` key): "the active
|
||||
// sampler instance should now play THIS sample." `wire` is pre-encoded
|
||||
// (assignment_request.h); a sibling one-shot write, not part of
|
||||
// saveToActiveProject's blob. Returns true iff written.
|
||||
// saveToActiveProject's blob. Same two-condition return as that call: false
|
||||
// means no active/saved project and nothing was written, true means the write
|
||||
// was ISSUED — never that the project took it.
|
||||
bool writeAssignmentRequest(const std::string& wire);
|
||||
|
||||
// Detects a project load or Save-As and reacts. Driven by REAPER's
|
||||
|
||||
Reference in New Issue
Block a user