Prechádzať zdrojové kódy

feat: props memoization is more powerful

This commit solves the memoization , properly memoizing properties that don't have any generic parameters. This is a rough heuristic to prevent non-static lifetimes from creeping into props and breaking our minual lifetime management.

Props that have a generic parameter are opted-out of the `partialeq` requirement and props *without* lifetimes must implement partialeq. We're going to leave manual disabling of memoization for future work.
Jonathan Kelley 4 rokov pred
rodič
commit
73047fe
37 zmenil súbory, kde vykonal 879 pridanie a 979 odobranie
  1. 2 1
      Cargo.toml
  2. 1 1
      notes/TODO.md
  3. 2 2
      packages/cli/Cargo.toml
  4. 1 1
      packages/cli/src/builder.rs
  5. 17 2
      packages/core-macro/src/props/mod.rs
  6. 1 1
      packages/core/.vscode/settings.json
  7. 4 2
      packages/core/examples/borrowed.rs
  8. 18 9
      packages/core/src/component.rs
  9. 23 1
      packages/core/src/debug_renderer.rs
  10. 21 17
      packages/core/src/diff.rs
  11. 2 4
      packages/core/src/lib.rs
  12. 12 0
      packages/core/src/nodebuilder.rs
  13. 146 97
      packages/core/src/nodes.rs
  14. 0 695
      packages/core/src/patch.rs
  15. 8 9
      packages/core/src/virtual_dom.rs
  16. 4 2
      packages/web/Cargo.toml
  17. 10 9
      packages/web/examples/basic.rs
  18. 8 10
      packages/web/examples/context.rs
  19. 1 0
      packages/web/examples/deep.rs
  20. 1 0
      packages/web/examples/demoday.rs
  21. 0 4
      packages/web/examples/derive.rs
  22. 11 11
      packages/web/examples/events.rs
  23. 184 0
      packages/web/examples/framework_benchmark.rs
  24. 14 1
      packages/web/examples/hello.rs
  25. 1 0
      packages/web/examples/helloworld.rs
  26. 4 5
      packages/web/examples/infer.rs
  27. 7 14
      packages/web/examples/input.rs
  28. 1 1
      packages/web/examples/landingpage.rs
  29. 1 1
      packages/web/examples/list.rs
  30. 1 0
      packages/web/examples/many.rs
  31. 13 0
      packages/web/examples/props.rs
  32. 16 15
      packages/web/examples/rsxt.rs
  33. 8 7
      packages/web/examples/todomvc/main.rs
  34. 10 12
      packages/web/examples/todomvc_simple.rs
  35. 1 1
      packages/web/examples/todomvcsingle.rs
  36. 50 42
      packages/web/src/lib.rs
  37. 275 2
      packages/web/src/new.rs

+ 2 - 1
Cargo.toml

@@ -52,7 +52,8 @@ members = [
     "packages/core-macro",
     "packages/core",
     "packages/html-namespace",
-    # "packages/web",
+    "packages/web",
+    "packages/cli",
     # "packages/atoms",
     # "packages/ssr",
     # "packages/docsite",

+ 1 - 1
notes/TODO.md

@@ -2,7 +2,7 @@
 - [] Transition away from names and towards compile-time safe tags
 - [] Fix diffing of fragments
 - [] Properly integrate memoization to prevent safety issues with children
-- [] Understand the issue with callbacks (outdated generations)
+- [x] Understand the issue with callbacks (outdated generations)
 - [] Fix examples for core, web, ssr, and general
 - [] Finish up documentation
 - [] Polish the Recoil (Dirac?) API

+ 2 - 2
packages/cli/Cargo.toml

@@ -11,13 +11,13 @@ description = "CLI tool for developing, testing, and publishing Dioxus apps"
 [dependencies]
 thiserror = "1.0.23"
 log = "0.4.13"
-fern = { version = "0.6.0", features = ["colored"] }
+fern = { version="0.6.0", features=["colored"] }
 wasm-bindgen-cli-support = "0.2.73"
 anyhow = "1.0.38"
 argh = "0.1.4"
 serde = "1.0.120"
 serde_json = "1.0.61"
-async-std = { version = "1.9.0", features = ["attributes"] }
+async-std = { version="1.9.0", features=["attributes"] }
 tide = "0.15.0"
 fs_extra = "1.2.0"
 

+ 1 - 1
packages/cli/src/builder.rs

@@ -131,7 +131,7 @@ fn gen_page(module: &str) -> String {
     <!-- Note the usage of `type=module` here as this is an ES6 module -->
     <script type="module">
       import init from "{}";
-      init();
+      init("./wasm/module_bg.wasm");
     </script>
   </body>
 </html>

+ 17 - 2
packages/core-macro/src/props/mod.rs

@@ -502,6 +502,7 @@ mod field_info {
 
 mod struct_info {
     use proc_macro2::TokenStream;
+    use quote::__private::ext::RepToTokensExt;
     use quote::quote;
     use syn::parse::Error;
 
@@ -569,6 +570,13 @@ mod struct_info {
                 ref builder_name,
                 ..
             } = *self;
+
+            // we're generating stuff that goes into unsafe code here
+            // we use the heuristic: are there *any* generic parameters?
+            // If so, then they might have non-static lifetimes and we can't compare two generic things that *might borrow*
+            // Therefore, we will generate code that shortcircuits the "comparison" in memoization
+            let are_there_generics = self.generics.params.len() > 0;
+
             let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
             let all_fields_param = syn::GenericParam::Type(
                 syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
@@ -650,6 +658,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                     .extend(predicates.predicates.clone());
             }
 
+            let can_memoize = match are_there_generics {
+                true => quote! { false  },
+                false => quote! { self == other },
+            };
+
             Ok(quote! {
                 impl #impl_generics #name #ty_generics #where_clause {
                     #[doc = #builder_method_doc]
@@ -679,12 +692,14 @@ Finally, call `.build()` to create the instance of `{name}`.
                     }
                 }
 
-                unsafe impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
+                impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
                     type Builder = #builder_name #generics_with_empty;
-                    const CAN_BE_MEMOIZED: bool = true;
                     fn builder() -> Self::Builder {
                         #name::builder()
                     }
+                    unsafe fn memoize(&self, other: &Self) -> bool {
+                        #can_memoize
+                    }
                 }
 
             })

+ 1 - 1
packages/core/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-    "rust-analyzer.inlayHints.enable": true
+    "rust-analyzer.inlayHints.enable": false
 }

+ 4 - 2
packages/core/examples/borrowed.rs

@@ -63,10 +63,12 @@ impl PartialEq for ChildProps {
         false
     }
 }
