repose_core/
scope.rs

1use std::any::Any;
2use std::cell::RefCell;
3use std::rc::{Rc, Weak};
4
5use crate::effects::Dispose;
6
7thread_local! {
8    static CURRENT_SCOPE: RefCell<Option<Weak<ScopeInner>>> = const { RefCell::new(None) };
9}
10
11pub struct Scope {
12    inner: Rc<ScopeInner>,
13}
14
15struct ScopeInner {
16    disposers: RefCell<Vec<Box<dyn FnOnce()>>>,
17    children: RefCell<Vec<Scope>>,
18    memo_cache: RefCell<std::collections::HashMap<String, Box<dyn Any>>>,
19}
20
21impl Default for Scope {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl Scope {
28    pub fn new() -> Self {
29        Self {
30            inner: Rc::new(ScopeInner {
31                disposers: RefCell::new(Vec::new()),
32                children: RefCell::new(Vec::new()),
33                memo_cache: RefCell::new(std::collections::HashMap::new()),
34            }),
35        }
36    }
37
38    pub fn run<R>(&self, f: impl FnOnce() -> R) -> R {
39        CURRENT_SCOPE.with(|current| {
40            let prev = current.borrow().clone();
41            *current.borrow_mut() = Some(Rc::downgrade(&self.inner));
42            let result = f();
43            *current.borrow_mut() = prev;
44            result
45        })
46    }
47
48    pub fn add_disposer(&self, disposer: impl FnOnce() + 'static) {
49        self.inner.disposers.borrow_mut().push(Box::new(disposer));
50    }
51
52    pub fn child(&self) -> Scope {
53        let child = Scope::new();
54        self.inner.children.borrow_mut().push(child.clone());
55        child
56    }
57
58    pub fn dispose(self) {
59        // Dispose children first
60        let children = std::mem::take(&mut *self.inner.children.borrow_mut());
61        for child in children {
62            child.dispose();
63        }
64
65        // Run disposers
66        let disposers = std::mem::take(&mut *self.inner.disposers.borrow_mut());
67        for disposer in disposers {
68            disposer();
69        }
70    }
71}
72
73impl Clone for Scope {
74    fn clone(&self) -> Self {
75        Self {
76            inner: self.inner.clone(),
77        }
78    }
79}
80
81pub fn current_scope() -> Option<Scope> {
82    CURRENT_SCOPE.with(|current| {
83        current
84            .borrow()
85            .as_ref()
86            .and_then(|weak| weak.upgrade().map(|inner| Scope { inner }))
87    })
88}
89
90/// Scoped effect that auto-cleans up.
91///
92/// Runs `f()` immediately and registers the returned `Dispose` to run when the
93/// current scope is disposed.
94pub fn scoped_effect<F>(f: F)
95where
96    F: FnOnce() -> Dispose + 'static,
97{
98    if let Some(scope) = current_scope() {
99        let cleanup = f();
100        scope.add_disposer(move || cleanup.run());
101    } else {
102        // No scope, run setup now, but drop cleanup (legacy "leak" behavior).
103        let _cleanup = f();
104    }
105}
106
107impl Drop for ScopeInner {
108    fn drop(&mut self) {
109        let children = std::mem::take(&mut *self.children.borrow_mut());
110        for child in children {
111            drop(child);
112        }
113
114        let disposers = std::mem::take(&mut *self.disposers.borrow_mut());
115        for disposer in disposers {
116            disposer();
117        }
118    }
119}