use std::{
any::Any,
cell::RefCell,
rc::{Rc, Weak},
};
use super::{RuntimeHandle, StateHandleLease};
use crate::{
MutableStateInner, ScopeId,
collections::map::HashMap,
state::{MutationPolicy, NeverEqual},
};
struct TypedStateCell<T: Clone + 'static> {
inner: MutableStateInner<T>,
}
trait ScopeWatchCell {
fn unregister_scope(&self, scope_id: ScopeId);
fn forget_scope(&self, scope_id: ScopeId);
fn invalidate_watchers(&self);
}
impl<T: Clone + 'static> ScopeWatchCell for TypedStateCell<T> {
fn unregister_scope(&self, scope_id: ScopeId) {
self.inner.unregister_scope(scope_id);
}
fn forget_scope(&self, scope_id: ScopeId) {
self.inner.forget_scope(scope_id);
}
fn invalidate_watchers(&self) {
self.inner.invalidate_watchers();
}
}
struct StateArenaSlot {
generation: u32,
cell: Option<Rc<dyn Any>>,
watcher_cell: Option<Rc<dyn ScopeWatchCell>>,
lease: Option<Weak<StateHandleLease>>,
}
#[derive(Default)]
struct StateArenaInner {
cells: Vec<StateArenaSlot>,
free: Vec<u32>,
derived: HashMap<u32, Weak<dyn DerivedRefresh>>,
}
pub(crate) trait DerivedRefresh {
fn refresh(&self);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct StateArenaDebugStats {
pub cells_len: usize,
pub cells_cap: usize,
pub free_len: usize,
pub free_cap: usize,
}
#[derive(Default)]
pub(crate) struct StateArena {
inner: RefCell<StateArenaInner>,
}
impl StateArena {
pub(crate) fn alloc<T: Clone + 'static>(&self, value: T, runtime: RuntimeHandle) -> StateId {
self.alloc_with_policy(value, runtime, Rc::new(NeverEqual))
}
fn claim_slot(&self) -> StateId {
let mut inner = self.inner.borrow_mut();
loop {
let Some(slot) = inner.free.pop() else {
let slot = inner.cells.len() as u32;
inner.cells.push(StateArenaSlot {
generation: 0,
cell: None,
watcher_cell: None,
lease: None,
});
return StateId::new(slot, 0);
};
let Some(entry) = inner.cells.get_mut(slot as usize) else {
continue;
};
if entry.cell.is_some() {
continue;
}
entry.watcher_cell = None;
entry.lease = None;
entry.generation = entry.generation.wrapping_add(1);
return StateId::new(slot, entry.generation);
}
}
pub(crate) fn alloc_with_policy<T: Clone + 'static>(
&self,
value: T,
runtime: RuntimeHandle,
policy: Rc<dyn MutationPolicy<T>>,
) -> StateId {
let id = self.claim_slot();
let slot = id.slot();
let inner = MutableStateInner::new_with_policy(value, runtime, policy);
inner.install_snapshot_observer(id);
let typed_cell = Rc::new(TypedStateCell { inner });
let cell: Rc<dyn Any> = Rc::<TypedStateCell<T>>::clone(&typed_cell);
let watcher_cell: Rc<dyn ScopeWatchCell> = typed_cell;
let mut arena = self.inner.borrow_mut();
let slot_entry = &mut arena.cells[slot as usize];
slot_entry.cell = Some(cell);
slot_entry.watcher_cell = Some(watcher_cell);
id
}
pub(crate) fn alloc_plain<T: 'static>(&self, value: T) -> StateId {
let id = self.claim_slot();
let cell: Rc<dyn Any> = Rc::new(value);
self.inner.borrow_mut().cells[id.slot_index()].cell = Some(cell);
id
}
pub(crate) fn plain<T: 'static>(&self, id: StateId) -> Option<Rc<T>> {
Rc::downcast::<T>(self.get_cell_opt(id)?).ok()
}
fn get_cell_opt(&self, id: StateId) -> Option<Rc<dyn Any>> {
self.inner
.borrow()
.cells
.get(id.slot_index())
.filter(|cell| cell.generation == id.generation())
.and_then(|cell| cell.cell.as_ref())
.cloned()
}
fn get_typed<T: Clone + 'static>(&self, id: StateId) -> Rc<TypedStateCell<T>> {
match self.get_cell_opt(id) {
None => panic!(
"state cell missing: slot={}, gen={}, expected={}",
id.slot(),
id.generation(),
std::any::type_name::<T>(),
),
Some(cell) => Rc::downcast::<TypedStateCell<T>>(cell).unwrap_or_else(|_| {
panic!(
"state cell type mismatch: slot={}, gen={}, expected={}",
id.slot(),
id.generation(),
std::any::type_name::<T>(),
)
}),
}
}
fn get_typed_opt<T: Clone + 'static>(&self, id: StateId) -> Option<Rc<TypedStateCell<T>>> {
Rc::downcast::<TypedStateCell<T>>(self.get_cell_opt(id)?).ok()
}
pub(crate) fn with_typed<T: Clone + 'static, R>(
&self,
id: StateId,
f: impl FnOnce(&MutableStateInner<T>) -> R,
) -> R {
let cell = self.get_typed::<T>(id);
f(&cell.inner)
}
pub(crate) fn with_typed_opt<T: Clone + 'static, R>(
&self,
id: StateId,
f: impl FnOnce(&MutableStateInner<T>) -> R,
) -> Option<R> {
let cell = self.get_typed_opt::<T>(id)?;
Some(f(&cell.inner))
}
pub(crate) fn release(&self, id: StateId) {
let cell = {
let mut inner = self.inner.borrow_mut();
let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
return;
};
if slot.generation != id.generation() {
return;
}
slot.lease = None;
slot.watcher_cell = None;
let cell = slot.cell.take();
if cell.is_some() {
inner.free.push(id.slot());
if !inner.derived.is_empty() {
inner.derived.remove(&id.slot());
}
}
cell
};
drop(cell);
}
pub(crate) fn register_derived(&self, id: StateId, refresh: Weak<dyn DerivedRefresh>) {
self.inner.borrow_mut().derived.insert(id.slot(), refresh);
}
#[cold]
pub(crate) fn refresh_derived(&self, id: StateId) {
let refresh = {
let inner = self.inner.borrow();
inner
.cells
.get(id.slot_index())
.filter(|cell| cell.generation == id.generation())
.and_then(|_| inner.derived.get(&id.slot()))
.and_then(Weak::upgrade)
};
if let Some(refresh) = refresh {
refresh.refresh();
}
}
pub(crate) fn stats(&self) -> (usize, usize) {
let inner = self.inner.borrow();
(inner.cells.len(), inner.free.len())
}
pub(crate) fn debug_stats(&self) -> StateArenaDebugStats {
let inner = self.inner.borrow();
StateArenaDebugStats {
cells_len: inner.cells.len(),
cells_cap: inner.cells.capacity(),
free_len: inner.free.len(),
free_cap: inner.free.capacity(),
}
}
pub(crate) fn unregister_scope(&self, id: StateId, scope_id: ScopeId) {
if let Some(watcher_cell) = self.watcher_cell(id) {
watcher_cell.unregister_scope(scope_id);
}
}
pub(crate) fn forget_scope(&self, id: StateId, scope_id: ScopeId) {
if let Some(watcher_cell) = self.watcher_cell(id) {
watcher_cell.forget_scope(scope_id);
}
}
pub(crate) fn invalidate_watchers(&self, id: StateId) {
if let Some(watcher_cell) = self.watcher_cell(id) {
watcher_cell.invalidate_watchers();
}
}
fn watcher_cell(&self, id: StateId) -> Option<Rc<dyn ScopeWatchCell>> {
let inner = self.inner.borrow();
inner
.cells
.get(id.slot_index())
.filter(|slot| slot.generation == id.generation())
.and_then(|slot| slot.watcher_cell.as_ref())
.cloned()
}
pub(crate) fn register_lease(&self, id: StateId, lease: &Rc<StateHandleLease>) {
let mut inner = self.inner.borrow_mut();
let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
return;
};
if slot.generation != id.generation() {
return;
}
slot.lease = Some(Rc::downgrade(lease));
}
pub(crate) fn retain_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
let inner = self.inner.borrow();
let slot = inner.cells.get(id.slot_index())?;
if slot.generation != id.generation() {
return None;
}
slot.lease.as_ref()?.upgrade()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct StateId {
slot: u32,
generation: u32,
}
impl StateId {
const fn new(slot: u32, generation: u32) -> Self {
Self { slot, generation }
}
pub(crate) const fn slot(self) -> u32 {
self.slot
}
pub(crate) const fn slot_index(self) -> usize {
self.slot as usize
}
pub(crate) const fn generation(self) -> u32 {
self.generation
}
}
#[cfg(test)]
#[path = "tests/state_arena_tests.rs"]
mod tests;