-unsafe impl Properties for ChildProps {
+impl Properties for ChildProps {
     type Builder = ();
-    const CAN_BE_MEMOIZED: bool = false;
     fn builder() -> Self::Builder {
         ()
     }
+    unsafe fn memoize(&self, other: &Self) -> bool {
+        self == other
+    }
 }

+ 18 - 9
packages/core/src/component.rs

@@ -7,21 +7,27 @@
 
 use crate::innerlude::FC;
 
-pub unsafe trait Properties: PartialEq + Sized {
+pub trait Properties: Sized {
     type Builder;
-    const CAN_BE_MEMOIZED: bool;
     fn builder() -> Self::Builder;
+
+    /// Memoization can only happen if the props are 'static
+    /// 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;
 }
 
-unsafe impl Properties for () {
-    const CAN_BE_MEMOIZED: bool = true;
+impl Properties for () {
     type Builder = EmptyBuilder;
-
     fn builder() -> Self::Builder {
         EmptyBuilder {}
     }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        true
+    }
 }
-
+// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
+// that the macros use to anonymously complete prop construction.
 pub struct EmptyBuilder;
 impl EmptyBuilder {
     #[inline]
@@ -30,6 +36,8 @@ impl EmptyBuilder {
     }
 }
 
+/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
+/// to initialize a component's props.
 pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
     T::builder()
 }
@@ -39,9 +47,10 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
 ///
 /// Fragments capture a series of children without rendering extra nodes.
 ///
-///
-///
-pub static Fragment: FC<()> = |ctx| {
+/// 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.
+#[allow(non_upper_case_globals)]
+pub const Fragment: FC<()> = |ctx| {
     use crate::prelude::*;
     ctx.render(LazyNodes::new(move |c| {
         crate::nodebuilder::vfragment(c, None, ctx.children())

+ 23 - 1
packages/core/src/debug_renderer.rs

@@ -3,6 +3,7 @@
 //!
 //! Renderers don't actually need to own the virtual dom (it's up to the implementer).
 
+use crate::innerlude::RealDom;
 use crate::{events::EventTrigger, virtual_dom::VirtualDom};
 use crate::{innerlude::Result, prelude::*};
 
@@ -40,7 +41,7 @@ impl DebugRenderer {
         Ok(())
     }
 
-    pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
+    pub fn step<Dom: RealDom>(&mut self, machine: &mut DiffMachine<Dom>) -> Result<()> {
         Ok(())
     }
 
@@ -70,6 +71,27 @@ impl DebugRenderer {
     pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
         Ok(())
     }
+
+    pub fn render_nodes<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
+    where
+        F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
+    {
+        Ok(())
+    }
+}
+
+pub struct DebugVNodeSource {
+    bump: Bump,
+}
+impl DebugVNodeSource {
+    fn new() -> Self {
+        Self { bump: Bump::new() }
+    }
+
+    fn render_nodes(&self) -> VNode {
+        // let ctx = NodeCtx
+        todo!()
+    }
 }
 
 #[cfg(test)]

+ 21 - 17
packages/core/src/diff.rs

@@ -44,31 +44,30 @@ use std::{
 /// single node
 pub trait RealDom {
     // Navigation
-    fn push_root(&self, root: RealDomNode);
-    fn pop(&self);
+    fn push_root(&mut self, root: RealDomNode);
 
     // Add Nodes to the dom
-    fn append_child(&self);
-    fn replace_with(&self);
+    fn append_child(&mut self);
+    fn replace_with(&mut self);
 
     // Remove Nodesfrom the dom
-    fn remove(&self);
-    fn remove_all_children(&self);
+    fn remove(&mut self);
+    fn remove_all_children(&mut self);
 
     // Create
-    fn create_text_node(&self, text: &str) -> RealDomNode;
-    fn create_element(&self, tag: &str) -> RealDomNode;
-    fn create_element_ns(&self, tag: &str, namespace: &str) -> RealDomNode;
+    fn create_text_node(&mut self, text: &str) -> RealDomNode;
+    fn create_element(&mut self, tag: &str) -> RealDomNode;
+    fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode;
 
     // events
-    fn new_event_listener(&self, event: &str, scope: ScopeIdx, id: usize);
-    // fn new_event_listener(&self, event: &str);
-    fn remove_event_listener(&self, event: &str);
+    fn new_event_listener(&mut self, event: &str, scope: ScopeIdx, id: usize);
+    // fn new_event_listener(&mut self, event: &str);
+    fn remove_event_listener(&mut self, event: &str);
 
     // modify
-    fn set_text(&self, text: &str);
-    fn set_attribute(&self, name: &str, value: &str, is_namespaced: bool);
-    fn remove_attribute(&self, name: &str);
+    fn set_text(&mut self, text: &str);
+    fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool);
+    fn remove_attribute(&mut self, name: &str);
 
     // node ref
     fn raw_node_as_any_mut(&self) -> &mut dyn Any;
@@ -470,7 +469,7 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
     //     [... node]
     //
     // The change list stack is left unchanged.
-    fn diff_listeners(&self, old: &[Listener<'_>], new: &[Listener<'_>]) {
+    fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) {
         if !old.is_empty() || !new.is_empty() {
             // self.dom.commit_traversal();
         }
@@ -518,7 +517,12 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
     //     [... node]
     //
     // The change list stack is left unchanged.
-    fn diff_attr(&self, old: &'a [Attribute<'a>], new: &'a [Attribute<'a>], is_namespaced: bool) {
+    fn diff_attr(
+        &mut self,
+        old: &'a [Attribute<'a>],
+        new: &'a [Attribute<'a>],
+        is_namespaced: bool,
+    ) {
         // Do O(n^2) passes to add/update and remove attributes, since
         // there are almost always very few attributes.
         //

+ 2 - 4
packages/core/src/lib.rs

@@ -11,10 +11,9 @@
 pub mod arena;
 pub mod component; // Logic for extending FC
 
-// pub mod debug_renderer;
+pub mod debug_renderer;
 pub mod diff;
-pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
-               // the diffing algorithm that builds the ChangeList
+
 pub mod error; // Error type we expose to the renderers
 pub mod events; // Manages the synthetic event API
 pub mod hooks; // Built-in hooks
@@ -36,7 +35,6 @@ pub(crate) mod innerlude {
     pub use crate::hooks::*;
     pub use crate::nodebuilder::*;
     pub use crate::nodes::*;
-    pub use crate::patch::*;
     pub use crate::virtual_dom::*;
 
     pub type FC<P> = fn(Context<P>) -> VNode;

+ 12 - 0
packages/core/src/nodebuilder.rs

@@ -494,10 +494,22 @@ where
     ///     .finish();
     /// ```    
     pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
+        let len_before = self.children.len();
         for item in nodes {
             let child = item.into_vnode(&self.ctx);
             self.children.push(child);
         }
+        let len_after = self.children.len();
+        if len_after > len_before {
+            let last_child = self.children.last().unwrap();
+            if last_child.key().is_none() {
+                // TODO: Somehow get the name of the component when NodeCtx is being made
+                const ERR_MSG: &str = r#"Warning: Each child in an array or iterator should have a unique "key" prop. 
+                Check the render method of XXXX. 
+                See fb.me/react-warning-keys for more information. "#;
+                log::error!("{}", ERR_MSG);
+            }
+        }
         self
     }
 }

+ 146 - 97
packages/core/src/nodes.rs

@@ -111,9 +111,29 @@ impl<'a> VNode<'a> {
         }
     }
 
-    fn get_child(&self, id: u32) -> Option<VNode<'a>> {
+    fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> {
         todo!()
     }
+
+    pub fn is_real(&self) -> bool {
+        match self {
+            VNode::Element(_) => true,
+            VNode::Text(_) => true,
+            VNode::Fragment(_) => false,
+            VNode::Suspended => false,
+            VNode::Component(_) => false,
+        }
+    }
+
+    pub fn get_mounted_id(&self) -> Option<RealDomNode> {
+        match self {
+            VNode::Element(_) => todo!(),
+            VNode::Text(_) => todo!(),
+            VNode::Fragment(_) => todo!(),
+            VNode::Suspended => todo!(),
+            VNode::Component(_) => todo!(),
+        }
+    }
 }
 
 #[derive(Clone)]
@@ -257,88 +277,68 @@ pub struct VComponent<'src> {
 }
 
 impl<'a> VComponent<'a> {
-    // use the type parameter on props creation and move it into a portable context
-    // this lets us keep scope generic *and* downcast its props when we need to:
-    // - perform comparisons when diffing (memoization)
-    // TODO: lift the requirement that props need to be static
-    // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
-
+    /// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl)
+    /// If it is set to true, then this method will be called which implements automatic memoization.
+    ///
+    /// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
     pub fn new<P: Properties + 'a>(
-        // bump: &'a Bump,
         ctx: &NodeCtx<'a>,
         component: FC<P>,
-        // props: bumpalo::boxed::Box<'a, P>,
         props: P,
         key: Option<&'a str>,
         children: &'a [VNode<'a>],
     ) -> Self {
-        // pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
-        // let bad_props = unsafe { transmogrify(props) };
         let bump = ctx.bump();
-        let caller_ref = component as *const ();
-        let props = bump.alloc(props);
+        let user_fc = component as *const ();
 
+        let props = bump.alloc(props);
         let raw_props = props as *const P as *const ();
 
         let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
-            if P::CAN_BE_MEMOIZED {
-                Some(bump.alloc(move |other: &VComponent| {
-                    // Safety:
-                    // We are guaranteed that the props will be of the same type because
-                    // there is no way to create a VComponent other than this `new` method.
-                    //
-                    // Therefore, if the render functions are identical (by address), then so will be
-                    // props type paramter (because it is the same render function). Therefore, we can be
-                    // sure
-                    if caller_ref == other.user_fc {
-                        // let g = other.raw_ctx.downcast_ref::<P>().unwrap();
-                        let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
-                        &props == &real_other
-                    } else {
-                        false
+            Some(bump.alloc(move |other: &VComponent| {
+                // Safety:
+                // ------
+                //
+                // Invariants:
+                // - Component function pointers are the same
+                // - Generic properties on the same function pointer are the same
+                // - Lifetime of P borrows from its parent
+                // - The parent scope still exists when method is called
+                // - Casting from T to *const () is portable
+                //
+                // Explanation:
+                //   We are guaranteed that the props will be of the same type because
+                //   there is no way to create a VComponent other than this `new` method.
+                //
+                //   Therefore, if the render functions are identical (by address), then so will be
+                //   props type paramter (because it is the same render function). Therefore, we can be
+                //   sure that it is safe to interperet the previous props raw pointer as the same props
+                //   type. From there, we can call the props' "memoize" method to see if we can
+                //   avoid re-rendering the component.
+                if user_fc == other.user_fc {
+                    let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
+                    let props_memoized = unsafe { props.memoize(&real_other) };
+                    match (props_memoized, children.len() == 0) {
+                        (true, true) => true,
+                        _ => false,
                     }
-                }))
-            } else {
-                None
-            }
+                } else {
+                    false
+                }
+            }))
         };
 
-        // let prref: &'a P = props.as_ref();
-
-        // let r = create_closure(component, raw_props);
-        // let caller: Rc<dyn for<'g> Fn(&'g Scope) -> VNode<'g>> = Rc::new(move |scope| {
-        //     // r(scope);
-        //     //
-        //     // let props2 = bad_props;
-        //     // props.as_ref();
-        //     // let ctx = Context {
-        //     //     props: prref,
-        //     //     scope,
-        //     // };
-        //     // let ctx: Context<'g, P> = todo!();
-        //     // todo!()
-        //     // let r = component(ctx);
-        //     todo!()
-        // });
-        let caller = create_closure(component, raw_props);
-
-        // let caller: Rc<dyn Fn(&Scope) -> VNode> = Rc::new(create_closure(component, raw_props));
-
-        let key = match key {
-            Some(key) => NodeKey::new(key),
-            None => NodeKey(None),
-        };
-
-        // raw_props: Box::new(props),
-        // comparator: Rc::new(props_comparator),
         Self {
-            key,
-            ass_scope: RefCell::new(None),
-            user_fc: caller_ref,
+            user_fc,
             comparator,
             raw_props,
             children,
-            caller,
+            ass_scope: RefCell::new(None),
+            key: match key {
+                Some(key) => NodeKey::new(key),
+                None => NodeKey(None),
+            },
+            caller: create_closure(component, raw_props),
             mounted_root: Cell::new(RealDomNode::empty()),
         }
     }
