scope.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. use crate::innerlude::*;
  2. use futures_channel::mpsc::UnboundedSender;
  3. use fxhash::FxHashMap;
  4. use std::{
  5. any::{Any, TypeId},
  6. cell::{Cell, RefCell},
  7. collections::HashMap,
  8. future::Future,
  9. rc::Rc,
  10. };
  11. use bumpalo::{boxed::Box as BumpBox, Bump};
  12. /// Components in Dioxus use the "Context" object to interact with their lifecycle.
  13. ///
  14. /// This lets components access props, schedule updates, integrate hooks, and expose shared state.
  15. ///
  16. /// For the most part, the only method you should be using regularly is `render`.
  17. ///
  18. /// ## Example
  19. ///
  20. /// ```ignore
  21. /// #[derive(Props)]
  22. /// struct ExampleProps {
  23. /// name: String
  24. /// }
  25. ///
  26. /// fn Example((cx, props): Scope<Props>) -> Element {
  27. /// cx.render(rsx!{ div {"Hello, {props.name}"} })
  28. /// }
  29. /// ```
  30. pub type Context<'a> = &'a Scope;
  31. /// Every component in Dioxus is represented by a `Scope`.
  32. ///
  33. /// Scopes contain the state for hooks, the component's props, and other lifecycle information.
  34. ///
  35. /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
  36. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
  37. ///
  38. /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
  39. /// use case they might have.
  40. pub struct Scope {
  41. // Book-keeping about our spot in the arena
  42. // safety:
  43. //
  44. // pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed
  45. // this is just a bit of a hack to not need an Rc to the ScopeArena.
  46. // todo: replace this will ScopeId and provide a connection to scope arena directly
  47. pub(crate) parent_scope: Option<*mut Scope>,
  48. pub(crate) our_arena_idx: ScopeId,
  49. pub(crate) height: u32,
  50. pub(crate) subtree: Cell<u32>,
  51. pub(crate) is_subtree_root: Cell<bool>,
  52. pub(crate) generation: Cell<u32>,
  53. // The double-buffering situation that we will use
  54. pub(crate) frames: [BumpFrame; 2],
  55. pub(crate) old_root: RefCell<Option<NodeLink>>,
  56. pub(crate) new_root: RefCell<Option<NodeLink>>,
  57. pub(crate) caller: *const dyn Fn(&Scope) -> Element,
  58. /*
  59. we care about:
  60. - listeners (and how to call them when an event is triggered)
  61. - borrowed props (and how to drop them when the parent is dropped)
  62. - suspended nodes (and how to call their callback when their associated tasks are complete)
  63. */
  64. pub(crate) items: RefCell<SelfReferentialItems<'static>>,
  65. // State
  66. pub(crate) hooks: HookList,
  67. // todo: move this into a centralized place - is more memory efficient
  68. pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
  69. pub(crate) sender: UnboundedSender<SchedulerMsg>,
  70. }
  71. pub struct SelfReferentialItems<'a> {
  72. pub(crate) listeners: Vec<&'a Listener<'a>>,
  73. pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
  74. pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended<'a>>,
  75. pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
  76. pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
  77. }
  78. /// A component's unique identifier.
  79. ///
  80. /// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
  81. /// unmounted, then the `ScopeId` will be reused for a new component.
  82. #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
  83. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
  84. pub struct ScopeId(pub usize);
  85. // Public methods exposed to libraries and components
  86. impl Scope {
  87. /// Get the subtree ID that this scope belongs to.
  88. ///
  89. /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
  90. /// the mutations to the correct window/portal/subtree.
  91. ///
  92. ///
  93. /// # Example
  94. ///
  95. /// ```rust
  96. /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
  97. /// dom.rebuild();
  98. ///
  99. /// let base = dom.base_scope();
  100. ///
  101. /// assert_eq!(base.subtree(), 0);
  102. /// ```
  103. pub fn subtree(&self) -> u32 {
  104. self.subtree.get()
  105. }
  106. /// Get the height of this Scope - IE the number of scopes above it.
  107. ///
  108. /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
  109. ///
  110. /// # Example
  111. ///
  112. /// ```rust
  113. /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
  114. /// dom.rebuild();
  115. ///
  116. /// let base = dom.base_scope();
  117. ///
  118. /// assert_eq!(base.height(), 0);
  119. /// ```
  120. pub fn height(&self) -> u32 {
  121. self.height
  122. }
  123. /// Get the Parent of this Scope within this Dioxus VirtualDOM.
  124. ///
  125. /// This ID is not unique across Dioxus VirtualDOMs or across time. IDs will be reused when components are unmounted.
  126. ///
  127. /// The base component will not have a parent, and will return `None`.
  128. ///
  129. /// # Example
  130. ///
  131. /// ```rust
  132. /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
  133. /// dom.rebuild();
  134. ///
  135. /// let base = dom.base_scope();
  136. ///
  137. /// assert_eq!(base.parent(), None);
  138. /// ```
  139. pub fn parent(&self) -> Option<ScopeId> {
  140. match self.parent_scope {
  141. Some(p) => Some(unsafe { &*p }.our_arena_idx),
  142. None => None,
  143. }
  144. }
  145. /// Get the ID of this Scope within this Dioxus VirtualDOM.
  146. ///
  147. /// This ID is not unique across Dioxus VirtualDOMs or across time. IDs will be reused when components are unmounted.
  148. ///
  149. /// # Example
  150. ///
  151. /// ```rust
  152. /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
  153. /// dom.rebuild();
  154. /// let base = dom.base_scope();
  155. ///
  156. /// assert_eq!(base.scope_id(), 0);
  157. /// ```
  158. pub fn scope_id(&self) -> ScopeId {
  159. self.our_arena_idx
  160. }
  161. /// Create a subscription that schedules a future render for the reference component
  162. ///
  163. /// ## Notice: you should prefer using prepare_update and get_scope_id
  164. pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
  165. // pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
  166. let chan = self.sender.clone();
  167. let id = self.scope_id();
  168. Rc::new(move || {
  169. let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
  170. })
  171. }
  172. /// Schedule an update for any component given its ScopeId.
  173. ///
  174. /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
  175. ///
  176. /// This method should be used when you want to schedule an update for a component
  177. pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
  178. let chan = self.sender.clone();
  179. Rc::new(move |id| {
  180. let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
  181. })
  182. }
  183. /// Get the [`ScopeId`] of a mounted component.
  184. ///
  185. /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
  186. pub fn needs_update(&self) {
  187. self.needs_update_any(self.scope_id())
  188. }
  189. /// Get the [`ScopeId`] of a mounted component.
  190. ///
  191. /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
  192. pub fn needs_update_any(&self, id: ScopeId) {
  193. let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
  194. }
  195. /// Get the [`ScopeId`] of a mounted component.
  196. ///
  197. /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
  198. pub fn bump(&self) -> &Bump {
  199. &self.wip_frame().bump
  200. }
  201. /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
  202. ///
  203. /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
  204. ///
  205. /// ## Example
  206. ///
  207. /// ```ignore
  208. /// fn Component(cx: Scope, props: &Props) -> Element {
  209. /// // Lazy assemble the VNode tree
  210. /// let lazy_nodes = rsx!("hello world");
  211. ///
  212. /// // Actually build the tree and allocate it
  213. /// cx.render(lazy_tree)
  214. /// }
  215. ///```
  216. pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
  217. let bump = &self.wip_frame().bump;
  218. let factory = NodeFactory { bump };
  219. let node = lazy_nodes.map(|f| f.call(factory))?;
  220. let idx = self
  221. .wip_frame()
  222. .add_node(unsafe { std::mem::transmute(node) });
  223. Some(NodeLink {
  224. gen_id: self.generation.get(),
  225. scope_id: self.our_arena_idx,
  226. link_idx: idx,
  227. })
  228. }
  229. /// Push an effect to be ran after the component has been successfully mounted to the dom
  230. /// Returns the effect's position in the stack
  231. pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
  232. // this is some tricker to get around not being able to actually call fnonces
  233. let mut slot = Some(effect);
  234. let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
  235. // wrap it in a type that will actually drop the contents
  236. let boxed_fut = unsafe { BumpBox::from_raw(fut) };
  237. // erase the 'src lifetime for self-referential storage
  238. let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
  239. let mut items = self.items.borrow_mut();
  240. items.pending_effects.push(self_ref_fut);
  241. items.pending_effects.len() - 1
  242. }
  243. /// Pushes the future onto the poll queue to be polled
  244. /// The future is forcibly dropped if the component is not ready by the next render
  245. pub fn push_task<'src>(&'src self, fut: impl Future<Output = ()> + 'src) -> usize {
  246. // allocate the future
  247. let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
  248. // wrap it in a type that will actually drop the contents
  249. let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
  250. // erase the 'src lifetime for self-referential storage
  251. let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
  252. let mut items = self.items.borrow_mut();
  253. items.tasks.push(self_ref_fut);
  254. items.tasks.len() - 1
  255. }
  256. /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
  257. ///
  258. /// This is a "fundamental" operation and should only be called during initialization of a hook.
  259. ///
  260. /// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead.
  261. ///
  262. /// When the component is dropped, so is the context. Be aware of this behavior when consuming
  263. /// the context via Rc/Weak.
  264. ///
  265. /// # Example
  266. ///
  267. /// ```
  268. /// struct SharedState(&'static str);
  269. ///
  270. /// static App: FC<()> = |(cx, props)|{
  271. /// cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {});
  272. /// rsx!(cx, Child {})
  273. /// }
  274. ///
  275. /// static Child: FC<()> = |(cx, props)|{
  276. /// let state = cx.consume_state::<SharedState>();
  277. /// rsx!(cx, div { "hello {state.0}" })
  278. /// }
  279. /// ```
  280. pub fn provide_state<T>(&self, value: T)
  281. where
  282. T: 'static,
  283. {
  284. self.shared_contexts
  285. .borrow_mut()
  286. .insert(TypeId::of::<T>(), Rc::new(value))
  287. .map(|f| f.downcast::<T>().ok())
  288. .flatten();
  289. }
  290. /// Try to retrieve a SharedState with type T from the any parent Scope.
  291. pub fn consume_state<T: 'static>(&self) -> Option<Rc<T>> {
  292. if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
  293. Some(shared.clone().downcast::<T>().unwrap())
  294. } else {
  295. let mut search_parent = self.parent_scope;
  296. while let Some(parent_ptr) = search_parent {
  297. let parent = unsafe { &*parent_ptr };
  298. if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
  299. return Some(shared.clone().downcast::<T>().unwrap());
  300. }
  301. search_parent = parent.parent_scope;
  302. }
  303. None
  304. }
  305. }
  306. /// Create a new subtree with this scope as the root of the subtree.
  307. ///
  308. /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
  309. /// the mutations to the correct window/portal/subtree.
  310. ///
  311. /// This method
  312. ///
  313. /// # Example
  314. ///
  315. /// ```rust
  316. /// static App: FC<()> = |(cx, props)| {
  317. /// todo!();
  318. /// rsx!(cx, div { "Subtree {id}"})
  319. /// };
  320. /// ```
  321. pub fn create_subtree(&self) -> Option<u32> {
  322. self.new_subtree()
  323. }
  324. /// Get the subtree ID that this scope belongs to.
  325. ///
  326. /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
  327. /// the mutations to the correct window/portal/subtree.
  328. ///
  329. /// # Example
  330. ///
  331. /// ```rust
  332. /// static App: FC<()> = |(cx, props)| {
  333. /// let id = cx.get_current_subtree();
  334. /// rsx!(cx, div { "Subtree {id}"})
  335. /// };
  336. /// ```
  337. pub fn get_current_subtree(&self) -> u32 {
  338. self.subtree()
  339. }
  340. /// Store a value between renders
  341. ///
  342. /// This is *the* foundational hook for all other hooks.
  343. ///
  344. /// - Initializer: closure used to create the initial hook state
  345. /// - Runner: closure used to output a value every time the hook is used
  346. ///
  347. /// To "cleanup" the hook, implement `Drop` on the stored hook value. Whenever the component is dropped, the hook
  348. /// will be dropped as well.
  349. ///
  350. /// # Example
  351. ///
  352. /// ```ignore
  353. /// // use_ref is the simplest way of storing a value between renders
  354. /// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
  355. /// use_hook(
  356. /// || Rc::new(RefCell::new(initial_value())),
  357. /// |state| state,
  358. /// )
  359. /// }
  360. /// ```
  361. pub fn use_hook<'src, State: 'static, Output: 'src>(
  362. &'src self,
  363. initializer: impl FnOnce(usize) -> State,
  364. runner: impl FnOnce(&'src mut State) -> Output,
  365. ) -> Output {
  366. if self.hooks.at_end() {
  367. self.hooks.push_hook(initializer(self.hooks.len()));
  368. }
  369. const HOOK_ERR_MSG: &str = r###"
  370. Unable to retrieve the hook that was initialized at this index.
  371. Consult the `rules of hooks` to understand how to use hooks properly.
  372. You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
  373. Functions prefixed with "use" should never be called conditionally.
  374. "###;
  375. runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
  376. }
  377. }
  378. // Important internal methods
  379. impl Scope {
  380. /// The "work in progress frame" represents the frame that is currently being worked on.
  381. pub(crate) fn wip_frame(&self) -> &BumpFrame {
  382. match self.generation.get() & 1 == 0 {
  383. true => &self.frames[0],
  384. false => &self.frames[1],
  385. }
  386. }
  387. pub(crate) fn fin_frame(&self) -> &BumpFrame {
  388. match self.generation.get() & 1 == 1 {
  389. true => &self.frames[0],
  390. false => &self.frames[1],
  391. }
  392. }
  393. pub unsafe fn reset_wip_frame(&self) {
  394. // todo: unsafecell or something
  395. let bump = self.wip_frame() as *const _ as *mut Bump;
  396. let g = &mut *bump;
  397. g.reset();
  398. }
  399. pub fn cycle_frame(&self) {
  400. self.generation.set(self.generation.get() + 1);
  401. }
  402. /// A safe wrapper around calling listeners
  403. pub(crate) fn call_listener(&self, event: UserEvent, element: ElementId) {
  404. let listners = &mut self.items.borrow_mut().listeners;
  405. let listener = listners.iter().find(|lis| {
  406. let search = lis;
  407. if search.event == event.name {
  408. let search_id = search.mounted_node.get();
  409. search_id.map(|f| f == element).unwrap_or(false)
  410. } else {
  411. false
  412. }
  413. });
  414. if let Some(listener) = listener {
  415. let mut cb = listener.callback.borrow_mut();
  416. if let Some(cb) = cb.as_mut() {
  417. (cb)(event.event);
  418. }
  419. } else {
  420. log::warn!("An event was triggered but there was no listener to handle it");
  421. }
  422. }
  423. // General strategy here is to load up the appropriate suspended task and then run it.
  424. // Suspended nodes cannot be called repeatedly.
  425. pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
  426. let mut nodes = &mut self.items.get_mut().suspended_nodes;
  427. if let Some(suspended) = nodes.remove(&task_id) {
  428. let sus: &'a VSuspended<'static> = unsafe { &*suspended };
  429. let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
  430. let mut boxed = sus.callback.borrow_mut().take().unwrap();
  431. let new_node: Element = boxed();
  432. }
  433. }
  434. // run the list of effects
  435. pub(crate) fn run_effects(&mut self) {
  436. for mut effect in self.items.get_mut().pending_effects.drain(..) {
  437. effect();
  438. }
  439. }
  440. pub(crate) fn new_subtree(&self) -> Option<u32> {
  441. todo!()
  442. // if self.is_subtree_root.get() {
  443. // None
  444. // } else {
  445. // let cur = self.shared.cur_subtree.get();
  446. // self.shared.cur_subtree.set(cur + 1);
  447. // Some(cur)
  448. // }
  449. }
  450. }
  451. pub struct BumpFrame {
  452. pub bump: Bump,
  453. pub nodes: RefCell<Vec<VNode<'static>>>,
  454. }
  455. impl BumpFrame {
  456. pub fn new() -> Self {
  457. let bump = Bump::new();
  458. let node = &*bump.alloc(VText {
  459. text: "asd",
  460. dom_id: Default::default(),
  461. is_static: false,
  462. });
  463. let nodes = RefCell::new(vec![VNode::Text(unsafe { std::mem::transmute(node) })]);
  464. Self { bump, nodes }
  465. }
  466. fn add_node(&self, node: VNode<'static>) -> usize {
  467. let mut nodes = self.nodes.borrow_mut();
  468. nodes.push(node);
  469. nodes.len() - 1
  470. }
  471. }