1use std::fmt;
43use std::sync::{Arc, Mutex, MutexGuard};
44
45pub trait UiAction: Clone + PartialEq + fmt::Debug + Send + Sync + 'static {}
52
53impl<T> UiAction for T where T: Clone + PartialEq + fmt::Debug + Send + Sync + 'static {}
54
55type Shared<A> = Arc<Mutex<Vec<A>>>;
56
57fn lock<A>(shared: &Shared<A>) -> MutexGuard<'_, Vec<A>> {
64 shared.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
65}
66
67#[derive(Clone)]
69pub struct Sink<A: UiAction> {
70 shared: Shared<A>,
71}
72
73impl<A: UiAction> Sink<A> {
74 pub fn push(&self, action: A) {
77 lock(&self.shared).push(action);
78 }
79
80 pub fn extend(&self, actions: impl IntoIterator<Item = A>) {
82 lock(&self.shared).extend(actions);
83 }
84
85 pub fn pending(&self) -> usize {
87 lock(&self.shared).len()
88 }
89
90 pub fn drain(&self) -> Vec<A> {
92 drain_buffer(&self.shared)
93 }
94}
95
96impl<A: UiAction> fmt::Debug for Sink<A> {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.debug_struct("Sink").field("pending", &self.pending()).finish()
99 }
100}
101
102#[derive(Clone)]
104pub struct ActionQueue<A: UiAction> {
105 shared: Shared<A>,
106}
107
108impl<A: UiAction> ActionQueue<A> {
109 pub fn new() -> Self {
110 Self {
111 shared: Arc::new(Mutex::new(Vec::new())),
112 }
113 }
114
115 pub fn pair() -> (Self, Sink<A>) {
117 let queue = Self::new();
118 let sink = queue.sink();
119 (queue, sink)
120 }
121
122 pub fn sink(&self) -> Sink<A> {
124 Sink {
125 shared: Arc::clone(&self.shared),
126 }
127 }
128
129 pub fn push(&self, action: A) {
131 lock(&self.shared).push(action);
132 }
133
134 pub fn pending(&self) -> usize {
136 lock(&self.shared).len()
137 }
138
139 pub fn is_empty(&self) -> bool {
140 self.pending() == 0
141 }
142
143 pub fn drain(&self) -> Vec<A> {
145 drain_buffer(&self.shared)
146 }
147
148 pub fn drain_into(&self, out: &mut Vec<A>) {
150 let mut buffer = lock(&self.shared);
151 out.append(&mut buffer);
152 }
153
154 pub fn absorb(&self, intents: &mut Intents<A>) -> usize {
156 intents.extend(self.drain())
157 }
158}
159
160impl<A: UiAction> Default for ActionQueue<A> {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166impl<A: UiAction> fmt::Debug for ActionQueue<A> {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 f.debug_struct("ActionQueue")
169 .field("pending", &self.pending())
170 .finish()
171 }
172}
173
174fn drain_buffer<A>(shared: &Shared<A>) -> Vec<A> {
175 std::mem::take(&mut *lock(shared))
176}
177
178#[derive(Clone, Debug, Default, PartialEq)]
185pub struct Intents<A: UiAction> {
186 pending: Vec<A>,
187}
188
189impl<A: UiAction> Intents<A> {
190 pub fn new() -> Self {
191 Self { pending: Vec::new() }
192 }
193
194 pub fn requests(&self) -> &[A] {
196 &self.pending
197 }
198
199 pub fn is_empty(&self) -> bool {
200 self.pending.is_empty()
201 }
202
203 pub fn len(&self) -> usize {
204 self.pending.len()
205 }
206
207 pub fn clear(&mut self) {
209 self.pending.clear();
210 }
211
212 pub fn request(&mut self, action: A) {
214 self.pending.push(action);
215 }
216
217 pub fn extend(&mut self, actions: impl IntoIterator<Item = A>) -> usize {
219 let before = self.pending.len();
220 self.pending.extend(actions);
221 self.pending.len() - before
222 }
223
224 pub fn requested(&self, action: &A) -> bool {
226 self.pending.contains(action)
227 }
228
229 pub fn count(&self, action: &A) -> usize {
231 self.pending.iter().filter(|pending| *pending == action).count()
232 }
233
234 pub fn take(&mut self, action: &A) -> bool {
236 match self.pending.iter().position(|pending| pending == action) {
237 Some(index) => {
238 self.pending.remove(index);
239 true
240 }
241 None => false,
242 }
243 }
244
245 pub fn take_matching(&mut self, mut predicate: impl FnMut(&A) -> bool) -> Option<A> {
250 let index = self.pending.iter().position(|action| predicate(action))?;
251 Some(self.pending.remove(index))
252 }
253
254 pub fn take_all(&mut self) -> Vec<A> {
256 std::mem::take(&mut self.pending)
257 }
258}
259
260impl<A: UiAction> FromIterator<A> for Intents<A> {
261 fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
262 Self {
263 pending: iter.into_iter().collect(),
264 }
265 }
266}
267
268pub fn absorb<A: UiAction>(queue: &ActionQueue<A>, intents: &mut Intents<A>) -> usize {
273 queue.absorb(intents)
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
281 enum Act {
282 Play,
283 Pause,
284 Pick(u8),
285 }
286
287 #[test]
288 fn drains_in_push_order() {
289 let (queue, sink) = ActionQueue::pair();
290 sink.push(Act::Play);
291 sink.push(Act::Pause);
292 sink.push(Act::Play);
293 assert_eq!(queue.drain(), vec![Act::Play, Act::Pause, Act::Play]);
294 }
295
296 #[test]
297 fn drain_empties_the_buffer_once() {
298 let (queue, sink) = ActionQueue::pair();
299 sink.push(Act::Play);
300 assert_eq!(queue.drain(), vec![Act::Play]);
301 assert!(queue.drain().is_empty());
302 assert!(queue.is_empty());
303 assert_eq!(queue.pending(), 0);
304 }
305
306 #[test]
307 fn a_batch_never_contains_what_arrives_after_the_drain_started() {
308 let (queue, sink) = ActionQueue::pair();
309 sink.push(Act::Play);
310 let batch = queue.drain();
311 sink.push(Act::Pause);
312 assert_eq!(batch, vec![Act::Play]);
313 assert_eq!(queue.drain(), vec![Act::Pause]);
314 }
315
316 #[test]
317 fn clones_push_into_the_same_buffer() {
318 let (queue, sink) = ActionQueue::pair();
319 let clone = sink.clone();
320 clone.push(Act::Play);
321 assert_eq!(sink.pending(), 1);
322 assert_eq!(queue.drain(), vec![Act::Play]);
323 }
324
325 #[test]
326 fn drain_into_appends_to_the_caller_buffer() {
327 let (queue, sink) = ActionQueue::pair();
328 let mut out = vec![Act::Play];
329 sink.push(Act::Pause);
330 queue.drain_into(&mut out);
331 assert_eq!(out, vec![Act::Play, Act::Pause]);
332 }
333
334 #[test]
335 fn a_panic_while_pushing_keeps_the_queued_actions() {
336 let (queue, sink) = ActionQueue::pair();
337 sink.push(Act::Play);
338 let shared = Arc::clone(&queue.shared);
339 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
340 let _guard = shared.lock().unwrap();
341 panic!("pusher panicked while holding the queue");
342 }));
343 sink.push(Act::Pause);
344 assert_eq!(queue.drain(), vec![Act::Play, Act::Pause]);
345 }
346
347 #[test]
348 fn intents_consume_one_occurrence_at_a_time() {
349 let mut intents = Intents::new();
350 intents.extend([Act::Play, Act::Pause, Act::Play]);
351 assert_eq!(intents.count(&Act::Play), 2);
352 assert!(intents.requested(&Act::Play));
353 assert!(intents.take(&Act::Play));
354 assert!(intents.take(&Act::Play));
355 assert!(!intents.take(&Act::Play));
356 assert!(!intents.requested(&Act::Play));
357 assert!(intents.take(&Act::Pause));
358 assert!(intents.is_empty());
359 }
360
361 #[test]
362 fn take_matching_keeps_the_other_requests() {
363 let mut intents = Intents::new();
364 intents.extend([Act::Play, Act::Pick(3), Act::Pick(7)]);
365 assert_eq!(
366 intents.take_matching(|action| matches!(action, Act::Pick(_))),
367 Some(Act::Pick(3))
368 );
369 assert_eq!(intents.requests(), [Act::Play, Act::Pick(7)]);
370 assert_eq!(intents.take_matching(|action| matches!(action, Act::Pick(_))), Some(Act::Pick(7)));
371 assert!(intents.take_matching(|action| matches!(action, Act::Pick(_))).is_none());
372 }
373
374 #[test]
375 fn absorb_reports_how_many_actions_arrived() {
376 let (queue, sink) = ActionQueue::pair();
377 let mut intents = Intents::new();
378 assert_eq!(queue.absorb(&mut intents), 0);
379 sink.extend([Act::Play, Act::Pause]);
380 assert_eq!(queue.absorb(&mut intents), 2);
381 assert_eq!(intents.requests(), [Act::Play, Act::Pause]);
382 assert_eq!(intents.take_all(), vec![Act::Play, Act::Pause]);
383 assert!(intents.is_empty());
384 }
385}