Skip to main content

guinea_core/scope/
state.rs

1use std::any::{Any, TypeId};
2use std::cell::{Ref, RefCell};
3use std::collections::HashMap;
4use std::rc::Rc;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use crate::actor::shape::Declared;
8
9use super::{Scope, ScopeData};
10
11static NEXT_SUBSCRIBER_ID: AtomicU64 = AtomicU64::new(0);
12
13pub struct Subscription {
14    unsubscribe: Option<Box<dyn FnOnce()>>,
15}
16
17impl Subscription {
18    /// A subscription to something that no longer exists - dropping it does
19    /// nothing.
20    pub(crate) fn inert() -> Self {
21        Self { unsubscribe: None }
22    }
23}
24
25impl Drop for Subscription {
26    fn drop(&mut self) {
27        if let Some(unsubscribe) = self.unsubscribe.take() {
28            unsubscribe();
29        }
30    }
31}
32
33pub struct StateHandle<T>(Rc<RefCell<T>>);
34
35impl<T> StateHandle<T> {
36    pub fn borrow(&self) -> Ref<'_, T> {
37        self.0.borrow()
38    }
39}
40
41impl<T> Clone for StateHandle<T> {
42    fn clone(&self) -> Self {
43        Self(self.0.clone())
44    }
45}
46
47impl<T> From<Rc<RefCell<T>>> for StateHandle<T> {
48    fn from(inner: Rc<RefCell<T>>) -> Self {
49        Self(inner)
50    }
51}
52
53/// State, and how it changes.
54///
55/// The type that implements this **is** the state - there is no `type State`,
56/// because the struct is already there and already named. Two items, both
57/// about state; a reducer cannot know who asked, only what changed.
58///
59/// ```ignore
60/// #[derive(Clone, Debug, Default)]
61/// pub struct Processes { pub items: Vec<String> }
62///
63/// pub enum Refreshed { Items(Vec<String>) }
64///
65/// impl Reducer for Processes {
66///     type Update = Refreshed;
67///
68///     fn reduce(&mut self, update: Refreshed) {
69///         match update { Refreshed::Items(items) => self.items = items }
70///     }
71/// }
72/// ```
73///
74/// Usually it is written as a function instead - `#[reducer] fn processes(this:
75/// &mut Processes, update: Refreshed)` - and the attribute writes this impl.
76///
77/// There is no actor here, and there must not be. A reducer knows its own
78/// state and how it changes; naming the actor that happens to drive it would
79/// put the domain's plumbing into the one declaration that is supposed to be
80/// free of it. What relates an action to an actor is
81/// [`Action`](crate::feature::Action), declared where actions already live.
82///
83/// `Clone` because a reader is handed the state as it is now, shared, and a
84/// change made while a reader still holds it goes to a copy.
85pub trait Reducer: Clone + Default + std::fmt::Debug + 'static {
86    /// What changes it. `Clone` because an observer is handed the update
87    /// itself, and `reduce` consumes it - the copy is made only when
88    /// something is actually observing.
89    type Update: Clone + 'static;
90
91    fn reduce(&mut self, update: Self::Update);
92}
93
94/// Where a reducer's state lives: the current value, shared with whoever read
95/// it last.
96pub type Slot<R> = RefCell<Rc<R>>;
97
98pub(super) struct Cell {
99    state: Rc<dyn Any>,
100    listeners: RefCell<Vec<(u64, Rc<dyn Fn()>)>>,
101    observers: RefCell<Vec<(u64, Rc<dyn Fn(&dyn Any)>)>>,
102}
103
104/// What a cell holds, for printing it without knowing the type.
105#[derive(Clone, Copy)]
106pub(super) struct Kind {
107    name: &'static str,
108    describe: fn(&dyn Any) -> String,
109}
110
111impl Kind {
112    fn of<R: Reducer>() -> Self {
113        Self {
114            name: std::any::type_name::<R>(),
115            describe: |state| match state.downcast_ref::<Slot<R>>() {
116                Some(cell) => match cell.try_borrow() {
117                    Ok(state) => format!("{:#?}", **state),
118                    Err(_) => "<being changed>".to_string(),
119                },
120                None => "<unknown>".to_string(),
121            },
122        }
123    }
124}
125
126/// One reducer as devtools see it.
127#[derive(Clone, Debug, PartialEq)]
128pub struct DescribedState {
129    pub type_name: &'static str,
130    pub state: String,
131    /// The feature that claimed it; `None` for the segment's own.
132    pub feature: Option<&'static str>,
133    /// Where it was claimed: the `cx.state::<R>()` that did it.
134    pub declared: Option<Declared>,
135}
136
137impl Cell {
138    fn empty<R: Reducer>() -> Self {
139        Self::holding(Rc::new(Slot::new(Rc::new(R::default()))))
140    }
141
142    fn holding(state: Rc<dyn Any>) -> Self {
143        Cell {
144            state,
145            listeners: RefCell::new(Vec::new()),
146            observers: RefCell::new(Vec::new()),
147        }
148    }
149}
150
151impl Scope {
152    /// Every reducer this scope holds whose type it has seen, printed.
153    pub fn describe_states(&self) -> Vec<DescribedState> {
154        let Some(data) = self.data() else {
155            return Vec::new();
156        };
157        let cells = data.cells.borrow();
158        let kinds = data.kinds.borrow();
159        let owners = data.owners.borrow();
160        let mut described: Vec<DescribedState> = cells
161            .iter()
162            .filter_map(|(type_id, cell)| {
163                let kind = kinds.get(type_id)?;
164                Some(DescribedState {
165                    type_name: kind.name,
166                    state: (kind.describe)(&*cell.state),
167                    feature: owners
168                        .get(type_id)
169                        .and_then(|section| data.section_name(*section)),
170                    declared: data.declarations.borrow().get(type_id).copied(),
171                })
172            })
173            .collect();
174        described.sort_by_key(|state| state.type_name);
175        described
176    }
177
178    /// `R`'s state here, created from `R::default()` on first read.
179    ///
180    /// A removed scope has no state to hand out: what comes back is a fresh
181    /// default nobody else sees.
182    pub fn state<R: Reducer>(&self) -> Rc<Slot<R>> {
183        match self.data() {
184            Some(data) => data.state::<R>(),
185            None => Rc::new(Slot::new(Rc::new(R::default()))),
186        }
187    }
188
189    pub fn peek<R: Reducer>(&self) -> Option<Rc<Slot<R>>> {
190        let data = self.data()?;
191        data.note_kind::<R>();
192        let cells = data.cells.borrow();
193        let cell = cells.get(&TypeId::of::<R>())?;
194        Some(
195            cell.state
196                .clone()
197                .downcast::<Slot<R>>()
198                .expect("Scope cell type mismatch for this TypeId - unreachable, keyed by R"),
199        )
200    }
201
202    /// Sets `R`'s starting value instead of `R::default()`. Call before
203    /// anything else touches `R` in this scope - overwrites any existing cell,
204    /// dropping its listeners.
205    pub fn seed<R: Reducer>(&self, state: R) {
206        let data = self.installing("seeding a reducer");
207        data.note_kind::<R>();
208        let replaced = data
209            .cells
210            .borrow_mut()
211            .insert(TypeId::of::<R>(), Cell::holding(Rc::new(Slot::new(Rc::new(state)))));
212        drop(replaced);
213    }
214
215    /// Applies `msg` to `F`'s state now, and marks the cell so its listeners
216    /// run at the next [`notify::drain`](crate::notify::drain).
217    ///
218    /// The state is current the moment this returns; what waits is the
219    /// redraw. Nothing a listener does - navigating, dropping scopes, sending
220    /// to another actor - happens on the stack of whoever pushed. A no-op once
221    /// the scope is removed.
222    pub fn push<R: Reducer>(&self, update: R::Update) {
223        let Some(data) = self.data() else { return };
224        let carried: Option<Box<dyn Any>> = data
225            .is_observed(TypeId::of::<R>())
226            .then(|| Box::new(update.clone()) as Box<dyn Any>);
227        let state = data.state::<R>();
228        drop(data);
229
230        {
231            let mut state = state.borrow_mut();
232            Rc::make_mut(&mut state).reduce(update);
233        }
234
235        crate::notify::mark(*self, TypeId::of::<R>(), carried);
236    }
237
238    /// Watches *what happened* to `F`, not merely that something did.
239    ///
240    /// A listener from [`subscribe`](Self::subscribe) learns the cell changed
241    /// and reads the new state; an observer is handed the update itself, which
242    /// is what tells a rename apart from a whole new list. Runs during the
243    /// drain, before any listener, so state that depends on this one has
244    /// settled by the time anything draws.
245    pub fn observe<R: Reducer>(&self, callback: impl Fn(&R::Update) + 'static) -> Subscription {
246        let Some(data) = self.data() else {
247            return Subscription::inert();
248        };
249        let id = NEXT_SUBSCRIBER_ID.fetch_add(1, Ordering::Relaxed);
250        {
251            let mut cells = data.cells.borrow_mut();
252            let cell = cells.entry(TypeId::of::<R>()).or_insert_with(Cell::empty::<R>);
253            cell.observers.borrow_mut().push((
254                id,
255                Rc::new(move |update: &dyn Any| {
256                    if let Some(update) = update.downcast_ref::<R::Update>() {
257                        callback(update);
258                    }
259                }),
260            ));
261        }
262
263        let scope = *self;
264        Subscription {
265            unsubscribe: Some(Box::new(move || {
266                let Some(data) = scope.data() else { return };
267                if let Some(cell) = data.cells.borrow_mut().get_mut(&TypeId::of::<R>()) {
268                    cell.observers.borrow_mut().retain(|(oid, _)| *oid != id);
269                }
270            })),
271        }
272    }
273
274    pub(crate) fn listeners_of(&self, cell: TypeId) -> Vec<Rc<dyn Fn()>> {
275        let Some(data) = self.data() else {
276            return Vec::new();
277        };
278        let cells = data.cells.borrow();
279        cells
280            .get(&cell)
281            .map(|cell| cell.listeners.borrow().iter().map(|(_, f)| f.clone()).collect())
282            .unwrap_or_default()
283    }
284
285    pub(crate) fn observers_of(&self, cell: TypeId) -> Vec<Rc<dyn Fn(&dyn Any)>> {
286        let Some(data) = self.data() else {
287            return Vec::new();
288        };
289        let cells = data.cells.borrow();
290        cells
291            .get(&cell)
292            .map(|cell| cell.observers.borrow().iter().map(|(_, f)| f.clone()).collect())
293            .unwrap_or_default()
294    }
295
296    pub fn subscribe<R: Reducer>(&self, callback: impl Fn() + 'static) -> Subscription {
297        let Some(data) = self.data() else {
298            return Subscription::inert();
299        };
300        let id = NEXT_SUBSCRIBER_ID.fetch_add(1, Ordering::Relaxed);
301        {
302            let mut cells = data.cells.borrow_mut();
303            let cell = cells.entry(TypeId::of::<R>()).or_insert_with(Cell::empty::<R>);
304            cell.listeners.borrow_mut().push((id, Rc::new(callback)));
305        }
306
307        let scope = *self;
308        Subscription {
309            unsubscribe: Some(Box::new(move || {
310                let Some(data) = scope.data() else { return };
311                if let Some(cell) = data.cells.borrow_mut().get_mut(&TypeId::of::<R>()) {
312                    cell.listeners.borrow_mut().retain(|(lid, _)| *lid != id);
313                }
314            })),
315        }
316    }
317
318    /// A handle to `R`'s state and actions in this scope, for code that reads
319    /// or watches them without being a UI hook. See [`crate::binding`].
320    pub fn binding<R: Reducer>(&self) -> crate::binding::ReducerBinding<R> {
321        crate::binding::ReducerBinding::new(*self)
322    }
323
324    /// Returns a shallow clone of every reducer state currently held by this
325    /// scope, keyed by the reducer's `TypeId`. Used by the router to cache
326    /// page state in memory while the page is not mounted.
327    pub fn snapshot_states(&self) -> HashMap<TypeId, Rc<dyn Any>> {
328        let Some(data) = self.data() else {
329            return HashMap::new();
330        };
331        data.cells
332            .borrow()
333            .iter()
334            .map(|(type_id, cell)| (*type_id, cell.state.clone()))
335            .collect()
336    }
337
338    /// Pre-populates reducer cells with previously cached states. This is the
339    /// inverse of [`snapshot_states`](Self::snapshot_states): when a page is
340    /// remounted, restoring its state before `install` runs lets the page find
341    /// ready data instead of defaults.
342    pub fn restore_states(&self, states: HashMap<TypeId, Rc<dyn Any>>) {
343        let data = self.installing("restoring state");
344        let mut replaced = Vec::new();
345        {
346            let mut cells = data.cells.borrow_mut();
347            for (type_id, state) in states {
348                replaced.extend(cells.insert(type_id, Cell::holding(state)));
349            }
350        }
351        drop(replaced);
352    }
353}
354
355impl ScopeData {
356    fn note_kind<R: Reducer>(&self) {
357        self.kinds
358            .borrow_mut()
359            .entry(TypeId::of::<R>())
360            .or_insert_with(Kind::of::<R>);
361    }
362
363    fn state<R: Reducer>(&self) -> Rc<Slot<R>> {
364        self.note_kind::<R>();
365        let mut cells = self.cells.borrow_mut();
366        let cell = cells.entry(TypeId::of::<R>()).or_insert_with(Cell::empty::<R>);
367        cell.state
368            .clone()
369            .downcast::<Slot<R>>()
370            .expect("Scope cell type mismatch for this TypeId - unreachable, keyed by R")
371    }
372
373    fn is_observed(&self, cell: TypeId) -> bool {
374        self.cells
375            .borrow()
376            .get(&cell)
377            .is_some_and(|cell| !cell.observers.borrow().is_empty())
378    }
379}