Make a bake's Banked landing reachable only through the persist upgrade
The dedup path claimed it directly, so a key could be answered Ok pointing at an entry a failed persist never wrote. Write-back is now guarded and three-valued, and landing one request split out of the scan pass.
This commit is contained in:
+96
-313
@@ -1,31 +1,23 @@
|
||||
// bake_land.cpp — see bake_land.h. main.cpp owns the API pointers; this TU gets them
|
||||
// extern. REAPER symbols used here (EnumProjects, EnumProjExtState, GetProjExtState,
|
||||
// SetProjExtState, ShowConsoleMsg, Undo_BeginBlock2/EndBlock2) are verified against
|
||||
// vendor/reaper-sdk/sdk/reaper_plugin_functions.h.
|
||||
// vendor/reaper-sdk/sdk/reaper_plugin_functions.h. Landing one request is bake_landing's.
|
||||
|
||||
#include "shell/capture/bake_land.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/capture/capture_paths.h" // deriveBankPaths / projectDirOfRpp
|
||||
#include "core/capture/wav_codec.h" // parseWavLayout / hashWavContent
|
||||
#include "core/model/bank_book.h"
|
||||
#include "core/model/bank_model.h"
|
||||
#include "core/model/resample_name.h" // the iteration-chain display name
|
||||
#include "core/tracking/tracking_authority.h" // resampleLanding (replace vs add-distinct)
|
||||
#include "core/util/file_bytes.h" // shared whole-file loader
|
||||
#include "core/capture/capture_paths.h" // projectDirOfRpp
|
||||
#include "core/wire/bake_wire.h"
|
||||
#include "core/wire/ext_state_read.h" // readProjExtStateGrowing (the shared grow loop)
|
||||
#include "core/wire/ext_state_read.h" // readProjExtStateGrowing (the shared grow loop)
|
||||
#include "ext_keys.h"
|
||||
#include "shell/panel/panel_input.h" // bankPanelRefresh
|
||||
#include "shell/capture/bake_landing.h"
|
||||
#include "shell/panel/panel_input.h" // bankPanelRefresh
|
||||
#include "shell/persist/session.h"
|
||||
|
||||
#include "reaper_plugin.h" // UNDO_STATE_MISCCFG
|
||||
@@ -44,256 +36,10 @@ namespace reasampler::capture {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using model::AddResult;
|
||||
using model::Sample;
|
||||
using model::nextIterationName;
|
||||
using wire::BakeOutcome;
|
||||
using wire::BakeRequest;
|
||||
using wire::BakeStatus;
|
||||
|
||||
BakeOutcome refuse(BakeStatus status, std::string message, std::int64_t generation) {
|
||||
BakeOutcome out;
|
||||
out.status = status;
|
||||
out.message = std::move(message);
|
||||
out.generation = generation;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool writeFileBytes(const std::string& path, const std::vector<std::uint8_t>& bytes) {
|
||||
std::ofstream f(path, std::ios::binary | std::ios::trunc);
|
||||
if (!f) return false;
|
||||
f.write(reinterpret_cast<const char*>(bytes.data()),
|
||||
static_cast<std::streamsize>(bytes.size()));
|
||||
return f.good();
|
||||
}
|
||||
|
||||
// The bank holding `sampleId`, plus the entry itself. nullptr when no bank holds it.
|
||||
const Sample* findSourceSample(const BankBook& book, const std::string& sampleId,
|
||||
std::string& bankIdOut) {
|
||||
for (const Bank& b : book.banks()) {
|
||||
if (const Sample* s = b.index.query(sampleId)) {
|
||||
bankIdOut = b.id;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Everything a landing DECIDES, before it touches disk or book: the staged WAV read and
|
||||
// hash, the source lookup, replace-vs-add, and the dedup short-circuit. Split from the
|
||||
// commit below so the two halves' throws can be told apart — a prepare that threw left the
|
||||
// project untouched, and only a commit that threw may have written something.
|
||||
struct PreparedLanding {
|
||||
std::optional<BakeOutcome> settled; // a refusal or a dedup hit: nothing left to commit
|
||||
BakeOutcome outcome; // the shape a successful commit completes
|
||||
Sample landed; // the entry the commit indexes
|
||||
std::vector<std::uint8_t> bytes; // the staged file, already read
|
||||
std::string bankId;
|
||||
std::string destDir;
|
||||
std::string destPath;
|
||||
bool replace = false;
|
||||
};
|
||||
|
||||
PreparedLanding prepareLanding(ReaSamplerSession& session, const std::string& projectDir,
|
||||
const BakeRequest& request) {
|
||||
PreparedLanding prep;
|
||||
if (projectDir.empty()) {
|
||||
prep.settled = refuse(BakeStatus::NoProject,
|
||||
"no saved project, so the bank has no location",
|
||||
request.generation);
|
||||
return prep;
|
||||
}
|
||||
|
||||
prep.bytes = util::readFileBytes(request.stagedFilePath);
|
||||
if (prep.bytes.empty()) {
|
||||
prep.settled = refuse(BakeStatus::StagedMissing, "the staged render was unreadable",
|
||||
request.generation);
|
||||
return prep;
|
||||
}
|
||||
const WavLayout layout = parseWavLayout(prep.bytes);
|
||||
if (!layout.valid || layout.frameCount() == 0) {
|
||||
prep.settled = refuse(BakeStatus::StagedMissing,
|
||||
"the staged render is not a usable WAV", request.generation);
|
||||
return prep;
|
||||
}
|
||||
|
||||
BankBook& book = session.book();
|
||||
const Sample* source = findSourceSample(book, request.sourceSampleId, prep.bankId);
|
||||
if (!source) {
|
||||
prep.settled = refuse(BakeStatus::NoSource,
|
||||
"the resampled capture is not in any bank", request.generation);
|
||||
return prep;
|
||||
}
|
||||
// Copied, not aliased: every mutation the commit makes invalidates the book's pointers.
|
||||
const Sample sourceCopy = *source;
|
||||
|
||||
const tracking::Landing landing = tracking::resampleLanding(
|
||||
session.tiedUsageFor(request.sourceRelativePath, request.ownUsageKey));
|
||||
prep.replace = (landing == tracking::Landing::Replace);
|
||||
|
||||
// WAV-aware hash: the bank's dedup key, and — on the add path only — the reason a
|
||||
// byte-identical bake yields no second entry. Replace never dedups, matching
|
||||
// updateSampleInPlace's own contract: an in-place refresh is not an insert.
|
||||
const std::string contentHash = hashWavContent(prep.bytes);
|
||||
|
||||
prep.outcome.generation = request.generation;
|
||||
prep.outcome.rootNote = request.rootNote;
|
||||
prep.outcome.channelCount = static_cast<int>(layout.channelCount);
|
||||
prep.outcome.replaced = prep.replace;
|
||||
|
||||
if (!prep.replace && !contentHash.empty()) {
|
||||
if (const model::BankModel* index = book.index(prep.bankId)) {
|
||||
if (const Sample* existing = index->findByHash(contentHash)) {
|
||||
// Dedup BEFORE the disk write: this bake is bytes the bank already holds,
|
||||
// so it re-points at that entry rather than depositing an unreferenced
|
||||
// twin for the prune to reclaim later.
|
||||
BakeOutcome out = prep.outcome;
|
||||
out.status = BakeStatus::Ok;
|
||||
out.sampleId = existing->id;
|
||||
out.relativePath = existing->relativePath;
|
||||
out.displayName = existing->displayName;
|
||||
out.message = "identical to an existing capture -- pointed at it";
|
||||
prep.settled = std::move(out);
|
||||
return prep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::int64_t nowSec = static_cast<std::int64_t>(std::time(nullptr));
|
||||
// Seconds alone are not unique enough here: two bakes of the same source inside one
|
||||
// second would derive the same file name, and the second would overwrite a file the
|
||||
// first had just indexed. The content hash separates them, and leaves a re-bake of
|
||||
// identical bytes idempotent rather than duplicated.
|
||||
const std::string uniqueTag =
|
||||
std::to_string(nowSec) +
|
||||
(contentHash.empty() ? std::string{} : "-" + contentHash.substr(0, 8));
|
||||
const std::string stem =
|
||||
sourceCopy.displayName.empty() ? std::string("resample") : sourceCopy.displayName;
|
||||
const BankPaths paths = deriveBankPaths(projectDir, stem, uniqueTag);
|
||||
prep.destDir = paths.absoluteDir;
|
||||
prep.destPath = paths.absoluteDir + "/" + paths.fileName;
|
||||
|
||||
// Replace keeps the entry's identity and its slot — the sound iterated, it did not
|
||||
// become a different capture. The superseded FILE is untouched: it stays on disk,
|
||||
// unreferenced, until a prune reclaims it, which is the iterate loop's recovery floor.
|
||||
prep.landed.id =
|
||||
prep.replace ? sourceCopy.id : ("bake-" + uniqueTag + "-" + paths.fileName);
|
||||
prep.landed.displayName =
|
||||
prep.replace ? sourceCopy.displayName : nextIterationName(sourceCopy.displayName);
|
||||
prep.landed.relativePath = paths.relativePath; // project-relative (invariant)
|
||||
prep.landed.channelCount = static_cast<int>(layout.channelCount);
|
||||
prep.landed.sampleRate = static_cast<int>(layout.sampleRate);
|
||||
prep.landed.lengthSeconds =
|
||||
layout.sampleRate ? static_cast<double>(layout.frameCount()) /
|
||||
static_cast<double>(layout.sampleRate)
|
||||
: 0.0;
|
||||
// Rendered AT root — that is what makes it survive.
|
||||
prep.landed.rootNote = request.rootNote;
|
||||
prep.landed.tier = model::Tier::Scratch;
|
||||
prep.landed.contentHash = contentHash;
|
||||
prep.landed.createdTimestamp = nowSec;
|
||||
// The lineage seed recordCreated reads: the ledger's parent chain is what makes a
|
||||
// repeated bake readable as one iteration chain.
|
||||
prep.landed.provenance = model::Provenance{sourceCopy.id, std::string{}};
|
||||
return prep;
|
||||
}
|
||||
|
||||
// The mutating half. From its first successful write a file exists and the book can change,
|
||||
// so a throw out of THIS function is not invisible to the project — which is why its caller
|
||||
// reports it differently from a prepare that threw. Its own refusals withdraw the file they
|
||||
// wrote, best-effort. Mutates book + ledger without persisting; the caller persists once for
|
||||
// the batch.
|
||||
BakeOutcome commitLanding(ReaSamplerSession& session, PreparedLanding& prep) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(prep.destDir, ec); // idempotent; the write reports failure
|
||||
if (!writeFileBytes(prep.destPath, prep.bytes)) {
|
||||
// A failed write can still have created the file before failing; withdraw it for
|
||||
// the same reason the refused index below does.
|
||||
fs::remove(prep.destPath, ec);
|
||||
return refuse(BakeStatus::Failed, "could not write the bake into the bank folder",
|
||||
prep.outcome.generation);
|
||||
}
|
||||
|
||||
BankBook& book = session.book();
|
||||
// On the replace path prepareLanding set `landed.id` to the source's own id, so this is
|
||||
// the entry being refreshed in place.
|
||||
const bool indexed =
|
||||
prep.replace ? book.updateSampleInPlace(prep.landed.id, prep.landed)
|
||||
: (book.index(prep.bankId) &&
|
||||
book.index(prep.bankId)->add(prep.landed) == AddResult::Added);
|
||||
if (!indexed) {
|
||||
// Self-cleanup of a file this call wrote seconds ago and never indexed — the
|
||||
// carve-out shell/persist/CLAUDE.md states, not prune's authority over the bank's
|
||||
// known bytes. Leaving it would deposit an untracked orphan per refused bake.
|
||||
fs::remove(prep.destPath, ec);
|
||||
return refuse(BakeStatus::IndexRejected,
|
||||
prep.replace ? "the bank refused the replacement"
|
||||
: "the bank refused the new capture",
|
||||
prep.outcome.generation);
|
||||
}
|
||||
session.recordCreated(prep.landed, tracking::OriginKind::Capture);
|
||||
|
||||
BakeOutcome out = prep.outcome;
|
||||
out.status = BakeStatus::Ok;
|
||||
out.sampleId = prep.landed.id;
|
||||
out.relativePath = prep.landed.relativePath;
|
||||
out.displayName = prep.landed.displayName;
|
||||
out.message = prep.replace ? "replaced the bank entry" : "added as a distinct capture";
|
||||
return out;
|
||||
}
|
||||
|
||||
// One request landed, with the two failure truths kept apart: a throw out of the prepare
|
||||
// half is a refusal that left the project untouched, while a throw out of the commit half
|
||||
// may have written a file and changed the book — calling that one "refused" would tell the
|
||||
// user nothing happened when something did.
|
||||
struct LandingAttempt {
|
||||
BakeOutcome outcome;
|
||||
bool partial = false; // the commit threw: a file and/or an entry may exist
|
||||
bool changedBook = false; // the book gained or refreshed an entry, unpersisted so far
|
||||
};
|
||||
|
||||
LandingAttempt attemptLanding(ReaSamplerSession& session, const std::string& projectDir,
|
||||
const BakeRequest& request) {
|
||||
LandingAttempt attempt;
|
||||
PreparedLanding prep;
|
||||
try {
|
||||
prep = prepareLanding(session, projectDir, request);
|
||||
} catch (const std::exception& e) {
|
||||
attempt.outcome =
|
||||
refuse(BakeStatus::Failed,
|
||||
std::string("the landing failed before it wrote anything: ") + e.what(),
|
||||
request.generation);
|
||||
return attempt;
|
||||
} catch (...) {
|
||||
attempt.outcome =
|
||||
refuse(BakeStatus::Failed,
|
||||
"the landing failed before it wrote anything, for an unknown reason",
|
||||
request.generation);
|
||||
return attempt;
|
||||
}
|
||||
if (prep.settled) {
|
||||
attempt.outcome = std::move(*prep.settled);
|
||||
return attempt; // a refusal, or a dedup hit that changed nothing
|
||||
}
|
||||
try {
|
||||
attempt.outcome = commitLanding(session, prep);
|
||||
attempt.changedBook = attempt.outcome.status == BakeStatus::Ok;
|
||||
} catch (const std::exception& e) {
|
||||
attempt.partial = true;
|
||||
attempt.outcome = refuse(BakeStatus::Failed,
|
||||
std::string("the landing failed while writing: ") + e.what(),
|
||||
request.generation);
|
||||
} catch (...) {
|
||||
attempt.partial = true;
|
||||
attempt.outcome = refuse(BakeStatus::Failed,
|
||||
"the landing failed while writing, for an unknown reason",
|
||||
request.generation);
|
||||
}
|
||||
return attempt;
|
||||
}
|
||||
|
||||
// Every "rsbake_*" key in one project, keys only — the value can outgrow
|
||||
// EnumProjExtState's fixed buffer, so it is read separately by the growing reader.
|
||||
std::vector<std::string> pendingBakeKeys(ReaProject* proj) {
|
||||
@@ -329,15 +75,6 @@ KeyRead readKey(ReaProject* proj, const std::string& key) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Writes one value under `key` and PROVES it by reading the key back. SetProjExtState's own
|
||||
// return cannot do that: it is the size of the whole extname's state, which `banks` alone
|
||||
// keeps non-zero in every case a bake can reach. One extra read per written key, on a cold
|
||||
// path. An empty `value` is a clear.
|
||||
bool writeKeyVerified(ReaProject* proj, const std::string& key, const std::string& value) {
|
||||
SetProjExtState(proj, kProjExtNamespace(), key.c_str(), value.c_str());
|
||||
return wire::bakeWriteLanded(value, readKey(proj, key).value);
|
||||
}
|
||||
|
||||
struct OpenProject {
|
||||
ReaProject* proj = nullptr;
|
||||
std::string dir; // the project's own directory; empty for a never-saved project
|
||||
@@ -390,6 +127,50 @@ private:
|
||||
int flags_ = 0;
|
||||
};
|
||||
|
||||
// One entry's write-back, and the proof of whether it took. Guarded whole: it encodes into
|
||||
// a fresh string and readKey grows a buffer toward 16 MB, and a throw escaping here would
|
||||
// skip the ShowConsoleMsg for EVERY key — the silent console this action exists to make
|
||||
// impossible. A throw is `Unknown` rather than a failure because the write may well have
|
||||
// landed before the check did.
|
||||
wire::BakeWriteProof writeBackOne(ScannedKey& entry, bool persisted) {
|
||||
try {
|
||||
std::string value; // empty = clear the key
|
||||
if (entry.write == ScannedKey::Write::Answer) {
|
||||
// Where a landing earns the word: the pass's persist runs after the whole scan,
|
||||
// so until here nothing had observed whether the project took it. Without it
|
||||
// the entry lives in memory alone, which is a failed bake to anyone who
|
||||
// reloads — so it is answered as one rather than as an Ok.
|
||||
const wire::BakeLanding after =
|
||||
wire::bakeLandingAfterPersist(entry.report.landing, persisted);
|
||||
if (after == wire::BakeLanding::Unpersisted) {
|
||||
entry.outcome = refuseBake(
|
||||
BakeStatus::Failed,
|
||||
"the bake reached the bank in memory, but this pass's persist did not "
|
||||
"report success, so this answer cannot promise a reload will find it",
|
||||
entry.outcome.generation);
|
||||
// `detail` left empty on purpose: describeLanding's Unpersisted clause is
|
||||
// that sentence's one home on the console side, and the message above is
|
||||
// the instrument's own copy on the wire.
|
||||
} else {
|
||||
entry.report.detail = entry.outcome.message;
|
||||
}
|
||||
entry.report.landing = after;
|
||||
value = wire::encodeBakeOutcome(entry.outcome);
|
||||
}
|
||||
SetProjExtState(entry.proj, kProjExtNamespace(), entry.key.c_str(), value.c_str());
|
||||
// SetProjExtState's own return is the size of the whole extname's state, which
|
||||
// `banks` alone keeps non-zero in every case a bake can reach, so only re-reading
|
||||
// THIS key can speak for it. An oversized read proves nothing either way — folding
|
||||
// it into bakeWriteLanded's absent case would report a clear off unread bytes.
|
||||
const KeyRead back = readKey(entry.proj, entry.key);
|
||||
if (back.oversized) return wire::BakeWriteProof::Unknown;
|
||||
return wire::bakeWriteLanded(value, back.value) ? wire::BakeWriteProof::Confirmed
|
||||
: wire::BakeWriteProof::Rejected;
|
||||
} catch (...) {
|
||||
return wire::BakeWriteProof::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RunResampleBake(ReaSamplerSession& session) {
|
||||
@@ -412,7 +193,8 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
std::vector<ScannedKey> scanned;
|
||||
wire::BakeScanTally tally; // every verdict below is counted, skips included
|
||||
std::string aborted; // set only when the scan itself threw
|
||||
bool persisted = false; // the pass's ONE persist actually happened
|
||||
bool persisted = false; // the pass's ONE persist reported success
|
||||
bool bookChanged = false; // some landing added or refreshed an entry
|
||||
// The scan allocates outside attemptLanding's own guards too (readKey grows a buffer
|
||||
// toward 16 MB, every container here allocates), and a throw that escaped would discard
|
||||
// the answers already buffered — the exact no-answer-with-a-silent-console this action
|
||||
@@ -465,25 +247,24 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
|
||||
if (verdict == wire::BakeScanVerdict::RefuseWrongProject) {
|
||||
entry.outcome =
|
||||
refuse(BakeStatus::WrongProject,
|
||||
"this bake's project tab is not the one the extension has "
|
||||
"loaded -- focus that tab and try again",
|
||||
request->generation);
|
||||
refuseBake(BakeStatus::WrongProject,
|
||||
"this bake's project tab is not the one the extension "
|
||||
"has loaded -- focus that tab and try again",
|
||||
request->generation);
|
||||
} else {
|
||||
LandingAttempt attempt = attemptLanding(session, open.dir, *request);
|
||||
if (attempt.outcome.status == BakeStatus::Ok) ++tally.landed;
|
||||
// Unpersisted rather than Banked for anything that changed the book: the
|
||||
// pass's persist has not run yet, and the write loop is where the
|
||||
// upgrade is earned. A dedup hit changed nothing, so its landing is not
|
||||
// this pass's to lose.
|
||||
if (attempt.changedBook) bookChanged = true;
|
||||
// Never Banked here, on ANY path: the pass's persist has not run yet,
|
||||
// and bakeLandingAfterPersist is the one place that word is earned. A
|
||||
// dedup hit is no exception — the entry it points at may be one this
|
||||
// same pass added and is about to fail to persist.
|
||||
if (attempt.partial)
|
||||
entry.report.landing = wire::BakeLanding::Partial;
|
||||
else if (attempt.outcome.status != BakeStatus::Ok)
|
||||
entry.report.landing = wire::BakeLanding::Refused;
|
||||
else
|
||||
entry.report.landing = attempt.changedBook
|
||||
? wire::BakeLanding::Unpersisted
|
||||
: wire::BakeLanding::Banked;
|
||||
entry.report.landing = wire::BakeLanding::Unpersisted;
|
||||
entry.outcome = std::move(attempt.outcome);
|
||||
}
|
||||
++tally.answered; // QUEUED, not written — the write loop below judges that
|
||||
@@ -493,56 +274,58 @@ void RunResampleBake(ReaSamplerSession& session) {
|
||||
}
|
||||
|
||||
if (tally.landed > 0) {
|
||||
// A bake changes what a live instance would play, so the generation bump rides
|
||||
// the persist — every other open instance refreshes hands-free.
|
||||
session.bumpBankGeneration();
|
||||
// A dedup hit rides the persist too, because that is the only observation that
|
||||
// can promote its landing — but it added nothing, so it earns neither the
|
||||
// refresh every open instance pays for nor an undo point named after a bake
|
||||
// that banked nothing.
|
||||
if (bookChanged) session.bumpBankGeneration();
|
||||
persisted = session.saveToActiveProject();
|
||||
if (persisted)
|
||||
if (persisted && bookChanged)
|
||||
undo.recordPoint("ReaSampler: resample bake into bank", UNDO_STATE_MISCCFG);
|
||||
}
|
||||
} catch (...) {
|
||||
aborted =
|
||||
"ReaSampler resample: the landing pass stopped early on an internal failure. "
|
||||
"The answers it had already prepared are written below; any key whose answer it "
|
||||
"had not prepared yet went unanswered and is not named at all.\n";
|
||||
"The answers it had already prepared are reported below; any key whose answer "
|
||||
"it had not prepared yet went unanswered and is not named at all.\n";
|
||||
}
|
||||
|
||||
// OUTSIDE the undo block on purpose: the answer is transient handshake state, and an
|
||||
// undo point that captured it could restore the consumed request on the next Ctrl-Z —
|
||||
// which the following bake would then re-land against a temp file that is long gone.
|
||||
std::string console = aborted;
|
||||
for (ScannedKey& entry : scanned) {
|
||||
if (entry.write != ScannedKey::Write::None) {
|
||||
std::string value; // empty = clear the key
|
||||
if (entry.write == ScannedKey::Write::Answer) {
|
||||
// Where a landing earns the word: the persist ran after the whole scan, so
|
||||
// until here nothing had observed whether the project took it. Without it
|
||||
// the entry lives in memory alone, which is a failed bake to anyone who
|
||||
// reloads — so it is answered as one rather than as an Ok.
|
||||
if (entry.report.landing == wire::BakeLanding::Unpersisted) {
|
||||
if (persisted)
|
||||
entry.report.landing = wire::BakeLanding::Banked;
|
||||
else
|
||||
entry.outcome = refuse(BakeStatus::Failed,
|
||||
"the bake reached the bank in memory, but "
|
||||
"this pass could not persist it, so the "
|
||||
"project's saved bank state does not carry it",
|
||||
entry.outcome.generation);
|
||||
try {
|
||||
for (ScannedKey& entry : scanned) {
|
||||
if (entry.write != ScannedKey::Write::None) {
|
||||
entry.report.proof = writeBackOne(entry, persisted);
|
||||
if (entry.write == ScannedKey::Write::Answer) {
|
||||
if (entry.report.proof == wire::BakeWriteProof::Rejected)
|
||||
++tally.writeFailed;
|
||||
else if (entry.report.proof == wire::BakeWriteProof::Unknown)
|
||||
++tally.writeUnproven;
|
||||
}
|
||||
value = wire::encodeBakeOutcome(entry.outcome);
|
||||
entry.report.detail = entry.outcome.message;
|
||||
}
|
||||
entry.report.writeConfirmed = writeKeyVerified(entry.proj, entry.key, value);
|
||||
if (!entry.report.writeConfirmed && entry.write == ScannedKey::Write::Answer)
|
||||
++tally.writeFailed;
|
||||
// Unconditional, including on success: the tally counts keys but cannot name
|
||||
// them, and the key name is the only thing that tells an instance which line is
|
||||
// its own.
|
||||
console += wire::describeBakeKey(entry.key, entry.report);
|
||||
}
|
||||
console += wire::describeBakeScan(tally);
|
||||
} catch (...) {
|
||||
// Whatever lines already accumulated must still reach the console — that guarantee
|
||||
// is this action's whole reason for existing. A second throw while appending the
|
||||
// note below is swallowed for exactly the same reason: the print is what matters,
|
||||
// and append gives the strong guarantee, so `console` survives either way.
|
||||
try {
|
||||
console +=
|
||||
"ReaSampler resample: the write-back pass stopped early on an internal "
|
||||
"failure. The lines above are everything it managed to report; any key not "
|
||||
"named above may or may not have been answered.\n";
|
||||
} catch (...) {
|
||||
}
|
||||
// Unconditional, including on success: the tally counts keys but cannot name them,
|
||||
// and the key name is the only thing that tells an instance which line is its own.
|
||||
console += wire::describeBakeKey(entry.key, entry.report);
|
||||
}
|
||||
console += wire::describeBakeScan(tally);
|
||||
if (!console.empty()) ShowConsoleMsg(console.c_str());
|
||||
if (tally.landed > 0) bankPanelRefresh();
|
||||
if (bookChanged) bankPanelRefresh();
|
||||
}
|
||||
|
||||
} // namespace reasampler::capture
|
||||
|
||||
Reference in New Issue
Block a user