floem_reactive/
scope.rs

1use std::{any::Any, cell::RefCell, collections::HashMap, fmt, marker::PhantomData, rc::Rc};
2
3use crate::{
4    create_effect, create_updater,
5    id::Id,
6    memo::{create_memo, Memo},
7    runtime::RUNTIME,
8    signal::{
9        create_rw_signal, create_signal, NotThreadSafe, ReadSignal, RwSignal, Signal, WriteSignal,
10    },
11    trigger::{create_trigger, Trigger},
12};
13
14/// You can manually control Signal's lifetime by using Scope.
15///
16/// Every Signal has a Scope created explicitly or implicitly,
17/// and when you Dispose the Scope, it will clean up all the Signals
18/// that belong to the Scope and all the child Scopes
19#[derive(Clone, Copy)]
20pub struct Scope(pub(crate) Id, pub(crate) PhantomData<NotThreadSafe>);
21
22impl Default for Scope {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl fmt::Debug for Scope {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        let mut s = f.debug_struct("Scope");
31        s.field("id", &self.0);
32        s.finish()
33    }
34}
35
36impl Scope {
37    /// Create a new Scope that isn't a child or parent of any scope
38    pub fn new() -> Self {
39        Self(Id::next(), PhantomData)
40    }
41
42    /// The current Scope in the Runtime. Any Signal/Effect/Memo created with
43    /// implicitly Scope will be under this Scope
44    pub fn current() -> Scope {
45        RUNTIME.with(|runtime| Scope(*runtime.current_scope.borrow(), PhantomData))
46    }
47
48    /// Create a child Scope of this Scope
49    pub fn create_child(&self) -> Scope {
50        let child = Id::next();
51        RUNTIME.with(|runtime| {
52            let mut children = runtime.children.borrow_mut();
53            let children = children.entry(self.0).or_default();
54            children.insert(child);
55        });
56        Scope(child, PhantomData)
57    }
58
59    /// Create a new Signal under this Scope
60    pub fn create_signal<T>(self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
61    where
62        T: Any + 'static,
63    {
64        with_scope(self, || create_signal(value))
65    }
66
67    /// Create a RwSignal under this Scope
68    pub fn create_rw_signal<T>(self, value: T) -> RwSignal<T>
69    where
70        T: Any + 'static,
71    {
72        with_scope(self, || create_rw_signal(value))
73    }
74
75    /// Create a Memo under this Scope
76    pub fn create_memo<T>(self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
77    where
78        T: PartialEq + 'static,
79    {
80        with_scope(self, || create_memo(f))
81    }
82
83    /// Create a Trigger under this Scope
84    pub fn create_trigger(self) -> Trigger {
85        with_scope(self, create_trigger)
86    }
87
88    /// Create effect under this Scope
89    pub fn create_effect<T>(self, f: impl Fn(Option<T>) -> T + 'static)
90    where
91        T: Any + 'static,
92    {
93        with_scope(self, || create_effect(f))
94    }
95
96    /// Create updater under this Scope
97    pub fn create_updater<R>(
98        self,
99        compute: impl Fn() -> R + 'static,
100        on_change: impl Fn(R) + 'static,
101    ) -> R
102    where
103        R: 'static,
104    {
105        with_scope(self, || create_updater(compute, on_change))
106    }
107
108    /// This is normally used in create_effect, and it will bind the effect's lifetime
109    /// to this scope
110    pub fn track(&self) {
111        let tracker = if let Some(signal) = self.0.signal() {
112            signal
113        } else {
114            let signal = Signal {
115                id: self.0,
116                subscribers: Rc::new(RefCell::new(HashMap::new())),
117                value: Rc::new(RefCell::new(())),
118                ts: PhantomData,
119            };
120            self.0.add_signal(signal.clone());
121            signal
122        };
123        tracker.subscribe();
124    }
125
126    /// Dispose this Scope, and it will cleanup all the Signals and child Scope
127    /// of this Scope.
128    pub fn dispose(&self) {
129        self.0.dispose();
130    }
131}
132
133/// Runs the given code with the given Scope
134pub fn with_scope<T>(scope: Scope, f: impl FnOnce() -> T) -> T
135where
136    T: 'static,
137{
138    let prev_scope = RUNTIME.with(|runtime| {
139        let mut current_scope = runtime.current_scope.borrow_mut();
140        let prev_scope = *current_scope;
141        *current_scope = scope.0;
142        prev_scope
143    });
144
145    let result = f();
146
147    RUNTIME.with(|runtime| {
148        *runtime.current_scope.borrow_mut() = prev_scope;
149    });
150
151    result
152}
153
154/// Wrap the closure so that whenever the closure runs, it will be under a child Scope
155/// of the current Scope
156pub fn as_child_of_current_scope<T, U>(f: impl Fn(T) -> U + 'static) -> impl Fn(T) -> (U, Scope)
157where
158    T: 'static,
159{
160    let current_scope = Scope::current();
161    move |t| {
162        let scope = current_scope.create_child();
163        let prev_scope = RUNTIME.with(|runtime| {
164            let mut current_scope = runtime.current_scope.borrow_mut();
165            let prev_scope = *current_scope;
166            *current_scope = scope.0;
167            prev_scope
168        });
169
170        let result = f(t);
171
172        RUNTIME.with(|runtime| {
173            *runtime.current_scope.borrow_mut() = prev_scope;
174        });
175
176        (result, scope)
177    }
178}