scope_context.rs 14 KB

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