Skip to main content

fui/
signal.rs

1use std::cell::RefCell;
2use std::rc::{Rc, Weak};
3
4pub type Callback = Rc<dyn Fn()>;
5
6struct ListenerEntry {
7    id: usize,
8    callback: Callback,
9}
10
11struct ListenerStore {
12    next_id: usize,
13    listeners: Vec<ListenerEntry>,
14}
15
16impl ListenerStore {
17    fn new() -> Self {
18        Self {
19            next_id: 1,
20            listeners: Vec::new(),
21        }
22    }
23
24    fn subscribe(&mut self, callback: Callback) -> usize {
25        let id = self.next_id;
26        self.next_id += 1;
27        self.listeners.push(ListenerEntry { id, callback });
28        id
29    }
30
31    fn unsubscribe(&mut self, id: usize) {
32        self.listeners.retain(|listener| listener.id != id);
33    }
34
35    fn callbacks(&self) -> Vec<Callback> {
36        self.listeners
37            .iter()
38            .map(|listener| listener.callback.clone())
39            .collect()
40    }
41}
42
43#[must_use = "subscription is removed when the guard is dropped"]
44pub struct SubscriptionGuard {
45    store: Weak<RefCell<ListenerStore>>,
46    id: usize,
47}
48
49pub type Subscription = SubscriptionGuard;
50
51impl SubscriptionGuard {
52    fn new(store: &Rc<RefCell<ListenerStore>>, id: usize) -> Self {
53        Self {
54            store: Rc::downgrade(store),
55            id,
56        }
57    }
58}
59
60impl Drop for SubscriptionGuard {
61    fn drop(&mut self) {
62        if let Some(store) = self.store.upgrade() {
63            if let Ok(mut store) = store.try_borrow_mut() {
64                store.unsubscribe(self.id);
65            }
66        }
67    }
68}
69
70pub struct Signal<T> {
71    value: T,
72    listeners: Rc<RefCell<ListenerStore>>,
73}
74
75impl<T> Signal<T> {
76    pub fn new(initial: T) -> Self {
77        Self {
78            value: initial,
79            listeners: Rc::new(RefCell::new(ListenerStore::new())),
80        }
81    }
82
83    pub fn subscribe(&mut self, callback: Callback) -> SubscriptionGuard {
84        let id = self.listeners.borrow_mut().subscribe(callback);
85        SubscriptionGuard::new(&self.listeners, id)
86    }
87}
88
89impl<T: Clone> Signal<T> {
90    pub fn get(&self) -> T {
91        self.value.clone()
92    }
93}
94
95impl<T: PartialEq> Signal<T> {
96    pub fn set(&mut self, next: T) -> Option<Vec<Callback>> {
97        if self.value == next {
98            return None;
99        }
100
101        self.value = next;
102        Some(self.listeners.borrow().callbacks())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::{Callback, Signal};
109    use std::cell::Cell;
110    use std::rc::Rc;
111
112    #[test]
113    fn dropping_subscription_guard_unsubscribes() {
114        let fired = Rc::new(Cell::new(0));
115        let fired_clone = fired.clone();
116        let callback: Callback = Rc::new(move || {
117            fired_clone.set(fired_clone.get() + 1);
118        });
119
120        let mut signal = Signal::new(1);
121        let guard = signal.subscribe(callback);
122        let listeners = signal.set(2).unwrap();
123        for listener in listeners {
124            listener();
125        }
126        assert_eq!(fired.get(), 1);
127
128        drop(guard);
129
130        let listeners = signal.set(3).unwrap();
131        assert!(listeners.is_empty());
132        assert_eq!(fired.get(), 1);
133    }
134}