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 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}