floem_reactive/
scope.rs

1use std::{
2    any::Any, cell::RefCell, collections::HashSet, fmt, marker::PhantomData, rc::Rc, sync::Arc,
3};
4
5use parking_lot::Mutex;
6
7use crate::{
8    create_effect, create_updater,
9    id::Id,
10    memo::{create_memo, Memo},
11    runtime::{Runtime, RUNTIME},
12    signal::{ReadSignal, RwSignal, SignalState, SignalValue, WriteSignal},
13    storage::{SyncStorage, UnsyncStorage},
14    trigger::{create_trigger, Trigger},
15};
16
17/// You can manually control Signal's lifetime by using Scope.
18///
19/// Every Signal has a Scope created explicitly or implicitly,
20/// and when you Dispose the Scope, it will clean up all the Signals
21/// that belong to the Scope and all the child Scopes
22#[derive(Clone, Copy)]
23pub struct Scope(pub(crate) Id, pub(crate) PhantomData<()>);
24
25impl Default for Scope {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl fmt::Debug for Scope {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        let mut s = f.debug_struct("Scope");
34        s.field("id", &self.0);
35        s.finish()
36    }
37}
38
39impl Scope {
40    /// Create a new Scope that isn't a child or parent of any scope
41    pub fn new() -> Self {
42        Self(Id::next(), PhantomData)
43    }
44
45    /// The current Scope in the Runtime. Any Signal/Effect/Memo created with
46    /// implicitly Scope will be under this Scope
47    pub fn current() -> Scope {
48        RUNTIME.with(|runtime| Scope(*runtime.current_scope.borrow(), PhantomData))
49    }
50
51    /// Create a child Scope of this Scope
52    pub fn create_child(&self) -> Scope {
53        let child = Id::next();
54        RUNTIME.with(|runtime| {
55            let mut children = runtime.children.borrow_mut();
56            let children = children.entry(self.0).or_default();
57            children.insert(child);
58        });
59        Scope(child, PhantomData)
60    }
61
62    /// Create a new Signal under this Scope
63    #[cfg_attr(debug_assertions, track_caller)]
64    pub fn create_signal<T>(self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
65    where
66        T: Any + 'static,
67    {
68        self.enter(|| RwSignal::new_split(value))
69    }
70
71    /// Create a RwSignal under this Scope (local/unsync by default)
72    #[cfg_attr(debug_assertions, track_caller)]
73    pub fn create_rw_signal<T>(self, value: T) -> RwSignal<T>
74    where
75        T: Any + 'static,
76    {
77        self.enter(|| RwSignal::new(value))
78    }
79
80    /// Create a sync Signal under this Scope
81    #[cfg_attr(debug_assertions, track_caller)]
82    pub fn create_sync_signal<T>(
83        self,
84        value: T,
85    ) -> (ReadSignal<T, SyncStorage>, WriteSignal<T, SyncStorage>)
86    where
87        T: Any + Send + Sync + 'static,
88    {
89        self.enter(|| RwSignal::<T, SyncStorage>::new_sync_split(value))
90    }
91
92    /// Create a sync RwSignal under this Scope
93    #[cfg_attr(debug_assertions, track_caller)]
94    pub fn create_sync_rw_signal<T>(self, value: T) -> RwSignal<T, SyncStorage>
95    where
96        T: Any + Send + Sync + 'static,
97    {
98        self.enter(|| RwSignal::<T, SyncStorage>::new_sync(value))
99    }
100
101    /// Create a local (unsync) Signal under this Scope
102    #[cfg_attr(debug_assertions, track_caller)]
103    pub fn create_local_signal<T>(
104        self,
105        value: T,
106    ) -> (ReadSignal<T, UnsyncStorage>, WriteSignal<T, UnsyncStorage>)
107    where
108        T: Any + 'static,
109    {
110        self.enter(|| RwSignal::new_split(value))
111    }
112
113    /// Create a local (unsync) RwSignal under this Scope
114    #[cfg_attr(debug_assertions, track_caller)]
115    pub fn create_local_rw_signal<T>(self, value: T) -> RwSignal<T, UnsyncStorage>
116    where
117        T: Any + 'static,
118    {
119        self.enter(|| RwSignal::new(value))
120    }
121
122    /// Create a Memo under this Scope
123    #[cfg_attr(debug_assertions, track_caller)]
124    pub fn create_memo<T>(self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
125    where
126        T: PartialEq + 'static,
127    {
128        self.enter(|| create_memo(f))
129    }
130
131    /// Create a Trigger under this Scope
132    #[cfg_attr(debug_assertions, track_caller)]
133    pub fn create_trigger(self) -> Trigger {
134        self.enter(create_trigger)
135    }
136
137    /// Create effect under this Scope
138    #[cfg_attr(debug_assertions, track_caller)]
139    pub fn create_effect<T>(self, f: impl Fn(Option<T>) -> T + 'static)
140    where
141        T: Any + 'static,
142    {
143        self.enter(|| create_effect(f))
144    }
145
146    /// Create updater under this Scope
147    #[cfg_attr(debug_assertions, track_caller)]
148    pub fn create_updater<R>(
149        self,
150        compute: impl Fn() -> R + 'static,
151        on_change: impl Fn(R) + 'static,
152    ) -> R
153    where
154        R: 'static,
155    {
156        self.enter(|| create_updater(compute, on_change))
157    }
158
159    /// Runs the given closure within this scope.
160    #[cfg_attr(debug_assertions, track_caller)]
161    pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T
162    where
163        T: 'static,
164    {
165        Runtime::assert_ui_thread();
166        let prev_scope = RUNTIME.with(|runtime| {
167            let mut current_scope = runtime.current_scope.borrow_mut();
168            let prev_scope = *current_scope;
169            *current_scope = self.0;
170            prev_scope
171        });
172
173        let result = f();
174
175        RUNTIME.with(|runtime| {
176            *runtime.current_scope.borrow_mut() = prev_scope;
177        });
178
179        result
180    }
181
182    /// Wraps a closure so it runs under a new child scope of this scope.
183    #[cfg_attr(debug_assertions, track_caller)]
184    pub fn enter_child<T, U>(&self, f: impl Fn(T) -> U + 'static) -> impl Fn(T) -> (U, Scope)
185    where
186        T: 'static,
187    {
188        Runtime::assert_ui_thread();
189        let parent = *self;
190        move |t| {
191            let scope = parent.create_child();
192            let prev_scope = RUNTIME.with(|runtime| {
193                let mut current_scope = runtime.current_scope.borrow_mut();
194                let prev_scope = *current_scope;
195                *current_scope = scope.0;
196                prev_scope
197            });
198
199            let result = f(t);
200
201            RUNTIME.with(|runtime| {
202                *runtime.current_scope.borrow_mut() = prev_scope;
203            });
204
205            (result, scope)
206        }
207    }
208
209    /// This is normally used in create_effect, and it will bind the effect's lifetime
210    /// to this scope
211    #[cfg_attr(debug_assertions, track_caller)]
212    pub fn track(&self) {
213        Runtime::assert_ui_thread();
214        let signal = if let Some(signal) = self.0.signal() {
215            signal
216        } else {
217            let signal = SignalState {
218                id: self.0,
219                subscribers: Arc::new(Mutex::new(HashSet::new())),
220                value: SignalValue::Local(Rc::new(RefCell::new(()))),
221            };
222            self.0.add_signal(signal.clone());
223            signal
224        };
225        signal.subscribe();
226    }
227
228    /// Dispose this Scope, and it will cleanup all the Signals and child Scope
229    /// of this Scope.
230    pub fn dispose(&self) {
231        self.0.dispose();
232    }
233}
234
235#[deprecated(
236    since = "0.2.0",
237    note = "Use Scope::enter instead; this will be removed in a future release"
238)]
239/// Runs the given code with the given Scope
240pub fn with_scope<T>(scope: Scope, f: impl FnOnce() -> T) -> T
241where
242    T: 'static,
243{
244    scope.enter(f)
245}
246
247/// Wrap the closure so that whenever the closure runs, it will be under a child Scope
248/// of the current Scope
249#[deprecated(
250    since = "0.2.0",
251    note = "Use Scope::current().enter_child instead; this will be removed in a future release"
252)]
253pub fn as_child_of_current_scope<T, U>(f: impl Fn(T) -> U + 'static) -> impl Fn(T) -> (U, Scope)
254where
255    T: 'static,
256{
257    Scope::current().enter_child(f)
258}