1use std::cell::{Cell, RefCell};
2use std::fmt;
3use std::rc::{Rc, Weak};
4
5use crate::{Lerp, Seconds, Tween};
6
7pub struct Signal<T>(Rc<SignalCell<T>>);
14
15struct SignalCell<T> {
16 name: RefCell<Option<Rc<str>>>,
17 value: RefCell<T>,
18 compute: RefCell<Option<Rc<dyn Fn() -> T>>>,
19 dirty: Cell<bool>,
20 computing: Cell<bool>,
21 subscribers: RefCell<Vec<Weak<dyn Observer>>>,
22}
23
24trait Observer {
25 fn invalidate(&self);
26}
27
28thread_local! {
29 static TRACKING: RefCell<Vec<Rc<dyn Observer>>> = const { RefCell::new(Vec::new()) };
31}
32
33impl<T: 'static> Observer for SignalCell<T> {
34 fn invalidate(&self) {
35 if self.compute.borrow().is_some() && !self.dirty.replace(true) {
36 self.notify();
37 }
38 }
39}
40
41impl<T: 'static> SignalCell<T> {
42 fn notify(&self) {
43 if self.subscribers.borrow().is_empty() {
45 return;
46 }
47 let subs: Vec<_> = {
49 let mut subs = self.subscribers.borrow_mut();
50 subs.retain(|w| w.strong_count() > 0);
51 subs.iter().filter_map(Weak::upgrade).collect()
52 };
53 for s in subs {
54 s.invalidate();
55 }
56 }
57
58 fn display_name(&self) -> String {
59 self.name.borrow().as_deref().map_or_else(
60 || format!("<unnamed {:p}>", self as *const _),
61 str::to_owned,
62 )
63 }
64}
65
66impl<T: Clone + 'static> Signal<T> {
67 pub fn new(value: T) -> Self {
69 Self(Rc::new(SignalCell {
70 name: RefCell::new(None),
71 value: RefCell::new(value),
72 compute: RefCell::new(None),
73 dirty: Cell::new(false),
74 computing: Cell::new(false),
75 subscribers: RefCell::new(Vec::new()),
76 }))
77 }
78
79 pub fn derived(f: impl Fn() -> T + 'static) -> Self
81 where
82 T: Default,
83 {
84 let s = Self::new(T::default());
85 s.bind(f);
86 s
87 }
88
89 pub fn with_name(self, name: impl Into<Rc<str>>) -> Self {
91 *self.0.name.borrow_mut() = Some(name.into());
92 self
93 }
94
95 pub fn name(&self) -> Option<Rc<str>> {
97 self.0.name.borrow().clone()
98 }
99
100 pub fn get(&self) -> T {
107 self.track();
108 let cell = &self.0;
109 if cell.dirty.get() {
110 let compute = cell.compute.borrow().clone();
111 if let Some(f) = compute {
112 if cell.computing.get() {
113 if cfg!(debug_assertions) {
114 panic!("signal dependency cycle through `{}`", cell.display_name());
115 }
116 return cell.value.borrow().clone();
117 }
118 cell.computing.set(true);
119 let observer: Rc<dyn Observer> = self.0.clone();
120 TRACKING.with(|t| t.borrow_mut().push(observer));
121 let value = f();
122 TRACKING.with(|t| t.borrow_mut().pop());
123 cell.computing.set(false);
124 *cell.value.borrow_mut() = value;
125 }
126 cell.dirty.set(false);
127 }
128 cell.value.borrow().clone()
129 }
130
131 pub fn peek(&self) -> T {
133 let saved = TRACKING.with(|t| std::mem::take(&mut *t.borrow_mut()));
134 let v = self.get();
135 TRACKING.with(|t| *t.borrow_mut() = saved);
136 v
137 }
138
139 pub fn set(&self, value: impl Into<T>) {
142 *self.0.compute.borrow_mut() = None;
143 self.0.dirty.set(false);
144 *self.0.value.borrow_mut() = value.into();
145 self.0.notify();
146 }
147
148 pub fn bind(&self, f: impl Fn() -> T + 'static) {
150 *self.0.compute.borrow_mut() = Some(Rc::new(f));
151 self.0.dirty.set(true);
152 self.0.notify();
153 }
154
155 pub fn is_bound(&self) -> bool {
157 self.0.compute.borrow().is_some()
158 }
159
160 pub fn to(&self, target: impl Into<T>, duration: impl Seconds) -> Tween<T>
164 where
165 T: Lerp,
166 {
167 Tween::new(self.clone(), target.into(), duration.seconds())
168 }
169
170 fn track(&self) {
171 TRACKING.with(|t| {
172 let t = t.borrow();
173 let Some(observer) = t.last() else { return };
174 let mut subs = self.0.subscribers.borrow_mut();
175 let already = subs
176 .iter()
177 .any(|w| std::ptr::addr_eq(w.as_ptr(), Rc::as_ptr(observer)));
178 if !already {
179 subs.push(Rc::downgrade(observer));
180 }
181 });
182 }
183}
184
185impl<T> Clone for Signal<T> {
186 fn clone(&self) -> Self {
187 Self(self.0.clone())
188 }
189}
190
191impl<T: fmt::Debug> fmt::Debug for Signal<T> {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 f.debug_struct("Signal")
194 .field("name", &self.0.name.borrow())
195 .field("value", &self.0.value.borrow())
196 .field("bound", &self.0.compute.borrow().is_some())
197 .finish()
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn set_and_get() {
207 let s = Signal::new(1.0);
208 s.set(2.0);
209 assert_eq!(s.get(), 2.0);
210 }
211
212 #[test]
213 fn derived_is_lazy_and_updates() {
214 let runs = Rc::new(Cell::new(0));
215 let a = Signal::new(2.0);
216 let b = {
217 let (a, runs) = (a.clone(), runs.clone());
218 Signal::derived(move || {
219 runs.set(runs.get() + 1);
220 a.get() * 10.0
221 })
222 };
223 assert_eq!(runs.get(), 0, "not computed before first get");
224 assert_eq!(b.get(), 20.0);
225 assert_eq!(b.get(), 20.0);
226 assert_eq!(runs.get(), 1, "cached while clean");
227 a.set(3.0);
228 a.set(4.0);
229 assert_eq!(runs.get(), 1, "writes only mark dirty");
230 assert_eq!(b.get(), 40.0);
231 assert_eq!(runs.get(), 2);
232 }
233
234 #[test]
235 fn chains_propagate() {
236 let a = Signal::new(1.0);
237 let b = {
238 let a = a.clone();
239 Signal::derived(move || a.get() + 1.0)
240 };
241 let c = {
242 let b = b.clone();
243 Signal::derived(move || b.get() * 2.0)
244 };
245 assert_eq!(c.get(), 4.0);
246 a.set(5.0);
247 assert_eq!(c.get(), 12.0);
248 }
249
250 #[test]
251 fn set_unbinds() {
252 let a = Signal::new(1.0);
253 let b = Signal::new(0.0);
254 let a2 = a.clone();
255 b.bind(move || a2.get());
256 assert_eq!(b.get(), 1.0);
257 b.set(7.0);
258 a.set(3.0);
259 assert_eq!(b.get(), 7.0);
260 assert!(!b.is_bound());
261 }
262
263 #[test]
264 #[cfg(debug_assertions)]
265 #[should_panic(expected = "signal dependency cycle through `loop-a`")]
266 fn cycle_panics_with_name() {
267 let a = Signal::new(0.0).with_name("loop-a");
268 let b = Signal::new(0.0).with_name("loop-b");
269 let (a2, b2) = (a.clone(), b.clone());
270 a.bind(move || b2.get() + 1.0);
271 b.bind(move || a2.get() + 1.0);
272 a.get();
273 }
274}