scope_context.rs 14 KB

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