use std::any::{Any, TypeId};
use std::num::NonZeroU32;
use std::rc::Rc;
use super::invalidation::clear_signal_subscribers;
use super::runtime::{EffectId, SignalId, with_runtime};
use super::state::with_reactive;
use super::storage::dispose_signal;
type Declarations = Vec<(TypeId, Rc<dyn Any>)>;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct OwnerId {
index: u32,
generation: NonZeroU32,
}
struct Owner {
parent: Option<OwnerId>,
signals: Vec<SignalId>,
effects: Vec<EffectId>,
cleanups: Vec<Box<dyn FnOnce()>>,
children: Vec<OwnerId>,
contexts: Option<Box<Declarations>>,
}
impl Owner {
fn new(parent: Option<OwnerId>) -> Self {
Self {
parent,
signals: Vec::new(),
effects: Vec::new(),
cleanups: Vec::new(),
children: Vec::new(),
contexts: None,
}
}
}
struct OwnerSlot {
owner: Option<Owner>,
generation: NonZeroU32,
}
#[derive(Default)]
pub(crate) struct OwnerArena {
slots: Vec<OwnerSlot>,
free_indices: Vec<u32>,
}
impl OwnerArena {
fn allocate(&mut self, parent: Option<OwnerId>) -> OwnerId {
let owner = Owner::new(parent);
if let Some(index) = self.free_indices.pop() {
let slot = &mut self.slots[index as usize];
slot.generation = slot.generation.checked_add(1).unwrap_or(NonZeroU32::MIN);
slot.owner = Some(owner);
OwnerId {
index,
generation: slot.generation,
}
} else {
let index = self.slots.len() as u32;
self.slots.push(OwnerSlot {
owner: Some(owner),
generation: NonZeroU32::MIN,
});
OwnerId {
index,
generation: NonZeroU32::MIN,
}
}
}
fn get(&self, id: OwnerId) -> Option<&Owner> {
self.slots
.get(id.index as usize)
.filter(|slot| slot.generation == id.generation)
.and_then(|slot| slot.owner.as_ref())
}
fn get_mut(&mut self, id: OwnerId) -> Option<&mut Owner> {
self.slots
.get_mut(id.index as usize)
.filter(|slot| slot.generation == id.generation)
.and_then(|slot| slot.owner.as_mut())
}
fn take(&mut self, id: OwnerId) -> Option<Owner> {
let slot = self
.slots
.get_mut(id.index as usize)
.filter(|slot| slot.generation == id.generation)?;
let owner = slot.owner.take();
if owner.is_some() {
self.free_indices.push(id.index);
}
owner
}
}
pub(crate) fn create_root_owner() -> OwnerId {
with_reactive(|reactive| {
let id = reactive.owners.borrow_mut().allocate(None);
reactive.current_owner.set(Some(id));
reactive.root_owner.set(Some(id));
id
})
}
pub(crate) fn with_root_owner<T>(f: impl FnOnce() -> T) -> T {
match with_reactive(|reactive| reactive.root_owner.get()) {
Some(root) => under_owner(root, f),
None => f(),
}
}
pub(crate) fn under_owner<T>(owner_id: OwnerId, f: impl FnOnce() -> T) -> T {
let previous = with_reactive(|reactive| reactive.current_owner.replace(Some(owner_id)));
let _guard = crate::reactive::guard::defer(move || {
with_reactive(|reactive| reactive.current_owner.set(previous));
});
f()
}
pub(crate) fn under_scope<T>(scope: Option<OwnerId>, f: impl FnOnce() -> T) -> T {
match scope {
Some(scope) if current_owner() != Some(scope) => under_owner(scope, f),
_ => f(),
}
}
pub fn with_owner<T>(f: impl FnOnce() -> T) -> (T, OwnerId) {
let owner_id = with_reactive(|reactive| {
let parent_id = reactive.current_owner.get();
let mut owners = reactive.owners.borrow_mut();
let id = owners.allocate(parent_id);
if let Some(parent_id) = parent_id
&& let Some(parent_owner) = owners.get_mut(parent_id)
{
parent_owner.children.push(id);
}
id
});
(under_owner(owner_id, f), owner_id)
}
pub(crate) fn with_scope_declarations<R>(f: impl FnOnce(&mut Declarations) -> R) -> Option<R> {
with_reactive(|reactive| {
let id = reactive.current_owner.get()?;
let mut owners = reactive.owners.borrow_mut();
let owner = owners.get_mut(id)?;
Some(f(owner.contexts.get_or_insert_default()))
})
}
pub(crate) fn nearest_declaration(type_id: TypeId) -> Option<Rc<dyn Any>> {
with_reactive(|reactive| {
let mut scope = reactive.current_owner.get();
let owners = reactive.owners.borrow();
while let Some(id) = scope {
let owner = owners.get(id)?;
let declared = owner
.contexts
.as_deref()
.and_then(|declarations| declarations.iter().find(|(d, _)| *d == type_id));
if let Some((_, value)) = declared {
return Some(Rc::clone(value));
}
scope = owner.parent;
}
None
})
}
pub fn current_owner() -> Option<OwnerId> {
with_reactive(|reactive| reactive.current_owner.get())
}
fn with_current_owner(f: impl FnOnce(&mut Owner)) {
with_reactive(|reactive| {
let Some(id) = reactive.current_owner.get() else {
return;
};
if let Some(owner) = reactive.owners.borrow_mut().get_mut(id) {
f(owner);
}
});
}
#[doc(hidden)]
pub fn dispose_owner_now(id: OwnerId) {
let owner = with_reactive(|reactive| {
let mut arena = reactive.owners.borrow_mut();
let owner = arena.take(id)?;
if let Some(parent_id) = owner.parent
&& let Some(parent) = arena.get_mut(parent_id)
&& let Some(pos) = parent.children.iter().position(|c| *c == id)
{
parent.children.swap_remove(pos);
}
Some(owner)
});
let Some(owner) = owner else {
return; };
for child_id in owner.children {
dispose_owner_now(child_id);
}
for cleanup in owner.cleanups.into_iter().rev() {
crate::reactive::diagnostics::snapshot_zone(cleanup);
}
for effect_id in owner.effects {
with_runtime(|rt| rt.dispose_effect(effect_id));
}
for signal_id in owner.signals {
clear_signal_subscribers(signal_id);
with_runtime(|rt| rt.dispose_signal_subscriptions(signal_id));
dispose_signal(signal_id);
}
}
pub fn on_cleanup(f: impl FnOnce() + 'static) {
with_current_owner(|owner| owner.cleanups.push(Box::new(f)));
}
pub(crate) fn register_signal(id: SignalId) {
with_current_owner(|owner| owner.signals.push(id));
}
pub(crate) fn register_effect(id: EffectId) {
with_current_owner(|owner| owner.effects.push(id));
}
#[cfg(test)]
pub(crate) fn effect_has_owner(id: EffectId) -> bool {
crate::reactive::runtime::with_runtime(|rt| rt.effect_scope(id)).is_some()
}
pub fn dispose_owner(id: OwnerId) {
with_reactive(|reactive| reactive.pending_disposals.push(id));
}
pub(crate) fn flush_pending_disposals() {
loop {
let batch = with_reactive(|reactive| reactive.pending_disposals.drain());
if batch.is_empty() {
return;
}
for id in batch {
dispose_owner_now(id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_unwinding_closure_puts_the_previous_owner_back() {
let hushed = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let outer = create_root_owner();
assert_eq!(current_owner(), Some(outer));
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
with_owner(|| panic!("a builder that gave up"));
}));
assert!(panicked.is_err());
assert_eq!(
current_owner(),
Some(outer),
"a new scope that unwound left itself current"
);
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
with_root_owner(|| panic!("a global initialiser that gave up"))
}));
assert!(panicked.is_err());
assert_eq!(
current_owner(),
Some(outer),
"the root stayed current after an unwind, and would have swallowed \
every scope opened afterwards"
);
std::panic::set_hook(hushed);
crate::reactive::state::reset();
}
#[test]
fn test_with_owner_basic() {
let (value, owner_id) = with_owner(|| 42);
assert_eq!(value, 42);
dispose_owner_now(owner_id);
}
#[test]
fn test_dispose_owner_defers_disposal() {
let cleaned = std::rc::Rc::new(std::cell::Cell::new(false));
let flag = cleaned.clone();
let (_, owner_id) = with_owner(move || {
on_cleanup(move || flag.set(true));
});
dispose_owner(owner_id);
dispose_owner(owner_id); assert!(
!cleaned.get(),
"public dispose_owner must not dispose synchronously"
);
flush_pending_disposals();
assert!(cleaned.get(), "flush must dispose the pending owner");
dispose_owner(owner_id);
flush_pending_disposals();
}
#[test]
fn test_dispose_during_flush_is_processed() {
let cleaned_b = std::rc::Rc::new(std::cell::Cell::new(false));
let flag_b = cleaned_b.clone();
let (_, owner_b) = with_owner(move || {
on_cleanup(move || flag_b.set(true));
});
let (_, owner_a) = with_owner(move || {
on_cleanup(move || dispose_owner(owner_b));
});
dispose_owner(owner_a);
flush_pending_disposals();
assert!(
cleaned_b.get(),
"an owner disposed during the flush must be handled in the same flush"
);
}
#[test]
fn test_stale_owner_id_cannot_alias_recycled_slot() {
let (_, first) = with_owner(|| ());
dispose_owner_now(first);
let disposed = std::rc::Rc::new(std::cell::Cell::new(false));
let flag = disposed.clone();
let (_, second) = with_owner(move || {
on_cleanup(move || flag.set(true));
});
assert_eq!(first.index, second.index, "slot should be recycled");
assert_ne!(first.generation, second.generation);
dispose_owner_now(first);
assert!(!disposed.get(), "stale OwnerId disposed the new owner");
dispose_owner_now(second);
assert!(disposed.get());
}
#[test]
fn test_dispose_prunes_parent_children_list() {
let (child_ids, parent_id) = with_owner(|| {
(0..4)
.map(|_| with_owner(|| ()).1)
.collect::<Vec<OwnerId>>()
});
for child in &child_ids {
dispose_owner_now(*child);
}
with_reactive(|reactive| {
let mut arena = reactive.owners.borrow_mut();
let parent = arena.get_mut(parent_id).expect("parent still live");
assert!(
parent.children.is_empty(),
"disposed children not pruned: {:?}",
parent.children
);
});
dispose_owner_now(parent_id);
}
#[test]
fn test_current_owner_inside_scope() {
let ((inner_owner, outer_owner), _outer_id) = with_owner(|| {
let outer = current_owner();
let (inner, _inner_id) = with_owner(current_owner);
(inner, outer)
});
assert!(inner_owner.is_some());
assert!(outer_owner.is_some());
assert_ne!(inner_owner, outer_owner);
}
#[test]
fn test_current_owner_outside_scope() {
assert!(current_owner().is_none());
}
#[test]
fn test_nested_owners() {
let cleanup_order = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let order = cleanup_order.clone();
let (_, outer_id) = with_owner(|| {
let order_inner = order.clone();
on_cleanup(move || {
order_inner.lock().unwrap().push("outer");
});
let order_nested = order.clone();
with_owner(|| {
on_cleanup(move || {
order_nested.lock().unwrap().push("inner");
});
});
});
dispose_owner_now(outer_id);
let order = cleanup_order.lock().unwrap();
assert_eq!(*order, vec!["inner", "outer"]);
}
#[test]
fn test_on_cleanup_reverse_order() {
let cleanup_order = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let order = cleanup_order.clone();
let (_, owner_id) = with_owner(|| {
let order1 = order.clone();
on_cleanup(move || {
order1.lock().unwrap().push("first");
});
let order2 = order.clone();
on_cleanup(move || {
order2.lock().unwrap().push("second");
});
let order3 = order.clone();
on_cleanup(move || {
order3.lock().unwrap().push("third");
});
});
dispose_owner_now(owner_id);
let order = cleanup_order.lock().unwrap();
assert_eq!(*order, vec!["third", "second", "first"]);
}
#[test]
fn test_dispose_owner_twice_is_safe() {
let (_, owner_id) = with_owner(|| {});
dispose_owner_now(owner_id);
dispose_owner_now(owner_id);
}
#[test]
fn test_effect_registration_and_reverse_mapping() {
use super::super::effect::create_effect_id;
use super::super::signal::create_signal;
let effect_ran = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let effect_ran_clone = effect_ran.clone();
let (effect_id, owner_id) = with_owner(|| {
let signal = create_signal(0);
create_effect_id(move || {
let _ = signal.get();
effect_ran_clone.store(true, std::sync::atomic::Ordering::SeqCst);
})
});
assert!(
effect_has_owner(effect_id),
"Effect should be owned after registration"
);
dispose_owner_now(owner_id);
assert!(
!effect_has_owner(effect_id),
"Effect should not be owned after disposal"
);
}
#[test]
fn test_signal_registration() {
use super::super::signal::create_signal;
let (signal, owner_id) = with_owner(|| create_signal(42));
assert_eq!(signal.get(), 42);
dispose_owner_now(owner_id);
}
#[test]
fn test_multiple_effects_registration() {
use super::super::effect::create_effect_id;
use super::super::signal::create_signal;
let (effect_ids, owner_id) = with_owner(|| {
let signal = create_signal(0);
let e1 = create_effect_id(move || {
let _ = signal.get();
});
let e2 = create_effect_id(move || {
let _ = signal.get();
});
let e3 = create_effect_id(move || {
let _ = signal.get();
});
(e1, e2, e3)
});
assert!(effect_has_owner(effect_ids.0));
assert!(effect_has_owner(effect_ids.1));
assert!(effect_has_owner(effect_ids.2));
dispose_owner_now(owner_id);
assert!(!effect_has_owner(effect_ids.0));
assert!(!effect_has_owner(effect_ids.1));
assert!(!effect_has_owner(effect_ids.2));
}
#[test]
fn test_nested_owners_effect_cleanup() {
use super::super::effect::create_effect_id;
use super::super::signal::create_signal;
let ((inner_effect, outer_effect), outer_id) = with_owner(|| {
let signal = create_signal(0);
let outer = create_effect_id(move || {
let _ = signal.get();
});
let (inner, _inner_id) = with_owner(|| {
create_effect_id(move || {
let _ = signal.get();
})
});
(inner, outer)
});
assert!(effect_has_owner(inner_effect));
assert!(effect_has_owner(outer_effect));
dispose_owner_now(outer_id);
assert!(!effect_has_owner(inner_effect));
assert!(!effect_has_owner(outer_effect));
}
#[test]
fn an_optional_owner_id_costs_no_more_than_an_owner_id() {
assert_eq!(size_of::<OwnerId>(), 8);
assert_eq!(size_of::<Option<OwnerId>>(), size_of::<OwnerId>());
}
#[test]
fn test_root_owner_and_reset() {
use super::super::signal::create_signal;
let root_id = create_root_owner();
assert!(current_owner().is_some());
let signal = create_signal(42);
assert_eq!(signal.get(), 42);
dispose_owner_now(root_id);
let panicked = std::panic::catch_unwind(|| signal.get()).is_err();
assert!(panicked, "Accessing disposed signal should panic");
crate::reactive::state::reset();
assert!(current_owner().is_none());
let new_root = create_root_owner();
assert!(current_owner().is_some());
let signal2 = create_signal(99);
assert_eq!(signal2.get(), 99);
dispose_owner_now(new_root);
crate::reactive::state::reset();
}
}