1use std::cell::RefCell;
2use std::rc::Rc;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5use crate::reactive;
6
7pub type SubId = usize;
8
9type SubCallback<T> = Rc<dyn Fn(&T)>;
11
12static NEXT_SIGNAL_ID: AtomicUsize = AtomicUsize::new(1);
13
14pub struct Signal<T: 'static>(Rc<RefCell<Inner<T>>>);
15
16impl<T> Clone for Signal<T> {
17 fn clone(&self) -> Self {
18 Self(self.0.clone())
19 }
20}
21
22struct Inner<T> {
23 id: usize,
24 value: T,
25 subs: Vec<Option<SubCallback<T>>>,
26 free_list: Vec<SubId>,
27}
28
29impl<T> Signal<T> {
30 pub fn new(value: T) -> Self {
31 let id = NEXT_SIGNAL_ID.fetch_add(1, Ordering::Relaxed);
32 Self(Rc::new(RefCell::new(Inner {
33 id,
34 value,
35 subs: Vec::new(),
36 free_list: Vec::new(),
37 })))
38 }
39
40 pub fn id(&self) -> usize {
41 self.0.borrow().id
42 }
43
44 pub fn get(&self) -> T
45 where
46 T: Clone,
47 {
48 let inner = self.0.borrow();
49 reactive::register_signal_read(inner.id);
50 inner.value.clone()
51 }
52
53 pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
56 let inner = self.0.borrow();
57 reactive::register_signal_read(inner.id);
58 f(&inner.value)
59 }
60
61 pub fn set_neq(&self, v: T)
64 where
65 T: PartialEq + Clone,
66 {
67 let id = {
68 let mut inner = self.0.borrow_mut();
69 if inner.value == v {
70 return;
71 }
72 inner.value = v;
73 inner.id
74 };
75 self.notify_and_request_frame(id);
76 }
77
78 pub fn set(&self, v: T)
82 where
83 T: Clone,
84 {
85 let id = {
86 let mut inner = self.0.borrow_mut();
87 inner.value = v;
88 inner.id
89 };
90 self.notify_and_request_frame(id);
91 }
92
93 pub fn update<F: FnOnce(&mut T)>(&self, f: F)
94 where
95 T: Clone,
96 {
97 let id = {
98 let mut inner = self.0.borrow_mut();
99 f(&mut inner.value);
100 inner.id
101 };
102 self.notify_and_request_frame(id);
103 }
104
105 fn notify_and_request_frame(&self, id: usize)
106 where
107 T: Clone,
108 {
109 let (cbs, snapshot): (Vec<SubCallback<T>>, T) = {
110 let inner = match self.0.try_borrow() {
111 Ok(b) => b,
112 Err(_) => {
113 log::warn!("Signal notify: inner already borrowed, skipping notify");
114 reactive::signal_changed(id);
115 crate::signal_fired();
116 crate::request_frame();
117 return;
118 }
119 };
120 let cbs = inner
121 .subs
122 .iter()
123 .filter_map(|s| s.clone())
124 .collect::<Vec<_>>();
125 let snapshot = inner.value.clone();
126 (cbs, snapshot)
127 };
128 reactive::without_observer(|| {
129 for cb in cbs {
130 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(&snapshot)));
131 if let Err(e) = res {
132 let msg = e
133 .downcast_ref::<String>()
134 .map(|s| s.as_str())
135 .or_else(|| e.downcast_ref::<&str>().copied())
136 .unwrap_or("unknown");
137 log::error!("Signal subscriber panicked: {msg}");
138 }
139 }
140 });
141
142 reactive::signal_changed(id);
143 crate::signal_fired();
144 crate::request_frame();
145 }
146
147 pub fn subscribe(&self, f: impl Fn(&T) + 'static) -> SubId {
148 let mut inner = self.0.borrow_mut();
149 if let Some(free_id) = inner.free_list.pop() {
150 inner.subs[free_id] = Some(Rc::new(f));
151 free_id
152 } else {
153 inner.subs.push(Some(Rc::new(f)));
154 inner.subs.len() - 1
155 }
156 }
157
158 pub fn unsubscribe(&self, id: SubId) -> bool {
160 let mut inner = self.0.borrow_mut();
161 if id < inner.subs.len() && inner.subs[id].is_some() {
162 inner.subs[id] = None;
163 inner.free_list.push(id);
164 while inner.subs.last().is_some_and(|s| s.is_none()) {
165 let popped = inner.subs.len() - 1;
166 inner.subs.pop();
167 if let Some(pos) = inner.free_list.iter().position(|&x| x == popped) {
169 inner.free_list.swap_remove(pos);
170 }
171 }
172 true
173 } else {
174 false
175 }
176 }
177
178 pub fn subscribe_guard(&self, f: impl Fn(&T) + 'static) -> SubGuard<T> {
180 let id = self.subscribe(f);
181 SubGuard {
182 sig: self.clone(),
183 id,
184 }
185 }
186}
187
188pub fn signal<T>(t: T) -> Signal<T> {
189 Signal::new(t)
190}
191
192pub struct SubGuard<T: 'static> {
194 sig: crate::Signal<T>,
195 id: SubId,
196}
197impl<T> Drop for SubGuard<T> {
198 fn drop(&mut self) {
199 let _ = self.sig.unsubscribe(self.id);
200 }
201}