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 in exactly one place — to assert that the input
55 /// timeline and the tree's simulated clock share an origin — and a backend
56 /// may read it to convert an OS timestamp into an [`EventTime`].
57 fn epoch(&self) -> Option<Instant> {
58 None
59 }
60
61 /// Move this clock forward by `d`, for a clock that has to be moved.
62 ///
63 /// A no-op by default, which is right for [`MonotonicClock`]: it already
64 /// advances on its own, and shifting its epoch would make every pending
65 /// deadline fire the moment a test nudged the *simulated* clock for an
66 /// unrelated reason. [`ManualClock`] overrides it, so
67 /// [`WidgetTree::advance_time`](crate::WidgetTree::advance_time) moves the
68 /// input timeline and the simulated one together and a fling advances by
69 /// exactly the duration the caller named.
70 fn advance(&self, d: Duration) {
71 let _ = d;
72 }
73}
74
75/// A clock that reads the wall clock, measured from a fixed epoch.
76///
77/// The epoch is supplied rather than captured so it can be the *same* instant
78/// the owning tree's simulated clock was seeded from — see the module docs.
79#[derive(Debug, Clone)]
80pub struct MonotonicClock {
81 epoch: Instant,
82}
83
84impl MonotonicClock {
85 /// A clock whose zero is `epoch`.
86 pub fn new(epoch: Instant) -> Self {
87 Self { epoch }
88 }
89
90 /// The instant this clock's zero corresponds to.
91 pub fn epoch_instant(&self) -> Instant {
92 self.epoch
93 }
94}
95
96impl InputClock for MonotonicClock {
97 fn now(&self) -> EventTime {
98 // `saturating_duration_since` rather than `-`: a caller may hand us an
99 // epoch fractionally in the future (the tree's epoch is captured a few
100 // instructions before the clock is built on some platforms' coarse
101 // timers), and a panic there would be absurd.
102 EventTime::from_duration(Instant::now().saturating_duration_since(self.epoch))
103 }
104
105 fn epoch(&self) -> Option<Instant> {
106 Some(self.epoch)
107 }
108}
109
110/// A clock the caller moves by hand. Never advances on its own.
111///
112/// What a headless test installs when it wants gesture deadlines to fire
113/// exactly when it says they do. `Cell` rather than a `&mut` API so it can be
114/// held behind the same `Rc<dyn InputClock>` as [`MonotonicClock`].
115#[derive(Debug, Clone)]
116pub struct ManualClock(Cell<EventTime>);
117
118impl ManualClock {
119 /// A clock reading `start`.
120 pub fn new(start: EventTime) -> Self {
121 Self(Cell::new(start))
122 }
123
124 /// Jump to `time`.
125 pub fn set(&self, time: EventTime) {
126 self.0.set(time);
127 }
128
129 /// Move forward by `d`. Saturates rather than overflowing.
130 pub fn advance(&self, d: Duration) {
131 let next = self
132 .0
133 .get()
134 .checked_add(d)
135 .unwrap_or(EventTime::from_duration(Duration::MAX));
136 self.0.set(next);
137 }
138}
139
140impl Default for ManualClock {
141 fn default() -> Self {
142 Self::new(EventTime::ZERO)
143 }
144}
145
146impl InputClock for ManualClock {
147 fn now(&self) -> EventTime {
148 self.0.get()
149 }
150
151 /// The owner said to move, so it moves — this is exactly the inherent
152 /// [`advance`](Self::advance), reached through the trait so
153 /// `WidgetTree::advance_time` can move any clock it happens to hold.
154 fn advance(&self, d: Duration) {
155 ManualClock::advance(self, d);
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn a_manual_clock_only_moves_when_told_to() {
165 let clock = ManualClock::new(EventTime::ZERO);
166 assert_eq!(clock.now(), EventTime::ZERO);
167 // Reading twice must not move it — that is the whole point.
168 assert_eq!(clock.now(), EventTime::ZERO);
169
170 clock.advance(Duration::from_millis(500));
171 assert_eq!(clock.now(), EventTime::from_millis(500));
172 clock.advance(Duration::from_millis(250));
173 assert_eq!(clock.now(), EventTime::from_millis(750));
174
175 clock.set(EventTime::from_millis(10));
176 assert_eq!(clock.now(), EventTime::from_millis(10));
177 }
178
179 #[test]
180 fn a_manual_clock_saturates_rather_than_overflowing() {
181 let clock = ManualClock::new(EventTime::from_millis(1));
182 clock.advance(Duration::MAX);
183 clock.advance(Duration::MAX);
184 assert_eq!(clock.now().as_duration(), Duration::MAX);
185 }
186
187 #[test]
188 fn a_manual_clock_has_no_wall_clock_epoch() {
189 assert_eq!(ManualClock::default().epoch(), None);
190 }
191
192 #[test]
193 fn a_monotonic_clock_measures_from_its_epoch() {
194 let epoch = Instant::now();
195 let clock = MonotonicClock::new(epoch);
196 assert_eq!(clock.epoch(), Some(epoch));
197 assert_eq!(clock.epoch_instant(), epoch);
198 // Time only moves forward.
199 let a = clock.now();
200 let b = clock.now();
201 assert!(b >= a);
202 }
203
204 /// An epoch fractionally in the future must read as zero, not panic.
205 #[test]
206 fn a_monotonic_clock_clamps_a_future_epoch() {
207 let clock = MonotonicClock::new(Instant::now() + Duration::from_secs(3600));
208 assert_eq!(clock.now(), EventTime::ZERO);
209 }
210
211 #[test]
212 fn clocks_are_usable_through_the_trait_object() {
213 let clocks: Vec<std::rc::Rc<dyn InputClock>> = vec![
214 std::rc::Rc::new(ManualClock::new(EventTime::from_millis(4))),
215 std::rc::Rc::new(MonotonicClock::new(Instant::now())),
216 ];
217 assert_eq!(clocks[0].now(), EventTime::from_millis(4));
218 assert!(clocks[1].epoch().is_some());
219 }
220}