Skip to main content

repose_core/
signal.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5use crate::reactive;
6
7pub type SubId = usize;
8
9static NEXT_SIGNAL_ID: AtomicUsize = AtomicUsize::new(1);
10
11pub struct Signal<T: 'static>(Rc<RefCell<Inner<T>>>);
12
13impl<T> Clone for Signal<T> {
14    fn clone(&self) -> Self {
15        Self(self.0.clone())
16    }
17}
18
19struct Inner<T> {
20    id: usize,
21    value: T,
22    subs: Vec<Option<Box<dyn Fn(&T)>>>,
23}
24
25impl<T> Signal<T> {
26    pub fn new(value: T) -> Self {
27        let id = NEXT_SIGNAL_ID.fetch_add(1, Ordering::Relaxed);
28        Self(Rc::new(RefCell::new(Inner {
29            id,
30            value,
31            subs: Vec::new(),
32        })))
33    }
34
35    pub fn id(&self) -> usize {
36        self.0.borrow().id
37    }
38
39    pub fn get(&self) -> T
40    where
41        T: Clone,
42    {
43        let inner = self.0.borrow();
44        reactive::register_signal_read(inner.id);
45        inner.value.clone()
46    }
47
48    /// Set the signal value and notify subscribers + the reactive graph.
49    pub fn set(&self, v: T) {
50        let id = {
51            let mut inner = self.0.borrow_mut();
52            inner.value = v;
53            inner.id
54        };
55        self.notify_and_request_frame(id);
56    }
57
58    pub fn update<F: FnOnce(&mut T)>(&self, f: F) {
59        let id = {
60            let mut inner = self.0.borrow_mut();
61            f(&mut inner.value);
62            inner.id
63        };
64        self.notify_and_request_frame(id);
65    }
66
67    fn notify_and_request_frame(&self, id: usize) {
68        // Call subscribers with CURRENT_OBSERVER cleared so subscriber reads
69        // don't register edges against the wrong observer.
70        reactive::without_observer(|| {
71            let inner = self.0.borrow();
72            let vref = &inner.value;
73            for s in &inner.subs {
74                if let Some(cb) = s.as_ref() {
75                    cb(vref);
76                }
77            }
78        });
79
80        reactive::signal_changed(id);
81        crate::request_frame();
82    }
83
84    pub fn subscribe(&self, f: impl Fn(&T) + 'static) -> SubId {
85        let mut inner = self.0.borrow_mut();
86        inner.subs.push(Some(Box::new(f)));
87        inner.subs.len() - 1
88    }
89
90    /// Remove a subscriber by id. Returns true if removed.
91    pub fn unsubscribe(&self, id: SubId) -> bool {
92        let mut inner = self.0.borrow_mut();
93        if id < inner.subs.len() {
94            inner.subs[id] = None;
95            // prevents unbounded tombstone growth
96            while inner.subs.last().is_some_and(|s| s.is_none()) {
97                inner.subs.pop();
98            }
99            true
100        } else {
101            false
102        }
103    }
104
105    /// Subscribe and get a guard that auto-unsubscribes on drop.
106    pub fn subscribe_guard(&self, f: impl Fn(&T) + 'static) -> SubGuard<T> {
107        let id = self.subscribe(f);
108        SubGuard {
109            sig: self.clone(),
110            id,
111        }
112    }
113}
114
115pub fn signal<T>(t: T) -> Signal<T> {
116    Signal::new(t)
117}
118
119/// RAII guard for a Signal subscription; unsubscribes on drop.
120pub struct SubGuard<T: 'static> {
121    sig: crate::Signal<T>,
122    id: SubId,
123}
124impl<T> Drop for SubGuard<T> {
125    fn drop(&mut self) {
126        let _ = self.sig.unsubscribe(self.id);
127    }
128}