Skip to main content

reactive_graph/owner/
context.rs

1use crate::owner::Owner;
2use or_poisoned::OrPoisoned;
3use std::{
4    any::{Any, TypeId},
5    collections::VecDeque,
6};
7
8impl Owner {
9    fn provide_context<T: Send + Sync + 'static>(&self, value: T) {
10        self.inner
11            .write()
12            .or_poisoned()
13            .contexts
14            .insert(value.type_id(), Box::new(value));
15    }
16
17    fn use_context<T: Clone + 'static>(&self) -> Option<T> {
18        self.with_context(Clone::clone)
19    }
20
21    fn take_context<T: 'static>(&self) -> Option<T> {
22        let ty = TypeId::of::<T>();
23        let mut inner = self.inner.write().or_poisoned();
24        let contexts = &mut inner.contexts;
25        if let Some(context) = contexts.remove(&ty) {
26            context.downcast::<T>().ok().map(|n| *n)
27        } else {
28            let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
29            while let Some(ref this_parent) = parent.clone() {
30                let mut this_parent = this_parent.write().or_poisoned();
31                let contexts = &mut this_parent.contexts;
32                let value = contexts.remove(&ty);
33                let downcast =
34                    value.and_then(|context| context.downcast::<T>().ok());
35                if let Some(value) = downcast {
36                    return Some(*value);
37                } else {
38                    parent =
39                        this_parent.parent.as_ref().and_then(|p| p.upgrade());
40                }
41            }
42            None
43        }
44    }
45
46    fn with_context<T: 'static, R>(
47        &self,
48        cb: impl FnOnce(&T) -> R,
49    ) -> Option<R> {
50        let ty = TypeId::of::<T>();
51        let inner = self.inner.read().or_poisoned();
52        let contexts = &inner.contexts;
53        let reference = if let Some(context) = contexts.get(&ty) {
54            context.downcast_ref::<T>()
55        } else {
56            let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
57            while let Some(ref this_parent) = parent.clone() {
58                let this_parent = this_parent.read().or_poisoned();
59                let contexts = &this_parent.contexts;
60                let value = contexts.get(&ty);
61                let downcast =
62                    value.and_then(|context| context.downcast_ref::<T>());
63                if let Some(value) = downcast {
64                    return Some(cb(value));
65                } else {
66                    parent =
67                        this_parent.parent.as_ref().and_then(|p| p.upgrade());
68                }
69            }
70
71            None
72        };
73        reference.map(cb)
74    }
75
76    fn update_context<T: 'static, R>(
77        &self,
78        cb: impl FnOnce(&mut T) -> R,
79    ) -> Option<R> {
80        let ty = TypeId::of::<T>();
81        let mut inner = self.inner.write().or_poisoned();
82        let contexts = &mut inner.contexts;
83        let reference = if let Some(context) = contexts.get_mut(&ty) {
84            context.downcast_mut::<T>()
85        } else {
86            let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade());
87            while let Some(ref this_parent) = parent.clone() {
88                let mut this_parent = this_parent.write().or_poisoned();
89                let contexts = &mut this_parent.contexts;
90                let value = contexts.get_mut(&ty);
91                let downcast =
92                    value.and_then(|context| context.downcast_mut::<T>());
93                if let Some(value) = downcast {
94                    return Some(cb(value));
95                } else {
96                    parent =
97                        this_parent.parent.as_ref().and_then(|p| p.upgrade());
98                }
99            }
100            None
101        };
102        reference.map(cb)
103    }
104
105    /// Searches for items stored in context in either direction, either among parents or among
106    /// descendants.
107    pub fn use_context_bidirectional<T: Clone + 'static>(&self) -> Option<T> {
108        self.use_context()
109            .unwrap_or_else(|| self.find_context_in_children())
110    }
111
112    fn find_context_in_children<T: Clone + 'static>(&self) -> Option<T> {
113        let ty = TypeId::of::<T>();
114        let inner = self.inner.read().or_poisoned();
115        let mut to_search = VecDeque::new();
116        to_search.extend(inner.children.clone());
117        drop(inner);
118
119        while let Some(next) = to_search.pop_front() {
120            if let Some(child) = next.upgrade() {
121                let child = child.read().or_poisoned();
122                let contexts = &child.contexts;
123                if let Some(context) = contexts.get(&ty) {
124                    return context.downcast_ref::<T>().cloned();
125                }
126
127                to_search.extend(child.children.clone());
128            }
129        }
130
131        None
132    }
133}
134
135/// Provides a context value of type `T` to the current reactive [`Owner`]
136/// and all of its descendants. This can be accessed using [`use_context`].
137///
138/// This is useful for passing values down to components or functions lower in a
139/// hierarchy without needs to “prop drill” by passing them through each layer as
140/// arguments to a function or properties of a component.
141///
142/// Context works similarly to variable scope: a context that is provided higher in
143/// the reactive graph can be used lower down, but a context that is provided lower
144/// down cannot be used higher up.
145///
146/// ```rust
147/// # use reactive_graph::prelude::*;
148/// # use reactive_graph::owner::*;
149/// # let owner = Owner::new(); owner.set();
150/// # use reactive_graph::effect::Effect;
151/// # futures::executor::block_on(async move {
152/// # any_spawner::Executor::init_futures_executor();
153/// Effect::new(move |_| {
154///     println!("Provider");
155///     provide_context(42i32); // provide an i32
156///
157///     Effect::new(move |_| {
158///         println!("intermediate node");
159///
160///         Effect::new(move |_| {
161///             let value = use_context::<i32>()
162///                 .expect("could not find i32 in context");
163///             assert_eq!(value, 42);
164///         });
165///     });
166/// });
167/// # });
168/// ```
169///
170/// ## Context Shadowing
171///
172/// Only a single value of any type can be provided via context. If you need to provide multiple
173/// values of the same type, wrap each one in a "newtype" struct wrapper so that each one is a
174/// distinct type.
175///
176/// Providing a second value of the same type "lower" in the ownership tree will shadow the value,
177/// just as a second `let` declaration with the same variable name will shadow that variable.
178///
179/// ```rust
180/// # use reactive_graph::prelude::*;
181/// # use reactive_graph::owner::*;
182/// # let owner = Owner::new(); owner.set();
183/// # use reactive_graph::effect::Effect;
184/// # futures::executor::block_on(async move {
185/// # any_spawner::Executor::init_futures_executor();
186/// Effect::new(move |_| {
187///     println!("Provider");
188///     provide_context("foo"); // provide a &'static str
189///
190///     Effect::new(move |_| {
191///         // before we provide another value of the same type, we can access the old one
192///         assert_eq!(use_context::<&'static str>(), Some("foo"));
193///         // but providing another value of the same type shadows it
194///         provide_context("bar");
195///
196///         Effect::new(move |_| {
197///             assert_eq!(use_context::<&'static str>(), Some("bar"));
198///         });
199///     });
200/// });
201/// # });
202/// ```
203pub fn provide_context<T: Send + Sync + 'static>(value: T) {
204    if let Some(owner) = Owner::current() {
205        owner.provide_context(value);
206    }
207}
208
209/// Extracts a context value of type `T` from the reactive system.
210///
211/// This traverses the reactive ownership graph, beginning from the current reactive
212/// [`Owner`] and iterating through its parents, if any. When the value is found, it is cloned.
213///
214/// The context value should have been provided elsewhere using
215/// [`provide_context`](provide_context).
216///
217/// This is useful for passing values down to components or functions lower in a
218/// hierarchy without needs to “prop drill” by passing them through each layer as
219/// arguments to a function or properties of a component.
220///
221/// Context works similarly to variable scope: a context that is provided higher in
222/// the reactive graph can be used lower down, but a context that is provided lower
223/// in the tree cannot be used higher up.
224///
225/// While the term “consume” is sometimes used, note that [`use_context`] clones the value, rather
226/// than removing it; it is still accessible to other users.
227///
228/// ```rust
229/// # use reactive_graph::prelude::*;
230/// # use reactive_graph::owner::*;
231/// # let owner = Owner::new(); owner.set();
232/// # use reactive_graph::effect::Effect;
233/// # futures::executor::block_on(async move {
234/// # any_spawner::Executor::init_futures_executor();
235/// Effect::new(move |_| {
236///     provide_context(String::from("foo"));
237///
238///     Effect::new(move |_| {
239///         // each use_context clones the value
240///         let value = use_context::<String>()
241///             .expect("could not find String in context");
242///         assert_eq!(value, "foo");
243///         let value2 = use_context::<String>()
244///             .expect("could not find String in context");
245///         assert_eq!(value2, "foo");
246///     });
247/// });
248/// # });
249/// ```
250pub fn use_context<T: Clone + 'static>() -> Option<T> {
251    Owner::current().and_then(|owner| owner.use_context())
252}
253
254/// Extracts a context value of type `T` from the reactive system, and
255/// panics if it can't be found.
256///
257/// This traverses the reactive ownership graph, beginning from the current reactive
258/// [`Owner`] and iterating through its parents, if any. When the value is found, it is cloned.
259///
260/// Panics if no value is found.
261///
262/// The context value should have been provided elsewhere using
263/// [`provide_context`](provide_context).
264///
265/// This is useful for passing values down to components or functions lower in a
266/// hierarchy without needs to “prop drill” by passing them through each layer as
267/// arguments to a function or properties of a component.
268///
269/// Context works similarly to variable scope: a context that is provided higher in
270/// the reactive graph can be used lower down, but a context that is provided lower
271/// in the tree cannot be used higher up.
272///
273/// While the term “consume” is sometimes used, note that [`use_context`] clones the value, rather
274/// than removing it; it is still accessible to other users.
275///
276/// ```rust
277/// # use reactive_graph::prelude::*;
278/// # use reactive_graph::owner::*;
279/// # let owner = Owner::new(); owner.set();
280/// # use reactive_graph::effect::Effect;
281/// # futures::executor::block_on(async move {
282/// # any_spawner::Executor::init_futures_executor();
283/// Effect::new(move |_| {
284///     provide_context(String::from("foo"));
285///
286///     Effect::new(move |_| {
287///         // each use_context clones the value
288///         let value = use_context::<String>()
289///             .expect("could not find String in context");
290///         assert_eq!(value, "foo");
291///         let value2 = use_context::<String>()
292///             .expect("could not find String in context");
293///         assert_eq!(value2, "foo");
294///     });
295/// });
296/// # });
297/// ```
298/// ## Panics
299/// Panics if a context of this type is not found in the current reactive
300/// owner or its ancestors.
301#[track_caller]
302pub fn expect_context<T: Clone + 'static>() -> T {
303    let location = std::panic::Location::caller();
304
305    use_context().unwrap_or_else(|| {
306        panic!(
307            "{:?} expected context of type {:?} to be present",
308            location,
309            std::any::type_name::<T>()
310        )
311    })
312}
313
314/// Extracts a context value of type `T` from the reactive system, and takes ownership,
315/// removing it from the context system.
316///
317/// This traverses the reactive ownership graph, beginning from the current reactive
318/// [`Owner`] and iterating through its parents, if any. When the value is found, it is removed,
319/// and is not available to any other [`use_context`] or [`take_context`] calls.
320///
321/// If the value is `Clone`, use [`use_context`] instead.
322///
323/// The context value should have been provided elsewhere using
324/// [`provide_context`](provide_context).
325///
326/// This is useful for passing values down to components or functions lower in a
327/// hierarchy without needs to “prop drill” by passing them through each layer as
328/// arguments to a function or properties of a component.
329///
330/// Context works similarly to variable scope: a context that is provided higher in
331/// the reactive graph can be used lower down, but a context that is provided lower
332/// in the tree cannot be used higher up.
333/// ```rust
334/// # use reactive_graph::prelude::*;
335/// # use reactive_graph::owner::*;
336/// # let owner = Owner::new(); owner.set();
337/// # use reactive_graph::effect::Effect;
338/// # futures::executor::block_on(async move {
339/// # any_spawner::Executor::init_futures_executor();
340///
341/// #[derive(Debug, PartialEq)]
342/// struct NotClone(String);
343///
344/// Effect::new(move |_| {
345///     provide_context(NotClone(String::from("foo")));
346///
347///     Effect::new(move |_| {
348///         // take_context removes the value from context without needing to clone
349///         let value = take_context::<NotClone>();
350///         assert_eq!(value, Some(NotClone(String::from("foo"))));
351///         let value2 = take_context::<NotClone>();
352///         assert_eq!(value2, None);
353///     });
354/// });
355/// # });
356/// ```
357pub fn take_context<T: 'static>() -> Option<T> {
358    Owner::current().and_then(|owner| owner.take_context())
359}
360
361/// Access a reference to a context value of type `T` in the reactive system.
362///
363/// This traverses the reactive ownership graph, beginning from the current reactive
364/// [`Owner`] and iterating through its parents, if any. When the value is found,
365/// the function that you pass is applied to an immutable reference to it.
366///
367/// The context value should have been provided elsewhere using
368/// [`provide_context`](provide_context).
369///
370/// This is useful for passing values down to components or functions lower in a
371/// hierarchy without needs to “prop drill” by passing them through each layer as
372/// arguments to a function or properties of a component.
373///
374/// Context works similarly to variable scope: a context that is provided higher in
375/// the reactive graph can be used lower down, but a context that is provided lower
376/// in the tree cannot be used higher up.
377///
378/// ```rust
379/// # use reactive_graph::prelude::*;
380/// # use reactive_graph::owner::*;
381/// # let owner = Owner::new(); owner.set();
382/// # use reactive_graph::effect::Effect;
383/// # futures::executor::block_on(async move {
384/// # any_spawner::Executor::init_futures_executor();
385/// Effect::new(move |_| {
386///     provide_context(String::from("foo"));
387///
388///     Effect::new(move |_| {
389///         let value = with_context::<String, _>(|val| val.to_string())
390///             .expect("could not find String in context");
391///         assert_eq!(value, "foo");
392///     });
393/// });
394/// # });
395/// ```
396pub fn with_context<T: 'static, R>(cb: impl FnOnce(&T) -> R) -> Option<R> {
397    Owner::current().and_then(|owner| owner.with_context(cb))
398}
399
400/// Update a context value of type `T` in the reactive system.
401///
402/// This traverses the reactive ownership graph, beginning from the current reactive
403/// [`Owner`] and iterating through its parents, if any. When the value is found,
404/// the function that you pass is applied to a mutable reference to it.
405///
406/// The context value should have been provided elsewhere using
407/// [`provide_context`](provide_context).
408///
409/// This is useful for passing values down to components or functions lower in a
410/// hierarchy without needs to “prop drill” by passing them through each layer as
411/// arguments to a function or properties of a component.
412///
413/// Context works similarly to variable scope: a context that is provided higher in
414/// the reactive graph can be used lower down, but a context that is provided lower
415/// in the tree cannot be used higher up.
416///
417/// ```rust
418/// # use reactive_graph::prelude::*;
419/// # use reactive_graph::owner::*;
420/// # let owner = Owner::new(); owner.set();
421/// # use reactive_graph::effect::Effect;
422/// # futures::executor::block_on(async move {
423/// # any_spawner::Executor::init_futures_executor();
424/// Effect::new(move |_| {
425///     provide_context(String::from("foo"));
426///
427///     Effect::new(move |_| {
428///         let value = update_context::<String, _>(|val| {
429///             std::mem::replace(val, "bar".to_string())
430///         })
431///         .expect("could not find String in context");
432///         assert_eq!(value, "foo");
433///         assert_eq!(expect_context::<String>(), "bar");
434///     });
435/// });
436/// # });
437/// ```
438pub fn update_context<T: 'static, R>(
439    cb: impl FnOnce(&mut T) -> R,
440) -> Option<R> {
441    Owner::current().and_then(|owner| owner.update_context(cb))
442}