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