nullable_utils/
output.rs

1use core::hint;
2use core::mem;
3use core::ops::Deref;
4
5use alloc::sync::Arc;
6use alloc::sync::Weak;
7use once_cell::unsync::Lazy;
8use parking_lot::Mutex;
9use parking_lot::RwLock;
10use parking_lot::RwLockReadGuard;
11
12/// `Listener` implements the emitter side of the [Output Tracking pattern].
13///
14/// [Output Tracking]: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking
15pub struct Listener<T> {
16    trackers: Mutex<Vec<Weak<RwLock<Vec<T>>>>>,
17}
18
19impl<T> Listener<T> {
20    /// Emit a new event to attached listeners.
21    pub fn emit(&self, event: &T)
22    where
23        T: Clone,
24    {
25        self.emit_with(|| event.clone());
26    }
27
28    /// Emit a new event to attached listeners.
29    ///
30    /// Calls the given closure to generate the event for each tracker. This
31    /// allows to skip expensive setup when no trackers are connected.
32    pub fn emit_with(&self, mut event_source: impl FnMut() -> T) {
33        let should_prune = self
34            .trackers
35            .lock()
36            .iter()
37            .fold(false, |should_prune, tracker| {
38                let Some(tracker) = tracker.upgrade() else {
39                    return true;
40                };
41
42                tracker.write().push(event_source());
43                should_prune
44            });
45
46        if should_prune {
47            self.prune();
48        }
49    }
50
51    /// Emit a new event to attached listeners.
52    ///
53    /// Calls the given closure at most once to generate the event. Afterwards,
54    /// the generated event is cached and reused for future trackers. This
55    /// allows to skip expensive setup when no trackers are connected.
56    pub fn emit_with_cached(&self, cached_event_source: impl FnOnce() -> T)
57    where
58        T: Clone,
59    {
60        self.emit_with({
61            let initializer = Lazy::new(cached_event_source);
62            move || initializer.clone()
63        });
64    }
65
66    /// Create a new [`Tracker`], connected to this listener.
67    pub fn track(&self) -> Tracker<T> {
68        let events = Arc::default();
69        self.trackers.lock().push(Arc::downgrade(&events));
70        Tracker { events }
71    }
72}
73
74impl<T> Listener<T> {
75    fn prune(&self) {
76        self.trackers.lock().retain(|arc| 0 < arc.strong_count());
77    }
78}
79
80impl<T> Default for Listener<T> {
81    fn default() -> Self {
82        let trackers = Mutex::default();
83        Self { trackers }
84    }
85}
86
87/// `Tracker` implements the receiver side of the [Output Tracking pattern].
88///
89/// [Output Tracking]: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking
90#[derive(Clone, Debug)]
91pub struct Tracker<T> {
92    events: Arc<RwLock<Vec<T>>>,
93}
94
95impl<T> Tracker<T> {
96    /// Consume all current events.
97    ///
98    /// The consumed events will not be returned by future invocations of `data` or `consume`.
99    #[must_use]
100    pub fn consume(&self) -> Vec<T> {
101        let mut events = self.events.write();
102        mem::take(&mut events)
103    }
104
105    /// Get a read-only view of tracked data so far.
106    ///
107    /// <div class="warning">
108    ///
109    /// This will block the sender side while the returned handle exists, so it should not be held for a long time.
110    /// Alternatively, you can copy out of the returned view or use [`Tracker::consume`] if you do not need to retain the
111    /// data.
112    ///
113    /// </div>
114    #[must_use]
115    pub fn data(&self) -> TrackerData<T> {
116        TrackerData(self.events.read())
117    }
118
119    /// Stop listening on this `Tracker`.
120    ///
121    /// This returns any remaining events in the tracker.
122    #[must_use]
123    pub fn stop(mut self) -> Vec<T> {
124        loop {
125            match Arc::try_unwrap(self.events) {
126                Ok(events) => return events.into_inner(),
127                Err(ev) => {
128                    self.events = ev;
129                    hint::spin_loop();
130                }
131            };
132        }
133    }
134}
135
136/// `TrackerData` is a read-only view of data tracked in an [`OutputTracker`] so far.
137///
138/// <div class="warning">
139///
140/// This will block the sender side while the handle exists, so it should not be held for a long time.
141/// Alternatively, you can copy out of the returned view or use [`Tracker::consume`] if you do not need to retain the
142/// data.
143///
144/// </div>
145#[derive(Debug)]
146pub struct TrackerData<'l, T>(RwLockReadGuard<'l, Vec<T>>);
147
148impl<T> Deref for TrackerData<'_, T> {
149    type Target = [T];
150
151    fn deref(&self) -> &Self::Target {
152        &self.0
153    }
154}