es_entity/clock/
global.rs

1use chrono::{DateTime, Utc};
2
3use std::sync::OnceLock;
4use std::time::Duration;
5
6use super::{ArtificialClockConfig, ClockController, ClockHandle, ClockSleep, ClockTimeout};
7
8struct GlobalState {
9    handle: ClockHandle,
10    controller: Option<ClockController>,
11}
12
13static GLOBAL: OnceLock<GlobalState> = OnceLock::new();
14
15/// Global clock access - like `Utc::now()` but testable.
16pub struct Clock;
17
18impl Clock {
19    /// Get current time from the global clock.
20    ///
21    /// Lazily initializes to realtime if not already set.
22    pub fn now() -> DateTime<Utc> {
23        Self::handle().now()
24    }
25
26    /// Get the current date (without time component).
27    ///
28    /// Lazily initializes to realtime if not already set.
29    pub fn today() -> chrono::NaiveDate {
30        Self::handle().today()
31    }
32
33    /// Sleep using the global clock.
34    pub fn sleep(duration: Duration) -> ClockSleep {
35        Self::handle().sleep(duration)
36    }
37
38    /// Timeout using the global clock.
39    pub fn timeout<F: std::future::Future>(duration: Duration, future: F) -> ClockTimeout<F> {
40        Self::handle().timeout(duration, future)
41    }
42
43    /// Get a reference to the global clock handle.
44    pub fn handle() -> &'static ClockHandle {
45        &GLOBAL
46            .get_or_init(|| GlobalState {
47                handle: ClockHandle::realtime(),
48                controller: None,
49            })
50            .handle
51    }
52
53    /// Install an artificial clock globally.
54    ///
55    /// - If not initialized: installs artificial clock, returns controller
56    /// - If already artificial: returns existing controller (idempotent)
57    /// - If already realtime: panics
58    ///
59    /// Must be called before any `Clock::now()` calls if you want artificial time.
60    pub fn install_artificial(config: ArtificialClockConfig) -> ClockController {
61        // Check if already initialized
62        if let Some(state) = GLOBAL.get() {
63            return state
64                .controller
65                .clone()
66                .expect("Cannot install artificial clock: realtime clock already initialized");
67        }
68
69        // Try to initialize
70        let (handle, ctrl) = ClockHandle::artificial(config);
71
72        match GLOBAL.set(GlobalState {
73            handle,
74            controller: Some(ctrl.clone()),
75        }) {
76            Ok(()) => ctrl,
77            Err(_) => {
78                // Race: someone else initialized between our check and set
79                GLOBAL
80                    .get()
81                    .unwrap()
82                    .controller
83                    .clone()
84                    .expect("Cannot install artificial clock: realtime clock already initialized")
85            }
86        }
87    }
88
89    /// Check if an artificial clock is installed.
90    pub fn is_artificial() -> bool {
91        GLOBAL
92            .get()
93            .map(|s| s.controller.is_some())
94            .unwrap_or(false)
95    }
96
97    /// Get the current artificial time, if an artificial clock is installed
98    /// and hasn't transitioned to realtime.
99    ///
100    /// Returns:
101    /// - `None` if no clock is initialized (doesn't initialize one)
102    /// - `None` for realtime clocks
103    /// - `None` for artificial clocks that have transitioned to realtime
104    /// - `Some(time)` for artificial clocks that are still artificial
105    pub fn artificial_now() -> Option<DateTime<Utc>> {
106        GLOBAL.get().and_then(|s| s.handle.artificial_now())
107    }
108}