hyperclock/
time.rs

1//! Contains the `SystemClock` and the raw `TickEvent` it produces.
2//!
3//! The `SystemClock` is the high-frequency heartbeat of the entire engine. Its only
4//! responsibility is to emit `TickEvent`s at a configured rate. It does not
5//! contain any other logic.
6
7use crate::config::ClockResolution;
8use std::sync::Arc;
9use std::time::Duration;
10use tokio::sync::broadcast;
11use tokio::time::Instant;
12use tracing::warn;
13
14/// A raw tick event generated by the `SystemClock`.
15///
16/// This is the most primitive time event in the engine. The `HyperclockEngine`
17/// consumes these events to drive its phase sequence.
18#[derive(Debug, Clone)]
19pub struct TickEvent {
20    /// The number of ticks that have elapsed since the clock started.
21    pub tick_count: u64,
22    /// The precise moment the tick was generated.
23    pub timestamp: Instant,
24}
25
26/// The master clock that generates a high-frequency stream of `TickEvent`s.
27#[doc(hidden)]
28pub(crate) struct SystemClock {
29    resolution: ClockResolution,
30    tick_sender: broadcast::Sender<Arc<TickEvent>>,
31}
32
33impl SystemClock {
34    /// Creates a new `SystemClock`.
35    pub(crate) fn new(
36        resolution: ClockResolution,
37        tick_sender: broadcast::Sender<Arc<TickEvent>>,
38    ) -> Self {
39        Self {
40            resolution,
41            tick_sender,
42        }
43    }
44
45    /// Runs the clock's main loop until a shutdown signal is received.
46    pub(crate) async fn run(&self, mut shutdown_rx: broadcast::Receiver<()>) {
47        let tick_duration = self.resolution.to_duration();
48        let mut interval = tokio::time::interval(tick_duration);
49        let mut tick_count = 0u64;
50
51        interval.tick().await; // The first tick fires immediately, so we consume it.
52
53        loop {
54            tokio::select! {
55                biased;
56                _ = shutdown_rx.recv() => break,
57                // CORRECTED: No .await inside the select! arm.
58                _ = interval.tick() => {
59                    tick_count += 1;
60                    let event = Arc::new(TickEvent {
61                        tick_count,
62                        timestamp: Instant::now(),
63                    });
64
65                    if self.tick_sender.send(event).is_err() {
66                        warn!("No active subscribers for TickEvent. This may be normal during startup/shutdown.");
67                    }
68                }
69            }
70        }
71    }
72}
73
74impl ClockResolution {
75    /// Converts the resolution enum into a concrete `Duration`.
76    pub(crate) fn to_duration(&self) -> Duration {
77        let ticks_per_sec = match self {
78            ClockResolution::Ultra => 120,
79            ClockResolution::High => 60,
80            ClockResolution::Medium => 30,
81            ClockResolution::Low => 1,
82            ClockResolution::Custom { ticks_per_second } => *ticks_per_second,
83        };
84        if ticks_per_sec == 0 {
85            return Duration::from_secs(u64::MAX);
86        }
87        Duration::from_secs_f64(1.0 / ticks_per_sec as f64)
88    }
89}