Skip to main content

embassy_supervisor/
stamped.rs

1//! A signal that remembers when it was last written.
2
3use embassy_time::{Duration, Instant};
4use portable_atomic::{AtomicBool, AtomicU32, Ordering};
5
6/// A signal wrapper that stamps every write with the time it happened, so a
7/// reader can ask how old the value is.
8///
9/// The read-side half of write freshness. The monitor side already exists:
10/// `writes: [X observed beat]` with a `beat_timeout:` reports a node that has
11/// not written recently. `Stamped` gives the *consumer* the same fact at the
12/// point of use — [`age`](Self::age), [`is_fresh`](Self::is_fresh),
13/// [`read_fresh`](Self::read_fresh) — for the case the monitor cannot cover:
14/// a value that is still being written but must not be trusted past a
15/// certain age. What neither can tell is whether a fresh value is *valid*;
16/// a plausible-looking drift needs a consumer that understands the value.
17///
18/// Writes go through [`w`](Self::w), reads through [`r`](Self::r); there is
19/// deliberately no `Deref`, so an unstamped write path does not exist. It
20/// records the time and nothing else — compose `Stamped<Counted<T>>` when
21/// the write count matters too. Costs one `AtomicU32` and one `AtomicBool`
22/// beside the wrapped value. The stamp is `Instant` ticks truncated to
23/// `u32`, like the node heartbeat: ages are exact up to `u32::MAX` ticks.
24pub struct Stamped<T> {
25    inner: T,
26    stamp: AtomicU32,
27    written: AtomicBool,
28}
29
30impl<T> Stamped<T> {
31    /// Wrap `inner`, never written.
32    pub const fn new(inner: T) -> Self {
33        Self {
34            inner,
35            stamp: AtomicU32::new(0),
36            written: AtomicBool::new(false),
37        }
38    }
39
40    /// Borrow the inner signal for a write, stamping now.
41    pub fn w(&self) -> &T {
42        self.stamp
43            .store(Instant::now().as_ticks() as u32, Ordering::Release);
44        self.written.store(true, Ordering::Release);
45        &self.inner
46    }
47
48    /// Borrow the inner signal for a read.
49    pub fn r(&self) -> &T {
50        &self.inner
51    }
52
53    /// Borrow the inner signal without touching the stamp.
54    pub fn inner(&self) -> &T {
55        &self.inner
56    }
57
58    /// Time since the last stamped write, `None` until the first.
59    pub fn age(&self) -> Option<Duration> {
60        if !self.written.load(Ordering::Acquire) {
61            return None;
62        }
63        let now = Instant::now().as_ticks() as u32;
64        let ticks = now.wrapping_sub(self.stamp.load(Ordering::Acquire));
65        Some(Duration::from_ticks(u64::from(ticks)))
66    }
67
68    /// Has the signal been written within `max_age`?
69    pub fn is_fresh(&self, max_age: Duration) -> bool {
70        self.age().is_some_and(|age| age <= max_age)
71    }
72
73    /// The inner signal if it was written within `max_age`, else `None`.
74    pub fn read_fresh(&self, max_age: Duration) -> Option<&T> {
75        self.is_fresh(max_age).then_some(&self.inner)
76    }
77}
78
79#[cfg(feature = "coupling-observe")]
80impl<T: crate::Observable> crate::Observable for Stamped<T> {
81    fn change_token(&self) -> u32 {
82        self.inner.change_token()
83    }
84}
85
86#[cfg(feature = "dataflow")]
87impl<T: crate::Sink> crate::Sink for Stamped<T> {
88    type Item = T::Item;
89    fn put(&self, v: T::Item) {
90        self.w().put(v);
91    }
92}
93
94#[cfg(feature = "dataflow")]
95impl<T: crate::Source> crate::Source for Stamped<T> {
96    type Item = T::Item;
97    fn get(&self) -> T::Item {
98        self.r().get()
99    }
100}