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    /// Read the current value without cloning it, tracking the read in the
49    /// reactive graph. Prefer over `get` for large/expensive-to-clone types.
50    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
51        let inner = self.0.borrow();
52        reactive::register_signal_read(inner.id);
53        f(&inner.value)
54    }
55
56    /// Set the signal value only if it changed, skipping subscribers, the
57    /// reactive graph, and the frame request when the value is unchanged.
58    pub fn set_neq(&self, v: T)
59    where
60        T: PartialEq,
61    {
62        let id = {
63            let mut inner = self.0.borrow_mut();
64            if inner.value == v {
65                return;
66            }
67            inner.value = v;
68            inner.id
69        };
70        self.notify_and_request_frame(id);
71    }
72
73    /// Set the signal value and notify subscribers + the reactive graph.
74    pub fn set(&self, v: T) {
75        let id = {
76            let mut inner = self.0.borrow_mut();
77            inner.value = v;
78            inner.id
79        };
80        self.notify_and_request_frame(id);
81    }
82
83    pub fn update<F: FnOnce(&mut T)>(&self, f: F) {
84        let id = {
85            let mut inner = self.0.borrow_mut();
86            f(&mut inner.value);
87            inner.id
88        };
89        self.notify_and_request_frame(id);
90    }
91
92    fn notify_and_request_frame(&self, id: usize) {
93        // Call subscribers with CURRENT_OBSERVER cleared so subscriber reads
94        // don't register edges against the wrong observer.
95        reactive::without_observer(|| {
96            let inner = self.0.borrow();
97            let vref = &inner.value;
98            for s in &inner.subs {
99                if let Some(cb) = s.as_ref() {
100                    cb(vref);
101                }
102            }
103        });
104
105        reactive::signal_changed(id);
106        crate::signal_fired();
107        crate::request_frame();
108    }
109
110    pub fn subscribe(&self, f: impl Fn(&T) + 'static) -> SubId {
111        let mut inner = self.0.borrow_mut();
112        inner.subs.push(Some(Box::new(f)));
113        inner.subs.len() - 1
114    }
115
116    /// Remove a subscriber by id. Returns true if removed.
117    pub fn unsubscribe(&self, id: SubId) -> bool {
118        let mut inner = self.0.borrow_mut();
119        if id < inner.subs.len() {
120            inner.subs[id] = None;
121            // prevents unbounded tombstone growth
122            while inner.subs.last().is_some_and(|s| s.is_none()) {
123                inner.subs.pop();
124            }
125            true
126        } else {
127            false
128        }
129    }
130
131    /// Subscribe and get a guard that auto-unsubscribes on drop.
132    pub fn subscribe_guard(&self, f: impl Fn(&T) + 'static) -> SubGuard<T> {
133        let id = self.subscribe(f);
134        SubGuard {
135            sig: self.clone(),
136            id,
137        }
138    }
139}
140
141pub fn signal<T>(t: T) -> Signal<T> {
142    Signal::new(t)
143}
144
145/// RAII guard for a Signal subscription; unsubscribes on drop.
146pub struct SubGuard<T: 'static> {
147    sig: crate::Signal<T>,
148    id: SubId,
149}
150impl<T> Drop for SubGuard<T> {
151    fn drop(&mut self) {
152        let _ = self.sig.unsubscribe(self.id);
153    }
154}