From d2f0547692cb8b4594fbec1d9fa0934e169d1706 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 9 Nov 2021 12:10:11 -0500 Subject: [PATCH] wip: move testdom methods into virtualdom --- README.md | 14 +- packages/core/src/diff.rs | 57 +++----- packages/core/src/lib.rs | 9 +- packages/core/src/nodes.rs | 9 +- packages/core/src/scope.rs | 25 ++-- packages/core/src/scopearena.rs | 64 +++------ packages/core/src/test_dom.rs | 93 ------------- packages/core/src/virtual_dom.rs | 194 ++++++++++++++++++++++------ packages/core/tests/diffing.rs | 52 ++++---- packages/core/tests/display_vdom.rs | 38 ------ packages/core/tests/sharedstate.rs | 2 +- 11 files changed, 247 insertions(+), 310 deletions(-) delete mode 100644 packages/core/src/test_dom.rs delete mode 100644 packages/core/tests/display_vdom.rs diff --git a/README.md b/README.md index f27f8236f..9b0060b43 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust. ```rust -fn App((cx, props): Scope<()>) -> Element { +fn App(cx: Context, props: &()) -> Element { let mut count = use_state(cx, || 0); cx.render(rsx!( @@ -141,16 +141,16 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | NodeRef | ✅ | ✅ | gain direct access to nodes | | Re-hydration | ✅ | ✅ | Pre-render to HTML to speed up first contentful paint | | Jank-Free Rendering | ✅ | ✅ | Large diffs are segmented across frames for silky-smooth transitions | -| Cooperative Scheduling | ✅ | ✅ | Prioritize important events over non-important events | +| Effects | ✅ | ✅ | Run effects after a component has been committed to render | +| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events | +| Server Components | 🛠 | ✅ | Hybrid components for SPA and Server | +| Bundle Splitting | 👀 | ✅ | Efficiently and asynchronously load the app | +| Lazy Components | 👀 | ✅ | Dynamically load the new components as the page is loaded | +| 1st class global state | ✅ | ✅ | redux/recoil/mobx on top of context | | Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) | -| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context | | Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees | | Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | | Heuristic Engine | ✅ | ❓ | track component memory usage to minimize future allocations | -| Effects | 🛠 | ✅ | Run effects after a component has been committed to render | -| Server Components | 🛠 | ✅ | Hybrid components for SPA and Server | -| Bundle Splitting | 👀 | ✅ | Hybrid components for SPA and Server | -| Lazy Components | 👀 | ✅ | Dynamically load the new components as the page is loaded | | Fine-grained reactivity | 👀 | ❓ | Skip diffing for fine-grain updates | - ✅ = implemented and working diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 4c68aed42..abc6626da 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -89,9 +89,7 @@ //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ use crate::innerlude::*; -use futures_channel::mpsc::UnboundedSender; use fxhash::{FxHashMap, FxHashSet}; -use slab::Slab; use DomEdit::*; /// Our DiffMachine is an iterative tree differ. @@ -325,30 +323,20 @@ impl<'bump> DiffState<'bump> { } fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { - // let caller = vcomponent.caller; - let parent_idx = self.stack.current_scope().unwrap(); - let shared: UnboundedSender = todo!(); - // let shared: UnboundedSender = self.sender.clone(); - // Insert a new scope into our component list let parent_scope = self.scopes.get_scope(&parent_idx).unwrap(); + let height = parent_scope.height + 1; + let subtree = parent_scope.subtree.get(); - let new_idx: ScopeId = todo!(); - // self - // .new_with_key(fc_ptr, vcomp, parent_scope, height, subtree, sender); + let parent_scope = unsafe { self.scopes.get_scope_mut(&parent_idx) }; + let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) }; + let fc_ptr = vcomponent.user_fc; - // .(|new_idx| { - // // ScopeInner::new( - // vcomponent, - // new_idx, - // Some(parent_idx), - // parent_scope.height + 1, - // parent_scope.subtree(), - // shared, - // // ) - // }); + let new_idx = self + .scopes + .new_with_key(fc_ptr, caller, parent_scope, height, subtree); // Actually initialize the caller's slot with the right address vcomponent.associated_scope.set(Some(new_idx)); @@ -356,7 +344,7 @@ impl<'bump> DiffState<'bump> { if !vcomponent.can_memoize { let cur_scope = self.scopes.get_scope(&parent_idx).unwrap(); let extended = unsafe { std::mem::transmute(vcomponent) }; - cur_scope.items.get_mut().borrowed_props.push(extended); + cur_scope.items.borrow_mut().borrowed_props.push(extended); } // TODO: @@ -368,28 +356,19 @@ impl<'bump> DiffState<'bump> { log::debug!( "initializing component {:?} with height {:?}", new_idx, - parent_scope.height + 1 + height + 1 ); // Run the scope for one iteration to initialize it - // - todo!("run scope"); - // if new_component.run_scope(self) { - // // Take the node that was just generated from running the component - // let nextnode = new_component.frames.fin_head(); - // self.stack.create_component(new_idx, nextnode); + if self.scopes.run_scope(&new_idx) { + // Take the node that was just generated from running the component + let nextnode = self.scopes.fin_head(&new_idx); + self.stack.create_component(new_idx, nextnode); - // // - // /* - // tree_item { - - // } - - // */ - // if new_component.is_subtree_root.get() { - // self.stack.push_subtree(); - // } - // } + if new_component.is_subtree_root.get() { + self.stack.push_subtree(); + } + } // Finally, insert this scope as a seen node. self.seen_scopes.insert(new_idx); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8af6fd940..9988d9415 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -21,7 +21,6 @@ pub(crate) mod mutations; pub(crate) mod nodes; pub(crate) mod scope; pub(crate) mod scopearena; -pub(crate) mod test_dom; pub(crate) mod virtual_dom; pub(crate) mod innerlude { @@ -34,7 +33,6 @@ pub(crate) mod innerlude { pub use crate::nodes::*; pub use crate::scope::*; pub use crate::scopearena::*; - pub use crate::test_dom::*; pub use crate::virtual_dom::*; pub type Element = Option; @@ -42,9 +40,10 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, - Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, - TestDom, UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, FC, + Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode, + LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, + ScopeChildren, ScopeId, UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, + FC, }; pub mod prelude { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 1acfdb844..bb6a4e9e8 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -20,6 +20,8 @@ use std::{ /// /// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key /// is essentially a unique key to guarantee safe usage of the Node. +/// +/// todo: remove "clone" #[derive(Clone, Debug)] pub struct NodeLink { pub(crate) link_idx: usize, @@ -177,7 +179,7 @@ impl<'src> VNode<'src> { } } - pub fn children(&self) -> &[VNode<'src>] { + pub(crate) fn children(&self) -> &[VNode<'src>] { match &self { VNode::Fragment(f) => f.children, VNode::Component(c) => todo!("children are not accessible through this"), @@ -396,7 +398,6 @@ pub struct VComponent<'src> { pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>, pub(crate) drop_props: RefCell>>, - // pub(crate) comparator: Option bool + 'src>>, } pub struct VSuspended<'a> { @@ -825,7 +826,7 @@ impl IntoVNode<'_> for Arguments<'_> { /// ## Example /// /// ```rust -/// const App: FC<()> = |(cx, props)|{ +/// const App: FC<()> = |cx, props|{ /// cx.render(rsx!{ /// CustomCard { /// h1 {} @@ -834,7 +835,7 @@ impl IntoVNode<'_> for Arguments<'_> { /// }) /// } /// -/// const CustomCard: FC<()> = |(cx, props)|{ +/// const CustomCard: FC<()> = |cx, props|{ /// cx.render(rsx!{ /// div { /// h1 {"Title card"} diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 895b4ca99..5fabc4724 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -26,7 +26,7 @@ use bumpalo::{boxed::Box as BumpBox, Bump}; /// name: String /// } /// -/// fn Example((cx, props): Scope) -> Element { +/// fn Example(cx: Context, props: &ExampleProps) -> Element { /// cx.render(rsx!{ div {"Hello, {props.name}"} }) /// } /// ``` @@ -42,8 +42,6 @@ pub type Context<'a> = &'a Scope; /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever /// use case they might have. pub struct Scope { - // Book-keeping about our spot in the arena - // safety: // // pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed @@ -113,7 +111,7 @@ impl Scope { /// # Example /// /// ```rust - /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -131,7 +129,7 @@ impl Scope { /// # Example /// /// ```rust - /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -151,7 +149,7 @@ impl Scope { /// # Example /// /// ```rust - /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -172,7 +170,7 @@ impl Scope { /// # Example /// /// ```rust - /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// let base = dom.base_scope(); /// @@ -307,12 +305,12 @@ impl Scope { /// ``` /// struct SharedState(&'static str); /// - /// static App: FC<()> = |(cx, props)|{ + /// static App: FC<()> = |cx, props|{ /// cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {}); /// rsx!(cx, Child {}) /// } /// - /// static Child: FC<()> = |(cx, props)|{ + /// static Child: FC<()> = |cx, props|{ /// let state = cx.consume_state::(); /// rsx!(cx, div { "hello {state.0}" }) /// } @@ -356,7 +354,7 @@ impl Scope { /// # Example /// /// ```rust - /// static App: FC<()> = |(cx, props)| { + /// static App: FC<()> = |cx, props| { /// todo!(); /// rsx!(cx, div { "Subtree {id}"}) /// }; @@ -373,7 +371,7 @@ impl Scope { /// # Example /// /// ```rust - /// static App: FC<()> = |(cx, props)| { + /// static App: FC<()> = |cx, props| { /// let id = cx.get_current_subtree(); /// rsx!(cx, div { "Subtree {id}"}) /// }; @@ -529,3 +527,8 @@ impl BumpFrame { nodes.len() - 1 } } + +#[test] +fn sizeof() { + dbg!(std::mem::size_of::()); +} diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index 6f6c56b69..f873e14bb 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -1,8 +1,5 @@ use slab::Slab; -use std::{ - borrow::BorrowMut, - cell::{Cell, RefCell}, -}; +use std::cell::{Cell, RefCell}; use bumpalo::{boxed::Box as BumpBox, Bump}; use futures_channel::mpsc::UnboundedSender; @@ -23,8 +20,8 @@ pub struct Heuristic { // has an internal heuristics engine to pre-allocate arenas to the right size pub(crate) struct ScopeArena { bump: Bump, - scopes: Vec<*mut Scope>, - free_scopes: Vec, + scopes: RefCell>, + free_scopes: RefCell>, nodes: RefCell>>, pub(crate) sender: UnboundedSender, } @@ -33,38 +30,38 @@ impl ScopeArena { pub fn new(sender: UnboundedSender) -> Self { Self { bump: Bump::new(), - scopes: Vec::new(), - free_scopes: Vec::new(), + scopes: RefCell::new(Vec::new()), + free_scopes: RefCell::new(Vec::new()), nodes: RefCell::new(Slab::new()), sender, } } pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> { - unsafe { Some(&*self.scopes[id.0]) } + unsafe { Some(&*self.scopes.borrow()[id.0]) } } // this is unsafe pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<*mut Scope> { - Some(self.scopes[id.0]) + Some(self.scopes.borrow()[id.0]) } pub fn new_with_key( - &mut self, + &self, fc_ptr: *const (), caller: *const dyn Fn(&Scope) -> Element, parent_scope: Option<*mut Scope>, height: u32, subtree: u32, ) -> ScopeId { - if let Some(id) = self.free_scopes.pop() { + if let Some(id) = self.free_scopes.borrow_mut().pop() { // have already called drop on it - the slot is still chillin tho - let scope = unsafe { &mut *self.scopes[id.0 as usize] }; + // let scope = unsafe { &mut *self.scopes.borrow()[id.0 as usize] }; todo!("override the scope contents"); id } else { - let scope_id = ScopeId(self.scopes.len()); + let scope_id = ScopeId(self.scopes.borrow().len()); let old_root = NodeLink { link_idx: 0, @@ -93,8 +90,6 @@ impl ScopeArena { old_root: RefCell::new(Some(old_root)), new_root: RefCell::new(Some(new_root)), - // old_root: RefCell::new(Some(old_root)), - // new_root: RefCell::new(None), items: RefCell::new(SelfReferentialItems { listeners: Default::default(), borrowed_props: Default::default(), @@ -105,7 +100,7 @@ impl ScopeArena { }; let stable = self.bump.alloc(new_scope); - self.scopes.push(stable); + self.scopes.borrow_mut().push(stable); scope_id } } @@ -119,8 +114,8 @@ impl ScopeArena { let entry = els.vacant_entry(); let key = entry.key(); let id = ElementId(key); - let node = node as *const _; - let node = unsafe { std::mem::transmute(node) }; + let node: *const VNode = node as *const _; + let node = unsafe { std::mem::transmute::<*const VNode, *const VNode>(node) }; entry.insert(node); id @@ -183,7 +178,6 @@ impl ScopeArena { .borrow_mut() .listeners .drain(..) - .map(|li| unsafe { &*li }) .for_each(|listener| drop(listener.callback.borrow_mut().take())); } @@ -256,39 +250,17 @@ impl ScopeArena { let wip_frame = scope.wip_frame(); let nodes = wip_frame.nodes.borrow(); let node = nodes.get(0).unwrap(); - unsafe { std::mem::transmute(node) } - // let root = scope.old_root.borrow(); - // let link = root.as_ref().unwrap(); - // dbg!(link); - - // // for now, components cannot pass portals through each other - // assert_eq!(link.scope_id, *id); - // // assert_eq!(link.gen_id, scope.generation.get() - 1); - - // // let items = scope.items.borrow(); - // let nodes = scope.wip_frame().nodes.borrow(); - // let node = nodes.get(link.link_idx).unwrap(); - // unsafe { std::mem::transmute(node) } + unsafe { std::mem::transmute::<&VNode, &VNode>(node) } } pub fn fin_head(&self, id: &ScopeId) -> &VNode { let scope = self.get_scope(id).unwrap(); let wip_frame = scope.fin_frame(); let nodes = wip_frame.nodes.borrow(); - let node = nodes.get(0).unwrap(); - unsafe { std::mem::transmute(node) } - // let scope = self.get_scope(id).unwrap(); - // let root = scope.new_root.borrow(); - // let link = root.as_ref().unwrap(); - - // // for now, components cannot pass portals through each other - // assert_eq!(link.scope_id, *id); - // // assert_eq!(link.gen_id, scope.generation.get()); - - // let nodes = scope.fin_frame().nodes.borrow(); - // let node = nodes.get(link.link_idx).unwrap(); - // unsafe { std::mem::transmute(node) } + let node: &VNode = nodes.get(0).unwrap(); + unsafe { std::mem::transmute::<&VNode, &VNode>(node) } } + pub fn root_node(&self, id: &ScopeId) -> &VNode { self.wip_head(id) } diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs deleted file mode 100644 index e6795902e..000000000 --- a/packages/core/src/test_dom.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! A DOM for testing - both internal and external code. -use bumpalo::Bump; - -use crate::innerlude::*; -use crate::nodes::IntoVNode; - -pub struct TestDom { - bump: Bump, - // scheduler: Scheduler, -} - -impl TestDom { - pub fn new() -> TestDom { - let bump = Bump::new(); - let (sender, receiver) = futures_channel::mpsc::unbounded::(); - todo!() - // let scheduler = Scheduler::new(sender, receiver, 10, 100); - // TestDom { bump, scheduler } - } - - pub fn new_factory(&self) -> NodeFactory { - NodeFactory::new(&self.bump) - } - - pub fn render_direct<'a>(&'a self, lazy_nodes: Option>) -> VNode<'a> { - lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) - } - - pub fn render<'a>(&'a self, lazy_nodes: Option>) -> &'a VNode<'a> { - self.bump - .alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump))) - } - - pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { - let mutations = Mutations::new(); - let mut machine: DiffState = todo!(); - // let mut machine = DiffState::new(mutations); - // let mut machine = DiffState::new(mutations); - machine.stack.push(DiffInstruction::Diff { new, old }); - machine.mutations - } - - pub fn create<'a>(&'a self, left: Option>) -> Mutations<'a> { - let old = self.bump.alloc(self.render_direct(left)); - - let mut machine: DiffState = todo!(); - // let mut machine = DiffState::new(Mutations::new()); - // let mut machine = DiffState::new(Mutations::new()); - - machine.stack.create_node(old, MountType::Append); - - todo!() - - // machine.work(&mut || false); - - // machine.mutations - } - - pub fn lazy_diff<'a>( - &'a self, - left: Option>, - right: Option>, - ) -> (Mutations<'a>, Mutations<'a>) { - let (old, new) = (self.render(left), self.render(right)); - - let mut machine: DiffState = todo!(); - // let mut machine = DiffState::new(Mutations::new()); - - machine.stack.create_node(old, MountType::Append); - - todo!() - - // machine.work(|| false); - // let create_edits = machine.mutations; - - // let mut machine: DiffState = todo!(); - // // let mut machine = DiffState::new(Mutations::new()); - - // machine.stack.push(DiffInstruction::Diff { old, new }); - - // machine.work(&mut || false); - - // let edits = machine.mutations; - - // (create_edits, edits) - } -} - -impl Default for TestDom { - fn default() -> Self { - Self::new() - } -} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index bea4c2686..c0a85b062 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,23 +1,6 @@ //! # VirtualDOM Implementation for Rust //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. -//! -//! In this file, multiple items are defined. This file is big, but should be documented well to -//! navigate the inner workings of the Dom. We try to keep these main mechanics in this file to limit -//! the possible exposed API surface (keep fields private). This particular implementation of VDOM -//! is extremely efficient, but relies on some unsafety under the hood to do things like manage -//! micro-heaps for components. We are currently working on refactoring the safety out into safe(r) -//! abstractions, but current tests (MIRI and otherwise) show no issues with the current implementation. -//! -//! Included is: -//! - The [`VirtualDom`] itself -//! - The [`Scope`] object for managing component lifecycle -//! - The [`ActiveFrame`] object for managing the Scope`s microheap -//! - The [`Context`] object for exposing VirtualDOM API to components -//! - The [`NodeFactory`] object for lazily exposing the `Context` API to the nodebuilder API -//! -//! This module includes just the barebones for a complete VirtualDOM API. -//! Additional functionality is defined in the respective files. use crate::innerlude::*; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -28,33 +11,92 @@ use std::pin::Pin; use std::task::Poll; use std::{any::Any, collections::VecDeque}; -/// An integrated virtual node system that progresses events and diffs UI trees. +/// A virtual node system that progresses user events and diffs UI trees. /// -/// Differences are converted into patches which a renderer can use to draw the UI. +/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`]. /// -/// If you are building an App with Dioxus, you probably won't want to reach for this directly, instead opting to defer -/// to a particular crate's wrapper over the [`VirtualDom`] API. -/// -/// Example /// ```rust -/// static App: FC<()> = |(cx, props)|{ +/// #[derive(Props, PartialEq)] +/// struct AppProps { +/// title: String +/// } +/// +/// fn App(cx: Context, props: &AppProps) -> Element { +/// cx.render(rsx!( +/// div {"hello, {props.title}"} +/// )) +/// } +/// ``` +/// +/// Components may be composed to make complex apps. +/// +/// ```rust +/// fn App(cx: Context, props: &AppProps) -> Element { +/// cx.render(rsx!( +/// NavBar { routes: ROUTES } +/// Title { "{props.title}" } +/// Footer {} +/// )) +/// } +/// ``` +/// +/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to +/// draw the UI. +/// +/// ```rust +/// let mut vdom = VirtualDom::new(App); +/// let edits = vdom.rebuild(); +/// ``` +/// +/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler. +/// +/// ```rust +/// let channel = vdom.get_scheduler_channel(); +/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent { +/// // ... +/// })) +/// ``` +/// +/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom. +/// +/// ```rust +/// vdom.wait_for_work().await; +/// ``` +/// +/// Once work is ready, call [`VirtualDom::work_with_deadline`] to compute the differences between the previous and +/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be +/// handled by the renderer. +/// +/// ```rust +/// let mutations = vdom.work_with_deadline(|| false); +/// for edit in mutations { +/// apply(edit); +/// } +/// ``` +/// +/// ## Building an event loop around Dioxus: +/// +/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above. +/// +/// ```rust +/// fn App(cx: Context, props: &()) -> Element { /// cx.render(rsx!{ -/// div { -/// "Hello World" -/// } +/// div { "Hello World" } /// }) /// } /// /// async fn main() { /// let mut dom = VirtualDom::new(App); +/// /// let mut inital_edits = dom.rebuild(); -/// initialize_screen(inital_edits); +/// apply_edits(inital_edits); /// /// loop { -/// let next_frame = TimeoutFuture::new(Duration::from_millis(16)); -/// let edits = dom.run_with_deadline(next_frame).await; +/// dom.wait_for_work().await; +/// let frame_timeout = TimeoutFuture::new(Duration::from_millis(16)); +/// let deadline = || (&mut frame_timeout).now_or_never(); +/// let edits = dom.run_with_deadline(deadline).await; /// apply_edits(edits); -/// render_frame(); /// } /// } /// ``` @@ -145,9 +187,9 @@ impl VirtualDom { sender: UnboundedSender, receiver: UnboundedReceiver, ) -> Self { - let mut scopes = ScopeArena::new(sender.clone()); + let scopes = ScopeArena::new(sender.clone()); - let caller = Box::new(move |f: &Scope| -> Element { root(f, &root_props) }); + let caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) }); let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller); let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0); @@ -164,7 +206,7 @@ impl VirtualDom { } } - /// Get the [`ScopeState`] for the root component. + /// Get the [`Scope`] for the root component. /// /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus /// directly. @@ -174,7 +216,7 @@ impl VirtualDom { self.get_scope(&self.base_scope).unwrap() } - /// Get the [`ScopeState`] for a component given its [`ScopeId`] + /// Get the [`Scope`] for a component given its [`ScopeId`] /// /// # Example /// @@ -305,7 +347,7 @@ impl VirtualDom { /// # Example /// /// ```no_run - /// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} ); + /// static App: FC<()> = |cx, props|rsx!(cx, div {"hello"} ); /// /// let mut dom = VirtualDom::new(App); /// @@ -436,7 +478,7 @@ impl VirtualDom { /// /// # Example /// ``` - /// static App: FC<()> = |(cx, props)| cx.render(rsx!{ "hello world" }); + /// static App: FC<()> = |cx, props| cx.render(rsx!{ "hello world" }); /// let mut dom = VirtualDom::new(); /// let edits = dom.rebuild(); /// @@ -453,7 +495,7 @@ impl VirtualDom { diff_state.stack.scope_stack.push(scope_id); - let work_completed = diff_state.work(|| false); + diff_state.work(|| false); } diff_state.mutations @@ -474,7 +516,7 @@ impl VirtualDom { /// value: Shared<&'static str>, /// } /// - /// static App: FC = |(cx, props)|{ + /// static App: FC = |cx, props|{ /// let val = cx.value.borrow(); /// cx.render(rsx! { div { "{val}" } }) /// }; @@ -501,7 +543,7 @@ impl VirtualDom { diff_machine.force_diff = true; - let work_completed = diff_machine.work(|| false); + diff_machine.work(|| false); // let scopes = &mut self.scopes; // crate::diff::diff_scope(&mut diff_machine, scope_id); // self.scopes.diff_scope(&mut diff_machine, scope_id); @@ -523,6 +565,78 @@ impl VirtualDom { None } } + + /// Renders an `rsx` call into the Base Scope's allocator. + /// + /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option>) -> &'a VNode<'a> { + todo!() + } + + /// Renders an `rsx` call into the Base Scope's allocator. + /// + /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { + todo!() + // let mutations = Mutations::new(); + // let mut machine: DiffState = todo!(); + // // let mut machine = DiffState::new(mutations); + // // let mut machine = DiffState::new(mutations); + // machine.stack.push(DiffInstruction::Diff { new, old }); + // machine.mutations + } + + /// Renders an `rsx` call into the Base Scope's allocator. + /// + /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + pub fn create_vnodes<'a>(&'a self, left: Option>) -> Mutations<'a> { + todo!() + // let old = self.bump.alloc(self.render_direct(left)); + + // let mut machine: DiffState = todo!(); + // // let mut machine = DiffState::new(Mutations::new()); + // // let mut machine = DiffState::new(Mutations::new()); + + // machine.stack.create_node(old, MountType::Append); + + // todo!() + + // // machine.work(&mut || false); + + // // machine.mutations + } + + /// Renders an `rsx` call into the Base Scope's allocator. + /// + /// Useful when needing to render nodes from outside the VirtualDom, such as in a test. + pub fn diff_lazynodes<'a>( + &'a self, + left: Option>, + right: Option>, + ) -> (Mutations<'a>, Mutations<'a>) { + let (old, new) = (self.render_vnodes(left), self.render_vnodes(right)); + + // let mut machine: DiffState = todo!(); + // let mut machine = DiffState::new(Mutations::new()); + + // machine.stack.create_node(old, MountType::Append); + + todo!() + + // machine.work(|| false); + // let create_edits = machine.mutations; + + // let mut machine: DiffState = todo!(); + // // let mut machine = DiffState::new(Mutations::new()); + + // machine.stack.push(DiffInstruction::Diff { old, new }); + + // machine.work(&mut || false); + + // let edits = machine.mutations; + + // (create_edits, edits) + } } pub enum SchedulerMsg { diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 93e7bd397..2e0948e13 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -5,17 +5,17 @@ //! //! It does not validated that component lifecycles work properly. This is done in another test file. -use dioxus::{prelude::*, DomEdit, TestDom, VSuspended}; +use dioxus::{prelude::*, DomEdit, VSuspended}; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; mod test_logging; -fn new_dom() -> TestDom { +fn new_dom() -> VirtualDom { const IS_LOGGING_ENABLED: bool = false; test_logging::set_up_logging(IS_LOGGING_ENABLED); - TestDom::new() + VirtualDom::new(|cx, props| todo!()) } use DomEdit::*; @@ -24,7 +24,7 @@ use DomEdit::*; #[test] fn html_and_rsx_generate_the_same_output() { let dom = new_dom(); - let (create, change) = dom.lazy_diff( + let (create, change) = dom.diff_lazynodes( rsx! ( div { "Hello world" } ), rsx! ( div { "Goodbye world" } ), ); @@ -58,7 +58,7 @@ fn html_and_rsx_generate_the_same_output() { fn fragments_create_properly() { let dom = new_dom(); - let create = dom.create(rsx! { + let create = dom.create_vnodes(rsx! { div { "Hello a" } div { "Hello b" } div { "Hello c" } @@ -107,7 +107,7 @@ fn empty_fragments_create_anchors() { let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) }); let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) }); - let (create, change) = dom.lazy_diff(left, right); + let (create, change) = dom.diff_lazynodes(left, right); assert_eq!( create.edits, @@ -133,7 +133,7 @@ fn empty_fragments_create_many_anchors() { let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) }); let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) }); - let (create, change) = dom.lazy_diff(left, right); + let (create, change) = dom.diff_lazynodes(left, right); assert_eq!( create.edits, [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] @@ -179,7 +179,7 @@ fn empty_fragments_create_anchors_with_many_children() { }) }); - let (create, change) = dom.lazy_diff(left, right); + let (create, change) = dom.diff_lazynodes(left, right); assert_eq!( create.edits, [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] @@ -231,7 +231,7 @@ fn many_items_become_fragment() { }); let right = rsx!({ (0..0).map(|_| rsx! { div {} }) }); - let (create, change) = dom.lazy_diff(left, right); + let (create, change) = dom.diff_lazynodes(left, right); assert_eq!( create.edits, [ @@ -284,7 +284,7 @@ fn two_equal_fragments_are_equal() { }) }); - let (_create, change) = dom.lazy_diff(left, right); + let (_create, change) = dom.diff_lazynodes(left, right); assert!(change.edits.is_empty()); } @@ -302,7 +302,7 @@ fn two_fragments_with_differrent_elements_are_differet() { p {} ); - let (_create, changes) = dom.lazy_diff(left, right); + let (_create, changes) = dom.diff_lazynodes(left, right); log::debug!("{:#?}", &changes); assert_eq!( changes.edits, @@ -335,7 +335,7 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { p {} ); - let (create, change) = dom.lazy_diff(left, right); + let (create, change) = dom.diff_lazynodes(left, right); assert_eq!( create.edits, [ @@ -391,7 +391,7 @@ fn two_fragments_with_same_elements_are_differet() { p {} ); - let (create, change) = dom.lazy_diff(left, right); + let (create, change) = dom.diff_lazynodes(left, right); assert_eq!( create.edits, [ @@ -441,7 +441,7 @@ fn keyed_diffing_order() { p {"e"} ); - let (create, change) = dom.lazy_diff(left, right); + let (create, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, [Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 },] @@ -465,7 +465,7 @@ fn keyed_diffing_out_of_order() { }) }); - let (_, changes) = dom.lazy_diff(left, right); + let (_, changes) = dom.diff_lazynodes(left, right); log::debug!("{:?}", &changes); assert_eq!( changes.edits, @@ -490,7 +490,7 @@ fn keyed_diffing_out_of_order_adds() { }) }); - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, [ @@ -517,7 +517,7 @@ fn keyed_diffing_out_of_order_adds_2() { }) }); - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, [ @@ -545,7 +545,7 @@ fn keyed_diffing_out_of_order_adds_3() { }) }); - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, [ @@ -573,7 +573,7 @@ fn keyed_diffing_out_of_order_adds_4() { }) }); - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, [ @@ -601,7 +601,7 @@ fn keyed_diffing_out_of_order_adds_5() { }) }); - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, [PushRoot { root: 4 }, InsertBefore { n: 1, root: 3 }] @@ -624,7 +624,7 @@ fn keyed_diffing_additions() { }) }); - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, [ @@ -657,7 +657,7 @@ fn keyed_diffing_additions_and_moves_on_ends() { }) }); - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); log::debug!("{:?}", change); assert_eq!( change.edits, @@ -696,7 +696,7 @@ fn keyed_diffing_additions_and_moves_in_middle() { }); // LIS: 4, 5, 6 - let (_, change) = dom.lazy_diff(left, right); + let (_, change) = dom.diff_lazynodes(left, right); log::debug!("{:#?}", change); assert_eq!( change.edits, @@ -745,7 +745,7 @@ fn controlled_keyed_diffing_out_of_order() { }); // LIS: 5, 6 - let (_, changes) = dom.lazy_diff(left, right); + let (_, changes) = dom.diff_lazynodes(left, right); log::debug!("{:#?}", &changes); assert_eq!( changes.edits, @@ -787,7 +787,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() { }) }); - let (_, changes) = dom.lazy_diff(left, right); + let (_, changes) = dom.diff_lazynodes(left, right); log::debug!("{:#?}", &changes); assert_eq!( changes.edits, @@ -808,7 +808,7 @@ fn suspense() { let dom = new_dom(); todo!() - // let edits = dom.create(Some(LazyNodes::new(|f| { + // let edits = dom.create_vnodes(Some(LazyNodes::new(|f| { // use std::cell::{Cell, RefCell}; // VNode::Suspended(f.bump().alloc(VSuspended { // task_id: 0, diff --git a/packages/core/tests/display_vdom.rs b/packages/core/tests/display_vdom.rs deleted file mode 100644 index ee32f27c0..000000000 --- a/packages/core/tests/display_vdom.rs +++ /dev/null @@ -1,38 +0,0 @@ -#![allow(unused, non_upper_case_globals)] - -//! test that we can display the virtualdom properly -//! -//! -//! - -use dioxus::prelude::*; -use dioxus_core as dioxus; -use dioxus_core_macro::*; -use dioxus_html as dioxus_elements; -mod test_logging; - -#[test] -fn please_work() { - static App: FC<()> = |cx, props| { - cx.render(rsx! { - div { - hidden: "true" - "hello" - div { "hello" } - // Child {} - // Child {} - // Child {} - } - // div { "hello" } - }) - }; - - static Child: FC<()> = |cx, props| { - cx.render(rsx! { - div { "child" } - }) - }; - - let mut dom = VirtualDom::new(App); - dom.rebuild(); -} diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 5a2e8d042..bf57a5186 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -1,6 +1,6 @@ #![allow(unused, non_upper_case_globals)] -use dioxus::{prelude::*, DomEdit, Mutations, TestDom}; +use dioxus::{prelude::*, DomEdit, Mutations}; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements;