Skip to main content

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, PartialEq, Eq)]
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            runtime
56                .children
57                .borrow_mut()
58                .entry(self.0)
59                .or_default()
60                .insert(child);
61            runtime.parents.borrow_mut().insert(child, self.0);
62        });
63        Scope(child, PhantomData)
64    }
65
66    /// Re-parent this scope to be a child of another scope.
67    ///
68    /// If this scope already has a parent, it will be removed from that parent first.
69    /// This is useful when the scope hierarchy needs to be adjusted after construction
70    /// to match the view hierarchy.
71    ///
72    /// # Example
73    /// ```rust
74    /// # use floem_reactive::Scope;
75    /// let parent = Scope::new();
76    /// let child = Scope::new(); // Initially has no parent
77    ///
78    /// child.set_parent(parent);
79    /// // Now child is a child of parent, and will be disposed when parent is disposed
80    /// ```
81    pub fn set_parent(&self, new_parent: Scope) {
82        RUNTIME.with(|runtime| {
83            // Remove from old parent's children set (if any)
84            if let Some(old_parent) = runtime.parents.borrow_mut().remove(&self.0) {
85                if let Some(children) = runtime.children.borrow_mut().get_mut(&old_parent) {
86                    children.remove(&self.0);
87                }
88            }
89
90            // Add to new parent's children set
91            runtime
92                .children
93                .borrow_mut()
94                .entry(new_parent.0)
95                .or_default()
96                .insert(self.0);
97
98            // Set new parent
99            runtime.parents.borrow_mut().insert(self.0, new_parent.0);
100        });
101    }
102
103    /// Returns the parent scope of this scope, if any.
104    pub fn parent(&self) -> Option<Scope> {
105        RUNTIME.with(|runtime| {
106            runtime
107                .parents
108                .borrow()
109                .get(&self.0)
110                .map(|id| Scope(*id, PhantomData))
111        })
112    }
113
114    /// Create a new Signal under this Scope
115    #[cfg_attr(debug_assertions, track_caller)]
116    pub fn create_signal<T>(self, value: T) -> (ReadSignal<T>, WriteSignal<T>)
117    where
118        T: Any + 'static,
119    {
120        self.enter(|| RwSignal::new_split(value))
121    }
122
123    /// Create a RwSignal under this Scope (local/unsync by default)
124    #[cfg_attr(debug_assertions, track_caller)]
125    pub fn create_rw_signal<T>(self, value: T) -> RwSignal<T>
126    where
127        T: Any + 'static,
128    {
129        self.enter(|| RwSignal::new(value))
130    }
131
132    /// Create a sync Signal under this Scope
133    #[cfg_attr(debug_assertions, track_caller)]
134    pub fn create_sync_signal<T>(
135        self,
136        value: T,
137    ) -> (ReadSignal<T, SyncStorage>, WriteSignal<T, SyncStorage>)
138    where
139        T: Any + Send + Sync + 'static,
140    {
141        self.enter(|| RwSignal::<T, SyncStorage>::new_sync_split(value))
142    }
143
144    /// Create a sync RwSignal under this Scope
145    #[cfg_attr(debug_assertions, track_caller)]
146    pub fn create_sync_rw_signal<T>(self, value: T) -> RwSignal<T, SyncStorage>
147    where
148        T: Any + Send + Sync + 'static,
149    {
150        self.enter(|| RwSignal::<T, SyncStorage>::new_sync(value))
151    }
152
153    /// Create a local (unsync) Signal under this Scope
154    #[cfg_attr(debug_assertions, track_caller)]
155    pub fn create_local_signal<T>(
156        self,
157        value: T,
158    ) -> (ReadSignal<T, UnsyncStorage>, WriteSignal<T, UnsyncStorage>)
159    where
160        T: Any + 'static,
161    {
162        self.enter(|| RwSignal::new_split(value))
163    }
164
165    /// Create a local (unsync) RwSignal under this Scope
166    #[cfg_attr(debug_assertions, track_caller)]
167    pub fn create_local_rw_signal<T>(self, value: T) -> RwSignal<T, UnsyncStorage>
168    where
169        T: Any + 'static,
170    {
171        self.enter(|| RwSignal::new(value))
172    }
173
174    /// Create a Memo under this Scope
175    #[cfg_attr(debug_assertions, track_caller)]
176    pub fn create_memo<T>(self, f: impl Fn(Option<&T>) -> T + 'static) -> Memo<T>
177    where
178        T: PartialEq + 'static,
179    {
180        self.enter(|| create_memo(f))
181    }
182
183    /// Create a Trigger under this Scope
184    #[cfg_attr(debug_assertions, track_caller)]
185    pub fn create_trigger(self) -> Trigger {
186        self.enter(create_trigger)
187    }
188
189    /// Create effect under this Scope
190    #[cfg_attr(debug_assertions, track_caller)]
191    pub fn create_effect<T>(self, f: impl Fn(Option<T>) -> T + 'static)
192    where
193        T: Any + 'static,
194    {
195        self.enter(|| create_effect(f))
196    }
197
198    /// Create updater under this Scope
199    #[cfg_attr(debug_assertions, track_caller)]
200    pub fn create_updater<R>(
201        self,
202        compute: impl Fn() -> R + 'static,
203        on_change: impl Fn(R) + 'static,
204    ) -> R
205    where
206        R: 'static,
207    {
208        self.enter(|| create_updater(compute, on_change))
209    }
210
211    /// Store a context value in this scope.
212    ///
213    /// The stored context value can be retrieved by this scope and any of its
214    /// descendants using [`Scope::get_context`] or [`Context::get`](crate::Context::get).
215    /// Child scopes can provide their own values of the same type, which will
216    /// shadow the parent's value for that subtree.
217    ///
218    /// Context values are automatically cleaned up when the scope is disposed.
219    ///
220    /// # Example
221    /// ```rust
222    /// # use floem_reactive::Scope;
223    /// let scope = Scope::new();
224    /// scope.provide_context(42i32);
225    /// scope.enter(|| {
226    ///     assert_eq!(scope.get_context::<i32>(), Some(42));
227    /// });
228    /// ```
229    pub fn provide_context<T>(&self, value: T)
230    where
231        T: Clone + 'static,
232    {
233        self.enter(|| crate::context::Context::provide(value))
234    }
235
236    /// Try to retrieve a stored context value from this scope or its ancestors.
237    ///
238    /// Context lookup walks up the scope tree from this scope to find the
239    /// nearest ancestor that provides a value of the requested type.
240    ///
241    /// Note: This method must be called while the scope is entered (i.e., inside
242    /// [`Scope::enter`]) for the lookup to work correctly.
243    ///
244    /// # Example
245    /// ```rust
246    /// # use floem_reactive::Scope;
247    /// let parent = Scope::new();
248    /// parent.provide_context(42i32);
249    ///
250    /// let child = parent.create_child();
251    /// child.enter(|| {
252    ///     // Child sees parent's context
253    ///     assert_eq!(child.get_context::<i32>(), Some(42));
254    /// });
255    /// ```
256    pub fn get_context<T>(&self) -> Option<T>
257    where
258        T: Clone + 'static,
259    {
260        self.enter(crate::context::Context::get::<T>)
261    }
262
263    /// Runs the given closure within this scope.
264    #[cfg_attr(debug_assertions, track_caller)]
265    pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T
266    where
267        T: 'static,
268    {
269        Runtime::assert_ui_thread();
270        let prev_scope = RUNTIME.with(|runtime| {
271            let mut current_scope = runtime.current_scope.borrow_mut();
272            let prev_scope = *current_scope;
273            *current_scope = self.0;
274            prev_scope
275        });
276
277        let result = f();
278
279        RUNTIME.with(|runtime| {
280            *runtime.current_scope.borrow_mut() = prev_scope;
281        });
282
283        result
284    }
285
286    /// Wraps a closure so it runs under a new child scope of this scope.
287    #[cfg_attr(debug_assertions, track_caller)]
288    pub fn enter_child<T, U>(&self, f: impl Fn(T) -> U + 'static) -> impl Fn(T) -> (U, Scope)
289    where
290        T: 'static,
291    {
292        Runtime::assert_ui_thread();
293        let parent = *self;
294        move |t| {
295            let scope = parent.create_child();
296            let prev_scope = RUNTIME.with(|runtime| {
297                let mut current_scope = runtime.current_scope.borrow_mut();
298                let prev_scope = *current_scope;
299                *current_scope = scope.0;
300                prev_scope
301            });
302
303            let result = f(t);
304
305            RUNTIME.with(|runtime| {
306                *runtime.current_scope.borrow_mut() = prev_scope;
307            });
308
309            (result, scope)
310        }
311    }
312
313    /// This is normally used in create_effect, and it will bind the effect's lifetime
314    /// to this scope
315    #[cfg_attr(debug_assertions, track_caller)]
316    pub fn track(&self) {
317        Runtime::assert_ui_thread();
318        let signal = if let Some(signal) = self.0.signal() {
319            signal
320        } else {
321            let signal = SignalState {
322                id: self.0,
323                subscribers: Arc::new(Mutex::new(HashSet::new())),
324                value: SignalValue::Local(Rc::new(RefCell::new(()))),
325            };
326            self.0.add_signal(signal.clone());
327            signal
328        };
329        signal.subscribe();
330    }
331
332    /// Dispose this Scope, and it will cleanup all the Signals and child Scope
333    /// of this Scope.
334    pub fn dispose(&self) {
335        self.0.dispose();
336    }
337}
338
339#[deprecated(
340    since = "0.2.0",
341    note = "Use Scope::enter instead; this will be removed in a future release"
342)]
343/// Runs the given code with the given Scope
344pub fn with_scope<T>(scope: Scope, f: impl FnOnce() -> T) -> T
345where
346    T: 'static,
347{
348    scope.enter(f)
349}
350
351/// Wrap the closure so that whenever the closure runs, it will be under a child Scope
352/// of the current Scope
353#[deprecated(
354    since = "0.2.0",
355    note = "Use Scope::current().enter_child instead; this will be removed in a future release"
356)]
357pub fn as_child_of_current_scope<T, U>(f: impl Fn(T) -> U + 'static) -> impl Fn(T) -> (U, Scope)
358where
359    T: 'static,
360{
361    Scope::current().enter_child(f)
362}