use std::any::{Any, TypeId};
use std::num::NonZeroU32;
use std::rc::Rc;
use super::invalidation::clear_signal_subscribers;
use super::runtime::{EffectId, SignalId, flush_unless_batching, 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,
}
impl OwnerId {
pub fn run<T>(self, f: impl FnOnce() -> T) -> T {
under_owner(self, f)
}
}
struct Owner {
parent: Option<OwnerId>,
held: Option<Box<Held>>,
}
#[derive(Default)]
struct Held {
signals: Vec<SignalId>,
effects: Vec<EffectId>,
cleanups: Vec<Box<dyn FnOnce()>>,
children: Vec<OwnerId>,
contexts: Declarations,
}
impl Owner {
fn new(parent: Option<OwnerId>) -> Self {
Self { parent, held: None }
}
fn held(&mut self) -> &mut Held {
self.held.get_or_insert_default()
}
}
impl Held {
fn is_empty(&self) -> bool {
self.signals.is_empty()
&& self.effects.is_empty()
&& self.cleanups.is_empty()
&& self.children.is_empty()
&& self.contexts.is_empty()
}
}
struct OwnerSlot {
owner: Option<Owner>,
generation: NonZeroU32,
paused: bool,
}
#[derive(Default)]
pub(crate) struct OwnerArena {
slots: Vec<OwnerSlot>,
free_indices: Vec<u32>,
}
impl OwnerArena {
fn allocate(&mut self, parent: Option<OwnerId>) -> OwnerId {
let paused = parent.is_some_and(|parent| self.is_paused(parent));
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);
slot.paused = paused;
OwnerId {
index,
generation: slot.generation,
}
} else {
let index = self.slots.len() as u32;
self.slots.push(OwnerSlot {
owner: Some(owner),
generation: NonZeroU32::MIN,
paused,
});
OwnerId {
index,
generation: NonZeroU32::MIN,
}
}
}
fn live_slot(&self, id: OwnerId) -> Option<&OwnerSlot> {
self.slots
.get(id.index as usize)
.filter(|slot| slot.generation == id.generation && slot.owner.is_some())
}
fn get(&self, id: OwnerId) -> Option<&Owner> {
self.live_slot(id).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 is_paused(&self, id: OwnerId) -> bool {
self.live_slot(id).is_some_and(|slot| slot.paused)
}
fn set_paused(&mut self, id: OwnerId, paused: bool) -> Vec<EffectId> {
let mut effects = Vec::new();
let mut stack = vec![id];
while let Some(id) = stack.pop() {
let Some(slot) = self
.slots
.get_mut(id.index as usize)
.filter(|slot| slot.generation == id.generation)
else {
continue;
};
let Some(owner) = &slot.owner else {
continue;
};
slot.paused = paused;
if let Some(held) = &owner.held {
effects.extend_from_slice(&held.effects);
stack.extend_from_slice(&held.children);
}
}
effects
}
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 = child_scope();
(under_owner(owner_id, f), owner_id)
}
pub(crate) fn child_scope() -> OwnerId {
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.held().children.push(id);
}
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(&mut owner.held().contexts))
})
}
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
.held
.as_ref()
.and_then(|held| held.contexts.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(held) = parent.held.as_mut()
&& let Some(pos) = held.children.iter().position(|c| *c == id)
{
held.children.swap_remove(pos);
}
Some(owner)
});
if let Some(mut held) = owner.and_then(|owner| owner.held) {
release(&mut held);
}
}
pub(crate) fn empty_owner(id: OwnerId) {
let held = with_reactive(|reactive| {
let mut arena = reactive.owners.borrow_mut();
let held = arena.get_mut(id)?.held.as_deref_mut()?;
(!held.is_empty()).then(|| std::mem::take(held))
});
let Some(mut held) = held else {
return;
};
release(&mut held);
with_reactive(|reactive| {
let mut arena = reactive.owners.borrow_mut();
if let Some(kept) = arena
.get_mut(id)
.and_then(|owner| owner.held.as_deref_mut())
&& kept.is_empty()
{
*kept = held;
}
});
}
fn release(held: &mut Held) {
for child_id in held.children.drain(..) {
dispose_owner_now(child_id);
}
for cleanup in held.cleanups.drain(..).rev() {
crate::reactive::diagnostics::snapshot_zone(cleanup);
}
for effect_id in held.effects.drain(..) {
with_runtime(|rt| rt.dispose_effect(effect_id));
}
for signal_id in held.signals.drain(..) {
clear_signal_subscribers(signal_id);
with_runtime(|rt| rt.dispose_signal_subscriptions(signal_id));
dispose_signal(signal_id);
}
held.contexts.clear();
}
pub(crate) fn pause_owner(id: OwnerId) {
let effects = with_reactive(|reactive| reactive.owners.borrow_mut().set_paused(id, true));
with_runtime(|rt| rt.set_effects_paused(&effects, true));
}
pub(crate) fn resume_owner(id: OwnerId) {
let effects = with_reactive(|reactive| reactive.owners.borrow_mut().set_paused(id, false));
with_runtime(|rt| rt.set_effects_paused(&effects, false));
flush_unless_batching();
}
pub(crate) fn current_owner_is_paused() -> bool {
with_reactive(|reactive| {
reactive
.current_owner
.get()
.is_some_and(|id| reactive.owners.borrow().is_paused(id))
})
}
pub fn on_cleanup(f: impl FnOnce() + 'static) {
with_current_owner(|owner| owner.held().cleanups.push(Box::new(f)));
}
pub(crate) fn register_signal(id: SignalId) {
with_current_owner(|owner| owner.held().signals.push(id));
}
pub(crate) fn register_effect(id: EffectId) {
with_current_owner(|owner| owner.held().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_and(|scope| {
with_reactive(|reactive| reactive.owners.borrow().get(scope)?.parent).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 emptying_a_scope_keeps_what_held_its_contents() {
use crate::reactive::create_signal;
let held_at = |id: OwnerId| {
with_reactive(|reactive| {
let arena = reactive.owners.borrow();
let held = arena.get(id)?.held.as_deref()?;
Some((std::ptr::from_ref(held), held.signals.as_ptr()))
})
};
let ((), scope) = with_owner(|| {
create_signal(0u32);
});
let before = held_at(scope).expect("a scope that made a signal holds it");
empty_owner(scope);
assert!(with_reactive(|reactive| {
let arena = reactive.owners.borrow();
arena
.get(scope)
.unwrap()
.held
.as_deref()
.unwrap()
.is_empty()
}));
under_owner(scope, || create_signal(1u32));
assert_eq!(held_at(scope), Some(before), "the scope allocated again");
dispose_owner_now(scope);
}
#[test]
fn emptying_a_scope_drops_what_it_declared() {
let ((), scope) = with_owner(|| {
with_scope_declarations(|declared| {
declared.push((TypeId::of::<u8>(), Rc::new(7u8)));
});
});
let declared = || under_owner(scope, || nearest_declaration(TypeId::of::<u8>()));
assert!(declared().is_some(), "the control: the scope declared it");
empty_owner(scope);
assert!(declared().is_none(), "emptied, it declares nothing");
dispose_owner_now(scope);
}
#[test]
fn test_dispose_prunes_parent_children_list() {
let (child_ids, parent_id) = with_owner(|| {
(0..4)
.map(|_| with_owner(|| ()).1)
.collect::<Vec<OwnerId>>()
});
let children = || {
with_reactive(|reactive| {
let arena = reactive.owners.borrow();
let parent = arena.get(parent_id).expect("parent still live");
let mut children = parent
.held
.as_deref()
.expect("it held children")
.children
.clone();
children.sort_by_key(|id| id.index);
children
})
};
dispose_owner_now(child_ids[1]);
assert_eq!(
children(),
[child_ids[0], child_ids[2], child_ids[3]],
"the disposed child, and only it, is pruned"
);
for child in &child_ids {
dispose_owner_now(*child);
}
assert!(children().is_empty(), "disposed children not pruned");
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));
}
fn counting_scopes(
signal: crate::reactive::RwSignal<u32>,
) -> (OwnerId, Rc<std::cell::Cell<u32>>, Rc<std::cell::Cell<u32>>) {
use super::super::effect::create_effect;
let outer = Rc::new(std::cell::Cell::new(0));
let inner = Rc::new(std::cell::Cell::new(0));
let (o, i) = (outer.clone(), inner.clone());
let (_, id) = with_owner(move || {
create_effect(move || {
signal.get();
o.set(o.get() + 1);
});
with_owner(move || {
create_effect(move || {
signal.get();
i.set(i.get() + 1);
})
});
});
(id, outer, inner)
}
#[test]
fn a_paused_scope_runs_what_it_missed_once_on_resume() {
use super::super::signal::create_signal;
let signal = create_signal(0u32);
let (id, outer, inner) = counting_scopes(signal);
assert_eq!((outer.get(), inner.get()), (1, 1));
pause_owner(id);
signal.set(1);
signal.set(2);
assert_eq!((outer.get(), inner.get()), (1, 1), "paused, nothing ran");
resume_owner(id);
assert_eq!((outer.get(), inner.get()), (2, 2), "each ran once");
signal.set(3);
assert_eq!(
(outer.get(), inner.get()),
(3, 3),
"and they are live again"
);
dispose_owner_now(id);
}
#[test]
fn a_paused_scope_that_missed_nothing_runs_nothing_on_resume() {
use super::super::signal::create_signal;
let signal = create_signal(0u32);
let (id, outer, inner) = counting_scopes(signal);
pause_owner(id);
resume_owner(id);
assert_eq!((outer.get(), inner.get()), (1, 1));
dispose_owner_now(id);
}
#[test]
fn a_paused_scope_disposed_runs_nothing() {
use super::super::signal::create_signal;
let signal = create_signal(0u32);
let (id, outer, inner) = counting_scopes(signal);
pause_owner(id);
signal.set(1);
dispose_owner_now(id);
resume_owner(id);
signal.set(2);
assert_eq!((outer.get(), inner.get()), (1, 1));
}
#[test]
fn what_is_made_under_a_paused_scope_waits_for_the_resume() {
use super::super::effect::create_effect;
use super::super::signal::create_signal;
let signal = create_signal(0u32);
let (_, id) = with_owner(|| ());
pause_owner(id);
let runs = Rc::new(std::cell::Cell::new(0));
let counted = runs.clone();
under_owner(id, || {
with_owner(move || {
create_effect(move || {
signal.get();
counted.set(counted.get() + 1);
})
})
});
signal.set(1);
assert_eq!(runs.get(), 0, "not even its first run");
resume_owner(id);
assert_eq!(runs.get(), 1, "the first run, on resume");
signal.set(2);
assert_eq!(runs.get(), 2);
dispose_owner_now(id);
}
#[test]
fn pausing_a_scope_leaves_its_parent_and_siblings_running() {
use super::super::effect::create_effect;
use super::super::signal::create_signal;
let signal = create_signal(0u32);
let runs = Rc::new(std::cell::Cell::new(0));
let counted = runs.clone();
let ((paused, sibling_runs), parent) = with_owner(move || {
create_effect(move || {
signal.get();
counted.set(counted.get() + 1);
});
let (paused, ..) = counting_scopes(signal);
let (_, sibling_runs, _) = counting_scopes(signal);
(paused, sibling_runs)
});
pause_owner(paused);
signal.set(1);
assert_eq!(runs.get(), 2, "the parent still runs");
assert_eq!(sibling_runs.get(), 2, "and so does its sibling");
dispose_owner_now(parent);
}
#[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();
}
}