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 if self.inner.disposed.get() {
69 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(disposer));
70 if let Err(e) = res {
71 let msg = e
72 .downcast_ref::<String>()
73 .map(|s| s.as_str())
74 .or_else(|| e.downcast_ref::<&str>().copied())
75 .unwrap_or("unknown");
76 log::error!("Scope disposer after disposal panicked: {msg}");
77 }
78 return;
79 }
80 self.inner.disposers.borrow_mut().push(Box::new(disposer));
81 }
82
83 pub fn memo<T: 'static>(&self, key: &str, init: impl FnOnce() -> T) -> Rc<T> {
86 if let Some(existing) = self.inner.memo_cache.borrow().get(key)
87 && let Some(v) = existing.downcast_ref::<Rc<T>>()
88 {
89 return v.clone();
90 }
91 let val: Rc<T> = Rc::new(init());
92 if !self.inner.disposed.get() {
93 let old = {
94 let mut cache = self.inner.memo_cache.borrow_mut();
95 cache.insert(key.to_string(), Box::new(val.clone()))
96 };
97 drop(old);
98 }
99 val
100 }
101
102 pub fn child(&self) -> Scope {
103 let child = Scope::new();
104 if self.inner.disposed.get() {
105 child.inner.disposed.set(true);
106 } else {
107 self.inner.children.borrow_mut().push(child.clone());
108 }
109 child
110 }
111
112 pub fn dispose(self) {
113 if self.inner.disposed.replace(true) {
114 return; }
116 let children = std::mem::take(&mut *self.inner.children.borrow_mut());
118 for child in children.into_iter().rev() {
119 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| child.dispose()));
120 if let Err(e) = res {
121 let msg = e
122 .downcast_ref::<String>()
123 .map(|s| s.as_str())
124 .or_else(|| e.downcast_ref::<&str>().copied())
125 .unwrap_or("unknown");
126 log::error!("Scope child dispose panicked: {msg}");
127 }
128 }
129
130 let disposers = std::mem::take(&mut *self.inner.disposers.borrow_mut());
131 for disposer in disposers.into_iter().rev() {
132 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(disposer));
133 if let Err(e) = res {
134 let msg = e
135 .downcast_ref::<String>()
136 .map(|s| s.as_str())
137 .or_else(|| e.downcast_ref::<&str>().copied())
138 .unwrap_or("unknown");
139 log::error!("Scope disposer panicked: {msg}");
140 }
141 }
142 let memo = std::mem::take(&mut *self.inner.memo_cache.borrow_mut());
143 drop(memo);
144 }
145}
146
147impl Clone for Scope {
148 fn clone(&self) -> Self {
149 Self {
150 inner: self.inner.clone(),
151 }
152 }
153}
154
155pub fn current_scope() -> Option<Scope> {
156 CURRENT_SCOPE.with(|current| {
157 current
158 .borrow()
159 .as_ref()
160 .and_then(|weak| weak.upgrade().map(|inner| Scope { inner }))
161 })
162}
163
164pub fn scope_memo<T: 'static>(key: &str, init: impl FnOnce() -> T) -> Rc<T> {
169 match current_scope() {
170 Some(scope) => scope.memo(key, init),
171 None => Rc::new(init()),
172 }
173}
174
175#[track_caller]
179pub fn scoped_effect_once(f: impl FnOnce() -> Dispose + 'static) {
180 let loc = std::panic::Location::caller();
181 let mut key = format!(
182 "scoped_effect:{}:{}:{}",
183 loc.file(),
184 loc.line(),
185 loc.column()
186 );
187 if let Some(identity) = crate::shortcuts::active_runtime_identity() {
188 key.push_str(&format!(":runtime:{identity}"));
189 }
190 if current_scope().is_none() {
191 debug_assert!(
192 false,
193 "scoped_effect called without a current Scope; setup skipped so cleanup cannot leak"
194 );
195 log::error!("scoped_effect called without a current Scope; setup skipped");
196 return;
197 }
198 crate::effects::effect_once_with_key(key, f);
199}
200
201pub fn scoped_effect<F>(f: F)
207where
208 F: FnOnce() -> Dispose + 'static,
209{
210 if let Some(scope) = current_scope() {
211 let cleanup = f();
212 scope.add_disposer(move || cleanup.run());
213 } else {
214 debug_assert!(
215 false,
216 "scoped_effect called without a current Scope; setup skipped so cleanup cannot leak"
217 );
218 log::error!("scoped_effect called without a current Scope; setup skipped");
219 }
220}
221
222impl Drop for ScopeInner {
223 fn drop(&mut self) {
224 if self.disposed.replace(true) {
225 return; }
227 let children = std::mem::take(&mut *self.children.borrow_mut());
228 for child in children.into_iter().rev() {
229 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(child)));
230 if let Err(e) = res {
231 log::error!(
232 "ScopeInner drop child panicked: {}",
233 e.downcast_ref::<String>()
234 .map(|s| s.as_str())
235 .or_else(|| e.downcast_ref::<&str>().copied())
236 .unwrap_or("unknown")
237 );
238 }
239 }
240
241 let disposers = std::mem::take(&mut *self.disposers.borrow_mut());
242 for disposer in disposers.into_iter().rev() {
243 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(disposer));
244 if let Err(e) = res {
245 log::error!(
246 "ScopeInner drop disposer panicked: {}",
247 e.downcast_ref::<String>()
248 .map(|s| s.as_str())
249 .or_else(|| e.downcast_ref::<&str>().copied())
250 .unwrap_or("unknown")
251 );
252 }
253 }
254 let memo = std::mem::take(&mut *self.memo_cache.borrow_mut());
255 drop(memo);
256 }
257}