Skip to main content

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}