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}