Skip to main content

ravenrs_reactive/
observer.rs

1use std::cell::{Cell, RefCell};
2use std::fmt;
3use std::rc::{Rc, Weak};
4
5pub(crate) struct ObserverInner {
6    epoch: Cell<u64>,
7    dirty: Cell<bool>,
8    on_invalidate: Box<dyn Fn()>,
9}
10
11thread_local! {
12    static TRACKING: RefCell<Vec<Rc<ObserverInner>>> = const { RefCell::new(Vec::new()) };
13}
14
15/// Runs a closure while recording which reactive values it reads, and calls
16/// `on_invalidate` (once per run) when any of them changes.
17pub struct Observer {
18    inner: Rc<ObserverInner>,
19}
20
21impl Observer {
22    /// Creates a dirty observer that calls `on_invalidate` when a tracked dependency changes.
23    ///
24    /// ```
25    /// use std::cell::Cell;
26    /// use std::rc::Rc;
27    /// use ravenrs_reactive::{Observer, State};
28    ///
29    /// let name = State::new("Ada".to_string());
30    /// let hits = Rc::new(Cell::new(0));
31    /// let h = hits.clone();
32    /// let observer = Observer::new(move || h.set(h.get() + 1));
33    /// observer.track(|| name.get());
34    /// name.set("Grace".to_string());
35    /// assert_eq!(hits.get(), 1);
36    /// assert!(observer.is_dirty());
37    /// ```
38    pub fn new(on_invalidate: impl Fn() + 'static) -> Self {
39        Self {
40            inner: Rc::new(ObserverInner {
41                epoch: Cell::new(0),
42                dirty: Cell::new(true),
43                on_invalidate: Box::new(on_invalidate),
44            }),
45        }
46    }
47
48    /// Runs `f`, replacing the previous dependency set with what `f` reads.
49    pub fn track<R>(&self, f: impl FnOnce() -> R) -> R {
50        self.inner.epoch.set(self.inner.epoch.get().wrapping_add(1));
51        self.inner.dirty.set(false);
52        TRACKING.with(|t| t.borrow_mut().push(self.inner.clone()));
53        // Pops even if `f` panics, so a caught panic cannot leave a stale observer on the stack.
54        struct Pop;
55        impl Drop for Pop {
56            fn drop(&mut self) {
57                TRACKING.with(|t| t.borrow_mut().pop());
58            }
59        }
60        let _pop = Pop;
61        f()
62    }
63
64    /// Runs `f` without recording dependencies for any observer.
65    pub fn untracked<R>(f: impl FnOnce() -> R) -> R {
66        let saved = TRACKING.with(|t| std::mem::take(&mut *t.borrow_mut()));
67        let result = f();
68        TRACKING.with(|t| *t.borrow_mut() = saved);
69        result
70    }
71
72    /// True until the first `track` and after any dependency changed.
73    pub fn is_dirty(&self) -> bool {
74        self.inner.dirty.get()
75    }
76
77    /// Marks the observer dirty and fires its callback, as if a dependency changed.
78    pub fn invalidate(&self) {
79        self.inner.invalidate();
80    }
81}
82
83impl fmt::Debug for Observer {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.debug_struct("Observer")
86            .field("epoch", &self.inner.epoch.get())
87            .field("dirty", &self.inner.dirty.get())
88            .finish()
89    }
90}
91
92impl ObserverInner {
93    fn invalidate(&self) {
94        if !self.dirty.replace(true) {
95            (self.on_invalidate)();
96        }
97    }
98}
99
100/// The set of observers that read a reactive value during their latest run.
101#[derive(Default)]
102pub(crate) struct Subscribers {
103    list: RefCell<Vec<(Weak<ObserverInner>, u64)>>,
104}
105
106impl Subscribers {
107    /// Subscribes the innermost tracking observer, if any.
108    pub(crate) fn track_read(&self) {
109        let Some(current) = TRACKING.with(|t| t.borrow().last().cloned()) else {
110            return;
111        };
112        let epoch = current.epoch.get();
113        let mut list = self.list.borrow_mut();
114        let already = list.iter().any(|(w, e)| *e == epoch && w.as_ptr() == Rc::as_ptr(&current));
115        if !already {
116            list.push((Rc::downgrade(&current), epoch));
117        }
118    }
119
120    /// Invalidates every live, current subscriber and clears the list. Observers
121    /// re-subscribe the next time they run.
122    pub(crate) fn notify(&self) {
123        let list = std::mem::take(&mut *self.list.borrow_mut());
124        for (weak, epoch) in list {
125            if let Some(observer) = weak.upgrade()
126                && observer.epoch.get() == epoch
127            {
128                observer.invalidate();
129            }
130        }
131    }
132
133    #[cfg(test)]
134    pub(crate) fn len(&self) -> usize {
135        self.list.borrow().len()
136    }
137}