152 lines
5.7 KiB
C++
Executable File
152 lines
5.7 KiB
C++
Executable File
#pragma once
|
|
|
|
#include "Model.h"
|
|
#include "ScheduleValidator.h"
|
|
#include "SessionClock.h"
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <utility>
|
|
|
|
namespace timbre_core {
|
|
|
|
class PatternEngine {
|
|
public:
|
|
using OutputCallback = std::function<void(RelayId, bool)>;
|
|
using ClockCallback = std::function<std::uint64_t()>;
|
|
using SimpleOutputCallback = std::function<void(bool)>;
|
|
|
|
struct Request {
|
|
Pattern pattern{};
|
|
std::uint64_t startAtMs = 0;
|
|
};
|
|
|
|
static constexpr std::size_t kQueueCapacity = kPatternQueueCapacity;
|
|
static constexpr std::uint8_t kNoPhase = 0xFFu;
|
|
static constexpr std::uint64_t kMillisecondsPerSecond = 1000ULL;
|
|
|
|
PatternEngine() {
|
|
initializeDefaults();
|
|
}
|
|
explicit PatternEngine(OutputCallback output) : output_(std::move(output)) {
|
|
initializeDefaults();
|
|
}
|
|
explicit PatternEngine(ClockCallback clock, OutputCallback output = OutputCallback())
|
|
: clock_(std::move(clock)), output_(std::move(output)) {
|
|
initializeDefaults();
|
|
}
|
|
explicit PatternEngine(MonotonicClock& clock, OutputCallback output = OutputCallback())
|
|
: clockInterface_(&clock), output_(std::move(output)) {
|
|
initializeDefaults();
|
|
}
|
|
explicit PatternEngine(const AppState& state, OutputCallback output = OutputCallback())
|
|
: output_(std::move(output)) {
|
|
initializeDefaults();
|
|
setPatterns(state);
|
|
}
|
|
|
|
void setClock(ClockCallback clock) {
|
|
clockInterface_ = nullptr;
|
|
clock_ = std::move(clock);
|
|
}
|
|
void setClock(MonotonicClock& clock) {
|
|
clock_ = ClockCallback();
|
|
clockInterface_ = &clock;
|
|
}
|
|
void setOutput(OutputCallback output) {
|
|
output_ = std::move(output);
|
|
simpleOutput_ = SimpleOutputCallback();
|
|
}
|
|
void setSimpleOutput(SimpleOutputCallback output) {
|
|
simpleOutput_ = std::move(output);
|
|
output_ = OutputCallback();
|
|
}
|
|
|
|
void setPattern(const Pattern& pattern) noexcept;
|
|
void setPatterns(const AppState& state) noexcept;
|
|
void setState(const AppState& state) noexcept { setPatterns(state); }
|
|
void configure(const AppState& state) noexcept { setPatterns(state); }
|
|
|
|
// Queueing is non-blocking and bounded. A request is a snapshot, so a
|
|
// later configuration commit cannot mutate an already queued sequence.
|
|
bool enqueue(PatternId id);
|
|
bool enqueue(PatternId id, std::uint64_t startAtMs);
|
|
bool enqueue(const Pattern& pattern);
|
|
bool enqueue(const Pattern& pattern, std::uint64_t startAtMs);
|
|
bool enqueue(PatternId id, const AppState& state, std::uint64_t startAtMs);
|
|
bool enqueue(PatternId id, const AppState& state);
|
|
|
|
bool start(PatternId id, std::uint64_t nowMs);
|
|
bool start(const Pattern& pattern, std::uint64_t nowMs);
|
|
bool start(PatternId id, const AppState& state, std::uint64_t nowMs);
|
|
bool start(PatternId id, const AppState& state);
|
|
|
|
// Returns true when the state/output changed. It performs no waiting and
|
|
// consumes at most one bounded transition burst (six phases).
|
|
bool update(std::uint64_t nowMs);
|
|
bool update();
|
|
bool tick(std::uint64_t nowMs) { return update(nowMs); }
|
|
bool tick() { return update(); }
|
|
|
|
// Stops the active sequence and guarantees an OFF output. Pending queue
|
|
// entries are retained; cancelAll() removes them as well.
|
|
void stop(std::uint64_t nowMs);
|
|
void stop();
|
|
void finish() { stop(); }
|
|
void cancelAll() noexcept;
|
|
|
|
bool running() const noexcept { return active_; }
|
|
bool outputOn() const noexcept { return outputOn_; }
|
|
PatternId activePatternId() const noexcept { return activePattern_.id; }
|
|
const Pattern* activePattern() const noexcept { return active_ ? &activePattern_ : nullptr; }
|
|
std::uint8_t activePhase() const noexcept { return active_ ? phase_ : kNoPhase; }
|
|
std::uint64_t deadlineMs() const noexcept { return active_ ? deadlineMs_ : 0; }
|
|
std::uint64_t deadline() const noexcept { return deadlineMs(); }
|
|
std::uint64_t nextDeadlineMs() const noexcept { return deadlineMs(); }
|
|
std::size_t queueSize() const noexcept { return queueSize_; }
|
|
std::size_t queueCapacity() const noexcept { return kQueueCapacity; }
|
|
bool configured(PatternId id) const noexcept {
|
|
const std::size_t index = patternIndex(id);
|
|
return index < kMaxPatterns && (configuredPatternMask_ & (1u << index)) != 0;
|
|
}
|
|
RelayId lastOutputRelay() const noexcept { return lastOutputRelay_; }
|
|
bool reservedRelayDriven() const noexcept { return reservedRelayDriven_; }
|
|
|
|
private:
|
|
void initializeDefaults() noexcept;
|
|
std::uint64_t readClock() const;
|
|
bool validPattern(const Pattern& pattern) const noexcept;
|
|
bool enqueueSnapshot(const Pattern& pattern, std::uint64_t startAtMs);
|
|
bool startSnapshot(const Pattern& pattern, std::uint64_t nowMs);
|
|
void pump(std::uint64_t nowMs);
|
|
void finishActive() noexcept;
|
|
void emit(bool on) noexcept;
|
|
static std::uint64_t addDuration(std::uint64_t deadline, std::uint8_t seconds) noexcept;
|
|
|
|
ClockCallback clock_;
|
|
MonotonicClock* clockInterface_ = nullptr;
|
|
OutputCallback output_;
|
|
SimpleOutputCallback simpleOutput_;
|
|
|
|
std::array<Pattern, kMaxPatterns> patterns_{};
|
|
std::uint8_t configuredPatternMask_ =
|
|
static_cast<std::uint8_t>((1u << kMaxPatterns) - 1u);
|
|
|
|
std::array<Request, kQueueCapacity> queue_{};
|
|
std::size_t queueHead_ = 0;
|
|
std::size_t queueSize_ = 0;
|
|
|
|
Pattern activePattern_{};
|
|
bool active_ = false;
|
|
std::uint8_t phase_ = kNoPhase;
|
|
std::uint64_t deadlineMs_ = 0;
|
|
std::uint64_t lastUpdateMs_ = 0;
|
|
bool hasUpdateTime_ = false;
|
|
bool outputOn_ = false;
|
|
RelayId lastOutputRelay_ = RelayId::Main;
|
|
bool reservedRelayDriven_ = false;
|
|
};
|
|
|
|
} // namespace timbre_core
|