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/// # Examples
25///
26/// ## Basic usage
27/// ```
28/// use std::rc::Rc;
29/// use reactive_cache::Signal;
30///
31/// let signal = Signal::new(10);
32/// assert_eq!(*signal.get(), 10);
33///
34/// signal.set(20);
35/// assert_eq!(*signal.get(), 20);
36/// ```
37///
38/// ## Using inside a struct
39/// ```
40/// use std::rc::Rc;
41/// use reactive_cache::Signal;
42///
43/// struct ViewModel {
44/// counter: Rc<Signal<i32>>,
45/// name: Rc<Signal<String>>,
46/// }
47///
48/// let vm = ViewModel {
49/// counter: Signal::new(0).into(),
50/// name: Signal::new("Alice".to_string()).into(),
51/// };
52///
53/// assert_eq!(*vm.counter.get(), 0);
54/// assert_eq!(*vm.name.get(), "Alice");
55///
56/// vm.counter.set(1);
57/// vm.name.set("Bob".into());
58///
59/// assert_eq!(*vm.counter.get(), 1);
60/// assert_eq!(*vm.name.get(), "Bob");
61/// ```
62pub struct Signal<T> {
63 value: RefCell<T>,
64 dependents: RefCell<Vec<Weak<dyn IMemo>>>,
65 effects: RefCell<Vec<Weak<Effect>>>,
66}
67
68impl<T> Signal<T> {
69 /// Re-runs all dependent effects that are still alive.
70 ///
71 /// This is triggered after the signal's value has changed.
72 /// Dead effects (already dropped) are cleaned up automatically.
73 fn flush_effects(&self) {
74 // When triggering an Effect, dependencies are not collected for that Effect.
75 self.effects.borrow_mut().retain(|w| {
76 if let Some(e) = w.upgrade() {
77 crate::effect::run_untracked(&e);
78 true
79 } else {
80 false
81 }
82 });
83 }
84
85 /// Called after the value is updated.
86 /// Triggers all dependent effects.
87 #[allow(non_snake_case)]
88 fn OnPropertyChanged(&self) {
89 self.flush_effects()
90 }
91
92 /// Called before the value is updated.
93 /// Invalidates all memoized computations depending on this signal.
94 #[allow(non_snake_case)]
95 fn OnPropertyChanging(&self) {
96 self.invalidate()
97 }
98
99 /// Creates a new `Signal` with the given initial value.
100 ///
101 /// # Examples
102 ///
103 /// Basic usage:
104 /// ```
105 /// use std::rc::Rc;
106 /// use reactive_cache::Signal;
107 ///
108 /// let signal = Signal::new(10);
109 /// assert_eq!(*signal.get(), 10);
110 /// ```
111 ///
112 /// Using inside a struct:
113 /// ```
114 /// use std::rc::Rc;
115 /// use reactive_cache::Signal;
116 ///
117 /// struct ViewModel {
118 /// counter: Rc<Signal<i32>>,
119 /// name: Rc<Signal<String>>,
120 /// }
121 ///
122 /// let vm = ViewModel {
123 /// counter: Signal::new(0),
124 /// name: Signal::new("Alice".to_string()),
125 /// };
126 ///
127 /// assert_eq!(*vm.counter.get(), 0);
128 /// assert_eq!(*vm.name.get(), "Alice");
129 ///
130 /// // Update values
131 /// assert!(vm.counter.set(1));
132 /// assert!(vm.name.set("Bob".into()));
133 ///
134 /// assert_eq!(*vm.counter.get(), 1);
135 /// assert_eq!(*vm.name.get(), "Bob");
136 /// ```
137 pub fn new(value: T) -> Rc<Self>
138 {
139 Signal {
140 value: value.into(),
141 dependents: vec![].into(),
142 effects: vec![].into(),
143 }
144 .into()
145 }
146
147 /// Gets a reference to the current value, tracking dependencies
148 /// and effects if inside a reactive context.
149 ///
150 /// # Examples
151 ///
152 /// ```
153 /// use reactive_cache::Signal;
154 ///
155 /// let signal = Signal::new(42);
156 /// assert_eq!(*signal.get(), 42);
157 /// ```
158 pub fn get(&self) -> Ref<'_, T> {
159 self.dependency_collection();
160
161 // Track effects in the call stack
162 if let Some(EffectStackEntry {
163 effect: e,
164 collecting,
165 }) = crate::effect_stack::effect_peak()
166 && *collecting
167 && !self.effects.borrow().iter().any(|w| Weak::ptr_eq(w, e))
168 {
169 self.effects.borrow_mut().push(e.clone());
170 }
171
172 self.value.borrow()
173 }
174
175 /// Sets the value of the signal.
176 ///
177 /// Returns `true` if the value changed, all dependent memos are
178 /// invalidated and dependent effects were triggered.
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// use reactive_cache::Signal;
184 ///
185 /// let signal = Signal::new(5);
186 /// assert_eq!(signal.set(10), true);
187 /// assert_eq!(*signal.get(), 10);
188 ///
189 /// // Setting to the same value returns false
190 /// assert_eq!(signal.set(10), false);
191 /// ```
192 pub fn set(&self, value: T) -> bool
193 where
194 T: Eq,
195 {
196 if *self.value.borrow() == value {
197 return false;
198 }
199
200 self.OnPropertyChanging();
201
202 *self.value.borrow_mut() = value;
203
204 self.OnPropertyChanged();
205
206 true
207 }
208}
209
210impl<T> IObservable for Signal<T> {
211 fn dependents(&self) -> &RefCell<Vec<Weak<dyn IMemo>>> {
212 &self.dependents
213 }
214}