Skip to main content

cranpose_core/runtime/
state_arena.rs

1use std::{
2    any::Any,
3    cell::RefCell,
4    rc::{Rc, Weak},
5};
6
7use super::{RuntimeHandle, StateHandleLease};
8use crate::{
9    MutableStateInner, ScopeId,
10    collections::map::HashMap,
11    state::{MutationPolicy, NeverEqual},
12};
13
14struct TypedStateCell<T: Clone + 'static> {
15    inner: MutableStateInner<T>,
16}
17
18trait ScopeWatchCell {
19    fn unregister_scope(&self, scope_id: ScopeId);
20    fn forget_scope(&self, scope_id: ScopeId);
21    fn invalidate_watchers(&self);
22}
23
24impl<T: Clone + 'static> ScopeWatchCell for TypedStateCell<T> {
25    fn unregister_scope(&self, scope_id: ScopeId) {
26        self.inner.unregister_scope(scope_id);
27    }
28
29    fn forget_scope(&self, scope_id: ScopeId) {
30        self.inner.forget_scope(scope_id);
31    }
32
33    fn invalidate_watchers(&self) {
34        self.inner.invalidate_watchers();
35    }
36}
37
38struct StateArenaSlot {
39    generation: u32,
40    cell: Option<Rc<dyn Any>>,
41    watcher_cell: Option<Rc<dyn ScopeWatchCell>>,
42    lease: Option<Weak<StateHandleLease>>,
43}
44
45#[derive(Default)]
46struct StateArenaInner {
47    cells: Vec<StateArenaSlot>,
48    free: Vec<u32>,
49    /// The derived states among the cells, by slot.
50    derived: HashMap<u32, Weak<dyn DerivedRefresh>>,
51}
52
53/// A derived state that brings its value up to date before a read.
54pub(crate) trait DerivedRefresh {
55    /// Derives the value again when a state it read changed since the
56    /// recomposition that last derived it.
57    fn refresh(&self);
58}
59
60/// Sizes of a runtime's state arena, for leak and memory checks. A `_len` field counts entries and
61/// a `_cap` field the capacity reserved.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63pub struct StateArenaDebugStats {
64    /// State cells, both in use and free.
65    pub cells_len: usize,
66    /// Capacity reserved for the state cells.
67    pub cells_cap: usize,
68    /// State cells free for reuse.
69    pub free_len: usize,
70    /// Capacity reserved for the free list.
71    pub free_cap: usize,
72}
73
74#[derive(Default)]
75pub(crate) struct StateArena {
76    inner: RefCell<StateArenaInner>,
77}
78
79impl StateArena {
80    pub(crate) fn alloc<T: Clone + 'static>(&self, value: T, runtime: RuntimeHandle) -> StateId {
81        self.alloc_with_policy(value, runtime, Rc::new(NeverEqual))
82    }
83
84    /// A free cell's id, for a new value to take.
85    fn claim_slot(&self) -> StateId {
86        let mut inner = self.inner.borrow_mut();
87        loop {
88            let Some(slot) = inner.free.pop() else {
89                let slot = inner.cells.len() as u32;
90                inner.cells.push(StateArenaSlot {
91                    generation: 0,
92                    cell: None,
93                    watcher_cell: None,
94                    lease: None,
95                });
96                return StateId::new(slot, 0);
97            };
98
99            let Some(entry) = inner.cells.get_mut(slot as usize) else {
100                continue;
101            };
102            if entry.cell.is_some() {
103                continue;
104            }
105
106            entry.watcher_cell = None;
107            entry.lease = None;
108            entry.generation = entry.generation.wrapping_add(1);
109            return StateId::new(slot, entry.generation);
110        }
111    }
112
113    pub(crate) fn alloc_with_policy<T: Clone + 'static>(
114        &self,
115        value: T,
116        runtime: RuntimeHandle,
117        policy: Rc<dyn MutationPolicy<T>>,
118    ) -> StateId {
119        let id = self.claim_slot();
120        let slot = id.slot();
121        let inner = MutableStateInner::new_with_policy(value, runtime, policy);
122        inner.install_snapshot_observer(id);
123        let typed_cell = Rc::new(TypedStateCell { inner });
124        let cell: Rc<dyn Any> = Rc::<TypedStateCell<T>>::clone(&typed_cell);
125        let watcher_cell: Rc<dyn ScopeWatchCell> = typed_cell;
126        let mut arena = self.inner.borrow_mut();
127        let slot_entry = &mut arena.cells[slot as usize];
128        slot_entry.cell = Some(cell);
129        slot_entry.watcher_cell = Some(watcher_cell);
130        id
131    }
132
133    /// Stores `value` in a cell of its own, which keeps no snapshot records
134    /// and no readers: see [`crate::RuntimeCell`].
135    pub(crate) fn alloc_plain<T: 'static>(&self, value: T) -> StateId {
136        let id = self.claim_slot();
137        let cell: Rc<dyn Any> = Rc::new(value);
138        self.inner.borrow_mut().cells[id.slot_index()].cell = Some(cell);
139        id
140    }
141
142    /// The value [`Self::alloc_plain`] stored for `id`, while it lives.
143    pub(crate) fn plain<T: 'static>(&self, id: StateId) -> Option<Rc<T>> {
144        Rc::downcast::<T>(self.get_cell_opt(id)?).ok()
145    }
146
147    fn get_cell_opt(&self, id: StateId) -> Option<Rc<dyn Any>> {
148        self.inner
149            .borrow()
150            .cells
151            .get(id.slot_index())
152            .filter(|cell| cell.generation == id.generation())
153            .and_then(|cell| cell.cell.as_ref())
154            .cloned()
155    }
156
157    fn get_typed<T: Clone + 'static>(&self, id: StateId) -> Rc<TypedStateCell<T>> {
158        match self.get_cell_opt(id) {
159            None => panic!(
160                "state cell missing: slot={}, gen={}, expected={}",
161                id.slot(),
162                id.generation(),
163                std::any::type_name::<T>(),
164            ),
165            Some(cell) => Rc::downcast::<TypedStateCell<T>>(cell).unwrap_or_else(|_| {
166                panic!(
167                    "state cell type mismatch: slot={}, gen={}, expected={}",
168                    id.slot(),
169                    id.generation(),
170                    std::any::type_name::<T>(),
171                )
172            }),
173        }
174    }
175
176    fn get_typed_opt<T: Clone + 'static>(&self, id: StateId) -> Option<Rc<TypedStateCell<T>>> {
177        Rc::downcast::<TypedStateCell<T>>(self.get_cell_opt(id)?).ok()
178    }
179
180    pub(crate) fn with_typed<T: Clone + 'static, R>(
181        &self,
182        id: StateId,
183        f: impl FnOnce(&MutableStateInner<T>) -> R,
184    ) -> R {
185        let cell = self.get_typed::<T>(id);
186        f(&cell.inner)
187    }
188
189    pub(crate) fn with_typed_opt<T: Clone + 'static, R>(
190        &self,
191        id: StateId,
192        f: impl FnOnce(&MutableStateInner<T>) -> R,
193    ) -> Option<R> {
194        let cell = self.get_typed_opt::<T>(id)?;
195        Some(f(&cell.inner))
196    }
197
198    pub(crate) fn release(&self, id: StateId) {
199        let cell = {
200            let mut inner = self.inner.borrow_mut();
201            let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
202                return;
203            };
204            if slot.generation != id.generation() {
205                return;
206            }
207            slot.lease = None;
208            slot.watcher_cell = None;
209            let cell = slot.cell.take();
210            if cell.is_some() {
211                inner.free.push(id.slot());
212                if !inner.derived.is_empty() {
213                    inner.derived.remove(&id.slot());
214                }
215            }
216            cell
217        };
218        drop(cell);
219    }
220
221    /// Makes `refresh` what brings the derived state `id` up to date before
222    /// each read, until the state is released.
223    pub(crate) fn register_derived(&self, id: StateId, refresh: Weak<dyn DerivedRefresh>) {
224        self.inner.borrow_mut().derived.insert(id.slot(), refresh);
225    }
226
227    /// Brings the derived state `id` up to date before a read; see
228    /// [`DerivedRefresh`].
229    #[cold]
230    pub(crate) fn refresh_derived(&self, id: StateId) {
231        let refresh = {
232            let inner = self.inner.borrow();
233            inner
234                .cells
235                .get(id.slot_index())
236                .filter(|cell| cell.generation == id.generation())
237                .and_then(|_| inner.derived.get(&id.slot()))
238                .and_then(Weak::upgrade)
239        };
240        if let Some(refresh) = refresh {
241            refresh.refresh();
242        }
243    }
244
245    pub(crate) fn stats(&self) -> (usize, usize) {
246        let inner = self.inner.borrow();
247        (inner.cells.len(), inner.free.len())
248    }
249
250    pub(crate) fn debug_stats(&self) -> StateArenaDebugStats {
251        let inner = self.inner.borrow();
252        StateArenaDebugStats {
253            cells_len: inner.cells.len(),
254            cells_cap: inner.cells.capacity(),
255            free_len: inner.free.len(),
256            free_cap: inner.free.capacity(),
257        }
258    }
259
260    pub(crate) fn unregister_scope(&self, id: StateId, scope_id: ScopeId) {
261        if let Some(watcher_cell) = self.watcher_cell(id) {
262            watcher_cell.unregister_scope(scope_id);
263        }
264    }
265
266    /// Stops the state `id` from recomposing the live scope `scope_id`.
267    pub(crate) fn forget_scope(&self, id: StateId, scope_id: ScopeId) {
268        if let Some(watcher_cell) = self.watcher_cell(id) {
269            watcher_cell.forget_scope(scope_id);
270        }
271    }
272
273    // Invalidates the scopes that read the state `id`, while it lives.
274    pub(crate) fn invalidate_watchers(&self, id: StateId) {
275        if let Some(watcher_cell) = self.watcher_cell(id) {
276            watcher_cell.invalidate_watchers();
277        }
278    }
279
280    fn watcher_cell(&self, id: StateId) -> Option<Rc<dyn ScopeWatchCell>> {
281        let inner = self.inner.borrow();
282        inner
283            .cells
284            .get(id.slot_index())
285            .filter(|slot| slot.generation == id.generation())
286            .and_then(|slot| slot.watcher_cell.as_ref())
287            .cloned()
288    }
289
290    pub(crate) fn register_lease(&self, id: StateId, lease: &Rc<StateHandleLease>) {
291        let mut inner = self.inner.borrow_mut();
292        let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
293            return;
294        };
295        if slot.generation != id.generation() {
296            return;
297        }
298        slot.lease = Some(Rc::downgrade(lease));
299    }
300
301    pub(crate) fn retain_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
302        let inner = self.inner.borrow();
303        let slot = inner.cells.get(id.slot_index())?;
304        if slot.generation != id.generation() {
305            return None;
306        }
307        slot.lease.as_ref()?.upgrade()
308    }
309}
310
311/// Names one state cell of a runtime. A generation count makes the id of a freed cell stale.
312#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
313pub struct StateId {
314    slot: u32,
315    generation: u32,
316}
317
318impl StateId {
319    const fn new(slot: u32, generation: u32) -> Self {
320        Self { slot, generation }
321    }
322
323    pub(crate) const fn slot(self) -> u32 {
324        self.slot
325    }
326
327    pub(crate) const fn slot_index(self) -> usize {
328        self.slot as usize
329    }
330
331    pub(crate) const fn generation(self) -> u32 {
332        self.generation
333    }
334}
335
336#[cfg(test)]
337#[path = "tests/state_arena_tests.rs"]
338mod tests;