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}