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::signal_fired();
82        crate::request_frame();
83    }
84
85    pub fn subscribe(&self, f: impl Fn(&T) + 'static) -> SubId {
86        let mut inner = self.0.borrow_mut();
87        inner.subs.push(Some(Box::new(f)));
88        inner.subs.len() - 1
89    }
90
91    /// Remove a subscriber by id. Returns true if removed.
92    pub fn unsubscribe(&self, id: SubId) -> bool {
93        let mut inner = self.0.borrow_mut();
94        if id < inner.subs.len() {
95            inner.subs[id] = None;
96            // prevents unbounded tombstone growth
97            while inner.subs.last().is_some_and(|s| s.is_none()) {
98                inner.subs.pop();
99            }
100            true
101        } else {
102            false
103        }
104    }
105
106    /// Subscribe and get a guard that auto-unsubscribes on drop.
107    pub fn subscribe_guard(&self, f: impl Fn(&T) + 'static) -> SubGuard<T> {
108        let id = self.subscribe(f);
109        SubGuard {
110            sig: self.clone(),
111            id,
112        }
113    }
114}
115
116pub fn signal<T>(t: T) -> Signal<T> {
117    Signal::new(t)
118}
119
120/// RAII guard for a Signal subscription; unsubscribes on drop.
121pub struct SubGuard<T: 'static> {
122    sig: crate::Signal<T>,
123    id: SubId,
124}
125impl<T> Drop for SubGuard<T> {
126    fn drop(&mut self) {
127        let _ = self.sig.unsubscribe(self.id);
128    }
129}