Explorar el Código

polish: tests and documentation

Jonathan Kelley hace 3 años
padre
commit
da81591

+ 1 - 0
.vscode/spellright.dict

@@ -66,3 +66,4 @@ constified
 SegVec
 contentful
 Jank
+noderef

+ 2 - 0
README.md

@@ -109,6 +109,8 @@ If you know React, then you already know Dioxus.
 - Sierpinski's triangle (web SPA)
 - Doxie Documentation Library (Web SPA with Hydration)
 
+See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
+
 <!-- 
 currently commented out until we have more content on the website
 ## Explore

+ 5 - 0
packages/core/src/bumpframe.rs

@@ -14,6 +14,7 @@ pub(crate) struct BumpFrame {
     pub bump: Bump,
     pub(crate) head_node: VNode<'static>,
 
+    #[cfg(test)]
     // used internally for debugging
     _name: &'static str,
 }
@@ -23,11 +24,15 @@ impl ActiveFrame {
         let frame_a = BumpFrame {
             bump: Bump::new(),
             head_node: NodeFactory::unstable_place_holder(),
+
+            #[cfg(test)]
             _name: "wip",
         };
         let frame_b = BumpFrame {
             bump: Bump::new(),
             head_node: NodeFactory::unstable_place_holder(),
+
+            #[cfg(test)]
             _name: "fin",
         };
         Self {

+ 0 - 1
packages/core/src/childiter.rs

@@ -64,7 +64,6 @@ impl<'a> Iterator for RealChildIterator<'a> {
                             .scopes
                             .get_scope(sc.associated_scope.get().unwrap())
                             .unwrap();
-                        // let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
 
                         // Simply swap the current node on the stack with the root of the component
                         *node = scope.frames.fin_head();

+ 27 - 22
packages/core/src/component.rs

@@ -7,6 +7,32 @@
 
 use crate::innerlude::{Context, DomTree, LazyNodes, FC};
 
+/// Create inline fragments using Component syntax.
+///
+/// Fragments capture a series of children without rendering extra nodes.
+///
+/// # Example
+///
+/// ```rust
+/// rsx!{
+///     Fragment { key: "abc" }
+/// }
+/// ```
+///
+/// # Details
+///
+/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+/// Try to avoid nesting fragments if you can. There is no protection against infinitely nested fragments.
+///
+/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+///
+/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+///
+#[allow(non_upper_case_globals, non_snake_case)]
+pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
+    cx.render(LazyNodes::new(|f| f.fragment_from_iter(cx.children())))
+}
+
 /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
 /// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
 /// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
@@ -44,7 +70,7 @@ pub trait Properties: Sized {
     const IS_STATIC: bool;
     fn builder() -> Self::Builder;
 
-    /// Memoization can only happen if the props are 'static
+    /// Memoization can only happen if the props are valid for the 'static lifetime
     /// The user must know if their props are static, but if they make a mistake, UB happens
     /// Therefore it's unsafe to memeoize.
     unsafe fn memoize(&self, other: &Self) -> bool;
@@ -75,24 +101,3 @@ impl EmptyBuilder {
 pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
     T::builder()
 }
-
-/// Create inline fragments
-///
-/// Fragments capture a series of children without rendering extra nodes.
-///
-/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
-/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
-///
-/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
-///
-/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
-///
-/// ```rust
-/// rsx!{
-///     Fragment { key: "abc" }
-/// }
-/// ```
-#[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
-    cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
-}

+ 59 - 117
packages/core/src/diff.rs

@@ -124,6 +124,7 @@ impl<'a> SavedDiffWork<'a> {
     pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> {
         std::mem::transmute(self)
     }
+
     pub unsafe fn promote<'b>(self, vdom: &'b mut ResourcePool) -> DiffMachine<'b> {
         let extended: SavedDiffWork<'b> = std::mem::transmute(self);
         DiffMachine {
@@ -136,10 +137,10 @@ impl<'a> SavedDiffWork<'a> {
 }
 
 impl<'bump> DiffMachine<'bump> {
-    pub(crate) fn new(edits: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
+    pub(crate) fn new(mutations: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
         Self {
+            mutations,
             stack: DiffStack::new(),
-            mutations: edits,
             vdom: shared,
             seen_scopes: FxHashSet::default(),
         }
@@ -156,7 +157,7 @@ impl<'bump> DiffMachine<'bump> {
     pub fn diff_scope(&mut self, id: ScopeId) {
         if let Some(component) = self.vdom.get_scope_mut(id) {
             let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
-            self.stack.push(DiffInstruction::DiffNode { new, old });
+            self.stack.push(DiffInstruction::Diff { new, old });
         }
     }
 
@@ -167,24 +168,14 @@ impl<'bump> DiffMachine<'bump> {
     /// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
     ///
     /// Returns a `bool` indicating that the work completed properly.
-    pub fn work(&mut self, deadline_expired: &mut impl FnMut() -> bool) -> bool {
+    pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool {
         while let Some(instruction) = self.stack.pop() {
-            // defer to individual functions so the compiler produces better code
-            // large functions tend to be difficult for the compiler to work with
             match instruction {
-                DiffInstruction::PopScope => {
-                    self.stack.pop_scope();
-                }
-
-                DiffInstruction::DiffNode { old, new, .. } => self.diff_node(old, new),
-
-                DiffInstruction::DiffChildren { old, new } => self.diff_children(old, new),
-
+                DiffInstruction::Diff { old, new } => self.diff_node(old, new),
                 DiffInstruction::Create { node } => self.create_node(node),
-
                 DiffInstruction::Mount { and } => self.mount(and),
-
-                DiffInstruction::PrepareMoveNode { node } => self.prepare_move_node(node),
+                DiffInstruction::PrepareMove { node } => self.prepare_move_node(node),
+                DiffInstruction::PopScope => self.stack.pop_off_scope(),
             };
 
             if deadline_expired() {
@@ -217,15 +208,21 @@ impl<'bump> DiffMachine<'bump> {
             }
 
             MountType::Replace { old } => {
-                let mut iter = RealChildIterator::new(old, self.vdom);
-                let first = iter.next().unwrap();
-                self.mutations
-                    .replace_with(first.mounted_id(), nodes_created as u32);
-                self.remove_nodes(iter);
+                if let Some(old_id) = old.try_mounted_id() {
+                    self.mutations.replace_with(old_id, nodes_created as u32);
+                } else {
+                    let mut iter = RealChildIterator::new(old, self.vdom);
+                    let first = iter.next().unwrap();
+                    self.mutations
+                        .replace_with(first.mounted_id(), nodes_created as u32);
+                    self.remove_nodes(iter);
+                }
             }
 
-            MountType::ReplaceByElementId { el: old } => {
-                self.mutations.replace_with(old, nodes_created as u32);
+            MountType::ReplaceByElementId { el } => {
+                if let Some(old) = el {
+                    self.mutations.replace_with(old, nodes_created as u32);
+                }
             }
 
             MountType::InsertAfter { other_node } => {
@@ -326,6 +323,7 @@ impl<'bump> DiffMachine<'bump> {
         let parent_idx = self.stack.current_scope().unwrap();
 
         let shared = self.vdom.channel.clone();
+
         // Insert a new scope into our component list
         let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
         let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
@@ -376,7 +374,9 @@ impl<'bump> DiffMachine<'bump> {
         match (old_node, new_node) {
             // Check the most common cases first
             (Text(old), Text(new)) => self.diff_text_nodes(old, new),
-            (Component(old), Component(new)) => self.diff_component_nodes(old, new),
+            (Component(old), Component(new)) => {
+                self.diff_component_nodes(old_node, new_node, old, new)
+            }
             (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
             (Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
             (Suspended(old), Suspended(new)) => new.dom_id.set(old.dom_id.get()),
@@ -393,19 +393,19 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
-        let root = old.dom_id.get().unwrap();
+        if let Some(root) = old.dom_id.get() {
+            if old.text != new.text {
+                self.mutations.push_root(root);
+                self.mutations.set_text(new.text);
+                self.mutations.pop();
+            }
 
-        if old.text != new.text {
-            self.mutations.push_root(root);
-            self.mutations.set_text(new.text);
-            self.mutations.pop();
+            new.dom_id.set(Some(root));
         }
-
-        new.dom_id.set(Some(root));
     }
 
     fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
-        let root = old.dom_id.get().unwrap();
+        let root = old.dom_id.get();
 
         // If the element type is completely different, the element needs to be re-rendered completely
         // This is an optimization React makes due to how users structure their code
@@ -417,21 +417,23 @@ impl<'bump> DiffMachine<'bump> {
             self.stack.push_nodes_created(0);
             self.stack.push(DiffInstruction::Mount {
                 and: MountType::ReplaceByElementId {
-                    el: old.dom_id.get().unwrap(),
+                    el: old.dom_id.get(),
                 },
             });
             self.create_element_node(new);
             return;
         }
 
-        new.dom_id.set(Some(root));
+        new.dom_id.set(root);
 
         // Don't push the root if we don't have to
         let mut has_comitted = false;
         let mut please_commit = |edits: &mut Vec<DomEdit>| {
             if !has_comitted {
                 has_comitted = true;
-                edits.push(PushRoot { id: root.as_u64() });
+                if let Some(root) = root {
+                    edits.push(PushRoot { id: root.as_u64() });
+                }
             }
         };
 
@@ -449,7 +451,6 @@ impl<'bump> DiffMachine<'bump> {
                 }
             }
         } else {
-            // TODO: provide some sort of report on how "good" the diffing was
             please_commit(&mut self.mutations.edits);
             for attribute in old.attributes {
                 self.mutations.remove_attribute(attribute);
@@ -467,7 +468,6 @@ impl<'bump> DiffMachine<'bump> {
         // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
         //
         // TODO: take a more efficient path than this
-
         if let Some(cur_scope_id) = self.stack.current_scope() {
             let scope = self.vdom.get_scope(cur_scope_id).unwrap();
 
@@ -487,22 +487,21 @@ impl<'bump> DiffMachine<'bump> {
                     self.mutations.remove_event_listener(listener.event);
                 }
                 for listener in new.listeners {
-                    listener.mounted_node.set(Some(root));
+                    listener.mounted_node.set(root);
                     self.mutations.new_event_listener(listener, cur_scope_id);
                     self.attach_listener_to_scope(listener, scope);
                 }
             }
         }
 
-        if has_comitted {
-            self.mutations.pop();
-        }
-
         self.diff_children(old.children, new.children);
     }
 
     fn diff_component_nodes(
         &mut self,
+        old_node: &'bump VNode<'bump>,
+        new_node: &'bump VNode<'bump>,
+
         old: &'bump VComponent<'bump>,
         new: &'bump VComponent<'bump>,
     ) {
@@ -526,7 +525,7 @@ impl<'bump> DiffMachine<'bump> {
 
             match compare(new) {
                 true => {
-                    // the props are the same...
+                    // the props are the same... do nothing
                 }
                 false => {
                     // the props are different...
@@ -540,25 +539,8 @@ impl<'bump> DiffMachine<'bump> {
 
             self.seen_scopes.insert(scope_addr);
         } else {
-            todo!();
-
-            // let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
-            // let first = old_iter
-            //     .next()
-            //     .expect("Components should generate a placeholder root");
-
-            // // remove any leftovers
-            // for to_remove in old_iter {
-            //     self.mutations.push_root(to_remove.direct_id());
-            //     self.mutations.remove();
-            // }
-
-            // // seems like we could combine this into a single instruction....
-            // self.replace_node_with_node(first.direct_id(), old_node, new_node);
-
-            // // Wipe the old one and plant the new one
-            // let old_scope = old.ass_scope.get().unwrap();
-            // self.destroy_scopes(old_scope);
+            self.stack
+                .create_node(new_node, MountType::Replace { old: old_node });
         }
     }
 
@@ -608,7 +590,7 @@ impl<'bump> DiffMachine<'bump> {
                 old_anchor.dom_id.set(new_anchor.dom_id.get());
             }
             ([VNode::Anchor(anchor)], _) => {
-                let el = anchor.dom_id.get().unwrap();
+                let el = anchor.dom_id.get();
                 self.stack
                     .create_children(new, MountType::ReplaceByElementId { el });
             }
@@ -652,7 +634,7 @@ impl<'bump> DiffMachine<'bump> {
         debug_assert!(!old.is_empty());
 
         for (new, old) in new.iter().zip(old.iter()).rev() {
-            self.stack.push(DiffInstruction::DiffNode { new, old });
+            self.stack.push(DiffInstruction::Diff { new, old });
         }
 
         if old.len() > new.len() {
@@ -748,7 +730,7 @@ impl<'bump> DiffMachine<'bump> {
             if old.key() != new.key() {
                 break;
             }
-            self.stack.push(DiffInstruction::DiffNode { old, new });
+            self.stack.push(DiffInstruction::Diff { old, new });
             left_offset += 1;
         }
 
@@ -887,8 +869,8 @@ impl<'bump> DiffMachine<'bump> {
                 stack.create_node(new_node, MountType::Absorb);
             } else {
                 // this funciton should never take LIS indicies
-                stack.push(DiffInstruction::PrepareMoveNode { node: new_node });
-                stack.push(DiffInstruction::DiffNode {
+                stack.push(DiffInstruction::PrepareMove { node: new_node });
+                stack.push(DiffInstruction::Diff {
                     new: new_node,
                     old: &old[old_index],
                 });
@@ -943,7 +925,7 @@ impl<'bump> DiffMachine<'bump> {
         }
 
         for idx in lis_sequence.iter().rev() {
-            self.stack.push(DiffInstruction::DiffNode {
+            self.stack.push(DiffInstruction::Diff {
                 new: &new[*idx],
                 old: &old[new_index_to_old_index[*idx]],
             });
@@ -998,6 +980,14 @@ impl<'bump> DiffMachine<'bump> {
         }
     }
 
+    fn replace_and_create_one_with_many(
+        &mut self,
+        old: &'bump VNode<'bump>,
+        new: &'bump [VNode<'bump>],
+    ) {
+        //
+    }
+
     fn replace_and_create_many_with_one(
         &mut self,
         old: &'bump [VNode<'bump>],
@@ -1070,60 +1060,12 @@ impl<'bump> DiffMachine<'bump> {
         }
     }
 
-    fn create_garbage(&mut self, node: &'bump VNode<'bump>) {
-        match self
-            .stack
-            .current_scope()
-            .and_then(|id| self.vdom.get_scope(id))
-        {
-            Some(scope) => {
-                let garbage: &'bump VNode<'static> = unsafe { std::mem::transmute(node) };
-                scope.pending_garbage.borrow_mut().push(garbage);
-            }
-            None => {
-                log::info!("No scope to collect garbage into")
-            }
-        }
-    }
-
     /// Adds a listener closure to a scope during diff.
     fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
         let mut queue = scope.listeners.borrow_mut();
         let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
         queue.push(long_listener as *const _)
     }
-
-    /// Destroy a scope and all of its descendents.
-    ///
-    /// Calling this will run the destuctors on all hooks in the tree.
-    /// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered.
-    fn destroy_scopes(&mut self, old_scope: ScopeId) {
-        let mut nodes_to_delete = vec![old_scope];
-        let mut scopes_to_explore = vec![old_scope];
-
-        // explore the scope tree breadth first
-        while let Some(scope_id) = scopes_to_explore.pop() {
-            // If we're planning on deleting this node, then we don't need to both rendering it
-            self.seen_scopes.insert(scope_id);
-            let scope = self.vdom.get_scope(scope_id).unwrap();
-            for child in scope.descendents.borrow().iter() {
-                // Add this node to be explored
-                scopes_to_explore.push(child.clone());
-
-                // Also add it for deletion
-                nodes_to_delete.push(child.clone());
-            }
-        }
-
-        // Delete all scopes that we found as part of this subtree
-        for node in nodes_to_delete {
-            log::debug!("Removing scope {:#?}", node);
-            let _scope = self.vdom.try_remove(node).unwrap();
-            // do anything we need to do to delete the scope
-            // I think we need to run the destructors on the hooks
-            // TODO
-        }
-    }
 }
 
 fn compare_strs(a: &str, b: &str) -> bool {

+ 8 - 9
packages/core/src/diff_stack.rs

@@ -3,23 +3,18 @@ use smallvec::{smallvec, SmallVec};
 
 /// The stack instructions we use to diff and create new nodes.
 #[derive(Debug)]
-pub enum DiffInstruction<'a> {
-    DiffNode {
+pub(crate) enum DiffInstruction<'a> {
+    Diff {
         old: &'a VNode<'a>,
         new: &'a VNode<'a>,
     },
 
-    DiffChildren {
-        old: &'a [VNode<'a>],
-        new: &'a [VNode<'a>],
-    },
-
     Create {
         node: &'a VNode<'a>,
     },
 
     /// pushes the node elements onto the stack for use in mount
-    PrepareMoveNode {
+    PrepareMove {
         node: &'a VNode<'a>,
     },
 
@@ -35,7 +30,7 @@ pub enum MountType<'a> {
     Absorb,
     Append,
     Replace { old: &'a VNode<'a> },
-    ReplaceByElementId { el: ElementId },
+    ReplaceByElementId { el: Option<ElementId> },
     InsertAfter { other_node: &'a VNode<'a> },
     InsertBefore { other_node: &'a VNode<'a> },
 }
@@ -63,6 +58,10 @@ impl<'bump> DiffStack<'bump> {
         self.instructions.pop()
     }
 
+    pub fn pop_off_scope(&mut self) {
+        self.scope_stack.pop();
+    }
+
     pub fn pop_scope(&mut self) -> Option<ScopeId> {
         self.scope_stack.pop()
     }

+ 5 - 0
packages/core/src/events.rs

@@ -125,8 +125,13 @@ pub mod on {
 
                         let callback: BumpBox<dyn FnMut(SyntheticEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
 
+
+                        // ie oncopy
                         let event_name = stringify!($name);
+
+                        // ie copy
                         let shortname: &'static str = &event_name[2..];
+
                         Listener {
                             event: shortname,
                             mounted_node: Cell::new(None),

+ 14 - 30
packages/core/src/hooklist.rs

@@ -3,45 +3,28 @@ use std::{
     cell::{Cell, RefCell, UnsafeCell},
 };
 
+/// An abstraction over internally stored data using a hook-based memory layout.
+///
+/// Hooks are allocated using Boxes and then our stored references are given out.
+///
+/// It's unsafe to "reset" the hooklist, but it is safe to add hooks into it.
+///
+/// Todo: this could use its very own bump arena, but that might be a tad overkill
+#[derive(Default)]
 pub(crate) struct HookList {
-    vals: RefCell<Vec<InnerHook<Box<dyn Any>>>>,
+    vals: RefCell<Vec<UnsafeCell<Box<dyn Any>>>>,
     idx: Cell<usize>,
 }
 
-impl Default for HookList {
-    fn default() -> Self {
-        Self {
-            vals: Default::default(),
-            idx: Cell::new(0),
-        }
-    }
-}
-
-struct InnerHook<T> {
-    cell: UnsafeCell<T>,
-}
-
-impl<T> InnerHook<T> {
-    fn new(new: T) -> Self {
-        Self {
-            cell: UnsafeCell::new(new),
-        }
-    }
-}
-
 impl HookList {
     pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
         self.vals.borrow().get(self.idx.get()).and_then(|inn| {
             self.idx.set(self.idx.get() + 1);
-            let raw_box = unsafe { &mut *inn.cell.get() };
+            let raw_box = unsafe { &mut *inn.get() };
             raw_box.downcast_mut::<T>()
         })
     }
 
-    pub(crate) fn push<T: 'static>(&self, new: T) {
-        self.vals.borrow_mut().push(InnerHook::new(Box::new(new)))
-    }
-
     /// This resets the internal iterator count
     /// It's okay that we've given out each hook, but now we have the opportunity to give it out again
     /// Therefore, resetting is cosudered unsafe
@@ -52,17 +35,18 @@ impl HookList {
         self.idx.set(0);
     }
 
-    #[inline]
+    pub(crate) fn push<T: 'static>(&self, new: T) {
+        self.vals.borrow_mut().push(UnsafeCell::new(Box::new(new)))
+    }
+
     pub(crate) fn len(&self) -> usize {
         self.vals.borrow().len()
     }
 
-    #[inline]
     pub(crate) fn cur_idx(&self) -> usize {
         self.idx.get()
     }
 
-    #[inline]
     pub(crate) fn at_end(&self) -> bool {
         self.cur_idx() >= self.len()
     }

+ 3 - 3
packages/core/src/lib.rs

@@ -56,9 +56,9 @@ pub(crate) mod innerlude {
 }
 
 pub use crate::innerlude::{
-    format_args_f, html, rsx, Context, DiffInstruction, DioxusElement, DomEdit, DomTree, ElementId,
-    EventPriority, LazyNodes, MountType, Mutations, NodeFactory, Properties, ScopeId,
-    SuspendedContext, SyntheticEvent, TestDom, UserEvent, VNode, VirtualDom, FC,
+    format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
+    LazyNodes, MountType, Mutations, NodeFactory, Properties, ScopeId, SuspendedContext,
+    SyntheticEvent, TestDom, UserEvent, VNode, VirtualDom, FC,
 };
 
 pub mod prelude {

+ 3 - 3
packages/core/src/nodes.rs

@@ -26,6 +26,7 @@ pub enum VNode<'src> {
     ///
     /// # Example
     ///
+    /// ```
     /// let node = cx.render(rsx!{ "hello" }).unwrap();
     ///
     /// if let VNode::Text(vtext) = node {
@@ -248,7 +249,7 @@ pub struct Listener<'bump> {
 
     /// The type of event to listen for.
     ///
-    /// IE "onclick" - whatever the renderer needs to attach the listener by name.
+    /// IE "click" - whatever the renderer needs to attach the listener by name.
     pub event: &'static str,
 
     pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(SyntheticEvent) + 'bump>>>,
@@ -283,8 +284,7 @@ pub struct VComponent<'src> {
 pub struct VSuspended<'a> {
     pub task_id: u64,
     pub dom_id: Cell<Option<ElementId>>,
-    pub(crate) callback:
-        RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> DomTree<'a>>>>,
+    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> DomTree<'a>>>>,
 }
 
 /// This struct provides an ergonomic API to quickly build VNodes.

+ 76 - 84
packages/core/src/scheduler.rs

@@ -14,7 +14,7 @@ Some essential reading:
 
 Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
 snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
-point if being "fast" if you can't also be "responsive."
+point in being "fast" if you can't also be "responsive."
 
 As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
 priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
@@ -87,7 +87,7 @@ use std::{
 };
 
 #[derive(Clone)]
-pub struct EventChannel {
+pub(crate) struct EventChannel {
     pub task_counter: Rc<Cell<u64>>,
     pub sender: UnboundedSender<SchedulerMsg>,
     pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
@@ -96,8 +96,13 @@ pub struct EventChannel {
 }
 
 pub enum SchedulerMsg {
-    Immediate(ScopeId),
+    // events from the host
     UiEvent(UserEvent),
+
+    // setstate
+    Immediate(ScopeId),
+
+    // tasks
     SubmitTask(FiberTask, u64),
     ToggleTask(u64),
     PauseTask(u64),
@@ -109,7 +114,7 @@ pub enum SchedulerMsg {
 ///
 /// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
 ///
-/// In Dioxus, the scheduler provides 3 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
+/// In Dioxus, the scheduler provides 4 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
 /// out.
 ///
 /// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
@@ -211,6 +216,7 @@ impl Scheduler {
 
         Self {
             pool,
+
             receiver,
 
             async_tasks: FuturesUnordered::new(),
@@ -219,7 +225,6 @@ impl Scheduler {
 
             heuristics,
 
-            // a storage for our receiver to dump into
             ui_events: VecDeque::new(),
 
             pending_immediates: VecDeque::new(),
@@ -230,7 +235,7 @@ impl Scheduler {
 
             current_priority: EventPriority::Low,
 
-            // a dedicated fiber for each priority
+            // sorted high to low by priority (0 = immediate, 3 = low)
             lanes: [
                 PriorityLane::new(),
                 PriorityLane::new(),
@@ -248,25 +253,69 @@ impl Scheduler {
 
     // Converts UI events into dirty scopes with various priorities
     pub fn consume_pending_events(&mut self) {
+        // consume all events that are "continuous" to be batched
+        // if we run into a discrete event, then bail early
+
         while let Some(trigger) = self.ui_events.pop_back() {
             if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {
                 if let Some(element) = trigger.mounted_dom_id {
-                    let priority = match &trigger.event {
-                        SyntheticEvent::ClipboardEvent(_) => {}
-                        SyntheticEvent::CompositionEvent(_) => {}
-                        SyntheticEvent::KeyboardEvent(_) => {}
-                        SyntheticEvent::FocusEvent(_) => {}
-                        SyntheticEvent::FormEvent(_) => {}
-                        SyntheticEvent::SelectionEvent(_) => {}
-                        SyntheticEvent::TouchEvent(_) => {}
-                        SyntheticEvent::WheelEvent(_) => {}
-                        SyntheticEvent::MediaEvent(_) => {}
-                        SyntheticEvent::AnimationEvent(_) => {}
-                        SyntheticEvent::TransitionEvent(_) => {}
-                        SyntheticEvent::ToggleEvent(_) => {}
-                        SyntheticEvent::MouseEvent(_) => {}
-                        SyntheticEvent::PointerEvent(_) => {}
-                        SyntheticEvent::GenericEvent(_) => {}
+                    let priority = match trigger.name {
+                        // clipboard
+                        "copy" | "cut" | "paste" => EventPriority::Medium,
+
+                        // Composition
+                        "compositionend" | "compositionstart" | "compositionupdate" => {
+                            EventPriority::Low
+                        }
+
+                        // Keyboard
+                        "keydown" | "keypress" | "keyup" => EventPriority::Low,
+
+                        // Focus
+                        "focus" | "blur" => EventPriority::Low,
+
+                        // Form
+                        "change" | "input" | "invalid" | "reset" | "submit" => EventPriority::Low,
+
+                        // Mouse
+                        "click" | "contextmenu" | "doubleclick" | "drag" | "dragend"
+                        | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
+                        | "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove"
+                        | "mouseout" | "mouseover" | "mouseup" => EventPriority::Low,
+
+                        // Pointer
+                        "pointerdown" | "pointermove" | "pointerup" | "pointercancel"
+                        | "gotpointercapture" | "lostpointercapture" | "pointerenter"
+                        | "pointerleave" | "pointerover" | "pointerout" => EventPriority::Low,
+
+                        // Selection
+                        "select" | "touchcancel" | "touchend" => EventPriority::Low,
+
+                        // Touch
+                        "touchmove" | "touchstart" => EventPriority::Low,
+
+                        // Wheel
+                        "scroll" | "wheel" => EventPriority::Low,
+
+                        // Media
+                        "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
+                        | "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata"
+                        | "loadstart" | "pause" | "play" | "playing" | "progress"
+                        | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
+                        | "timeupdate" | "volumechange" | "waiting" => EventPriority::Low,
+
+                        // Animation
+                        "animationstart" | "animationend" | "animationiteration" => {
+                            EventPriority::Low
+                        }
+
+                        // Transition
+                        "transitionend" => EventPriority::Low,
+
+                        // Toggle
+                        "toggle" => EventPriority::Low,
+
+                        _ => EventPriority::Low,
                     };
 
                     scope.call_listener(trigger.event, element);
@@ -341,7 +390,6 @@ impl Scheduler {
         self.manually_poll_events();
 
         if !self.has_any_work() {
-            self.clean_up_garbage();
             return committed_mutations;
         }
 
@@ -349,7 +397,7 @@ impl Scheduler {
 
         while self.has_any_work() {
             self.shift_priorities();
-            self.work_on_current_lane(&mut || false, &mut committed_mutations);
+            self.work_on_current_lane(|| false, &mut committed_mutations);
         }
 
         committed_mutations
@@ -393,7 +441,6 @@ impl Scheduler {
 
             // Wait for any new events if we have nothing to do
             if !self.has_any_work() {
-                self.clean_up_garbage();
                 let deadline_expired = self.wait_for_any_trigger(&mut deadline_reached).await;
 
                 if deadline_expired {
@@ -425,7 +472,7 @@ impl Scheduler {
     /// Returns true if the lane is finished before the deadline could be met.
     pub fn work_on_current_lane(
         &mut self,
-        deadline_reached: &mut impl FnMut() -> bool,
+        deadline_reached: impl FnMut() -> bool,
         mutations: &mut Vec<Mutations>,
     ) -> bool {
         // Work through the current subtree, and commit the results when it finishes
@@ -448,7 +495,7 @@ impl Scheduler {
             if let Some(scope) = self.current_lane().dirty_scopes.pop() {
                 let component = self.pool.get_scope(scope).unwrap();
                 let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
-                machine.stack.push(DiffInstruction::DiffNode { new, old });
+                machine.stack.push(DiffInstruction::Diff { new, old });
             }
         }
 
@@ -537,61 +584,6 @@ impl Scheduler {
     fn collect_garbage(&mut self, id: ElementId) {
         //
     }
-
-    pub fn clean_up_garbage(&mut self) {
-        let mut scopes_to_kill = Vec::new();
-        let mut garbage_list = Vec::new();
-
-        for scope in self.garbage_scopes.drain() {
-            let scope = self.pool.get_scope_mut(scope).unwrap();
-            for node in scope.consume_garbage() {
-                garbage_list.push(node);
-            }
-
-            while let Some(node) = garbage_list.pop() {
-                match &node {
-                    VNode::Text(_) => {
-                        self.pool.collect_garbage(node.mounted_id());
-                    }
-                    VNode::Anchor(_) => {
-                        self.pool.collect_garbage(node.mounted_id());
-                    }
-                    VNode::Suspended(_) => {
-                        self.pool.collect_garbage(node.mounted_id());
-                    }
-
-                    VNode::Element(el) => {
-                        self.pool.collect_garbage(node.mounted_id());
-                        for child in el.children {
-                            garbage_list.push(child);
-                        }
-                    }
-
-                    VNode::Fragment(frag) => {
-                        for child in frag.children {
-                            garbage_list.push(child);
-                        }
-                    }
-
-                    VNode::Component(comp) => {
-                        // TODO: run the hook destructors and then even delete the scope
-
-                        let scope_id = comp.associated_scope.get().unwrap();
-                        let scope = self.pool.get_scope(scope_id).unwrap();
-                        let root = scope.root();
-
-                        garbage_list.push(root);
-                        scopes_to_kill.push(scope_id);
-                    }
-                }
-            }
-        }
-
-        for scope in scopes_to_kill.drain(..) {
-            //
-            // kill em
-        }
-    }
 }
 
 pub(crate) struct PriorityLane {
@@ -619,8 +611,8 @@ impl PriorityLane {
 }
 
 pub struct TaskHandle {
-    pub sender: UnboundedSender<SchedulerMsg>,
-    pub our_id: u64,
+    pub(crate) sender: UnboundedSender<SchedulerMsg>,
+    pub(crate) our_id: u64,
 }
 
 impl TaskHandle {

+ 0 - 22
packages/core/src/scope.rs

@@ -31,7 +31,6 @@ pub struct Scope {
     pub(crate) frames: ActiveFrame,
     pub(crate) caller: Rc<WrappedCaller>,
     pub(crate) child_nodes: ScopeChildren<'static>,
-    pub(crate) pending_garbage: RefCell<Vec<*const VNode<'static>>>,
 
     // Listeners
     pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
@@ -90,7 +89,6 @@ impl Scope {
             listeners: Default::default(),
             borrowed_props: Default::default(),
             descendents: Default::default(),
-            pending_garbage: Default::default(),
         }
     }
 
@@ -110,7 +108,6 @@ impl Scope {
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
-
         self.ensure_drop_safety(pool);
 
         // Safety:
@@ -146,12 +143,6 @@ impl Scope {
     /// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
     /// all listeners and borrowed props so we can clear them here.
     pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
-        // make sure all garabge is collected before trying to proceed with anything else
-        debug_assert!(
-            self.pending_garbage.borrow().is_empty(),
-            "clean up your garabge please"
-        );
-
         // todo!("arch changes");
 
         // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
@@ -235,19 +226,6 @@ impl Scope {
         }
     }
 
-    pub(crate) fn consume_garbage(&self) -> Vec<&VNode> {
-        self.pending_garbage
-            .borrow_mut()
-            .drain(..)
-            .map(|node| {
-                // safety: scopes cannot cycle without their garbage being collected. these nodes are safe
-                let node: &VNode<'static> = unsafe { &*node };
-                let node: &VNode = unsafe { std::mem::transmute(node) };
-                node
-            })
-            .collect::<Vec<_>>()
-    }
-
     pub fn root(&self) -> &VNode {
         self.frames.fin_head()
     }

+ 20 - 8
packages/core/src/test_dom.rs

@@ -20,17 +20,25 @@ impl TestDom {
         NodeFactory::new(&self.bump)
     }
 
-    pub fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
+    pub fn render_direct<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
     where
         F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
     {
         lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
     }
 
+    pub fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> &'a VNode<'a>
+    where
+        F: FnOnce(NodeFactory<'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 = DiffMachine::new(mutations, &self.scheduler.pool);
-        machine.stack.push(DiffInstruction::DiffNode { new, old });
+        machine.stack.push(DiffInstruction::Diff { new, old });
         machine.mutations
     }
 
@@ -38,7 +46,7 @@ impl TestDom {
     where
         F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
     {
-        let old = self.bump.alloc(self.render(left));
+        let old = self.bump.alloc(self.render_direct(left));
 
         let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
 
@@ -58,20 +66,18 @@ impl TestDom {
         F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
         F2: FnOnce(NodeFactory<'a>) -> VNode<'a>,
     {
-        let old = self.bump.alloc(self.render(left));
-
-        let new = self.bump.alloc(self.render(right));
+        let (old, new) = (self.render(left), self.render(right));
 
         let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
 
         machine.stack.create_node(old, MountType::Append);
 
-        machine.work(&mut || false);
+        machine.work(|| false);
         let create_edits = machine.mutations;
 
         let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
 
-        machine.stack.push(DiffInstruction::DiffNode { old, new });
+        machine.stack.push(DiffInstruction::Diff { old, new });
 
         machine.work(&mut || false);
 
@@ -80,3 +86,9 @@ impl TestDom {
         (create_edits, edits)
     }
 }
+
+impl VirtualDom {
+    pub fn simulate(&mut self) {
+        //
+    }
+}

+ 42 - 29
packages/core/src/virtual_dom.rs

@@ -1,4 +1,5 @@
 //! # 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
@@ -20,21 +21,38 @@
 
 use crate::innerlude::*;
 use futures_util::{Future, FutureExt};
-use std::{
-    any::{Any, TypeId},
-    pin::Pin,
-    rc::Rc,
-};
+use std::{any::Any, pin::Pin};
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
-/// Differences are converted into patches which a renderer can use to draw the UI.
-///
-///
 ///
+/// Differences are converted into patches which a renderer can use to draw the UI.
 ///
+/// 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| {
+///     cx.render(rsx!{
+///         div {
+///             "Hello World"
+///         }
+///     })
+/// }
 ///
+/// async fn main() {
+///     let mut dom = VirtualDom::new(App);
+///     let mut inital_edits = dom.rebuild();
+///     initialize_screen(inital_edits);
 ///
+///     loop {
+///         let next_frame = TimeoutFuture::new(Duration::from_millis(16));
+///         let edits = dom.run_with_deadline(next_frame).await;
+///         apply_edits(edits);
+///         render_frame();
+///     }
+/// }
+/// ```
 pub struct VirtualDom {
     scheduler: Scheduler,
 
@@ -42,7 +60,7 @@ pub struct VirtualDom {
 
     root_fc: Box<dyn Any>,
 
-    root_props: Pin<Box<dyn std::any::Any>>,
+    root_props: Pin<Box<dyn Any>>,
 }
 
 impl VirtualDom {
@@ -178,28 +196,22 @@ impl VirtualDom {
         }
     }
 
-    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
+    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
     ///
-    /// The diff machine expects the RealDom's stack to be the root of the application
+    /// The diff machine expects the RealDom's stack to be the root of the application.
     ///
-    /// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
-    /// through "run". We completely avoid the task scheduler infrastructure.
-    pub fn rebuild<'s>(&'s mut self) -> Mutations<'s> {
-        let mut fut = self.rebuild_async().boxed_local();
-
-        loop {
-            if let Some(edits) = (&mut fut).now_or_never() {
-                break edits;
-            }
-        }
-    }
-
-    /// Rebuild the dom from the ground up
+    /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
+    /// root component will be ran once and then diffed. All updates will flow out as mutations.
+    ///
+    /// # Example
+    /// ```
+    /// static App: FC<()> = |cx| cx.render(rsx!{ "hello world" });
+    /// let mut dom = VirtualDom::new();
+    /// let edits = dom.rebuild();
     ///
-    /// This method is asynchronous to prevent the application from blocking while the dom is being rebuilt. Computing
-    /// the diff and creating nodes can be expensive, so we provide this method to avoid blocking the main thread. This
-    /// method can be useful when needing to perform some crucial periodic tasks.
-    pub async fn rebuild_async<'s>(&'s mut self) -> Mutations<'s> {
+    /// apply_edits(edits);
+    /// ```
+    pub fn rebuild<'s>(&'s mut self) -> Mutations<'s> {
         let mut shared = self.scheduler.pool.clone();
         let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
 
@@ -214,9 +226,10 @@ impl VirtualDom {
             diff_machine
                 .stack
                 .create_node(cur_component.frames.fin_head(), MountType::Append);
+
             diff_machine.stack.scope_stack.push(self.base_scope);
 
-            // let completed = diff_machine.work();
+            diff_machine.work(|| false);
         } else {
             // todo: should this be a hard error?
             log::warn!(

+ 53 - 0
packages/core/tests/README.md

@@ -0,0 +1,53 @@
+# Testing of Dioxus core
+
+
+NodeFactory
+- [] rsx, html, NodeFactory generate the same structures
+
+Diffing
+- [x] create elements
+- [x] create text
+- [x] create fragments
+- [x] create empty fragments (placeholders)
+- [x] diff elements
+- [x] diff from element/text to fragment
+- [x] diff from element/text to empty fragment
+- [x] diff to element with children works too
+- [x] replace with works forward
+- [x] replace with works backward
+- [x] un-keyed diffing
+- [x] keyed diffing
+- [x] keyed diffing out of order
+- [x] keyed diffing with prefix/suffix
+- [x] suspended nodes work 
+
+Lifecycle
+- [] Components mount properly
+- [] Components create new child components
+- [] Replaced components unmount old components and mount new
+- [] Post-render effects are called
+- [] 
+
+
+Shared Context
+- [] Shared context propagates downwards
+- [] unwrapping shared context if it doesn't exist works too
+
+Suspense
+- [] use_suspense generates suspended nodes
+
+
+Hooks 
+- [] Drop order is maintained
+- [] Shared hook state is okay
+- [] use_hook works
+- [] use_ref works
+- [] use_noderef works
+- [] use_provide_state
+- [] use_consume_state
+
+
+VirtualDOM API
+- [] work
+- [] rebuild
+- [] change props

+ 0 - 19
packages/core/tests/channels.rs

@@ -1,19 +0,0 @@
-use futures_channel::mpsc::unbounded;
-
-#[async_std::test]
-async fn channels() {
-    let (sender, mut receiver) = unbounded::<u32>();
-
-    // drop(sender);
-
-    match receiver.try_next() {
-        Ok(a) => {
-            dbg!(a);
-        }
-        Err(no) => {
-            dbg!(no);
-        }
-    }
-
-    sender.unbounded_send(1).unwrap();
-}

+ 36 - 34
packages/core/tests/create_iterative.rs → packages/core/tests/create_dom.rs

@@ -1,17 +1,23 @@
-//! tests to prove that the iterative implementation works
+//! Prove that the dom works normally through virtualdom methods.
+//! This methods all use "rebuild" which completely bypasses the scheduler.
+//! Hard rebuilds don't consume any events from the event queue.
 
-use anyhow::{Context, Result};
-use dioxus::{prelude::*, DomEdit, Mutations};
-mod test_logging;
+use dioxus::{prelude::*, DomEdit};
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;
+
+mod test_logging;
 use DomEdit::*;
 
-const LOGGING_ENABLED: bool = false;
+fn new_dom<P: Properties + 'static>(app: FC<P>, props: P) -> VirtualDom {
+    const IS_LOGGING_ENABLED: bool = false;
+    test_logging::set_up_logging(IS_LOGGING_ENABLED);
+    VirtualDom::new_with_props(app, props)
+}
 
 #[test]
 fn test_original_diff() {
-    static App: FC<()> = |cx| {
+    static APP: FC<()> = |cx| {
         cx.render(rsx! {
             div {
                 div {
@@ -21,7 +27,7 @@ fn test_original_diff() {
         })
     };
 
-    let mut dom = VirtualDom::new(App);
+    let mut dom = new_dom(APP, ());
     let mutations = dom.rebuild();
     assert_eq!(
         mutations.edits,
@@ -41,7 +47,7 @@ fn test_original_diff() {
 
 #[async_std::test]
 async fn create() {
-    static App: FC<()> = |cx| {
+    static APP: FC<()> = |cx| {
         cx.render(rsx! {
             div {
                 div {
@@ -59,9 +65,9 @@ async fn create() {
         })
     };
 
-    test_logging::set_up_logging(LOGGING_ENABLED);
-    let mut dom = VirtualDom::new(App);
-    let mutations = dom.rebuild_async().await;
+    let mut dom = new_dom(APP, ());
+    let mutations = dom.rebuild();
+
     assert_eq!(
         mutations.edits,
         [
@@ -92,7 +98,7 @@ async fn create() {
 
 #[async_std::test]
 async fn create_list() {
-    static App: FC<()> = |cx| {
+    static APP: FC<()> = |cx| {
         cx.render(rsx! {
             {(0..3).map(|f| rsx!{ div {
                 "hello"
@@ -100,10 +106,8 @@ async fn create_list() {
         })
     };
 
-    test_logging::set_up_logging(LOGGING_ENABLED);
-
-    let mut dom = VirtualDom::new(App);
-    let mutations = dom.rebuild_async().await;
+    let mut dom = new_dom(APP, ());
+    let mutations = dom.rebuild();
 
     // copilot wrote this test :P
     assert_eq!(
@@ -134,7 +138,7 @@ async fn create_list() {
 
 #[async_std::test]
 async fn create_simple() {
-    static App: FC<()> = |cx| {
+    static APP: FC<()> = |cx| {
         cx.render(rsx! {
             div {}
             div {}
@@ -143,10 +147,8 @@ async fn create_simple() {
         })
     };
 
-    test_logging::set_up_logging(LOGGING_ENABLED);
-
-    let mut dom = VirtualDom::new(App);
-    let mutations = dom.rebuild_async().await;
+    let mut dom = new_dom(APP, ());
+    let mutations = dom.rebuild();
 
     // copilot wrote this test :P
     assert_eq!(
@@ -179,10 +181,8 @@ async fn create_components() {
         })
     };
 
-    test_logging::set_up_logging(LOGGING_ENABLED);
-
-    let mut dom = VirtualDom::new(App);
-    let mutations = dom.rebuild_async().await;
+    let mut dom = new_dom(App, ());
+    let mutations = dom.rebuild();
 
     assert_eq!(
         mutations.edits,
@@ -225,10 +225,8 @@ async fn anchors() {
         })
     };
 
-    test_logging::set_up_logging(LOGGING_ENABLED);
-
-    let mut dom = VirtualDom::new(App);
-    let mutations = dom.rebuild_async().await;
+    let mut dom = new_dom(App, ());
+    let mutations = dom.rebuild();
     assert_eq!(
         mutations.edits,
         [
@@ -247,14 +245,18 @@ async fn anchors() {
 #[async_std::test]
 async fn suspended() {
     static App: FC<()> = |cx| {
-        let val = use_suspense(cx, || async {}, |cx, _| cx.render(rsx! { "hi "}));
+        let val = use_suspense(
+            cx,
+            || async {
+                //
+            },
+            |cx, _| cx.render(rsx! { "hi "}),
+        );
         cx.render(rsx! { {val} })
     };
 
-    test_logging::set_up_logging(LOGGING_ENABLED);
-
-    let mut dom = VirtualDom::new(App);
-    let mutations = dom.rebuild_async().await;
+    let mut dom = new_dom(App, ());
+    let mutations = dom.rebuild();
 
     assert_eq!(
         mutations.edits,

+ 0 - 2
packages/core/tests/debugdiff.rs

@@ -1,2 +0,0 @@
-/// A virtualdom wrapper used for testing purposes.
-pub struct DebugDiff {}

+ 0 - 48
packages/core/tests/diff_iterative.rs

@@ -1,48 +0,0 @@
-//! tests to prove that the iterative implementation works
-
-use dioxus::prelude::*;
-
-mod test_logging;
-use dioxus_core as dioxus;
-use dioxus_html as dioxus_elements;
-
-const LOGGING_ENABLED: bool = false;
-
-#[async_std::test]
-async fn test_iterative_create_components() {
-    static App: FC<()> = |cx| {
-        // test root fragments
-        cx.render(rsx! {
-            Child { "abc1" }
-            Child { "abc2" }
-            Child { "abc3" }
-        })
-    };
-
-    fn Child(cx: Context<()>) -> DomTree {
-        // test root fragments, anchors, and ChildNode type
-        cx.render(rsx! {
-            h1 {}
-            div { {cx.children()} }
-            Fragment {
-                Fragment {
-                    Fragment {
-                        "wozza"
-                    }
-                }
-            }
-            {(0..0).map(|_f| rsx!{ div { "walalla"}})}
-            p {}
-        })
-    }
-
-    test_logging::set_up_logging(LOGGING_ENABLED);
-
-    let mut dom = VirtualDom::new(App);
-
-    let mutations = dom.rebuild_async().await;
-    dbg!(mutations);
-
-    let mutations = dom.diff();
-    dbg!(mutations);
-}

+ 32 - 19
packages/core/tests/diffing.rs

@@ -4,24 +4,19 @@
 //!
 //! It does not validated that component lifecycles work properly. This is done in another test file.
 
-use dioxus::{prelude::*, DomEdit, TestDom};
+use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;
 
 mod test_logging;
-use DomEdit::*;
-
-// logging is wired up to the test harness
-// feel free to enable while debugging
-const IS_LOGGING_ENABLED: bool = false;
 
 fn new_dom() -> TestDom {
+    const IS_LOGGING_ENABLED: bool = false;
     test_logging::set_up_logging(IS_LOGGING_ENABLED);
     TestDom::new()
 }
 
-#[test]
-fn diffing_works() {}
+use DomEdit::*;
 
 /// Should push the text node onto the stack and modify it
 #[test]
@@ -98,8 +93,8 @@ fn fragments_create_properly() {
 fn empty_fragments_create_anchors() {
     let dom = new_dom();
 
-    let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
-    let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
+    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);
 
@@ -121,8 +116,8 @@ fn empty_fragments_create_anchors() {
 fn empty_fragments_create_many_anchors() {
     let dom = new_dom();
 
-    let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
-    let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
+    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);
     assert_eq!(
@@ -148,7 +143,7 @@ fn empty_fragments_create_many_anchors() {
 fn empty_fragments_create_anchors_with_many_children() {
     let dom = new_dom();
 
-    let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
+    let left = rsx!({ (0..0).map(|_| rsx! { div {} }) });
     let right = rsx!({
         (0..3).map(|f| {
             rsx! { div { "hello: {f}" }}
@@ -192,11 +187,11 @@ fn many_items_become_fragment() {
     let dom = new_dom();
 
     let left = rsx!({
-        (0..2).map(|f| {
+        (0..2).map(|_| {
             rsx! { div { "hello" }}
         })
     });
-    let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
+    let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
 
     let (create, change) = dom.lazy_diff(left, right);
     assert_eq!(
@@ -235,17 +230,17 @@ fn two_equal_fragments_are_equal() {
     let dom = new_dom();
 
     let left = rsx!({
-        (0..2).map(|f| {
+        (0..2).map(|_| {
             rsx! { div { "hello" }}
         })
     });
     let right = rsx!({
-        (0..2).map(|f| {
+        (0..2).map(|_| {
             rsx! { div { "hello" }}
         })
     });
 
-    let (create, change) = dom.lazy_diff(left, right);
+    let (_create, change) = dom.lazy_diff(left, right);
     assert!(change.edits.is_empty());
 }
 
@@ -263,7 +258,7 @@ fn two_fragments_with_differrent_elements_are_differet() {
         p {}
     );
 
-    let (create, changes) = dom.lazy_diff(left, right);
+    let (_create, changes) = dom.lazy_diff(left, right);
     log::debug!("{:#?}", &changes);
     assert_eq!(
         changes.edits,
@@ -700,3 +695,21 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
         ]
     );
 }
+
+#[test]
+fn suspense() {
+    let dom = new_dom();
+
+    let edits = dom.create(LazyNodes::new(|f| {
+        use std::cell::{Cell, RefCell};
+        VNode::Suspended(f.bump().alloc(VSuspended {
+            task_id: 0,
+            callback: RefCell::new(None),
+            dom_id: Cell::new(None),
+        }))
+    }));
+    assert_eq!(
+        edits.edits,
+        [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
+    );
+}

+ 0 - 6
packages/core/tests/integration.rs

@@ -1,6 +0,0 @@
-
-// type VirtualNode = VNode;
-
-/// Test a basic usage of a virtual dom + text renderer combo
-#[test]
-fn simple_integration() {}

+ 1 - 0
packages/core/tests/lifecycle.rs

@@ -0,0 +1 @@
+//! Tests for the lifecycle of components.

+ 1 - 0
packages/core/tests/scheduler.rs

@@ -0,0 +1 @@
+//! Tests for the scheduler.

+ 0 - 30
packages/core/tests/work_sync.rs

@@ -1,30 +0,0 @@
-//! Diffing is interruptible, but uses yield_now which is loop-pollable
-//!
-//! This means you can actually call it synchronously if you want.
-
-use anyhow::{Context, Result};
-use dioxus::{prelude::*, scope::Scope};
-use dioxus_core as dioxus;
-use dioxus_html as dioxus_elements;
-use futures_util::FutureExt;
-
-#[test]
-fn worksync() {
-    static App: FC<()> = |cx| {
-        cx.render(rsx! {
-            div {"hello"}
-        })
-    };
-    let mut dom = VirtualDom::new(App);
-
-    let mut fut = dom.rebuild_async().boxed_local();
-
-    let mutations = loop {
-        let g = (&mut fut).now_or_never();
-        if g.is_some() {
-            break g.unwrap();
-        }
-    };
-
-    dbg!(mutations);
-}

+ 0 - 4
packages/web/Cargo.toml

@@ -65,10 +65,6 @@ features = [
     "HtmlOptionElement",
 ]
 
-[profile.release]
-lto = true
-opt-level = 's'
-
 [lib]
 crate-type = ["cdylib", "rlib"]
 

+ 79 - 79
packages/web/src/dom.rs

@@ -582,85 +582,85 @@ pub fn load_document() -> Document {
 
 pub fn event_name_from_typ(typ: &str) -> &'static str {
     match typ {
-        "copy" => "oncopy",
-        "cut" => "oncut",
-        "paste" => "onpaste",
-        "compositionend" => "oncompositionend",
-        "compositionstart" => "oncompositionstart",
-        "compositionupdate" => "oncompositionupdate",
-        "keydown" => "onkeydown",
-        "keypress" => "onkeypress",
-        "keyup" => "onkeyup",
-        "focus" => "onfocus",
-        "blur" => "onblur",
-        "change" => "onchange",
-        "input" => "oninput",
-        "invalid" => "oninvalid",
-        "reset" => "onreset",
-        "submit" => "onsubmit",
-        "click" => "onclick",
-        "contextmenu" => "oncontextmenu",
-        "doubleclick" => "ondoubleclick",
-        "drag" => "ondrag",
-        "dragend" => "ondragend",
-        "dragenter" => "ondragenter",
-        "dragexit" => "ondragexit",
-        "dragleave" => "ondragleave",
-        "dragover" => "ondragover",
-        "dragstart" => "ondragstart",
-        "drop" => "ondrop",
-        "mousedown" => "onmousedown",
-        "mouseenter" => "onmouseenter",
-        "mouseleave" => "onmouseleave",
-        "mousemove" => "onmousemove",
-        "mouseout" => "onmouseout",
-        "mouseover" => "onmouseover",
-        "mouseup" => "onmouseup",
-        "pointerdown" => "onpointerdown",
-        "pointermove" => "onpointermove",
-        "pointerup" => "onpointerup",
-        "pointercancel" => "onpointercancel",
-        "gotpointercapture" => "ongotpointercapture",
-        "lostpointercapture" => "onlostpointercapture",
-        "pointerenter" => "onpointerenter",
-        "pointerleave" => "onpointerleave",
-        "pointerover" => "onpointerover",
-        "pointerout" => "onpointerout",
-        "select" => "onselect",
-        "touchcancel" => "ontouchcancel",
-        "touchend" => "ontouchend",
-        "touchmove" => "ontouchmove",
-        "touchstart" => "ontouchstart",
-        "scroll" => "onscroll",
-        "wheel" => "onwheel",
-        "animationstart" => "onanimationstart",
-        "animationend" => "onanimationend",
-        "animationiteration" => "onanimationiteration",
-        "transitionend" => "ontransitionend",
-        "abort" => "onabort",
-        "canplay" => "oncanplay",
-        "canplaythrough" => "oncanplaythrough",
-        "durationchange" => "ondurationchange",
-        "emptied" => "onemptied",
-        "encrypted" => "onencrypted",
-        "ended" => "onended",
-        "error" => "onerror",
-        "loadeddata" => "onloadeddata",
-        "loadedmetadata" => "onloadedmetadata",
-        "loadstart" => "onloadstart",
-        "pause" => "onpause",
-        "play" => "onplay",
-        "playing" => "onplaying",
-        "progress" => "onprogress",
-        "ratechange" => "onratechange",
-        "seeked" => "onseeked",
-        "seeking" => "onseeking",
-        "stalled" => "onstalled",
-        "suspend" => "onsuspend",
-        "timeupdate" => "ontimeupdate",
-        "volumechange" => "onvolumechange",
-        "waiting" => "onwaiting",
-        "toggle" => "ontoggle",
+        "copy" => "copy",
+        "cut" => "cut",
+        "paste" => "paste",
+        "compositionend" => "compositionend",
+        "compositionstart" => "compositionstart",
+        "compositionupdate" => "compositionupdate",
+        "keydown" => "keydown",
+        "keypress" => "keypress",
+        "keyup" => "keyup",
+        "focus" => "focus",
+        "blur" => "blur",
+        "change" => "change",
+        "input" => "input",
+        "invalid" => "invalid",
+        "reset" => "reset",
+        "submit" => "submit",
+        "click" => "click",
+        "contextmenu" => "contextmenu",
+        "doubleclick" => "doubleclick",
+        "drag" => "drag",
+        "dragend" => "dragend",
+        "dragenter" => "dragenter",
+        "dragexit" => "dragexit",
+        "dragleave" => "dragleave",
+        "dragover" => "dragover",
+        "dragstart" => "dragstart",
+        "drop" => "drop",
+        "mousedown" => "mousedown",
+        "mouseenter" => "mouseenter",
+        "mouseleave" => "mouseleave",
+        "mousemove" => "mousemove",
+        "mouseout" => "mouseout",
+        "mouseover" => "mouseover",
+        "mouseup" => "mouseup",
+        "pointerdown" => "pointerdown",
+        "pointermove" => "pointermove",
+        "pointerup" => "pointerup",
+        "pointercancel" => "pointercancel",
+        "gotpointercapture" => "gotpointercapture",
+        "lostpointercapture" => "lostpointercapture",
+        "pointerenter" => "pointerenter",
+        "pointerleave" => "pointerleave",
+        "pointerover" => "pointerover",
+        "pointerout" => "pointerout",
+        "select" => "select",
+        "touchcancel" => "touchcancel",
+        "touchend" => "touchend",
+        "touchmove" => "touchmove",
+        "touchstart" => "touchstart",
+        "scroll" => "scroll",
+        "wheel" => "wheel",
+        "animationstart" => "animationstart",
+        "animationend" => "animationend",
+        "animationiteration" => "animationiteration",
+        "transitionend" => "transitionend",
+        "abort" => "abort",
+        "canplay" => "canplay",
+        "canplaythrough" => "canplaythrough",
+        "durationchange" => "durationchange",
+        "emptied" => "emptied",
+        "encrypted" => "encrypted",
+        "ended" => "ended",
+        "error" => "error",
+        "loadeddata" => "loadeddata",
+        "loadedmetadata" => "loadedmetadata",
+        "loadstart" => "loadstart",
+        "pause" => "pause",
+        "play" => "play",
+        "playing" => "playing",
+        "progress" => "progress",
+        "ratechange" => "ratechange",
+        "seeked" => "seeked",
+        "seeking" => "seeking",
+        "stalled" => "stalled",
+        "suspend" => "suspend",
+        "timeupdate" => "timeupdate",
+        "volumechange" => "volumechange",
+        "waiting" => "waiting",
+        "toggle" => "toggle",
         _ => {
             panic!("unsupported event type")
         }