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}