Skip to main content

t2t_core/
timed.rs

1//! A value and the stamp it was captured with.
2
3use derive_more::{Deref, DerefMut, From, Into};
4#[cfg(feature = "zerocopy")]
5use zerocopy::{FromBytes, Immutable, KnownLayout};
6
7use crate::{TimePoint, Timestamp};
8
9/// A value `T` and the stamp `S` its writer captured it with.
10///
11/// The stamp comes from the writer: by the time a reader holds the value the moment has gone, and
12/// a clock read on the reading side would time the reader. The stamp comes first, so the derived
13/// order is chronological, and the value is reached through [`Deref`](core::ops::Deref).
14///
15/// # Examples
16/// ```
17/// use t2t_core::{Timed, Timedelta, Timestamp};
18///
19/// let captured = Timestamp::from_secs(1_700_000_000);
20/// let sample = Timed::new(captured, 101_u64);
21///
22/// assert_eq!(*sample, 101, "the value, through `Deref`");
23/// let now = captured + Timedelta::from_millis(5);
24/// assert_eq!(sample.elapsed(now), Timedelta::from_millis(5), "its age");
25/// ```
26#[repr(C)]
27#[derive(
28    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Deref, DerefMut, From, Into,
29)]
30#[cfg_attr(feature = "zerocopy", derive(FromBytes, Immutable, KnownLayout))]
31pub struct Timed<T, S = Timestamp> {
32    /// When the value was captured.
33    pub stamp: S,
34    /// What was captured.
35    #[deref]
36    #[deref_mut]
37    pub value: T,
38}
39
40const _: () = assert!(size_of::<Timed<u64>>() == 16, "a stamp and a word, with no padding");
41
42// What `zerocopy` gives a stamped value: read from bytes, never written to them, since its
43// `IntoBytes` derive takes a generic struct only where every field is `Unaligned`, and no stamp is.
44#[cfg(feature = "zerocopy")]
45const _: () = {
46    /// Compiles only for a type read from any bytes.
47    const fn is_from_bytes<T: FromBytes>() {}
48    is_from_bytes::<Timed<u64>>();
49};
50
51impl<T, S> Timed<T, S> {
52    /// `value`, captured at `stamp`.
53    #[inline]
54    #[must_use]
55    pub const fn new(stamp: S, value: T) -> Self {
56        Self { stamp, value }
57    }
58
59    /// The value, without its stamp.
60    #[inline]
61    #[must_use]
62    pub fn into_inner(self) -> T {
63        self.value
64    }
65
66    /// The stamp and the value.
67    #[inline]
68    #[must_use]
69    pub fn into_parts(self) -> (S, T) {
70        (self.stamp, self.value)
71    }
72
73    /// The same stamp over a value made from this one.
74    #[inline]
75    #[must_use]
76    pub fn map<U, F: FnOnce(T) -> U>(self, transform: F) -> Timed<U, S> {
77        Timed { stamp: self.stamp, value: transform(self.value) }
78    }
79
80    /// The same value under a stamp made from this one.
81    #[inline]
82    #[must_use]
83    pub fn map_stamp<R, F: FnOnce(S) -> R>(self, transform: F) -> Timed<T, R> {
84        Timed { stamp: transform(self.stamp), value: self.value }
85    }
86}
87
88impl<T, S: Copy> Timed<T, S> {
89    /// The same stamp over a borrow of the value.
90    #[inline]
91    #[must_use]
92    pub const fn as_ref(&self) -> Timed<&T, S> {
93        Timed { stamp: self.stamp, value: &self.value }
94    }
95
96    /// The same stamp over a mutable borrow of the value.
97    #[inline]
98    #[must_use]
99    pub const fn as_mut(&mut self) -> Timed<&mut T, S> {
100        Timed { stamp: self.stamp, value: &mut self.value }
101    }
102}
103
104impl<T, S: TimePoint> Timed<T, S> {
105    /// The span from capture to `now`, negative for a stamp still ahead.
106    #[inline]
107    #[must_use]
108    #[expect(
109        clippy::arithmetic_side_effects,
110        reason = "the point's own subtraction, which saturates for t2t's"
111    )]
112    pub fn elapsed(&self, now: S) -> S::Span {
113        now - self.stamp
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use core::mem::offset_of;
120
121    use crate::{Tickdelta, Tickstamp, Timed, Timedelta, Timestamp, Uptime};
122
123    #[test]
124    fn the_stamp_leads() {
125        assert_eq!(offset_of!(Timed<u64>, stamp), 0, "the stamp at the front, for its order");
126    }
127
128    #[test]
129    fn the_order_is_chronological_whatever_the_value() {
130        let first_quote = Timed::new(Timestamp::from_secs(1), 99_u64);
131        let second_quote = Timed::new(Timestamp::from_secs(2), 0_u64);
132        assert!(first_quote < second_quote, "the stamp decides");
133    }
134
135    #[test]
136    fn elapsed_is_the_span_on_the_stamps_own_timeline() {
137        let wall = Timed::new(Timestamp::from_secs(10), ()).elapsed(Timestamp::from_secs(13));
138        assert_eq!(wall, Timedelta::from_secs(3), "on the wall clock");
139        let ahead = Timed::new(Uptime::from_secs(10), ()).elapsed(Uptime::from_secs(9));
140        assert_eq!(ahead, -Timedelta::SECOND, "negative for a stamp still ahead");
141        let counted =
142            Timed::new(Tickstamp::from_ticks(1_000), ()).elapsed(Tickstamp::from_ticks(1_025));
143        assert_eq!(counted, Tickdelta::from_ticks(25), "and in ticks on a counter");
144    }
145
146    #[test]
147    fn mapping_keeps_the_other_half() {
148        let timed = Timed::new(Timestamp::from_secs(7), 21_u64);
149        let doubled = Timed::new(Timestamp::from_secs(7), 42);
150        assert_eq!(timed.map(|value| value * 2), doubled, "a new value, the same stamp");
151        assert_eq!(timed.map_stamp(Timestamp::as_secs), Timed::new(7, 21), "and the other way");
152        let parts = (Timestamp::from_secs(7), 21);
153        assert_eq!(timed.into_parts(), parts, "the stamp and the value");
154        assert_eq!(Timed::from(parts), timed, "and back");
155        assert_eq!(<(Timestamp, u64)>::from(timed), parts, "through `From` both ways");
156    }
157
158    #[test]
159    fn a_borrow_keeps_the_stamp() {
160        let mut timed = Timed::new(Timestamp::from_secs(5), [1_u8, 2, 3]);
161        assert_eq!(timed.len(), 3, "the value's own methods, through `Deref`");
162        timed.as_mut().value[0] = 9;
163        assert_eq!(timed.as_ref().value, &[9, 2, 3], "a borrow under the same stamp");
164    }
165}