Răsfoiți Sursa

wip: move testdom methods into virtualdom

Jonathan Kelley 3 ani în urmă
părinte
comite
d2f0547692

+ 7 - 7
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

+ 20 - 41
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<SchedulerMsg> = todo!();
-        // let shared: UnboundedSender<SchedulerMsg> = 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);
-
-        // .(|new_idx| {
-        //     // ScopeInner::new(
-        //         vcomponent,
-        //         new_idx,
-        //         Some(parent_idx),
-        //         parent_scope.height + 1,
-        //         parent_scope.subtree(),
-        //         shared,
-        //     // )
-        // });
+        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;
+
+        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);
-
-        //     //
-        //     /*
-        //     tree_item {
-
-        //     }
-
-        //     */
-        //     if new_component.is_subtree_root.get() {
-        //         self.stack.push_subtree();
-        //     }
-        // }
+        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);
+
+            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);

+ 4 - 5
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<NodeLink>;
@@ -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 {

+ 5 - 4
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<Option<BumpBox<'src, dyn FnMut()>>>,
-    // pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> 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"}

+ 14 - 11
packages/core/src/scope.rs

@@ -26,7 +26,7 @@ use bumpalo::{boxed::Box as BumpBox, Bump};
 ///     name: String
 /// }
 ///
-/// fn Example((cx, props): Scope<Props>) -> 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::<SharedState>();
     ///     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::<Scope>());
+}

+ 18 - 46
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<ScopeId>,
+    scopes: RefCell<Vec<*mut Scope>>,
+    free_scopes: RefCell<Vec<ScopeId>>,
     nodes: RefCell<Slab<*const VNode<'static>>>,
     pub(crate) sender: UnboundedSender<SchedulerMsg>,
 }
@@ -33,38 +30,38 @@ impl ScopeArena {
     pub fn new(sender: UnboundedSender<SchedulerMsg>) -> 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)
     }

+ 0 - 93
packages/core/src/test_dom.rs

@@ -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::<SchedulerMsg>();
-        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<LazyNodes<'a, '_>>) -> VNode<'a> {
-        lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
-    }
-
-    pub fn render<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'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<LazyNodes<'a, '_>>) -> 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<LazyNodes<'a, '_>>,
-        right: Option<LazyNodes<'a, '_>>,
-    ) -> (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()
-    }
-}

+ 153 - 39
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.
+///
+/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].  
+///
+/// ```rust
+/// #[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);
+/// }
+/// ```
 ///
-/// Differences are converted into patches which a renderer can use to draw the UI.
+/// ## Building an event loop around Dioxus:
 ///
-/// 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.
+/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
 ///
-/// Example
 /// ```rust
-/// static App: FC<()> = |(cx, props)|{
+/// 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<SchedulerMsg>,
         receiver: UnboundedReceiver<SchedulerMsg>,
     ) -> 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<AppProps> = |(cx, props)|{
+    /// static App: FC<AppProps> = |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<LazyNodes<'a, '_>>) -> &'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<LazyNodes<'a, '_>>) -> 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<LazyNodes<'a, '_>>,
+        right: Option<LazyNodes<'a, '_>>,
+    ) -> (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 {

+ 26 - 26
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,

+ 0 - 38
packages/core/tests/display_vdom.rs

@@ -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();
-}

+ 1 - 1
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;