1use derive_more::{Deref, DerefMut, From, Into};
4#[cfg(feature = "zerocopy")]
5use zerocopy::{FromBytes, Immutable, KnownLayout};
6
7use crate::{TimePoint, Timestamp};
8
9#[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 pub stamp: S,
34 #[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#[cfg(feature = "zerocopy")]
45const _: () = {
46 const fn is_from_bytes<T: FromBytes>() {}
48 is_from_bytes::<Timed<u64>>();
49};
50
51impl<T, S> Timed<T, S> {
52 #[inline]
54 #[must_use]
55 pub const fn new(stamp: S, value: T) -> Self {
56 Self { stamp, value }
57 }
58
59 #[inline]
61 #[must_use]
62 pub fn into_inner(self) -> T {
63 self.value
64 }
65
66 #[inline]
68 #[must_use]
69 pub fn into_parts(self) -> (S, T) {
70 (self.stamp, self.value)
71 }
72
73 #[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 #[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 #[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 #[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 #[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}