fix: bake-answer messages stop asserting causes the classifier can't see
Rewords Cleared/Unanswered/ForeignRequest to name only observed facts and every live hypothesis instead of picking a winner; adds a guarded answeredOutcome accessor against a future unhandled BakeAnswerKind; folds an unreachable BakeScanKey/Context test state; fixes generation-stamp timing.
This commit is contained in:
@@ -127,6 +127,9 @@ std::optional<BakeOutcome> decodeBakeOutcome(const std::string& wire) {
|
||||
BakeAnswer classifyBakeAnswer(const std::optional<std::string>& raw,
|
||||
const BakeRequest& sent) {
|
||||
BakeAnswer answer;
|
||||
// `raw` folds three distinct bridge outcomes into one — absent, explicitly
|
||||
// cleared, and an unreadable/oversized read — because the bridge cannot label
|
||||
// which occurred. A caller's message for `Cleared` must not claim more than that.
|
||||
if (!raw || raw->empty()) {
|
||||
answer.kind = BakeAnswerKind::Cleared;
|
||||
return answer;
|
||||
@@ -150,6 +153,11 @@ BakeAnswer classifyBakeAnswer(const std::optional<std::string>& raw,
|
||||
return answer;
|
||||
}
|
||||
|
||||
const BakeOutcome* answeredOutcome(const BakeAnswer& answer) {
|
||||
if (answer.kind != BakeAnswerKind::Answered || !answer.outcome) return nullptr;
|
||||
return &*answer.outcome;
|
||||
}
|
||||
|
||||
BakeScanVerdict classifyBakeScan(const BakeScanContext& session, const BakeScanKey& key,
|
||||
std::int64_t nowSec) {
|
||||
// Not a request: an outcome the writing instance has not collected yet, or a value
|
||||
@@ -161,11 +169,11 @@ BakeScanVerdict classifyBakeScan(const BakeScanContext& session, const BakeScanK
|
||||
if (age > kMaxRequestAgeSeconds || age < -kMaxRequestAgeSeconds)
|
||||
return BakeScanVerdict::ClearStale;
|
||||
|
||||
// Three tabs have to agree before anything may land: the tab the request was found in,
|
||||
// the tab the session's book/ledger last loaded, and the tab a persist will write into.
|
||||
// Landing on a disagreement writes one tab's bake into another's bank.
|
||||
const bool landable = session.sessionHasLoadedProject &&
|
||||
session.loadedProjectIsActive && key.inLoadedProject;
|
||||
// Two facts have to agree before anything may land: the loaded project is the one a
|
||||
// persist would write into (loadedProjectIsActive), and this key's tab IS that loaded
|
||||
// project (matchesLoadedProject — false by construction whenever no project is
|
||||
// loaded, which is what folds the former three-way check into two).
|
||||
const bool landable = session.loadedProjectIsActive && key.matchesLoadedProject;
|
||||
return landable ? BakeScanVerdict::Land : BakeScanVerdict::RefuseWrongProject;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,8 @@ enum class BakeAnswerKind {
|
||||
ForeignOutcome, // a decodable outcome, but for another generation
|
||||
Unanswered, // this request, unchanged: nothing on the extension side read it
|
||||
ForeignRequest, // a request that is not ours — another instance shares this key
|
||||
Cleared, // the key holds nothing: cleared without an answer
|
||||
Cleared, // the key holds nothing — absent, explicitly cleared, or a read
|
||||
// failure at the bridge; these are not distinguishable from here
|
||||
Undecodable, // neither record — a build whose wire this one does not read
|
||||
};
|
||||
|
||||
@@ -105,9 +106,20 @@ struct BakeAnswer {
|
||||
BakeAnswer classifyBakeAnswer(const std::optional<std::string>& raw,
|
||||
const BakeRequest& sent);
|
||||
|
||||
// The outcome iff `answer.kind == Answered`; nullptr in every other state. The ONE place
|
||||
// the Answered-implies-outcome-is-set contract is enforced, so a caller can fail closed
|
||||
// on a state this switch does not (yet) name instead of dereferencing `answer.outcome` on
|
||||
// the strength of switch exhaustiveness alone — exhaustiveness a future BakeAnswerKind
|
||||
// enumerator (BakeAnswerKind is documented append-only, like its BakeStatus neighbor)
|
||||
// would silently break with no compiler diagnostic (no -Wswitch/-Werror configured).
|
||||
const BakeOutcome* answeredOutcome(const BakeAnswer& answer);
|
||||
|
||||
// The whole chain is a call and a return inside ONE editor tick, so a request older than
|
||||
// this has no reader left. Landing one would bank it for nobody and leave an outcome
|
||||
// nobody collects in the .rpp forever.
|
||||
// nobody collects in the .rpp forever. The bound is only meaningful because both ends
|
||||
// read the same wall clock (std::time) AND the instrument stamps `generation`
|
||||
// immediately before publishing the request — after staging the WAV, so that write
|
||||
// never eats into the budget.
|
||||
inline constexpr std::int64_t kMaxRequestAgeSeconds = 30;
|
||||
|
||||
// The extension's per-key verdict on one scanned `rsbake_*` key.
|
||||
@@ -118,19 +130,24 @@ enum class BakeScanVerdict {
|
||||
Ignore, // not a request (an uncollected outcome, or a wire we do not read)
|
||||
};
|
||||
|
||||
// The session's side of the verdict — which tab the extension's book belongs to, and
|
||||
// whether that tab is the one REAPER will persist into.
|
||||
// The session's side of the verdict: whether the loaded project is the one REAPER will
|
||||
// persist into. Whether a project is loaded at ALL is folded into
|
||||
// BakeScanKey::matchesLoadedProject below rather than carried as a second flag here — a
|
||||
// key can only ever match a project that is loaded, so a standalone "session has a
|
||||
// project" flag on this side could disagree with the key's and describe a state the
|
||||
// shell can never actually produce.
|
||||
struct BakeScanContext {
|
||||
bool sessionHasLoadedProject = false; // the session has polled a project
|
||||
bool loadedProjectIsActive = false; // that project is REAPER's active tab
|
||||
bool loadedProjectIsActive = false; // the loaded project is REAPER's active tab
|
||||
};
|
||||
|
||||
// The scanned key's side. Both flags are per-TAB, which is what makes a request found in
|
||||
// a background tab decidable without any REAPER type crossing into this module.
|
||||
// The scanned key's side — per-TAB, which is what makes a request found in a background
|
||||
// tab decidable without any REAPER type crossing into this module.
|
||||
struct BakeScanKey {
|
||||
bool decoded = false; // the value decoded as a BakeRequest
|
||||
std::int64_t generation = 0;
|
||||
bool inLoadedProject = false;
|
||||
bool matchesLoadedProject = false; // this key's tab IS the session's loaded project
|
||||
// — false whenever the session has no loaded
|
||||
// project, by construction (see BakeScanContext)
|
||||
};
|
||||
|
||||
BakeScanVerdict classifyBakeScan(const BakeScanContext& session, const BakeScanKey& key,
|
||||
|
||||
Reference in New Issue
Block a user