1use std::cell::{Cell, RefCell};
2use std::fmt;
3use std::rc::{Rc, Weak};
4
5use crate::{Lerp, 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: T) {
141 *self.0.compute.borrow_mut() = None;
142 self.0.dirty.set(false);
143 *self.0.value.borrow_mut() = value;
144 self.0.notify();
145 }
146
147 pub fn bind(&self, f: impl Fn() -> T + 'static) {
149 *self.0.compute.borrow_mut() = Some(Rc::new(f));
150 self.0.dirty.set(true);
151 self.0.notify();
152 }
153
154 pub fn is_bound(&self) -> bool {
156 self.0.compute.borrow().is_some()
157 }
158
159 pub fn to(&self, target: T, duration: f64) -> Tween<T>
161 where
162 T: Lerp,
163 {
164 Tween::new(self.clone(), target, duration)
165 }
166
167 fn track(&self) {
168 TRACKING.with(|t| {
169 let t = t.borrow();
170 let Some(observer) = t.last() else { return };
171 let mut subs = self.0.subscribers.borrow_mut();
172 let already = subs
173 .iter()
174 .any(|w| std::ptr::addr_eq(w.as_ptr(), Rc::as_ptr(observer)));
175 if !already {
176 subs.push(Rc::downgrade(observer));
177 }
178 });
179 }
180}
181
182impl<T> Clone for Signal<T> {
183 fn clone(&self) -> Self {
184 Self(self.0.clone())
185 }
186}
187
188impl<T: fmt::Debug> fmt::Debug for Signal<T> {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 f.debug_struct("Signal")
191 .field("name", &self.0.name.borrow())
192 .field("value", &self.0.value.borrow())
193 .field("bound", &self.0.compute.borrow().is_some())
194 .finish()
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn set_and_get() {
204 let s = Signal::new(1.0);
205 s.set(2.0);
206 assert_eq!(s.get(), 2.0);
207 }
208
209 #[test]
210 fn derived_is_lazy_and_updates() {
211 let runs = Rc::new(Cell::new(0));
212 let a = Signal::new(2.0);
213 let b = {
214 let (a, runs) = (a.clone(), runs.clone());
215 Signal::derived(move || {
216 runs.set(runs.get() + 1);
217 a.get() * 10.0
218 })
219 };
220 assert_eq!(runs.get(), 0, "not computed before first get");
221 assert_eq!(b.get(), 20.0);
222 assert_eq!(b.get(), 20.0);
223 assert_eq!(runs.get(), 1, "cached while clean");
224 a.set(3.0);
225 a.set(4.0);
226 assert_eq!(runs.get(), 1, "writes only mark dirty");
227 assert_eq!(b.get(), 40.0);
228 assert_eq!(runs.get(), 2);
229 }
230
231 #[test]
232 fn chains_propagate() {
233 let a = Signal::new(1.0);
234 let b = {
235 let a = a.clone();
236 Signal::derived(move || a.get() + 1.0)
237 };
238 let c = {
239 let b = b.clone();
240 Signal::derived(move || b.get() * 2.0)
241 };
242 assert_eq!(c.get(), 4.0);
243 a.set(5.0);
244 assert_eq!(c.get(), 12.0);
245 }
246
247 #[test]
248 fn set_unbinds() {
249 let a = Signal::new(1.0);
250 let b = Signal::new(0.0);
251 let a2 = a.clone();
252 b.bind(move || a2.get());
253 assert_eq!(b.get(), 1.0);
254 b.set(7.0);
255 a.set(3.0);
256 assert_eq!(b.get(), 7.0);
257 assert!(!b.is_bound());
258 }
259
260 #[test]
261 #[cfg(debug_assertions)]
262 #[should_panic(expected = "signal dependency cycle through `loop-a`")]
263 fn cycle_panics_with_name() {
264 let a = Signal::new(0.0).with_name("loop-a");
265 let b = Signal::new(0.0).with_name("loop-b");
266 let (a2, b2) = (a.clone(), b.clone());
267 a.bind(move || b2.get() + 1.0);
268 b.bind(move || a2.get() + 1.0);
269 a.get();
270 }
271}