@@ -346,7 +346,7 @@ impl<'a> VComponent<'a> {
 
 type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
 
-fn create_closure<'a, P: Properties + 'a>(
+fn create_closure<'a, P: 'a>(
     component: FC<P>,
     raw_props: *const (),
 ) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
@@ -385,7 +385,9 @@ impl<'a> VFragment<'a> {
 }
 
 /// This method converts a list of nested real/virtual nodes into a stream of nodes that are definitely associated
-/// with the real dom.
+/// with the real dom. The only types of nodes that may be returned are text, elemets, and components.
+///
+/// Components *are* considered virtual, but this iterator can't necessarily handle them without the scope arena.
 ///
 /// Why?
 /// ---
@@ -401,48 +403,81 @@ pub fn iterate_real_nodes<'a>(nodes: &'a [VNode<'a>]) -> RealNodeIterator<'a> {
     RealNodeIterator::new(nodes)
 }
 
-struct RealNodeIterator<'a> {
+pub struct RealNodeIterator<'a> {
     nodes: &'a [VNode<'a>],
 
-    // an idx for each level of nesting
-    // it's highly highly unlikely to hit 4 levels of nested fragments
-    // so... we just don't support it
-    nesting_idxs: [Option<u32>; 3],
+    // this node is always a "real" node
+    // the index is "what sibling # is it"
+    // IE in a list of children on a fragment, the node will be a text node that's the 5th sibling
+    node_stack: Vec<(&'a VNode<'a>, u32)>,
 }
 
 impl<'a> RealNodeIterator<'a> {
+    // We immediately descend to the first real node we can find
     fn new(nodes: &'a [VNode<'a>]) -> Self {
-        Self {
-            nodes,
-            nesting_idxs: [None, None, None],
+        let mut node_stack = Vec::new();
+        if nodes.len() > 0 {
+            let mut cur_node = nodes.get(0).unwrap();
+            loop {
+                node_stack.push((cur_node, 0_u32));
+                if !cur_node.is_real() {
+                    cur_node = cur_node.get_child(0).unwrap();
+                } else {
+                    break;
+                }
+            }
         }
-    }
 
-    // advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
-    fn advance_cursor(&mut self) {
-        match self.nesting_idxs {
-            [None, ..] => {}
-        }
+        Self { nodes, node_stack }
     }
 
-    fn get_current_node(&self) -> Option<&VNode<'a>> {
-        match self.nesting_idxs {
-            [None, None, None] => None,
-            [Some(a), None, None] => Some(&self.nodes[a as usize]),
-            [Some(a), Some(b), None] => {
+    // // advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
+    // fn advance_cursor(&mut self) {
+    //     let (mut cur_node, mut cur_id) = self.node_stack.last().unwrap();
+
+    //     while !cur_node.is_real() {
+    //         match cur_node {
+    //             VNode::Element(_) | VNode::Text(_) => todo!(),
+    //             VNode::Suspended => todo!(),
+    //             VNode::Component(_) => todo!(),
+    //             VNode::Fragment(frag) => {
+    //                 let p = frag.children;
+    //             }
+    //         }
+    //     }
+    // }
+
+    fn next_node(&mut self) -> bool {
+        let (mut cur_node, cur_id) = self.node_stack.last_mut().unwrap();
+
+        match cur_node {
+            VNode::Fragment(frag) => {
                 //
-                *&self.nodes[a as usize].get_child(b).as_ref()
-            }
-            [Some(a), Some(b), Some(c)] => {
-                //
-                *&self.nodes[a as usize]
-                    .get_child(b)
-                    .unwrap()
-                    .get_child(c)
-                    .as_ref()
+                if *cur_id + 1 > frag.children.len() as u32 {
+                    self.node_stack.pop();
+                    let next = self.node_stack.last_mut();
+                    return false;
+                }
+                *cur_id += 1;
+                true
             }
+
+            VNode::Element(_) => todo!(),
+            VNode::Text(_) => todo!(),
+            VNode::Suspended => todo!(),
+            VNode::Component(_) => todo!(),
         }
     }
+
+    fn get_current_node(&self) -> Option<&VNode<'a>> {
+        self.node_stack.last().map(|(node, id)| match node {
+            VNode::Element(_) => todo!(),
+            VNode::Text(_) => todo!(),
+            VNode::Fragment(_) => todo!(),
+            VNode::Suspended => todo!(),
+            VNode::Component(_) => todo!(),
+        })
+    }
 }
 
 impl<'a> Iterator for RealNodeIterator<'a> {
@@ -485,12 +520,26 @@ impl<'a> Iterator for RealNodeIterator<'a> {
 }
 
 mod tests {
+    use crate::debug_renderer::DebugRenderer;
     use crate::nodebuilder::LazyNodes;
 
+    use crate as dioxus;
+    use dioxus::prelude::*;
     #[test]
     fn iterate_nodes() {
-        // let t1 = LazyNodes::new(|b| {
-        //     //
-        // });
+        let rs = rsx! {
+            Fragment {
+                Fragment {
+                    Fragment {
+                        Fragment {
+                            h1 {"abc1"}
+                        }
+                        h2 {"abc2"}
+                    }
+                    h3 {"abc3"}
+                }
+                h4 {"abc4"}
+            }
+        };
     }
 }

+ 0 - 695
packages/core/src/patch.rs

@@ -1,695 +0,0 @@
-//! Changelist
-//! ----------
-//!
-//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom.
-//!
-//! # Design
-//! ---
-//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer.
-//!
-//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms,
-//! this is an appropriate abstraction .
-//!
-//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back
-//! to the renderer. The renderer is responsible for propogating the updates to the final display.
-//!
-//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
-//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
-//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
-//!
-//! # Known Issues
-//! ----
-//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom) - solvable by the renderer
-
-use crate::innerlude::ScopeIdx;
-
-pub type EditList<'src> = Vec<Edit<'src>>;
-
-/// The `Edit` represents a single modifcation of the renderer tree.
-/// todo @jon, go through and make certain fields static. tag names should be known at compile time
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serde", serde(tag = "type"))]
-#[derive(Debug)]
-pub enum Edit<'src_bump> {
-    // ========================================================
-    // Common Ops: The most common operation types
-    // ========================================================
-    SetText {
-        text: &'src_bump str,
-    },
-    SetClass {
-        class_name: &'src_bump str,
-    },
-    CreateTextNode {
-        text: &'src_bump str,
-    },
-    CreateElement {
-        // todo - make static?
-        tag_name: &'src_bump str,
-    },
-    CreateElementNs {
-        // todo - make static?
-        tag_name: &'src_bump str,
-        // todo - make static?
-        ns: &'src_bump str,
-    },
-
-    // ========================================================
-    // Attributes
-    // ========================================================
-    SetAttribute {
-        name: &'src_bump str,
-        value: &'src_bump str,
-    },
-    RemoveAttribute {
-        name: &'src_bump str,
-    },
-    RemoveChild {
-        n: u32,
-    },
-
-    // ============================================================
-    // Event Listeners: Event types and IDs used to update the VDOM
-    // ============================================================
-    NewListener {
-        // todo - make static?
-        event: &'src_bump str,
-        scope: ScopeIdx,
-        id: usize,
-    },
-    UpdateListener {
-        // todo - make static?
-        event: &'src_bump str,
-        scope: ScopeIdx,
-        id: usize,
-    },
-    RemoveListener {
-        // todo - make static?
-        event: &'src_bump str,
-    },
-
-    // ========================================================
-    // Cached Roots: The mount point for individual components
-    //               Allows quick traversal to cached entrypoints
-    // ========================================================
-    // push a known node on to the stack
-    TraverseToKnown {
-        node: u32,
-        // node: ScopeIdx,
-    },
-    // Add the current top of the stack to the known nodes
-    MakeKnown {
-        node: u32,
-        // node: ScopeIdx,
-    },
-    // Remove the current top of the stack from the known nodes
-    RemoveKnown,
-
-    // ========================================================
-    // Stack OPs: Operations for manipulating the stack machine
-    // ========================================================
-    PushReverseChild {
-        n: u32,
-    },
-    PopPushChild {
-        n: u32,
-    },
-    Pop,
-    AppendChild,
-    RemoveSelfAndNextSiblings {},
-    ReplaceWith,
-    SaveChildrenToTemporaries {
-        temp: u32,
-        start: u32,
-        end: u32,
-    },
-    PushChild {
-        n: u32,
-    },
-    PushTemporary {
-        temp: u32,
-    },
-    InsertBefore,
-    PopPushReverseChild {
-        n: u32,
-    },
-}
-
-/// The edit machine represents a stream of differences between two component trees.
-///
-/// This struct is interesting in that it keeps track of differences by borrowing
-/// from the source rather than writing to a new buffer. This means that the virtual dom
-/// *cannot* be updated while this machine is in existence without "unsafe".
-///
-/// This unsafety is handled by methods on the virtual dom and is not exposed via lib code.
-pub struct EditMachine<'lock> {
-    pub traversal: Traversal,
-    next_temporary: u32,
-    forcing_new_listeners: bool,
-    pub cur_height: u32,
-
-    // // if the current node is a "known" node
-    // // any actions that modify this node should update the mapping
-    // current_known: Option<u32>,
-    pub emitter: EditList<'lock>,
-}
-
-impl<'lock> EditMachine<'lock> {
-    pub fn new() -> Self {
-        Self {
-            // current_known: None,
-            traversal: Traversal::new(),
-            cur_height: 0,
-            next_temporary: 0,
-            forcing_new_listeners: false,
-            emitter: EditList::<'lock>::default(),
-        }
-    }
-}
-
-// ===================================
-//  Traversal Methods
-// ===================================
-impl<'src> EditMachine<'src> {
-    pub fn go_down(&mut self) {
-        self.traversal.down();
-    }
-
-    pub fn go_down_to_child(&mut self, index: usize) {
-        self.traversal.down();
-        self.traversal.sibling(index);
-    }
-
-    pub fn go_down_to_reverse_child(&mut self, index: usize) {
-        self.traversal.down();
-        self.traversal.reverse_sibling(index);
-    }
-
-    pub fn go_up(&mut self) {
-        self.traversal.up();
-    }
-
-    pub fn go_to_sibling(&mut self, index: usize) {
-        self.traversal.sibling(index);
-    }
-
-    pub fn go_to_temp_sibling(&mut self, temp: u32) {
-        self.traversal.up();
-        self.traversal.down_to_temp(temp);
-    }
-
-    pub fn go_down_to_temp_child(&mut self, temp: u32) {
-        self.traversal.down_to_temp(temp);
-    }
-
-    pub fn commit_traversal(&mut self) {
-        if self.traversal.is_committed() {
-            return;
-        }
-
-        for mv in self.traversal.commit() {
-            match mv {
-                MoveTo::Parent => self.emitter.push(Edit::Pop {}),
-                MoveTo::Child(n) => self.emitter.push(Edit::PushChild { n }),
-                MoveTo::ReverseChild(n) => self.emitter.push(Edit::PushReverseChild { n }),
-                MoveTo::Sibling(n) => self.emitter.push(Edit::PopPushChild { n }),
-                MoveTo::ReverseSibling(n) => self.emitter.push(Edit::PopPushReverseChild { n }),
-                MoveTo::TempChild(temp) => self.emitter.push(Edit::PushTemporary { temp }),
-            }
-        }
-    }
-
-    pub fn traversal_is_committed(&self) -> bool {
-        self.traversal.is_committed()
-    }
-}
-
-// ===================================
-//  Stack methods
-// ===================================
-impl<'a> EditMachine<'a> {
-    pub fn next_temporary(&self) -> u32 {
-        self.next_temporary
-    }
-
-    pub fn set_next_temporary(&mut self, next_temporary: u32) {
-        self.next_temporary = next_temporary;
-    }
-
-    pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
-        debug_assert!(self.traversal_is_committed());
-        debug_assert!(start < end);
-        let temp_base = self.next_temporary;
-        self.next_temporary = temp_base + (end - start) as u32;
-        self.emitter.push(Edit::SaveChildrenToTemporaries {
-            temp: temp_base,
-            start: start as u32,
-            end: end as u32,
-        });
-        temp_base
-    }
-
-    pub fn push_temporary(&mut self, temp: u32) {
-        debug_assert!(self.traversal_is_committed());
-
-        self.emitter.push(Edit::PushTemporary { temp });
-    }
-
-    pub fn remove_child(&mut self, child: usize) {
-        debug_assert!(self.traversal_is_committed());
-
-        self.emitter.push(Edit::RemoveChild { n: child as u32 })
-    }
-
-    pub fn insert_before(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-
-        self.emitter.push(Edit::InsertBefore {})
-    }
-
-    pub fn set_text(&mut self, text: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::SetText { text });
-    }
-
-    pub fn remove_self_and_next_siblings(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
-    }
-
-    pub fn replace_with(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: replace_with()");
-        // if let Some(id) = self.current_known {
-        //     // update mapping
-        //     self.emitter.push(Edit::MakeKnown{node: id});
-        //     self.current_known = None;
-        // }
-        // self.emitter.replace_with();
-        self.emitter.push(Edit::ReplaceWith {});
-    }
-
-    pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
-        debug_assert!(self.traversal_is_committed());
-
-        if name == "class" && !is_namespaced {
-            self.emitter.push(Edit::SetClass { class_name: value });
-        } else {
-            self.emitter.push(Edit::SetAttribute { name, value });
-        }
-    }
-
-    pub fn remove_attribute(&mut self, name: &'a str) {
-        self.emitter.push(Edit::RemoveAttribute { name });
-    }
-
-    pub fn append_child(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::AppendChild {});
-    }
-
-    pub fn create_text_node(&mut self, text: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::CreateTextNode { text });
-    }
-
-    pub fn create_element(&mut self, tag_name: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::CreateElement { tag_name });
-    }
-
-    pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::CreateElementNs { tag_name, ns });
-    }
-
-    pub fn push_force_new_listeners(&mut self) -> bool {
-        let old = self.forcing_new_listeners;
-        self.forcing_new_listeners = true;
-        old
-    }
-
-    pub fn pop_force_new_listeners(&mut self, previous: bool) {
-        debug_assert!(self.forcing_new_listeners);
-        self.forcing_new_listeners = previous;
-    }
-
-    pub fn new_event_listener(&mut self, event: &'a str, scope: ScopeIdx, id: usize) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::NewListener { event, scope, id });
-    }
-
-    pub fn update_event_listener(&mut self, event: &'a str, scope: ScopeIdx, id: usize) {
-        debug_assert!(self.traversal_is_committed());
-        if self.forcing_new_listeners {
-            self.new_event_listener(event, scope, id);
-            return;
-        }
-
-        self.emitter.push(Edit::NewListener { event, scope, id });
-    }
-
-    pub fn remove_event_listener(&mut self, event: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::RemoveListener { event });
-    }
-
-    pub fn save_known_root(&mut self, id: u32) {
-        log::debug!("emit: save_known_root({:?})", id);
-        self.emitter.push(Edit::MakeKnown { node: id })
-    }
-
-    pub fn load_known_root(&mut self, id: u32) {
-        log::debug!("emit: TraverseToKnown({:?})", id);
-        self.emitter.push(Edit::TraverseToKnown { node: id })
-    }
-}
-
-// Keeps track of where we are moving in a DOM tree, and shortens traversal
-// paths between mutations to their minimal number of operations.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum MoveTo {
-    /// Move from the current node up to its parent.
-    Parent,
-
-    /// Move to the current node's n^th child.
-    Child(u32),
-
-    /// Move to the current node's n^th from last child.
-    ReverseChild(u32),
-
-    /// Move to the n^th sibling. Not relative from the current
-    /// location. Absolute indexed within all of the current siblings.
-    Sibling(u32),
-
-    /// Move to the n^th from last sibling. Not relative from the current
-    /// location. Absolute indexed within all of the current siblings.
-    ReverseSibling(u32),
-
-    /// Move down to the given saved temporary child.
-    TempChild(u32),
-}
-
-#[derive(Debug)]
-pub struct Traversal {
-    uncommitted: Vec<MoveTo>,
-}
-
-impl Traversal {
-    /// Construct a new `Traversal` with its internal storage backed by the
-    /// given bump arena.
-    pub fn new() -> Traversal {
-        Traversal {
-            uncommitted: Vec::with_capacity(32),
-        }
-    }
-
-    /// Move the traversal up in the tree.
-    pub fn up(&mut self) {
-        match self.uncommitted.last() {
-            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Parent);
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
-                self.uncommitted.pop();
-                // And we're back at the parent.
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::Parent);
-            }
-        }
-    }
-
-    /// Move the traversal down in the tree to the first child of the current
-    /// node.
-    pub fn down(&mut self) {
-        if let Some(&MoveTo::Parent) = self.uncommitted.last() {
-            self.uncommitted.pop();
-            self.sibling(0);
-        } else {
-            self.uncommitted.push(MoveTo::Child(0));
-        }
-    }
-
-    /// Move the traversal to the n^th sibling.
-    pub fn sibling(&mut self, index: usize) {
-        let index = index as u32;
-        match self.uncommitted.last_mut() {
-            Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
-                *n = index;
-            }
-            Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Sibling(index));
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Child(index))
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::Sibling(index));
-            }
-        }
-    }
-
-    /// Move the the n^th from last sibling.
-    pub fn reverse_sibling(&mut self, index: usize) {
-        let index = index as u32;
-        match self.uncommitted.last_mut() {
-            Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
-                *n = index;
-            }
-            Some(MoveTo::Sibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::ReverseSibling(index));
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::ReverseChild(index))
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::ReverseSibling(index));
-            }
-        }
-    }
-
-    /// Go to the given saved temporary.
-    pub fn down_to_temp(&mut self, temp: u32) {
-        match self.uncommitted.last() {
-            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-            }
-            Some(MoveTo::Parent)
-            | Some(MoveTo::TempChild(_))
-            | Some(MoveTo::Child(_))
-            | Some(MoveTo::ReverseChild(_))
-            | None => {
-                // Can't remove moves to parents since we rely on their stack
-                // pops.
-            }
-        }
-        self.uncommitted.push(MoveTo::TempChild(temp));
-    }
-
-    /// Are all the traversal's moves committed? That is, are there no moves
-    /// that have *not* been committed yet?
-    #[inline]
-    pub fn is_committed(&self) -> bool {
-        self.uncommitted.is_empty()
-    }
-
-    /// Commit this traversals moves and return the optimized path from the last
-    /// commit.
-    #[inline]
-    pub fn commit(&mut self) -> std::vec::Drain<'_, MoveTo> {
-        self.uncommitted.drain(..)
-    }
-
-    #[inline]
-    pub fn reset(&mut self) {
-        self.uncommitted.clear();
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_traversal() {
-        fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
-        where
-            F: 'static + FnMut(&mut Traversal),
-        {
-            Box::new(f) as _
-        }
-
-        for (mut traverse, expected_moves) in vec![
-            (
-                t(|t| {
-                    t.down();
-                }),
-                vec![MoveTo::Child(0)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.sibling(42);
-                }),
-                vec![MoveTo::Sibling(42)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(2);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(3);
-                }),
-                vec![MoveTo::Child(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(4);
-                    t.sibling(8);
-                }),
-                vec![MoveTo::Child(8)],
-            ),
-            (
-                t(|t| {
-                    t.sibling(1);
-                    t.sibling(1);
-                }),
-                vec![MoveTo::Sibling(1)],
-            ),
-            (
-                t(|t| {
-                    t.reverse_sibling(3);
-                }),
-                vec![MoveTo::ReverseSibling(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                }),
-                vec![MoveTo::ReverseChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                }),
-                vec![MoveTo::ReverseChild(6)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                }),
-                vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.sibling(3);
-                    t.sibling(6);
-                }),
-                vec![MoveTo::Parent, MoveTo::Sibling(6)],
-            ),
-            (
-                t(|t| {
-                    t.sibling(3);
-                    t.sibling(6);
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Child(0), MoveTo::TempChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.sibling(5);
-                }),
-                vec![MoveTo::Child(5)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.reverse_sibling(5);
-                }),
-                vec![MoveTo::ReverseChild(5)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.sibling(2);
-                    t.up();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Parent, MoveTo::TempChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Parent, MoveTo::TempChild(3)],
-            ),
-        ] {
-            let mut traversal = Traversal::new();
-            traverse(&mut traversal);
-            let actual_moves: Vec<_> = traversal.commit().collect();
-            assert_eq!(actual_moves, expected_moves);
-        }
-    }
-}

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

