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    ///
50    /// Should never call into the reactive graph while holding a RefCell borrow.
51    /// It also calls subscribers under an *immutable* borrow so callbacks may read (`get()`)
52    /// without panicking. (Mutating the same signal inside its own subscriber is still
53    /// considered invalid and may panic, which is a reasonable constraint for a small core.)
54    pub fn set(&self, v: T) {
55        let id = {
56            let mut inner = self.0.borrow_mut();
57            inner.value = v;
58            inner.id
59        };
60
61        // Call subscribers under an immutable borrow (safe for reads).
62        {
63            let inner = self.0.borrow();
64            let vref = &inner.value;
65            for s in &inner.subs {
66                if let Some(cb) = s.as_ref() {
67                    cb(vref);
68                }
69            }
70        }
71
72        // Notify reactive graph after all borrows are dropped.
73        reactive::signal_changed(id);
74
75        crate::request_frame();
76    }
77
78    pub fn update<F: FnOnce(&mut T)>(&self, f: F) {
79        let id = {
80            let mut inner = self.0.borrow_mut();
81            f(&mut inner.value);
82            inner.id
83        };
84
85        {
86            let inner = self.0.borrow();
87            let vref = &inner.value;
88            for s in &inner.subs {
89                if let Some(cb) = s.as_ref() {
90                    cb(vref);
91                }
92            }
93        }
94
95        reactive::signal_changed(id);
96
97        crate::request_frame();
98    }
99
100    pub fn subscribe(&self, f: impl Fn(&T) + 'static) -> SubId {
101        self.0.borrow_mut().subs.push(Some(Box::new(f)));
102        self.0.borrow().subs.len() - 1
103    }
104
105    /// Remove a subscriber by id. Returns true if removed.
106    pub fn unsubscribe(&self, id: SubId) -> bool {
107        let mut inner = self.0.borrow_mut();
108        if id < inner.subs.len() {
109            inner.subs[id] = None;
110            true
111        } else {
112            false
113        }
114    }
115
116    /// Subscribe and get a guard that auto-unsubscribes on drop.
117    pub fn subscribe_guard(&self, f: impl Fn(&T) + 'static) -> SubGuard<T> {
118        let id = self.subscribe(f);
119        let sig = self.clone();
120        SubGuard {
121            sig,
122            id,
123            active: true,
124        }
125    }
126}
127
128pub fn signal<T>(t: T) -> Signal<T> {
129    Signal::new(t)
130}
131
132/// RAII guard for a Signal subscription; unsubscribes on drop.
133pub struct SubGuard<T: 'static> {
134    sig: crate::Signal<T>,
135    id: SubId,
136    active: bool,
137}
138impl<T> Drop for SubGuard<T> {
139    fn drop(&mut self) {
140        if self.active {
141            let _ = self.sig.unsubscribe(self.id);
142            self.active = false;
143        }
144    }
145}