Stamped Values
Timed<T, S> holds a value T and the stamp S its writer
captured it with, a Timestamp unless a stamp of another timeline is named. It
says how old the value is at any later point, and reaches the value through
Deref, so a stamped sample’s fields read as the sample’s own.
fn a_stamped_value_says_how_old_it_is() {
use t2t::{Timed, Timedelta, Timestamp};
/// What a weather station measured.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Weather {
/// The temperature, in degrees Celsius.
celsius: i16,
/// The relative humidity, in percent.
humidity: u8,
}
let captured = Timestamp::from_secs(1_700_000_000);
let sample = Timed::new(captured, Weather { celsius: 21, humidity: 40 });
assert_eq!(sample.celsius, 21, "the value's fields, through `Deref`");
let now = captured + Timedelta::from_millis(1_500);
assert_eq!(sample.elapsed(now), Timedelta::from_millis(1_500), "its age at `now`");
assert!(sample.elapsed(now) > Timedelta::SECOND, "so it is stale after a second");
let early = captured - Timedelta::MILLISECOND;
assert!(sample.elapsed(early).is_negative(), "and its age is negative before its stamp");
}
elapsed is the span from the stamp to the point it is given, on the stamp’s
own timeline, and negative for a stamp still ahead of it.
The Writer Stamps
The code that captures a value stamps it, and whatever reads it keeps that
stamp: by the time a reader holds the value, the moment it was captured has
gone, and a clock read on the reading side would time the reader. map makes a
new value under the same stamp, and map_stamp a new stamp over the same value.
fn the_writer_stamps_and_the_reader_keeps_the_stamp() {
use t2t::clock::{Clock, ManualClock};
use t2t::{Timed, Timedelta, Timestamp};
/// A value, stamped by `clock` as it is written.
fn publish<C: Clock<Reading = Timestamp>, T>(clock: &C, value: T) -> Timed<T> {
Timed::new(clock.now(), value)
}
let clock = ManualClock::new(Timestamp::from_secs(1_700_000_000));
let sample = publish(&clock, 101_u64);
clock.advance(Timedelta::from_millis(3));
// Whatever the reader makes of the value, the stamp stays the writer's.
let doubled = sample.map(|count| count.saturating_mul(2));
assert_eq!((doubled.stamp, doubled.value), (sample.stamp, 202), "a new value, the same stamp");
let in_seconds = sample.map_stamp(Timestamp::as_secs);
assert_eq!(in_seconds.stamp, 1_700_000_000, "or a new stamp over the same value");
assert_eq!(sample.elapsed(clock.now()), Timedelta::from_millis(3), "and its age, read late");
assert_eq!(sample.into_parts(), (sample.stamp, 101), "and its parts, the stamp first");
}
as_ref and as_mut borrow the value under the same stamp; into_inner gives
the value, into_parts the stamp and the value, and a Timed converts to and
from the pair (S, T).
Order
The stamp is the first field, so the order a Timed derives is chronological,
and the value decides only between equal stamps.
fn stamped_values_sort_by_their_stamps() {
use t2t::{Timed, Uptime};
let mut events = [
Timed::new(Uptime::from_secs(3), "third"),
Timed::new(Uptime::from_secs(1), "first"),
Timed::new(Uptime::from_secs(2), "second"),
];
events.sort();
let order = events.map(Timed::into_inner);
assert_eq!(order, ["first", "second", "third"], "the stamp decides the order");
}
Layout
A Timed is #[repr(C)]: the stamp at offset zero, then the value. A
Timed<u64> is 16 bytes, with no padding, on any timeline, since every stamp is
an i64. With zerocopy-08, a Timed is read from bytes, as
Serialization shows.
fn a_stamped_word_is_two_words() {
use core::mem::offset_of;
use t2t::{Tickstamp, Timed};
assert_eq!(size_of::<Timed<u64>>(), 16, "a stamp and a word, no padding");
assert_eq!(offset_of!(Timed<u64>, stamp), 0, "the stamp first");
assert_eq!(size_of::<Timed<u64, Tickstamp>>(), 16, "on any timeline");
}