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