Skip to main content

ai_ui_core/
subscribe.rs

1//! Change notification (`Subscribable` upstream).
2//!
3//! Callbacks run after a state change completes, never while runtime state is
4//! borrowed, so a callback may read the runtime or call back into it. A
5//! callback added during notification first runs on the next change.
6
7use std::cell::{Cell, RefCell};
8use std::rc::{Rc, Weak};
9
10type Callback = Rc<dyn Fn()>;
11
12#[derive(Default)]
13/// A list of change callbacks shared by runtimes and view layers.
14pub struct Subscribers {
15    next_id: Cell<u64>,
16    callbacks: RefCell<Vec<(u64, Callback)>>,
17}
18
19impl Subscribers {
20    /// Register a callback; dropping the returned guard unregisters it.
21    pub fn subscribe(self: &Rc<Self>, callback: impl Fn() + 'static) -> Subscription {
22        let id = self.next_id.get();
23        self.next_id.set(id + 1);
24        self.callbacks.borrow_mut().push((id, Rc::new(callback)));
25        Subscription {
26            list: Rc::downgrade(self),
27            id,
28        }
29    }
30
31    /// Call every registered callback (none is called while internal state is borrowed).
32    pub fn notify(&self) {
33        let callbacks: Vec<Callback> = self
34            .callbacks
35            .borrow()
36            .iter()
37            .map(|(_, c)| c.clone())
38            .collect();
39        for callback in callbacks {
40            callback();
41        }
42    }
43}
44
45/// Keeps a callback registered; dropping it unsubscribes.
46#[must_use = "dropping a Subscription unsubscribes immediately"]
47pub struct Subscription {
48    list: Weak<Subscribers>,
49    id: u64,
50}
51
52impl Subscription {
53    /// Keep the callback for the lifetime of the runtime.
54    pub fn detach(self) {
55        std::mem::forget(self);
56    }
57}
58
59impl Drop for Subscription {
60    fn drop(&mut self) {
61        if let Some(list) = self.list.upgrade() {
62            list.callbacks.borrow_mut().retain(|(id, _)| *id != self.id);
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn dropping_the_guard_unsubscribes_and_reentrant_subscribe_is_safe() {
73        let list = Rc::new(Subscribers::default());
74        let count = Rc::new(Cell::new(0));
75        let inner: Rc<RefCell<Vec<Subscription>>> = Rc::default();
76        let guard = {
77            let (count, list2, inner) = (count.clone(), list.clone(), inner.clone());
78            list.subscribe(move || {
79                count.set(count.get() + 1);
80                inner.borrow_mut().push(list2.subscribe(|| {}));
81            })
82        };
83        list.notify();
84        assert_eq!(count.get(), 1);
85        drop(guard);
86        list.notify();
87        assert_eq!(count.get(), 1);
88    }
89}