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}