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    /// Sleep using the global clock.
27    pub fn sleep(duration: Duration) -> ClockSleep {
28        Self::handle().sleep(duration)
29    }
30
31    /// Timeout using the global clock.
32    pub fn timeout<F: std::future::Future>(duration: Duration, future: F) -> ClockTimeout<F> {
33        Self::handle().timeout(duration, future)
34    }
35
36    /// Get a reference to the global clock handle.
37    pub fn handle() -> &'static ClockHandle {
38        &GLOBAL
39            .get_or_init(|| GlobalState {
40                handle: ClockHandle::realtime(),
41                controller: None,
42            })
43            .handle
44    }
45
46    /// Install an artificial clock globally.
47    ///
48    /// - If not initialized: installs artificial clock, returns controller
49    /// - If already artificial: returns existing controller (idempotent)
50    /// - If already realtime: panics
51    ///
52    /// Must be called before any `Clock::now()` calls if you want artificial time.
53    pub fn install_artificial(config: ArtificialClockConfig) -> ClockController {
54        // Check if already initialized
55        if let Some(state) = GLOBAL.get() {
56            return state
57                .controller
58                .clone()
59                .expect("Cannot install artificial clock: realtime clock already initialized");
60        }
61
62        // Try to initialize
63        let (handle, ctrl) = ClockHandle::artificial(config);
64
65        match GLOBAL.set(GlobalState {
66            handle,
67            controller: Some(ctrl.clone()),
68        }) {
69            Ok(()) => ctrl,
70            Err(_) => {
71                // Race: someone else initialized between our check and set
72                GLOBAL
73                    .get()
74                    .unwrap()
75                    .controller
76                    .clone()
77                    .expect("Cannot install artificial clock: realtime clock already initialized")
78            }
79        }
80    }
81
82    /// Check if an artificial clock is installed.
83    pub fn is_artificial() -> bool {
84        GLOBAL
85            .get()
86            .map(|s| s.controller.is_some())
87            .unwrap_or(false)
88    }
89}