hooks.rs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. //! Built-in hooks
  2. //!
  3. //! This module contains all the low-level built-in hooks that require 1st party support to work.
  4. //!
  5. //! Hooks:
  6. //! - use_hook
  7. //! - use_state_provider
  8. //! - use_state_consumer
  9. //! - use_task
  10. //! - use_suspense
  11. use crate::innerlude::*;
  12. use futures_util::FutureExt;
  13. use std::{
  14. any::{Any, TypeId},
  15. cell::{Cell, RefCell},
  16. future::Future,
  17. rc::Rc,
  18. };
  19. /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
  20. ///
  21. /// This is a hook, so it may not be called conditionally!
  22. ///
  23. /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
  24. /// so don't put it in a conditional.
  25. ///
  26. /// When the component is dropped, so is the context. Be aware of this behavior when consuming
  27. /// the context via Rc/Weak.
  28. ///
  29. ///
  30. ///
  31. pub fn use_provide_state<'src, Pr, T, F>(cx: Context<'src, Pr>, init: F) -> &'src Rc<T>
  32. where
  33. T: 'static,
  34. F: FnOnce() -> T,
  35. {
  36. let ty = TypeId::of::<T>();
  37. let contains_key = cx.scope.shared_contexts.borrow().contains_key(&ty);
  38. let is_initialized = cx.use_hook(
  39. |_| false,
  40. |s| {
  41. let i = s.clone();
  42. *s = true;
  43. i
  44. },
  45. |_| {},
  46. );
  47. match (is_initialized, contains_key) {
  48. // Do nothing, already initialized and already exists
  49. (true, true) => {}
  50. // Needs to be initialized
  51. (false, false) => {
  52. log::debug!("Initializing context...");
  53. cx.add_shared_state(init());
  54. log::info!(
  55. "There are now {} shared contexts for scope {:?}",
  56. cx.scope.shared_contexts.borrow().len(),
  57. cx.scope.our_arena_idx,
  58. );
  59. }
  60. _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
  61. };
  62. use_consume_state::<T, _>(cx)
  63. }
  64. /// There are hooks going on here!
  65. pub fn use_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> &'src Rc<T> {
  66. use_try_consume_state::<T, _>(cx).unwrap()
  67. }
  68. /// Uses a context, storing the cached value around
  69. ///
  70. /// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a
  71. /// context was added later. This allows using another hook as a fallback
  72. ///
  73. pub fn use_try_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> Option<&'src Rc<T>> {
  74. struct UseContextHook<C>(Option<Rc<C>>);
  75. cx.use_hook(
  76. move |_| UseContextHook(cx.consume_shared_state::<T>()),
  77. move |hook| hook.0.as_ref(),
  78. |_| {},
  79. )
  80. }
  81. /// Awaits the given task, forcing the component to re-render when the value is ready.
  82. ///
  83. ///
  84. ///
  85. ///
  86. pub fn use_task<'src, Out, Fut, Init, P>(
  87. cx: Context<'src, P>,
  88. task_initializer: Init,
  89. ) -> (&'src TaskHandle, &'src Option<Out>)
  90. where
  91. Out: 'static,
  92. Fut: Future<Output = Out> + 'static,
  93. Init: FnOnce() -> Fut + 'src,
  94. {
  95. struct TaskHook<T> {
  96. handle: TaskHandle,
  97. task_dump: Rc<RefCell<Option<T>>>,
  98. value: Option<T>,
  99. }
  100. // whenever the task is complete, save it into th
  101. cx.use_hook(
  102. move |_| {
  103. let task_fut = task_initializer();
  104. let task_dump = Rc::new(RefCell::new(None));
  105. let slot = task_dump.clone();
  106. let updater = cx.prepare_update();
  107. let update_id = cx.get_scope_id();
  108. let originator = cx.scope.our_arena_idx.clone();
  109. let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
  110. *slot.as_ref().borrow_mut() = Some(output);
  111. updater(update_id);
  112. originator
  113. })));
  114. TaskHook {
  115. task_dump,
  116. value: None,
  117. handle,
  118. }
  119. },
  120. |hook| {
  121. if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
  122. hook.value = Some(val);
  123. }
  124. (&hook.handle, &hook.value)
  125. },
  126. |_| {},
  127. )
  128. }
  129. /// Asynchronously render new nodes once the given future has completed.
  130. ///
  131. /// # Easda
  132. ///
  133. ///
  134. ///
  135. ///
  136. /// # Example
  137. ///
  138. ///
  139. pub fn use_suspense<'src, Out, Fut, Cb, P>(
  140. cx: Context<'src, P>,
  141. task_initializer: impl FnOnce() -> Fut,
  142. user_callback: Cb,
  143. ) -> DomTree<'src>
  144. where
  145. Fut: Future<Output = Out> + 'static,
  146. Out: 'static,
  147. Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
  148. {
  149. /*
  150. General strategy:
  151. - Create a slot for the future to dump its output into
  152. - Create a new future feeding off the user's future that feeds the output into that slot
  153. - Submit that future as a task
  154. - Take the task handle id and attach that to our suspended node
  155. - when the hook runs, check if the value exists
  156. - if it does, then we can render the node directly
  157. - if it doesn't, then we render a suspended node along with with the callback and task id
  158. */
  159. cx.use_hook(
  160. move |hook_idx| {
  161. let value = Rc::new(RefCell::new(None));
  162. let slot = value.clone();
  163. let originator = cx.scope.our_arena_idx.clone();
  164. let handle = cx.submit_task(Box::pin(task_initializer().then(
  165. move |output| async move {
  166. *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
  167. originator
  168. },
  169. )));
  170. SuspenseHook { handle, value }
  171. },
  172. move |hook| match hook.value.borrow().as_ref() {
  173. Some(value) => {
  174. let out = value.downcast_ref::<Out>().unwrap();
  175. let sus = SuspendedContext {
  176. inner: Context {
  177. props: &(),
  178. scope: cx.scope,
  179. },
  180. };
  181. user_callback(sus, out)
  182. }
  183. None => {
  184. let value = hook.value.clone();
  185. cx.render(LazyNodes::new(|f| {
  186. let bump = f.bump();
  187. let g: &dyn FnOnce(SuspendedContext<'src>) -> DomTree<'src> =
  188. bump.alloc(|sus| {
  189. let out = value
  190. .borrow()
  191. .as_ref()
  192. .unwrap()
  193. .as_ref()
  194. .downcast_ref::<Out>()
  195. .unwrap();
  196. user_callback(sus, out)
  197. });
  198. VNode::Suspended(bump.alloc(VSuspended {
  199. dom_id: empty_cell(),
  200. task_id: hook.handle.our_id,
  201. callback: RefCell::new(Some(g)),
  202. }))
  203. }))
  204. }
  205. },
  206. |_| {},
  207. )
  208. }
  209. pub(crate) struct SuspenseHook {
  210. pub handle: TaskHandle,
  211. pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
  212. }
  213. type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
  214. pub struct SuspendedContext<'a> {
  215. pub(crate) inner: Context<'a, ()>,
  216. }
  217. impl<'src> SuspendedContext<'src> {
  218. pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
  219. self,
  220. lazy_nodes: LazyNodes<'src, F>,
  221. ) -> DomTree<'src> {
  222. let scope_ref = self.inner.scope;
  223. let bump = &self.inner.scope.frames.wip_frame().bump;
  224. Some(lazy_nodes.into_vnode(NodeFactory { bump }))
  225. }
  226. }
  227. #[derive(Clone, Copy)]
  228. pub struct NodeRef<'src, T: 'static>(&'src RefCell<T>);
  229. pub fn use_node_ref<T, P>(cx: Context<P>) -> NodeRef<T> {
  230. cx.use_hook(
  231. |f| {},
  232. |f| {
  233. //
  234. todo!()
  235. },
  236. |f| {
  237. //
  238. },
  239. )
  240. }