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::Signal;
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::Signal;
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::Signal;
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::Signal;
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::Signal;
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 /// Sets the value of the signal.
200 ///
201 /// Returns `true` if the value changed, all dependent memos are
202 /// invalidated and dependent effects were triggered.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// use reactive_cache::Signal;
208 ///
209 /// let signal = Signal::new(5);
210 /// assert_eq!(signal.set(10), true);
211 /// assert_eq!(*signal.get(), 10);
212 ///
213 /// // Setting to the same value returns false
214 /// assert_eq!(signal.set(10), false);
215 /// ```
216 pub fn set(&self, value: T) -> bool
217 where
218 T: Eq,
219 {
220 if *self.value.borrow() == value {
221 return false;
222 }
223
224 self.OnPropertyChanging();
225
226 *self.value.borrow_mut() = value;
227
228 self.OnPropertyChanged();
229
230 true
231 }
232}
233
234impl<T> IObservable for Signal<T> {
235 fn dependents(&self) -> &RefCell<Vec<Weak<dyn IMemo>>> {
236 &self.dependents
237 }
238}