Fist commit

This commit is contained in:
Angel Ivan
2026-10-02 12:08:21 -06:00
commit 2a9eabce14
54 changed files with 10737 additions and 0 deletions

259
lib/timbre_core/PatternEngine.cpp Executable file
View File

@@ -0,0 +1,259 @@
#include "PatternEngine.h"
#include <limits>
namespace timbre_core {
void PatternEngine::initializeDefaults() noexcept {
for (std::size_t i = 0; i < kMaxPatterns; ++i) {
patterns_[i].id = patternIdFromIndex(i);
}
configuredPatternMask_ =
static_cast<std::uint8_t>((1u << kMaxPatterns) - 1u);
}
std::uint64_t PatternEngine::readClock() const {
if (clockInterface_ != nullptr) {
return clockInterface_->nowMs();
}
if (clock_) {
return clock_();
}
return 0;
}
bool PatternEngine::validPattern(const Pattern& pattern) const noexcept {
return isPatternId(pattern.id) && ScheduleValidator::validatePattern(pattern).valid;
}
void PatternEngine::setPattern(const Pattern& pattern) noexcept {
if (!validPattern(pattern)) {
return;
}
const std::size_t index = patternIndex(pattern.id);
if (index < patterns_.size()) {
patterns_[index] = pattern;
configuredPatternMask_ = static_cast<std::uint8_t>(
configuredPatternMask_ | (1u << index));
}
}
void PatternEngine::setPatterns(const AppState& state) noexcept {
bool allValid = true;
for (std::size_t i = 0; i < kMaxPatterns; ++i) {
if (!validPattern(state.patterns[i])) {
allValid = false;
break;
}
}
if (!allValid) {
return;
}
patterns_ = state.patterns;
configuredPatternMask_ = static_cast<std::uint8_t>((1u << kMaxPatterns) - 1u);
}
bool PatternEngine::enqueueSnapshot(const Pattern& pattern, std::uint64_t startAtMs) {
if (!validPattern(pattern) || queueSize_ >= kQueueCapacity) {
return false;
}
const std::size_t tail = (queueHead_ + queueSize_) % kQueueCapacity;
queue_[tail].pattern = pattern;
queue_[tail].startAtMs = startAtMs;
++queueSize_;
return true;
}
bool PatternEngine::enqueue(PatternId id) {
return enqueue(id, readClock());
}
bool PatternEngine::enqueue(PatternId id, std::uint64_t startAtMs) {
const std::size_t index = patternIndex(id);
if (index >= patterns_.size() ||
(configuredPatternMask_ & (1u << index)) == 0) {
return false;
}
return enqueueSnapshot(patterns_[index], startAtMs);
}
bool PatternEngine::enqueue(const Pattern& pattern) {
return enqueue(pattern, readClock());
}
bool PatternEngine::enqueue(const Pattern& pattern, std::uint64_t startAtMs) {
return enqueueSnapshot(pattern, startAtMs);
}
bool PatternEngine::enqueue(PatternId id, const AppState& state, std::uint64_t startAtMs) {
const Pattern* pattern = state.pattern(id);
return pattern != nullptr && enqueueSnapshot(*pattern, startAtMs);
}
bool PatternEngine::enqueue(PatternId id, const AppState& state) {
return enqueue(id, state, readClock());
}
bool PatternEngine::start(PatternId id, std::uint64_t nowMs) {
const std::size_t index = patternIndex(id);
if (index >= patterns_.size() ||
(configuredPatternMask_ & (1u << index)) == 0) {
return false;
}
return startSnapshot(patterns_[index], nowMs);
}
bool PatternEngine::start(const Pattern& pattern, std::uint64_t nowMs) {
return startSnapshot(pattern, nowMs);
}
bool PatternEngine::start(PatternId id, const AppState& state, std::uint64_t nowMs) {
const Pattern* pattern = state.pattern(id);
return pattern != nullptr && startSnapshot(*pattern, nowMs);
}
bool PatternEngine::start(PatternId id, const AppState& state) {
return start(id, state, readClock());
}
std::uint64_t PatternEngine::addDuration(std::uint64_t deadline,
std::uint8_t seconds) noexcept {
const std::uint64_t duration = static_cast<std::uint64_t>(seconds) * kMillisecondsPerSecond;
if (deadline > std::numeric_limits<std::uint64_t>::max() - duration) {
return std::numeric_limits<std::uint64_t>::max();
}
return deadline + duration;
}
bool PatternEngine::startSnapshot(const Pattern& pattern, std::uint64_t nowMs) {
if (!validPattern(pattern)) {
return false;
}
if (active_) {
finishActive();
}
activePattern_ = pattern;
active_ = true;
phase_ = 0;
deadlineMs_ = 0;
outputOn_ = false;
hasUpdateTime_ = true;
lastUpdateMs_ = nowMs;
std::size_t firstActivePhase = 0;
while (firstActivePhase < pattern.phaseCount &&
pattern.phases[firstActivePhase] == 0) {
++firstActivePhase;
}
if (firstActivePhase == pattern.phaseCount) {
finishActive();
return true;
}
phase_ = static_cast<std::uint8_t>(firstActivePhase);
emit(pattern.phaseIsOn(firstActivePhase));
deadlineMs_ = addDuration(nowMs, pattern.phases[firstActivePhase]);
return true;
}
void PatternEngine::emit(bool on) noexcept {
// The engine has one physical output by design. Reserved GPIO23 is never
// passed to an output sink, even if a caller supplies an arbitrary ID.
outputOn_ = on;
lastOutputRelay_ = RelayId::Main;
if (output_) {
output_(RelayId::Main, on);
} else if (simpleOutput_) {
simpleOutput_(on);
}
}
void PatternEngine::finishActive() noexcept {
if (active_ && outputOn_) {
emit(false);
}
active_ = false;
phase_ = kNoPhase;
deadlineMs_ = 0;
outputOn_ = false;
}
void PatternEngine::pump(std::uint64_t nowMs) {
// FIFO order is intentional. A future head blocks later requests rather
// than reordering a user's schedule.
while (!active_ && queueSize_ != 0 && queue_[queueHead_].startAtMs <= nowMs) {
const Request request = queue_[queueHead_];
queueHead_ = (queueHead_ + 1) % kQueueCapacity;
--queueSize_;
(void)startSnapshot(request.pattern, nowMs);
}
}
bool PatternEngine::update(std::uint64_t nowMs) {
if (hasUpdateTime_ && nowMs < lastUpdateMs_) {
// A clock regression is unsafe for an active relay. Stop the sequence
// instead of leaving the last output energized indefinitely.
const bool changed = active_ || outputOn_;
finishActive();
return changed;
}
hasUpdateTime_ = true;
lastUpdateMs_ = nowMs;
bool changed = false;
if (active_) {
// Deadlines are absolute. A delayed poll walks the finite phase list
// and cannot accumulate delay or schedule a phase in the past.
while (active_ && nowMs >= deadlineMs_) {
if (phase_ + 1u >= activePattern_.phaseCount) {
finishActive();
changed = true;
break;
}
++phase_;
emit(activePattern_.phaseIsOn(phase_));
deadlineMs_ = addDuration(deadlineMs_, activePattern_.phases[phase_]);
changed = true;
}
}
if (!active_ && queueSize_ != 0) {
const std::size_t before = queueSize_;
pump(nowMs);
changed = changed || before != queueSize_ || active_;
}
return changed;
}
bool PatternEngine::update() {
if (clockInterface_ == nullptr && !clock_) {
return false;
}
return update(readClock());
}
void PatternEngine::stop(std::uint64_t nowMs) {
if (hasUpdateTime_ && nowMs < lastUpdateMs_) {
// A bad sample must not prevent the safety turn-off.
finishActive();
return;
}
hasUpdateTime_ = true;
lastUpdateMs_ = nowMs;
finishActive();
}
void PatternEngine::stop() {
if (clockInterface_ != nullptr || clock_) {
stop(readClock());
} else {
stop(lastUpdateMs_);
}
}
void PatternEngine::cancelAll() noexcept {
queueHead_ = 0;
queueSize_ = 0;
}
} // namespace timbre_core