proxima_clock/ticks.rs
1/// A hardware counter reading: whatever a monotonic tick source produced,
2/// at that source's own frequency.
3///
4/// Ticks, not nanoseconds. Deriving nanoseconds costs a multiply and a
5/// divide per read — see [`crate::anchor::ToUnixNanos`] for where that
6/// conversion belongs (the edge, once per anchor/export), never on the hot
7/// path where a caller just wants `now_ticks - then_ticks`. A counter's
8/// rate is whatever the hardware runs at: 24 MHz for the ARM generic timer
9/// (`CNTVCT_EL0`), ~1 GHz nominal for `RDTSC`, 100 MHz for a PTP hardware
10/// clock, 1000 Hz for `prime`'s timer wheel (see
11/// `prime::core::timer::Clock`, the sibling tick source this type is
12/// deliberately compatible with).
13///
14/// `#[repr(transparent)]` over `u64`, matching
15/// `proxima_protocols::Instant`/`Duration`'s convention for tier-3 value
16/// types.
17#[repr(transparent)]
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
19pub struct Ticks(u64);
20
21impl Ticks {
22 /// The zero tick.
23 pub const ZERO: Self = Self(0);
24
25 /// Construct from a raw tick count. Hardware source impls use this;
26 /// the value's meaning (which counter, which frequency) travels
27 /// separately, alongside the source that produced it.
28 #[must_use]
29 pub const fn from_raw(raw: u64) -> Self {
30 Self(raw)
31 }
32
33 /// The raw tick count.
34 #[must_use]
35 pub const fn as_raw(self) -> u64 {
36 self.0
37 }
38
39 /// `self - earlier`, wrapping on counter rollover (a free-running
40 /// hardware counter wraps at `u64::MAX`; the elapsed count is still
41 /// correct via modular arithmetic as long as fewer than `u64::MAX`
42 /// ticks elapsed between the two reads, true for any counter this
43 /// side of the heat death of the universe).
44 #[must_use]
45 pub const fn wrapping_sub(self, earlier: Self) -> u64 {
46 self.0.wrapping_sub(earlier.0)
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::Ticks;
53
54 #[test]
55 fn wrapping_sub_handles_counter_rollover() {
56 let before_wrap = Ticks::from_raw(u64::MAX - 5);
57 let after_wrap = Ticks::from_raw(4);
58
59 let elapsed = after_wrap.wrapping_sub(before_wrap);
60
61 assert_eq!(
62 elapsed, 10,
63 "5 ticks to wrap plus 5 ticks past it, via modular arithmetic"
64 );
65 }
66
67 #[test]
68 fn wrapping_sub_is_zero_for_identical_reads() {
69 let reading = Ticks::from_raw(24_000_000);
70
71 assert_eq!(reading.wrapping_sub(reading), 0);
72 }
73}