Skip to main content

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}