1use std::any::Any;
2use std::cell::{Cell, RefCell};
3use std::rc::{Rc, Weak};
4
5use rustc_hash::FxHashMap;
6
7use crate::effects::Dispose;
8
9thread_local! {
10 static CURRENT_SCOPE: RefCell<Option<Weak<ScopeInner>>> = const { RefCell::new(None) };
11}
12
13pub struct Scope {
14 inner: Rc<ScopeInner>,
15}
16
17struct ScopeInner {
18 disposers: RefCell<Vec<Box<dyn FnOnce()>>>,
19 children: RefCell<Vec<Scope>>,
20 memo_cache: RefCell<FxHashMap<String, Box<dyn Any>>>,
21 disposed: Cell<bool>,
22}
23
24impl Default for Scope {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl Scope {
31 pub fn new() -> Self {
32 Self {
33 inner: Rc::new(ScopeInner {
34 disposers: RefCell::new(Vec::new()),
35 children: RefCell::new(Vec::new()),
36 memo_cache: RefCell::new(FxHashMap::default()),
37 disposed: Cell::new(false),
38 }),
39 }
40 }
41
42 pub fn run<R>(&self, f: impl FnOnce() -> R) -> R {
43 struct Guard {
44 prev: Option<Weak<ScopeInner>>,
45 }
46 impl Drop for Guard {
47 fn drop(&mut self) {
48 CURRENT_SCOPE.with(|current| {
49 if let Ok(mut b) = current.try_borrow_mut() {
50 *b = self.prev.take();
51 } else {
52 log::error!(
53 "scope: CURRENT_SCOPE busy during scope exit; stale scope reference retained"
54 );
55 }
56 });
57 }
58 }
59 let prev = CURRENT_SCOPE.with(|current| current.borrow().clone());
60 CURRENT_SCOPE.with(|current| {
61 *current.borrow_mut() = Some(Rc::downgrade(&self.inner));
62 });
63 let _guard = Guard { prev };
64 f()
65 }
66
67 pub fn add_disposer(&self, disposer: impl FnOnce() + 'static) {
68 self.inner.disposers.borrow_mut().push(Box::new(disposer));
69 }
70
71 pub fn memo<T: 'static>(&self, key: &str, init: impl FnOnce() -> T) -> Rc<T> {
75 let mut cache = self.inner.memo_cache.borrow_mut();
76 if let Some(existing) = cache.get(key)
77 && let Some(v) = existing.downcast_ref::<Rc<T>>()
78 {
79 return v.clone();
80 }
81 let val: Rc<T> = Rc::new(init());
82 cache.insert(key.to_string(), Box::new(val.clone()));
83 val
84 }
85
86 pub fn child(&self) -> Scope {
87 let child = Scope::new();
88 self.inner.children.borrow_mut().push(child.clone());
89 child
90 }
91
92 pub fn dispose(self) {
93 if self.inner.disposed.replace(true) {
94 return; }
96 let children = std::mem::take(&mut *self.inner.children.borrow_mut());
98 for child in children {
99 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| child.dispose()));
100 if let Err(e) = res {
101 let msg = e
102 .downcast_ref::<String>()
103 .map(|s| s.as_str())
104 .or_else(|| e.downcast_ref::<&str>().copied())
105 .unwrap_or("unknown");
106 log::error!("Scope child dispose panicked: {msg}");
107 }
108 }
109
110 let disposers = std::mem::take(&mut *self.inner.disposers.borrow_mut());
111 for disposer in disposers {
112 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(disposer));
113 if let Err(e) = res {
114 let msg = e
115 .downcast_ref::<String>()
116 .map(|s| s.as_str())
117 .or_else(|| e.downcast_ref::<&str>().copied())
118 .unwrap_or("unknown");
119 log::error!("Scope disposer panicked: {msg}");
120 }
121 }
122 }
123}
124
125impl Clone for Scope {
126 fn clone(&self) -> Self {
127 Self {
128 inner: self.inner.clone(),
129 }
130 }
131}
132
133pub fn current_scope() -> Option<Scope> {
134 CURRENT_SCOPE.with(|current| {
135 current
136 .borrow()
137 .as_ref()
138 .and_then(|weak| weak.upgrade().map(|inner| Scope { inner }))
139 })
140}
141
142pub fn scope_memo<T: 'static>(key: &str, init: impl FnOnce() -> T) -> Rc<T> {
147 match current_scope() {
148 Some(scope) => scope.memo(key, init),
149 None => Rc::new(init()),
150 }
151}
152
153#[track_caller]
157pub fn scoped_effect_once(f: impl FnOnce() -> Dispose + 'static) {
158 let loc = std::panic::Location::caller();
159 let key = format!(
160 "scoped_effect:{}:{}:{}",
161 loc.file(),
162 loc.line(),
163 loc.column()
164 );
165 let installed = crate::remember_with_key(key, || std::cell::Cell::new(false));
166 if !installed.get() {
167 installed.set(true);
168 scoped_effect(f);
169 }
170}
171
172pub fn scoped_effect<F>(f: F)
178where
179 F: FnOnce() -> Dispose + 'static,
180{
181 if let Some(scope) = current_scope() {
182 let cleanup = f();
183 scope.add_disposer(move || cleanup.run());
184 } else {
185 debug_assert!(
186 false,
187 "scoped_effect called without a current Scope; setup skipped so cleanup cannot leak"
188 );
189 log::error!("scoped_effect called without a current Scope; setup skipped");
190 }
191}
192
193impl Drop for ScopeInner {
194 fn drop(&mut self) {
195 if self.disposed.replace(true) {
196 return; }
198 let children = std::mem::take(&mut *self.children.borrow_mut());
199 for child in children {
200 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(child)));
201 if let Err(e) = res {
202 log::error!(
203 "ScopeInner drop child panicked: {}",
204 e.downcast_ref::<String>()
205 .map(|s| s.as_str())
206 .or_else(|| e.downcast_ref::<&str>().copied())
207 .unwrap_or("unknown")
208 );
209 }
210 }
211
212 let disposers = std::mem::take(&mut *self.disposers.borrow_mut());
213 for disposer in disposers {
214 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(disposer));
215 if let Err(e) = res {
216 log::error!(
217 "ScopeInner drop disposer panicked: {}",
218 e.downcast_ref::<String>()
219 .map(|s| s.as_str())
220 .or_else(|| e.downcast_ref::<&str>().copied())
221 .unwrap_or("unknown")
222 );
223 }
224 }
225 }
226}