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