use derive_more::{Deref, DerefMut, From, Into};
#[cfg(feature = "zerocopy")]
use zerocopy::{FromBytes, Immutable, KnownLayout};
use crate::{TimePoint, Timestamp};
#[repr(C)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Deref, DerefMut, From, Into,
)]
#[cfg_attr(feature = "zerocopy", derive(FromBytes, Immutable, KnownLayout))]
pub struct Timed<T, S = Timestamp> {
pub stamp: S,
#[deref]
#[deref_mut]
pub value: T,
}
const _: () = assert!(size_of::<Timed<u64>>() == 16, "a stamp and a word, with no padding");
#[cfg(feature = "zerocopy")]
const _: () = {
const fn is_from_bytes<T: FromBytes>() {}
is_from_bytes::<Timed<u64>>();
};
impl<T, S> Timed<T, S> {
#[inline]
#[must_use]
pub const fn new(stamp: S, value: T) -> Self {
Self { stamp, value }
}
#[inline]
#[must_use]
pub fn into_inner(self) -> T {
self.value
}
#[inline]
#[must_use]
pub fn into_parts(self) -> (S, T) {
(self.stamp, self.value)
}
#[inline]
#[must_use]
pub fn map<U, F: FnOnce(T) -> U>(self, transform: F) -> Timed<U, S> {
Timed { stamp: self.stamp, value: transform(self.value) }
}
#[inline]
#[must_use]
pub fn map_stamp<R, F: FnOnce(S) -> R>(self, transform: F) -> Timed<T, R> {
Timed { stamp: transform(self.stamp), value: self.value }
}
}
impl<T, S: Copy> Timed<T, S> {
#[inline]
#[must_use]
pub const fn as_ref(&self) -> Timed<&T, S> {
Timed { stamp: self.stamp, value: &self.value }
}
#[inline]
#[must_use]
pub const fn as_mut(&mut self) -> Timed<&mut T, S> {
Timed { stamp: self.stamp, value: &mut self.value }
}
}
impl<T, S: TimePoint> Timed<T, S> {
#[inline]
#[must_use]
#[expect(
clippy::arithmetic_side_effects,
reason = "the point's own subtraction, which saturates for t2t's"
)]
pub fn elapsed(&self, now: S) -> S::Span {
now - self.stamp
}
}
#[cfg(test)]
mod tests {
use core::mem::offset_of;
use crate::{Tickdelta, Tickstamp, Timed, Timedelta, Timestamp, Uptime};
#[test]
fn the_stamp_leads() {
assert_eq!(offset_of!(Timed<u64>, stamp), 0, "the stamp at the front, for its order");
}
#[test]
fn the_order_is_chronological_whatever_the_value() {
let first_quote = Timed::new(Timestamp::from_secs(1), 99_u64);
let second_quote = Timed::new(Timestamp::from_secs(2), 0_u64);
assert!(first_quote < second_quote, "the stamp decides");
}
#[test]
fn elapsed_is_the_span_on_the_stamps_own_timeline() {
let wall = Timed::new(Timestamp::from_secs(10), ()).elapsed(Timestamp::from_secs(13));
assert_eq!(wall, Timedelta::from_secs(3), "on the wall clock");
let ahead = Timed::new(Uptime::from_secs(10), ()).elapsed(Uptime::from_secs(9));
assert_eq!(ahead, -Timedelta::SECOND, "negative for a stamp still ahead");
let counted =
Timed::new(Tickstamp::from_ticks(1_000), ()).elapsed(Tickstamp::from_ticks(1_025));
assert_eq!(counted, Tickdelta::from_ticks(25), "and in ticks on a counter");
}
#[test]
fn mapping_keeps_the_other_half() {
let timed = Timed::new(Timestamp::from_secs(7), 21_u64);
let doubled = Timed::new(Timestamp::from_secs(7), 42);
assert_eq!(timed.map(|value| value * 2), doubled, "a new value, the same stamp");
assert_eq!(timed.map_stamp(Timestamp::as_secs), Timed::new(7, 21), "and the other way");
let parts = (Timestamp::from_secs(7), 21);
assert_eq!(timed.into_parts(), parts, "the stamp and the value");
assert_eq!(Timed::from(parts), timed, "and back");
assert_eq!(<(Timestamp, u64)>::from(timed), parts, "through `From` both ways");
}
#[test]
fn a_borrow_keeps_the_stamp() {
let mut timed = Timed::new(Timestamp::from_secs(5), [1_u8, 2, 3]);
assert_eq!(timed.len(), 3, "the value's own methods, through `Deref`");
timed.as_mut().value[0] = 9;
assert_eq!(timed.as_ref().value, &[9, 2, 3], "a borrow under the same stamp");
}
}