Fist commit
This commit is contained in:
259
lib/timbre_core/PatternEngine.cpp
Executable file
259
lib/timbre_core/PatternEngine.cpp
Executable 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
|
||||
Reference in New Issue
Block a user