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 CURRENT_SCOPE.with(|current| {
44 let prev = current.borrow().clone();
45 *current.borrow_mut() = Some(Rc::downgrade(&self.inner));
46 let result = f();
47 *current.borrow_mut() = prev;
48 result
49 })
50 }
51
52 pub fn add_disposer(&self, disposer: impl FnOnce() + 'static) {
53 self.inner.disposers.borrow_mut().push(Box::new(disposer));
54 }
55
56 pub fn memo<T: 'static>(&self, key: &str, init: impl FnOnce() -> T) -> Rc<T> {
60 let mut cache = self.inner.memo_cache.borrow_mut();
61 if let Some(existing) = cache.get(key)
62 && let Some(v) = existing.downcast_ref::<Rc<T>>()
63 {
64 return v.clone();
65 }
66 let val: Rc<T> = Rc::new(init());
67 cache.insert(key.to_string(), Box::new(val.clone()));
68 val
69 }
70
71 pub fn child(&self) -> Scope {
72 let child = Scope::new();
73 self.inner.children.borrow_mut().push(child.clone());
74 child
75 }
76
77 pub fn dispose(self) {
78 if self.inner.disposed.replace(true) {
79 return; }
81 let children = std::mem::take(&mut *self.inner.children.borrow_mut());
83 for child in children {
84 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| child.dispose()));
85 if let Err(e) = res {
86 let msg = e
87 .downcast_ref::<String>()
88 .map(|s| s.as_str())
89 .or_else(|| e.downcast_ref::<&str>().copied())
90 .unwrap_or("unknown");
91 log::error!("Scope child dispose panicked: {msg}");
92 }
93 }
94
95 let disposers = std::mem::take(&mut *self.inner.disposers.borrow_mut());
96 for disposer in disposers {
97 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| disposer()));
98 if let Err(e) = res {
99 let msg = e
100 .downcast_ref::<String>()
101 .map(|s| s.as_str())
102 .or_else(|| e.downcast_ref::<&str>().copied())
103 .unwrap_or("unknown");
104 log::error!("Scope disposer panicked: {msg}");
105 }
106 }
107 }
108}
109
110impl Clone for Scope {
111 fn clone(&self) -> Self {
112 Self {
113 inner: self.inner.clone(),
114 }
115 }
116}
117
118pub fn current_scope() -> Option<Scope> {
119 CURRENT_SCOPE.with(|current| {
120 current
121 .borrow()
122 .as_ref()
123 .and_then(|weak| weak.upgrade().map(|inner| Scope { inner }))
124 })
125}
126
127pub fn scope_memo<T: 'static>(key: &str, init: impl FnOnce() -> T) -> Rc<T> {
134 match current_scope() {
135 Some(scope) => scope.memo(key, init),
136 None => Rc::new(init()),
137 }
138}
139
140pub fn scoped_effect<F>(f: F)
145where
146 F: FnOnce() -> Dispose + 'static,
147{
148 if let Some(scope) = current_scope() {
149 let cleanup = f();
150 scope.add_disposer(move || cleanup.run());
151 } else {
152 let _cleanup = f();
154 }
155}
156
157impl Drop for ScopeInner {
158 fn drop(&mut self) {
159 if self.disposed.replace(true) {
160 return; }
162 let children = std::mem::take(&mut *self.children.borrow_mut());
163 for child in children {
164 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(child)));
165 if let Err(e) = res {
166 log::error!(
167 "ScopeInner drop child panicked: {}",
168 e.downcast_ref::<String>()
169 .map(|s| s.as_str())
170 .or_else(|| e.downcast_ref::<&str>().copied())
171 .unwrap_or("unknown")
172 );
173 }
174 }
175
176 let disposers = std::mem::take(&mut *self.disposers.borrow_mut());
177 for disposer in disposers {
178 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| disposer()));
179 if let Err(e) = res {
180 log::error!(
181 "ScopeInner drop disposer panicked: {}",
182 e.downcast_ref::<String>()
183 .map(|s| s.as_str())
184 .or_else(|| e.downcast_ref::<&str>().copied())
185 .unwrap_or("unknown")
186 );
187 }
188 }
189 }
190}