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}