scope.rs 17 KB

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