Skip to main content

rtic_sync/
watch.rs

1//! A "latest only" value store with unlimited writers and async waiting. Value is always available once initialized.
2
3use crate::signal::{Signal, SignalWriter, Store};
4use core::{future::poll_fn, task::Poll};
5use portable_atomic::Ordering::{AcqRel, Acquire, Release};
6
7/// A "latest only" value store with unlimited writers and async waiting. Value is always available once initialized.
8pub struct Watch<T: Copy>(Signal<T>);
9
10impl<T> core::fmt::Debug for Watch<T>
11where
12    T: core::marker::Copy,
13{
14    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
15        fmt.write_fmt(format_args!(
16            "Watch<{}>{{ .. }}",
17            core::any::type_name::<T>()
18        ))
19    }
20}
21
22impl<T: Copy> Default for Watch<T> {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28unsafe impl<T: Copy> Send for Watch<T> {}
29unsafe impl<T: Copy> Sync for Watch<T> {}
30
31impl<T: Copy> Watch<T> {
32    /// Create a new watch.
33    pub const fn new() -> Self {
34        Self(Signal::new())
35    }
36
37    /// Split the watch into a writer and watch reader.
38    pub fn split(&self) -> (WatchWriter<'_, T>, WatchReader<'_, T>) {
39        if self.0.already_split.swap(true, AcqRel) {
40            panic!("`Watch` cannot be split twice");
41        }
42        (
43            WatchWriter(SignalWriter { parent: &self.0 }),
44            WatchReader { parent: &self.0 },
45        )
46    }
47}
48
49/// Creates a split watch with `'static` lifetime.
50#[macro_export]
51macro_rules! make_watch {
52    ( $T:ty ) => {{
53        static WATCH: $crate::watch::Watch<$T> = $crate::watch::Watch::new();
54
55        WATCH.split()
56    }};
57}
58
59/// Facilitates the writing of values to a Watch.
60#[derive(Clone)]
61pub struct WatchWriter<'a, T: Copy>(SignalWriter<'a, T>);
62
63impl<T> core::fmt::Debug for WatchWriter<'_, T>
64where
65    T: core::marker::Copy,
66{
67    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68        fmt.write_fmt(format_args!(
69            "WatchWriter<{}>{{ .. }}",
70            core::any::type_name::<T>()
71        ))
72    }
73}
74
75impl<T: Copy> WatchWriter<'_, T> {
76    /// Write a value to the Watch.
77    pub fn write(&mut self, value: T) {
78        self.0.write(value);
79    }
80}
81
82/// Facilitates the async reading of values from the Watch.
83pub struct WatchReader<'a, T: Copy> {
84    parent: &'a Signal<T>,
85}
86
87impl<T> core::fmt::Debug for WatchReader<'_, T>
88where
89    T: core::marker::Copy,
90{
91    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
92        fmt.write_fmt(format_args!(
93            "WatchReader<{}>{{ .. }}",
94            core::any::type_name::<T>()
95        ))
96    }
97}
98
99impl<T: Copy> WatchReader<'_, T> {
100    /// Immediately get the latest value stored in the Signal.
101    fn get_inner(&mut self) -> Store<T> {
102        critical_section::with(|_| {
103            // SAFETY: in a cs: exclusive access
104            unsafe { self.parent.store.get().read() }
105        })
106    }
107
108    /// Immediately get the seen attribute stored in the Signal.
109    fn get_seen(&mut self) -> bool {
110        self.parent.seen.load(Acquire)
111    }
112
113    /// Mark value as seen.
114    fn mark_seen(&mut self) {
115        self.parent.seen.store(true, Release);
116    }
117
118    /// Returns the latest value, or None if uninitialized.
119    pub fn try_get(&mut self) -> Option<T> {
120        match self.get_inner() {
121            Store::Unset => None,
122            Store::Set(value) => {
123                self.mark_seen();
124                Some(value)
125            }
126        }
127    }
128
129    /// Wait for an unseen value.
130    ///
131    /// If the current value is unseen it will be returned immediately.
132    ///
133    /// If current value is already seen, it will wait for a new value to be written and then read it.
134    pub async fn changed(&mut self) -> T {
135        poll_fn(|ctx| {
136            self.parent.waker.register(ctx.waker());
137
138            if self.get_seen() {
139                return Poll::Pending;
140            }
141
142            match self.get_inner() {
143                Store::Unset => Poll::Pending,
144                Store::Set(value) => {
145                    self.mark_seen();
146                    Poll::Ready(value)
147                }
148            }
149        })
150        .await
151    }
152}
153
154#[cfg(test)]
155#[cfg(not(loom))]
156mod tests {
157    use super::*;
158    use static_cell::StaticCell;
159
160    #[test]
161    fn empty() {
162        let (_writer, mut reader) = make_watch!(u32);
163
164        assert!(reader.try_get().is_none());
165    }
166
167    #[test]
168    fn ping_pong() {
169        let (mut writer, mut reader) = make_watch!(u32);
170
171        writer.write(0xde);
172        assert!(reader.try_get().is_some_and(|value| value == 0xde));
173    }
174
175    #[test]
176    fn latest() {
177        let (mut writer, mut reader) = make_watch!(u32);
178
179        writer.write(0xde);
180        writer.write(0xad);
181        writer.write(0xbe);
182        writer.write(0xef);
183        assert!(reader.try_get().is_some_and(|value| value == 0xef));
184    }
185
186    #[test]
187    fn no_consumption() {
188        let (mut writer, mut reader) = make_watch!(u32);
189
190        writer.write(0xaa);
191        assert!(reader.try_get().is_some_and(|value| value == 0xaa));
192        assert!(reader.try_get().is_some_and(|value| value == 0xaa));
193    }
194
195    #[test]
196    #[should_panic]
197    fn no_multi_split() {
198        fn scary_helper<'a>() -> (WatchWriter<'a, u32>, WatchReader<'a, u32>) {
199            make_watch!(u32)
200        }
201        let (mut _writer1, mut _reader1) = scary_helper();
202        let (mut _writer2, mut _reader2) = scary_helper();
203    }
204
205    #[tokio::test]
206    async fn pending() {
207        let (mut writer, mut reader) = make_watch!(u32);
208
209        writer.write(0xaa);
210
211        assert_eq!(reader.changed().await, 0xaa);
212    }
213
214    #[tokio::test]
215    async fn changed() {
216        static READER: StaticCell<WatchReader<u32>> = StaticCell::new();
217        let (mut writer, mut reader) = make_watch!(u32);
218
219        writer.write(0xaa);
220        assert_eq!(reader.changed().await, 0xaa);
221
222        let reader = READER.init(reader);
223        let handle = tokio::spawn(reader.changed());
224
225        tokio::task::yield_now().await; // encourage tokio executor to poll reader future
226        assert!(!handle.is_finished()); // verify reader future did not resolve after poll
227
228        writer.write(0xab);
229        assert!(handle.await.is_ok_and(|value| value == 0xab));
230    }
231
232    #[tokio::test]
233    async fn try_get_marks_seen() {
234        static READER: StaticCell<WatchReader<u32>> = StaticCell::new();
235        let (mut writer, mut reader) = make_watch!(u32);
236
237        writer.write(0xaa);
238        assert!(reader.try_get().is_some_and(|value| value == 0xaa));
239
240        let reader = READER.init(reader);
241        let handle = tokio::spawn(reader.changed());
242
243        tokio::task::yield_now().await; // encourage tokio executor to poll reader future
244        assert!(!handle.is_finished()); // verify reader future did not resolve after poll
245    }
246}