#pragma once #include "CivilTime.h" #include #include #include namespace timbre_core { // Hardware integration supplies this interface. The core never calls a // platform-specific tick function itself. class MonotonicClock { public: virtual ~MonotonicClock() = default; virtual std::uint64_t nowMs() const = 0; }; class SessionClock { public: using Callback = std::function; struct Observation { bool valid = false; bool firstObservation = false; bool monotonicRegression = false; bool resynchronized = false; std::uint64_t monotonicMs = 0; std::int64_t utcEpochSeconds = 0; CivilDateTime local{}; constexpr operator bool() const noexcept { return valid; } constexpr bool isFirstObservation() const noexcept { return firstObservation; } }; explicit SessionClock(Callback callback) : callback_(std::move(callback)) {} explicit SessionClock(MonotonicClock& clock) : clock_(&clock) {} SessionClock(const SessionClock&) = delete; SessionClock& operator=(const SessionClock&) = delete; // The first wall-clock observation establishes an anchor and returns // firstObservation=true. It never attempts to replay older wall time. Observation observe(std::int64_t utcEpochSeconds); Observation poll(std::int64_t utcEpochSeconds) { return observe(utcEpochSeconds); } Observation sync(std::int64_t utcEpochSeconds) { return observe(utcEpochSeconds); } Observation update(std::int64_t utcEpochSeconds) { return observe(utcEpochSeconds); } // Deterministic injection for tests and for a caller that already has a // monotonic sample. It follows exactly the same first-observation rule. Observation observeAt(std::int64_t utcEpochSeconds, std::uint64_t monotonicMs); // Advance only from the injected monotonic source. No wall-clock catch-up // is performed, so a sleep or a clock correction cannot create old events. Observation tick(); Observation update() { return tick(); } bool initialized() const noexcept { return initialized_; } bool hasObservation() const noexcept { return initialized_; } bool hasFirstObservation() const noexcept { return initialized_; } bool lastWasFirstObservation() const noexcept { return lastObservation_.firstObservation; } std::uint64_t monotonicNowMs() const; std::uint64_t now() const { return monotonicNowMs(); } std::uint64_t elapsedMs() const noexcept; std::uint64_t sessionElapsedMs() const noexcept; std::uint64_t elapsedSinceAnchorMs() const noexcept { return sessionElapsedMs(); } std::int64_t utcEpochSeconds() const noexcept { return lastWallSeconds_; } std::int64_t wallEpochSeconds() const noexcept { return lastWallSeconds_; } std::int64_t anchorUtcEpochSeconds() const noexcept { return anchorWallSeconds_; } const CivilDateTime& localTime() const noexcept { return lastLocal_; } const CivilDateTime& civilTime() const noexcept { return lastLocal_; } const Observation& lastObservation() const noexcept { return lastObservation_; } void reset() noexcept; private: std::uint64_t readMonotonic() const; bool setObservation(std::int64_t wallSeconds, std::uint64_t monotonicMs, bool first, bool regression, bool resynchronized); Callback callback_; MonotonicClock* clock_ = nullptr; bool initialized_ = false; std::uint64_t anchorMonotonicMs_ = 0; std::uint64_t lastMonotonicMs_ = 0; std::int64_t anchorWallSeconds_ = 0; std::int64_t lastWallSeconds_ = 0; CivilDateTime lastLocal_{}; Observation lastObservation_{}; }; } // namespace timbre_core