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}