es_entity/clock/controller.rs
1use chrono::{DateTime, Utc};
2
3use std::{sync::Arc, time::Duration};
4
5use super::artificial::ArtificialClock;
6
7/// Controller for artificial time operations.
8///
9/// This is only available for artificial clocks and provides methods to
10/// advance time, set time, and inspect pending wake events.
11///
12/// Created alongside a [`ClockHandle`](crate::ClockHandle) via
13/// [`ClockHandle::artificial()`](crate::ClockHandle::artificial).
14#[derive(Clone)]
15pub struct ClockController {
16 pub(crate) clock: Arc<ArtificialClock>,
17}
18
19impl ClockController {
20 /// Advance artificial time by the given duration.
21 ///
22 /// Wake events are processed in chronological order. If you advance by
23 /// 1 day and there are sleeps scheduled at 1 hour and 2 hours, they will
24 /// wake at their scheduled times (seeing the correct `now()` value),
25 /// not at +1 day.
26 ///
27 /// Returns the number of wake events that were processed.
28 ///
29 /// # Example
30 ///
31 /// ```rust
32 /// use es_entity::clock::{ClockHandle, ArtificialClockConfig};
33 /// use std::time::Duration;
34 ///
35 /// # async fn example() {
36 /// let (clock, ctrl) = ClockHandle::artificial(ArtificialClockConfig::manual());
37 /// let t0 = clock.now();
38 ///
39 /// let clock2 = clock.clone();
40 /// let handle = tokio::spawn(async move {
41 /// clock2.sleep(Duration::from_secs(3600)).await;
42 /// clock2.now() // Will be t0 + 1 hour
43 /// });
44 ///
45 /// tokio::task::yield_now().await; // Let task register its sleep
46 ///
47 /// // Advance 1 day - task wakes at exactly +1 hour
48 /// ctrl.advance(Duration::from_secs(86400)).await;
49 ///
50 /// let wake_time = handle.await.unwrap();
51 /// assert_eq!(wake_time, t0 + chrono::Duration::hours(1));
52 /// # }
53 /// ```
54 pub async fn advance(&self, duration: Duration) -> usize {
55 self.clock.advance(duration).await
56 }
57
58 /// Advance to the next pending wake event.
59 ///
60 /// Returns the time that was advanced to, or `None` if there are no
61 /// pending wake events.
62 ///
63 /// This is useful for step-by-step testing where you want to process
64 /// events one at a time.
65 ///
66 /// # Example
67 ///
68 /// ```rust
69 /// use es_entity::clock::{ClockHandle, ArtificialClockConfig};
70 /// use std::time::Duration;
71 ///
72 /// # async fn example() {
73 /// let (clock, ctrl) = ClockHandle::artificial(ArtificialClockConfig::manual());
74 /// let t0 = clock.now();
75 ///
76 /// // Spawn tasks with different sleep durations
77 /// let c = clock.clone();
78 /// tokio::spawn(async move { c.sleep(Duration::from_secs(60)).await; });
79 /// let c = clock.clone();
80 /// tokio::spawn(async move { c.sleep(Duration::from_secs(120)).await; });
81 ///
82 /// tokio::task::yield_now().await;
83 ///
84 /// // Step through one wake at a time
85 /// let t1 = ctrl.advance_to_next_wake().await;
86 /// assert_eq!(t1, Some(t0 + chrono::Duration::seconds(60)));
87 ///
88 /// let t2 = ctrl.advance_to_next_wake().await;
89 /// assert_eq!(t2, Some(t0 + chrono::Duration::seconds(120)));
90 ///
91 /// let t3 = ctrl.advance_to_next_wake().await;
92 /// assert_eq!(t3, None); // No more pending wakes
93 /// # }
94 /// ```
95 pub async fn advance_to_next_wake(&self) -> Option<DateTime<Utc>> {
96 self.clock.advance_to_next_wake().await
97 }
98
99 /// Set the artificial time to a specific value.
100 ///
101 /// **Warning**: Unlike `advance()`, this does NOT process wake events in order.
102 /// All tasks whose wake time has passed will see the new time when they wake.
103 /// Use this for "jump ahead, don't care about intermediate events" scenarios.
104 ///
105 /// For deterministic testing, prefer `advance()` or `advance_to_next_wake()`.
106 pub fn set_time(&self, time: DateTime<Utc>) {
107 self.clock.set_time(time);
108 // Wake all tasks that are now past their wake time
109 self.clock.wake_tasks_at(time.timestamp_millis());
110 }
111
112 /// Get the number of pending wake events.
113 ///
114 /// This is useful for testing to verify that tasks have registered
115 /// their sleeps before advancing time.
116 pub fn pending_wake_count(&self) -> usize {
117 self.clock.pending_wake_count()
118 }
119
120 /// Get the current artificial time.
121 ///
122 /// This is equivalent to calling `now()` on the associated `ClockHandle`.
123 pub fn now(&self) -> DateTime<Utc> {
124 self.clock.now()
125 }
126
127 /// Transition to realtime mode.
128 ///
129 /// After this call:
130 /// - `now()` returns `Utc::now()`
131 /// - `sleep()` uses real tokio timers
132 /// - `advance()` becomes a no-op
133 ///
134 /// Pending sleeps are woken immediately and will re-register using real timers
135 /// for their remaining duration (based on the original wake time).
136 pub fn transition_to_realtime(&self) {
137 self.clock.transition_to_realtime();
138 }
139
140 /// Check if clock has transitioned to realtime.
141 pub fn is_realtime(&self) -> bool {
142 self.clock.is_realtime()
143 }
144
145 /// Clear all pending wake events.
146 pub fn clear_pending_wakes(&self) {
147 self.clock.clear_pending_wakes();
148 }
149
150 /// Reset clock to a specific time and clear all pending wakes.
151 ///
152 /// Useful for test isolation between test cases.
153 pub fn reset_to(&self, time: DateTime<Utc>) {
154 self.clock.set_time(time);
155 self.clock.clear_pending_wakes();
156 }
157}
158
159impl std::fmt::Debug for ClockController {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("ClockController")
162 .field("now", &self.clock.now())
163 .field("pending_wakes", &self.clock.pending_wake_count())
164 .finish()
165 }
166}