ravenrs_reactive/
derived.rs1use 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
15pub struct Derived<T> {
19 inner: Rc<DerivedInner<T>>,
20}
21
22impl<T: Clone + 'static> Derived<T> {
23 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 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 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}