Skip to main content

t2t_core/
tick.rs

1//! A hardware counter's readings, and the span between two.
2
3use core::str::FromStr;
4
5use derive_more::{Debug, Display};
6#[cfg(feature = "zerocopy-08")]
7use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
8
9use crate::spelling::{Count, read_count};
10use crate::{ParseTickdeltaError, TickRate, Timedelta};
11
12/// A point on a hardware counter, in ticks since an origin the hardware chose.
13///
14/// The origin means nothing, so only the difference of two readings does, and only with the
15/// counter's [`TickRate`]. Every core and process on a machine reads one counter, so a reading one
16/// process took, another may subtract from. It is written as its count, `1000 ticks`, and parses
17/// back from it.
18///
19/// # Examples
20/// ```
21/// use t2t_core::{TickRate, Tickdelta, Tickstamp, Timedelta};
22///
23/// let start = Tickstamp::from_ticks(1_000);
24/// let end = start + Tickdelta::from_ticks(24);
25///
26/// assert_eq!(end - start, Tickdelta::from_ticks(24), "a reading minus a reading");
27/// let rate = TickRate::from_hertz(24_000_000).expect("a nonzero rate");
28/// assert_eq!((end - start).to_timedelta(rate), Timedelta::MICROSECOND, "worth a span");
29/// ```
30#[repr(transparent)]
31#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
32#[display("{}", Count(*_0, " ticks"))]
33#[debug("{self}")]
34#[cfg_attr(feature = "zerocopy-08", derive(FromBytes, IntoBytes, Immutable, KnownLayout))]
35pub struct Tickstamp(pub(crate) i64);
36
37impl Tickstamp {
38    /// The point `ticks` ticks after the counter's origin, as its register holds it.
39    #[inline]
40    #[must_use]
41    pub const fn from_ticks(ticks: i64) -> Self {
42        Self(ticks)
43    }
44
45    /// Ticks since the counter's origin, as its register holds them.
46    #[inline]
47    #[must_use]
48    pub const fn as_ticks(self) -> i64 {
49        self.0
50    }
51}
52
53/// A signed span of counter ticks: how far apart two [`Tickstamp`]s are.
54///
55/// Its operators saturate, each with a `checked_*` twin, as a [`Timedelta`]'s do; a [`TickRate`]
56/// turns it into one. It is written as its count, `24 ticks`, and parses back from it.
57///
58/// # Examples
59/// ```
60/// use t2t_core::{TickRate, Tickdelta, Timedelta};
61///
62/// let span: Tickdelta = "3000 ticks".parse()?;
63/// let rate = TickRate::from_hertz(3_000_000_000).expect("a nonzero rate");
64/// assert_eq!(span.to_timedelta(rate), Timedelta::MICROSECOND, "3000 ticks at 3 GHz");
65/// # Ok::<(), t2t_core::ParseTickdeltaError>(())
66/// ```
67#[repr(transparent)]
68#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
69#[display("{}", Count(*_0, " ticks"))]
70#[debug("{self}")]
71#[cfg_attr(feature = "zerocopy-08", derive(FromBytes, IntoBytes, Immutable, KnownLayout))]
72pub struct Tickdelta(pub(crate) i64);
73
74impl Tickdelta {
75    /// A span of `ticks` ticks.
76    #[inline]
77    #[must_use]
78    pub const fn from_ticks(ticks: i64) -> Self {
79        Self(ticks)
80    }
81
82    /// The span in ticks.
83    #[inline]
84    #[must_use]
85    pub const fn as_ticks(self) -> i64 {
86        self.0
87    }
88
89    /// How long the span lasts at `rate`: a multiply and a shift, to within a nanosecond.
90    #[inline]
91    #[must_use]
92    pub const fn to_timedelta(self, rate: TickRate) -> Timedelta {
93        Timedelta(rate.ticks_to_nanos(self.0))
94    }
95}
96
97/// Reading a count of ticks from the spelling `Display` writes.
98macro_rules! read_as_ticks {
99    ($count:ident) => {
100        /// Reads what [`Display`](core::fmt::Display) writes: a count, then ` ticks`.
101        impl FromStr for $count {
102            type Err = ParseTickdeltaError;
103
104            fn from_str(text: &str) -> Result<Self, Self::Err> {
105                let count = text.strip_suffix(" ticks").and_then(read_count);
106                count.map(Self).ok_or(ParseTickdeltaError)
107            }
108        }
109    };
110}
111
112read_as_ticks!(Tickstamp);
113read_as_ticks!(Tickdelta);
114
115#[cfg(test)]
116mod tests {
117    use alloc::format;
118    use alloc::string::ToString as _;
119
120    use rstest::rstest;
121
122    use crate::{ParseTickdeltaError, Tickdelta, Tickstamp};
123
124    #[test]
125    fn a_count_of_ticks_reads_back_from_its_spelling() {
126        let span = Tickdelta::from_ticks(-24);
127        assert_eq!(span.to_string(), "-24 ticks", "written as its count");
128        assert_eq!("-24 ticks".parse(), Ok(span), "and read back");
129        assert_eq!(
130            "1000 ticks".parse(),
131            Ok(Tickstamp::from_ticks(1_000)),
132            "a reading the same way"
133        );
134    }
135
136    #[test]
137    fn a_width_pads_the_count_with_its_unit() {
138        assert_eq!(format!("[{:>10}]", Tickdelta::from_ticks(24)), "[  24 ticks]", "a span");
139        assert_eq!(format!("[{:<10}]", Tickstamp::from_ticks(24)), "[24 ticks  ]", "and a reading");
140    }
141
142    #[rstest]
143    #[case::no_unit("24")]
144    #[case::a_sign_display_never_writes("+24 ticks")]
145    #[case::one_tick("1 tick")]
146    #[case::no_space("24ticks")]
147    #[case::past_the_range("9223372036854775808 ticks")]
148    fn a_malformed_count_of_ticks_is_refused(#[case] text: &str) {
149        assert_eq!(text.parse::<Tickdelta>(), Err(ParseTickdeltaError), "{text:?} is refused");
150    }
151}