scope_context.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. use crate::{
  2. innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
  3. runtime::{with_current_scope, with_runtime},
  4. Element, ScopeId, TaskId,
  5. };
  6. use rustc_hash::FxHashSet;
  7. use std::{
  8. any::Any,
  9. cell::{Cell, RefCell},
  10. fmt::Debug,
  11. future::Future,
  12. rc::Rc,
  13. sync::Arc,
  14. };
  15. /// A component's state separate from its props.
  16. ///
  17. /// This struct exists to provide a common interface for all scopes without relying on generics.
  18. pub(crate) struct ScopeContext {
  19. pub(crate) name: &'static str,
  20. pub(crate) id: ScopeId,
  21. pub(crate) parent_id: Option<ScopeId>,
  22. pub(crate) height: u32,
  23. pub(crate) suspended: Cell<bool>,
  24. pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
  25. pub(crate) tasks: Rc<Scheduler>,
  26. pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
  27. }
  28. impl ScopeContext {
  29. pub(crate) fn new(
  30. name: &'static str,
  31. id: ScopeId,
  32. parent_id: Option<ScopeId>,
  33. height: u32,
  34. tasks: Rc<Scheduler>,
  35. ) -> Self {
  36. Self {
  37. name,
  38. id,
  39. parent_id,
  40. height,
  41. suspended: Cell::new(false),
  42. shared_contexts: RefCell::new(vec![]),
  43. tasks,
  44. spawned_tasks: RefCell::new(FxHashSet::default()),
  45. }
  46. }
  47. pub fn parent_id(&self) -> Option<ScopeId> {
  48. self.parent_id
  49. }
  50. pub fn scope_id(&self) -> ScopeId {
  51. self.id
  52. }
  53. /// Create a subscription that schedules a future render for the reference component
  54. ///
  55. /// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
  56. pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
  57. let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
  58. Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
  59. }
  60. /// Schedule an update for any component given its [`ScopeId`].
  61. ///
  62. /// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
  63. ///
  64. /// This method should be used when you want to schedule an update for a component
  65. pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
  66. let chan = self.tasks.sender.clone();
  67. Arc::new(move |id| {
  68. chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap();
  69. })
  70. }
  71. /// Mark this scope as dirty, and schedule a render for it.
  72. pub fn needs_update(&self) {
  73. self.needs_update_any(self.scope_id());
  74. }
  75. /// Get the [`ScopeId`] of a mounted component.
  76. ///
  77. /// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
  78. pub fn needs_update_any(&self, id: ScopeId) {
  79. self.tasks
  80. .sender
  81. .unbounded_send(SchedulerMsg::Immediate(id))
  82. .expect("Scheduler to exist if scope exists");
  83. }
  84. /// Return any context of type T if it exists on this scope
  85. pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
  86. self.shared_contexts
  87. .borrow()
  88. .iter()
  89. .find_map(|any| any.downcast_ref::<T>())
  90. .cloned()
  91. }
  92. /// Try to retrieve a shared state with type `T` from any parent scope.
  93. ///
  94. /// Clones the state if it exists.
  95. pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
  96. tracing::trace!(
  97. "looking for context {} ({:?}) in {}",
  98. std::any::type_name::<T>(),
  99. std::any::TypeId::of::<T>(),
  100. self.name
  101. );
  102. if let Some(this_ctx) = self.has_context() {
  103. return Some(this_ctx);
  104. }
  105. let mut search_parent = self.parent_id;
  106. match with_runtime(|runtime: &crate::runtime::Runtime| {
  107. while let Some(parent_id) = search_parent {
  108. let parent = runtime.get_context(parent_id).unwrap();
  109. tracing::trace!(
  110. "looking for context {} ({:?}) in {}",
  111. std::any::type_name::<T>(),
  112. std::any::TypeId::of::<T>(),
  113. parent.name
  114. );
  115. if let Some(shared) = parent.shared_contexts.borrow().iter().find_map(|any| {
  116. tracing::trace!("found context {:?}", (**any).type_id());
  117. any.downcast_ref::<T>()
  118. }) {
  119. return Some(shared.clone());
  120. }
  121. search_parent = parent.parent_id;
  122. }
  123. None
  124. })
  125. .flatten()
  126. {
  127. Some(ctx) => Some(ctx),
  128. None => {
  129. tracing::trace!(
  130. "context {} ({:?}) not found",
  131. std::any::type_name::<T>(),
  132. std::any::TypeId::of::<T>()
  133. );
  134. None
  135. }
  136. }
  137. }
  138. /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
  139. ///
  140. /// This is a "fundamental" operation and should only be called during initialization of a hook.
  141. ///
  142. /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.
  143. ///
  144. /// # Example
  145. ///
  146. /// ```rust, ignore
  147. /// struct SharedState(&'static str);
  148. ///
  149. /// static App: Component = |cx| {
  150. /// cx.use_hook(|| cx.provide_context(SharedState("world")));
  151. /// render!(Child {})
  152. /// }
  153. ///
  154. /// static Child: Component = |cx| {
  155. /// let state = cx.consume_state::<SharedState>();
  156. /// render!(div { "hello {state.0}" })
  157. /// }
  158. /// ```
  159. pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
  160. tracing::trace!(
  161. "providing context {} ({:?}) in {}",
  162. std::any::type_name::<T>(),
  163. std::any::TypeId::of::<T>(),
  164. self.name
  165. );
  166. let mut contexts = self.shared_contexts.borrow_mut();
  167. // If the context exists, swap it out for the new value
  168. for ctx in contexts.iter_mut() {
  169. // Swap the ptr directly
  170. if let Some(ctx) = ctx.downcast_mut::<T>() {
  171. std::mem::swap(ctx, &mut value.clone());
  172. return value;
  173. }
  174. }
  175. // Else, just push it
  176. contexts.push(Box::new(value.clone()));
  177. value
  178. }
  179. /// Provide a context to the root and then consume it
  180. ///
  181. /// This is intended for "global" state management solutions that would rather be implicit for the entire app.
  182. /// Things like signal runtimes and routers are examples of "singletons" that would benefit from lazy initialization.
  183. ///
  184. /// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
  185. /// when a context already exists will swap the context out for the new one, which may not be what you want.
  186. pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
  187. with_runtime(|runtime| {
  188. runtime
  189. .get_context(ScopeId::ROOT)
  190. .unwrap()
  191. .provide_context(context)
  192. })
  193. .expect("Runtime to exist")
  194. }
  195. /// Pushes the future onto the poll queue to be polled after the component renders.
  196. pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
  197. let id = self.tasks.spawn(self.id, fut);
  198. self.spawned_tasks.borrow_mut().insert(id);
  199. id
  200. }
  201. /// Spawns the future but does not return the [`TaskId`]
  202. pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
  203. self.push_future(fut);
  204. }
  205. /// Spawn a future that Dioxus won't clean up when this component is unmounted
  206. ///
  207. /// This is good for tasks that need to be run after the component has been dropped.
  208. pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
  209. // The root scope will never be unmounted so we can just add the task at the top of the app
  210. self.tasks.spawn(ScopeId::ROOT, fut)
  211. }
  212. /// Informs the scheduler that this task is no longer needed and should be removed.
  213. ///
  214. /// This drops the task immediately.
  215. pub fn remove_future(&self, id: TaskId) {
  216. self.tasks.remove(id);
  217. }
  218. /// Inject an error into the nearest error boundary and quit rendering
  219. ///
  220. /// The error doesn't need to implement Error or any specific traits since the boundary
  221. /// itself will downcast the error into a trait object.
  222. pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
  223. if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
  224. cx.insert_error(self.scope_id(), Box::new(error));
  225. }
  226. // Always return none during a throw
  227. None
  228. }
  229. /// Mark this component as suspended and then return None
  230. pub fn suspend(&self) -> Option<Element> {
  231. self.suspended.set(true);
  232. None
  233. }
  234. }
  235. /// Schedule an update for any component given its [`ScopeId`].
  236. ///
  237. /// A component's [`ScopeId`] can be obtained from `use_hook` or the [`crate::scopes::ScopeState::scope_id`] method.
  238. ///
  239. /// This method should be used when you want to schedule an update for a component
  240. pub fn schedule_update_any() -> Option<Arc<dyn Fn(ScopeId) + Send + Sync>> {
  241. with_current_scope(|cx| cx.schedule_update_any())
  242. }
  243. /// Get the current scope id
  244. pub fn current_scope_id() -> Option<ScopeId> {
  245. with_runtime(|rt| rt.current_scope_id()).flatten()
  246. }
  247. #[doc(hidden)]
  248. /// Check if the virtual dom is currently inside of the body of a component
  249. pub fn vdom_is_rendering() -> bool {
  250. with_runtime(|rt| rt.rendering.get()).unwrap_or_default()
  251. }
  252. /// Consume context from the current scope
  253. pub fn consume_context<T: 'static + Clone>() -> Option<T> {
  254. with_current_scope(|cx| cx.consume_context::<T>()).flatten()
  255. }
  256. /// Consume context from the current scope
  257. pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
  258. with_runtime(|rt| {
  259. rt.get_context(scope_id)
  260. .and_then(|cx| cx.consume_context::<T>())
  261. })
  262. .flatten()
  263. }
  264. /// Check if the current scope has a context
  265. pub fn has_context<T: 'static + Clone>() -> Option<T> {
  266. with_current_scope(|cx| cx.has_context::<T>()).flatten()
  267. }
  268. /// Provide context to the current scope
  269. pub fn provide_context<T: 'static + Clone>(value: T) -> Option<T> {
  270. with_current_scope(|cx| cx.provide_context(value))
  271. }
  272. /// Provide context to the the given scope
  273. pub fn provide_context_to_scope<T: 'static + Clone>(scope_id: ScopeId, value: T) -> Option<T> {
  274. with_runtime(|rt| rt.get_context(scope_id).map(|cx| cx.provide_context(value))).flatten()
  275. }
  276. /// Provide a context to the root scope
  277. pub fn provide_root_context<T: 'static + Clone>(value: T) -> Option<T> {
  278. with_current_scope(|cx| cx.provide_root_context(value))
  279. }
  280. /// Suspends the current component
  281. pub fn suspend() -> Option<Element<'static>> {
  282. with_current_scope(|cx| {
  283. cx.suspend();
  284. });
  285. None
  286. }
  287. /// Throw an error into the nearest error boundary
  288. pub fn throw(error: impl Debug + 'static) -> Option<()> {
  289. with_current_scope(|cx| cx.throw(error)).flatten()
  290. }
  291. /// Pushes the future onto the poll queue to be polled after the component renders.
  292. pub fn push_future(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
  293. with_current_scope(|cx| cx.push_future(fut))
  294. }
  295. /// Spawns the future but does not return the [`TaskId`]
  296. pub fn spawn(fut: impl Future<Output = ()> + 'static) {
  297. with_current_scope(|cx| cx.spawn(fut));
  298. }
  299. /// Spawn a future that Dioxus won't clean up when this component is unmounted
  300. ///
  301. /// This is good for tasks that need to be run after the component has been dropped.
  302. pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
  303. with_current_scope(|cx| cx.spawn_forever(fut))
  304. }
  305. /// Informs the scheduler that this task is no longer needed and should be removed.
  306. ///
  307. /// This drops the task immediately.
  308. pub fn remove_future(id: TaskId) {
  309. with_current_scope(|cx| cx.remove_future(id));
  310. }