es_entity/clock/
handle.rs

1use chrono::{DateTime, Utc};
2
3use std::{sync::Arc, time::Duration};
4
5use super::{
6    artificial::ArtificialClock,
7    config::ArtificialClockConfig,
8    controller::ClockController,
9    inner::ClockInner,
10    realtime::RealtimeClock,
11    sleep::{ClockSleep, ClockTimeout},
12};
13
14pub use super::sleep::Elapsed;
15
16/// A handle to a clock for getting time and performing time-based operations.
17///
18/// This is the main interface for time operations. It's cheap to clone and
19/// can be shared across tasks and threads. All clones share the same underlying
20/// clock, so they see consistent time.
21///
22/// # Creating a Clock
23///
24/// ```rust
25/// use es_entity::clock::{ClockHandle, ArtificialClockConfig};
26///
27/// // Real-time clock for production
28/// let clock = ClockHandle::realtime();
29///
30/// // Artificial clock for testing - returns (handle, controller)
31/// let (clock, ctrl) = ClockHandle::artificial(ArtificialClockConfig::manual());
32/// ```
33///
34/// # Basic Operations
35///
36/// ```rust
37/// use es_entity::clock::ClockHandle;
38/// use std::time::Duration;
39///
40/// # async fn example() {
41/// let clock = ClockHandle::realtime();
42///
43/// // Get current time
44/// let now = clock.now();
45///
46/// // Sleep for a duration
47/// clock.sleep(Duration::from_secs(1)).await;
48///
49/// // Timeout a future
50/// match clock.timeout(Duration::from_secs(5), some_async_operation()).await {
51///     Ok(result) => println!("Completed: {:?}", result),
52///     Err(_) => println!("Timed out"),
53/// }
54/// # }
55/// # async fn some_async_operation() -> i32 { 42 }
56/// ```
57#[derive(Clone)]
58pub struct ClockHandle {
59    inner: Arc<ClockInner>,
60}
61
62impl ClockHandle {
63    /// Create a real-time clock that uses the system clock and tokio timers.
64    pub fn realtime() -> Self {
65        Self {
66            inner: Arc::new(ClockInner::Realtime(RealtimeClock)),
67        }
68    }
69
70    /// Create an artificial clock with the given configuration.
71    ///
72    /// Returns a tuple of `(ClockHandle, ClockController)`. The handle provides
73    /// the common time interface, while the controller provides operations
74    /// for advancing time.
75    ///
76    /// # Example
77    ///
78    /// ```rust
79    /// use es_entity::clock::{ClockHandle, ArtificialClockConfig, ArtificialMode};
80    /// use chrono::Utc;
81    ///
82    /// // Manual mode - time only advances via controller.advance()
83    /// let (clock, ctrl) = ClockHandle::artificial(ArtificialClockConfig::manual());
84    ///
85    /// // Auto mode - time advances 1000x faster than real time
86    /// let (clock, ctrl) = ClockHandle::artificial(ArtificialClockConfig::auto(1000.0));
87    ///
88    /// // Start at a specific time
89    /// let (clock, ctrl) = ClockHandle::artificial(ArtificialClockConfig {
90    ///     start_at: Utc::now() - chrono::Duration::days(30),
91    ///     mode: ArtificialMode::Manual,
92    /// });
93    /// ```
94    pub fn artificial(config: ArtificialClockConfig) -> (Self, ClockController) {
95        let clock = Arc::new(ArtificialClock::new(config));
96        let handle = Self {
97            inner: Arc::new(ClockInner::Artificial(Arc::clone(&clock))),
98        };
99        let controller = ClockController { clock };
100        (handle, controller)
101    }
102
103    /// Get the current time.
104    ///
105    /// This is a fast, synchronous operation regardless of clock type.
106    ///
107    /// For real-time clocks, this returns `Utc::now()`.
108    /// For artificial clocks, this returns the current artificial time.
109    #[inline]
110    pub fn now(&self) -> DateTime<Utc> {
111        match &*self.inner {
112            ClockInner::Realtime(rt) => rt.now(),
113            ClockInner::Artificial(clock) => clock.now(),
114        }
115    }
116
117    /// Sleep for the given duration.
118    ///
119    /// For real-time clocks, this delegates to `tokio::time::sleep`.
120    /// For artificial clocks in manual mode, this waits until time is advanced.
121    /// For artificial clocks in auto mode, this sleeps for a scaled real duration.
122    pub fn sleep(&self, duration: Duration) -> ClockSleep {
123        ClockSleep::new(&self.inner, duration)
124    }
125
126    /// Apply a timeout to a future.
127    ///
128    /// Returns `Ok(output)` if the future completes before the timeout,
129    /// or `Err(Elapsed)` if the timeout expires first.
130    pub fn timeout<F>(&self, duration: Duration, future: F) -> ClockTimeout<F>
131    where
132        F: std::future::Future,
133    {
134        ClockTimeout::new(&self.inner, duration, future)
135    }
136
137    /// Check if this clock is artificial (as opposed to realtime).
138    pub fn is_artificial(&self) -> bool {
139        matches!(&*self.inner, ClockInner::Artificial(_))
140    }
141
142    /// Get the current artificial time, if this is an artificial clock that
143    /// hasn't transitioned to realtime.
144    ///
145    /// Returns:
146    /// - `None` for realtime clocks
147    /// - `None` for artificial clocks that have transitioned to realtime
148    /// - `Some(time)` for artificial clocks (manual or auto) that are still artificial
149    ///
150    /// This is useful for code that needs to cache time when running under
151    /// artificial clocks but use fresh time for realtime clocks.
152    pub fn artificial_now(&self) -> Option<DateTime<Utc>> {
153        match &*self.inner {
154            ClockInner::Realtime(_) => None,
155            ClockInner::Artificial(clock) => {
156                if clock.is_realtime() {
157                    None
158                } else {
159                    Some(clock.now())
160                }
161            }
162        }
163    }
164}
165
166impl std::fmt::Debug for ClockHandle {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        match &*self.inner {
169            ClockInner::Realtime(_) => f.debug_struct("ClockHandle::Realtime").finish(),
170            ClockInner::Artificial(clock) => f
171                .debug_struct("ClockHandle::Artificial")
172                .field("now", &clock.now())
173                .finish(),
174        }
175    }
176}