Skip to main content

ravenrs_reactive/
derived.rs

1use std::cell::RefCell;
2use std::fmt;
3use std::rc::{Rc, Weak};
4
5use crate::Observer;
6use crate::observer::Subscribers;
7
8struct DerivedInner<T> {
9    compute: Box<dyn Fn() -> T>,
10    cache: RefCell<Option<T>>,
11    observer: Observer,
12    subscribers: Subscribers,
13}
14
15/// A cached value computed from other reactive values. It recomputes lazily, on
16/// the first read after a dependency changed, and notifies its own readers when
17/// that happens.
18pub struct Derived<T> {
19    inner: Rc<DerivedInner<T>>,
20}
21
22impl<T: Clone + 'static> Derived<T> {
23    /// Creates a derived value from `compute`, which runs on the first read.
24    ///
25    /// ```
26    /// use ravenrs_reactive::{Derived, State};
27    ///
28    /// let count = State::new(2);
29    /// let c = count.clone();
30    /// let doubled = Derived::new(move || c.get() * 2);
31    /// assert_eq!(doubled.get(), 4);
32    /// count.set(5);
33    /// assert_eq!(doubled.get(), 10);
34    /// ```
35    pub fn new(compute: impl Fn() -> T + 'static) -> Self {
36        let inner = Rc::new_cyclic(|weak: &Weak<DerivedInner<T>>| {
37            let weak = weak.clone();
38            DerivedInner {
39                compute: Box::new(compute),
40                cache: RefCell::new(None),
41                observer: Observer::new(move || {
42                    if let Some(inner) = weak.upgrade() {
43                        inner.subscribers.notify();
44                    }
45                }),
46                subscribers: Subscribers::default(),
47            }
48        });
49        Self { inner }
50    }
51
52    /// Returns the cached value, recomputing it first if a dependency changed, and records the read.
53    pub fn get(&self) -> T {
54        self.inner.subscribers.track_read();
55        if self.inner.observer.is_dirty() || self.inner.cache.borrow().is_none() {
56            let value = self.inner.observer.track(|| (self.inner.compute)());
57            *self.inner.cache.borrow_mut() = Some(value.clone());
58            return value;
59        }
60        match &*self.inner.cache.borrow() {
61            Some(v) => v.clone(),
62            // Unreachable: the branch above fills the cache when it is empty.
63            None => (self.inner.compute)(),
64        }
65    }
66}
67
68impl<T> Clone for Derived<T> {
69    fn clone(&self) -> Self {
70        Self { inner: self.inner.clone() }
71    }
72}
73
74impl<T> fmt::Debug for Derived<T> {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.debug_struct("Derived").field("dirty", &self.inner.observer.is_dirty()).finish()
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use std::cell::Cell;
83    use std::rc::Rc;
84
85    use crate::{Derived, Observer, State};
86
87    #[test]
88    fn recomputes_lazily_and_caches() {
89        let runs = Rc::new(Cell::new(0));
90        let count = State::new(2);
91        let (r, c) = (runs.clone(), count.clone());
92        let doubled = Derived::new(move || {
93            r.set(r.get() + 1);
94            c.get() * 2
95        });
96        assert_eq!(doubled.get(), 4);
97        assert_eq!(doubled.get(), 4);
98        assert_eq!(runs.get(), 1);
99        count.set(5);
100        assert_eq!(runs.get(), 1, "not recomputed until read");
101        assert_eq!(doubled.get(), 10);
102        assert_eq!(runs.get(), 2);
103    }
104
105    #[test]
106    fn propagates_to_its_readers() {
107        let count = State::new(1);
108        let c = count.clone();
109        let label = Derived::new(move || format!("Clicks: {}", c.get()));
110        let hits = Rc::new(Cell::new(0));
111        let h = hits.clone();
112        let ui = Observer::new(move || h.set(h.get() + 1));
113        ui.track(|| label.get());
114        count.set(2);
115        assert_eq!(hits.get(), 1);
116        assert_eq!(ui.track(|| label.get()), "Clicks: 2");
117    }
118}