Skip to main content

ravenrs_reactive/
state.rs

1use std::cell::{Ref, RefCell};
2use std::fmt;
3use std::rc::Rc;
4
5use crate::Binding;
6use crate::observer::Subscribers;
7
8struct StateInner<T> {
9    value: RefCell<T>,
10    subscribers: Subscribers,
11}
12
13/// A mutable value that notifies the observers that read it. Cloning a `State`
14/// clones the handle, not the value: all clones share one value.
15pub struct State<T> {
16    inner: Rc<StateInner<T>>,
17}
18
19impl<T: 'static> State<T> {
20    /// Creates a state holding `value`.
21    pub fn new(value: T) -> Self {
22        Self {
23            inner: Rc::new(StateInner { value: RefCell::new(value), subscribers: Subscribers::default() }),
24        }
25    }
26
27    /// Returns a clone of the value and records the read.
28    pub fn get(&self) -> T
29    where
30        T: Clone,
31    {
32        self.with(T::clone)
33    }
34
35    /// Reads the value by reference and records the read.
36    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
37        self.inner.subscribers.track_read();
38        f(&self.inner.value.borrow())
39    }
40
41    /// Borrows the value without recording a dependency.
42    pub fn peek(&self) -> Ref<'_, T> {
43        self.inner.value.borrow()
44    }
45
46    /// Replaces the value and notifies dependents.
47    pub fn set(&self, value: T) {
48        *self.inner.value.borrow_mut() = value;
49        self.inner.subscribers.notify();
50    }
51
52    /// Replaces the value only if it differs, notifying only on change.
53    pub fn set_if_changed(&self, value: T)
54    where
55        T: PartialEq,
56    {
57        let changed = *self.inner.value.borrow() != value;
58        if changed {
59            self.set(value);
60        }
61    }
62
63    /// Mutates the value in place and notifies dependents.
64    pub fn update(&self, f: impl FnOnce(&mut T)) {
65        f(&mut self.inner.value.borrow_mut());
66        self.inner.subscribers.notify();
67    }
68
69    /// A read/write view of this state for child components.
70    pub fn binding(&self) -> Binding<T>
71    where
72        T: Clone,
73    {
74        let (read, write) = (self.clone(), self.clone());
75        Binding::new(move || read.get(), move |v| write.set(v))
76    }
77
78    #[cfg(test)]
79    pub(crate) fn subscriber_count(&self) -> usize {
80        self.inner.subscribers.len()
81    }
82}
83
84impl<T> Clone for State<T> {
85    fn clone(&self) -> Self {
86        Self { inner: self.inner.clone() }
87    }
88}
89
90impl<T: fmt::Debug> fmt::Debug for State<T> {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self.inner.value.try_borrow() {
93            Ok(v) => f.debug_tuple("State").field(&*v).finish(),
94            Err(_) => f.write_str("State(<borrowed>)"),
95        }
96    }
97}
98
99impl<T: Default + 'static> Default for State<T> {
100    fn default() -> Self {
101        Self::new(T::default())
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use std::cell::Cell;
108
109    use super::*;
110    use crate::Observer;
111
112    fn counting_observer() -> (Observer, Rc<Cell<u32>>) {
113        let hits = Rc::new(Cell::new(0));
114        let h = hits.clone();
115        (Observer::new(move || h.set(h.get() + 1)), hits)
116    }
117
118    #[test]
119    fn notifies_only_observers_that_read() {
120        let a = State::new(1);
121        let b = State::new(10);
122        let (obs, hits) = counting_observer();
123        obs.track(|| a.get());
124        b.set(11);
125        assert_eq!(hits.get(), 0, "b was never read");
126        a.set(2);
127        assert_eq!(hits.get(), 1);
128        assert!(obs.is_dirty());
129    }
130
131    #[test]
132    fn callback_fires_once_until_retracked() {
133        let a = State::new(0);
134        let (obs, hits) = counting_observer();
135        obs.track(|| a.get());
136        a.set(1);
137        a.set(2);
138        assert_eq!(hits.get(), 1);
139        obs.track(|| a.get());
140        a.update(|v| *v += 1);
141        assert_eq!(hits.get(), 2);
142    }
143
144    #[test]
145    fn dependencies_are_replaced_on_each_run() {
146        let a = State::new(0);
147        let b = State::new(0);
148        let (obs, hits) = counting_observer();
149        obs.track(|| a.get());
150        obs.track(|| b.get());
151        a.set(5);
152        assert_eq!(hits.get(), 0, "a is no longer a dependency");
153        b.set(5);
154        assert_eq!(hits.get(), 1);
155    }
156
157    #[test]
158    fn set_if_changed_skips_equal_values() {
159        let a = State::new(3);
160        let (obs, hits) = counting_observer();
161        obs.track(|| a.get());
162        a.set_if_changed(3);
163        assert_eq!(hits.get(), 0);
164        a.set_if_changed(4);
165        assert_eq!(hits.get(), 1);
166    }
167
168    #[test]
169    fn dropped_observers_are_pruned() {
170        let a = State::new(0);
171        {
172            let (obs, _) = counting_observer();
173            obs.track(|| a.get());
174            assert_eq!(a.subscriber_count(), 1);
175        }
176        a.set(1);
177        assert_eq!(a.subscriber_count(), 0);
178    }
179
180    #[test]
181    fn reads_are_deduplicated_and_untracked_reads_ignored() {
182        let a = State::new(0);
183        let (obs, hits) = counting_observer();
184        obs.track(|| {
185            a.get();
186            a.get();
187            Observer::untracked(|| a.get());
188        });
189        assert_eq!(a.subscriber_count(), 1);
190        a.set(1);
191        assert_eq!(hits.get(), 1);
192    }
193}