Skip to main content

repose_core/
scope.rs

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