@@ -64,8 +64,11 @@ pub struct VirtualDom {
 }
 
 #[derive(Clone, Copy)]
-pub struct RealDomNode(u32);
+pub struct RealDomNode(pub u32);
 impl RealDomNode {
+    pub fn new(id: u32) -> Self {
+        Self(id)
+    }
     pub fn empty() -> Self {
         Self(u32::MIN)
     }
@@ -318,7 +321,8 @@ impl VirtualDom {
             cur_component.run_scope()?;
             // diff_machine.change_list.load_known_root(1);
 
-            let (old, new) = cur_component.get_frames_mut();
+            let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
+            // let (old, new) = cur_component.get_frames_mut();
             diff_machine.diff_node(old, new);
 
             // cur_height = cur_component.height;
@@ -527,7 +531,8 @@ impl Scope {
         let EventTrigger {
             listener_id, event, ..
         } = trigger;
-        //
+
+        // todo: implement scanning for outdated events
         unsafe {
             // Convert the raw ptr into an actual object
             // This operation is assumed to be safe
@@ -547,12 +552,6 @@ impl Scope {
         Ok(())
     }
 
-    fn get_frames_mut<'bump>(
-        &'bump mut self,
-    ) -> (&'bump mut VNode<'bump>, &'bump mut VNode<'bump>) {
-        todo!()
-    }
-
     pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
         self.frames.current_head_node()
     }

+ 4 - 2
packages/web/Cargo.toml

@@ -27,8 +27,8 @@ atoms = { path="../atoms" }
 # futures = "0.3.12"
 # html-validation = { path = "../html-validation", version = "0.1.1" }
 
-async-channel = "1.6.1"
-wee_alloc = "0.4.5"
+# async-channel = "1.6.1"
+nohash-hasher = "0.2.0"
 # futures-lite = "1.11.3"
 
 [dependencies.web-sys]
@@ -79,6 +79,8 @@ opt-level = 's'
 crate-type = ["cdylib", "rlib"]
 
 [dev-dependencies]
+im-rc = "15.0.0"
+rand = { version="0.8.4", features=["small_rng"] }
 uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
 
 [[example]]

+ 10 - 9
packages/web/examples/basic.rs

@@ -1,5 +1,6 @@
 //! Basic example that renders a simple VNode to the browser.
 
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::*;
 
@@ -50,15 +51,15 @@ static DocExamples: FC<()> = |ctx| {
         }
     };
 
-    rsx! {
-        div {}
-        h1 {}
-        {""}
-        "asbasd"
-        dioxus::Fragment {
-            //
-        }
-    }
+    // rsx! {
+    //     div {}
+    //     h1 {}
+    //     {""}
+    //     "asbasd"
+    //     dioxus::Fragment {
+    //         //
+    //     }
+    // }
 
     ctx.render(rsx! {
         div {

+ 8 - 10
packages/web/examples/context.rs

@@ -2,14 +2,14 @@
 //! --------------------
 //! This example demonstrates how to use the raw context api for sharing state throughout the VirtualDOM Tree.
 //! A custom context must be its own unique type - otherwise use_context will fail. A context may be c
-//! 
-//! 
-//! 
-//! 
-//! 
-//! 
-//! 
-//! 
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
 
 use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
@@ -20,7 +20,6 @@ fn main() {
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
 }
 
-
 #[derive(Debug)]
 struct CustomContext([&'static str; 3]);
 
@@ -46,7 +45,6 @@ static Example: FC<()> = |ctx| {
     })
 };
 
-
 #[derive(Props, PartialEq)]
 struct ButtonProps {
     id: u8,

+ 1 - 0
packages/web/examples/deep.rs

@@ -1,5 +1,6 @@
 use std::rc::Rc;
 
+use dioxus_core as dioxus;
 use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
 
 fn main() {

+ 1 - 0
packages/web/examples/demoday.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
 
 fn main() {

+ 0 - 4
packages/web/examples/derive.rs

@@ -8,10 +8,6 @@ fn main() {
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
 }
 
-// Use `wee_alloc` as the global allocator.
-#[global_allocator]
-static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
-
 fn App(ctx: Context<()>) -> VNode {
     let cansee = use_state_new(&ctx, || false);
     rsx! { in ctx,

+ 11 - 11
packages/web/examples/events.rs

@@ -5,17 +5,17 @@ use dioxus_core::prelude::*;
 fn main() {}
 
 fn autocomplete() {
-    let handler = move |evt| {
-        let r = evt.alt_key();
-        if evt.alt_key() {}
-    };
+    // let handler = move |evt| {
+    //     let r = evt.alt_key();
+    //     if evt.alt_key() {}
+    // };
 
-    let g = rsx! {
-        button {
-            button {
-                onclick: {handler}
-            }
-        }
+    // let g = rsx! {
+    //     button {
+    //         button {
+    //             onclick: {handler}
+    //         }
+    //     }
 
-    };
+    // };
 }

+ 184 - 0
packages/web/examples/framework_benchmark.rs

@@ -0,0 +1,184 @@
+//! JS Framework Benchmark
+//! ----------------------
+//!
+//! This example is used in the JS framework benchmarking tool to compare Dioxus' performance with other frontend frameworks.
+//!
+//!
+//!
+
+use std::rc::Rc;
+
+use dioxus::events::on::MouseEvent;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_web::WebsysRenderer;
+
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+
+// We use a special immutable hashmap to make hashmap operations efficient
+type RowList = im_rc::HashMap<usize, Rc<str>, nohash_hasher::BuildNoHashHasher<usize>>;
+
+static App: FC<()> = |cx| {
+    let (items, set_items) = use_state(&cx, || RowList::default());
+    let (selection, set_selection) = use_state(&cx, || None as Option<usize>);
+
+    let create_rendered_rows = move |from, num| move |_| set_items(create_row_list(from, num));
+
+    let append_1_000_rows =
+        move |_| set_items(create_row_list(items.len(), 1000).union(items.clone()));
+
+    let update_every_10th_row = move |_| {
+        let mut new_items = items.clone();
+        let mut small_rng = SmallRng::from_entropy();
+        new_items
+            .iter_mut()
+            .step_by(10)
+            .for_each(|(_, val)| *val = create_new_row_label(&mut small_rng));
+        set_items(new_items);
+    };
+    let clear_rows = move |_| set_items(RowList::default());
+
+    let swap_rows = move |_| {
+        // this looks a bit ugly because we're using a hashmap instead of a vec
+        if items.len() > 998 {
+            let mut new_items = items.clone();
+            let a = new_items.get(&0).unwrap().clone();
+            *new_items.get_mut(&0).unwrap() = new_items.get(&998).unwrap().clone();
+            *new_items.get_mut(&998).unwrap() = a;
+            set_items(new_items);
+        }
+    };
+
+    let rows = items.iter().map(|(key, value)| {
+        rsx!(Row {
+            key: "{key}",
+            row_id: *key as usize,
+            label: value.clone(),
+        })
+    });
+
+    cx.render(rsx! {
+        div { class: "container"
+            div { class: "jumbotron"
+                div { class: "row"
+                    div { class: "col-md-6", h1 { "Dioxus" } }
+                    div { class: "col-md-6"
+                        div { class: "row"
+                            ActionButton { name: "Create 1,000 rows", id: "run", action: create_rendered_rows(0, 1_000) }
+                            ActionButton { name: "Create 10,000 rows", id: "runlots", action: create_rendered_rows(0, 10_000) }
+                            ActionButton { name: "Append 1,000 rows", id: "add", action: append_1_000_rows }
+                            ActionButton { name: "Update every 10th row", id: "update", action: update_every_10th_row, }
+                            ActionButton { name: "Clear", id: "clear", action: clear_rows }
+                            ActionButton { name: "Swap rows", id: "swaprows", action: swap_rows }
+                        }
+                    }
+                }
+            }
+            table { 
+                tbody {
+                    {rows}
+                }
+             }
+            span {}
+        }
+    })
+};
+
+#[derive(Props)]
+struct ActionButtonProps<F: Fn(Rc<dyn MouseEvent>)> {
+    name: &'static str,
+    id: &'static str,
+    action: F,
+}
+fn ActionButton<F: Fn(Rc<dyn MouseEvent>)>(cx: Context<ActionButtonProps<F>>) -> VNode {
+    cx.render(rsx! {
+        div { class: "col-sm-6 smallpad"
+            button {class:"btn btn-primary btn-block", type: "button", id: "{cx.id}",  onclick: {&cx.action},
+                "{cx.name}"
+            }
+        }
+    })
+}
+
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Rc<str>,
+}
+fn Row<'a>(cx: Context<'a, RowProps>) -> VNode {
+    cx.render(rsx! {
+        tr {
+            td { class:"col-md-1", "{cx.row_id}" }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+                a { class: "lbl", "{cx.label}" }
+            }
+            td { class: "col-md-1"
+                a { class: "remove", onclick: move |_| {/* remove */}
+                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                }
+            }
+            td { class: "col-md-6" }
+        }
+    })
+}
+
+use rand::prelude::*;
+fn create_new_row_label(rng: &mut SmallRng) -> Rc<str> {
+    let mut label = String::new();
+    label.push_str(ADJECTIVES.choose(rng).unwrap());
+    label.push(' ');
+    label.push_str(COLOURS.choose(rng).unwrap());
+    label.push(' ');
+    label.push_str(NOUNS.choose(rng).unwrap());
+    Rc::from(label)
+}
+
+fn create_row_list(from: usize, num: usize) -> RowList {
+    let mut small_rng = SmallRng::from_entropy();
+    (from..num + from)
+        .map(|f| (f, create_new_row_label(&mut small_rng)))
+        .collect::<RowList>()
+}
+
+static ADJECTIVES: &[&str] = &[
+    "pretty",
+    "large",
+    "big",
+    "small",
+    "tall",
+    "short",
+    "long",
+    "handsome",
+    "plain",
+    "quaint",
+    "clean",
+    "elegant",
+    "easy",
+    "angry",
+    "crazy",
+    "helpful",
+    "mushy",
+    "odd",
+    "unsightly",
+    "adorable",
+    "important",
+    "inexpensive",
+    "cheap",
+    "expensive",
+    "fancy",
+];
+
+static COLOURS: &[&str] = &[
+    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
+    "orange",
+];
+
+static NOUNS: &[&str] = &[
+    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
+    "pizza", "mouse", "keyboard",
+];

+ 14 - 1
packages/web/examples/hello.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
 
@@ -10,12 +11,24 @@ fn main() {
 }
 
 static Example: FC<()> = |ctx| {
+    let nodes = (0..5).map(|f| {
+        rsx! {
+            li {"{f}"}
+        }
+    });
     ctx.render(rsx! {
         div {
             span {
                 class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100"
-                "DUE DATE : 18 JUN"
+                "DUE DATE : 189 JUN"
+            }
+            p {
+                "these"
+                "are"
+                "text"
+                "nodes"
             }
+            {nodes}
         }
     })
 };

+ 1 - 0
packages/web/examples/helloworld.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_web::prelude::*;
 
 fn main() {

+ 4 - 5
packages/web/examples/infer.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_core::{events::on::MouseEvent, prelude::*};
 use dioxus_web::WebsysRenderer;
 
@@ -14,7 +15,7 @@ fn main() {
 static Example: FC<()> = |ctx| {
     let (event, set_event) = use_state(&ctx, || None);
 
-    let handler = move |evt: MouseEvent| {
+    let handler = move |evt| {
         set_event(Some(evt));
     };
 
@@ -42,17 +43,15 @@ static Example: FC<()> = |ctx| {
     })
 };
 
-
 #[derive(Debug, PartialEq, Props)]
 struct ExampleProps {
-    name: String
+    name: String,
 }
 
 static Example2: FC<ExampleProps> = |ctx| {
-    ctx.render(rsx!{
+    ctx.render(rsx! {
         div {
             h1 {"hello {ctx.name}"}
         }
     })
 };
-

+ 7 - 14
packages/web/examples/input.rs

@@ -26,25 +26,22 @@ static App: FC<()> = |ctx| {
                         id: "username" 
                         type: "text"
                         value: "{val}"
-                        oninput: move |evet| {
-                            log::debug!("Value is {:#?}", evet);
-                            set_val(evet.value);
-                        }
+                        oninput: move |evet| set_val(evet.value())
                     }
                     p { "Val is: {val}" }
                 }            
             }
         }
-    })    
+    })
 };
 
 static Example: FC<()> = |ctx| {
     ctx.render(rsx! {
         div { class: "max-w-lg max-w-xs bg-blue-800 shadow-2xl rounded-lg mx-auto text-center py-12 mt-4 rounded-xl"
             div { class: "container py-5 max-w-md mx-auto"
-                h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl", 
+                h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl",
                     "Text Input Example"
-                } 
+                }
                 UserInput {}
             }
         }
@@ -54,19 +51,15 @@ static Example: FC<()> = |ctx| {
 static UserInput: FC<()> = |ctx| {
     let (val, set_val) = use_state(&ctx, || "asd".to_string());
 
-    rsx!{ in ctx,
+    rsx! { in ctx,
         div { class: "mb-4"
             input { class: "shadow appearance-none rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                 placeholder: "Username"
-                id: "username" 
+                id: "username"
                 type: "text"
-                oninput: move |evet| {
-                    log::debug!("Value is {:#?}", evet);
-                    set_val(evet.value);
-                }
+                oninput: move |evet| set_val(evet.value())
             }
             p { "Val is: {val}" }
         }
     }
-    
 };

+ 1 - 1
packages/web/examples/landingpage.rs

@@ -6,7 +6,7 @@ use dioxus_core::prelude::*;
 use dioxus_web::*;
 fn main() {
     // Setup logging
-    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    // wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
     console_error_panic_hook::set_once();
     // Run the app
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));

+ 1 - 1
packages/web/examples/list.rs

@@ -53,7 +53,7 @@ static App: FC<()> = |ctx| {
                     input {
                         class: "new-todo"
                         placeholder: "What needs to be done?"
-                        oninput: move |evt| set_draft(evt.value)
+                        oninput: move |evt| set_draft(evt.value())
                     }
                 }
 

+ 1 - 0
packages/web/examples/many.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
 

+ 13 - 0
packages/web/examples/props.rs

@@ -0,0 +1,13 @@
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+
+#[derive(Props)]
+struct MyProps<'a> {
+    blah: u128,
+    b: &'a (),
+}
+
+fn main() {
+    // let p = unsafe { MyProps {}.memoize(&MyProps {}) };
+    // dbg!(p);
+}

+ 16 - 15
packages/web/examples/rsxt.rs

@@ -1,14 +1,18 @@
 #![allow(non_snake_case)]
-use dioxus_core as dioxus;
+use std::rc::Rc;
+
 use dioxus::{events::on::MouseEvent, prelude::*};
+use dioxus_core as dioxus;
 use dioxus_web::WebsysRenderer;
 
 fn main() {
     wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
     console_error_panic_hook::set_once();
-    
+
     wasm_bindgen_futures::spawn_local(async {
-        let props = ExampleProps { initial_name: "..?"};
+        let props = ExampleProps {
+            initial_name: "..?",
+        };
         WebsysRenderer::new_with_props(Example, props)
             .run()
             .await
@@ -25,17 +29,17 @@ static Example: FC<ExampleProps> = |ctx| {
     let name = use_state_new(&ctx, move || ctx.initial_name);
 
     ctx.render(rsx! {
-        div { 
+        div {
             class: "py-12 px-4 text-center w-full max-w-2xl mx-auto"
-            span { 
+            span {
                 class: "text-sm font-semibold"
                 "Dioxus Example: Jack and Jill"
             }
-            h2 { 
-                class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"   
+            h2 {
+                class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
                 "Hello, {name}"
             }
-            
+
             CustomButton { name: "Jack!", handler: move |_| name.set("Jack") }
             CustomButton { name: "Jill!", handler: move |_| name.set("Jill") }
             CustomButton { name: "Bob!", handler: move |_| name.set("Bob")}
@@ -45,12 +49,10 @@ static Example: FC<ExampleProps> = |ctx| {
     })
 };
 
-
-
 #[derive(Props)]
-struct ButtonProps<'src, F: Fn(MouseEvent)> {
+struct ButtonProps<'src, F: Fn(Rc<dyn MouseEvent>)> {
     name: &'src str,
-    handler: F
+    handler: F,
 }
 
 fn CustomButton<'a, F: Fn(MouseEvent)>(ctx: Context<'a, ButtonProps<'a, F>>) -> VNode {
@@ -69,13 +71,12 @@ impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
     }
 }
 
-
 #[derive(Props, PartialEq)]
 struct PlaceholderProps {
-    val: &'static str
+    val: &'static str,
 }
 fn Placeholder(ctx: Context<PlaceholderProps>) -> VNode {
-    ctx.render(rsx!{
+    ctx.render(rsx! {
         div {
             "child: {ctx.val}"
         }

+ 8 - 7
packages/web/examples/todomvc/main.rs

@@ -1,10 +1,11 @@
+use dioxus_core as dioxus;
 use dioxus_web::{prelude::*, WebsysRenderer};
 
-mod filtertoggles;
-mod recoil;
-mod state;
-mod todoitem;
-mod todolist;
+// mod filtertoggles;
+// mod recoil;
+// mod state;
+// mod todoitem;
+// mod todolist;
 
 static APP_STYLE: &'static str = include_str!("./style.css");
 
@@ -13,10 +14,10 @@ fn main() {
         ctx.render(rsx! {
             div {
                 id: "app"
-                style { "{APP_STYLE}" }
+                // style { "{APP_STYLE}" }
 
                 // list
-                todolist::TodoList {}
+                // todolist::TodoList {}
 
                 // footer
                 footer {

+ 10 - 12
packages/web/examples/todomvc_simple.rs

@@ -1,5 +1,6 @@
 use std::{collections::HashMap, rc::Rc};
 
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
 
@@ -65,7 +66,7 @@ pub fn TodoList(ctx: Context<()>) -> VNode {
                     class: "new-todo"
                     placeholder: "What needs to be done?"
                     value: "{draft}"
-                    oninput: move |evt| set_draft(evt.value)
+                    oninput: move |evt| set_draft(evt.value())
                 }
             }
 
@@ -78,7 +79,7 @@ pub fn TodoList(ctx: Context<()>) -> VNode {
                     FilterState::Completed => item.checked,
                 })
                 .map(|(id, item)| {
-                    TodoEntry!();
+                    // TodoEntry!();
                     todo!()
                     // rsx!(TodoEntry {
                     //     key: "{order}",
@@ -100,17 +101,14 @@ pub struct TodoEntryProps {
     item: Rc<TodoItem>,
 }
 
-pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
-// #[inline_props]
-pub fn TodoEntry(
-    ctx: Context,
-    baller: &impl Fn() -> (),
-    caller: &impl Fn() -> (),
-    todo: &Rc<TodoItem>,
-) -> VNode {
-    // pub fn TodoEntry(ctx: Context, todo: &Rc<TodoItem>) -> VNode {
+pub fn TodoEntry(ctx: Context<TodoEntryProps>) -> VNode {
     let (is_editing, set_is_editing) = use_state(&ctx, || false);
-    // let todo = &ctx.item;
+    let contents = "";
+    let todo = TodoItem {
+        checked: false,
+        contents: "asd".to_string(),
+        id: uuid::Uuid::new_v4(),
+    };
 
     ctx.render(rsx! (
         li {

+ 1 - 1
packages/web/examples/todomvcsingle.rs

@@ -9,7 +9,7 @@
 //! Here, we show to use Dioxus' Recoil state management solution to simplify app logic
 #![allow(non_snake_case)]
 use dioxus_web::dioxus::prelude::*;
-use recoil::*;
+
 use std::collections::HashMap;
 use uuid::Uuid;
 

+ 50 - 42
packages/web/src/lib.rs

@@ -63,62 +63,70 @@ impl WebsysRenderer {
     }
 
     pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
-        let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
+        // let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
 
         let body_element = prepare_websys_dom();
 
-        let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
-            log::debug!("Event trigger! {:#?}", ev);
-            let mut c = sender.clone();
-            wasm_bindgen_futures::spawn_local(async move {
-                c.send(ev).await.unwrap();
-            });
-        });
+        // let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
+        //     log::debug!("Event trigger! {:#?}", ev);
+        //     let mut c = sender.clone();
+        //     wasm_bindgen_futures::spawn_local(async move {
+        //         c.send(ev).await.unwrap();
+        //     });
+        // });
+
         let root_node = body_element.first_child().unwrap();
-        patch_machine.stack.push(root_node.clone());
+
+        let mut websys_dom = crate::new::WebsysDom::new(body_element);
+
+        websys_dom.stack.push(root_node);
+        // patch_machine.stack.push(root_node.clone());
 
         // todo: initialize the event registry properly on the root
 
-        let edits = self.internal_dom.rebuild()?;
-        log::debug!("Received edits: {:#?}", edits);
-        edits.iter().for_each(|edit| {
-            log::debug!("patching with  {:?}", edit);
-            patch_machine.handle_edit(edit);
-        });
+        self.internal_dom.rebuild(&mut websys_dom)?;
+        // let edits = self.internal_dom.rebuild()?;
+        // log::debug!("Received edits: {:#?}", edits);
+        // edits.iter().for_each(|edit| {
+        //     log::debug!("patching with  {:?}", edit);
+        //     patch_machine.handle_edit(edit);
+        // });
 
-        patch_machine.reset();
-        let root_node = body_element.first_child().unwrap();
-        patch_machine.stack.push(root_node.clone());
+        // patch_machine.reset();
+        // let root_node = body_element.first_child().unwrap();
+        // patch_machine.stack.push(root_node.clone());
 
         // log::debug!("patch stack size {:?}", patch_machine.stack);
 
         // Event loop waits for the receiver to finish up
         // TODO! Connect the sender to the virtual dom's suspense system
         // Suspense is basically an external event that can force renders to specific nodes
-        while let Ok(event) = receiver.recv().await {
-            log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
-            // log::debug!("patch stack size before {:#?}", patch_machine.stack);
-            // patch_machine.reset();
-            // patch_machine.stack.push(root_node.clone());
-            let edits = self.internal_dom.progress_with_event(event)?;
-            log::debug!("Received edits: {:#?}", edits);
-
-            for edit in &edits {
-                // log::debug!("edit stream {:?}", edit);
-                // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
-                patch_machine.handle_edit(edit);
-            }
-
-            // log::debug!("patch stack size after {:#?}", patch_machine.stack);
-            patch_machine.reset();
-            // our root node reference gets invalidated
-            // not sure why
-            // for now, just select the first child again.
-            // eventually, we'll just make our own root element instead of using body
-            // or just use body directly IDEK
-            let root_node = body_element.first_child().unwrap();
-            patch_machine.stack.push(root_node.clone());
-        }
+        // while let Ok(event) = receiver.recv().await {
+        // log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
+        // log::debug!("patch stack size before {:#?}", patch_machine.stack);
+        // patch_machine.reset();
+        // patch_machine.stack.push(root_node.clone());
+        // self.internal_dom
+        //     .progress_with_event(&mut websys_dom, event)?;
+        // let edits = self.internal_dom.progress_with_event(event)?;
+        // log::debug!("Received edits: {:#?}", edits);
+
+        // for edit in &edits {
+        //     // log::debug!("edit stream {:?}", edit);
+        //     // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
+        //     patch_machine.handle_edit(edit);
+        // }
+
+        // log::debug!("patch stack size after {:#?}", patch_machine.stack);
+        // patch_machine.reset();
+        // our root node reference gets invalidated
+        // not sure why
+        // for now, just select the first child again.
+        // eventually, we'll just make our own root element instead of using body
+        // or just use body directly IDEK
+        // let root_node = body_element.first_child().unwrap();
+        // patch_machine.stack.push(root_node.clone());
+        // }
 
         Ok(()) // should actually never return from this, should be an error, rustc just cant see it
     }

+ 275 - 2
packages/web/src/new.rs

@@ -1,2 +1,275 @@
-pub struct WebsysDom {}
-impl WebsysDom {}
+use std::collections::HashMap;
+
+use dioxus_core::virtual_dom::RealDomNode;
+use nohash_hasher::IntMap;
+use wasm_bindgen::{closure::Closure, JsCast};
+use web_sys::{
+    window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
+};
+
+use crate::interpreter::Stack;
+pub struct WebsysDom {
+    pub stack: Stack,
+    nodes: IntMap<u32, Node>,
+    document: Document,
+    root: Element,
+
+    // We need to make sure to add comments between text nodes
+    // We ensure that the text siblings are patched by preventing the browser from merging
+    // neighboring text nodes. Originally inspired by some of React's work from 2016.
+    //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
+    //  -> https://github.com/facebook/react/pull/5753
+    //
+    // `ptns` = Percy text node separator
+    // TODO
+    last_node_was_text: bool,
+
+    node_counter: Counter,
+}
+impl WebsysDom {
+    pub fn new(root: Element) -> Self {
+        let document = window()
+            .expect("must have access to the window")
+            .document()
+            .expect("must have access to the Document");
+
+        Self {
+            stack: Stack::with_capacity(10),
+            nodes: HashMap::with_capacity_and_hasher(
+                1000,
+                nohash_hasher::BuildNoHashHasher::default(),
+            ),
+            document,
+            root,
+            last_node_was_text: false,
+            node_counter: Counter(0),
+        }
+    }
+}
+
+struct Counter(u32);
+impl Counter {
+    fn next(&mut self) -> u32 {
+        self.0 += 1;
+        self.0
+    }
+}
+impl dioxus_core::diff::RealDom for WebsysDom {
+    fn push_root(&mut self, root: dioxus_core::virtual_dom::RealDomNode) {
+        let domnode = self.nodes.get(&root.0).expect("Failed to pop know root");
+        self.stack.push(domnode.clone());
+    }
+
+    fn append_child(&mut self) {
+        let child = self.stack.pop();
+
+        if child.dyn_ref::<web_sys::Text>().is_some() {
+            if self.last_node_was_text {
+                let comment_node = self
+                    .document
+                    .create_comment("dioxus")
+                    .dyn_into::<Node>()
+                    .unwrap();
+                self.stack.top().append_child(&comment_node).unwrap();
+            }
+            self.last_node_was_text = true;
+        } else {
+            self.last_node_was_text = false;
+        }
+
+        self.stack.top().append_child(&child).unwrap();
+    }
+
+    fn replace_with(&mut self) {
+        let new_node = self.stack.pop();
+        let old_node = self.stack.pop();
+
+        if old_node.has_type::<Element>() {
+            old_node
+                .dyn_ref::<Element>()
+                .unwrap()
+                .replace_with_with_node_1(&new_node)
+                .unwrap();
+        } else if old_node.has_type::<web_sys::CharacterData>() {
+            old_node
+                .dyn_ref::<web_sys::CharacterData>()
+                .unwrap()
+                .replace_with_with_node_1(&new_node)
+                .unwrap();
+        } else if old_node.has_type::<web_sys::DocumentType>() {
+            old_node
+                .dyn_ref::<web_sys::DocumentType>()
+                .unwrap()
+                .replace_with_with_node_1(&new_node)
+                .unwrap();
+        } else {
+            panic!("Cannot replace node: {:?}", old_node);
+        }
+
+        // // poc to see if this is a valid solution
+        // if let Some(id) = self.current_known {
+        //     // update mapping
+        //     self.known_roots.insert(id, new_node.clone());
+        //     self.current_known = None;
+        // }
+
+        self.stack.push(new_node);
+    }
+
+    fn remove(&mut self) {
+        todo!()
+    }
+
+    fn remove_all_children(&mut self) {
+        todo!()
+    }
+
+    fn create_text_node(&mut self, text: &str) -> dioxus_core::virtual_dom::RealDomNode {
+        let nid = self.node_counter.next();
+        let textnode = self
+            .document
+            .create_text_node(text)
+            .dyn_into::<Node>()
+            .unwrap();
+        self.stack.push(textnode.clone());
+        self.nodes.insert(nid, textnode);
+
+        RealDomNode::new(nid)
+    }
+
+    fn create_element(&mut self, tag: &str) -> dioxus_core::virtual_dom::RealDomNode {
+        let el = self
+            .document
+            .create_element(tag)
+            .unwrap()
+            .dyn_into::<Node>()
+            .unwrap();
+
+        self.stack.push(el.clone());
+        let nid = self.node_counter.next();
+        self.nodes.insert(nid, el);
+        RealDomNode::new(nid)
+    }
+
+    fn create_element_ns(
+        &mut self,
+        tag: &str,
+        namespace: &str,
+    ) -> dioxus_core::virtual_dom::RealDomNode {
+        let el = self
+            .document
+            .create_element_ns(Some(namespace), tag)
+            .unwrap()
+            .dyn_into::<Node>()
+            .unwrap();
+
+        self.stack.push(el.clone());
+        let nid = self.node_counter.next();
+        self.nodes.insert(nid, el);
+        RealDomNode::new(nid)
+    }
+
+    fn new_event_listener(
+        &mut self,
+        event: &str,
+        scope: dioxus_core::prelude::ScopeIdx,
+        id: usize,
+    ) {
+        // if let Some(entry) = self.listeners.get_mut(event) {
+        //     entry.0 += 1;
+        // } else {
+        //     let trigger = self.trigger.clone();
+        //     let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
+        //         log::debug!("Handling event!");
+
+        //         let target = event
+        //             .target()
+        //             .expect("missing target")
+        //             .dyn_into::<Element>()
+        //             .expect("not a valid element");
+
+        //         let typ = event.type_();
+
+        //         let gi_id: Option<usize> = target
+        //             .get_attribute(&format!("dioxus-giid-{}", typ))
+        //             .and_then(|v| v.parse().ok());
+
+        //         let gi_gen: Option<u64> = target
+        //             .get_attribute(&format!("dioxus-gigen-{}", typ))
+        //             .and_then(|v| v.parse().ok());
+
+        //         let li_idx: Option<usize> = target
+        //             .get_attribute(&format!("dioxus-lidx-{}", typ))
+        //             .and_then(|v| v.parse().ok());
+
+        //         if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
+        //             // Call the trigger
+        //             log::debug!(
+        //                 "decoded gi_id: {},  gi_gen: {},  li_idx: {}",
+        //                 gi_id,
+        //                 gi_gen,
+        //                 li_idx
+        //             );
+
+        //             let triggered_scope = ScopeIdx::from_raw_parts(gi_id, gi_gen);
+        //             trigger.0.as_ref()(EventTrigger::new(
+        //                 virtual_event_from_websys_event(event),
+        //                 triggered_scope,
+        //                 // scope,
+        //                 li_idx,
+        //             ));
+        //         }
+        //     }) as Box<dyn FnMut(&Event)>);
+
+        //     self.root
+        //         .add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
+        //         .unwrap();
+
+        //     // Increment the listeners
+        //     self.listeners.insert(event.into(), (1, handler));
+        // }
+    }
+
+    fn remove_event_listener(&mut self, event: &str) {
+        todo!()
+    }
+
+    fn set_text(&mut self, text: &str) {
+        self.stack.top().set_text_content(Some(text))
+    }
+
+    fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
+        if name == "class" {
+            if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+                el.set_class_name(value);
+            }
+        } else {
+        }
+    }
+
+    fn remove_attribute(&mut self, name: &str) {
+        let node = self.stack.top();
+        if let Some(node) = node.dyn_ref::<web_sys::Element>() {
+            node.remove_attribute(name).unwrap();
+        }
+        if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
+            // Some attributes are "volatile" and don't work through `removeAttribute`.
+            if name == "value" {
+                node.set_value("");
+            }
+            if name == "checked" {
+                node.set_checked(false);
+            }
+        }
+
+        if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
+            if name == "selected" {
+                node.set_selected(true);
+            }
+        }
+    }
+
+    fn raw_node_as_any_mut(&self) -> &mut dyn std::any::Any {
+        todo!()
+    }
+}