1use std::cell::RefCell;
2use std::rc::Rc;
3
4#[derive(Clone)]
5pub struct Dispose(Rc<RefCell<Option<Box<dyn FnOnce()>>>>);
6
7impl Dispose {
8 pub fn new(f: impl FnOnce() + 'static) -> Self {
9 Self(Rc::new(RefCell::new(Some(Box::new(f)))))
10 }
11
12 pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
13 Rc::ptr_eq(&self.0, &other.0)
14 }
15
16 pub fn run(&self) {
17 let function = {
18 let mut function = self.0.borrow_mut();
19 function.take()
20 };
21 if let Some(function) = function {
22 function();
23 }
24 }
25}
26
27pub fn effect<F>(f: F) -> Dispose
32where
33 F: FnOnce() -> Dispose + 'static,
34{
35 let d = f();
36
37 if let Some(scope) = crate::scope::current_scope() {
38 let d2 = d.clone();
39 scope.add_disposer(move || d2.run());
40 } else {
41 debug_assert!(
42 false,
43 "effect called without a current Scope; cleanup cannot be tracked"
44 );
45 log::error!("effect called without a current Scope; cleanup untracked");
46 }
47
48 d
49}
50
51#[track_caller]
54pub fn effect_once(f: impl FnOnce() -> Dispose + 'static) -> Dispose {
55 let loc = std::panic::Location::caller();
56 effect_once_with_key(
57 format!("effect:{}:{}:{}", loc.file(), loc.line(), loc.column()),
58 f,
59 )
60}
61
62pub fn effect_once_with_key(
63 key: impl Into<String>,
64 f: impl FnOnce() -> Dispose + 'static,
65) -> Dispose {
66 let key = key.into();
67 let slot = crate::remember_with_key(key.clone(), || RefCell::new(None::<Dispose>));
68 let existing = {
69 let slot = slot.borrow();
70 slot.as_ref().cloned()
71 };
72 if let Some(existing) = existing {
73 return register_scope_owner(&key, &existing);
74 }
75
76 let disposer = f();
77 let old = {
78 let mut slot = slot.borrow_mut();
79 slot.replace(disposer.clone())
80 };
81 drop(old);
82 register_scope_owner(&key, &disposer)
83}
84
85fn register_scope_owner(key: &str, disposer: &Dispose) -> Dispose {
86 let Some(scope) = crate::scope::current_scope() else {
87 crate::runtime::register_keyed_disposer(key.to_string(), disposer.clone());
88 return disposer.clone();
89 };
90 let owner =
91 crate::runtime::scope_owner_token(crate::scope_cache::current_scope_key().as_deref());
92 let token_key = key.to_string();
93 let token_disposer = disposer.clone();
94 let token_owner = owner.clone();
95 if !crate::runtime::keyed_disposer_has_owner(&token_key, &token_disposer, &owner) {
96 crate::runtime::register_keyed_disposer_for_owner(
97 token_key.clone(),
98 token_disposer.clone(),
99 owner.clone(),
100 );
101 let registered = token_disposer.clone();
102 let scope_key = token_key.clone();
103 let scope_owner = owner.clone();
104 scope.add_disposer(move || {
105 crate::runtime::remove_keyed_disposer_for_owner(&scope_key, ®istered, &scope_owner);
106 });
107 }
108 Dispose::new(move || {
109 crate::runtime::remove_keyed_disposer_for_owner(&token_key, &token_disposer, &token_owner);
110 })
111}
112pub fn on_unmount(f: impl FnOnce() + 'static) -> Dispose {
114 Dispose::new(f)
115}