reactive_cache/signal.rs
1use std::{
2 cell::{Ref, RefCell},
3 rc::{Rc, Weak},
4};
5
6use crate::{Effect, IMemo, IObservable, effect_stack::EffectStackEntry};
7
8/// A reactive signal that holds a value, tracks dependencies, and triggers effects.
9///
10/// `Signal<T>` behaves similarly to a traditional "Property" (getter/setter),
11/// but on top of that, it automatically tracks which reactive computations
12/// or effects access it. When its value changes, all dependent effects
13/// are automatically re-run.
14///
15/// In short:
16/// - Like a Property: provides `get()` and `set()` for accessing and updating the value.
17/// - Adds tracking: automatically records dependencies when read inside reactive contexts,
18/// and automatically triggers dependent `Effect`s when updated.
19///
20/// # Type Parameters
21///
22/// - `T`: The type of the value stored in the signal. Must implement `Eq`.
23///
24/// # Memory Management Note
25///
26/// When referencing `Signal` instances that belong to other struct instances
27/// (for example, when one `ViewModel` holds references to signals in another `ViewModel`),
28/// you **must** store them as `Weak<Signal<T>>` obtained via `Rc::downgrade` instead of
29/// cloning a strong `Rc`. Failing to do so can create reference cycles between the structs
30/// and their dependent effects, preventing proper cleanup and causing memory leaks.
31///
32/// # Examples
33///
34/// ## Basic usage
35/// ```
36/// use std::rc::Rc;
37/// use reactive_cache::prelude::*;
38///
39/// let signal = Signal::new(10);
40/// assert_eq!(*signal.get(), 10);
41///
42/// signal.set(20);
43/// assert_eq!(*signal.get(), 20);
44/// ```
45///
46/// ## Using inside a struct
47/// ```
48/// use std::rc::Rc;
49/// use reactive_cache::prelude::*;
50///
51/// struct ViewModel {
52/// counter: Rc<Signal<i32>>,
53/// name: Rc<Signal<String>>,
54/// }
55///
56/// let vm = ViewModel {
57/// counter: Signal::new(0).into(),
58/// name: Signal::new("Alice".to_string()).into(),
59/// };
60///
61/// assert_eq!(*vm.counter.get(), 0);
62/// assert_eq!(*vm.name.get(), "Alice");
63///
64/// vm.counter.set(1);
65/// vm.name.set("Bob".into());
66///
67/// assert_eq!(*vm.counter.get(), 1);
68/// assert_eq!(*vm.name.get(), "Bob");
69/// ```
70pub struct Signal<T> {
71 /// Current value of the signal.
72 value: RefCell<T>,
73
74 /// Memoized computations that depend on this signal.
75 /// Weak references are used to avoid memory leaks.
76 dependents: RefCell<Vec<Weak<dyn IMemo>>>,
77
78 /// Effects that depend on this signal.
79 /// Weak references prevent retaining dropped effects.
80 effects: RefCell<Vec<Weak<Effect>>>,
81}
82
83impl<T: Default> Default for Signal<T> {
84 fn default() -> Self {
85 Self {
86 value: Default::default(),
87 dependents: Default::default(),
88 effects: Default::default(),
89 }
90 }
91}
92
93impl<T> Signal<T> {
94 /// Re-runs all dependent effects that are still alive.
95 ///
96 /// This is triggered after the signal's value has changed.
97 /// Dead effects (already dropped) are cleaned up automatically.
98 fn flush_effects(&self) {
99 // When triggering an Effect, dependencies are not collected for that Effect.
100 self.effects.borrow_mut().retain(|w| {
101 if let Some(e) = w.upgrade() {
102 crate::effect::run_untracked(&e);
103 true
104 } else {
105 false
106 }
107 });
108 }
109
110 /// Called after the value is updated.
111 /// Triggers all dependent effects.
112 #[allow(non_snake_case)]
113 fn OnPropertyChanged(&self) {
114 self.flush_effects()
115 }
116
117 /// Called before the value is updated.
118 /// Invalidates all memoized computations depending on this signal.
119 #[allow(non_snake_case)]
120 fn OnPropertyChanging(&self) {
121 self.invalidate()
122 }
123
124 /// Creates a new `Signal` with the given initial value.
125 ///
126 /// # Examples
127 ///
128 /// Basic usage:
129 /// ```
130 /// use std::rc::Rc;
131 /// use reactive_cache::prelude::*;
132 ///
133 /// let signal = Signal::new(10);
134 /// assert_eq!(*signal.get(), 10);
135 /// ```
136 ///
137 /// Using inside a struct:
138 /// ```
139 /// use std::rc::Rc;
140 /// use reactive_cache::prelude::*;
141 ///
142 /// struct ViewModel {
143 /// counter: Rc<Signal<i32>>,
144 /// name: Rc<Signal<String>>,
145 /// }
146 ///
147 /// let vm = ViewModel {
148 /// counter: Signal::new(0),
149 /// name: Signal::new("Alice".to_string()),
150 /// };
151 ///
152 /// assert_eq!(*vm.counter.get(), 0);
153 /// assert_eq!(*vm.name.get(), "Alice");
154 ///
155 /// // Update values
156 /// assert!(vm.counter.set(1));
157 /// assert!(vm.name.set("Bob".into()));
158 ///
159 /// assert_eq!(*vm.counter.get(), 1);
160 /// assert_eq!(*vm.name.get(), "Bob");
161 /// ```
162 pub fn new(value: T) -> Rc<Self> {
163 Signal {
164 value: value.into(),
165 dependents: vec![].into(),
166 effects: vec![].into(),
167 }
168 .into()
169 }
170
171 /// Gets a reference to the current value, tracking dependencies
172 /// and effects if inside a reactive context.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// use reactive_cache::prelude::*;
178 ///
179 /// let signal = Signal::new(42);
180 /// assert_eq!(*signal.get(), 42);
181 /// ```
182 pub fn get(&self) -> Ref<'_, T> {
183 self.dependency_collection();
184
185 // Track effects in the call stack
186 if let Some(EffectStackEntry {
187 effect: e,
188 collecting,
189 }) = crate::effect_stack::effect_peak()
190 && *collecting
191 && !self.effects.borrow().iter().any(|w| Weak::ptr_eq(w, e))
192 {
193 self.effects.borrow_mut().push(e.clone());
194 }
195
196 self.value.borrow()
197 }
198}
199
200pub trait SignalSetter<T> {
201 fn set(&self, value: T) -> bool;
202}
203
204impl<T> SignalSetter<T> for Signal<T> {
205 /// Sets the value of the signal.
206 ///
207 /// For generic types `T` that do not support comparison, they are treated as
208 /// always changing, so the value is always set and `true` is always returned.
209 /// All dependent memos are invalidated and dependent effects were triggered.
210 ///
211 /// # Examples
212 ///
213 /// ```
214 /// use reactive_cache::prelude::*;
215 ///
216 /// #[derive(Debug)]
217 /// struct Num(i32);
218 ///
219 /// let signal = Signal::new(Num(5));
220 /// assert_eq!(signal.set(Num(10)), true);
221 /// assert_eq!(signal.get().0, 10);
222 ///
223 /// // Setting to the same value always return true and trigger all effects.
224 /// assert_eq!(signal.set(Num(10)), true);
225 /// ```
226 default fn set(&self, value: T) -> bool {
227 self.OnPropertyChanging();
228
229 *self.value.borrow_mut() = value;
230
231 self.OnPropertyChanged();
232
233 true
234 }
235}
236
237impl<T: Eq> SignalSetter<T> for Signal<T> {
238 /// Sets the value of the signal.
239 ///
240 /// Returns `true` if the value changed, all dependent memos are
241 /// invalidated and dependent effects were triggered.
242 ///
243 /// # Examples
244 ///
245 /// ```
246 /// use reactive_cache::prelude::*;
247 ///
248 /// let signal = Signal::new(5);
249 /// assert_eq!(signal.set(10), true);
250 /// assert_eq!(*signal.get(), 10);
251 ///
252 /// // Setting to the same value returns false
253 /// assert_eq!(signal.set(10), false);
254 /// ```
255 fn set(&self, value: T) -> bool {
256 if *self.value.borrow() == value {
257 return false;
258 }
259
260 self.OnPropertyChanging();
261
262 *self.value.borrow_mut() = value;
263
264 self.OnPropertyChanged();
265
266 true
267 }
268}
269
270impl<T> IObservable for Signal<T> {
271 fn dependents(&self) -> &RefCell<Vec<Weak<dyn IMemo>>> {
272 &self.dependents
273 }
274}