#pragma once #include "Model.h" #include "ScheduleValidator.h" #include "SessionClock.h" #include #include #include #include namespace timbre_core { class PatternEngine { public: using OutputCallback = std::function; using ClockCallback = std::function; using SimpleOutputCallback = std::function; 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 patterns_{}; std::uint8_t configuredPatternMask_ = static_cast((1u << kMaxPatterns) - 1u); std::array 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