// bank_sync.cpp — see bank_sync.h. Pure; standard library only. #include "bank_sync.h" #include #include #include namespace reasampler::vst { std::int64_t parseBankGeneration(const std::string& raw) { if (raw.empty()) return kBankGenerationAbsent; // Whole-string, non-negative decimal parse WITHOUT exceptions or locale surprises. // A leading '+' / '-' , any non-digit, an empty digit run, or overflow past int64 max // all reject to the absent default (0). Manual accumulation with an overflow guard so a // pathologically long digit run can never wrap into a bogus small value. std::int64_t value = 0; constexpr std::int64_t kMax = std::numeric_limits::max(); for (const char c : raw) { if (c < '0' || c > '9') return kBankGenerationAbsent; // any non-digit -> reject whole const int digit = c - '0'; // Guard value*10 + digit against overflow before performing it. if (value > (kMax - digit) / 10) return kBankGenerationAbsent; // would overflow -> reject value = value * 10 + digit; } return value; } std::string formatBankGeneration(std::int64_t generation) { // Non-negative decimal; a negative (should never be produced by the writer) formats as // its std::to_string form and would parse back to 0, so the writer's monotonic counter // stays in the >= 0 domain by construction. return std::to_string(generation); } bool bankGenerationChanged(std::int64_t seen, std::int64_t current) { return current != seen; } AssignConsumeDecision consumeDecision(const std::optional& request, std::int64_t lastConsumed, bool resolves, bool isFocusedTarget) { AssignConsumeDecision d; d.consumedGeneration = lastConsumed; // default: nothing changes // Rule 1: no request, or not newer than what we already consumed -> nothing new. if (!request) return d; if (request->generation <= lastConsumed) return d; // Rule 2: a new request, but this instance is not the target -> do not act, do NOT // advance the marker (stay eligible if focus later lands here). No thundering herd. if (!isFocusedTarget) return d; // The request is new AND we are the target: it will be consumed-as-seen either way, so // advance the marker to its generation so it is never re-evaluated. d.consumedGeneration = request->generation; // Rule 3: unresolvable (bankId, sampleId) -> DROP silently (reader requirement): marker // advanced above, but no selection change. if (!resolves) return d; // Rule 4: new, target, resolvable -> apply the selection. d.apply = true; d.bankId = request->bankId; d.sampleId = request->sampleId; return d; } } // namespace reasampler::vst