reactive_cache/memo.rs
1use std::cell::RefCell;
2
3use crate::{Observable, call_stack, remove_from_cache, store_in_cache, touch};
4
5/// A memoized reactive computation that caches its result and tracks dependencies.
6///
7/// `Memo<T, F>` behaves similarly to a computed property: it stores the result of a closure
8/// and only recomputes when its dependencies change. Other signals or effects that access
9/// the memo will automatically be tracked.
10///
11/// In short:
12/// - Like a computed property: returns a cached value derived from other signals.
13/// - Adds tracking: recomputes only when dependencies are invalidated.
14///
15/// # Type Parameters
16///
17/// - `T`: The result type of the computation. Must implement `Clone`.
18/// - `F`: The closure type that computes the value. Must implement `Fn() -> T`.
19pub struct Memo<T, F>
20where
21    F: Fn() -> T,
22{
23    f: F,
24    dependents: RefCell<Vec<&'static dyn Observable>>,
25}
26
27impl<T, F> Observable for Memo<T, F>
28where
29    F: Fn() -> T,
30{
31    fn invalidate(&'static self) {
32        remove_from_cache(self);
33        self.dependents.borrow().iter().for_each(|d| d.invalidate());
34    }
35}
36
37impl<T, F> Memo<T, F>
38where
39    F: Fn() -> T,
40{
41    /// Creates a new `Memo` wrapping the provided closure.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use reactive_cache::Memo;
47    ///
48    /// let memo = Memo::new(|| 10);
49    /// ```
50    pub fn new(f: F) -> Self {
51        Memo {
52            f,
53            dependents: vec![].into(),
54        }
55    }
56
57    /// Returns the memoized value, recomputing it only if necessary.
58    ///
59    /// During the computation, dependencies are tracked for reactive updates.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use once_cell::unsync::Lazy;
65    /// use reactive_cache::Memo;
66    ///
67    /// static mut MEMO: Lazy<Memo<i32, fn() -> i32>> = Lazy::new(|| Memo::new(|| 5));
68    /// assert_eq!(unsafe { (*MEMO).get() }, 5);
69    /// ```
70    pub fn get(&'static self) -> T
71    where
72        T: Clone,
73    {
74        if let Some(last) = call_stack::last()
75            && !self
76                .dependents
77                .borrow()
78                .iter()
79                .any(|d| std::ptr::eq(*d, *last))
80        {
81            self.dependents.borrow_mut().push(*last);
82        }
83
84        call_stack::push(self);
85
86        let rc = if let Some(rc) = touch(self) {
87            rc
88        } else {
89            let result: T = (self.f)();
90            store_in_cache(self, result)
91        };
92
93        call_stack::pop();
94
95        (*rc).clone()
96    }
97}