teksilo_core/pointer/clock.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! The one clock.
5//!
6//! # The rule
7//!
8//! **`Instant` never enters a recognizer.** Every deadline the input layer
9//! owns — a long press, a double-tap window, a fling's decay, a press-feedback
10//! delay — is an [`EventTime`] read from the tree's [`InputClock`]. A
11//! recognizer that calls `Instant::now()` cannot be driven by a test, and a
12//! recognizer that cannot be driven by a test is one whose timing is only ever
13//! exercised by sleeping.
14//!
15//! # Why the epoch is shared
16//!
17//! A [`WidgetTree`](crate::WidgetTree) already has a simulated clock: the
18//! `sim_clock` that `advance_time` moves and that the animation scheduler is
19//! ticked against. Its epoch is the `Instant` captured when the tree was
20//! built. [`MonotonicClock`] is seeded from **that same instant**, so
21//! `EventTime::ZERO` and `simulated_now()` name the same moment and the two
22//! timelines are one axis rather than two.
23//!
24//! Without that, a test would have to advance two clocks in step to move a
25//! long press and the animation it kicks off, and the two would drift by
26//! however long the test itself took — the exact failure mode the animation
27//! clock already had to be rescued from (see `WidgetTree::animation_clock`).
28//!
29//! # Choosing an implementation
30//!
31//! [`MonotonicClock`] reads the wall clock and is what a real window uses.
32//! [`ManualClock`] is set by the caller and never moves on its own, which is
33//! what a headless test wants when it drives time explicitly. Both are held as
34//! `Rc<dyn InputClock>`, so a tree's clock can be swapped at any point with
35//! [`WidgetTree::set_input_clock`](crate::WidgetTree::set_input_clock).
36
37use std::cell::Cell;
38use std::time::{Duration, Instant};
39
40use super::EventTime;
41
42/// The source of [`EventTime`]s for one tree.
43///
44/// `&self` rather than `&mut self` so a clock can be shared through an `Rc` and
45/// read from anywhere in a dispatch without threading a mutable borrow.
46pub trait InputClock {
47 /// The current time on this tree's input timeline.
48 fn now(&self) -> EventTime;
49
50 /// The real instant this clock's [`EventTime::ZERO`] corresponds to, for a
51 /// clock that has one.
52 ///
53 /// `None` for a clock with no wall-clock anchor ([`ManualClock`]). The
54 /// framework reads it wherever the input timeline has to be converted to
55 /// or from a wall-clock `Instant` — `event_time_for`, `instant_for` and
56 /// `rearm_sim_input_origin` each branch on whether there is an anchor at
57 /// all — a test asserts through it that the input timeline and the tree's
58 /// simulated clock share an origin, and a backend may read it to convert
59 /// an OS timestamp into an [`EventTime`].
60 fn epoch(&self) -> Option<Instant> {
61 None
62 }
63
64 /// Move this clock forward by `d`, for a clock that has to be moved.
65 ///
66 /// A no-op by default, which is right for [`MonotonicClock`]: it already
67 /// advances on its own, and shifting its epoch would make every pending
68 /// deadline fire the moment a test nudged the *simulated* clock for an
69 /// unrelated reason. [`ManualClock`] overrides it, so
70 /// [`WidgetTree::advance_time`](crate::WidgetTree::advance_time) moves the
71 /// input timeline and the simulated one together and a fling advances by
72 /// exactly the duration the caller named.
73 fn advance(&self, d: Duration) {
74 let _ = d;
75 }
76}
77
78/// A clock that reads the wall clock, measured from a fixed epoch.
79///
80/// The epoch is supplied rather than captured so it can be the *same* instant
81/// the owning tree's simulated clock was seeded from — see the module docs.
82#[derive(Debug, Clone)]
83pub struct MonotonicClock {
84 epoch: Instant,
85}
86
87impl MonotonicClock {
88 /// A clock whose zero is `epoch`.
89 pub fn new(epoch: Instant) -> Self {
90 Self { epoch }
91 }
92
93 /// The instant this clock's zero corresponds to.
94 pub fn epoch_instant(&self) -> Instant {
95 self.epoch
96 }
97}
98
99impl InputClock for MonotonicClock {
100 fn now(&self) -> EventTime {
101 // `saturating_duration_since` rather than `-`: a caller may hand us an
102 // epoch fractionally in the future (the tree's epoch is captured a few
103 // instructions before the clock is built on some platforms' coarse
104 // timers), and a panic there would be absurd.
105 EventTime::from_duration(Instant::now().saturating_duration_since(self.epoch))
106 }
107
108 fn epoch(&self) -> Option<Instant> {
109 Some(self.epoch)
110 }
111}
112
113/// A clock the caller moves by hand. Never advances on its own.
114///
115/// What a headless test installs when it wants gesture deadlines to fire
116/// exactly when it says they do. `Cell` rather than a `&mut` API so it can be
117/// held behind the same `Rc<dyn InputClock>` as [`MonotonicClock`].
118#[derive(Debug, Clone)]
119pub struct ManualClock(Cell<EventTime>);
120
121impl ManualClock {
122 /// A clock reading `start`.
123 pub fn new(start: EventTime) -> Self {
124 Self(Cell::new(start))
125 }
126
127 /// Jump to `time`.
128 pub fn set(&self, time: EventTime) {
129 self.0.set(time);
130 }
131
132 /// Move forward by `d`. Saturates rather than overflowing.
133 pub fn advance(&self, d: Duration) {
134 let next = self
135 .0
136 .get()
137 .checked_add(d)
138 .unwrap_or(EventTime::from_duration(Duration::MAX));
139 self.0.set(next);
140 }
141}
142
143impl Default for ManualClock {
144 fn default() -> Self {
145 Self::new(EventTime::ZERO)
146 }
147}
148
149impl InputClock for ManualClock {
150 fn now(&self) -> EventTime {
151 self.0.get()
152 }
153
154 /// The owner said to move, so it moves — this is exactly the inherent
155 /// [`advance`](Self::advance), reached through the trait so
156 /// `WidgetTree::advance_time` can move any clock it happens to hold.
157 fn advance(&self, d: Duration) {
158 ManualClock::advance(self, d);
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn a_manual_clock_only_moves_when_told_to() {
168 let clock = ManualClock::new(EventTime::ZERO);
169 assert_eq!(clock.now(), EventTime::ZERO);
170 // Reading twice must not move it — that is the whole point.
171 assert_eq!(clock.now(), EventTime::ZERO);
172
173 clock.advance(Duration::from_millis(500));
174 assert_eq!(clock.now(), EventTime::from_millis(500));
175 clock.advance(Duration::from_millis(250));
176 assert_eq!(clock.now(), EventTime::from_millis(750));
177
178 clock.set(EventTime::from_millis(10));
179 assert_eq!(clock.now(), EventTime::from_millis(10));
180 }
181
182 #[test]
183 fn a_manual_clock_saturates_rather_than_overflowing() {
184 let clock = ManualClock::new(EventTime::from_millis(1));
185 clock.advance(Duration::MAX);
186 clock.advance(Duration::MAX);
187 assert_eq!(clock.now().as_duration(), Duration::MAX);
188 }
189
190 #[test]
191 fn a_manual_clock_has_no_wall_clock_epoch() {
192 assert_eq!(ManualClock::default().epoch(), None);
193 }
194
195 #[test]
196 fn a_monotonic_clock_measures_from_its_epoch() {
197 let epoch = Instant::now();
198 let clock = MonotonicClock::new(epoch);
199 assert_eq!(clock.epoch(), Some(epoch));
200 assert_eq!(clock.epoch_instant(), epoch);
201 // Time only moves forward.
202 let a = clock.now();
203 let b = clock.now();
204 assert!(b >= a);
205 }
206
207 /// An epoch fractionally in the future must read as zero, not panic.
208 #[test]
209 fn a_monotonic_clock_clamps_a_future_epoch() {
210 let clock = MonotonicClock::new(Instant::now() + Duration::from_secs(3600));
211 assert_eq!(clock.now(), EventTime::ZERO);
212 }
213
214 #[test]
215 fn clocks_are_usable_through_the_trait_object() {
216 let clocks: Vec<std::rc::Rc<dyn InputClock>> = vec![
217 std::rc::Rc::new(ManualClock::new(EventTime::from_millis(4))),
218 std::rc::Rc::new(MonotonicClock::new(Instant::now())),
219 ];
220 assert_eq!(clocks[0].now(), EventTime::from_millis(4));
221 assert!(clocks[1].epoch().is_some());
222 }
223}