hooks.rs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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::RefCell,
  16. future::Future,
  17. rc::Rc,
  18. };
  19. /// Awaits the given task, forcing the component to re-render when the value is ready.
  20. ///
  21. ///
  22. ///
  23. ///
  24. pub fn use_task<'src, Out, Fut, Init, P>(
  25. cx: Context<'src, P>,
  26. task_initializer: Init,
  27. ) -> (&'src TaskHandle, &'src Option<Out>)
  28. where
  29. Out: 'static,
  30. Fut: Future<Output = Out> + 'static,
  31. Init: FnOnce() -> Fut + 'src,
  32. {
  33. struct TaskHook<T> {
  34. handle: TaskHandle,
  35. task_dump: Rc<RefCell<Option<T>>>,
  36. value: Option<T>,
  37. }
  38. // whenever the task is complete, save it into th
  39. cx.use_hook(
  40. move |_| {
  41. let task_fut = task_initializer();
  42. let task_dump = Rc::new(RefCell::new(None));
  43. let slot = task_dump.clone();
  44. let updater = cx.prepare_update();
  45. let originator = cx.scope.our_arena_idx;
  46. let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
  47. *slot.as_ref().borrow_mut() = Some(output);
  48. updater(originator);
  49. originator
  50. })));
  51. TaskHook {
  52. task_dump,
  53. value: None,
  54. handle,
  55. }
  56. },
  57. |hook| {
  58. if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
  59. hook.value = Some(val);
  60. }
  61. (&hook.handle, &hook.value)
  62. },
  63. |_| {},
  64. )
  65. }
  66. /// Asynchronously render new nodes once the given future has completed.
  67. ///
  68. /// # Easda
  69. ///
  70. ///
  71. ///
  72. ///
  73. /// # Example
  74. ///
  75. ///
  76. pub fn use_suspense<'src, Out, Fut, Cb, P>(
  77. cx: Context<'src, P>,
  78. task_initializer: impl FnOnce() -> Fut,
  79. user_callback: Cb,
  80. ) -> DomTree<'src>
  81. where
  82. Fut: Future<Output = Out> + 'static,
  83. Out: 'static,
  84. Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
  85. {
  86. /*
  87. General strategy:
  88. - Create a slot for the future to dump its output into
  89. - Create a new future feeding off the user's future that feeds the output into that slot
  90. - Submit that future as a task
  91. - Take the task handle id and attach that to our suspended node
  92. - when the hook runs, check if the value exists
  93. - if it does, then we can render the node directly
  94. - if it doesn't, then we render a suspended node along with with the callback and task id
  95. */
  96. cx.use_hook(
  97. move |hook_idx| {
  98. let value = Rc::new(RefCell::new(None));
  99. let slot = value.clone();
  100. let originator = cx.scope.our_arena_idx.clone();
  101. let handle = cx.submit_task(Box::pin(task_initializer().then(
  102. move |output| async move {
  103. *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
  104. originator
  105. },
  106. )));
  107. SuspenseHook { handle, value }
  108. },
  109. move |hook| match hook.value.borrow().as_ref() {
  110. Some(value) => {
  111. let out = value.downcast_ref::<Out>().unwrap();
  112. let sus = SuspendedContext {
  113. inner: Context {
  114. props: &(),
  115. scope: cx.scope,
  116. },
  117. };
  118. user_callback(sus, out)
  119. }
  120. None => {
  121. let value = hook.value.clone();
  122. cx.render(LazyNodes::new(|f| {
  123. let bump = f.bump();
  124. use bumpalo::boxed::Box as BumpBox;
  125. let f: &mut dyn FnMut(SuspendedContext<'src>) -> DomTree<'src> =
  126. bump.alloc(move |sus| {
  127. let val = value.borrow();
  128. let out = val
  129. .as_ref()
  130. .unwrap()
  131. .as_ref()
  132. .downcast_ref::<Out>()
  133. .unwrap();
  134. user_callback(sus, out)
  135. });
  136. let callback = unsafe { BumpBox::from_raw(f) };
  137. VNode::Suspended(bump.alloc(VSuspended {
  138. dom_id: empty_cell(),
  139. task_id: hook.handle.our_id,
  140. callback: RefCell::new(Some(callback)),
  141. }))
  142. }))
  143. }
  144. },
  145. |_| {},
  146. )
  147. }
  148. pub(crate) struct SuspenseHook {
  149. pub handle: TaskHandle,
  150. pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
  151. }
  152. type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
  153. pub struct SuspendedContext<'a> {
  154. pub(crate) inner: Context<'a, ()>,
  155. }
  156. impl<'src> SuspendedContext<'src> {
  157. pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
  158. self,
  159. lazy_nodes: LazyNodes<'src, F>,
  160. ) -> DomTree<'src> {
  161. let scope_ref = self.inner.scope;
  162. let bump = &self.inner.scope.frames.wip_frame().bump;
  163. Some(lazy_nodes.into_vnode(NodeFactory { bump }))
  164. }
  165. }
  166. #[derive(Clone, Copy)]
  167. pub struct NodeRef<'src, T: 'static>(&'src RefCell<T>);
  168. pub fn use_node_ref<T, P>(cx: Context<P>) -> NodeRef<T> {
  169. cx.use_hook(
  170. |f| {},
  171. |f| {
  172. //
  173. todo!()
  174. },
  175. |f| {
  176. //
  177. },
  178. )
  179. }