rdx-hyperclock 0.3.1

A high-performance, event-driven, phased time simulation engine.
Documentation
//! Contains the `SystemClock` and the raw `TickEvent` it produces.
//!
//! The `SystemClock` is the high-frequency heartbeat of the entire engine. Its only
//! responsibility is to emit `TickEvent`s at a configured rate. It does not
//! contain any other logic.

use crate::config::ClockResolution;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::broadcast;
use tokio::time::Instant;
use tracing::warn;

/// A raw tick event generated by the `SystemClock`.
///
/// This is the most primitive time event in the engine. The `HyperclockEngine`
/// consumes these events to drive its phase sequence.
#[derive(Debug, Clone)]
pub struct TickEvent {
    /// The number of ticks that have elapsed since the clock started.
    pub tick_count: u64,
    /// The precise moment the tick was generated.
    pub timestamp: Instant,
}

/// The master clock that generates a high-frequency stream of `TickEvent`s.
#[doc(hidden)]
pub(crate) struct SystemClock {
    resolution: ClockResolution,
    tick_sender: broadcast::Sender<Arc<TickEvent>>,
}

impl SystemClock {
    /// Creates a new `SystemClock`.
    pub(crate) fn new(
        resolution: ClockResolution,
        tick_sender: broadcast::Sender<Arc<TickEvent>>,
    ) -> Self {
        Self {
            resolution,
            tick_sender,
        }
    }

    /// Runs the clock's main loop until a shutdown signal is received.
    pub(crate) async fn run(&self, mut shutdown_rx: broadcast::Receiver<()>) {
        let tick_duration = self.resolution.to_duration();
        let mut interval = tokio::time::interval(tick_duration);
        let mut tick_count = 0u64;

        interval.tick().await; // The first tick fires immediately, so we consume it.

        loop {
            tokio::select! {
                biased;
                _ = shutdown_rx.recv() => break,
                // CORRECTED: No .await inside the select! arm.
                _ = interval.tick() => {
                    tick_count += 1;
                    let event = Arc::new(TickEvent {
                        tick_count,
                        timestamp: Instant::now(),
                    });

                    if self.tick_sender.send(event).is_err() {
                        warn!("No active subscribers for TickEvent. This may be normal during startup/shutdown.");
                    }
                }
            }
        }
    }
}

impl ClockResolution {
    /// Converts the resolution enum into a concrete `Duration`.
    pub(crate) fn to_duration(&self) -> Duration {
        let ticks_per_sec = match self {
            ClockResolution::Ultra => 120,
            ClockResolution::High => 60,
            ClockResolution::Medium => 30,
            ClockResolution::Low => 1,
            ClockResolution::Custom { ticks_per_second } => *ticks_per_second,
        };
        if ticks_per_sec == 0 {
            return Duration::from_secs(u64::MAX);
        }
        Duration::from_secs_f64(1.0 / ticks_per_sec as f64)
    }
}