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}