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}