Преглед на файлове

Merge pull request #17 from jkelleyrtp/jk/jankfree

feat: jank free rendering and noderefs
Jonathan Kelley преди 3 години
родител
ревизия
39cc0849b8
променени са 72 файла, в които са добавени 5630 реда и са изтрити 3814 реда
  1. 1 0
      .vscode/spellright.dict
  2. 11 3
      Cargo.toml
  3. 31 31
      README.md
  4. 1 1
      examples/async.rs
  5. 1 1
      examples/hydration.rs
  6. 2 0
      examples/reference/noderefs.rs
  7. 2 2
      examples/reference/task.rs
  8. 1 1
      examples/reference/tostring.rs
  9. 8 1
      examples/ssr.rs
  10. 136 0
      examples/web_tick.rs
  11. 4 8
      packages/core-macro/src/rsx/ambiguous.rs
  12. 6 2
      packages/core-macro/src/rsx/element.rs
  13. 1 1
      packages/core/.vscode/settings.json
  14. 21 9
      packages/core/Cargo.toml
  15. 0 39
      packages/core/README.md
  16. 1 0
      packages/core/benches/create.rs
  17. 126 0
      packages/core/benches/jsframework.rs
  18. 5 6
      packages/core/examples/alternative.rs
  19. 1 1
      packages/core/examples/async.rs
  20. 0 18
      packages/core/examples/fragment.rs
  21. 13 17
      packages/core/examples/fragment_from_iter.rs
  22. 109 0
      packages/core/examples/jsframework.rs
  23. 0 137
      packages/core/examples/macrosrc.rs
  24. 15 0
      packages/core/examples/vdom_usage.rs
  25. 0 190
      packages/core/src/arena.rs
  26. 2 12
      packages/core/src/bumpframe.rs
  27. 94 0
      packages/core/src/childiter.rs
  28. 10 57
      packages/core/src/context.rs
  29. 0 78
      packages/core/src/debug_renderer.rs
  30. 781 1289
      packages/core/src/diff.rs
  31. 116 0
      packages/core/src/diff_stack.rs
  32. 0 99
      packages/core/src/editor.rs
  33. 0 30
      packages/core/src/error.rs
  34. 121 271
      packages/core/src/events.rs
  35. 7 23
      packages/core/src/hooklist.rs
  36. 87 78
      packages/core/src/hooks.rs
  37. 53 48
      packages/core/src/lib.rs
  38. 247 0
      packages/core/src/mutations.rs
  39. 119 0
      packages/core/src/noderef.rs
  40. 327 221
      packages/core/src/nodes.rs
  41. 813 0
      packages/core/src/scheduler.rs
  42. 64 82
      packages/core/src/scope.rs
  43. 0 5
      packages/core/src/signals.rs
  44. 82 0
      packages/core/src/test_dom.rs
  45. 56 18
      packages/core/src/util.rs
  46. 182 359
      packages/core/src/virtual_dom.rs
  47. 263 0
      packages/core/tests/create_iterative.rs
  48. 2 0
      packages/core/tests/debugdiff.rs
  49. 48 0
      packages/core/tests/diff_iterative.rs
  50. 524 162
      packages/core/tests/diffing.rs
  51. 3 13
      packages/core/tests/eventsystem.rs
  52. 21 0
      packages/core/tests/hooks.rs
  53. 41 0
      packages/core/tests/set_state_batch.rs
  54. 52 0
      packages/core/tests/test_logging.rs
  55. 8 8
      packages/core/tests/vdom_rebuild.rs
  56. 30 0
      packages/core/tests/work_sync.rs
  57. 1 7
      packages/desktop/src/dom.rs
  58. 10 6
      packages/desktop/src/events.rs
  59. 3 0
      packages/html/src/attrval.rs
  60. 1 7
      packages/mobile/src/dom.rs
  61. 4 4
      packages/mobile/src/lib.rs
  62. 25 0
      packages/ssr/examples/basic.rs
  63. 3 1
      packages/ssr/examples/tide.rs
  64. 2 1
      packages/ssr/examples/tofile.rs
  65. 16 16
      packages/ssr/src/lib.rs
  66. 2 3
      packages/web/Cargo.toml
  67. 21 13
      packages/web/examples/basic.rs
  68. 248 365
      packages/web/src/dom.rs
  69. 478 0
      packages/web/src/events.rs
  70. 81 70
      packages/web/src/lib.rs
  71. 58 0
      packages/web/src/ric_raf.rs
  72. 28 0
      packages/web/src/ricpolyfill.js

+ 1 - 0
.vscode/spellright.dict

@@ -65,3 +65,4 @@ asynchronicity
 constified
 constified
 SegVec
 SegVec
 contentful
 contentful
+Jank

+ 11 - 3
Cargo.toml

@@ -51,14 +51,22 @@ argh = "0.1.5"
 env_logger = "*"
 env_logger = "*"
 async-std = { version = "1.9.0", features = ["attributes"] }
 async-std = { version = "1.9.0", features = ["attributes"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
-surf = {version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
+surf = { version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" }
+gloo-timers = "0.2.1"
 
 
 [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 gloo-timers = "0.2.1"
 gloo-timers = "0.2.1"
-surf = {version = "2.2.0", default-features = false, features = ["wasm-client"], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
+wasm-logger = "0.2.0"
+console_error_panic_hook = "0.1.6"
+rand = { version = "0.8.4", features = ["small_rng"] }
+wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] }
+
+# surf = { version = "2.2.0", default-features = false, features = [
+#     "wasm-client",
+# ], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" }
 
 
 
 
-[dependencies.getrandom]
+[dev-dependencies.getrandom]
 version = "0.2"
 version = "0.2"
 features = ["js"]
 features = ["js"]
 
 

+ 31 - 31
README.md

@@ -147,37 +147,37 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 
 
 ### Phase 1: The Basics
 ### Phase 1: The Basics
 
 
-| Feature                   | Dioxus | React | Notes for Dioxus                                            |
-| ------------------------- | ------ | ----- | ----------------------------------------------------------- |
-| Conditional Rendering     | ✅      | ✅     | if/then to hide/show component                              |
-| Map, Iterator             | ✅      | ✅     | map/filter/reduce to produce rsx!                           |
-| Keyed Components          | ✅      | ✅     | advanced diffing with keys                                  |
-| Web                       | ✅      | ✅     | renderer for web browser                                    |
-| Desktop (webview)         | ✅      | ✅     | renderer for desktop                                        |
-| Shared State (Context)    | ✅      | ✅     | share state through the tree                                |
-| Hooks                     | ✅      | ✅     | memory cells in components                                  |
-| SSR                       | ✅      | ✅     | render directly to string                                   |
-| Component Children        | ✅      | ✅     | cx.children() as a list of nodes                            |
-| Headless components       | ✅      | ✅     | components that don't return real elements                  |
-| Fragments                 | ✅      | ✅     | multiple elements without a real root                       |
-| Manual Props              | ✅      | ✅     | Manually pass in props with spread syntax                   |
-| Controlled Inputs         | ✅      | ✅     | stateful wrappers around inputs                             |
-| CSS/Inline Styles         | ✅      | ✅     | syntax for inline styles/attribute groups                   |
-| Custom elements           | ✅      | ✅     | Define new element primitives                               |
-| Suspense                  | ✅      | ✅     | schedule future render from future/promise                  |
-| Integrated error handling | ✅      | ✅     | Gracefully handle errors with ? syntax                      |
-| Re-hydration              | ✅      | ✅     | Pre-render to HTML to speed up first contentful paint       |
-| Cooperative Scheduling    | 🛠      | ✅     | Prioritize important events over non-important events       |
-| Runs natively             | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)              |
-| 1st class global state    | ✅      | ❓     | redux/recoil/mobx on top of context                         |
-| Subtree Memoization       | ✅      | ❓     | skip diffing static element subtrees                        |
-| Compile-time correct      | ✅      | ❓     | Throw errors on invalid template layouts                    |
-| Heuristic Engine          | 🛠      | ❓     | track component memory usage to minimize future allocations |
-| Fine-grained reactivity   | 🛠      | ❓     | Skip diffing for fine-grain updates                         |
-| Effects                   | 🛠      | ✅     | Run effects after a component has been committed to render  |
-| NodeRef                   | 🛠      | ✅     | gain direct access to nodes [1]                             |
-
-- [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API.
+| Feature                   | Dioxus | React | Notes for Dioxus                                                     |
+| ------------------------- | ------ | ----- | -------------------------------------------------------------------- |
+| Conditional Rendering     | ✅      | ✅     | if/then to hide/show component                                       |
+| Map, Iterator             | ✅      | ✅     | map/filter/reduce to produce rsx!                                    |
+| Keyed Components          | ✅      | ✅     | advanced diffing with keys                                           |
+| Web                       | ✅      | ✅     | renderer for web browser                                             |
+| Desktop (webview)         | ✅      | ✅     | renderer for desktop                                                 |
+| Shared State (Context)    | ✅      | ✅     | share state through the tree                                         |
+| Hooks                     | ✅      | ✅     | memory cells in components                                           |
+| SSR                       | ✅      | ✅     | render directly to string                                            |
+| Component Children        | ✅      | ✅     | cx.children() as a list of nodes                                     |
+| Headless components       | ✅      | ✅     | components that don't return real elements                           |
+| Fragments                 | ✅      | ✅     | multiple elements without a real root                                |
+| Manual Props              | ✅      | ✅     | Manually pass in props with spread syntax                            |
+| Controlled Inputs         | ✅      | ✅     | stateful wrappers around inputs                                      |
+| CSS/Inline Styles         | ✅      | ✅     | syntax for inline styles/attribute groups                            |
+| Custom elements           | ✅      | ✅     | Define new element primitives                                        |
+| Suspense                  | ✅      | ✅     | schedule future render from future/promise                           |
+| Integrated error handling | ✅      | ✅     | Gracefully handle errors with ? syntax                               |
+| NodeRef                   | ✅      | ✅     | gain direct access to nodes                                          |
+| Re-hydration              | ✅      | ✅     | Pre-render to HTML to speed up first contentful paint                |
+| Jank-Free Rendering       | ✅      | ✅     | Large diffs are segmented across frames for silky-smooth transitions |
+| Cooperative Scheduling    | ✅      | ✅     | Prioritize important events over non-important events                |
+| Runs natively             | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)                       |
+| 1st class global state    | ✅      | ❓     | redux/recoil/mobx on top of context                                  |
+| Subtree Memoization       | ✅      | ❓     | skip diffing static element subtrees                                 |
+| Compile-time correct      | ✅      | ❓     | Throw errors on invalid template layouts                             |
+| Heuristic Engine          | ✅      | ❓     | track component memory usage to minimize future allocations          |
+| Fine-grained reactivity   | 🛠      | ❓     | Skip diffing for fine-grain updates                                  |
+| Effects                   | 🛠      | ✅     | Run effects after a component has been committed to render           |
+
 
 
 ### Phase 2: Advanced Toolkits
 ### Phase 2: Advanced Toolkits
 
 

+ 1 - 1
examples/async.rs

@@ -28,7 +28,7 @@ pub static App: FC<()> = |cx| {
             }
             }
             button {
             button {
                 "Start counting"
                 "Start counting"
-                onclick: move |_| task.start()
+                onclick: move |_| task.resume()
             }
             }
             button {
             button {
                 "Switch counting direcion"
                 "Switch counting direcion"

+ 1 - 1
examples/hydration.rs

@@ -13,7 +13,7 @@ use dioxus::prelude::*;
 use dioxus::ssr;
 use dioxus::ssr;
 
 
 fn main() {
 fn main() {
-    let mut vdom = VirtualDom::launch_in_place(App);
+    let vdom = VirtualDom::new(App);
     let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
     let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
 
 
     dioxus::desktop::launch(App, |c| c.with_prerendered(content)).unwrap();
     dioxus::desktop::launch(App, |c| c.with_prerendered(content)).unwrap();

+ 2 - 0
examples/reference/noderefs.rs

@@ -2,6 +2,8 @@ use dioxus::prelude::*;
 fn main() {}
 fn main() {}
 
 
 pub static Example: FC<()> = |cx| {
 pub static Example: FC<()> = |cx| {
+    let p = 10;
+
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
 
 

+ 2 - 2
examples/reference/task.rs

@@ -30,7 +30,7 @@ pub static Example: FC<()> = |cx| {
 
 
     // Tasks are 'static, so we need to copy relevant items in
     // Tasks are 'static, so we need to copy relevant items in
     let (async_count, dir) = (count.for_async(), *direction);
     let (async_count, dir) = (count.for_async(), *direction);
-    
+
     let (task, result) = use_task(cx, move || async move {
     let (task, result) = use_task(cx, move || async move {
         loop {
         loop {
             gloo_timers::future::TimeoutFuture::new(250).await;
             gloo_timers::future::TimeoutFuture::new(250).await;
@@ -47,7 +47,7 @@ pub static Example: FC<()> = |cx| {
             }
             }
             button {
             button {
                 "Start counting"
                 "Start counting"
-                onclick: move |_| task.start()
+                onclick: move |_| task.resume()
             }
             }
             button {
             button {
                 "Switch counting direcion"
                 "Switch counting direcion"

+ 1 - 1
examples/reference/tostring.rs

@@ -6,7 +6,7 @@ pub static Example: FC<()> = |cx| {
         // Currently, SSR is only supported for whole VirtualDOMs
         // Currently, SSR is only supported for whole VirtualDOMs
         // This is an easy/low hanging fruit to improve upon
         // This is an easy/low hanging fruit to improve upon
         let mut dom = VirtualDom::new(SomeApp);
         let mut dom = VirtualDom::new(SomeApp);
-        dom.rebuild_in_place().unwrap();
+        dom.rebuild();
         ssr::render_vdom(&dom, |c| c)
         ssr::render_vdom(&dom, |c| c)
     });
     });
 
 

+ 8 - 1
examples/ssr.rs

@@ -5,7 +5,7 @@ use dioxus::ssr;
 
 
 fn main() {
 fn main() {
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);
-    vdom.rebuild_in_place().expect("Rebuilding failed");
+    // vdom.rebuild_in_place().expect("Rebuilding failed");
     println!("{}", ssr::render_vdom(&vdom, |c| c));
     println!("{}", ssr::render_vdom(&vdom, |c| c));
 }
 }
 
 
@@ -17,3 +17,10 @@ static App: FC<()> = |cx| {
         }
         }
     ))
     ))
 };
 };
+
+struct MyProps<'a> {
+    text: &'a str,
+}
+fn App2<'a>(cx: Context<'a, MyProps>) -> DomTree<'a> {
+    None
+}

+ 136 - 0
examples/web_tick.rs

@@ -0,0 +1,136 @@
+#![allow(non_upper_case_globals, non_snake_case)]
+//! Example: Webview Renderer
+//! -------------------------
+//!
+//! This example shows how to use the dioxus_desktop crate to build a basic desktop application.
+//!
+//! Under the hood, the dioxus_desktop crate bridges a native Dioxus VirtualDom with a custom prebuit application running
+//! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events
+//! into the native VDom instance.
+//!
+//! Currently, NodeRefs won't work properly, but all other event functionality will.
+
+use dioxus::prelude::*;
+
+fn main() {
+    #[cfg(target_arch = "wasm32")]
+    intern_strings();
+
+    dioxus::web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx| {
+    let mut rng = SmallRng::from_entropy();
+    let rows = (0..1_000).map(|f| {
+        let label = Label::new(&mut rng);
+        rsx! {
+            Row {
+                row_id: f,
+                label: label
+            }
+        }
+    });
+
+    cx.render(rsx! {
+        table {
+            tbody {
+                {rows}
+            }
+        }
+    })
+};
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Label,
+}
+fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
+    let [adj, col, noun] = cx.label.0;
+    cx.render(rsx! {
+        tr {
+            td { class:"col-md-1", "{cx.row_id}" }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+                a { class: "lbl", "{adj}" "{col}" "{noun}" }
+            }
+            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::*;
+
+#[derive(PartialEq)]
+struct Label([&'static str; 3]);
+
+impl Label {
+    fn new(rng: &mut SmallRng) -> Self {
+        Label([
+            ADJECTIVES.choose(rng).unwrap(),
+            COLOURS.choose(rng).unwrap(),
+            NOUNS.choose(rng).unwrap(),
+        ])
+    }
+}
+
+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",
+];
+
+#[cfg(target_arch = "wasm32")]
+fn intern_strings() {
+    for adj in ADJECTIVES {
+        wasm_bindgen::intern(adj);
+    }
+    for col in COLOURS {
+        wasm_bindgen::intern(col);
+    }
+    for no in NOUNS {
+        wasm_bindgen::intern(no);
+    }
+    wasm_bindgen::intern("col-md-1");
+    wasm_bindgen::intern("col-md-6");
+    wasm_bindgen::intern("glyphicon glyphicon-remove remove");
+    wasm_bindgen::intern("remove");
+    wasm_bindgen::intern("dioxus");
+    wasm_bindgen::intern("lbl");
+    wasm_bindgen::intern("true");
+}

+ 4 - 8
packages/core-macro/src/rsx/ambiguous.rs

@@ -39,7 +39,8 @@ impl Parse for AmbiguousElement<AS_RSX> {
             }
             }
         }
         }
 
 
-        if let Ok(name) = input.fork().parse::<Ident>() {
+        use syn::ext::IdentExt;
+        if let Ok(name) = input.fork().call(Ident::parse_any) {
             let name_str = name.to_string();
             let name_str = name.to_string();
 
 
             let first_char = name_str.chars().next().unwrap();
             let first_char = name_str.chars().next().unwrap();
@@ -53,9 +54,6 @@ impl Parse for AmbiguousElement<AS_RSX> {
                     .map(|c| AmbiguousElement::Element(c))
                     .map(|c| AmbiguousElement::Element(c))
             }
             }
         } else {
         } else {
-            if input.peek(LitStr) {
-                panic!("it's actually a litstr");
-            }
             Err(Error::new(input.span(), "Not a valid Html tag"))
             Err(Error::new(input.span(), "Not a valid Html tag"))
         }
         }
     }
     }
@@ -80,7 +78,8 @@ impl Parse for AmbiguousElement<AS_HTML> {
             }
             }
         }
         }
 
 
-        if let Ok(name) = input.fork().parse::<Ident>() {
+        use syn::ext::IdentExt;
+        if let Ok(name) = input.fork().call(Ident::parse_any) {
             let name_str = name.to_string();
             let name_str = name.to_string();
 
 
             let first_char = name_str.chars().next().unwrap();
             let first_char = name_str.chars().next().unwrap();
@@ -94,9 +93,6 @@ impl Parse for AmbiguousElement<AS_HTML> {
                     .map(|c| AmbiguousElement::Element(c))
                     .map(|c| AmbiguousElement::Element(c))
             }
             }
         } else {
         } else {
-            if input.peek(LitStr) {
-                panic!("it's actually a litstr");
-            }
             Err(Error::new(input.span(), "Not a valid Html tag"))
             Err(Error::new(input.span(), "Not a valid Html tag"))
         }
         }
     }
     }

+ 6 - 2
packages/core-macro/src/rsx/element.rs

@@ -32,6 +32,7 @@ impl Parse for Element<AS_RSX> {
         let mut listeners: Vec<ElementAttr<AS_RSX>> = vec![];
         let mut listeners: Vec<ElementAttr<AS_RSX>> = vec![];
         let mut children: Vec<BodyNode<AS_RSX>> = vec![];
         let mut children: Vec<BodyNode<AS_RSX>> = vec![];
         let mut key = None;
         let mut key = None;
+        let mut el_ref = None;
 
 
         'parsing: loop {
         'parsing: loop {
             // [1] Break if empty
             // [1] Break if empty
@@ -45,6 +46,7 @@ impl Parse for Element<AS_RSX> {
                     &mut attributes,
                     &mut attributes,
                     &mut listeners,
                     &mut listeners,
                     &mut key,
                     &mut key,
+                    &mut el_ref,
                     name.clone(),
                     name.clone(),
                 )?;
                 )?;
             } else {
             } else {
@@ -237,6 +239,7 @@ fn parse_rsx_element_field(
     attrs: &mut Vec<ElementAttr<AS_RSX>>,
     attrs: &mut Vec<ElementAttr<AS_RSX>>,
     listeners: &mut Vec<ElementAttr<AS_RSX>>,
     listeners: &mut Vec<ElementAttr<AS_RSX>>,
     key: &mut Option<LitStr>,
     key: &mut Option<LitStr>,
+    el_ref: &mut Option<Expr>,
     element_name: Ident,
     element_name: Ident,
 ) -> Result<()> {
 ) -> Result<()> {
     let name = Ident::parse_any(stream)?;
     let name = Ident::parse_any(stream)?;
@@ -311,8 +314,9 @@ fn parse_rsx_element_field(
         "namespace" => {
         "namespace" => {
             todo!("custom namespace not supported")
             todo!("custom namespace not supported")
         }
         }
-        "ref" => {
-            todo!("NodeRefs are currently not supported! This is currently a reserved keyword.")
+        "node_ref" => {
+            *el_ref = Some(stream.parse::<Expr>()?);
+            return Ok(());
         }
         }
 
 
         // Fall through
         // Fall through

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

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

+ 21 - 9
packages/core/Cargo.toml

@@ -16,9 +16,6 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
 # Bumpalo is used as a micro heap backing each component
 # Bumpalo is used as a micro heap backing each component
 bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
 bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
 
 
-# custom error type
-thiserror = "1"
-
 # faster hashmaps
 # faster hashmaps
 fxhash = "0.2.1"
 fxhash = "0.2.1"
 
 
@@ -26,12 +23,7 @@ fxhash = "0.2.1"
 longest-increasing-subsequence = "0.1.0"
 longest-increasing-subsequence = "0.1.0"
 
 
 # internall used
 # internall used
-log = "0.4"
-
-# # Serialize the Edits for use in Webview/Liveview instances
-serde = { version = "1", features = ["derive"], optional = true }
-
-appendlist = "1.4.0"
+log = { verison = "0.4", features = ["release_max_level_off"] }
 
 
 futures-util = "0.3.15"
 futures-util = "0.3.15"
 
 
@@ -41,13 +33,33 @@ slab = "0.4.3"
 
 
 futures-channel = "0.3.16"
 futures-channel = "0.3.16"
 
 
+# used for noderefs
+once_cell = "1.8.0"
+
+# # Serialize the Edits for use in Webview/Liveview instances
+serde = { version = "1", features = ["derive"], optional = true }
+
+indexmap = "1.7.0"
+
 
 
 [dev-dependencies]
 [dev-dependencies]
 anyhow = "1.0.42"
 anyhow = "1.0.42"
 async-std = { version = "1.9.0", features = ["attributes"] }
 async-std = { version = "1.9.0", features = ["attributes"] }
+criterion = "0.3.5"
 dioxus-html = { path = "../html" }
 dioxus-html = { path = "../html" }
+fern = { version = "0.6.0", features = ["colored"] }
+rand = { version = "0.8.4", features = ["small_rng"] }
+simple_logger = "1.13.0"
 
 
 
 
 [features]
 [features]
 default = ["serialize"]
 default = ["serialize"]
 serialize = ["serde"]
 serialize = ["serde"]
+
+[[bench]]
+name = "create"
+harness = false
+
+[[bench]]
+name = "jsframework"
+harness = false

+ 0 - 39
packages/core/README.md

@@ -40,42 +40,3 @@ We have big goals for Dioxus. The final implementation must:
 - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
 - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
 - Be modular. Components and hooks should be work anywhere without worrying about target platform.
 - Be modular. Components and hooks should be work anywhere without worrying about target platform.
 
 
-## Optimizations
-
-- Support a pluggable allocation strategy that makes VNode creation **very** fast
-- Support lazy VNodes (ie VNodes that are not actually created when the html! macro is used)
-- Support advanced diffing strategies (patience, Meyers, etc)
-
-```rust
-
-rsx!{ "this is a text node" }
-
-rsx!{
-    div {}
-    "asd"
-    div {}
-    div {}
-}
-rsx!{
-    div {
-        a {}
-        b {}
-        c {}
-        Container {
-            Container {
-                Container {
-                    Container {
-                        Container {
-                            div {}
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-
-
-
-```

+ 1 - 0
packages/core/benches/create.rs

@@ -0,0 +1 @@
+fn main() {}

+ 126 - 0
packages/core/benches/jsframework.rs

@@ -0,0 +1,126 @@
+#![allow(non_snake_case, non_upper_case_globals)]
+//! This benchmark tests just the overhead of Dioxus itself.
+//!
+//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes
+//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
+//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
+//!
+//! On my MBP 2019:
+//! - Dioxus takes 3ms to create 1_000 rows
+//! - Dioxus takes 30ms to create 10_000 rows
+//!
+//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
+//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_html as dioxus_elements;
+use rand::prelude::*;
+
+criterion_group!(mbenches, create_rows);
+criterion_main!(mbenches);
+
+fn create_rows(c: &mut Criterion) {
+    static App: FC<()> = |cx| {
+        let mut rng = SmallRng::from_entropy();
+        let rows = (0..10_000).map(|f| {
+            let label = Label::new(&mut rng);
+            rsx! {
+                Row {
+                    row_id: f,
+                    label: label
+                }
+            }
+        });
+        cx.render(rsx! {
+            table {
+                tbody {
+                    {rows}
+                }
+            }
+        })
+    };
+
+    c.bench_function("create rows", |b| {
+        b.iter(|| {
+            let mut dom = VirtualDom::new(App);
+            let g = dom.rebuild();
+            assert!(g.edits.len() > 1);
+        })
+    });
+}
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Label,
+}
+fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
+    let [adj, col, noun] = cx.label.0;
+    cx.render(rsx! {
+        tr {
+            td { class:"col-md-1", "{cx.row_id}" }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+                a { class: "lbl", "{adj}" "{col}" "{noun}" }
+            }
+            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" }
+        }
+    })
+}
+
+#[derive(PartialEq)]
+struct Label([&'static str; 3]);
+
+impl Label {
+    fn new(rng: &mut SmallRng) -> Self {
+        Label([
+            ADJECTIVES.choose(rng).unwrap(),
+            COLOURS.choose(rng).unwrap(),
+            NOUNS.choose(rng).unwrap(),
+        ])
+    }
+}
+
+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",
+];

+ 5 - 6
packages/core/examples/alternative.rs

@@ -3,20 +3,19 @@ use dioxus_core::prelude::*;
 fn main() {}
 fn main() {}
 
 
 pub static Example: FC<()> = |cx| {
 pub static Example: FC<()> = |cx| {
-    let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
+    let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!()));
 
 
     cx.render(LazyNodes::new(move |cx| {
     cx.render(LazyNodes::new(move |cx| {
-        let bump = cx.bump();
         cx.raw_element(
         cx.raw_element(
             "div",
             "div",
             None,
             None,
-            &mut [],
-            &mut [],
-            cx.bump().alloc([
+            [],
+            [],
+            [
                 cx.text(format_args!("hello")),
                 cx.text(format_args!("hello")),
                 cx.text(format_args!("hello")),
                 cx.text(format_args!("hello")),
                 cx.fragment_from_iter(list),
                 cx.fragment_from_iter(list),
-            ]),
+            ],
             None,
             None,
         )
         )
     }))
     }))

+ 1 - 1
packages/core/examples/async.rs

@@ -21,7 +21,7 @@ const App: FC<()> = |cx| {
 };
 };
 
 
 const Task: FC<()> = |cx| {
 const Task: FC<()> = |cx| {
-    let (task, res) = use_task(cx, || async { true });
+    let (_task, _res) = use_task(cx, || async { true });
     // task.pause();
     // task.pause();
     // task.restart();
     // task.restart();
     // task.stop();
     // task.stop();

+ 0 - 18
packages/core/examples/fragment.rs

@@ -1,18 +0,0 @@
-
-
-fn main() {
-
-    // let g = rsx! {
-    //     Fragment {
-    //         // div {}
-    //         // div {}
-    //         // div {}
-    //         // div {}
-    //         // div {}
-    //         // div {}
-    //         // div {}
-    //         // div {}
-    //         // div {}
-    //     }
-    // };
-}

+ 13 - 17
packages/core/examples/fragment_from_iter.rs

@@ -1,31 +1,27 @@
-fn main() {}
-
 use dioxus_core::prelude::*;
 use dioxus_core::prelude::*;
 
 
-fn App(cx: Context<()>) -> DomTree {
-    //
+fn main() {}
+
+fn app(cx: Context<()>) -> DomTree {
     let vak = use_suspense(
     let vak = use_suspense(
         cx,
         cx,
         || async {},
         || async {},
-        |c, res| {
-            //
-            c.render(LazyNodes::new(move |f| f.text(format_args!(""))))
-        },
+        |c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))),
     );
     );
 
 
     let d1 = cx.render(LazyNodes::new(move |f| {
     let d1 = cx.render(LazyNodes::new(move |f| {
         f.raw_element(
         f.raw_element(
             "div",
             "div",
             None,
             None,
-            &mut [],
-            &[],
-            f.bump().alloc([
+            [],
+            [],
+            [
                 f.fragment_from_iter(vak),
                 f.fragment_from_iter(vak),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
-            ]),
+            ],
             None,
             None,
         )
         )
     }));
     }));
@@ -34,15 +30,15 @@ fn App(cx: Context<()>) -> DomTree {
         f.raw_element(
         f.raw_element(
             "div",
             "div",
             None,
             None,
-            &mut [],
-            &[],
-            f.bump().alloc([
+            [],
+            [],
+            [
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
                 f.text(format_args!("")),
-                f.fragment_from_iter(d1),
-            ]),
+                d1.unwrap(),
+            ],
             None,
             None,
         )
         )
     }))
     }))

+ 109 - 0
packages/core/examples/jsframework.rs

@@ -0,0 +1,109 @@
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_html as dioxus_elements;
+use rand::prelude::*;
+use std::fmt::Display;
+
+fn main() {
+    let mut dom = VirtualDom::new(App);
+    let g = dom.rebuild();
+    assert!(g.edits.len() > 1);
+}
+
+static App: FC<()> = |cx| {
+    let mut rng = SmallRng::from_entropy();
+    let rows = (0..10_000).map(|f| {
+        let label = Label::new(&mut rng);
+        rsx! {
+            Row {
+                row_id: f,
+                label: label
+            }
+        }
+    });
+    cx.render(rsx! {
+        table {
+            tbody {
+                {rows}
+            }
+        }
+    })
+};
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Label,
+}
+fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
+    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" }
+        }
+    })
+}
+
+#[derive(PartialEq)]
+struct Label([&'static str; 3]);
+
+impl Label {
+    fn new(rng: &mut SmallRng) -> Self {
+        Label([
+            ADJECTIVES.choose(rng).unwrap(),
+            COLOURS.choose(rng).unwrap(),
+            NOUNS.choose(rng).unwrap(),
+        ])
+    }
+}
+impl Display for Label {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{} {} {}", self.0[0], self.0[1], self.0[2])
+    }
+}
+
+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",
+];

+ 0 - 137
packages/core/examples/macrosrc.rs

@@ -1,137 +0,0 @@
-// #![allow(unused, non_upper_case_globals, non_snake_case)]
-// use bumpalo::Bump;
-// use dioxus_core::nodebuilder::*;
-// use dioxus_core::prelude::*;
-// use std::{collections::HashMap, future::Future, marker::PhantomData};
-
-fn main() {}
-// fn main() {
-//     let mut vdom = VirtualDom::new_with_props(
-//         component,
-//         Props {
-//             blah: false,
-//             text: "blah".into(),
-//         },
-//     );
-
-//     vdom.progress();
-
-//     let somet = String::from("asd");
-//     let text = somet.as_str();
-
-//     /*
-//     this could be auto-generated via the macro
-//     this props is allocated in this
-//     but the component and props would like need to be cached
-//     we could box this fn, abstracting away the props requirement and just keep the entrance and allocator requirement
-//     How do we keep cached things around?
-//     Need some sort of caching mechanism
-
-//     how do we enter into a childscope from a parent scope?
-
-//     Problems:
-//     1: Comp props need to be stored somewhere so we can re-evalute components when they receive updates
-//     2: Trees are not evaluated
-
-//     */
-//     let example_caller = move |cx: &Bump| {
-//         todo!()
-//         // let p = Props { blah: true, text };
-//         // let c = Context { props: &p };
-//         // let r = component(&c);
-//     };
-
-//     // check the edit list
-// }
-
-// // ~~~ Text shared between components via props can be done with lifetimes! ~~~
-// // Super duper efficient :)
-// struct Props {
-//     blah: bool,
-//     text: String,
-//     // text: &'src str,
-// }
-// impl Properties for Props {
-//     fn new() -> Self {
-//         todo!()
-//     }
-// }
-
-// fn component<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
-//     // Write asynchronous rendering code that immediately returns a "suspended" VNode
-//     // The concurrent API will then progress this component when the future finishes
-//     // You can suspend the entire component, or just parts of it
-//     let product_list = cx.suspend(async {
-//         // Suspend the rendering that completes when the future is done
-//         match fetch_data().await {
-//             Ok(data) => html! { <div> "success!" </div>},
-//             Err(_) => html! { <div> "failure :(" </div>},
-//         }
-//     });
-
-//     // todo!()
-//     cx.render(html! {
-//         <div>
-//             <h1> "Products" </h1>
-//             <button> "hello!" </button>
-//             // Subnodes can even be suspended
-//             // When completely rendered, they won't cause the component itself to re-render, just their slot
-
-//             // <p> { product_list } </p>
-//         </div>
-//     })
-// }
-
-// fn BuilderComp<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
-//     // VNodes can be constructed via a builder or the html! macro
-//     // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
-//     // We can "view" them with Context for ultimate speed while inside components
-//     cx.render(|bump| {
-//         div(bump)
-//             .attr("class", "edit")
-//             .child(text("Hello"))
-//             .child(text(cx.cx.text.as_ref()))
-//             .finish()
-//     })
-// }
-
-// #[fc]
-// fn EffcComp(cx: Context, name: &str) -> DomTree {
-//     // VNodes can be constructed via a builder or the html! macro
-//     // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
-//     // We can "view" them with Context for ultimate speed while inside components
-//     // use "phase" style allocation;
-
-//     cx.render(html! {
-//         <div>
-//             // your template goes here
-//             // feel free to directly use "name"
-//         </div>
-//     })
-// }
-
-// fn FullySuspended<'a>(cx: &'a Context<Props>) -> DomTree<'a> {
-//     cx.suspend(async {
-//         let i: i32 = 0;
-
-//         // full suspended works great with just returning VNodes!
-//         // Feel free to capture the html! macro directly
-//         // Anything returned here is automatically viewed
-//         let tex = match i {
-//             1 => html! { <div> </div> },
-//             2 => html! { <div> </div> },
-//             _ => html! { <div> </div> },
-//         };
-
-//         if cx.cx.blah {
-//             html! { <div> </div> }
-//         } else {
-//             tex
-//         }
-//     })
-// }
-
-// /// An example of a datafetching service
-// async fn fetch_data() -> Result<String, ()> {
-//     todo!()
-// }

+ 15 - 0
packages/core/examples/vdom_usage.rs

@@ -0,0 +1,15 @@
+use std::time::Duration;
+
+use dioxus_core::prelude::*;
+
+#[async_std::main]
+async fn main() {
+    static App: FC<()> = |cx| cx.render(LazyNodes::new(|f| f.text(format_args!("hello"))));
+
+    let mut dom = VirtualDom::new(App);
+
+    dom.rebuild();
+
+    let deadline = async_std::task::sleep(Duration::from_millis(50));
+    let _fut = dom.run_with_deadline(deadline);
+}

+ 0 - 190
packages/core/src/arena.rs

@@ -1,190 +0,0 @@
-use std::cell::{RefCell, RefMut};
-use std::fmt::Display;
-use std::{cell::UnsafeCell, rc::Rc};
-
-use crate::heuristics::*;
-use crate::innerlude::*;
-use futures_util::stream::FuturesUnordered;
-use fxhash::{FxHashMap, FxHashSet};
-use slab::Slab;
-use smallvec::SmallVec;
-
-// slotmap::new_key_type! {
-//     // A dedicated key type for the all the scopes
-//     pub struct ScopeId;
-// }
-// #[cfg(feature = "serialize", serde::Serialize)]
-// #[cfg(feature = "serialize", serde::Serialize)]
-#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct ScopeId(pub usize);
-
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct ElementId(pub usize);
-impl Display for ElementId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
-impl ElementId {
-    pub fn as_u64(self) -> u64 {
-        self.0 as u64
-    }
-}
-
-type Shared<T> = Rc<RefCell<T>>;
-type TaskReceiver = futures_channel::mpsc::UnboundedReceiver<EventTrigger>;
-type TaskSender = futures_channel::mpsc::UnboundedSender<EventTrigger>;
-
-/// These are resources shared among all the components and the virtualdom itself
-#[derive(Clone)]
-pub struct SharedResources {
-    pub components: Rc<UnsafeCell<Slab<Scope>>>,
-
-    pub(crate) heuristics: Shared<HeuristicsEngine>,
-
-    ///
-    pub task_sender: TaskSender,
-
-    pub task_receiver: Shared<TaskReceiver>,
-
-    pub async_tasks: Shared<FuturesUnordered<FiberTask>>,
-
-    /// We use a SlotSet to keep track of the keys that are currently being used.
-    /// However, we don't store any specific data since the "mirror"
-    pub raw_elements: Rc<RefCell<Slab<()>>>,
-
-    pub task_setter: Rc<dyn Fn(ScopeId)>,
-}
-
-impl SharedResources {
-    pub fn new() -> Self {
-        // preallocate 2000 elements and 20 scopes to avoid dynamic allocation
-        let components: Rc<UnsafeCell<Slab<Scope>>> =
-            Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
-
-        // elements are super cheap - the value takes no space
-        let raw_elements = Slab::with_capacity(2000);
-
-        let (sender, receiver) = futures_channel::mpsc::unbounded();
-
-        let heuristics = HeuristicsEngine::new();
-
-        // we allocate this task setter once to save us from having to allocate later
-        let task_setter = {
-            let queue = sender.clone();
-            let components = components.clone();
-            Rc::new(move |idx: ScopeId| {
-                let comps = unsafe { &*components.get() };
-
-                if let Some(scope) = comps.get(idx.0) {
-                    queue
-                        .unbounded_send(EventTrigger::new(
-                            VirtualEvent::ScheduledUpdate {
-                                height: scope.height,
-                            },
-                            idx,
-                            None,
-                            EventPriority::High,
-                        ))
-                        .expect("The event queu receiver should *never* be dropped");
-                }
-            }) as Rc<dyn Fn(ScopeId)>
-        };
-
-        Self {
-            components,
-            async_tasks: Rc::new(RefCell::new(FuturesUnordered::new())),
-            task_receiver: Rc::new(RefCell::new(receiver)),
-            task_sender: sender,
-            heuristics: Rc::new(RefCell::new(heuristics)),
-            raw_elements: Rc::new(RefCell::new(raw_elements)),
-            task_setter,
-        }
-    }
-
-    /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
-        let inner = unsafe { &*self.components.get() };
-        inner.get(idx.0)
-    }
-
-    /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
-        let inner = unsafe { &mut *self.components.get() };
-        inner.get_mut(idx.0)
-    }
-
-    pub fn with_scope<'b, O: 'static>(
-        &'b self,
-        _id: ScopeId,
-        _f: impl FnOnce(&'b mut Scope) -> O,
-    ) -> Result<O> {
-        todo!()
-    }
-
-    // return a bumpframe with a lifetime attached to the arena borrow
-    // this is useful for merging lifetimes
-    pub fn with_scope_vnode<'b>(
-        &self,
-        _id: ScopeId,
-        _f: impl FnOnce(&mut Scope) -> &VNode<'b>,
-    ) -> Result<&VNode<'b>> {
-        todo!()
-    }
-
-    pub fn try_remove(&self, id: ScopeId) -> Result<Scope> {
-        let inner = unsafe { &mut *self.components.get() };
-        Ok(inner.remove(id.0))
-        // .try_remove(id.0)
-        // .ok_or_else(|| Error::FatalInternal("Scope not found"))
-    }
-
-    pub fn reserve_node(&self) -> ElementId {
-        ElementId(self.raw_elements.borrow_mut().insert(()))
-    }
-
-    /// return the id, freeing the space of the original node
-    pub fn collect_garbage(&self, id: ElementId) {
-        self.raw_elements.borrow_mut().remove(id.0);
-    }
-
-    pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
-        let g = unsafe { &mut *self.components.get() };
-        let entry = g.vacant_entry();
-        let id = ScopeId(entry.key());
-        entry.insert(f(id));
-        id
-    }
-
-    pub fn schedule_update(&self) -> Rc<dyn Fn(ScopeId)> {
-        self.task_setter.clone()
-    }
-
-    pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        self.async_tasks.borrow_mut().push(task);
-        TaskHandle {}
-    }
-
-    pub fn make_trigger_key(&self, trigger: &EventTrigger) -> EventKey {
-        let height = self
-            .get_scope(trigger.originator)
-            .map(|f| f.height)
-            .unwrap();
-
-        EventKey {
-            height,
-            originator: trigger.originator,
-            priority: trigger.priority,
-        }
-    }
-}
-
-pub struct TaskHandle {}
-
-impl TaskHandle {
-    pub fn toggle(&self) {}
-    pub fn start(&self) {}
-    pub fn stop(&self) {}
-    pub fn restart(&self) {}
-}

+ 2 - 12
packages/core/src/bumpframe.rs

@@ -2,7 +2,7 @@ use crate::innerlude::*;
 use bumpalo::Bump;
 use bumpalo::Bump;
 use std::cell::Cell;
 use std::cell::Cell;
 
 
-pub struct ActiveFrame {
+pub(crate) struct ActiveFrame {
     // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
     // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
     pub generation: Cell<usize>,
     pub generation: Cell<usize>,
 
 
@@ -10,7 +10,7 @@ pub struct ActiveFrame {
     pub frames: [BumpFrame; 2],
     pub frames: [BumpFrame; 2],
 }
 }
 
 
-pub struct BumpFrame {
+pub(crate) struct BumpFrame {
     pub bump: Bump,
     pub bump: Bump,
     pub(crate) head_node: VNode<'static>,
     pub(crate) head_node: VNode<'static>,
 
 
@@ -40,10 +40,6 @@ impl ActiveFrame {
         self.wip_frame_mut().bump.reset()
         self.wip_frame_mut().bump.reset()
     }
     }
 
 
-    pub fn update_head_node<'a>(&mut self, node: VNode<'a>) {
-        self.wip_frame_mut().head_node = unsafe { std::mem::transmute(node) };
-    }
-
     /// The "work in progress frame" represents the frame that is currently being worked on.
     /// The "work in progress frame" represents the frame that is currently being worked on.
     pub fn wip_frame(&self) -> &BumpFrame {
     pub fn wip_frame(&self) -> &BumpFrame {
         match self.generation.get() & 1 == 0 {
         match self.generation.get() & 1 == 0 {
@@ -67,12 +63,6 @@ impl ActiveFrame {
         }
         }
     }
     }
 
 
-    pub fn finished_frame_mut(&mut self) -> &mut BumpFrame {
-        match self.generation.get() & 1 == 1 {
-            true => &mut self.frames[0],
-            false => &mut self.frames[1],
-        }
-    }
     /// Give out our self-referential item with our own borrowed lifetime
     /// Give out our self-referential item with our own borrowed lifetime
     pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
     pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
         let cur_head = &self.finished_frame().head_node;
         let cur_head = &self.finished_frame().head_node;

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

@@ -0,0 +1,94 @@
+use crate::innerlude::*;
+
+/// This iterator iterates through a list of virtual children and only returns real children (Elements, Text, Anchors).
+///
+/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
+/// "InsertBefore".
+pub(crate) struct RealChildIterator<'a> {
+    scopes: &'a ResourcePool,
+
+    // Heuristcally we should never bleed into 4 completely nested fragments/components
+    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
+    // TODO: use const generics instead of the 4 estimation
+    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>,
+}
+
+impl<'a> RealChildIterator<'a> {
+    pub fn new(starter: &'a VNode<'a>, scopes: &'a ResourcePool) -> Self {
+        Self {
+            scopes,
+            stack: smallvec::smallvec![(0, starter)],
+        }
+    }
+}
+
+impl<'a> Iterator for RealChildIterator<'a> {
+    type Item = &'a VNode<'a>;
+
+    fn next(&mut self) -> Option<&'a VNode<'a>> {
+        let mut should_pop = false;
+        let mut returned_node: Option<&'a VNode<'a>> = None;
+        let mut should_push = None;
+
+        while returned_node.is_none() {
+            if let Some((count, node)) = self.stack.last_mut() {
+                match &node {
+                    // We can only exit our looping when we get "real" nodes
+                    // This includes fragments and components when they're empty (have a single root)
+                    VNode::Element(_) | VNode::Text(_) | VNode::Suspended(_) | VNode::Anchor(_) => {
+                        // We've recursed INTO an element/text
+                        // We need to recurse *out* of it and move forward to the next
+                        should_pop = true;
+                        returned_node = Some(&*node);
+                    }
+
+                    // If we get a fragment we push the next child
+                    VNode::Fragment(frag) => {
+                        let subcount = *count as usize;
+
+                        if frag.children.len() == 0 {
+                            should_pop = true;
+                            returned_node = Some(&*node);
+                        }
+
+                        if subcount >= frag.children.len() {
+                            should_pop = true;
+                        } else {
+                            should_push = Some(&frag.children[subcount]);
+                        }
+                    }
+
+                    // For components, we load their root and push them onto the stack
+                    VNode::Component(sc) => {
+                        let scope = self
+                            .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();
+                    }
+                }
+            } else {
+                // If there's no more items on the stack, we're done!
+                return None;
+            }
+
+            if should_pop {
+                self.stack.pop();
+                if let Some((id, _)) = self.stack.last_mut() {
+                    *id += 1;
+                }
+                should_pop = false;
+            }
+
+            if let Some(push) = should_push {
+                self.stack.push((0, push));
+                should_push = None;
+            }
+        }
+
+        returned_node
+    }
+}

+ 10 - 57
packages/core/src/context.rs

@@ -87,6 +87,10 @@ impl<'src, P> Context<'src, P> {
     ///     })
     ///     })
     /// }
     /// }
     /// ```
     /// ```
+    ///
+    /// ## Notes:
+    ///
+    /// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime.
     pub fn children(&self) -> ScopeChildren<'src> {
     pub fn children(&self) -> ScopeChildren<'src> {
         self.scope.child_nodes()
         self.scope.child_nodes()
     }
     }
@@ -96,27 +100,11 @@ impl<'src, P> Context<'src, P> {
     /// ## Notice: you should prefer using prepare_update and get_scope_id
     /// ## Notice: you should prefer using prepare_update and get_scope_id
     ///
     ///
     pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
     pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        let cb = self.scope.vdom.schedule_update();
-        let id = self.get_scope_id();
-        Rc::new(move || cb(id))
+        self.scope.memoized_updater.clone()
     }
     }
 
 
     pub fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
     pub fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
-        self.scope.vdom.schedule_update()
-    }
-
-    pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
-        todo!()
-    }
-
-    pub fn schedule_layout_effect(&self) {
-        todo!()
-    }
-
-    /// Get's this component's unique identifier.
-    ///
-    pub fn get_scope_id(&self) -> ScopeId {
-        self.scope.our_arena_idx.clone()
+        self.scope.shared.schedule_any_immediate.clone()
     }
     }
 
 
     /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
     /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
@@ -160,7 +148,7 @@ impl<'src, P> Context<'src, P> {
     ///
     ///
     ///
     ///
     pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
     pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        self.scope.vdom.submit_task(task)
+        (self.scope.shared.submit_task)(task)
     }
     }
 
 
     /// Add a state globally accessible to child components via tree walking
     /// Add a state globally accessible to child components via tree walking
@@ -174,46 +162,11 @@ impl<'src, P> Context<'src, P> {
             });
             });
     }
     }
 
 
-    /// Walk the tree to find a shared state with the TypeId of the generic type
-    ///
     pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
     pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
-        let mut scope = Some(self.scope);
-        let mut parent = None;
-
+        let getter = &self.scope.shared.get_shared_context;
         let ty = TypeId::of::<T>();
         let ty = TypeId::of::<T>();
-        while let Some(inner) = scope {
-            log::debug!(
-                "Searching {:#?} for valid shared_context",
-                inner.our_arena_idx
-            );
-            let shared_ctx = {
-                let shared_contexts = inner.shared_contexts.borrow();
-
-                log::debug!(
-                    "This component has {} shared contexts",
-                    shared_contexts.len()
-                );
-                shared_contexts.get(&ty).map(|f| f.clone())
-            };
-
-            if let Some(shared_cx) = shared_ctx {
-                log::debug!("found matching cx");
-                let rc = shared_cx
-                    .clone()
-                    .downcast::<T>()
-                    .expect("Should not fail, already validated the type from the hashmap");
-                parent = Some(rc);
-                break;
-            } else {
-                match inner.parent_idx {
-                    Some(parent_id) => {
-                        scope = unsafe { inner.vdom.get_scope(parent_id) };
-                    }
-                    None => break,
-                }
-            }
-        }
-        parent
+        let idx = self.scope.our_arena_idx;
+        getter(idx, ty).map(|f| f.downcast().expect("TypeID already validated"))
     }
     }
 
 
     /// Store a value between renders
     /// Store a value between renders

+ 0 - 78
packages/core/src/debug_renderer.rs

@@ -1,78 +0,0 @@
-//! Debug virtual doms!
-//! This renderer comes built in with dioxus core and shows how to implement a basic renderer.
-//!
-//! 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::*};
-
-pub struct DebugRenderer {
-    internal_dom: VirtualDom,
-}
-
-impl DebugRenderer {
-    /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
-    ///
-    /// This means that the root component must either consumes its own context, or statics are used to generate the page.
-    /// The root component can access things like routing in its context.
-    pub fn new(root: FC<()>) -> Self {
-        Self::new_with_props(root, ())
-    }
-
-    /// Create a new text-renderer instance from a functional component root.
-    /// Automatically progresses the creation of the VNode tree to completion.
-    ///
-    /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
-    pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
-        Self::from_vdom(VirtualDom::new_with_props(root, root_props))
-    }
-
-    /// Create a new text renderer from an existing Virtual DOM.
-    pub fn from_vdom(dom: VirtualDom) -> Self {
-        // todo: initialize the event registry properly
-        Self { internal_dom: dom }
-    }
-
-    pub fn handle_event(&mut self, trigger: EventTrigger) -> Result<()> {
-        Ok(())
-    }
-
-    // pub fn step<Dom: RealDom>(&mut self, machine: &mut DiffMachine<Dom>) -> Result<()> {
-    //     Ok(())
-    // }
-
-    // this does a "holy" compare - if something is missing in the rhs, it doesn't complain.
-    // it only complains if something shows up that's not in the lhs, *or* if a value is different.
-    // This lets you exclude various fields if you just want to drill in to a specific prop
-    // It leverages the internal diffing mechanism.
-    // If you have a list or "nth" child, you do need to list those children, but you don't need to
-    // fill in their children/attrs/etc
-    // Does not handle children or lifecycles and will always fail the test if they show up in the rhs
-    pub fn compare<F>(&self, other: LazyNodes<F>) -> Result<()>
-    where
-        F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>,
-    {
-        Ok(())
-    }
-
-    // Do a full compare - everything must match
-    // Ignores listeners and children components
-    pub fn compare_full<F>(&self, other: LazyNodes<F>) -> Result<()>
-    where
-        F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>,
-    {
-        Ok(())
-    }
-
-    pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
-        Ok(())
-    }
-
-    pub fn render_nodes<F>(&self, other: LazyNodes<F>) -> Result<()>
-    where
-        F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>,
-    {
-        Ok(())
-    }
-}

+ 781 - 1289
packages/core/src/diff.rs

@@ -1,30 +1,53 @@
 //! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
 //! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
-//! The DiffMachine calculates the diffs between the old and new frames, updates the new nodes, and modifies the real dom.
+//!
+//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
+//! of mutations for the RealDom to apply.
 //!
 //!
 //! ## Notice:
 //! ## Notice:
+//!
 //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
 //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
-//! Components, Fragments, Suspense, SubTree memoization, and additional batching operations.
+//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancelation, NodeRefs, pausing, priority
+//! scheduling, and additional batching operations.
 //!
 //!
 //! ## Implementation Details:
 //! ## Implementation Details:
 //!
 //!
 //! ### IDs for elements
 //! ### IDs for elements
 //! --------------------
 //! --------------------
 //! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
 //! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
-//! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
-//! to batch nodes if it is more performant for their application. The expectation is that renderers use a Slotmap for nodes
-//! whose keys can be converted to u64 on FFI boundaries.
+//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
+//! to batch nodes if it is more performant for their application. The element IDs are indicies into the internal element
+//! array. The expectation is that implemenetors will use the ID as an index into a Vec of real nodes, allowing for passive
+//! garbage collection as the VirtualDOM replaces old nodes.
 //!
 //!
-//! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we
-//! always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly and
-//! brick the user's page.
+//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
+//! we always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly
+//! and brick the user's page.
 //!
 //!
 //! ### Fragment Support
 //! ### Fragment Support
-//!
+//! --------------------
 //! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
 //! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
-//! can be particularly challenging when they are empty, so the placeholder node lets us "reserve" a spot for the empty
+//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
 //! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is
 //! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is
-//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. This is
-//! slightly inefficient, but represents a such an uncommon use case that it is not worth optimizing.
+//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
+//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
+//!  the platform.
+//!
+//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
+//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
+//!
+//! ### Suspense
+//! ------------
+//! Dioxus implements suspense slightly differently than React. In React, each fiber is manually progressed until it runs
+//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once
+//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before
+//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on.
+//! Due to the frequent calls to "yield_now" we can get the pure "fetch-as-you-render" behavior of React fiber.
+//!
+//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to
+//! DOM, but as a placeholder.
+//!
+//! Right now, the "suspense" queue is intertwined the hooks. In the future, we should allow any future to drive attributes
+//! and contents, without the need for the "use_suspense" hook. In the interim, this is the quickest way to get suspense working.
 //!
 //!
 //! ## Subtree Memoization
 //! ## Subtree Memoization
 //! -----------------------
 //! -----------------------
@@ -35,13 +58,15 @@
 //! rsx!( div { class: "hello world", "this node is entirely static" } )
 //! rsx!( div { class: "hello world", "this node is entirely static" } )
 //! ```
 //! ```
 //! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
 //! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
-//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP
+//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
+//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
+//! a way of short-circuiting the most expensive checks.
 //!
 //!
 //! ## Bloom Filter and Heuristics
 //! ## Bloom Filter and Heuristics
 //! ------------------------------
 //! ------------------------------
 //! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
 //! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
-//! currently very rough, but will get better as time goes on. For FFI, we recommend using a bloom filter to cache strings.
-//!
+//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a
+//! bump arena after first render, the number of hooks, and the number of nodes in the tree.
 //!
 //!
 //! ## Garbage Collection
 //! ## Garbage Collection
 //! ---------------------
 //! ---------------------
@@ -53,521 +78,505 @@
 //! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
 //! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
 //! for the client. As new nodes are created, old nodes will be over-written.
 //! for the client. As new nodes are created, old nodes will be over-written.
 //!
 //!
-//! HEADS-UP:
-//!     For now, deferred garabge collection is disabled. The code-paths are almost wired up, but it's quite complex to
-//!     get working safely and efficiently. For now, garabge is collected immediately during diffing. This adds extra
-//!     overhead, but is faster to implement in the short term.
-//!
-//! Further Reading and Thoughts
+//! ## Further Reading and Thoughts
 //! ----------------------------
 //! ----------------------------
 //! There are more ways of increasing diff performance here that are currently not implemented.
 //! There are more ways of increasing diff performance here that are currently not implemented.
+//! - Strong memoization of subtrees.
+//! - Guided diffing.
+//! - Certain web-dom-specific optimizations.
+//!
 //! More info on how to improve this diffing algorithm:
 //! More info on how to improve this diffing algorithm:
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
 
 
-use crate::{arena::SharedResources, innerlude::*};
-use futures_util::Future;
-use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
-use smallvec::{smallvec, SmallVec};
-
-use std::{any::Any, cell::Cell, cmp::Ordering, marker::PhantomData, pin::Pin};
+use crate::innerlude::*;
+use fxhash::{FxHashMap, FxHashSet};
 use DomEdit::*;
 use DomEdit::*;
 
 
-pub struct DiffMachine<'r, 'bump> {
-    pub vdom: &'bump SharedResources,
-
-    pub edits: Mutations<'bump>,
-
-    pub scope_stack: SmallVec<[ScopeId; 5]>,
-
-    pub diffed: FxHashSet<ScopeId>,
+/// Our DiffMachine is an iterative tree differ.
+///
+/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
+/// was origially implemented using recursive techniques, but Rust lacks the abilty to call async functions recursively,
+/// meaning we could not "pause" the original diffing algorithm.
+///
+/// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically
+/// calls "yield_now" which allows the machine to pause and return control to the caller. The caller can then wait for
+/// the next period of idle time, preventing our diff algorithm from blocking the main thread.
+///
+/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
+/// stack machines all the way down!
+pub(crate) struct DiffMachine<'bump> {
+    pub vdom: &'bump ResourcePool,
+    pub mutations: Mutations<'bump>,
+    pub stack: DiffStack<'bump>,
+    pub seen_scopes: FxHashSet<ScopeId>,
+}
 
 
-    // will be used later for garbage collection
-    // we check every seen node and then schedule its eventual deletion
+/// a "saved" form of a diff machine
+/// in regular diff machine, the &'bump reference is a stack borrow, but the
+/// bump lifetimes are heap borrows.
+pub(crate) struct SavedDiffWork<'bump> {
+    pub mutations: Mutations<'bump>,
+    pub stack: DiffStack<'bump>,
     pub seen_scopes: FxHashSet<ScopeId>,
     pub seen_scopes: FxHashSet<ScopeId>,
+}
 
 
-    _r: PhantomData<&'r ()>,
+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 {
+            vdom,
+            mutations: extended.mutations,
+            stack: extended.stack,
+            seen_scopes: extended.seen_scopes,
+        }
+    }
 }
 }
 
 
-impl<'r, 'bump> DiffMachine<'r, 'bump> {
-    pub(crate) fn new(
-        edits: Mutations<'bump>,
-        cur_scope: ScopeId,
-        shared: &'bump SharedResources,
-    ) -> Self {
+impl<'bump> DiffMachine<'bump> {
+    pub(crate) fn new(edits: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
         Self {
         Self {
-            edits,
-            scope_stack: smallvec![cur_scope],
+            stack: DiffStack::new(),
+            mutations: edits,
             vdom: shared,
             vdom: shared,
-            diffed: FxHashSet::default(),
             seen_scopes: FxHashSet::default(),
             seen_scopes: FxHashSet::default(),
-            _r: PhantomData,
         }
         }
     }
     }
 
 
-    /// Allows the creation of a diff machine without the concept of scopes or a virtualdom
-    /// this is mostly useful for testing
-    ///
-    /// This will PANIC if any component elements are passed in.
-    pub fn new_headless(shared: &'bump SharedResources) -> Self {
-        Self {
-            edits: Mutations { edits: Vec::new() },
-            scope_stack: smallvec![ScopeId(0)],
-            vdom: shared,
-            diffed: FxHashSet::default(),
-            seen_scopes: FxHashSet::default(),
-            _r: PhantomData,
+    pub fn save(self) -> SavedDiffWork<'bump> {
+        SavedDiffWork {
+            mutations: self.mutations,
+            stack: self.stack,
+            seen_scopes: self.seen_scopes,
         }
         }
     }
     }
 
 
-    //
-    pub fn diff_scope(&mut self, id: ScopeId) -> Result<()> {
-        let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?;
-        let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
-        self.diff_node(old, new);
-        Ok(())
+    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 });
+        }
     }
     }
 
 
-    // Diff the `old` node with the `new` node. Emits instructions to modify a
-    // physical DOM node that reflects `old` into something that reflects `new`.
-    //
-    // the real stack should be what it is coming in and out of this function (ideally empty)
-    //
-    // each function call assumes the stack is fresh (empty).
-    pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
-        match (&old_node.kind, &new_node.kind) {
-            // Handle the "sane" cases first.
-            // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
-            // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
-            (VNodeKind::Text(old), VNodeKind::Text(new)) => {
-                let root = old_node.direct_id();
-
-                if old.text != new.text {
-                    self.edit_push_root(root);
-                    self.edit_set_text(new.text);
-                    self.edit_pop();
+    /// Progress the diffing for this "fiber"
+    ///
+    /// This method implements a depth-first iterative tree traversal.
+    ///
+    /// 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 {
+        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();
                 }
                 }
 
 
-                new.dom_id.set(Some(root));
-            }
+                DiffInstruction::DiffNode { old, new, .. } => self.diff_node(old, new),
 
 
-            (VNodeKind::Element(old), VNodeKind::Element(new)) => {
-                let root = old_node.direct_id();
+                DiffInstruction::DiffChildren { old, new } => self.diff_children(old, new),
 
 
-                // 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
-                //
-                // This case is rather rare (typically only in non-keyed lists)
-                if new.tag_name != old.tag_name || new.namespace != old.namespace {
-                    self.replace_node_with_node(root, old_node, new_node);
-                    return;
-                }
+                DiffInstruction::Create { node } => self.create_node(node),
 
 
-                new.dom_id.set(Some(root));
+                DiffInstruction::Mount { and } => self.mount(and),
 
 
-                // 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() });
-                    }
-                };
-
-                // Diff Attributes
-                //
-                // It's extraordinarily rare to have the number/order of attributes change
-                // In these cases, we just completely erase the old set and make a new set
-                //
-                // TODO: take a more efficient path than this
-                if old.attributes.len() == new.attributes.len() {
-                    for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
-                        if old_attr.value != new_attr.value {
-                            please_commit(&mut self.edits.edits);
-                            self.edit_set_attribute(new_attr);
-                        }
-                    }
-                } else {
-                    // TODO: provide some sort of report on how "good" the diffing was
-                    please_commit(&mut self.edits.edits);
-                    for attribute in old.attributes {
-                        self.edit_remove_attribute(attribute);
-                    }
-                    for attribute in new.attributes {
-                        self.edit_set_attribute(attribute)
-                    }
-                }
+                DiffInstruction::PrepareMoveNode { node } => self.prepare_move_node(node),
+            };
 
 
-                // Diff listeners
-                //
-                // It's extraordinarily rare to have the number/order of listeners change
-                // In the cases where the listeners change, we completely wipe the data attributes and add new ones
-                //
-                // 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
-                let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone();
-                if old.listeners.len() == new.listeners.len() {
-                    for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
-                        if old_l.event != new_l.event {
-                            please_commit(&mut self.edits.edits);
-                            self.edit_remove_event_listener(old_l.event);
-                            self.edit_new_event_listener(new_l, cur_scope);
-                        }
-                        new_l.mounted_node.set(old_l.mounted_node.get());
-                        self.fix_listener(new_l);
-                    }
-                } else {
-                    please_commit(&mut self.edits.edits);
-                    for listener in old.listeners {
-                        self.edit_remove_event_listener(listener.event);
-                    }
-                    for listener in new.listeners {
-                        listener.mounted_node.set(Some(root));
-                        self.edit_new_event_listener(listener, cur_scope);
+            if deadline_expired() {
+                return false;
+            }
+        }
 
 
-                        // Make sure the listener gets attached to the scope list
-                        self.fix_listener(listener);
-                    }
-                }
+        true
+    }
 
 
-                if has_comitted {
-                    self.edit_pop();
-                }
+    fn prepare_move_node(&mut self, node: &'bump VNode<'bump>) {
+        for el in RealChildIterator::new(node, self.vdom) {
+            self.mutations.push_root(el.mounted_id());
+            self.stack.add_child_count(1);
+        }
+    }
 
 
-                self.diff_children(old.children, new.children);
+    fn mount(&mut self, and: MountType<'bump>) {
+        let nodes_created = self.stack.pop_nodes_created();
+        match and {
+            // add the nodes from this virtual list to the parent
+            // used by fragments and components
+            MountType::Absorb => {
+                self.stack.add_child_count(nodes_created);
+            }
+            MountType::Append => {
+                self.mutations.edits.push(AppendChildren {
+                    many: nodes_created as u32,
+                });
             }
             }
 
 
-            (VNodeKind::Component(old), VNodeKind::Component(new)) => {
-                let scope_addr = old.ass_scope.get().unwrap();
+            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);
+            }
 
 
-                // Make sure we're dealing with the same component (by function pointer)
-                if old.user_fc == new.user_fc {
-                    //
-                    self.scope_stack.push(scope_addr);
+            MountType::ReplaceByElementId { el: old } => {
+                self.mutations.replace_with(old, nodes_created as u32);
+            }
 
 
-                    // Make sure the new component vnode is referencing the right scope id
-                    new.ass_scope.set(Some(scope_addr));
+            MountType::InsertAfter { other_node } => {
+                let root = self.find_last_element(other_node).unwrap();
+                self.mutations.insert_after(root, nodes_created as u32);
+            }
 
 
-                    // make sure the component's caller function is up to date
-                    let scope = self.get_scope_mut(&scope_addr).unwrap();
+            MountType::InsertBefore { other_node } => {
+                let root = self.find_first_element_id(other_node).unwrap();
+                self.mutations.insert_before(root, nodes_created as u32);
+            }
+        }
+    }
 
 
-                    scope
-                        .update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
+    // =================================
+    //  Tools for creating new nodes
+    // =================================
+
+    fn create_node(&mut self, node: &'bump VNode<'bump>) {
+        match node {
+            VNode::Text(vtext) => self.create_text_node(vtext),
+            VNode::Suspended(suspended) => self.create_suspended_node(suspended),
+            VNode::Anchor(anchor) => self.create_anchor_node(anchor),
+            VNode::Element(element) => self.create_element_node(element),
+            VNode::Fragment(frag) => self.create_fragment_node(frag),
+            VNode::Component(component) => self.create_component_node(component),
+        }
+    }
 
 
-                    // React doesn't automatically memoize, but we do.
-                    let compare = old.comparator.unwrap();
+    fn create_text_node(&mut self, vtext: &'bump VText<'bump>) {
+        let real_id = self.vdom.reserve_node();
+        self.mutations.create_text_node(vtext.text, real_id);
+        vtext.dom_id.set(Some(real_id));
+        self.stack.add_child_count(1);
+    }
 
 
-                    match compare(new) {
-                        true => {
-                            // the props are the same...
-                        }
-                        false => {
-                            // the props are different...
-                            scope.run_scope().unwrap();
-                            self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
-                        }
-                    }
+    fn create_suspended_node(&mut self, suspended: &'bump VSuspended) {
+        let real_id = self.vdom.reserve_node();
+        self.mutations.create_placeholder(real_id);
+        suspended.dom_id.set(Some(real_id));
+        self.stack.add_child_count(1);
+    }
 
 
-                    self.scope_stack.pop();
+    fn create_anchor_node(&mut self, anchor: &'bump VAnchor) {
+        let real_id = self.vdom.reserve_node();
+        self.mutations.create_placeholder(real_id);
+        anchor.dom_id.set(Some(real_id));
+        self.stack.add_child_count(1);
+    }
 
 
-                    self.seen_scopes.insert(scope_addr);
-                } else {
-                    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.edit_push_root(to_remove.direct_id());
-                        self.edit_remove();
-                    }
+    fn create_element_node(&mut self, element: &'bump VElement<'bump>) {
+        let VElement {
+            tag_name,
+            listeners,
+            attributes,
+            children,
+            namespace,
+            dom_id,
+            ..
+        } = element;
 
 
-                    // seems like we could combine this into a single instruction....
-                    self.replace_node_with_node(first.direct_id(), old_node, new_node);
+        let real_id = self.vdom.reserve_node();
+        dom_id.set(Some(real_id));
 
 
-                    // Wipe the old one and plant the new one
-                    let old_scope = old.ass_scope.get().unwrap();
-                    self.destroy_scopes(old_scope);
-                }
-            }
+        self.mutations.create_element(tag_name, *namespace, real_id);
 
 
-            (VNodeKind::Fragment(old), VNodeKind::Fragment(new)) => {
-                // This is the case where options or direct vnodes might be used.
-                // In this case, it's faster to just skip ahead to their diff
-                if old.children.len() == 1 && new.children.len() == 1 {
-                    self.diff_node(&old.children[0], &new.children[0]);
-                    return;
-                }
+        self.stack.add_child_count(1);
 
 
-                self.diff_children(old.children, new.children);
-            }
+        if let Some(cur_scope_id) = self.stack.current_scope() {
+            let scope = self.vdom.get_scope(cur_scope_id).unwrap();
 
 
-            (VNodeKind::Anchor(old), VNodeKind::Anchor(new)) => {
-                new.dom_id.set(old.dom_id.get());
-            }
+            listeners.iter().for_each(|listener| {
+                self.attach_listener_to_scope(listener, scope);
+                listener.mounted_node.set(Some(real_id));
+                self.mutations
+                    .new_event_listener(listener, cur_scope_id.clone());
+            });
+        } else {
+            log::warn!("create element called with no scope on the stack - this is an error for a live dom");
+        }
 
 
-            // The strategy here is to pick the first possible node from the previous set and use that as our replace with root
-            //
-            // We also walk the "real node" list to make sure all latent roots are claened up
-            // This covers the case any time a fragment or component shows up with pretty much anything else
-            //
-            // This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases
-            // are pretty rare.  IE replacing a list (component or fragment) with a single node.
-            (
-                VNodeKind::Component(_)
-                | VNodeKind::Fragment(_)
-                | VNodeKind::Text(_)
-                | VNodeKind::Element(_)
-                | VNodeKind::Anchor(_),
-                VNodeKind::Component(_)
-                | VNodeKind::Fragment(_)
-                | VNodeKind::Text(_)
-                | VNodeKind::Element(_)
-                | VNodeKind::Anchor(_),
-            ) => {
-                self.replace_and_create_many_with_many([old_node], [new_node]);
-            }
+        for attr in *attributes {
+            self.mutations.set_attribute(attr);
+        }
 
 
-            // TODO
-            (VNodeKind::Suspended(old), new) => {
-                //
-                self.replace_and_create_many_with_many([old_node], [new_node]);
-            }
-            // a node that was once real is now suspended
-            (old, VNodeKind::Suspended(_)) => {
-                //
-                self.replace_and_create_many_with_many([old_node], [new_node]);
-            }
+        if children.len() > 0 {
+            self.stack.create_children(children, MountType::Append);
         }
         }
     }
     }
 
 
-    // Emit instructions to create the given virtual node.
-    //
-    // The change list stack may have any shape upon entering this function:
-    //
-    //     [...]
-    //
-    // When this function returns, the new node is on top of the change list stack:
-    //
-    //     [... node]
-    pub fn create_vnode(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
-        match &node.kind {
-            VNodeKind::Text(text) => {
-                let real_id = self.vdom.reserve_node();
-                self.edit_create_text_node(text.text, real_id);
-                text.dom_id.set(Some(real_id));
-                CreateMeta::new(text.is_static, 1)
-            }
-
-            VNodeKind::Anchor(anchor) => {
-                let real_id = self.vdom.reserve_node();
-                self.edit_create_placeholder(real_id);
-                anchor.dom_id.set(Some(real_id));
-                CreateMeta::new(false, 1)
-            }
-
-            VNodeKind::Element(el) => {
-                // we have the potential to completely eliminate working on this node in the future(!)
-                //
-                // This can only be done if all of the elements properties (attrs, children, listeners, etc) are static
-                // While creating these things, keep track if we can memoize this element.
-                // At the end, we'll set this flag on the element to skip it
-                let mut is_static: bool = true;
-
-                let VElement {
-                    tag_name,
-                    listeners,
-                    attributes,
-                    children,
-                    namespace,
-                    static_attrs: _,
-                    static_children: _,
-                    static_listeners: _,
-                    dom_id,
-                } = el;
-
-                let real_id = self.vdom.reserve_node();
-                self.edit_create_element(tag_name, *namespace, real_id);
-                dom_id.set(Some(real_id));
-
-                let cur_scope = self.current_scope().unwrap();
-
-                listeners.iter().for_each(|listener| {
-                    self.fix_listener(listener);
-                    listener.mounted_node.set(Some(real_id));
-                    self.edit_new_event_listener(listener, cur_scope.clone());
-
-                    // if the node has an event listener, then it must be visited ?
-                    is_static = false;
-                });
+    fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) {
+        self.stack.create_children(frag.children, MountType::Absorb);
+    }
 
 
-                for attr in *attributes {
-                    is_static = is_static && attr.is_static;
-                    self.edit_set_attribute(attr);
-                }
+    fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
+        let caller = vcomponent.caller.clone();
+
+        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| {
+            let height = parent_scope.height + 1;
+            Scope::new(
+                caller,
+                new_idx,
+                Some(parent_idx),
+                height,
+                ScopeChildren(vcomponent.children),
+                shared,
+            )
+        });
 
 
-                // Fast path: if there is a single text child, it is faster to
-                // create-and-append the text node all at once via setting the
-                // parent's `textContent` in a single change list instruction than
-                // to emit three instructions to (1) create a text node, (2) set its
-                // text content, and finally (3) append the text node to this
-                // parent.
-                //
-                // Notice: this is a web-specific optimization and may be changed in the future
-                //
-                // TODO move over
-                // if children.len() == 1 {
-                //     if let VNodeKind::Text(text) = &children[0].kind {
-                //         self.set_text(text.text);
-                //         return CreateMeta::new(is_static, 1);
-                //     }
-                // }
-
-                for child in *children {
-                    let child_meta = self.create_vnode(child);
-                    is_static = is_static && child_meta.is_static;
-
-                    // append whatever children were generated by this call
-                    self.edit_append_children(child_meta.added_to_stack);
-                }
+        // Actually initialize the caller's slot with the right address
+        vcomponent.associated_scope.set(Some(new_idx));
 
 
-                CreateMeta::new(is_static, 1)
-            }
+        if !vcomponent.can_memoize {
+            let cur_scope = self.vdom.get_scope_mut(parent_idx).unwrap();
+            let extended = vcomponent as *const VComponent;
+            let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) };
+            cur_scope.borrowed_props.borrow_mut().push(extended);
+        }
 
 
-            VNodeKind::Component(vcomponent) => {
-                let caller = vcomponent.caller.clone();
-
-                let parent_idx = self.scope_stack.last().unwrap().clone();
-
-                // Insert a new scope into our component list
-                let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
-                    let parent_scope = self.get_scope(&parent_idx).unwrap();
-                    let height = parent_scope.height + 1;
-                    Scope::new(
-                        caller,
-                        new_idx,
-                        Some(parent_idx),
-                        height,
-                        ScopeChildren(vcomponent.children),
-                        self.vdom.clone(),
-                    )
-                });
+        // TODO:
+        //  add noderefs to current noderef list Noderefs
+        //  add effects to current effect list Effects
 
 
-                // Actually initialize the caller's slot with the right address
-                vcomponent.ass_scope.set(Some(new_idx));
+        let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
 
 
-                if !vcomponent.can_memoize {
-                    let cur_scope = self.get_scope_mut(&parent_idx).unwrap();
-                    let extended = *vcomponent as *const VComponent;
-                    let extended: *const VComponent<'static> =
-                        unsafe { std::mem::transmute(extended) };
-                    cur_scope.borrowed_props.borrow_mut().push(extended);
-                }
+        // Run the scope for one iteration to initialize it
+        if new_component.run_scope(self.vdom) {
+            // Take the node that was just generated from running the component
+            let nextnode = new_component.frames.fin_head();
+            self.stack.create_component(new_idx, nextnode);
+        }
 
 
-                // TODO:
-                //  add noderefs to current noderef list Noderefs
-                //  add effects to current effect list Effects
+        // Finally, insert this scope as a seen node.
+        self.seen_scopes.insert(new_idx);
+    }
 
 
-                let new_component = self.get_scope_mut(&new_idx).unwrap();
+    // =================================
+    //  Tools for diffing nodes
+    // =================================
 
 
-                // Run the scope for one iteration to initialize it
-                match new_component.run_scope() {
-                    Ok(_) => {
-                        // all good, new nodes exist
-                    }
-                    Err(err) => {
-                        // failed to run. this is the first time the component ran, and it failed
-                        // we manually set its head node to an empty fragment
-                        panic!("failing components not yet implemented");
-                    }
-                }
+    pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
+        use VNode::*;
+        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),
+            (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()),
+            (Element(old), Element(new)) => self.diff_element_nodes(old, new),
+
+            // Anything else is just a basic replace and create
+            (
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_),
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_),
+            ) => self
+                .stack
+                .create_node(new_node, MountType::Replace { old: old_node }),
+        }
+    }
 
 
-                // Take the node that was just generated from running the component
-                let nextnode = new_component.frames.fin_head();
+    fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
+        let root = old.dom_id.get().unwrap();
 
 
-                // Push the new scope onto the stack
-                self.scope_stack.push(new_idx);
+        if old.text != new.text {
+            self.mutations.push_root(root);
+            self.mutations.set_text(new.text);
+            self.mutations.pop();
+        }
 
 
-                // Run the creation algorithm with this scope on the stack
-                let meta = self.create_vnode(nextnode);
+        new.dom_id.set(Some(root));
+    }
 
 
-                // pop the scope off the stack
-                self.scope_stack.pop();
+    fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
+        let root = old.dom_id.get().unwrap();
 
 
-                if meta.added_to_stack == 0 {
-                    panic!("Components should *always* generate nodes - even if they fail");
-                }
+        // 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
+        //
+        // This case is rather rare (typically only in non-keyed lists)
+        if new.tag_name != old.tag_name || new.namespace != old.namespace {
+            // maybe make this an instruction?
+            // issue is that we need the "vnode" but this method only has the velement
+            self.stack.push_nodes_created(0);
+            self.stack.push(DiffInstruction::Mount {
+                and: MountType::ReplaceByElementId {
+                    el: old.dom_id.get().unwrap(),
+                },
+            });
+            self.create_element_node(new);
+            return;
+        }
 
 
-                // Finally, insert this scope as a seen node.
-                self.seen_scopes.insert(new_idx);
+        new.dom_id.set(Some(root));
 
 
-                CreateMeta::new(vcomponent.is_static, meta.added_to_stack)
+        // 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() });
             }
             }
+        };
 
 
-            // Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators).
-            // We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them.
-            // Fragments will just put all their nodes onto the stack after creation
-            VNodeKind::Fragment(frag) => self.create_children(frag.children),
-
-            VNodeKind::Suspended(VSuspended { node: real_node }) => {
-                let id = self.vdom.reserve_node();
-                self.edit_create_placeholder(id);
-                real_node.set(Some(id));
-                CreateMeta::new(false, 1)
+        // Diff Attributes
+        //
+        // It's extraordinarily rare to have the number/order of attributes change
+        // In these cases, we just completely erase the old set and make a new set
+        //
+        // TODO: take a more efficient path than this
+        if old.attributes.len() == new.attributes.len() {
+            for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
+                if old_attr.value != new_attr.value {
+                    please_commit(&mut self.mutations.edits);
+                    self.mutations.set_attribute(new_attr);
+                }
+            }
+        } 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);
+            }
+            for attribute in new.attributes {
+                self.mutations.set_attribute(attribute)
             }
             }
         }
         }
-    }
 
 
-    fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta {
-        let mut is_static = true;
-        let mut added_to_stack = 0;
+        // Diff listeners
+        //
+        // It's extraordinarily rare to have the number/order of listeners change
+        // In the cases where the listeners change, we completely wipe the data attributes and add new ones
+        //
+        // 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();
 
 
-        // add them backwards
-        for child in children.iter().rev() {
-            let child_meta = self.create_vnode(child);
-            is_static = is_static && child_meta.is_static;
-            added_to_stack += child_meta.added_to_stack;
+            if old.listeners.len() == new.listeners.len() {
+                for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
+                    if old_l.event != new_l.event {
+                        please_commit(&mut self.mutations.edits);
+                        self.mutations.remove_event_listener(old_l.event);
+                        self.mutations.new_event_listener(new_l, cur_scope_id);
+                    }
+                    new_l.mounted_node.set(old_l.mounted_node.get());
+                    self.attach_listener_to_scope(new_l, scope);
+                }
+            } else {
+                please_commit(&mut self.mutations.edits);
+                for listener in old.listeners {
+                    self.mutations.remove_event_listener(listener.event);
+                }
+                for listener in new.listeners {
+                    listener.mounted_node.set(Some(root));
+                    self.mutations.new_event_listener(listener, cur_scope_id);
+                    self.attach_listener_to_scope(listener, scope);
+                }
+            }
         }
         }
 
 
-        CreateMeta {
-            is_static,
-            added_to_stack,
+        if has_comitted {
+            self.mutations.pop();
         }
         }
+
+        self.diff_children(old.children, new.children);
     }
     }
 
 
-    /// 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];
+    fn diff_component_nodes(
+        &mut self,
+        old: &'bump VComponent<'bump>,
+        new: &'bump VComponent<'bump>,
+    ) {
+        let scope_addr = old.associated_scope.get().unwrap();
 
 
-        // 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.get_scope(&scope_id).unwrap();
-            for child in scope.descendents.borrow().iter() {
-                // Add this node to be explored
-                scopes_to_explore.push(child.clone());
+        // Make sure we're dealing with the same component (by function pointer)
+        if old.user_fc == new.user_fc {
+            //
+            self.stack.scope_stack.push(scope_addr);
 
 
-                // Also add it for deletion
-                nodes_to_delete.push(child.clone());
+            // Make sure the new component vnode is referencing the right scope id
+            new.associated_scope.set(Some(scope_addr));
+
+            // make sure the component's caller function is up to date
+            let scope = self.vdom.get_scope_mut(scope_addr).unwrap();
+
+            scope.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
+
+            // React doesn't automatically memoize, but we do.
+            let compare = old.comparator.unwrap();
+
+            match compare(new) {
+                true => {
+                    // the props are the same...
+                }
+                false => {
+                    // the props are different...
+                    if scope.run_scope(self.vdom) {
+                        self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
+                    }
+                }
             }
             }
+
+            self.stack.scope_stack.pop();
+
+            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);
         }
         }
+    }
 
 
-        // 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 diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) {
+        // This is the case where options or direct vnodes might be used.
+        // In this case, it's faster to just skip ahead to their diff
+        if old.children.len() == 1 && new.children.len() == 1 {
+            self.diff_node(&old.children[0], &new.children[0]);
+            return;
         }
         }
+
+        self.diff_children(old.children, new.children);
     }
     }
 
 
+    // =============================================
+    //  Utilites for creating new diff instructions
+    // =============================================
+
     // Diff the given set of old and new children.
     // Diff the given set of old and new children.
     //
     //
     // The parent must be on top of the change list stack when this function is
     // The parent must be on top of the change list stack when this function is
@@ -580,76 +589,82 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
     // If old no anchors are provided, then it's assumed that we can freely append to the parent.
     // If old no anchors are provided, then it's assumed that we can freely append to the parent.
     //
     //
     // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
     // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
+    //
+    // Frament nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
+    // to an element, and appending makes sense.
     fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
     fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
-        const IS_EMPTY: bool = true;
-        const IS_NOT_EMPTY: bool = false;
-
-        match (old.is_empty(), new.is_empty()) {
-            (IS_EMPTY, IS_EMPTY) => {}
-
-            // Completely adding new nodes, removing any placeholder if it exists
-            (IS_EMPTY, IS_NOT_EMPTY) => {
-                let meta = self.create_children(new);
-                self.edit_append_children(meta.added_to_stack);
+        // Remember, fragments can never be empty (they always have a single child)
+        match (old, new) {
+            ([], []) => {}
+            ([], _) => {
+                self.stack.create_children(new, MountType::Append);
             }
             }
-
-            // Completely removing old nodes and putting an anchor in its place
-            // no anchor (old has nodes) and the new is empty
-            // remove all the old nodes
-            (IS_NOT_EMPTY, IS_EMPTY) => {
+            (_, []) => {
                 for node in old {
                 for node in old {
-                    self.remove_vnode(node);
+                    self.remove_nodes(Some(node));
                 }
                 }
             }
             }
+            ([VNode::Anchor(old_anchor)], [VNode::Anchor(new_anchor)]) => {
+                old_anchor.dom_id.set(new_anchor.dom_id.get());
+            }
+            ([VNode::Anchor(anchor)], _) => {
+                let el = anchor.dom_id.get().unwrap();
+                self.stack
+                    .create_children(new, MountType::ReplaceByElementId { el });
+            }
+            (_, [VNode::Anchor(_)]) => {
+                self.replace_and_create_many_with_one(old, &new[0]);
+            }
+            _ => {
+                let new_is_keyed = new[0].key().is_some();
+                let old_is_keyed = old[0].key().is_some();
 
 
-            (IS_NOT_EMPTY, IS_NOT_EMPTY) => {
-                let first_old = &old[0];
-                let first_new = &new[0];
+                debug_assert!(
+                    new.iter().all(|n| n.key().is_some() == new_is_keyed),
+                    "all siblings must be keyed or all siblings must be non-keyed"
+                );
+                debug_assert!(
+                    old.iter().all(|o| o.key().is_some() == old_is_keyed),
+                    "all siblings must be keyed or all siblings must be non-keyed"
+                );
 
 
-                match (&first_old.kind, &first_new.kind) {
-                    // Anchors can only appear in empty fragments
-                    (VNodeKind::Anchor(old_anchor), VNodeKind::Anchor(new_anchor)) => {
-                        old_anchor.dom_id.set(new_anchor.dom_id.get());
-                    }
+                if new_is_keyed && old_is_keyed {
+                    self.diff_keyed_children(old, new);
+                } else {
+                    self.diff_non_keyed_children(old, new);
+                }
+            }
+        }
+    }
 
 
-                    // Replace the anchor with whatever new nodes are coming down the pipe
-                    (VNodeKind::Anchor(anchor), _) => {
-                        self.edit_push_root(anchor.dom_id.get().unwrap());
-                        let mut added = 0;
-                        for el in new {
-                            let meta = self.create_vnode(el);
-                            added += meta.added_to_stack;
-                        }
-                        self.edit_replace_with(1, added);
-                    }
+    // Diff children that are not keyed.
+    //
+    // The parent must be on the top of the change list stack when entering this
+    // function:
+    //
+    //     [... parent]
+    //
+    // the change list stack is in the same state when this function returns.
+    fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+        // Handled these cases in `diff_children` before calling this function.
+        log::debug!("diffing non-keyed case");
+        debug_assert!(!new.is_empty());
+        debug_assert!(!old.is_empty());
 
 
-                    // Replace whatever nodes are sitting there with the anchor
-                    (_, VNodeKind::Anchor(anchor)) => {
-                        self.replace_and_create_many_with_many(old, [first_new]);
-                    }
+        for (new, old) in new.iter().zip(old.iter()).rev() {
+            self.stack.push(DiffInstruction::DiffNode { new, old });
+        }
 
 
-                    // Use the complex diff algorithm to diff the nodes
-                    _ => {
-                        let new_is_keyed = new[0].key.is_some();
-                        let old_is_keyed = old[0].key.is_some();
-
-                        debug_assert!(
-                            new.iter().all(|n| n.key.is_some() == new_is_keyed),
-                            "all siblings must be keyed or all siblings must be non-keyed"
-                        );
-                        debug_assert!(
-                            old.iter().all(|o| o.key.is_some() == old_is_keyed),
-                            "all siblings must be keyed or all siblings must be non-keyed"
-                        );
-
-                        if new_is_keyed && old_is_keyed {
-                            self.diff_keyed_children(old, new);
-                        } else {
-                            self.diff_non_keyed_children(old, new);
-                        }
-                    }
-                }
-            }
+        if old.len() > new.len() {
+            self.remove_nodes(&old[new.len()..]);
+        } else if new.len() > old.len() {
+            log::debug!("Calling create children on array differences");
+            self.stack.create_children(
+                &new[old.len()..],
+                MountType::InsertAfter {
+                    other_node: old.last().unwrap(),
+                },
+            );
         }
         }
     }
     }
 
 
@@ -675,7 +690,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
             let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
             let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
                 keys.clear();
                 keys.clear();
                 for child in children {
                 for child in children {
-                    let key = child.key;
+                    let key = child.key();
                     debug_assert!(
                     debug_assert!(
                         key.is_some(),
                         key.is_some(),
                         "if any sibling is keyed, all siblings must be keyed"
                         "if any sibling is keyed, all siblings must be keyed"
@@ -697,106 +712,77 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
         //
         //
         // `shared_prefix_count` is the count of how many nodes at the start of
         // `shared_prefix_count` is the count of how many nodes at the start of
         // `new` and `old` share the same keys.
         // `new` and `old` share the same keys.
-        //
-        // TODO: just inline this
-        let shared_prefix_count = match self.diff_keyed_prefix(old, new) {
-            KeyedPrefixResult::Finished => return,
-            KeyedPrefixResult::MoreWorkToDo(count) => count,
+        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
+            Some(count) => count,
+            None => return,
         };
         };
-
-        // Next, we find out how many of the nodes at the end of the children have
-        // the same key. We do _not_ diff them yet, since we want to emit the change
-        // list instructions such that they can be applied in a single pass over the
-        // DOM. Instead, we just save this information for later.
-        //
-        // `shared_suffix_count` is the count of how many nodes at the end of `new`
-        // and `old` share the same keys.
-        let shared_suffix_count = old[shared_prefix_count..]
-            .iter()
-            .rev()
-            .zip(new[shared_prefix_count..].iter().rev())
-            .take_while(|&(old, new)| old.key == new.key)
-            .count();
-
-        let old_shared_suffix_start = old.len() - shared_suffix_count;
-        let new_shared_suffix_start = new.len() - shared_suffix_count;
+        log::debug!(
+            "Left offset, right offset, {}, {}",
+            left_offset,
+            right_offset,
+        );
 
 
         // Ok, we now hopefully have a smaller range of children in the middle
         // Ok, we now hopefully have a smaller range of children in the middle
         // within which to re-order nodes with the same keys, remove old nodes with
         // within which to re-order nodes with the same keys, remove old nodes with
         // now-unused keys, and create new nodes with fresh keys.
         // now-unused keys, and create new nodes with fresh keys.
         self.diff_keyed_middle(
         self.diff_keyed_middle(
-            &old[shared_prefix_count..old_shared_suffix_start],
-            &new[shared_prefix_count..new_shared_suffix_start],
-            shared_prefix_count,
-            shared_suffix_count,
-            old_shared_suffix_start,
+            &old[left_offset..(old.len() - right_offset)],
+            &new[left_offset..(new.len() - right_offset)],
         );
         );
-
-        // Finally, diff the nodes at the end of `old` and `new` that share keys.
-        let old_suffix = &old[old_shared_suffix_start..];
-        let new_suffix = &new[new_shared_suffix_start..];
-        debug_assert_eq!(old_suffix.len(), new_suffix.len());
-        if !old_suffix.is_empty() {
-            self.diff_keyed_suffix(old_suffix, new_suffix, new_shared_suffix_start)
-        }
     }
     }
 
 
-    // Diff the prefix of children in `new` and `old` that share the same keys in
-    // the same order.
-    //
-    // The stack is empty upon entry.
-    fn diff_keyed_prefix(
+    /// Diff both ends of the children that share keys.
+    ///
+    /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
+    ///
+    /// If there is no offset, then this function returns None and the diffing is complete.
+    fn diff_keyed_ends(
         &mut self,
         &mut self,
         old: &'bump [VNode<'bump>],
         old: &'bump [VNode<'bump>],
         new: &'bump [VNode<'bump>],
         new: &'bump [VNode<'bump>],
-    ) -> KeyedPrefixResult {
-        let mut shared_prefix_count = 0;
+    ) -> Option<(usize, usize)> {
+        let mut left_offset = 0;
 
 
         for (old, new) in old.iter().zip(new.iter()) {
         for (old, new) in old.iter().zip(new.iter()) {
             // abort early if we finally run into nodes with different keys
             // abort early if we finally run into nodes with different keys
             if old.key() != new.key() {
             if old.key() != new.key() {
                 break;
                 break;
             }
             }
-            self.diff_node(old, new);
-            shared_prefix_count += 1;
+            self.stack.push(DiffInstruction::DiffNode { old, new });
+            left_offset += 1;
         }
         }
 
 
         // If that was all of the old children, then create and append the remaining
         // If that was all of the old children, then create and append the remaining
         // new children and we're finished.
         // new children and we're finished.
-        if shared_prefix_count == old.len() {
-            // Load the last element
-            let last_node = self.find_last_element(new.last().unwrap()).direct_id();
-            self.edit_push_root(last_node);
-
-            // Create the new children and insert them after
-            let meta = self.create_children(&new[shared_prefix_count..]);
-            self.edit_insert_after(meta.added_to_stack);
-
-            return KeyedPrefixResult::Finished;
+        if left_offset == old.len() {
+            self.stack.create_children(
+                &new[left_offset..],
+                MountType::InsertAfter {
+                    other_node: old.last().unwrap(),
+                },
+            );
+            return None;
         }
         }
 
 
         // And if that was all of the new children, then remove all of the remaining
         // And if that was all of the new children, then remove all of the remaining
         // old children and we're finished.
         // old children and we're finished.
-        if shared_prefix_count == new.len() {
-            self.remove_children(&old[shared_prefix_count..]);
-            return KeyedPrefixResult::Finished;
+        if left_offset == new.len() {
+            self.remove_nodes(&old[left_offset..]);
+            return None;
         }
         }
 
 
-        KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
-    }
-
-    // Create the given children and append them to the parent node.
-    //
-    // The parent node must currently be on top of the change list stack:
-    //
-    //     [... parent]
-    //
-    // When this function returns, the change list stack is in the same state.
-    pub fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) {
-        for child in new {
-            let meta = self.create_vnode(child);
-            self.edit_append_children(meta.added_to_stack);
+        // if the shared prefix is less than either length, then we need to walk backwards
+        let mut right_offset = 0;
+        for (old, new) in old.iter().rev().zip(new.iter().rev()) {
+            // abort early if we finally run into nodes with different keys
+            if old.key() != new.key() {
+                break;
+            }
+            self.diff_node(old, new);
+            right_offset += 1;
         }
         }
+
+        Some((left_offset, right_offset))
     }
     }
 
 
     // The most-general, expensive code path for keyed children diffing.
     // The most-general, expensive code path for keyed children diffing.
@@ -812,455 +798,284 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
     // This function will load the appropriate nodes onto the stack and do diffing in place.
     // This function will load the appropriate nodes onto the stack and do diffing in place.
     //
     //
     // Upon exit from this function, it will be restored to that same state.
     // Upon exit from this function, it will be restored to that same state.
-    fn diff_keyed_middle(
-        &mut self,
-        old: &'bump [VNode<'bump>],
-        mut new: &'bump [VNode<'bump>],
-        shared_prefix_count: usize,
-        shared_suffix_count: usize,
-        old_shared_suffix_start: usize,
-    ) {
+    fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+        /*
+        1. Map the old keys into a numerical ordering based on indicies.
+        2. Create a map of old key to its index
+        3. Map each new key to the old key, carrying over the old index.
+            - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
+            - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
+
+        now, we should have a list of integers that indicates where in the old list the new items map to.
+
+        4. Compute the LIS of this list
+            - this indicates the longest list of new children that won't need to be moved.
+
+        5. Identify which nodes need to be removed
+        6. Identify which nodes will need to be diffed
+
+        7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.
+            - if the item already existed, just move it to the right place.
+
+        8. Finally, generate instructions to remove any old children.
+        9. Generate instructions to finally diff children that are the same between both
+        */
+
+        // 0. Debug sanity checks
         // Should have already diffed the shared-key prefixes and suffixes.
         // Should have already diffed the shared-key prefixes and suffixes.
         debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
         debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
         debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
         debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
 
 
-        // // The algorithm below relies upon using `u32::MAX` as a sentinel
-        // // value, so if we have that many new nodes, it won't work. This
-        // // check is a bit academic (hence only enabled in debug), since
-        // // wasm32 doesn't have enough address space to hold that many nodes
-        // // in memory.
-        // debug_assert!(new.len() < u32::MAX as usize);
-
-        // Map from each `old` node's key to its index within `old`.
+        // 1. Map the old keys into a numerical ordering based on indicies.
+        // 2. Create a map of old key to its index
         // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
         // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
-        let mut old_key_to_old_index = old
+        let old_key_to_old_index = old
             .iter()
             .iter()
             .enumerate()
             .enumerate()
             .map(|(i, o)| (o.key().unwrap(), i))
             .map(|(i, o)| (o.key().unwrap(), i))
             .collect::<FxHashMap<_, _>>();
             .collect::<FxHashMap<_, _>>();
 
 
-        // The set of shared keys between `new` and `old`.
         let mut shared_keys = FxHashSet::default();
         let mut shared_keys = FxHashSet::default();
-        // let mut to_remove = FxHashSet::default();
-        let mut to_add = FxHashSet::default();
 
 
-        // Map from each index in `new` to the index of the node in `old` that
-        // has the same key.
-        let mut new_index_to_old_index = new
+        // 3. Map each new key to the old key, carrying over the old index.
+        let new_index_to_old_index = new
             .iter()
             .iter()
-            .map(|n| {
-                let key = n.key().unwrap();
-                match old_key_to_old_index.get(&key) {
-                    Some(&index) => {
-                        shared_keys.insert(key);
-                        index
-                    }
-                    None => {
-                        //
-                        to_add.insert(key);
-                        u32::MAX as usize
-                    }
+            .map(|node| {
+                let key = node.key().unwrap();
+                if let Some(&index) = old_key_to_old_index.get(&key) {
+                    shared_keys.insert(key);
+                    index
+                } else {
+                    u32::MAX as usize
                 }
                 }
             })
             })
             .collect::<Vec<_>>();
             .collect::<Vec<_>>();
 
 
-        dbg!(&shared_keys);
-        dbg!(&to_add);
-
-        // If none of the old keys are reused by the new children, then we
-        // remove all the remaining old children and create the new children
-        // afresh.
-        if shared_suffix_count == 0 && shared_keys.is_empty() {
+        // If none of the old keys are reused by the new children, then we remove all the remaining old children and
+        // create the new children afresh.
+        if shared_keys.is_empty() {
             self.replace_and_create_many_with_many(old, new);
             self.replace_and_create_many_with_many(old, new);
             return;
             return;
         }
         }
 
 
-        // // Remove any old children whose keys were not reused in the new
-        // // children. Remove from the end first so that we don't mess up indices.
-        // for old_child in old.iter().rev() {
-        //     if !shared_keys.contains(&old_child.key()) {
-        //         self.remove_child(old_child);
-        //     }
-        // }
-
-        // let old_keyds = old.iter().map(|f| f.key()).collect::<Vec<_>>();
-        // let new_keyds = new.iter().map(|f| f.key()).collect::<Vec<_>>();
-        // dbg!(old_keyds);
-        // dbg!(new_keyds);
-
-        // // If there aren't any more new children, then we are done!
-        // if new.is_empty() {
-        //     return;
-        // }
-
-        // The longest increasing subsequence within `new_index_to_old_index`. This
-        // is the longest sequence on DOM nodes in `old` that are relatively ordered
-        // correctly within `new`. We will leave these nodes in place in the DOM,
-        // and only move nodes that are not part of the LIS. This results in the
-        // maximum number of DOM nodes left in place, AKA the minimum number of DOM
-        // nodes moved.
-        let mut new_index_is_in_lis = FxHashSet::default();
-        new_index_is_in_lis.reserve(new_index_to_old_index.len());
+        // 4. Compute the LIS of this list
+        let mut lis_sequence = Vec::default();
+        lis_sequence.reserve(new_index_to_old_index.len());
 
 
         let mut predecessors = vec![0; new_index_to_old_index.len()];
         let mut predecessors = vec![0; new_index_to_old_index.len()];
         let mut starts = vec![0; new_index_to_old_index.len()];
         let mut starts = vec![0; new_index_to_old_index.len()];
 
 
         longest_increasing_subsequence::lis_with(
         longest_increasing_subsequence::lis_with(
             &new_index_to_old_index,
             &new_index_to_old_index,
-            &mut new_index_is_in_lis,
+            &mut lis_sequence,
             |a, b| a < b,
             |a, b| a < b,
             &mut predecessors,
             &mut predecessors,
             &mut starts,
             &mut starts,
         );
         );
 
 
-        dbg!(&new_index_is_in_lis);
-        // use the old nodes to navigate the new nodes
-
-        let mut lis_in_order = new_index_is_in_lis.into_iter().collect::<Vec<_>>();
-        lis_in_order.sort_unstable();
-
-        dbg!(&lis_in_order);
-
-        // we walk front to back, creating the head node
+        // the lis comes out backwards, I think. can't quite tell.
+        lis_sequence.sort_unstable();
 
 
-        // diff the shared, in-place nodes first
-        // this makes sure we can rely on their first/last nodes being correct later on
-        for id in &lis_in_order {
-            let new_node = &new[*id];
-            let key = new_node.key().unwrap();
-            let old_index = old_key_to_old_index.get(&key).unwrap();
-            let old_node = &old[*old_index];
-            self.diff_node(old_node, new_node);
+        // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
+        if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
+            lis_sequence.pop();
         }
         }
 
 
-        // return the old node from the key
-        let load_old_node_from_lsi = |key| -> &VNode {
-            let old_index = old_key_to_old_index.get(key).unwrap();
-            let old_node = &old[*old_index];
-            old_node
+        let apply = |new_idx, new_node: &'bump VNode<'bump>, stack: &mut DiffStack<'bump>| {
+            let old_index = new_index_to_old_index[new_idx];
+            if old_index == u32::MAX as usize {
+                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 {
+                    new: new_node,
+                    old: &old[old_index],
+                });
+            }
         };
         };
 
 
-        let mut root = None;
-        let mut new_iter = new.iter().enumerate();
-        for lis_id in &lis_in_order {
-            eprintln!("tracking {:?}", lis_id);
-            // this is the next milestone node we are working up to
-            let new_anchor = &new[*lis_id];
-            root = Some(new_anchor);
-
-            let anchor_el = self.find_first_element(new_anchor);
-            self.edit_push_root(anchor_el.direct_id());
-            // let mut pushed = false;
-
-            'inner: loop {
-                let (next_id, next_new) = new_iter.next().unwrap();
-                if next_id == *lis_id {
-                    // we've reached the milestone, break this loop so we can step to the next milestone
-                    // remember: we already diffed this node
-                    eprintln!("breaking {:?}", next_id);
-                    break 'inner;
-                } else {
-                    let key = next_new.key().unwrap();
-                    eprintln!("found key {:?}", key);
-                    if shared_keys.contains(&key) {
-                        eprintln!("key is contained {:?}", key);
-                        shared_keys.remove(key);
-                        // diff the two nodes
-                        let old_node = load_old_node_from_lsi(key);
-                        self.diff_node(old_node, next_new);
-
-                        // now move all the nodes into the right spot
-                        for child in RealChildIterator::new(next_new, self.vdom) {
-                            let el = child.direct_id();
-                            self.edit_push_root(el);
-                            self.edit_insert_before(1);
-                        }
-                    } else {
-                        eprintln!("key is not contained {:?}", key);
-                        // new node needs to be created
-                        // insert it before the current milestone
-                        let meta = self.create_vnode(next_new);
-                        self.edit_insert_before(meta.added_to_stack);
-                    }
-                }
+        // add mount instruction for the last items not covered by the lis
+        let first_lis = *lis_sequence.first().unwrap();
+        if first_lis > 0 {
+            self.stack.push_nodes_created(0);
+            self.stack.push(DiffInstruction::Mount {
+                and: MountType::InsertBefore {
+                    other_node: &new[first_lis],
+                },
+            });
+
+            for (idx, new_node) in new[..first_lis].iter().enumerate().rev() {
+                apply(idx, new_node, &mut self.stack);
             }
             }
-
-            self.edit_pop();
         }
         }
 
 
-        let final_lis_node = root.unwrap();
-        let final_el_node = self.find_last_element(final_lis_node);
-        let final_el = final_el_node.direct_id();
-        self.edit_push_root(final_el);
-
-        let mut last_iter = new.iter().rev().enumerate();
-        let last_key = final_lis_node.key().unwrap();
-        loop {
-            let (last_id, last_node) = last_iter.next().unwrap();
-            let key = last_node.key().unwrap();
-
-            eprintln!("checking final nodes {:?}", key);
-
-            if last_key == key {
-                eprintln!("breaking final nodes");
-                break;
-            }
-
-            if shared_keys.contains(&key) {
-                eprintln!("key is contained {:?}", key);
-                shared_keys.remove(key);
-                // diff the two nodes
-                let old_node = load_old_node_from_lsi(key);
-                self.diff_node(old_node, last_node);
-
-                // now move all the nodes into the right spot
-                for child in RealChildIterator::new(last_node, self.vdom) {
-                    let el = child.direct_id();
-                    self.edit_push_root(el);
-                    self.edit_insert_after(1);
+        // for each spacing, generate a mount instruction
+        let mut lis_iter = lis_sequence.iter().rev();
+        let mut last = *lis_iter.next().unwrap();
+        while let Some(&next) = lis_iter.next() {
+            if last - next > 1 {
+                self.stack.push_nodes_created(0);
+                self.stack.push(DiffInstruction::Mount {
+                    and: MountType::InsertBefore {
+                        other_node: &new[last],
+                    },
+                });
+                for (idx, new_node) in new[(next + 1)..last].iter().enumerate().rev() {
+                    apply(idx + next + 1, new_node, &mut self.stack);
                 }
                 }
-            } else {
-                eprintln!("key is not contained {:?}", key);
-                // new node needs to be created
-                // insert it before the current milestone
-                let meta = self.create_vnode(last_node);
-                self.edit_insert_after(meta.added_to_stack);
             }
             }
+            last = next;
         }
         }
-        self.edit_pop();
-    }
-
-    // Diff the suffix of keyed children that share the same keys in the same order.
-    //
-    // The parent must be on the change list stack when we enter this function:
-    //
-    //     [... parent]
-    //
-    // When this function exits, the change list stack remains the same.
-    fn diff_keyed_suffix(
-        &mut self,
-        old: &'bump [VNode<'bump>],
-        new: &'bump [VNode<'bump>],
-        new_shared_suffix_start: usize,
-    ) {
-        debug_assert_eq!(old.len(), new.len());
-        debug_assert!(!old.is_empty());
-
-        for (old_child, new_child) in old.iter().zip(new.iter()) {
-            self.diff_node(old_child, new_child);
-        }
-    }
-
-    // Diff children that are not keyed.
-    //
-    // The parent must be on the top of the change list stack when entering this
-    // function:
-    //
-    //     [... parent]
-    //
-    // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
-        // Handled these cases in `diff_children` before calling this function.
-        //
-        debug_assert!(!new.is_empty());
-        debug_assert!(!old.is_empty());
-
-        match old.len().cmp(&new.len()) {
-            // old.len > new.len -> removing some nodes
-            Ordering::Greater => {
-                // diff them together
-                for (new_child, old_child) in new.iter().zip(old.iter()) {
-                    self.diff_node(old_child, new_child);
-                }
-
-                // todo: we would emit fewer instructions if we just did a replace many
-                // remove whatever is still dangling
-                for item in &old[new.len()..] {
-                    for i in RealChildIterator::new(item, self.vdom) {
-                        self.edit_push_root(i.direct_id());
-                        self.edit_remove();
-                    }
-                }
-            }
-
-            // old.len < new.len -> adding some nodes
-            // this is wrong in the case where we're diffing fragments
-            //
-            // we need to save the last old element and then replace it with all the new ones
-            Ordering::Less => {
-                // Add the new elements to the last old element while it still exists
-                let last = self.find_last_element(old.last().unwrap());
-                self.edit_push_root(last.direct_id());
-
-                // create the rest and insert them
-                let meta = self.create_children(&new[old.len()..]);
-                self.edit_insert_after(meta.added_to_stack);
-
-                self.edit_pop();
-
-                // diff the rest
-                new.iter()
-                    .zip(old.iter())
-                    .for_each(|(new_child, old_child)| self.diff_node(old_child, new_child));
-            }
 
 
-            // old.len == new.len -> no nodes added/removed, but perhaps changed
-            Ordering::Equal => {
-                for (new_child, old_child) in new.iter().zip(old.iter()) {
-                    self.diff_node(old_child, new_child);
-                }
+        // add mount instruction for the first items not covered by the lis
+        let last = *lis_sequence.last().unwrap();
+        if last < (new.len() - 1) {
+            self.stack.push_nodes_created(0);
+            self.stack.push(DiffInstruction::Mount {
+                and: MountType::InsertAfter {
+                    other_node: &new[last],
+                },
+            });
+            for (idx, new_node) in new[(last + 1)..].iter().enumerate().rev() {
+                apply(idx + last + 1, new_node, &mut self.stack);
             }
             }
         }
         }
-    }
 
 
-    // ======================
-    // Support methods
-    // ======================
-    // Remove all of a node's children.
-    //
-    // The change list stack must have this shape upon entry to this function:
-    //
-    //     [... parent]
-    //
-    // When this function returns, the change list stack is in the same state.
-    fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) {
-        // debug_assert!(self.traversal_is_committed());
-        log::debug!("REMOVING CHILDREN");
-        for _child in old {
-            // registry.remove_subtree(child);
+        for idx in lis_sequence.iter().rev() {
+            self.stack.push(DiffInstruction::DiffNode {
+                new: &new[*idx],
+                old: &old[new_index_to_old_index[*idx]],
+            });
         }
         }
-        // Fast way to remove all children: set the node's textContent to an empty
-        // string.
-        todo!()
-        // self.set_inner_text("");
-    }
-    // Remove the current child and all of its following siblings.
-    //
-    // The change list stack must have this shape upon entry to this function:
-    //
-    //     [... parent child]
-    //
-    // After the function returns, the child is no longer on the change list stack:
-    //
-    //     [... parent]
-    fn remove_children(&mut self, old: &'bump [VNode<'bump>]) {
-        self.replace_and_create_many_with_many(old, None)
     }
     }
 
 
-    fn find_last_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> {
+    // =====================
+    //  Utilities
+    // =====================
+
+    fn find_last_element(&mut self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
         let mut search_node = Some(vnode);
         let mut search_node = Some(vnode);
 
 
         loop {
         loop {
-            let node = search_node.take().unwrap();
-            match &node.kind {
-                // the ones that have a direct id
-                VNodeKind::Text(_)
-                | VNodeKind::Element(_)
-                | VNodeKind::Anchor(_)
-                | VNodeKind::Suspended(_) => break node,
+            match &search_node.take().unwrap() {
+                VNode::Text(t) => break t.dom_id.get(),
+                VNode::Element(t) => break t.dom_id.get(),
+                VNode::Suspended(t) => break t.dom_id.get(),
+                VNode::Anchor(t) => break t.dom_id.get(),
 
 
-                VNodeKind::Fragment(frag) => {
+                VNode::Fragment(frag) => {
                     search_node = frag.children.last();
                     search_node = frag.children.last();
                 }
                 }
-                VNodeKind::Component(el) => {
-                    let scope_id = el.ass_scope.get().unwrap();
-                    let scope = self.get_scope(&scope_id).unwrap();
+                VNode::Component(el) => {
+                    let scope_id = el.associated_scope.get().unwrap();
+                    let scope = self.vdom.get_scope(scope_id).unwrap();
                     search_node = Some(scope.root());
                     search_node = Some(scope.root());
                 }
                 }
             }
             }
         }
         }
     }
     }
 
 
-    fn find_first_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> {
+    fn find_first_element_id(&mut self, vnode: &'bump VNode<'bump>) -> Option<ElementId> {
         let mut search_node = Some(vnode);
         let mut search_node = Some(vnode);
 
 
         loop {
         loop {
-            let node = search_node.take().unwrap();
-            match &node.kind {
+            match &search_node.take().unwrap() {
                 // the ones that have a direct id
                 // the ones that have a direct id
-                VNodeKind::Text(_)
-                | VNodeKind::Element(_)
-                | VNodeKind::Anchor(_)
-                | VNodeKind::Suspended(_) => break node,
-
-                VNodeKind::Fragment(frag) => {
+                VNode::Fragment(frag) => {
                     search_node = Some(&frag.children[0]);
                     search_node = Some(&frag.children[0]);
                 }
                 }
-                VNodeKind::Component(el) => {
-                    let scope_id = el.ass_scope.get().unwrap();
-                    let scope = self.get_scope(&scope_id).unwrap();
+                VNode::Component(el) => {
+                    let scope_id = el.associated_scope.get().unwrap();
+                    let scope = self.vdom.get_scope(scope_id).unwrap();
                     search_node = Some(scope.root());
                     search_node = Some(scope.root());
                 }
                 }
+                VNode::Text(t) => break t.dom_id.get(),
+                VNode::Element(t) => break t.dom_id.get(),
+                VNode::Suspended(t) => break t.dom_id.get(),
+                VNode::Anchor(t) => break t.dom_id.get(),
             }
             }
         }
         }
     }
     }
 
 
-    fn remove_child(&mut self, node: &'bump VNode<'bump>) {
-        self.replace_and_create_many_with_many(Some(node), None);
-    }
-
-    /// Remove all the old nodes and replace them with newly created new nodes.
-    ///
-    /// The new nodes *will* be created - don't create them yourself!
-    fn replace_and_create_many_with_many(
+    fn replace_and_create_many_with_one(
         &mut self,
         &mut self,
-        old_nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
-        new_nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
+        old: &'bump [VNode<'bump>],
+        new: &'bump VNode<'bump>,
     ) {
     ) {
-        let mut nodes_to_replace = Vec::new();
-        let mut nodes_to_search = old_nodes.into_iter().collect::<Vec<_>>();
-        let mut scopes_obliterated = Vec::new();
-        while let Some(node) = nodes_to_search.pop() {
-            match &node.kind {
-                // the ones that have a direct id return immediately
-                VNodeKind::Text(el) => nodes_to_replace.push(el.dom_id.get().unwrap()),
-                VNodeKind::Element(el) => nodes_to_replace.push(el.dom_id.get().unwrap()),
-                VNodeKind::Anchor(el) => nodes_to_replace.push(el.dom_id.get().unwrap()),
-                VNodeKind::Suspended(el) => nodes_to_replace.push(el.node.get().unwrap()),
-
-                // Fragments will either have a single anchor or a list of children
-                VNodeKind::Fragment(frag) => {
-                    for child in frag.children {
-                        nodes_to_search.push(child);
-                    }
+        if let Some(first_old) = old.get(0) {
+            self.remove_nodes(&old[1..]);
+            self.stack
+                .create_node(new, MountType::Replace { old: first_old });
+        } else {
+            self.stack.create_node(new, MountType::Append {});
+        }
+    }
+
+    /// schedules nodes for garbage collection and pushes "remove" to the mutation stack
+    /// remove can happen whenever
+    fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'bump VNode<'bump>>) {
+        // or cache the vec on the diff machine
+        for node in nodes {
+            match node {
+                VNode::Text(t) => {
+                    t.dom_id.get().map(|id| {
+                        self.mutations.remove(id.as_u64());
+                        self.vdom.collect_garbage(id);
+                    });
+                }
+                VNode::Suspended(s) => {
+                    s.dom_id.get().map(|id| {
+                        self.mutations.remove(id.as_u64());
+                        self.vdom.collect_garbage(id);
+                    });
+                }
+                VNode::Anchor(a) => {
+                    a.dom_id.get().map(|id| {
+                        self.mutations.remove(id.as_u64());
+                        self.vdom.collect_garbage(id);
+                    });
+                }
+                VNode::Element(e) => {
+                    e.dom_id.get().map(|id| self.mutations.remove(id.as_u64()));
+                }
+                VNode::Fragment(f) => {
+                    self.remove_nodes(f.children);
                 }
                 }
 
 
-                // Components can be any of the nodes above
-                // However, we do need to track which components need to be removed
-                VNodeKind::Component(el) => {
-                    let scope_id = el.ass_scope.get().unwrap();
-                    let scope = self.get_scope(&scope_id).unwrap();
+                VNode::Component(c) => {
+                    let scope_id = c.associated_scope.get().unwrap();
+                    let scope = self.vdom.get_scope(scope_id).unwrap();
                     let root = scope.root();
                     let root = scope.root();
-                    nodes_to_search.push(root);
-                    scopes_obliterated.push(scope_id);
+                    self.remove_nodes(Some(root));
                 }
                 }
             }
             }
-            // TODO: enable internal garabge collection
-            // self.create_garbage(node);
         }
         }
+    }
 
 
-        let n = nodes_to_replace.len();
-        for node in nodes_to_replace {
-            self.edit_push_root(node);
-        }
-
-        let mut nodes_created = 0;
-        for node in new_nodes {
-            let meta = self.create_vnode(node);
-            nodes_created += meta.added_to_stack;
-        }
-
-        // if 0 nodes are created, then it gets interperted as a deletion
-        self.edit_replace_with(n as u32, nodes_created);
-
-        // obliterate!
-        for scope in scopes_obliterated {
-            self.destroy_scopes(scope);
+    /// Remove all the old nodes and replace them with newly created new nodes.
+    ///
+    /// The new nodes *will* be created - don't create them yourself!
+    fn replace_and_create_many_with_many(
+        &mut self,
+        old: &'bump [VNode<'bump>],
+        new: &'bump [VNode<'bump>],
+    ) {
+        if let Some(first_old) = old.get(0) {
+            self.remove_nodes(&old[1..]);
+            self.stack
+                .create_children(new, MountType::Replace { old: first_old })
+        } else {
+            self.stack.create_children(new, MountType::Append {});
         }
         }
     }
     }
 
 
     fn create_garbage(&mut self, node: &'bump VNode<'bump>) {
     fn create_garbage(&mut self, node: &'bump VNode<'bump>) {
-        match self.current_scope().and_then(|id| self.get_scope(&id)) {
+        match self
+            .stack
+            .current_scope()
+            .and_then(|id| self.vdom.get_scope(id))
+        {
             Some(scope) => {
             Some(scope) => {
                 let garbage: &'bump VNode<'static> = unsafe { std::mem::transmute(node) };
                 let garbage: &'bump VNode<'static> = unsafe { std::mem::transmute(node) };
                 scope.pending_garbage.borrow_mut().push(garbage);
                 scope.pending_garbage.borrow_mut().push(garbage);
@@ -1271,366 +1086,43 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
         }
         }
     }
     }
 
 
-    fn immediately_dispose_garabage(&mut self, node: ElementId) {
-        self.vdom.collect_garbage(node)
-    }
-
-    fn replace_node_with_node(
-        &mut self,
-        anchor: ElementId,
-        old_node: &'bump VNode<'bump>,
-        new_node: &'bump VNode<'bump>,
-    ) {
-        self.edit_push_root(anchor);
-        let meta = self.create_vnode(new_node);
-        self.edit_replace_with(1, meta.added_to_stack);
-        self.create_garbage(old_node);
-        self.edit_pop();
-    }
-
-    fn remove_vnode(&mut self, node: &'bump VNode<'bump>) {
-        match &node.kind {
-            VNodeKind::Text(el) => self.immediately_dispose_garabage(node.direct_id()),
-            VNodeKind::Element(el) => {
-                self.immediately_dispose_garabage(node.direct_id());
-                for child in el.children {
-                    self.remove_vnode(&child);
-                }
-            }
-            VNodeKind::Anchor(a) => {
-                //
-            }
-            VNodeKind::Fragment(frag) => {
-                for child in frag.children {
-                    self.remove_vnode(&child);
-                }
-            }
-            VNodeKind::Component(el) => {
-                //
-                // self.destroy_scopes(old_scope)
-            }
-            VNodeKind::Suspended(_) => todo!(),
-        }
-    }
-
-    fn current_scope(&self) -> Option<ScopeId> {
-        self.scope_stack.last().map(|f| f.clone())
-    }
-
-    fn fix_listener<'a>(&mut self, listener: &'a Listener<'a>) {
-        let scope_id = self.current_scope();
-        if let Some(scope_id) = scope_id {
-            let scope = self.get_scope(&scope_id).unwrap();
-            let mut queue = scope.listeners.borrow_mut();
-            let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
-            queue.push(long_listener as *const _)
-        }
-    }
-
-    pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'bump mut Scope> {
-        // ensure we haven't seen this scope before
-        // if we have, then we're trying to alias it, which is not allowed
-        debug_assert!(!self.seen_scopes.contains(id));
-
-        unsafe { self.vdom.get_scope_mut(*id) }
-    }
-    pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'bump Scope> {
-        // ensure we haven't seen this scope before
-        // if we have, then we're trying to alias it, which is not allowed
-        unsafe { self.vdom.get_scope(*id) }
-    }
-
-    // Navigation
-    pub(crate) fn edit_push_root(&mut self, root: ElementId) {
-        let id = root.as_u64();
-        self.edits.edits.push(PushRoot { id });
-    }
-
-    pub(crate) fn edit_pop(&mut self) {
-        self.edits.edits.push(PopRoot {});
-    }
-
-    // Add Nodes to the dom
-    // add m nodes from the stack
-    pub(crate) fn edit_append_children(&mut self, many: u32) {
-        self.edits.edits.push(AppendChildren { many });
-    }
-
-    // replace the n-m node on the stack with the m nodes
-    // ends with the last element of the chain on the top of the stack
-    pub(crate) fn edit_replace_with(&mut self, n: u32, m: u32) {
-        self.edits.edits.push(ReplaceWith { n, m });
-    }
-
-    pub(crate) fn edit_insert_after(&mut self, n: u32) {
-        self.edits.edits.push(InsertAfter { n });
-    }
-
-    pub(crate) fn edit_insert_before(&mut self, n: u32) {
-        self.edits.edits.push(InsertBefore { n });
-    }
-
-    // Remove Nodesfrom the dom
-    pub(crate) fn edit_remove(&mut self) {
-        self.edits.edits.push(Remove);
-    }
-
-    // Create
-    pub(crate) fn edit_create_text_node(&mut self, text: &'bump str, id: ElementId) {
-        let id = id.as_u64();
-        self.edits.edits.push(CreateTextNode { text, id });
-    }
-
-    pub(crate) fn edit_create_element(
-        &mut self,
-        tag: &'static str,
-        ns: Option<&'static str>,
-        id: ElementId,
-    ) {
-        let id = id.as_u64();
-        match ns {
-            Some(ns) => self.edits.edits.push(CreateElementNs { id, ns, tag }),
-            None => self.edits.edits.push(CreateElement { id, tag }),
-        }
-    }
-
-    // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
-    pub(crate) fn edit_create_placeholder(&mut self, id: ElementId) {
-        let id = id.as_u64();
-        self.edits.edits.push(CreatePlaceholder { id });
-    }
-
-    // events
-    pub(crate) fn edit_new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
-        let Listener {
-            event,
-            mounted_node,
-            ..
-        } = listener;
-
-        let element_id = mounted_node.get().unwrap().as_u64();
-
-        self.edits.edits.push(NewEventListener {
-            scope,
-            event_name: event,
-            mounted_node_id: element_id,
-        });
-    }
-
-    pub(crate) fn edit_remove_event_listener(&mut self, event: &'static str) {
-        self.edits.edits.push(RemoveEventListener { event });
-    }
-
-    // modify
-    pub(crate) fn edit_set_text(&mut self, text: &'bump str) {
-        self.edits.edits.push(SetText { text });
-    }
-
-    pub(crate) fn edit_set_attribute(&mut self, attribute: &'bump Attribute) {
-        let Attribute {
-            name,
-            value,
-            is_static,
-            is_volatile,
-            namespace,
-        } = attribute;
-        // field: &'static str,
-        // value: &'bump str,
-        // ns: Option<&'static str>,
-        self.edits.edits.push(SetAttribute {
-            field: name,
-            value,
-            ns: *namespace,
-        });
-    }
-
-    pub(crate) fn edit_set_attribute_ns(
-        &mut self,
-        attribute: &'bump Attribute,
-        namespace: &'bump str,
-    ) {
-        let Attribute {
-            name,
-            value,
-            is_static,
-            is_volatile,
-            // namespace,
-            ..
-        } = attribute;
-        // field: &'static str,
-        // value: &'bump str,
-        // ns: Option<&'static str>,
-        self.edits.edits.push(SetAttribute {
-            field: name,
-            value,
-            ns: Some(namespace),
-        });
-    }
-
-    pub(crate) fn edit_remove_attribute(&mut self, attribute: &Attribute) {
-        let name = attribute.name;
-        self.edits.edits.push(RemoveAttribute { name });
-    }
-}
-
-// When we create new nodes, we need to propagate some information back up the call chain.
-// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
-#[derive(Debug)]
-pub struct CreateMeta {
-    pub is_static: bool,
-    pub added_to_stack: u32,
-}
-
-impl CreateMeta {
-    fn new(is_static: bool, added_to_tack: u32) -> Self {
-        Self {
-            is_static,
-            added_to_stack: added_to_tack,
-        }
-    }
-}
-
-enum KeyedPrefixResult {
-    // Fast path: we finished diffing all the children just by looking at the
-    // prefix of shared keys!
-    Finished,
-    // There is more diffing work to do. Here is a count of how many children at
-    // the beginning of `new` and `old` we already processed.
-    MoreWorkToDo(usize),
-}
-
-fn find_first_real_node<'a>(
-    nodes: impl IntoIterator<Item = &'a VNode<'a>>,
-    scopes: &'a SharedResources,
-) -> Option<&'a VNode<'a>> {
-    for node in nodes {
-        let mut iter = RealChildIterator::new(node, scopes);
-        if let Some(node) = iter.next() {
-            return Some(node);
-        }
+    /// 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 _)
     }
     }
 
 
-    None
-}
-
-/// This iterator iterates through a list of virtual children and only returns real children (Elements, Text, Anchors).
-///
-/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
-/// "InsertBefore".
-pub struct RealChildIterator<'a> {
-    scopes: &'a SharedResources,
-
-    // Heuristcally we should never bleed into 4 completely nested fragments/components
-    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
-    // TODO: use const generics instead of the 4 estimation
-    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>,
-}
-
-impl<'a> RealChildIterator<'a> {
-    pub fn new(starter: &'a VNode<'a>, scopes: &'a SharedResources) -> Self {
-        Self {
-            scopes,
-            stack: smallvec::smallvec![(0, starter)],
-        }
-    }
-    // keep the memory around
-    pub fn reset_with(&mut self, node: &'a VNode<'a>) {
-        self.stack.clear();
-        self.stack.push((0, node));
-    }
-}
-
-impl<'a> Iterator for RealChildIterator<'a> {
-    type Item = &'a VNode<'a>;
-
-    fn next(&mut self) -> Option<&'a VNode<'a>> {
-        let mut should_pop = false;
-        let mut returned_node: Option<&'a VNode<'a>> = None;
-        let mut should_push = None;
-
-        while returned_node.is_none() {
-            if let Some((count, node)) = self.stack.last_mut() {
-                match &node.kind {
-                    // We can only exit our looping when we get "real" nodes
-                    // This includes fragments and components when they're empty (have a single root)
-                    VNodeKind::Element(_) | VNodeKind::Text(_) => {
-                        // We've recursed INTO an element/text
-                        // We need to recurse *out* of it and move forward to the next
-                        should_pop = true;
-                        returned_node = Some(&*node);
-                    }
-
-                    // If we get a fragment we push the next child
-                    VNodeKind::Fragment(frag) => {
-                        let subcount = *count as usize;
-
-                        if frag.children.len() == 0 {
-                            should_pop = true;
-                            returned_node = Some(&*node);
-                        }
-
-                        if subcount >= frag.children.len() {
-                            should_pop = true;
-                        } else {
-                            should_push = Some(&frag.children[subcount]);
-                        }
-                    }
-                    // // If we get a fragment we push the next child
-                    // VNodeKind::Fragment(frag) => {
-                    //     let subcount = *count as usize;
-
-                    //     if frag.children.len() == 0 {
-                    //         should_pop = true;
-                    //         returned_node = Some(&*node);
-                    //     }
-
-                    //     if subcount >= frag.children.len() {
-                    //         should_pop = true;
-                    //     } else {
-                    //         should_push = Some(&frag.children[subcount]);
-                    //     }
-                    // }
-
-                    // Immediately abort suspended nodes - can't do anything with them yet
-                    VNodeKind::Suspended(node) => {
-                        // VNodeKind::Suspended => should_pop = true,
-                        todo!()
-                    }
-
-                    VNodeKind::Anchor(a) => {
-                        todo!()
-                    }
-
-                    // For components, we load their root and push them onto the stack
-                    VNodeKind::Component(sc) => {
-                        let scope =
-                            unsafe { self.scopes.get_scope(sc.ass_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();
-                    }
-                }
-            } else {
-                // If there's no more items on the stack, we're done!
-                return None;
-            }
+    /// 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];
 
 
-            if should_pop {
-                self.stack.pop();
-                if let Some((id, _)) = self.stack.last_mut() {
-                    *id += 1;
-                }
-                should_pop = false;
-            }
+        // 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());
 
 
-            if let Some(push) = should_push {
-                self.stack.push((0, push));
-                should_push = None;
+                // Also add it for deletion
+                nodes_to_delete.push(child.clone());
             }
             }
         }
         }
 
 
-        returned_node
+        // 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
+        }
     }
     }
 }
 }
 
 

+ 116 - 0
packages/core/src/diff_stack.rs

@@ -0,0 +1,116 @@
+use crate::innerlude::*;
+use smallvec::{smallvec, SmallVec};
+
+/// The stack instructions we use to diff and create new nodes.
+#[derive(Debug)]
+pub enum DiffInstruction<'a> {
+    DiffNode {
+        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 {
+        node: &'a VNode<'a>,
+    },
+
+    Mount {
+        and: MountType<'a>,
+    },
+
+    PopScope,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum MountType<'a> {
+    Absorb,
+    Append,
+    Replace { old: &'a VNode<'a> },
+    ReplaceByElementId { el: ElementId },
+    InsertAfter { other_node: &'a VNode<'a> },
+    InsertBefore { other_node: &'a VNode<'a> },
+}
+
+pub(crate) struct DiffStack<'bump> {
+    instructions: Vec<DiffInstruction<'bump>>,
+    nodes_created_stack: SmallVec<[usize; 10]>,
+    pub scope_stack: SmallVec<[ScopeId; 5]>,
+}
+
+impl<'bump> DiffStack<'bump> {
+    pub fn new() -> Self {
+        Self {
+            instructions: Vec::with_capacity(1000),
+            nodes_created_stack: smallvec![],
+            scope_stack: smallvec![],
+        }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.instructions.is_empty()
+    }
+
+    pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
+        self.instructions.pop()
+    }
+
+    pub fn pop_scope(&mut self) -> Option<ScopeId> {
+        self.scope_stack.pop()
+    }
+
+    pub fn push(&mut self, instruction: DiffInstruction<'bump>) {
+        self.instructions.push(instruction)
+    }
+
+    pub fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
+        self.nodes_created_stack.push(0);
+        self.instructions.push(DiffInstruction::Mount { and });
+
+        for child in children.into_iter().rev() {
+            self.instructions
+                .push(DiffInstruction::Create { node: child });
+        }
+    }
+
+    pub fn push_nodes_created(&mut self, count: usize) {
+        self.nodes_created_stack.push(count);
+    }
+
+    pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
+        self.nodes_created_stack.push(0);
+        self.instructions.push(DiffInstruction::Mount { and });
+        self.instructions.push(DiffInstruction::Create { node });
+    }
+
+    pub fn add_child_count(&mut self, count: usize) {
+        *self.nodes_created_stack.last_mut().unwrap() += count;
+    }
+
+    pub fn pop_nodes_created(&mut self) -> usize {
+        self.nodes_created_stack.pop().unwrap()
+    }
+
+    pub fn current_scope(&self) -> Option<ScopeId> {
+        self.scope_stack.last().map(|f| f.clone())
+    }
+
+    pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
+        // Push the new scope onto the stack
+        self.scope_stack.push(idx);
+
+        self.instructions.push(DiffInstruction::PopScope);
+
+        // Run the creation algorithm with this scope on the stack
+        // ?? I think we treat components as framgnets??
+        self.instructions.push(DiffInstruction::Create { node });
+    }
+}

+ 0 - 99
packages/core/src/editor.rs

@@ -1,99 +0,0 @@
-//!
-//!
-//!
-//!
-//!
-//!
-
-use crate::innerlude::ScopeId;
-
-/// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the
-/// network or through FFI boundaries.
-#[derive(Debug)]
-#[cfg_attr(
-    feature = "serialize",
-    derive(serde::Serialize, serde::Deserialize),
-    serde(tag = "type")
-)]
-pub enum DomEdit<'bump> {
-    PushRoot {
-        id: u64,
-    },
-    PopRoot,
-    AppendChildren {
-        many: u32,
-    },
-    ReplaceWith {
-        // the first n elements
-        n: u32,
-
-        // the last m elements
-        m: u32,
-    },
-    InsertAfter {
-        n: u32,
-    },
-    InsertBefore {
-        n: u32,
-    },
-    Remove,
-    RemoveAllChildren,
-    CreateTextNode {
-        text: &'bump str,
-        id: u64,
-    },
-    CreateElement {
-        tag: &'bump str,
-        id: u64,
-    },
-    CreateElementNs {
-        tag: &'bump str,
-        id: u64,
-        ns: &'static str,
-    },
-    CreatePlaceholder {
-        id: u64,
-    },
-    NewEventListener {
-        event_name: &'static str,
-        scope: ScopeId,
-        mounted_node_id: u64,
-    },
-    RemoveEventListener {
-        event: &'static str,
-    },
-    SetText {
-        text: &'bump str,
-    },
-    SetAttribute {
-        field: &'static str,
-        value: &'bump str,
-        ns: Option<&'bump str>,
-    },
-    RemoveAttribute {
-        name: &'static str,
-    },
-}
-impl DomEdit<'_> {
-    pub fn is(&self, id: &'static str) -> bool {
-        match self {
-            DomEdit::InsertAfter { .. } => id == "InsertAfter",
-            DomEdit::InsertBefore { .. } => id == "InsertBefore",
-            DomEdit::PushRoot { .. } => id == "PushRoot",
-            DomEdit::PopRoot => id == "PopRoot",
-            DomEdit::AppendChildren { .. } => id == "AppendChildren",
-            DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
-            DomEdit::Remove => id == "Remove",
-            DomEdit::RemoveAllChildren => id == "RemoveAllChildren",
-            DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
-            DomEdit::CreateElement { .. } => id == "CreateElement",
-            DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
-            DomEdit::CreatePlaceholder { .. } => id == "CreatePlaceholder",
-            DomEdit::NewEventListener { .. } => id == "NewEventListener",
-            DomEdit::RemoveEventListener { .. } => id == "RemoveEventListener",
-            DomEdit::SetText { .. } => id == "SetText",
-            DomEdit::SetAttribute { .. } => id == "SetAttribute",
-            DomEdit::RemoveAttribute { .. } => id == "RemoveAttribute",
-        }
-    }
-}

+ 0 - 30
packages/core/src/error.rs

@@ -1,30 +0,0 @@
-//! Internal error handling for Dioxus
-//!
-//!
-
-use thiserror::Error as ThisError;
-pub type Result<T, E = Error> = std::result::Result<T, E>;
-
-#[derive(ThisError, Debug)]
-pub enum Error {
-    #[error("Fatal Internal Error: {0}")]
-    FatalInternal(&'static str),
-
-    #[error("Context is missing")]
-    MissingSharedContext,
-
-    #[error("No event to progress")]
-    NoEvent,
-
-    #[error("Wrong Properties Type")]
-    WrongProps,
-
-    #[error("The component failed to return VNodes")]
-    ComponentFailed,
-
-    #[error("Base scope has not been mounted yet")]
-    NotMounted,
-
-    #[error("I/O Error: {0}")]
-    IO(#[from] std::io::Error),
-}

+ 121 - 271
packages/core/src/events.rs

@@ -3,212 +3,71 @@
 //!
 //!
 //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
 //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
 //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
 //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
+use crate::innerlude::{ElementId, ScopeId};
 
 
-use std::{
-    cell::{Cell, RefCell},
-    rc::Rc,
-};
+use bumpalo::boxed::Box as BumpBox;
+use std::{any::Any, cell::RefCell, fmt::Debug, ops::Deref, rc::Rc};
 
 
 use crate::{
 use crate::{
-    innerlude::{ElementId, ScopeId},
-    VNode,
+    innerlude::NodeFactory,
+    innerlude::{Attribute, Listener, VNode},
 };
 };
+use std::cell::Cell;
 
 
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct EventTrigger {
+pub struct UserEvent {
     /// The originator of the event trigger
     /// The originator of the event trigger
-    pub originator: ScopeId,
+    pub scope: ScopeId,
 
 
     /// The optional real node associated with the trigger
     /// The optional real node associated with the trigger
-    pub real_node_id: Option<ElementId>,
-
-    /// The type of event
-    pub event: VirtualEvent,
-
-    /// The priority of the event
-    pub priority: EventPriority,
-}
-
-#[derive(PartialEq, Eq, Clone, Copy)]
-pub struct EventKey {
-    /// The originator of the event trigger
-    pub originator: ScopeId,
-    /// The priority of the event
-    pub priority: EventPriority,
-    /// The height of the scope (used for ordering)
-    pub height: u32,
-    // TODO: add the time that the event was queued
-}
+    pub mounted_dom_id: Option<ElementId>,
 
 
-impl PartialOrd for EventKey {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        todo!()
-    }
-}
-impl Ord for EventKey {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        todo!()
-    }
-}
-
-/// Priority of Event Triggers.
-///
-/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
-/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
-/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
-///
-/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
-pub enum EventPriority {
-    /// Garbage collection is a type of work than can be scheduled around other work, but must be completed in a specific
-    /// order. The GC must be run for a component before any other future work for that component is run. Otherwise,
-    /// we will leak slots in our slab.
+    /// The event type IE "onclick" or "onmouseover"
     ///
     ///
-    /// Garbage collection mixes with the safety aspects of the virtualdom so it's very important to get it done before
-    /// other work.
-    GarbageCollection,
+    /// The name that the renderer will use to mount the listener.
+    pub name: &'static str,
 
 
-    /// "High Priority" work will not interrupt other high priority work, but will interrupt long medium and low priority work.
-    ///
-    /// This is typically reserved for things like user interaction.
-    High,
-
-    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
-    /// than "High Priority" events and will take presedence over low priority events.
-    ///
-    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
-    Medium,
-
-    /// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
-    /// advanced to the front of the work queue until completed.
-    ///
-    /// The primary user of Low Priority work is the asynchronous work system (suspense).
-    Low,
-}
-
-impl EventTrigger {
-    pub fn new(
-        event: VirtualEvent,
-        scope: ScopeId,
-        mounted_dom_id: Option<ElementId>,
-        priority: EventPriority,
-    ) -> Self {
-        Self {
-            priority,
-            originator: scope,
-            real_node_id: mounted_dom_id,
-            event,
-        }
-    }
+    /// The type of event
+    pub event: SyntheticEvent,
 }
 }
 
 
-pub enum VirtualEvent {
-    /// Generated during diffing to signal that a component's nodes to be given back
-    ///
-    /// Typically has a high priority
-    ///
-    /// If an event is scheduled for a component that has "garbage", that garabge will be cleaned up before the event can
-    /// be processed.
-    GarbageCollection,
-
-    /// A type of "immediate" event scheduled by components
-    ///
-    /// Usually called through "set_state"
-    ScheduledUpdate {
-        height: u32,
-    },
-
-    // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
-    //
-    // Async events don't necessarily propagate into a scope being ran. It's up to the event itself
-    // to force an update for itself.
-    //
-    // Most async events should have a low priority.
-    //
-    // This type exists for the task/concurrency system to signal that a task is ready.
-    // However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
-    // own re-run.
-    AsyncEvent {
-        should_rerender: bool,
-    },
-
-    // Suspense events are a type of async event generated when suspended nodes are ready to be processed.
-    //
-    // they have the lowest priority
-    SuspenseEvent {
-        hook_idx: usize,
-        domnode: Rc<Cell<Option<ElementId>>>,
-    },
-
-    // image event has conflicting method types
-    // ImageEvent(event_data::ImageEvent),
-
-    // Real events
+pub enum SyntheticEvent {
+    AnimationEvent(on::AnimationEvent),
     ClipboardEvent(on::ClipboardEvent),
     ClipboardEvent(on::ClipboardEvent),
     CompositionEvent(on::CompositionEvent),
     CompositionEvent(on::CompositionEvent),
-    KeyboardEvent(on::KeyboardEvent),
     FocusEvent(on::FocusEvent),
     FocusEvent(on::FocusEvent),
     FormEvent(on::FormEvent),
     FormEvent(on::FormEvent),
-    SelectionEvent(on::SelectionEvent),
+    KeyboardEvent(on::KeyboardEvent),
+    GenericEvent(on::GenericEvent),
     TouchEvent(on::TouchEvent),
     TouchEvent(on::TouchEvent),
-    UIEvent(on::UIEvent),
-    WheelEvent(on::WheelEvent),
-    MediaEvent(on::MediaEvent),
-    AnimationEvent(on::AnimationEvent),
-    TransitionEvent(on::TransitionEvent),
     ToggleEvent(on::ToggleEvent),
     ToggleEvent(on::ToggleEvent),
+    MediaEvent(on::MediaEvent),
     MouseEvent(on::MouseEvent),
     MouseEvent(on::MouseEvent),
+    WheelEvent(on::WheelEvent),
+    SelectionEvent(on::SelectionEvent),
+    TransitionEvent(on::TransitionEvent),
     PointerEvent(on::PointerEvent),
     PointerEvent(on::PointerEvent),
 }
 }
-impl VirtualEvent {
-    pub fn is_input_event(&self) -> bool {
-        match self {
-            VirtualEvent::ClipboardEvent(_)
-            | VirtualEvent::CompositionEvent(_)
-            | VirtualEvent::KeyboardEvent(_)
-            | VirtualEvent::FocusEvent(_)
-            | VirtualEvent::FormEvent(_)
-            | VirtualEvent::SelectionEvent(_)
-            | VirtualEvent::TouchEvent(_)
-            | VirtualEvent::UIEvent(_)
-            | VirtualEvent::WheelEvent(_)
-            | VirtualEvent::MediaEvent(_)
-            | VirtualEvent::AnimationEvent(_)
-            | VirtualEvent::TransitionEvent(_)
-            | VirtualEvent::ToggleEvent(_)
-            | VirtualEvent::MouseEvent(_)
-            | VirtualEvent::PointerEvent(_) => true,
-
-            VirtualEvent::GarbageCollection
-            | VirtualEvent::ScheduledUpdate { .. }
-            | VirtualEvent::AsyncEvent { .. }
-            | VirtualEvent::SuspenseEvent { .. } => false,
-        }
-    }
-}
+// ImageEvent(event_data::ImageEvent),
 
 
-impl std::fmt::Debug for VirtualEvent {
+impl std::fmt::Debug for SyntheticEvent {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let name = match self {
         let name = match self {
-            VirtualEvent::ClipboardEvent(_) => "ClipboardEvent",
-            VirtualEvent::CompositionEvent(_) => "CompositionEvent",
-            VirtualEvent::KeyboardEvent(_) => "KeyboardEvent",
-            VirtualEvent::FocusEvent(_) => "FocusEvent",
-            VirtualEvent::FormEvent(_) => "FormEvent",
-            VirtualEvent::SelectionEvent(_) => "SelectionEvent",
-            VirtualEvent::TouchEvent(_) => "TouchEvent",
-            VirtualEvent::UIEvent(_) => "UIEvent",
-            VirtualEvent::WheelEvent(_) => "WheelEvent",
-            VirtualEvent::MediaEvent(_) => "MediaEvent",
-            VirtualEvent::AnimationEvent(_) => "AnimationEvent",
-            VirtualEvent::TransitionEvent(_) => "TransitionEvent",
-            VirtualEvent::ToggleEvent(_) => "ToggleEvent",
-            VirtualEvent::MouseEvent(_) => "MouseEvent",
-            VirtualEvent::PointerEvent(_) => "PointerEvent",
-            VirtualEvent::GarbageCollection => "GarbageCollection",
-            VirtualEvent::ScheduledUpdate { .. } => "SetStateEvent",
-            VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
-            VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
+            SyntheticEvent::ClipboardEvent(_) => "ClipboardEvent",
+            SyntheticEvent::CompositionEvent(_) => "CompositionEvent",
+            SyntheticEvent::KeyboardEvent(_) => "KeyboardEvent",
+            SyntheticEvent::FocusEvent(_) => "FocusEvent",
+            SyntheticEvent::FormEvent(_) => "FormEvent",
+            SyntheticEvent::SelectionEvent(_) => "SelectionEvent",
+            SyntheticEvent::TouchEvent(_) => "TouchEvent",
+            SyntheticEvent::WheelEvent(_) => "WheelEvent",
+            SyntheticEvent::MediaEvent(_) => "MediaEvent",
+            SyntheticEvent::AnimationEvent(_) => "AnimationEvent",
+            SyntheticEvent::TransitionEvent(_) => "TransitionEvent",
+            SyntheticEvent::ToggleEvent(_) => "ToggleEvent",
+            SyntheticEvent::MouseEvent(_) => "MouseEvent",
+            SyntheticEvent::PointerEvent(_) => "PointerEvent",
+            SyntheticEvent::GenericEvent(_) => "GenericEvent",
         };
         };
 
 
         f.debug_struct("VirtualEvent").field("type", &name).finish()
         f.debug_struct("VirtualEvent").field("type", &name).finish()
@@ -222,20 +81,8 @@ pub mod on {
     //! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling
     //! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling
     //! Arc allocation through "get_mut"
     //! Arc allocation through "get_mut"
     //!
     //!
-    //!
-    //!
-
-    #![allow(unused)]
-    use bumpalo::boxed::Box as BumpBox;
-    use std::{cell::RefCell, fmt::Debug, ops::Deref, rc::Rc};
-
-    use crate::{
-        innerlude::NodeFactory,
-        innerlude::{Attribute, ElementId, Listener, VNode},
-    };
-    use std::cell::Cell;
-
-    use super::VirtualEvent;
+    //! React recently dropped support for re-using event allocation and just passes the real event along.
+    use super::*;
 
 
     macro_rules! event_directory {
     macro_rules! event_directory {
         ( $(
         ( $(
@@ -271,12 +118,12 @@ pub mod on {
                     {
                     {
                         let bump = &c.bump();
                         let bump = &c.bump();
 
 
-                        let cb: &mut dyn FnMut(VirtualEvent) = bump.alloc(move |evt: VirtualEvent| match evt {
-                            VirtualEvent::$wrapper(event) => callback(event),
-                            _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
+                        let cb: &mut dyn FnMut(SyntheticEvent) = bump.alloc(move |evt: SyntheticEvent| match evt {
+                            SyntheticEvent::$wrapper(event) => callback(event),
+                            _ => unreachable!("Downcasted SyntheticEvent to wrong event type - this is an internal bug!")
                         });
                         });
 
 
-                        let callback: BumpBox<dyn FnMut(VirtualEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
+                        let callback: BumpBox<dyn FnMut(SyntheticEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
 
 
                         let event_name = stringify!($name);
                         let event_name = stringify!($name);
                         let shortname: &'static str = &event_name[2..];
                         let shortname: &'static str = &event_name[2..];
@@ -292,14 +139,6 @@ pub mod on {
     }
     }
 
 
     // The Dioxus Synthetic event system
     // The Dioxus Synthetic event system
-    //
-    //
-    //
-    //
-    //
-    //
-    //
-    //
     event_directory! {
     event_directory! {
         ClipboardEventInner(ClipboardEvent): [
         ClipboardEventInner(ClipboardEvent): [
             /// Called when "copy"
             /// Called when "copy"
@@ -447,7 +286,13 @@ pub mod on {
             onmousemove
             onmousemove
             /// onmouseout
             /// onmouseout
             onmouseout
             onmouseout
+
+            ///
+            onscroll
+
             /// onmouseover
             /// onmouseover
+            ///
+            /// Triggered when the users's mouse hovers over an element.
             onmouseover
             onmouseover
             /// onmouseup
             /// onmouseup
             onmouseup
             onmouseup
@@ -492,14 +337,9 @@ pub mod on {
             ontouchstart
             ontouchstart
         ];
         ];
 
 
-        UIEventInner(UIEvent): [
-            ///
-            scroll
-        ];
-
         WheelEventInner(WheelEvent): [
         WheelEventInner(WheelEvent): [
             ///
             ///
-            wheel
+            onwheel
         ];
         ];
 
 
         MediaEventInner(MediaEvent): [
         MediaEventInner(MediaEvent): [
@@ -571,7 +411,11 @@ pub mod on {
         ];
         ];
     }
     }
 
 
+    pub struct GenericEvent(pub Rc<dyn GenericEventInner>);
+
     pub trait GenericEventInner {
     pub trait GenericEventInner {
+        /// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type.
+        fn raw_event(&self) -> &dyn Any;
         /// Returns whether or not a specific event is a bubbling event
         /// Returns whether or not a specific event is a bubbling event
         fn bubbles(&self) -> bool;
         fn bubbles(&self) -> bool;
         /// Sets or returns whether the event should propagate up the hierarchy or not
         /// Sets or returns whether the event should propagate up the hierarchy or not
@@ -580,14 +424,18 @@ pub mod on {
         fn cancelable(&self) -> bool;
         fn cancelable(&self) -> bool;
         /// Returns whether the event is composed or not
         /// Returns whether the event is composed or not
         fn composed(&self) -> bool;
         fn composed(&self) -> bool;
-        /// Returns the event's path
-        fn composed_path(&self) -> String;
+
+        // Currently not supported because those no way we could possibly support it
+        // just cast the event to the right platform-specific type and return it
+        // /// Returns the event's path
+        // fn composed_path(&self) -> String;
+
         /// Returns the element whose event listeners triggered the event
         /// Returns the element whose event listeners triggered the event
         fn current_target(&self);
         fn current_target(&self);
         /// Returns whether or not the preventDefault method was called for the event
         /// Returns whether or not the preventDefault method was called for the event
         fn default_prevented(&self) -> bool;
         fn default_prevented(&self) -> bool;
         /// Returns which phase of the event flow is currently being evaluated
         /// Returns which phase of the event flow is currently being evaluated
-        fn event_phase(&self) -> usize;
+        fn event_phase(&self) -> u16;
         /// Returns whether or not an event is trusted
         /// Returns whether or not an event is trusted
         fn is_trusted(&self) -> bool;
         fn is_trusted(&self) -> bool;
         /// Cancels the event if it is cancelable, meaning that the default action that belongs to the event will
         /// Cancels the event if it is cancelable, meaning that the default action that belongs to the event will
@@ -599,7 +447,7 @@ pub mod on {
         /// Returns the element that triggered the event
         /// Returns the element that triggered the event
         fn target(&self);
         fn target(&self);
         /// Returns the time (in milliseconds relative to the epoch) at which the event was created
         /// Returns the time (in milliseconds relative to the epoch) at which the event was created
-        fn time_stamp(&self) -> usize;
+        fn time_stamp(&self) -> f64;
     }
     }
 
 
     pub trait ClipboardEventInner {
     pub trait ClipboardEventInner {
@@ -611,8 +459,31 @@ pub mod on {
     }
     }
 
 
     pub trait KeyboardEventInner {
     pub trait KeyboardEventInner {
+        fn alt_key(&self) -> bool;
+
         fn char_code(&self) -> u32;
         fn char_code(&self) -> u32;
 
 
+        /// Identify which "key" was entered.
+        ///
+        /// This is the best method to use for all languages. They key gets mapped to a String sequence which you can match on.
+        /// The key isn't an enum because there are just so many context-dependent keys.
+        ///
+        /// A full list on which keys to use is available at:
+        /// <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values>
+        ///
+        /// # Example
+        ///
+        /// ```rust
+        /// match event.key().as_str() {
+        ///     "Esc" | "Escape" => {}
+        ///     "ArrowDown" => {}
+        ///     "ArrowLeft" => {}
+        ///      _ => {}
+        /// }
+        /// ```
+        ///
+        fn key(&self) -> String;
+
         /// Get the key code as an enum Variant.
         /// Get the key code as an enum Variant.
         ///
         ///
         /// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys.
         /// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys.
@@ -636,35 +507,14 @@ pub mod on {
         /// Check if the ctrl key was pressed down
         /// Check if the ctrl key was pressed down
         fn ctrl_key(&self) -> bool;
         fn ctrl_key(&self) -> bool;
 
 
-        /// Identify which "key" was entered.
-        ///
-        /// This is the best method to use for all languages. They key gets mapped to a String sequence which you can match on.
-        /// The key isn't an enum because there are just so many context-dependent keys.
-        ///
-        /// A full list on which keys to use is available at:
-        /// <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values>
-        ///
-        /// # Example
-        ///
-        /// ```rust
-        /// match event.key().as_str() {
-        ///     "Esc" | "Escape" => {}
-        ///     "ArrowDown" => {}
-        ///     "ArrowLeft" => {}
-        ///      _ => {}
-        /// }
-        /// ```
-        ///
-        fn key(&self) -> String;
+        fn get_modifier_state(&self, key_code: &str) -> bool;
 
 
-        // fn key(&self) -> String;
         fn locale(&self) -> String;
         fn locale(&self) -> String;
         fn location(&self) -> usize;
         fn location(&self) -> usize;
         fn meta_key(&self) -> bool;
         fn meta_key(&self) -> bool;
         fn repeat(&self) -> bool;
         fn repeat(&self) -> bool;
         fn shift_key(&self) -> bool;
         fn shift_key(&self) -> bool;
         fn which(&self) -> usize;
         fn which(&self) -> usize;
-        fn get_modifier_state(&self, key_code: usize) -> bool;
     }
     }
 
 
     pub trait FocusEventInner {
     pub trait FocusEventInner {
@@ -695,8 +545,8 @@ pub mod on {
     pub trait PointerEventInner {
     pub trait PointerEventInner {
         // Mouse only
         // Mouse only
         fn alt_key(&self) -> bool;
         fn alt_key(&self) -> bool;
-        fn button(&self) -> usize;
-        fn buttons(&self) -> usize;
+        fn button(&self) -> i16;
+        fn buttons(&self) -> u16;
         fn client_x(&self) -> i32;
         fn client_x(&self) -> i32;
         fn client_y(&self) -> i32;
         fn client_y(&self) -> i32;
         fn ctrl_key(&self) -> bool;
         fn ctrl_key(&self) -> bool;
@@ -706,12 +556,12 @@ pub mod on {
         fn screen_x(&self) -> i32;
         fn screen_x(&self) -> i32;
         fn screen_y(&self) -> i32;
         fn screen_y(&self) -> i32;
         fn shift_key(&self) -> bool;
         fn shift_key(&self) -> bool;
-        fn get_modifier_state(&self, key_code: usize) -> bool;
-        fn pointer_id(&self) -> usize;
-        fn width(&self) -> usize;
-        fn height(&self) -> usize;
-        fn pressure(&self) -> usize;
-        fn tangential_pressure(&self) -> usize;
+        fn get_modifier_state(&self, key_code: &str) -> bool;
+        fn pointer_id(&self) -> i32;
+        fn width(&self) -> i32;
+        fn height(&self) -> i32;
+        fn pressure(&self) -> f32;
+        fn tangential_pressure(&self) -> f32;
         fn tilt_x(&self) -> i32;
         fn tilt_x(&self) -> i32;
         fn tilt_y(&self) -> i32;
         fn tilt_y(&self) -> i32;
         fn twist(&self) -> i32;
         fn twist(&self) -> i32;
@@ -726,7 +576,7 @@ pub mod on {
         fn ctrl_key(&self) -> bool;
         fn ctrl_key(&self) -> bool;
         fn meta_key(&self) -> bool;
         fn meta_key(&self) -> bool;
         fn shift_key(&self) -> bool;
         fn shift_key(&self) -> bool;
-        fn get_modifier_state(&self, key_code: usize) -> bool;
+        fn get_modifier_state(&self, key_code: &str) -> bool;
         // changedTouches: DOMTouchList,
         // changedTouches: DOMTouchList,
         // targetTouches: DOMTouchList,
         // targetTouches: DOMTouchList,
         // touches: DOMTouchList,
         // touches: DOMTouchList,
@@ -738,10 +588,10 @@ pub mod on {
     }
     }
 
 
     pub trait WheelEventInner {
     pub trait WheelEventInner {
-        fn delta_mode(&self) -> i32;
-        fn delta_x(&self) -> i32;
-        fn delta_y(&self) -> i32;
-        fn delta_z(&self) -> i32;
+        fn delta_mode(&self) -> u32;
+        fn delta_x(&self) -> f64;
+        fn delta_y(&self) -> f64;
+        fn delta_z(&self) -> f64;
     }
     }
 
 
     pub trait MediaEventInner {}
     pub trait MediaEventInner {}
@@ -788,16 +638,16 @@ pub mod on {
             DownArrow = 40,
             DownArrow = 40,
             Insert = 45,
             Insert = 45,
             Delete = 46,
             Delete = 46,
-            _0 = 48,
-            _1 = 49,
-            _2 = 50,
-            _3 = 51,
-            _4 = 52,
-            _5 = 53,
-            _6 = 54,
-            _7 = 55,
-            _8 = 56,
-            _9 = 57,
+            Num0 = 48,
+            Num1 = 49,
+            Num2 = 50,
+            Num3 = 51,
+            Num4 = 52,
+            Num5 = 53,
+            Num6 = 54,
+            Num7 = 55,
+            Num8 = 56,
+            Num9 = 57,
             A = 65,
             A = 65,
             B = 66,
             B = 66,
             C = 67,
             C = 67,
@@ -893,16 +743,16 @@ pub mod on {
                     40 => DownArrow,
                     40 => DownArrow,
                     45 => Insert,
                     45 => Insert,
                     46 => Delete,
                     46 => Delete,
-                    48 => _0,
-                    49 => _1,
-                    50 => _2,
-                    51 => _3,
-                    52 => _4,
-                    53 => _5,
-                    54 => _6,
-                    55 => _7,
-                    56 => _8,
-                    57 => _9,
+                    48 => Num0,
+                    49 => Num1,
+                    50 => Num2,
+                    51 => Num3,
+                    52 => Num4,
+                    53 => Num5,
+                    54 => Num6,
+                    55 => Num7,
+                    56 => Num8,
+                    57 => Num9,
                     65 => A,
                     65 => A,
                     66 => B,
                     66 => B,
                     67 => C,
                     67 => C,

+ 7 - 23
packages/core/src/hooklist.rs

@@ -1,10 +1,10 @@
 use std::{
 use std::{
     any::Any,
     any::Any,
-    cell::{Cell, UnsafeCell},
+    cell::{Cell, RefCell, UnsafeCell},
 };
 };
 
 
-pub struct HookList {
-    vals: appendlist::AppendList<InnerHook<Box<dyn Any>>>,
+pub(crate) struct HookList {
+    vals: RefCell<Vec<InnerHook<Box<dyn Any>>>>,
     idx: Cell<usize>,
     idx: Cell<usize>,
 }
 }
 
 
@@ -20,6 +20,7 @@ impl Default for HookList {
 struct InnerHook<T> {
 struct InnerHook<T> {
     cell: UnsafeCell<T>,
     cell: UnsafeCell<T>,
 }
 }
+
 impl<T> InnerHook<T> {
 impl<T> InnerHook<T> {
     fn new(new: T) -> Self {
     fn new(new: T) -> Self {
         Self {
         Self {
@@ -29,33 +30,16 @@ impl<T> InnerHook<T> {
 }
 }
 
 
 impl HookList {
 impl HookList {
-    /// Unsafely get a mutable reference to any of the hooks
-    ///
-    /// This is unsafe because an &mut T might be aliased if the hook data is already borrowed/in use in the component
-    ///
-    /// This method should be reserved for internal methods that are guaranteed that this hook is not aliased anyhwere
-    /// inside the component body, or outside into children components.
-    ///
-    /// This method is currently used only by the suspense system whose hook implementation guarantees that all &T is dropped
-    /// before the suspense handler is ran.
-    pub(crate) unsafe fn get_mut<T: 'static>(&self, idx: usize) -> Option<&mut T> {
-        self.vals.get(idx).and_then(|inn| {
-            let raw_box = unsafe { &mut *inn.cell.get() };
-            raw_box.downcast_mut::<T>()
-        })
-    }
-
     pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
     pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
-        self.vals.get(self.idx.get()).and_then(|inn| {
+        self.vals.borrow().get(self.idx.get()).and_then(|inn| {
             self.idx.set(self.idx.get() + 1);
             self.idx.set(self.idx.get() + 1);
             let raw_box = unsafe { &mut *inn.cell.get() };
             let raw_box = unsafe { &mut *inn.cell.get() };
             raw_box.downcast_mut::<T>()
             raw_box.downcast_mut::<T>()
         })
         })
     }
     }
 
 
-    #[inline]
     pub(crate) fn push<T: 'static>(&self, new: T) {
     pub(crate) fn push<T: 'static>(&self, new: T) {
-        self.vals.push(InnerHook::new(Box::new(new)))
+        self.vals.borrow_mut().push(InnerHook::new(Box::new(new)))
     }
     }
 
 
     /// This resets the internal iterator count
     /// This resets the internal iterator count
@@ -70,7 +54,7 @@ impl HookList {
 
 
     #[inline]
     #[inline]
     pub(crate) fn len(&self) -> usize {
     pub(crate) fn len(&self) -> usize {
-        self.vals.len()
+        self.vals.borrow().len()
     }
     }
 
 
     #[inline]
     #[inline]

+ 87 - 78
packages/core/src/hooks.rs

@@ -3,17 +3,17 @@
 //! This module contains all the low-level built-in hooks that require 1st party support to work.
 //! This module contains all the low-level built-in hooks that require 1st party support to work.
 //!
 //!
 //! Hooks:
 //! Hooks:
-//! - use_hook
-//! - use_state_provider
-//! - use_state_consumer
-//! - use_task
-//! - use_suspense
+//! - [`use_hook`]
+//! - [`use_state_provider`]
+//! - [`use_state_consumer`]
+//! - [`use_task`]
+//! - [`use_suspense`]
 
 
 use crate::innerlude::*;
 use crate::innerlude::*;
 use futures_util::FutureExt;
 use futures_util::FutureExt;
 use std::{
 use std::{
     any::{Any, TypeId},
     any::{Any, TypeId},
-    cell::{Cell, RefCell},
+    cell::RefCell,
     future::Future,
     future::Future,
     rc::Rc,
     rc::Rc,
 };
 };
@@ -53,15 +53,7 @@ where
         (true, true) => {}
         (true, true) => {}
 
 
         // Needs to be initialized
         // Needs to be initialized
-        (false, false) => {
-            log::debug!("Initializing context...");
-            cx.add_shared_state(init());
-            log::info!(
-                "There are now {} shared contexts for scope {:?}",
-                cx.scope.shared_contexts.borrow().len(),
-                cx.scope.our_arena_idx,
-            );
-        }
+        (false, false) => cx.add_shared_state(init()),
 
 
         _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
         _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
     };
     };
@@ -119,21 +111,12 @@ where
             let slot = task_dump.clone();
             let slot = task_dump.clone();
 
 
             let updater = cx.prepare_update();
             let updater = cx.prepare_update();
-            let update_id = cx.get_scope_id();
-
-            let originator = cx.scope.our_arena_idx.clone();
+            let originator = cx.scope.our_arena_idx;
 
 
             let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
             let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
                 *slot.as_ref().borrow_mut() = Some(output);
                 *slot.as_ref().borrow_mut() = Some(output);
-                updater(update_id);
-                EventTrigger {
-                    event: VirtualEvent::AsyncEvent {
-                        should_rerender: false,
-                    },
-                    originator,
-                    priority: EventPriority::Low,
-                    real_node_id: None,
-                }
+                updater(originator);
+                originator
             })));
             })));
 
 
             TaskHook {
             TaskHook {
@@ -172,79 +155,89 @@ where
     Out: 'static,
     Out: 'static,
     Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
     Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
 {
 {
+    /*
+    General strategy:
+    - Create a slot for the future to dump its output into
+    - Create a new future feeding off the user's future that feeds the output into that slot
+    - Submit that future as a task
+    - Take the task handle id and attach that to our suspended node
+    - when the hook runs, check if the value exists
+    - if it does, then we can render the node directly
+    - if it doesn't, then we render a suspended node along with with the callback and task id
+    */
     cx.use_hook(
     cx.use_hook(
         move |hook_idx| {
         move |hook_idx| {
             let value = Rc::new(RefCell::new(None));
             let value = Rc::new(RefCell::new(None));
+            let slot = value.clone();
 
 
-            let dom_node_id = Rc::new(empty_cell());
-            let domnode = dom_node_id.clone();
+            let originator = cx.scope.our_arena_idx.clone();
 
 
-            let slot = value.clone();
+            let handle = cx.submit_task(Box::pin(task_initializer().then(
+                move |output| async move {
+                    *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
+                    originator
+                },
+            )));
+
+            SuspenseHook { handle, value }
+        },
+        move |hook| match hook.value.borrow().as_ref() {
+            Some(value) => {
+                let out = value.downcast_ref::<Out>().unwrap();
+                let sus = SuspendedContext {
+                    inner: Context {
+                        props: &(),
+                        scope: cx.scope,
+                    },
+                };
+                user_callback(sus, out)
+            }
+            None => {
+                let value = hook.value.clone();
 
 
-            let callback: SuspendedCallback = Box::new(move |ctx: SuspendedContext| {
-                let v: std::cell::Ref<Option<Box<dyn Any>>> = slot.as_ref().borrow();
-                match v.as_ref() {
-                    Some(a) => {
-                        let v: &dyn Any = a.as_ref();
-                        let real_val = v.downcast_ref::<Out>().unwrap();
-                        user_callback(ctx, real_val)
-                    }
-                    None => {
-                        //
-                        Some(VNode {
-                            key: None,
-                            kind: VNodeKind::Suspended(VSuspended {
-                                node: domnode.clone(),
-                            }),
-                        })
-                    }
-                }
-            });
+                cx.render(LazyNodes::new(|f| {
+                    let bump = f.bump();
 
 
-            let originator = cx.scope.our_arena_idx.clone();
-            let task_fut = task_initializer();
-            let domnode = dom_node_id.clone();
+                    use bumpalo::boxed::Box as BumpBox;
 
 
-            let slot = value.clone();
-            cx.submit_task(Box::pin(task_fut.then(move |output| async move {
-                // When the new value arrives, set the hooks internal slot
-                // Dioxus will call the user's callback to generate new nodes outside of the diffing system
-                *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
-                EventTrigger {
-                    event: VirtualEvent::SuspenseEvent { hook_idx, domnode },
-                    originator,
-                    priority: EventPriority::Low,
-                    real_node_id: None,
-                }
-            })));
+                    let f: &mut dyn FnMut(SuspendedContext<'src>) -> DomTree<'src> =
+                        bump.alloc(move |sus| {
+                            let val = value.borrow();
 
 
-            SuspenseHook {
-                value,
-                callback,
-                dom_node_id,
+                            let out = val
+                                .as_ref()
+                                .unwrap()
+                                .as_ref()
+                                .downcast_ref::<Out>()
+                                .unwrap();
+
+                            user_callback(sus, out)
+                        });
+                    let callback = unsafe { BumpBox::from_raw(f) };
+
+                    VNode::Suspended(bump.alloc(VSuspended {
+                        dom_id: empty_cell(),
+                        task_id: hook.handle.our_id,
+                        callback: RefCell::new(Some(callback)),
+                    }))
+                }))
             }
             }
         },
         },
-        move |hook| {
-            let cx = Context {
-                scope: &cx.scope,
-                props: &(),
-            };
-            let csx = SuspendedContext { inner: cx };
-            (&hook.callback)(csx)
-        },
         |_| {},
         |_| {},
     )
     )
 }
 }
 
 
 pub(crate) struct SuspenseHook {
 pub(crate) struct SuspenseHook {
+    pub handle: TaskHandle,
     pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
     pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
-    pub callback: SuspendedCallback,
-    pub dom_node_id: Rc<Cell<Option<ElementId>>>,
 }
 }
+
 type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
 type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
+
 pub struct SuspendedContext<'a> {
 pub struct SuspendedContext<'a> {
     pub(crate) inner: Context<'a, ()>,
     pub(crate) inner: Context<'a, ()>,
 }
 }
+
 impl<'src> SuspendedContext<'src> {
 impl<'src> SuspendedContext<'src> {
     pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
     pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
         self,
         self,
@@ -256,3 +249,19 @@ impl<'src> SuspendedContext<'src> {
         Some(lazy_nodes.into_vnode(NodeFactory { bump }))
         Some(lazy_nodes.into_vnode(NodeFactory { bump }))
     }
     }
 }
 }
+
+#[derive(Clone, Copy)]
+pub struct NodeRef<'src, T: 'static>(&'src RefCell<T>);
+
+pub fn use_node_ref<T, P>(cx: Context<P>) -> NodeRef<T> {
+    cx.use_hook(
+        |f| {},
+        |f| {
+            //
+            todo!()
+        },
+        |f| {
+            //
+        },
+    )
+}

+ 53 - 48
packages/core/src/lib.rs

@@ -1,46 +1,51 @@
 #![allow(non_snake_case)]
 #![allow(non_snake_case)]
 #![doc = include_str!("../README.md")]
 #![doc = include_str!("../README.md")]
-//! Dioxus Core
-//! ----------
-//!
-//!
-//!
-//!
-//!
-//!
-//!
 
 
-pub use crate::innerlude::{
-    format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
-    EventTrigger, LazyNodes, NodeFactory, Properties, ScopeId, SuspendedContext, VNode, VNodeKind,
-    VirtualDom, VirtualEvent, FC,
-};
+/*
+Navigating this crate:
+- virtual_dom: the primary entrypoint for the crate
+- scheduler: the core interior logic called by virtual_dom
+- nodes: the definition of VNodes, listeners, etc.
+- diff: the stackmachine-based diffing algorithm
+- hooks: foundational hooks that require crate-private APIs
+- mutations: DomEdits/NodeRefs and internal API to create them
 
 
-pub mod prelude {
-    pub use crate::component::{fc_to_builder, Fragment, Properties};
-    pub use crate::context::Context;
-    pub use crate::hooks::*;
-    pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, NodeFactory, FC};
-    pub use crate::nodes::VNode;
-    pub use crate::VirtualDom;
-    pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
-}
+Some utilities
+*/
+pub mod bumpframe;
+pub mod childiter;
+pub mod component;
+pub mod context;
+pub mod diff;
+pub mod diff_stack;
+pub mod events;
+pub mod heuristics;
+pub mod hooklist;
+pub mod hooks;
+pub mod mutations;
+pub mod nodes;
+pub mod scheduler;
+pub mod scope;
+pub mod test_dom;
+pub mod util;
+pub mod virtual_dom;
 
 
-// types used internally that are important
 pub(crate) mod innerlude {
 pub(crate) mod innerlude {
-    pub use crate::arena::*;
-    pub use crate::bumpframe::*;
+    pub(crate) use crate::bumpframe::*;
+    pub(crate) use crate::childiter::*;
     pub use crate::component::*;
     pub use crate::component::*;
     pub use crate::context::*;
     pub use crate::context::*;
-    pub use crate::diff::*;
-    pub use crate::editor::*;
-    pub use crate::error::*;
+    pub(crate) use crate::diff::*;
+    pub use crate::diff_stack::*;
     pub use crate::events::*;
     pub use crate::events::*;
     pub use crate::heuristics::*;
     pub use crate::heuristics::*;
-    pub use crate::hooklist::*;
+    pub(crate) use crate::hooklist::*;
     pub use crate::hooks::*;
     pub use crate::hooks::*;
+    pub use crate::mutations::*;
     pub use crate::nodes::*;
     pub use crate::nodes::*;
+    pub use crate::scheduler::*;
     pub use crate::scope::*;
     pub use crate::scope::*;
+    pub use crate::test_dom::*;
     pub use crate::util::*;
     pub use crate::util::*;
     pub use crate::virtual_dom::*;
     pub use crate::virtual_dom::*;
 
 
@@ -50,24 +55,24 @@ pub(crate) mod innerlude {
     pub use dioxus_core_macro::{format_args_f, html, rsx};
     pub use dioxus_core_macro::{format_args_f, html, rsx};
 }
 }
 
 
+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,
+};
+
+pub mod prelude {
+    pub use crate::component::{fc_to_builder, Fragment, Properties};
+    pub use crate::context::Context;
+    pub use crate::hooks::*;
+    pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, Mutations, NodeFactory, FC};
+    pub use crate::nodes::VNode;
+    pub use crate::VirtualDom;
+    pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
+}
+
 pub mod exports {
 pub mod exports {
-    // export important things here
+    //! Important dependencies that are used by the rest of the library
+    // the foundation of this library
     pub use bumpalo;
     pub use bumpalo;
 }
 }
-
-pub mod arena;
-pub mod bumpframe;
-pub mod component;
-pub mod context;
-pub mod diff;
-pub mod editor;
-pub mod error;
-pub mod events;
-pub mod heuristics;
-pub mod hooklist;
-pub mod hooks;
-pub mod nodes;
-pub mod scope;
-pub mod signals;
-pub mod util;
-pub mod virtual_dom;

+ 247 - 0
packages/core/src/mutations.rs

@@ -0,0 +1,247 @@
+//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
+//!
+//! This module contains an internal API to generate these instructions.
+
+use crate::innerlude::*;
+use std::any::Any;
+
+#[derive(Debug)]
+pub struct Mutations<'a> {
+    pub edits: Vec<DomEdit<'a>>,
+    pub noderefs: Vec<NodeRefMutation<'a>>,
+}
+
+use DomEdit::*;
+
+impl<'a> Mutations<'a> {
+    pub(crate) fn new() -> Self {
+        let edits = Vec::new();
+        let noderefs = Vec::new();
+        Self { edits, noderefs }
+    }
+
+    // Navigation
+    pub(crate) fn push_root(&mut self, root: ElementId) {
+        let id = root.as_u64();
+        self.edits.push(PushRoot { id });
+    }
+
+    pub(crate) fn pop(&mut self) {
+        self.edits.push(PopRoot {});
+    }
+
+    pub(crate) fn replace_with(&mut self, root: ElementId, m: u32) {
+        let root = root.as_u64();
+        self.edits.push(ReplaceWith { m, root });
+    }
+
+    pub(crate) fn insert_after(&mut self, root: ElementId, n: u32) {
+        let root = root.as_u64();
+        self.edits.push(InsertAfter { n, root });
+    }
+
+    pub(crate) fn insert_before(&mut self, root: ElementId, n: u32) {
+        let root = root.as_u64();
+        self.edits.push(InsertBefore { n, root });
+    }
+
+    // Remove Nodesfrom the dom
+    pub(crate) fn remove(&mut self, id: u64) {
+        self.edits.push(Remove { root: id });
+    }
+
+    // Create
+    pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) {
+        let id = id.as_u64();
+        self.edits.push(CreateTextNode { text, id });
+    }
+
+    pub(crate) fn create_element(
+        &mut self,
+        tag: &'static str,
+        ns: Option<&'static str>,
+        id: ElementId,
+    ) {
+        let id = id.as_u64();
+        match ns {
+            Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }),
+            None => self.edits.push(CreateElement { id, tag }),
+        }
+    }
+    // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
+    pub(crate) fn create_placeholder(&mut self, id: ElementId) {
+        let id = id.as_u64();
+        self.edits.push(CreatePlaceholder { id });
+    }
+
+    // events
+    pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
+        let Listener {
+            event,
+            mounted_node,
+            ..
+        } = listener;
+
+        let element_id = mounted_node.get().unwrap().as_u64();
+
+        self.edits.push(NewEventListener {
+            scope,
+            event_name: event,
+            mounted_node_id: element_id,
+        });
+    }
+    pub(crate) fn remove_event_listener(&mut self, event: &'static str) {
+        self.edits.push(RemoveEventListener { event });
+    }
+
+    // modify
+    pub(crate) fn set_text(&mut self, text: &'a str) {
+        self.edits.push(SetText { text });
+    }
+
+    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute) {
+        let Attribute {
+            name,
+            value,
+            namespace,
+            ..
+        } = attribute;
+
+        self.edits.push(SetAttribute {
+            field: name,
+            value,
+            ns: *namespace,
+        });
+    }
+
+    pub(crate) fn remove_attribute(&mut self, attribute: &Attribute) {
+        let name = attribute.name;
+        self.edits.push(RemoveAttribute { name });
+    }
+}
+
+// refs are only assigned once
+pub struct NodeRefMutation<'a> {
+    pub element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
+    pub element_id: ElementId,
+}
+
+impl<'a> std::fmt::Debug for NodeRefMutation<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("NodeRefMutation")
+            .field("element_id", &self.element_id)
+            .finish()
+    }
+}
+
+impl<'a> NodeRefMutation<'a> {
+    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
+        self.element
+            .as_ref()
+            .and_then(|f| f.get())
+            .and_then(|f| f.downcast_ref::<T>())
+    }
+    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
+        self.element
+            .as_mut()
+            .and_then(|f| f.get_mut())
+            .and_then(|f| f.downcast_mut::<T>())
+    }
+}
+
+/// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the
+/// network or through FFI boundaries.
+#[derive(Debug, PartialEq)]
+#[cfg_attr(
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
+)]
+pub enum DomEdit<'bump> {
+    PushRoot {
+        id: u64,
+    },
+    PopRoot,
+
+    AppendChildren {
+        many: u32,
+    },
+
+    // "Root" refers to the item direclty
+    // it's a waste of an instruction to push the root directly
+    ReplaceWith {
+        root: u64,
+        m: u32,
+    },
+    InsertAfter {
+        root: u64,
+        n: u32,
+    },
+    InsertBefore {
+        root: u64,
+        n: u32,
+    },
+    Remove {
+        root: u64,
+    },
+
+    RemoveAllChildren,
+    CreateTextNode {
+        text: &'bump str,
+        id: u64,
+    },
+    CreateElement {
+        tag: &'bump str,
+        id: u64,
+    },
+    CreateElementNs {
+        tag: &'bump str,
+        id: u64,
+        ns: &'static str,
+    },
+    CreatePlaceholder {
+        id: u64,
+    },
+    NewEventListener {
+        event_name: &'static str,
+        scope: ScopeId,
+        mounted_node_id: u64,
+    },
+    RemoveEventListener {
+        event: &'static str,
+    },
+    SetText {
+        text: &'bump str,
+    },
+    SetAttribute {
+        field: &'static str,
+        value: &'bump str,
+        ns: Option<&'bump str>,
+    },
+    RemoveAttribute {
+        name: &'static str,
+    },
+}
+impl DomEdit<'_> {
+    pub fn is(&self, id: &'static str) -> bool {
+        match self {
+            DomEdit::InsertAfter { .. } => id == "InsertAfter",
+            DomEdit::InsertBefore { .. } => id == "InsertBefore",
+            DomEdit::PushRoot { .. } => id == "PushRoot",
+            DomEdit::PopRoot => id == "PopRoot",
+            DomEdit::AppendChildren { .. } => id == "AppendChildren",
+            DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
+            DomEdit::Remove { .. } => id == "Remove",
+            DomEdit::RemoveAllChildren => id == "RemoveAllChildren",
+            DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
+            DomEdit::CreateElement { .. } => id == "CreateElement",
+            DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
+            DomEdit::CreatePlaceholder { .. } => id == "CreatePlaceholder",
+            DomEdit::NewEventListener { .. } => id == "NewEventListener",
+            DomEdit::RemoveEventListener { .. } => id == "RemoveEventListener",
+            DomEdit::SetText { .. } => id == "SetText",
+            DomEdit::SetAttribute { .. } => id == "SetAttribute",
+            DomEdit::RemoveAttribute { .. } => id == "RemoveAttribute",
+        }
+    }
+}

+ 119 - 0
packages/core/src/noderef.rs

@@ -0,0 +1,119 @@
+// let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
+
+// let mut garbage_list = scope.consume_garbage();
+
+// let mut scopes_to_kill = Vec::new();
+// while let Some(node) = garbage_list.pop() {
+//     match &node.kind {
+//         VNodeKind::Text(_) => {
+//             self.shared.collect_garbage(node.direct_id());
+//         }
+//         VNodeKind::Anchor(_) => {
+//             self.shared.collect_garbage(node.direct_id());
+//         }
+//         VNodeKind::Suspended(_) => {
+//             self.shared.collect_garbage(node.direct_id());
+//         }
+
+//         VNodeKind::Element(el) => {
+//             self.shared.collect_garbage(node.direct_id());
+//             for child in el.children {
+//                 garbage_list.push(child);
+//             }
+//         }
+
+//         VNodeKind::Fragment(frag) => {
+//             for child in frag.children {
+//                 garbage_list.push(child);
+//             }
+//         }
+
+//         VNodeKind::Component(comp) => {
+//             // TODO: run the hook destructors and then even delete the scope
+
+//             let scope_id = comp.ass_scope.get().unwrap();
+//             let scope = self.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 {
+//     // oy kill em
+//     log::debug!("should be removing scope {:#?}", scope);
+// }
+
+// // On the primary event queue, there is no batching, we take them off one-by-one
+// let trigger = match receiver.try_next() {
+//     Ok(Some(trigger)) => trigger,
+//     _ => {
+//         // Continuously poll the future pool and the event receiver for work
+//         let mut tasks = self.shared.async_tasks.borrow_mut();
+//         let tasks_tasks = tasks.next();
+
+//         // if the new event generates work more important than our current fiber, we should consider switching
+//         // only switch if it impacts different scopes.
+//         let mut ui_receiver = self.shared.ui_event_receiver.borrow_mut();
+//         let ui_reciv_task = ui_receiver.next();
+
+//         // right now, this polling method will only catch batched set_states that don't get awaited.
+//         // However, in the future, we might be interested in batching set_states across await points
+//         let immediate_tasks = ();
+
+//         futures_util::pin_mut!(tasks_tasks);
+//         futures_util::pin_mut!(ui_reciv_task);
+
+//         // Poll the event receiver and the future pool for work
+//         // Abort early if our deadline has ran out
+//         let mut deadline = (&mut deadline_future).fuse();
+
+//         let trig = futures_util::select! {
+//             trigger = tasks_tasks => trigger,
+//             trigger = ui_reciv_task => trigger,
+
+//             // abort if we're out of time
+//             _ = deadline => { return Ok(diff_machine.mutations); }
+//         };
+
+//         trig.unwrap()
+//     }
+// };
+
+// async fn select_next_event(&mut self) -> Option<EventTrigger> {
+//     let mut receiver = self.shared.task_receiver.borrow_mut();
+
+//     // drain the in-flight events so that we can sort them out with the current events
+//     while let Ok(Some(trigger)) = receiver.try_next() {
+//         log::info!("retrieving event from receiver");
+//         let key = self.shared.make_trigger_key(&trigger);
+//         self.pending_events.insert(key, trigger);
+//     }
+
+//     if self.pending_events.is_empty() {
+//         // Continuously poll the future pool and the event receiver for work
+//         let mut tasks = self.shared.async_tasks.borrow_mut();
+//         let tasks_tasks = tasks.next();
+
+//         let mut receiver = self.shared.task_receiver.borrow_mut();
+//         let reciv_task = receiver.next();
+
+//         futures_util::pin_mut!(tasks_tasks);
+//         futures_util::pin_mut!(reciv_task);
+
+//         let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await {
+//             futures_util::future::Either::Left((trigger, _)) => trigger,
+//             futures_util::future::Either::Right((trigger, _)) => trigger,
+//         }
+//         .unwrap();
+//         let key = self.shared.make_trigger_key(&trigger);
+//         self.pending_events.insert(key, trigger);
+//     }
+
+//     // pop the most important event off
+//     let key = self.pending_events.keys().next().unwrap().clone();
+//     let trigger = self.pending_events.remove(&key).unwrap();
+
+//     Some(trigger)
+// }

+ 327 - 221
packages/core/src/nodes.rs

@@ -1,80 +1,216 @@
 //! Virtual Node Support
 //! Virtual Node Support
-//! --------------------
-//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers.
 //!
 //!
-//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
-use crate::{
-    events::VirtualEvent,
-    innerlude::{empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, FC},
+//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
+//! cheap and *very* fast to construct - building a full tree should be quick.
+
+use crate::innerlude::{
+    empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, SuspendedContext,
+    SyntheticEvent, FC,
 };
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
 use std::{
     cell::{Cell, RefCell},
     cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
     fmt::{Arguments, Debug, Formatter},
     marker::PhantomData,
     marker::PhantomData,
-    mem::ManuallyDrop,
     rc::Rc,
     rc::Rc,
 };
 };
 
 
-pub struct VNode<'src> {
-    pub kind: VNodeKind<'src>,
-
-    pub(crate) key: Option<&'src str>,
-}
-
-impl<'src> VNode<'src> {
-    pub fn key(&self) -> Option<&'src str> {
-        self.key
-    }
-    pub fn direct_id(&self) -> ElementId {
-        self.try_direct_id().unwrap()
-    }
-    pub fn try_direct_id(&self) -> Option<ElementId> {
-        match &self.kind {
-            VNodeKind::Text(el) => el.dom_id.get(),
-            VNodeKind::Element(el) => el.dom_id.get(),
-            VNodeKind::Anchor(el) => el.dom_id.get(),
-            VNodeKind::Fragment(_) => None,
-            VNodeKind::Component(_) => None,
-            VNodeKind::Suspended(_) => None,
-        }
-    }
-}
-
-/// Tools for the base unit of the virtual dom - the VNode
-/// VNodes are intended to be quickly-allocated, lightweight enum values.
+/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
 ///
 ///
-/// Components will be generating a lot of these very quickly, so we want to
-/// limit the amount of heap allocations / overly large enum sizes.
-pub enum VNodeKind<'src> {
+/// VNodes are designed to be lightweight and used with with a bump alloactor. To create a VNode, you can use either of:
+/// - the [`rsx`] macro
+/// - the [`html`] macro
+/// - the [`NodeFactory`] API
+pub enum VNode<'src> {
+    /// Text VNodes simply bump-allocated (or static) string slices
+    ///
+    /// # Example
+    ///
+    /// let node = cx.render(rsx!{ "hello" }).unwrap();
+    ///
+    /// if let VNode::Text(vtext) = node {
+    ///     assert_eq!(vtext.text, "hello");
+    ///     assert_eq!(vtext.dom_id.get(), None);
+    ///     assert_eq!(vtext.is_static, true);
+    /// }
+    /// ```
     Text(VText<'src>),
     Text(VText<'src>),
 
 
+    /// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// let node = cx.render(rsx!{
+    ///     div {
+    ///         key: "a",
+    ///         onclick: |e| log::info!("clicked"),
+    ///         hidden: "true",
+    ///         style: { background_color: "red" }
+    ///         "hello"
+    ///     }
+    /// }).unwrap();
+    /// if let VNode::Element(velement) = node {
+    ///     assert_eq!(velement.tag_name, "div");
+    ///     assert_eq!(velement.namespace, None);
+    ///     assert_eq!(velement.key, Some("a));
+    /// }
+    /// ```
     Element(&'src VElement<'src>),
     Element(&'src VElement<'src>),
 
 
+    /// Fragment nodes may contain many VNodes without a single root.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// rsx!{
+    ///     a {}
+    ///     link {}
+    ///     style {}
+    ///     "asd"
+    ///     Example {}
+    /// }
+    /// ```
     Fragment(VFragment<'src>),
     Fragment(VFragment<'src>),
 
 
+    /// Component nodes represent a mounted component with props, children, and a key.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// fn Example(cx: Context<()>) -> DomTree {
+    ///     todo!()
+    /// }
+    ///
+    /// let node = cx.render(rsx!{
+    ///     Example {}
+    /// }).unwrap();
+    ///
+    /// if let VNode::Component(vcomp) = node {
+    ///     assert_eq!(vcomp.user_fc, Example as *const ());
+    /// }
+    /// ```
     Component(&'src VComponent<'src>),
     Component(&'src VComponent<'src>),
 
 
-    Suspended(VSuspended),
+    /// Suspended VNodes represent chunks of the UI tree that are not yet ready to be displayed.
+    ///
+    /// These nodes currently can only be constructed via the [`use_suspense`] hook.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// rsx!{
+    /// }
+    /// ```
+    Suspended(&'src VSuspended<'src>),
 
 
+    /// Anchors are a type of placeholder VNode used when fragments don't contain any children.
+    ///
+    /// Anchors cannot be directly constructed via public APIs.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// let node = cx.render(rsx! ( Fragment {} )).unwrap();    
+    /// if let VNode::Fragment(frag) = node {
+    ///     let root = &frag.children[0];
+    ///     assert_eq!(root, VNode::Anchor);
+    /// }
+    /// ```
     Anchor(VAnchor),
     Anchor(VAnchor),
 }
 }
 
 
+impl<'src> VNode<'src> {
+    /// Get the VNode's "key" used in the keyed diffing algorithm.
+    pub fn key(&self) -> Option<&'src str> {
+        match &self {
+            VNode::Element(el) => el.key,
+            VNode::Component(c) => c.key,
+            VNode::Fragment(f) => f.key,
+            VNode::Text(_t) => None,
+            VNode::Suspended(_s) => None,
+            VNode::Anchor(_f) => None,
+        }
+    }
+
+    /// Get the ElementID of the mounted VNode.
+    ///
+    /// Panics if the mounted ID is None or if the VNode is not represented by a single Element.
+    pub fn mounted_id(&self) -> ElementId {
+        self.try_mounted_id().unwrap()
+    }
+
+    /// Try to get the ElementID of the mounted VNode.
+    ///
+    /// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component)
+    pub fn try_mounted_id(&self) -> Option<ElementId> {
+        match &self {
+            VNode::Text(el) => el.dom_id.get(),
+            VNode::Element(el) => el.dom_id.get(),
+            VNode::Anchor(el) => el.dom_id.get(),
+            VNode::Suspended(el) => el.dom_id.get(),
+            VNode::Fragment(_) => None,
+            VNode::Component(_) => None,
+        }
+    }
+}
+
+/// A placeholder node only generated when Fragments don't have any children.
 pub struct VAnchor {
 pub struct VAnchor {
     pub dom_id: Cell<Option<ElementId>>,
     pub dom_id: Cell<Option<ElementId>>,
 }
 }
 
 
+/// A bump-alloacted string slice and metadata.
 pub struct VText<'src> {
 pub struct VText<'src> {
     pub text: &'src str,
     pub text: &'src str,
+
     pub dom_id: Cell<Option<ElementId>>,
     pub dom_id: Cell<Option<ElementId>>,
+
     pub is_static: bool,
     pub is_static: bool,
 }
 }
 
 
+/// A list of VNodes with no single root.
 pub struct VFragment<'src> {
 pub struct VFragment<'src> {
+    pub key: Option<&'src str>,
+
     pub children: &'src [VNode<'src>],
     pub children: &'src [VNode<'src>],
+
     pub is_static: bool,
     pub is_static: bool,
 }
 }
 
 
+/// An element like a "div" with children, listeners, and attributes.
+pub struct VElement<'a> {
+    pub tag_name: &'static str,
+
+    pub namespace: Option<&'static str>,
+
+    pub key: Option<&'a str>,
+
+    pub dom_id: Cell<Option<ElementId>>,
+
+    pub listeners: &'a [Listener<'a>],
+
+    pub attributes: &'a [Attribute<'a>],
+
+    pub children: &'a [VNode<'a>],
+}
+
+/// A trait for any generic Dioxus Element.
+///
+/// This trait provides the ability to use custom elements in the `rsx!` macro.
+///
+/// ```rust
+/// struct my_element;
+///
+/// impl DioxusElement for my_element {
+///     const TAG_NAME: "my_element";
+///     const NAME_SPACE: None;
+/// }
+///
+/// let _ = rsx!{
+///     my_element {}
+/// };
+/// ```
 pub trait DioxusElement {
 pub trait DioxusElement {
     const TAG_NAME: &'static str;
     const TAG_NAME: &'static str;
     const NAME_SPACE: Option<&'static str>;
     const NAME_SPACE: Option<&'static str>;
@@ -88,22 +224,6 @@ pub trait DioxusElement {
     }
     }
 }
 }
 
 
-pub struct VElement<'a> {
-    // tag is always static
-    pub tag_name: &'static str,
-    pub namespace: Option<&'static str>,
-    pub dom_id: Cell<Option<ElementId>>,
-
-    pub static_listeners: bool,
-    pub listeners: &'a [Listener<'a>],
-
-    pub static_attrs: bool,
-    pub attributes: &'a [Attribute<'a>],
-
-    pub static_children: bool,
-    pub children: &'a [VNode<'a>],
-}
-
 /// An attribute on a DOM node, such as `id="my-thing"` or
 /// An attribute on a DOM node, such as `id="my-thing"` or
 /// `href="https://example.com"`.
 /// `href="https://example.com"`.
 #[derive(Clone, Debug)]
 #[derive(Clone, Debug)]
@@ -116,35 +236,35 @@ pub struct Attribute<'a> {
 
 
     pub is_volatile: bool,
     pub is_volatile: bool,
 
 
-    // Doesn't exist in the html spec, mostly used to denote "style" tags - could be for any type of group
+    // Doesn't exist in the html spec.
+    // Used in Dioxus to denote "style" tags.
     pub namespace: Option<&'static str>,
     pub namespace: Option<&'static str>,
 }
 }
 
 
 /// An event listener.
 /// An event listener.
 /// IE onclick, onkeydown, etc
 /// IE onclick, onkeydown, etc
 pub struct Listener<'bump> {
 pub struct Listener<'bump> {
-    /// The type of event to listen for.
-    pub(crate) event: &'static str,
-
     pub mounted_node: Cell<Option<ElementId>>,
     pub mounted_node: Cell<Option<ElementId>>,
 
 
-    pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(VirtualEvent) + 'bump>>>,
-}
+    /// The type of event to listen for.
+    ///
+    /// IE "onclick" - whatever the renderer needs to attach the listener by name.
+    pub event: &'static str,
 
 
-impl Listener<'_> {
-    // serialize the listener event stuff to a string
-    pub fn serialize(&self) {
-        //
-    }
-    pub fn deserialize() {
-        //
-    }
+    pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(SyntheticEvent) + 'bump>>>,
 }
 }
 
 
 /// Virtual Components for custom user-defined components
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 /// Only supports the functional syntax
 pub struct VComponent<'src> {
 pub struct VComponent<'src> {
-    pub ass_scope: Cell<Option<ScopeId>>,
+    pub key: Option<&'src str>,
+
+    pub associated_scope: Cell<Option<ScopeId>>,
+
+    pub is_static: bool,
+
+    // Function pointer to the FC that was used to generate this component
+    pub user_fc: *const (),
 
 
     pub(crate) caller: Rc<dyn Fn(&Scope) -> DomTree>,
     pub(crate) caller: Rc<dyn Fn(&Scope) -> DomTree>,
 
 
@@ -154,19 +274,17 @@ pub struct VComponent<'src> {
 
 
     pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
     pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
 
 
-    pub is_static: bool,
+    pub(crate) can_memoize: bool,
 
 
-    pub can_memoize: bool,
-
-    // a pointer into the bump arena (given by the 'src lifetime)
+    // Raw pointer into the bump arena for the props of the component
     pub(crate) raw_props: *const (),
     pub(crate) raw_props: *const (),
-
-    // a pointer to the raw fn typ
-    pub(crate) user_fc: *const (),
 }
 }
 
 
-pub struct VSuspended {
-    pub node: Rc<Cell<Option<ElementId>>>,
+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>>>>,
 }
 }
 
 
 /// This struct provides an ergonomic API to quickly build VNodes.
 /// This struct provides an ergonomic API to quickly build VNodes.
@@ -196,26 +314,20 @@ impl<'a> NodeFactory<'a> {
     }
     }
 
 
     pub fn unstable_place_holder() -> VNode<'static> {
     pub fn unstable_place_holder() -> VNode<'static> {
-        VNode {
-            key: None,
-            kind: VNodeKind::Text(VText {
-                text: "",
-                dom_id: empty_cell(),
-                is_static: true,
-            }),
-        }
+        VNode::Text(VText {
+            text: "",
+            dom_id: empty_cell(),
+            is_static: true,
+        })
     }
     }
 
 
-    /// Used in a place or two to make it easier to build vnodes from dummy text
+    /// Directly pass in text blocks without the need to use the format_args macro.
     pub fn static_text(&self, text: &'static str) -> VNode<'a> {
     pub fn static_text(&self, text: &'static str) -> VNode<'a> {
-        VNode {
-            key: None,
-            kind: VNodeKind::Text(VText {
-                dom_id: empty_cell(),
-                text,
-                is_static: true,
-            }),
-        }
+        VNode::Text(VText {
+            dom_id: empty_cell(),
+            text,
+            is_static: true,
+        })
     }
     }
 
 
     /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
     /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
@@ -226,9 +338,9 @@ impl<'a> NodeFactory<'a> {
             Some(static_str) => (static_str, true),
             Some(static_str) => (static_str, true),
             None => {
             None => {
                 use bumpalo::core_alloc::fmt::Write;
                 use bumpalo::core_alloc::fmt::Write;
-                let mut s = bumpalo::collections::String::new_in(self.bump());
-                s.write_fmt(args).unwrap();
-                (s.into_bump_str(), false)
+                let mut str_buf = bumpalo::collections::String::new_in(self.bump());
+                str_buf.write_fmt(args).unwrap();
+                (str_buf.into_bump_str(), false)
             }
             }
         }
         }
     }
     }
@@ -237,14 +349,12 @@ impl<'a> NodeFactory<'a> {
     ///
     ///
     pub fn text(&self, args: Arguments) -> VNode<'a> {
     pub fn text(&self, args: Arguments) -> VNode<'a> {
         let (text, is_static) = self.raw_text(args);
         let (text, is_static) = self.raw_text(args);
-        VNode {
-            key: None,
-            kind: VNodeKind::Text(VText {
-                text,
-                is_static,
-                dom_id: empty_cell(),
-            }),
-        }
+
+        VNode::Text(VText {
+            text,
+            is_static,
+            dom_id: empty_cell(),
+        })
     }
     }
 
 
     pub fn element<L, A, V>(
     pub fn element<L, A, V>(
@@ -272,7 +382,7 @@ impl<'a> NodeFactory<'a> {
 
 
     pub fn raw_element<L, A, V>(
     pub fn raw_element<L, A, V>(
         &self,
         &self,
-        tag: &'static str,
+        tag_name: &'static str,
         namespace: Option<&'static str>,
         namespace: Option<&'static str>,
         listeners: L,
         listeners: L,
         attributes: A,
         attributes: A,
@@ -295,31 +405,15 @@ impl<'a> NodeFactory<'a> {
 
 
         let key = key.map(|f| self.raw_text(f).0);
         let key = key.map(|f| self.raw_text(f).0);
 
 
-        VNode {
+        VNode::Element(self.bump().alloc(VElement {
+            tag_name,
             key,
             key,
-            kind: VNodeKind::Element(self.bump().alloc(VElement {
-                tag_name: tag,
-                namespace,
-                listeners,
-                attributes,
-                children,
-                dom_id: empty_cell(),
-
-                // todo: wire up more constization
-                static_listeners: false,
-                static_attrs: false,
-                static_children: false,
-            })),
-        }
-    }
-
-    pub fn suspended() -> VNode<'static> {
-        VNode {
-            key: None,
-            kind: VNodeKind::Suspended(VSuspended {
-                node: Rc::new(empty_cell()),
-            }),
-        }
+            namespace,
+            listeners,
+            attributes,
+            children,
+            dom_id: empty_cell(),
+        }))
     }
     }
 
 
     pub fn attr(
     pub fn attr(
@@ -339,22 +433,6 @@ impl<'a> NodeFactory<'a> {
         }
         }
     }
     }
 
 
-    pub fn attr_with_alloc_val(
-        &self,
-        name: &'static str,
-        val: &'a str,
-        namespace: Option<&'static str>,
-        is_volatile: bool,
-    ) -> Attribute<'a> {
-        Attribute {
-            name,
-            value: val,
-            is_static: false,
-            namespace,
-            is_volatile,
-        }
-    }
-
     pub fn component<P, V>(
     pub fn component<P, V>(
         &self,
         &self,
         component: FC<P>,
         component: FC<P>,
@@ -367,14 +445,9 @@ impl<'a> NodeFactory<'a> {
         V: 'a + AsRef<[VNode<'a>]>,
         V: 'a + AsRef<[VNode<'a>]>,
     {
     {
         let bump = self.bump();
         let bump = self.bump();
-
-        // We don't want the fat part of the fat pointer
-        // This function does static dispatch so we don't need any VTable stuff
         let children: &'a V = bump.alloc(children);
         let children: &'a V = bump.alloc(children);
         let children = children.as_ref();
         let children = children.as_ref();
-
         let props = bump.alloc(props);
         let props = bump.alloc(props);
-
         let raw_props = props as *mut P as *mut ();
         let raw_props = props as *mut P as *mut ();
         let user_fc = component as *const ();
         let user_fc = component as *const ();
 
 
@@ -403,42 +476,50 @@ impl<'a> NodeFactory<'a> {
             }
             }
         }));
         }));
 
 
-        // create a closure to drop the props
-        let mut has_dropped = false;
-        let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
-            move || unsafe {
-                if !has_dropped {
-                    let real_other = raw_props as *mut _ as *mut P;
-                    let b = BumpBox::from_raw(real_other);
-                    std::mem::drop(b);
+        let drop_props = {
+            // create a closure to drop the props
+            let mut has_dropped = false;
+
+            let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
+                move || unsafe {
+                    if !has_dropped {
+                        let real_other = raw_props as *mut _ as *mut P;
+                        let b = BumpBox::from_raw(real_other);
+                        std::mem::drop(b);
 
 
-                    has_dropped = true;
+                        has_dropped = true;
+                    }
                 }
                 }
-            }
-        });
-        let drop_props = unsafe { BumpBox::from_raw(drop_props) };
+            });
+
+            let drop_props = unsafe { BumpBox::from_raw(drop_props) };
+
+            RefCell::new(Some(drop_props))
+        };
 
 
         let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
         let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
 
 
         let key = key.map(|f| self.raw_text(f).0);
         let key = key.map(|f| self.raw_text(f).0);
 
 
-        VNode {
+        let caller = NodeFactory::create_component_caller(component, raw_props);
+
+        let can_memoize = children.len() == 0 && P::IS_STATIC;
+
+        VNode::Component(bump.alloc_with(|| VComponent {
+            user_fc,
+            comparator,
+            raw_props,
+            children,
+            caller,
+            is_static,
             key,
             key,
-            kind: VNodeKind::Component(bump.alloc_with(|| VComponent {
-                user_fc,
-                comparator,
-                raw_props,
-                children,
-                caller: NodeFactory::create_component_caller(component, raw_props),
-                is_static,
-                drop_props: RefCell::new(Some(drop_props)),
-                can_memoize: P::IS_STATIC,
-                ass_scope: Cell::new(None),
-            })),
-        }
+            can_memoize,
+            drop_props,
+            associated_scope: Cell::new(None),
+        }))
     }
     }
 
 
-    pub fn create_component_caller<'g, P: 'g>(
+    pub(crate) fn create_component_caller<'g, P: 'g>(
         component: FC<P>,
         component: FC<P>,
         raw_props: *const (),
         raw_props: *const (),
     ) -> Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r>> {
     ) -> Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r>> {
@@ -450,12 +531,8 @@ impl<'a> NodeFactory<'a> {
                 props: safe_props,
                 props: safe_props,
                 scope: scp,
                 scope: scp,
             };
             };
-
             let res = component(cx);
             let res = component(cx);
-
-            let g2 = unsafe { std::mem::transmute(res) };
-
-            g2
+            unsafe { std::mem::transmute(res) }
         });
         });
         unsafe { std::mem::transmute::<_, Captured<'static>>(caller) }
         unsafe { std::mem::transmute::<_, Captured<'static>>(caller) }
     }
     }
@@ -463,13 +540,30 @@ impl<'a> NodeFactory<'a> {
     pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> {
     pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> {
         let children = node_iter.into_vnode_list(self);
         let children = node_iter.into_vnode_list(self);
 
 
-        VNode {
+        // TODO
+        // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
+        //
+        // if cfg!(debug_assertions) {
+        //     if children.len() > 1 {
+        //         if children.last().unwrap().key().is_none() {
+        //             log::error!(
+        //                 r#"
+        // Warning: Each child in an array or iterator should have a unique "key" prop.
+        // Not providing a key will lead to poor performance with lists.
+        // See docs.rs/dioxus for more information.
+        // ---
+        // To help you identify where this error is coming from, we've generated a backtrace.
+        //                         "#,
+        //             );
+        //         }
+        //     }
+        // }
+
+        VNode::Fragment(VFragment {
+            children,
             key: None,
             key: None,
-            kind: VNodeKind::Fragment(VFragment {
-                children,
-                is_static: false,
-            }),
-        }
+            is_static: false,
+        })
     }
     }
 }
 }
 
 
@@ -508,50 +602,51 @@ where
             nodes.push(node.into_vnode(cx));
             nodes.push(node.into_vnode(cx));
         }
         }
 
 
-        if cfg!(debug_assertions) {
-            if nodes.len() > 1 {
-                if nodes.last().unwrap().key().is_none() {
-                    log::error!(
-                        r#"
-        Warning: Each child in an array or iterator should have a unique "key" prop.
-        Not providing a key will lead to poor performance with lists.
-        See docs.rs/dioxus for more information.
-        ---
-        To help you identify where this error is coming from, we've generated a backtrace.
-                                "#,
-                    );
-                }
-            }
-        }
-
         if nodes.len() == 0 {
         if nodes.len() == 0 {
-            nodes.push(VNode {
-                kind: VNodeKind::Anchor(VAnchor {
-                    dom_id: empty_cell(),
-                }),
-                key: None,
-            });
+            nodes.push(VNode::Anchor(VAnchor {
+                dom_id: empty_cell(),
+            }));
         }
         }
 
 
         nodes.into_bump_slice()
         nodes.into_bump_slice()
     }
     }
 }
 }
 
 
+/// Child nodes of the parent component.
+///
+/// # Example
+///
+/// ```rust
+/// let children = cx.children();
+/// let first_node = &children[0];
+/// rsx!{
+///     h1 { {first_node} }
+///     p { {&children[1..]} }
+/// }
+/// ```
+///
 pub struct ScopeChildren<'a>(pub &'a [VNode<'a>]);
 pub struct ScopeChildren<'a>(pub &'a [VNode<'a>]);
+
 impl Copy for ScopeChildren<'_> {}
 impl Copy for ScopeChildren<'_> {}
+
 impl<'a> Clone for ScopeChildren<'a> {
 impl<'a> Clone for ScopeChildren<'a> {
     fn clone(&self) -> Self {
     fn clone(&self) -> Self {
         ScopeChildren(self.0)
         ScopeChildren(self.0)
     }
     }
 }
 }
+
 impl ScopeChildren<'_> {
 impl ScopeChildren<'_> {
-    pub unsafe fn extend_lifetime(self) -> ScopeChildren<'static> {
+    // dangerous method - used to fix the associated lifetime
+    pub(crate) unsafe fn extend_lifetime(self) -> ScopeChildren<'static> {
         std::mem::transmute(self)
         std::mem::transmute(self)
     }
     }
-    pub unsafe fn unextend_lfetime<'a>(self) -> ScopeChildren<'a> {
+
+    // dangerous method - used to fix the associated lifetime
+    pub(crate) unsafe fn shorten_lifetime<'a>(self) -> ScopeChildren<'a> {
         std::mem::transmute(self)
         std::mem::transmute(self)
     }
     }
 }
 }
+
 impl<'a> IntoVNodeList<'a> for ScopeChildren<'a> {
 impl<'a> IntoVNodeList<'a> for ScopeChildren<'a> {
     fn into_vnode_list(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] {
     fn into_vnode_list(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] {
         self.0
         self.0
@@ -638,6 +733,7 @@ impl IntoVNode<'_> for Option<()> {
         cx.fragment_from_iter(None as Option<VNode>)
         cx.fragment_from_iter(None as Option<VNode>)
     }
     }
 }
 }
+
 impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
 impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
         match self {
         match self {
@@ -666,14 +762,24 @@ impl Debug for NodeFactory<'_> {
 
 
 impl Debug for VNode<'_> {
 impl Debug for VNode<'_> {
     fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
     fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
-        match &self.kind {
-            VNodeKind::Element(el) => write!(s, "VElement {{ name: {} }}", el.tag_name),
-            VNodeKind::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
-            VNodeKind::Anchor(a) => write!(s, "VAnchor"),
-
-            VNodeKind::Fragment(_) => write!(s, "fragment"),
-            VNodeKind::Suspended { .. } => write!(s, "suspended"),
-            VNodeKind::Component(_) => write!(s, "component"),
+        match &self {
+            VNode::Element(el) => {
+                //
+                s.debug_struct("VElement")
+                    .field("name", &el.tag_name)
+                    .field("key", &el.key)
+                    .finish()
+            }
+            VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
+            VNode::Anchor(_) => write!(s, "VAnchor"),
+
+            VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children),
+            VNode::Suspended { .. } => write!(s, "VSuspended"),
+            VNode::Component(comp) => write!(
+                s,
+                "VComponent {{ fc: {:?}, children: {:?} }}",
+                comp.user_fc, comp.children
+            ),
         }
         }
     }
     }
 }
 }

+ 813 - 0
packages/core/src/scheduler.rs

@@ -0,0 +1,813 @@
+/*
+Welcome to Dioxus's cooperative, priority-based scheduler.
+
+I hope you enjoy your stay.
+
+Some essential reading:
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
+- https://github.com/WICG/is-input-pending
+- https://web.dev/rail/
+- https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
+
+# What's going on?
+
+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."
+
+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).
+The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
+different types of work simultaneously.
+
+# How does it work?
+
+Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
+React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
+React, and the UI still stays snappy!
+
+While it's straightforward to run code ASAP and be as "fast as possible", what's not  _not_ straightforward is how to do
+this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
+has time to paint and run animations. When the browser is finished, we can step in and continue our work.
+
+React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
+necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
+"commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
+our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
+so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
+
+React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
+now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
+before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
+are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
+future is paused and saved (self-referentially).
+
+# Priorty System
+
+So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
+doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
+priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
+a "lower" priority. That way, we can interrupt long-running low-prioty work with short-running high-priority work.
+
+React's priority system is quite complex.
+
+There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
+uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
+
+For Dioxus, we're going with a 4 tier priorty system:
+- Sync: Things that need to be done by the next frame, like TextInput on controlled elements
+- High: for events that block all others - clicks, keyboard, and hovers
+- Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
+- Low: set_state called asynchronously, and anything generated by suspense
+
+In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
+work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
+as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
+do anything too arduous from onInput.
+
+For the rest, we defer to the rIC period and work down each queue from high to low.
+*/
+use crate::heuristics::*;
+use crate::innerlude::*;
+use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use futures_util::stream::FuturesUnordered;
+use futures_util::{future::FusedFuture, pin_mut, Future, FutureExt, StreamExt};
+use fxhash::{FxHashMap, FxHashSet};
+use indexmap::IndexSet;
+use slab::Slab;
+use smallvec::SmallVec;
+use std::{
+    any::{Any, TypeId},
+    cell::{Cell, RefCell, RefMut, UnsafeCell},
+    collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque},
+    fmt::Display,
+    pin::Pin,
+    rc::Rc,
+};
+
+#[derive(Clone)]
+pub struct EventChannel {
+    pub task_counter: Rc<Cell<u64>>,
+    pub sender: UnboundedSender<SchedulerMsg>,
+    pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
+    pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
+    pub get_shared_context: Rc<dyn Fn(ScopeId, TypeId) -> Option<Rc<dyn Any>>>,
+}
+
+pub enum SchedulerMsg {
+    Immediate(ScopeId),
+    UiEvent(UserEvent),
+    SubmitTask(FiberTask, u64),
+    ToggleTask(u64),
+    PauseTask(u64),
+    ResumeTask(u64),
+    DropTask(u64),
+}
+
+/// The scheduler holds basically everything around "working"
+///
+/// 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
+/// 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
+/// pending DiffInstructions, Mutations, and their underlying Scope. It's okay for us to be self-referential with this data, provided we don't priority
+/// task shift to a higher priority task that needs mutable access to the same scopes.
+///
+/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
+///
+///
+pub(crate) struct Scheduler {
+    /// All mounted components are arena allocated to make additions, removals, and references easy to work with
+    /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
+    ///
+    /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
+    /// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
+    pub pool: ResourcePool,
+
+    pub heuristics: HeuristicsEngine,
+
+    pub receiver: UnboundedReceiver<SchedulerMsg>,
+
+    // Garbage stored
+    pub pending_garbage: FxHashSet<ScopeId>,
+
+    // In-flight futures
+    pub async_tasks: FuturesUnordered<FiberTask>,
+
+    // scheduler stuff
+    pub current_priority: EventPriority,
+
+    pub ui_events: VecDeque<UserEvent>,
+
+    pub pending_immediates: VecDeque<ScopeId>,
+
+    pub pending_tasks: VecDeque<UserEvent>,
+
+    pub garbage_scopes: HashSet<ScopeId>,
+
+    pub lanes: [PriorityLane; 4],
+}
+
+impl Scheduler {
+    pub fn new() -> Self {
+        /*
+        Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
+        Perhaps this should be configurable from some external config?
+        */
+        let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
+        let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(2000)));
+
+        let heuristics = HeuristicsEngine::new();
+
+        let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
+        let task_counter = Rc::new(Cell::new(0));
+
+        let channel = EventChannel {
+            task_counter: task_counter.clone(),
+            sender: sender.clone(),
+            schedule_any_immediate: {
+                let sender = sender.clone();
+                Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap())
+            },
+            submit_task: {
+                let sender = sender.clone();
+                Rc::new(move |fiber_task| {
+                    let task_id = task_counter.get();
+                    task_counter.set(task_id + 1);
+                    sender
+                        .unbounded_send(SchedulerMsg::SubmitTask(fiber_task, task_id))
+                        .unwrap();
+                    TaskHandle {
+                        our_id: task_id,
+                        sender: sender.clone(),
+                    }
+                })
+            },
+            get_shared_context: {
+                let components = components.clone();
+                Rc::new(move |id, ty| {
+                    let components = unsafe { &*components.get() };
+                    let mut search: Option<&Scope> = components.get(id.0);
+                    while let Some(inner) = search.take() {
+                        if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
+                            return Some(shared.clone());
+                        } else {
+                            search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
+                        }
+                    }
+                    None
+                })
+            },
+        };
+
+        let pool = ResourcePool {
+            components: components.clone(),
+            raw_elements,
+            channel,
+        };
+
+        Self {
+            pool,
+            receiver,
+
+            async_tasks: FuturesUnordered::new(),
+
+            pending_garbage: FxHashSet::default(),
+
+            heuristics,
+
+            // a storage for our receiver to dump into
+            ui_events: VecDeque::new(),
+
+            pending_immediates: VecDeque::new(),
+
+            pending_tasks: VecDeque::new(),
+
+            garbage_scopes: HashSet::new(),
+
+            current_priority: EventPriority::Low,
+
+            // a dedicated fiber for each priority
+            lanes: [
+                PriorityLane::new(),
+                PriorityLane::new(),
+                PriorityLane::new(),
+                PriorityLane::new(),
+            ],
+        }
+    }
+
+    pub fn manually_poll_events(&mut self) {
+        while let Ok(Some(msg)) = self.receiver.try_next() {
+            self.handle_channel_msg(msg);
+        }
+    }
+
+    // Converts UI events into dirty scopes with various priorities
+    pub fn consume_pending_events(&mut self) {
+        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(_) => {}
+                    };
+
+                    scope.call_listener(trigger.event, element);
+                    // let receiver = self.immediate_receiver.clone();
+                    // let mut receiver = receiver.borrow_mut();
+
+                    // // Drain the immediates into the dirty scopes, setting the appropiate priorities
+                    // while let Ok(Some(dirty_scope)) = receiver.try_next() {
+                    //     self.add_dirty_scope(dirty_scope, trigger.priority)
+                    // }
+                }
+            }
+        }
+    }
+
+    // nothing to do, no events on channels, no work
+    pub fn has_any_work(&self) -> bool {
+        let pending_lanes = self.lanes.iter().find(|f| f.has_work()).is_some();
+        pending_lanes || self.has_pending_events()
+    }
+
+    pub fn has_pending_events(&self) -> bool {
+        self.ui_events.len() > 0
+    }
+
+    fn shift_priorities(&mut self) {
+        self.current_priority = match (
+            self.lanes[0].has_work(),
+            self.lanes[1].has_work(),
+            self.lanes[2].has_work(),
+            self.lanes[3].has_work(),
+        ) {
+            (true, _, _, _) => EventPriority::Immediate,
+            (false, true, _, _) => EventPriority::High,
+            (false, false, true, _) => EventPriority::Medium,
+            (false, false, false, _) => EventPriority::Low,
+        };
+    }
+
+    /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
+    fn balance_lanes(&mut self) {}
+
+    fn load_current_lane(&mut self) -> &mut PriorityLane {
+        match self.current_priority {
+            EventPriority::Immediate => todo!(),
+            EventPriority::High => todo!(),
+            EventPriority::Medium => todo!(),
+            EventPriority::Low => todo!(),
+        }
+    }
+
+    fn save_work(&mut self, lane: SavedDiffWork) {
+        let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) };
+        self.load_current_lane().saved_state = Some(saved);
+    }
+
+    fn load_work(&mut self) -> SavedDiffWork<'static> {
+        match self.current_priority {
+            EventPriority::Immediate => todo!(),
+            EventPriority::High => todo!(),
+            EventPriority::Medium => todo!(),
+            EventPriority::Low => todo!(),
+        }
+    }
+
+    /// Work the scheduler down, not polling any ongoing tasks.
+    ///
+    /// Will use the standard priority-based scheduling, batching, etc, but just won't interact with the async reactor.
+    pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
+        let mut committed_mutations = Vec::new();
+
+        self.manually_poll_events();
+
+        if !self.has_any_work() {
+            self.clean_up_garbage();
+            return committed_mutations;
+        }
+
+        self.consume_pending_events();
+
+        while self.has_any_work() {
+            self.shift_priorities();
+            self.work_on_current_lane(&mut || false, &mut committed_mutations);
+        }
+
+        committed_mutations
+    }
+
+    /// The primary workhorse of the VirtualDOM.
+    ///
+    /// Uses some fairly complex logic to schedule what work should be produced.
+    ///
+    /// Returns a list of successful mutations.
+    pub async fn work_with_deadline<'a>(
+        &'a mut self,
+        mut deadline_reached: Pin<Box<impl FusedFuture<Output = ()>>>,
+    ) -> Vec<Mutations<'a>> {
+        /*
+        Strategy:
+        - When called, check for any UI events that might've been received since the last frame.
+        - Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
+
+        - If there are any pending discrete events, then elevate our priorty level. If our priority level is already "high,"
+            then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
+            will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
+            to bump the other work out of the way, or choose to process it so we don't have any conflicts.
+            'static components have a leg up here since their work can be re-used among multiple scopes.
+            "High priority" is only for blocking! Should only be used on "clicks"
+
+        - If there are no pending discrete events, then check for continuous events. These can be completely batched
+
+
+        Open questions:
+        - what if we get two clicks from the component during the same slice?
+            - should we batch?
+            - react says no - they are continuous
+            - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
+        */
+        let mut committed_mutations = Vec::<Mutations<'static>>::new();
+
+        loop {
+            // Internalize any pending work since the last time we ran
+            self.manually_poll_events();
+
+            // 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 {
+                    return committed_mutations;
+                }
+            }
+
+            // Create work from the pending event queue
+            self.consume_pending_events();
+
+            // shift to the correct lane
+            self.shift_priorities();
+
+            let mut deadline_reached = || (&mut deadline_reached).now_or_never().is_some();
+
+            let finished_before_deadline =
+                self.work_on_current_lane(&mut deadline_reached, &mut committed_mutations);
+
+            if !finished_before_deadline {
+                break;
+            }
+        }
+
+        committed_mutations
+    }
+
+    /// Load the current lane, and work on it, periodically checking in if the deadline has been reached.
+    ///
+    /// 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,
+        mutations: &mut Vec<Mutations>,
+    ) -> bool {
+        // Work through the current subtree, and commit the results when it finishes
+        // When the deadline expires, give back the work
+        let saved_state = self.load_work();
+
+        // We have to split away some parts of ourself - current lane is borrowed mutably
+        let mut shared = self.pool.clone();
+        let mut machine = unsafe { saved_state.promote(&mut shared) };
+
+        if machine.stack.is_empty() {
+            let shared = self.pool.clone();
+
+            self.current_lane().dirty_scopes.sort_by(|a, b| {
+                let h1 = shared.get_scope(*a).unwrap().height;
+                let h2 = shared.get_scope(*b).unwrap().height;
+                h1.cmp(&h2)
+            });
+
+            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 });
+            }
+        }
+
+        let deadline_expired = machine.work(deadline_reached);
+
+        let machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) };
+        let mut saved = machine.save();
+
+        if deadline_expired {
+            self.save_work(saved);
+            false
+        } else {
+            for node in saved.seen_scopes.drain() {
+                self.current_lane().dirty_scopes.remove(&node);
+            }
+
+            let mut new_mutations = Mutations::new();
+            std::mem::swap(&mut new_mutations, &mut saved.mutations);
+
+            mutations.push(new_mutations);
+            self.save_work(saved);
+            true
+        }
+    }
+
+    // waits for a trigger, canceling early if the deadline is reached
+    // returns true if the deadline was reached
+    // does not return the trigger, but caches it in the scheduler
+    pub async fn wait_for_any_trigger(
+        &mut self,
+        deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
+    ) -> bool {
+        use futures_util::future::{select, Either};
+
+        let event_fut = async {
+            match select(self.receiver.next(), self.async_tasks.next()).await {
+                Either::Left((msg, _other)) => {
+                    self.handle_channel_msg(msg.unwrap());
+                }
+                Either::Right((task, _other)) => {
+                    // do nothing, async task will likely generate a set of scheduler messages
+                }
+            }
+        };
+
+        pin_mut!(event_fut);
+
+        match select(event_fut, deadline).await {
+            Either::Left((msg, _other)) => false,
+            Either::Right((deadline, _)) => true,
+        }
+    }
+
+    pub fn current_lane(&mut self) -> &mut PriorityLane {
+        match self.current_priority {
+            EventPriority::Immediate => &mut self.lanes[0],
+            EventPriority::High => &mut self.lanes[1],
+            EventPriority::Medium => &mut self.lanes[2],
+            EventPriority::Low => &mut self.lanes[3],
+        }
+    }
+
+    pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
+        match msg {
+            SchedulerMsg::Immediate(_) => todo!(),
+            SchedulerMsg::UiEvent(_) => todo!(),
+
+            //
+            SchedulerMsg::SubmitTask(_, _) => todo!(),
+            SchedulerMsg::ToggleTask(_) => todo!(),
+            SchedulerMsg::PauseTask(_) => todo!(),
+            SchedulerMsg::ResumeTask(_) => todo!(),
+            SchedulerMsg::DropTask(_) => todo!(),
+        }
+    }
+
+    fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
+        todo!()
+        // match priority {
+        //     EventPriority::High => self.high_priorty.dirty_scopes.insert(scope),
+        //     EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope),
+        //     EventPriority::Low => self.low_priority.dirty_scopes.insert(scope),
+        // };
+    }
+
+    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 {
+    pub dirty_scopes: IndexSet<ScopeId>,
+    pub saved_state: Option<SavedDiffWork<'static>>,
+    pub in_progress: bool,
+}
+
+impl PriorityLane {
+    pub fn new() -> Self {
+        Self {
+            saved_state: None,
+            dirty_scopes: Default::default(),
+            in_progress: false,
+        }
+    }
+
+    fn has_work(&self) -> bool {
+        todo!()
+    }
+
+    fn work(&mut self) {
+        let scope = self.dirty_scopes.pop();
+    }
+}
+
+pub struct TaskHandle {
+    pub sender: UnboundedSender<SchedulerMsg>,
+    pub our_id: u64,
+}
+
+impl TaskHandle {
+    /// Toggles this coroutine off/on.
+    ///
+    /// This method is not synchronous - your task will not stop immediately.
+    pub fn toggle(&self) {
+        self.sender
+            .unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
+            .unwrap()
+    }
+
+    /// This method is not synchronous - your task will not stop immediately.
+    pub fn resume(&self) {
+        self.sender
+            .unbounded_send(SchedulerMsg::ResumeTask(self.our_id))
+            .unwrap()
+    }
+
+    /// This method is not synchronous - your task will not stop immediately.
+    pub fn stop(&self) {
+        self.sender
+            .unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
+            .unwrap()
+    }
+
+    /// This method is not synchronous - your task will not stop immediately.
+    pub fn restart(&self) {
+        self.sender
+            .unbounded_send(SchedulerMsg::ToggleTask(self.our_id))
+            .unwrap()
+    }
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct ScopeId(pub usize);
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct ElementId(pub usize);
+impl Display for ElementId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl ElementId {
+    pub fn as_u64(self) -> u64 {
+        self.0 as u64
+    }
+}
+
+/// Priority of Event Triggers.
+///
+/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
+/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
+/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
+///
+/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
+///
+/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
+/// we keep it simple, and just use a 3-tier priority system.
+///
+/// - NoPriority = 0
+/// - LowPriority = 1
+/// - NormalPriority = 2
+/// - UserBlocking = 3
+/// - HighPriority = 4
+/// - ImmediatePriority = 5
+///
+/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
+/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
+/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
+pub enum EventPriority {
+    /// Work that must be completed during the EventHandler phase.
+    ///
+    /// Currently this is reserved for controlled inputs.
+    Immediate = 3,
+
+    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
+    ///
+    /// This is typically reserved for things like user interaction.
+    ///
+    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
+    High = 2,
+
+    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
+    /// than "High Priority" events and will take presedence over low priority events.
+    ///
+    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
+    ///
+    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
+    Medium = 1,
+
+    /// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
+    /// advanced to the front of the work queue until completed.
+    ///
+    /// The primary user of Low Priority work is the asynchronous work system (suspense).
+    ///
+    /// This is considered "idle" work or "background" work.
+    Low = 0,
+}
+
+#[derive(Clone)]
+pub(crate) struct ResourcePool {
+    /*
+    This *has* to be an UnsafeCell.
+
+    Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to
+    its bumpframe conents immutably.
+
+    However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly
+    simple to audit.
+
+    Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!)
+    */
+    pub components: Rc<UnsafeCell<Slab<Scope>>>,
+
+    /*
+    Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy
+    that slab uses. The slab essentially just provides keys for ElementIDs that we can re-use in a Vec on the client.
+
+    This just happened to be the simplest and most efficient way to implement a deterministic keyed map with slot reuse.
+
+    In the future, we could actually store a pointer to the VNode instead of nil to provide O(1) lookup for VNodes...
+    */
+    pub raw_elements: Rc<UnsafeCell<Slab<()>>>,
+
+    pub channel: EventChannel,
+}
+
+impl ResourcePool {
+    /// this is unsafe because the caller needs to track which other scopes it's already using
+    pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
+        let inner = unsafe { &*self.components.get() };
+        inner.get(idx.0)
+    }
+
+    /// this is unsafe because the caller needs to track which other scopes it's already using
+    pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
+        let inner = unsafe { &mut *self.components.get() };
+        inner.get_mut(idx.0)
+    }
+
+    pub fn with_scope<'b, O: 'static>(
+        &'b self,
+        _id: ScopeId,
+        _f: impl FnOnce(&'b mut Scope) -> O,
+    ) -> Option<O> {
+        todo!()
+    }
+
+    // return a bumpframe with a lifetime attached to the arena borrow
+    // this is useful for merging lifetimes
+    pub fn with_scope_vnode<'b>(
+        &self,
+        _id: ScopeId,
+        _f: impl FnOnce(&mut Scope) -> &VNode<'b>,
+    ) -> Option<&VNode<'b>> {
+        todo!()
+    }
+
+    pub fn try_remove(&self, id: ScopeId) -> Option<Scope> {
+        let inner = unsafe { &mut *self.components.get() };
+        Some(inner.remove(id.0))
+        // .try_remove(id.0)
+        // .ok_or_else(|| Error::FatalInternal("Scope not found"))
+    }
+
+    pub fn reserve_node(&self) -> ElementId {
+        let els = unsafe { &mut *self.raw_elements.get() };
+        ElementId(els.insert(()))
+    }
+
+    /// return the id, freeing the space of the original node
+    pub fn collect_garbage(&self, id: ElementId) {
+        todo!("garabge collection currently WIP")
+        // self.raw_elements.remove(id.0);
+    }
+
+    pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
+        let g = unsafe { &mut *self.components.get() };
+        let entry = g.vacant_entry();
+        let id = ScopeId(entry.key());
+        entry.insert(f(id));
+        id
+    }
+
+    pub fn borrow_bumpframe(&self) {}
+}

+ 64 - 82
packages/core/src/scope.rs

@@ -1,11 +1,9 @@
 use crate::innerlude::*;
 use crate::innerlude::*;
-use bumpalo::boxed::Box as BumpBox;
 use fxhash::FxHashSet;
 use fxhash::FxHashSet;
 use std::{
 use std::{
     any::{Any, TypeId},
     any::{Any, TypeId},
-    borrow::BorrowMut,
-    cell::{Cell, RefCell},
-    collections::{HashMap, HashSet},
+    cell::RefCell,
+    collections::HashMap,
     future::Future,
     future::Future,
     pin::Pin,
     pin::Pin,
     rc::Rc,
     rc::Rc,
@@ -38,20 +36,23 @@ pub struct Scope {
     // Listeners
     // Listeners
     pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
     pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
     pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
     pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
+    pub(crate) suspended_nodes: RefCell<HashMap<u64, *const VSuspended<'static>>>,
 
 
     // State
     // State
     pub(crate) hooks: HookList,
     pub(crate) hooks: HookList,
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
 
-    // A reference to the resources shared by all the comonents
-    pub(crate) vdom: SharedResources,
+    pub(crate) memoized_updater: Rc<dyn Fn() + 'static>,
+
+    pub(crate) shared: EventChannel,
 }
 }
 
 
 // The type of closure that wraps calling components
 // The type of closure that wraps calling components
 pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
 pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
 
 
-// The type of task that gets sent to the task scheduler
-pub type FiberTask = Pin<Box<dyn Future<Output = EventTrigger>>>;
+/// The type of task that gets sent to the task scheduler
+/// Submitting a fiber task returns a handle to that task, which can be used to wake up suspended nodes
+pub type FiberTask = Pin<Box<dyn Future<Output = ScopeId>>>;
 
 
 impl Scope {
 impl Scope {
     // we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
     // we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
@@ -61,38 +62,30 @@ impl Scope {
     //
     //
     // Scopes cannot be made anywhere else except for this file
     // Scopes cannot be made anywhere else except for this file
     // Therefore, their lifetimes are connected exclusively to the virtual dom
     // Therefore, their lifetimes are connected exclusively to the virtual dom
-    pub fn new<'creator_node>(
+    pub(crate) fn new<'creator_node>(
         caller: Rc<WrappedCaller>,
         caller: Rc<WrappedCaller>,
-
         arena_idx: ScopeId,
         arena_idx: ScopeId,
-
         parent: Option<ScopeId>,
         parent: Option<ScopeId>,
-
         height: u32,
         height: u32,
-
         child_nodes: ScopeChildren,
         child_nodes: ScopeChildren,
-
-        vdom: SharedResources,
+        shared: EventChannel,
     ) -> Self {
     ) -> Self {
         let child_nodes = unsafe { child_nodes.extend_lifetime() };
         let child_nodes = unsafe { child_nodes.extend_lifetime() };
 
 
-        // insert ourself as a descendent of the parent
-        // when the parent is removed, this map will be traversed, and we will also be cleaned up.
-        if let Some(parent) = &parent {
-            let parent = unsafe { vdom.get_scope(*parent) }.unwrap();
-            parent.descendents.borrow_mut().insert(arena_idx);
-        }
+        let up = shared.schedule_any_immediate.clone();
+        let memoized_updater = Rc::new(move || up(arena_idx));
 
 
         Self {
         Self {
+            memoized_updater,
+            shared,
             child_nodes,
             child_nodes,
             caller,
             caller,
             parent_idx: parent,
             parent_idx: parent,
             our_arena_idx: arena_idx,
             our_arena_idx: arena_idx,
             height,
             height,
-            vdom,
             frames: ActiveFrame::new(),
             frames: ActiveFrame::new(),
-
             hooks: Default::default(),
             hooks: Default::default(),
+            suspended_nodes: Default::default(),
             shared_contexts: Default::default(),
             shared_contexts: Default::default(),
             listeners: Default::default(),
             listeners: Default::default(),
             borrowed_props: Default::default(),
             borrowed_props: Default::default(),
@@ -107,17 +100,18 @@ impl Scope {
         child_nodes: ScopeChildren,
         child_nodes: ScopeChildren,
     ) {
     ) {
         self.caller = caller;
         self.caller = caller;
-        // let child_nodes = unsafe { std::mem::transmute(child_nodes) };
         let child_nodes = unsafe { child_nodes.extend_lifetime() };
         let child_nodes = unsafe { child_nodes.extend_lifetime() };
         self.child_nodes = child_nodes;
         self.child_nodes = child_nodes;
     }
     }
 
 
-    pub(crate) fn run_scope<'sel>(&'sel mut self) -> Result<()> {
+    /// Returns true if the scope completed successfully
+    ///
+    pub(crate) fn run_scope<'sel>(&'sel mut self, pool: &ResourcePool) -> bool {
         // Cycle to the next frame and then reset it
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
         // Remove all the outdated listeners
 
 
-        self.ensure_drop_safety();
+        self.ensure_drop_safety(pool);
 
 
         // Safety:
         // Safety:
         // - We dropped the listeners, so no more &mut T can be used while these are held
         // - We dropped the listeners, so no more &mut T can be used while these are held
@@ -132,17 +126,12 @@ impl Scope {
         let render: &WrappedCaller = self.caller.as_ref();
         let render: &WrappedCaller = self.caller.as_ref();
 
 
         match render(self) {
         match render(self) {
-            None => {
-                // the user's component failed. We avoid cycling to the next frame
-                log::error!("Running your component failed! It will no longer receive events.");
-                Err(Error::ComponentFailed)
-            }
+            None => false,
             Some(new_head) => {
             Some(new_head) => {
                 // the user's component succeeded. We can safely cycle to the next frame
                 // the user's component succeeded. We can safely cycle to the next frame
                 self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
                 self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
                 self.frames.cycle_frame();
                 self.frames.cycle_frame();
-                log::debug!("Cycle okay");
-                Ok(())
+                true
             }
             }
         }
         }
     }
     }
@@ -156,26 +145,28 @@ impl Scope {
     ///
     ///
     /// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
     /// 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.
     /// all listeners and borrowed props so we can clear them here.
-    fn ensure_drop_safety(&mut self) {
+    pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
         // make sure all garabge is collected before trying to proceed with anything else
         // make sure all garabge is collected before trying to proceed with anything else
         debug_assert!(
         debug_assert!(
             self.pending_garbage.borrow().is_empty(),
             self.pending_garbage.borrow().is_empty(),
             "clean up your garabge please"
             "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
         // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
         // run the hooks (which hold an &mut Referrence)
         // run the hooks (which hold an &mut Referrence)
         // right now, we don't drop
         // right now, we don't drop
-        let vdom = &self.vdom;
+        // let vdom = &self.vdom;
         self.borrowed_props
         self.borrowed_props
             .get_mut()
             .get_mut()
             .drain(..)
             .drain(..)
             .map(|li| unsafe { &*li })
             .map(|li| unsafe { &*li })
             .for_each(|comp| {
             .for_each(|comp| {
                 // First drop the component's undropped references
                 // First drop the component's undropped references
-                let scope_id = comp.ass_scope.get().unwrap();
-                let scope = unsafe { vdom.get_scope_mut(scope_id) }.unwrap();
-                scope.ensure_drop_safety();
+                let scope_id = comp.associated_scope.get().unwrap();
+                let scope = pool.get_scope_mut(scope_id).unwrap();
+                scope.ensure_drop_safety(pool);
 
 
                 // Now, drop our own reference
                 // Now, drop our own reference
                 let mut dropper = comp.drop_props.borrow_mut().take().unwrap();
                 let mut dropper = comp.drop_props.borrow_mut().take().unwrap();
@@ -193,51 +184,24 @@ impl Scope {
     }
     }
 
 
     // A safe wrapper around calling listeners
     // A safe wrapper around calling listeners
-    // calling listeners will invalidate the list of listeners
-    // The listener list will be completely drained because the next frame will write over previous listeners
-    pub(crate) fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> {
-        let EventTrigger {
-            real_node_id,
-            event,
-            ..
-        } = trigger;
-
-        if let &VirtualEvent::AsyncEvent { .. } = &event {
-            log::info!("arrived a fiber event");
-            return Ok(());
-        }
-
-        log::debug!(
-            "There are  {:?} listeners associated with this scope {:#?}",
-            self.listeners.borrow().len(),
-            self.our_arena_idx
-        );
-
+    //
+    //
+    pub(crate) fn call_listener(&mut self, event: SyntheticEvent, element: ElementId) {
         let listners = self.listeners.borrow_mut();
         let listners = self.listeners.borrow_mut();
 
 
         let raw_listener = listners.iter().find(|lis| {
         let raw_listener = listners.iter().find(|lis| {
             let search = unsafe { &***lis };
             let search = unsafe { &***lis };
             let search_id = search.mounted_node.get();
             let search_id = search.mounted_node.get();
-            log::info!(
-                "searching listener {:#?} for real {:?}",
-                search_id,
-                real_node_id
-            );
-
-            match (real_node_id, search_id) {
-                (Some(e), Some(search_id)) => search_id == e,
-                _ => false,
+
+            // this assumes the node might not be mounted - should we assume that though?
+            match search_id.map(|f| f == element) {
+                Some(same) => same,
+                None => false,
             }
             }
         });
         });
 
 
         if let Some(raw_listener) = raw_listener {
         if let Some(raw_listener) = raw_listener {
             let listener = unsafe { &**raw_listener };
             let listener = unsafe { &**raw_listener };
-
-            // log::info!(
-            //     "calling listener {:?}, {:?}",
-            //     listener.event,
-            //     // listener.scope
-            // );
             let mut cb = listener.callback.borrow_mut();
             let mut cb = listener.callback.borrow_mut();
             if let Some(cb) = cb.as_mut() {
             if let Some(cb) = cb.as_mut() {
                 (cb)(event);
                 (cb)(event);
@@ -245,21 +209,35 @@ impl Scope {
         } else {
         } else {
             log::warn!("An event was triggered but there was no listener to handle it");
             log::warn!("An event was triggered but there was no listener to handle it");
         }
         }
-
-        Ok(())
     }
     }
 
 
-    pub fn root(&self) -> &VNode {
-        self.frames.fin_head()
+    pub(crate) fn child_nodes<'a>(&'a self) -> ScopeChildren {
+        unsafe { self.child_nodes.shorten_lifetime() }
     }
     }
 
 
-    pub fn child_nodes<'a>(&'a self) -> ScopeChildren {
-        unsafe { self.child_nodes.unextend_lfetime() }
+    pub(crate) fn call_suspended_node<'a>(&'a self, task: u64) {
+        let g = self.suspended_nodes.borrow_mut();
+
+        if let Some(suspended) = g.get(&task) {
+            let sus: &'a VSuspended<'static> = unsafe { &**suspended };
+            let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
+
+            let bump = self.frames.wip_frame();
+            let mut cb = sus.callback.borrow_mut();
+            let mut _cb = cb.take().unwrap();
+            let cx: SuspendedContext<'a> = SuspendedContext {
+                inner: Context {
+                    props: &(),
+                    scope: &self,
+                },
+            };
+            let n: DomTree<'a> = (_cb)(cx);
+        }
     }
     }
 
 
-    pub fn consume_garbage(&self) -> Vec<&VNode> {
-        let mut garbage = self.pending_garbage.borrow_mut();
-        garbage
+    pub(crate) fn consume_garbage(&self) -> Vec<&VNode> {
+        self.pending_garbage
+            .borrow_mut()
             .drain(..)
             .drain(..)
             .map(|node| {
             .map(|node| {
                 // safety: scopes cannot cycle without their garbage being collected. these nodes are safe
                 // safety: scopes cannot cycle without their garbage being collected. these nodes are safe
@@ -269,4 +247,8 @@ impl Scope {
             })
             })
             .collect::<Vec<_>>()
             .collect::<Vec<_>>()
     }
     }
+
+    pub fn root(&self) -> &VNode {
+        self.frames.fin_head()
+    }
 }
 }

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

@@ -1,5 +0,0 @@
-//! Signas: Avoid the Diff Engine
-//! -----------------------------
-//!
-//!
-//! TODO!

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

@@ -0,0 +1,82 @@
+//! A DOM for testing - both internal and external code.
+use bumpalo::Bump;
+
+use crate::innerlude::*;
+use crate::nodes::IntoVNode;
+
+pub struct TestDom {
+    bump: Bump,
+    scheduler: Scheduler,
+}
+
+impl TestDom {
+    pub fn new() -> TestDom {
+        let bump = Bump::new();
+        let mut scheduler = Scheduler::new();
+        TestDom { bump, scheduler }
+    }
+
+    pub fn new_factory<'a>(&'a self) -> NodeFactory<'a> {
+        NodeFactory::new(&self.bump)
+    }
+
+    pub fn render<'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 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.mutations
+    }
+
+    pub fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a>
+    where
+        F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
+    {
+        let old = self.bump.alloc(self.render(left));
+
+        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
+
+        machine.stack.create_node(old, MountType::Append);
+
+        machine.work(&mut || false);
+
+        machine.mutations
+    }
+
+    pub fn lazy_diff<'a, F1, F2>(
+        &'a self,
+        left: LazyNodes<'a, F1>,
+        right: LazyNodes<'a, F2>,
+    ) -> (Mutations<'a>, Mutations<'a>)
+    where
+        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 mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
+
+        machine.stack.create_node(old, MountType::Append);
+
+        machine.work(&mut || false);
+        let create_edits = machine.mutations;
+
+        let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
+
+        machine.stack.push(DiffInstruction::DiffNode { old, new });
+
+        machine.work(&mut || false);
+
+        let edits = machine.mutations;
+
+        (create_edits, edits)
+    }
+}

+ 56 - 18
packages/core/src/util.rs

@@ -8,21 +8,59 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
     Cell::new(None)
     Cell::new(None)
 }
 }
 
 
-// /// A helper type that lets scopes be ordered by their height
-// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
-// pub struct HeightMarker {
-//     pub idx: ScopeId,
-//     pub height: u32,
-// }
-
-// impl Ord for HeightMarker {
-//     fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-//         self.height.cmp(&other.height)
-//     }
-// }
-
-// impl PartialOrd for HeightMarker {
-//     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-//         Some(self.cmp(other))
-//     }
-// }
+pub fn type_name_of<T>(_: T) -> &'static str {
+    std::any::type_name::<T>()
+}
+
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+// use crate::task::{Context, Poll};
+
+/// Cooperatively gives up a timeslice to the task scheduler.
+///
+/// Calling this function will move the currently executing future to the back
+/// of the execution queue, making room for other futures to execute. This is
+/// especially useful after running CPU-intensive operations inside a future.
+///
+/// See also [`task::spawn_blocking`].
+///
+/// [`task::spawn_blocking`]: fn.spawn_blocking.html
+///
+/// # Examples
+///
+/// Basic usage:
+///
+/// ```
+/// # async_std::task::block_on(async {
+/// #
+/// use async_std::task;
+///
+/// task::yield_now().await;
+/// #
+/// # })
+/// ```
+#[inline]
+pub async fn yield_now() {
+    YieldNow(false).await
+}
+
+struct YieldNow(bool);
+
+impl Future for YieldNow {
+    type Output = ();
+
+    // The futures executor is implemented as a FIFO queue, so all this future
+    // does is re-schedule the future back to the end of the queue, giving room
+    // for other futures to progress.
+    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        if !self.0 {
+            self.0 = true;
+            cx.waker().wake_by_ref();
+            Poll::Pending
+        } else {
+            Poll::Ready(())
+        }
+    }
+}

+ 182 - 359
packages/core/src/virtual_dom.rs

@@ -14,23 +14,17 @@
 //! - The [`ActiveFrame`] object for managing the Scope`s microheap
 //! - The [`ActiveFrame`] object for managing the Scope`s microheap
 //! - The [`Context`] object for exposing VirtualDOM API to components
 //! - The [`Context`] object for exposing VirtualDOM API to components
 //! - The [`NodeFactory`] object for lazyily exposing the `Context` API to the nodebuilder API
 //! - The [`NodeFactory`] object for lazyily exposing the `Context` API to the nodebuilder API
-//! - The [`Hook`] object for exposing state management in components.
 //!
 //!
 //! This module includes just the barebones for a complete VirtualDOM API.
 //! This module includes just the barebones for a complete VirtualDOM API.
 //! Additional functionality is defined in the respective files.
 //! Additional functionality is defined in the respective files.
-#![allow(unreachable_code)]
-use futures_util::StreamExt;
-use fxhash::FxHashMap;
 
 
-use crate::hooks::{SuspendedContext, SuspenseHook};
-use crate::{arena::SharedResources, innerlude::*};
-
-use std::any::Any;
-
-use std::any::TypeId;
-use std::cell::{Ref, RefCell, RefMut};
-use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet};
-use std::pin::Pin;
+use crate::innerlude::*;
+use futures_util::{Future, FutureExt};
+use std::{
+    any::{Any, TypeId},
+    pin::Pin,
+    rc::Rc,
+};
 
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
 /// 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.
@@ -42,150 +36,146 @@ use std::pin::Pin;
 ///
 ///
 ///
 ///
 pub struct VirtualDom {
 pub struct VirtualDom {
-    /// All mounted components are arena allocated to make additions, removals, and references easy to work with
-    /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
-    ///
-    /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
-    /// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
-    pub shared: SharedResources,
+    scheduler: Scheduler,
 
 
-    /// The index of the root component
-    /// Should always be the first (gen=0, id=0)
-    pub base_scope: ScopeId,
+    base_scope: ScopeId,
 
 
-    active_fibers: Vec<Fiber<'static>>,
+    root_fc: Box<dyn Any>,
 
 
-    // for managing the props that were used to create the dom
-    #[doc(hidden)]
-    _root_prop_type: std::any::TypeId,
-
-    #[doc(hidden)]
-    _root_props: std::pin::Pin<Box<dyn std::any::Any>>,
+    root_props: Pin<Box<dyn std::any::Any>>,
 }
 }
 
 
 impl VirtualDom {
 impl VirtualDom {
-    /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
-    ///
-    /// This means that the root component must either consumes its own context, or statics are used to generate the page.
-    /// The root component can access things like routing in its context.
-    ///
-    /// As an end-user, you'll want to use the Renderer's "new" method instead of this method.
-    /// Directly creating the VirtualDOM is only useful when implementing a new renderer.
+    /// Create a new VirtualDOM with a component that does not have special props.
     ///
     ///
+    /// # Description
     ///
     ///
-    /// ```ignore
-    /// // Directly from a closure
-    ///
-    /// let dom = VirtualDom::new(|cx| cx.render(rsx!{ div {"hello world"} }));
-    ///
-    /// // or pass in...
+    /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
     ///
     ///
-    /// let root = |cx| {
-    ///     cx.render(rsx!{
-    ///         div {"hello world"}
-    ///     })
-    /// }
-    /// let dom = VirtualDom::new(root);
+    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
+    /// to toss out the entire tree.
     ///
     ///
-    /// // or directly from a fn
     ///
     ///
+    /// # Example
+    /// ```
     /// fn Example(cx: Context<()>) -> DomTree  {
     /// fn Example(cx: Context<()>) -> DomTree  {
-    ///     cx.render(rsx!{ div{"hello world"} })
+    ///     cx.render(rsx!( div { "hello world" } ))
     /// }
     /// }
     ///
     ///
     /// let dom = VirtualDom::new(Example);
     /// let dom = VirtualDom::new(Example);
     /// ```
     /// ```
+    ///
+    /// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
     pub fn new(root: FC<()>) -> Self {
     pub fn new(root: FC<()>) -> Self {
         Self::new_with_props(root, ())
         Self::new_with_props(root, ())
     }
     }
 
 
-    /// Start a new VirtualDom instance with a dependent cx.
+    /// Create a new VirtualDOM with the given properties for the root component.
+    ///
+    /// # Description
+    ///
     /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
     /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
     ///
     ///
     /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
     /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
     /// to toss out the entire tree.
     /// to toss out the entire tree.
     ///
     ///
-    /// ```ignore
-    /// // Directly from a closure
     ///
     ///
-    /// let dom = VirtualDom::new(|cx| cx.render(rsx!{ div {"hello world"} }));
-    ///
-    /// // or pass in...
-    ///
-    /// let root = |cx| {
-    ///     cx.render(rsx!{
-    ///         div {"hello world"}
-    ///     })
+    /// # Example
+    /// ```
+    /// #[derive(PartialEq, Props)]
+    /// struct SomeProps {
+    ///     name: &'static str
     /// }
     /// }
-    /// let dom = VirtualDom::new(root);
-    ///
-    /// // or directly from a fn
     ///
     ///
-    /// fn Example(cx: Context, props: &SomeProps) -> VNode  {
-    ///     cx.render(rsx!{ div{"hello world"} })
+    /// fn Example(cx: Context<SomeProps>) -> DomTree  {
+    ///     cx.render(rsx!{ div{ "hello {cx.name}" } })
     /// }
     /// }
     ///
     ///
     /// let dom = VirtualDom::new(Example);
     /// let dom = VirtualDom::new(Example);
     /// ```
     /// ```
+    ///
+    /// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
+    ///
+    /// ```rust
+    /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
+    /// let mutations = dom.rebuild();
+    /// ```
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
-        let components = SharedResources::new();
+        let scheduler = Scheduler::new();
 
 
         let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
         let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
-        let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
+        let props_ptr = root_props.downcast_ref::<P>().unwrap() as *const P;
 
 
-        let link = components.clone();
-
-        let base_scope = components.insert_scope_with_key(move |myidx| {
+        let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
             let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
             let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
-            Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), link)
+            Scope::new(
+                caller,
+                myidx,
+                None,
+                0,
+                ScopeChildren(&[]),
+                scheduler.pool.channel.clone(),
+            )
         });
         });
 
 
         Self {
         Self {
+            root_fc: Box::new(root),
             base_scope,
             base_scope,
-            _root_props: root_props,
-            shared: components,
-            active_fibers: Vec::new(),
-            _root_prop_type: TypeId::of::<P>(),
+            scheduler,
+            root_props,
         }
         }
     }
     }
 
 
-    pub fn launch_in_place(root: FC<()>) -> Self {
-        let mut s = Self::new(root);
-        s.rebuild_in_place().unwrap();
-        s
-    }
-
-    /// Creates a new virtualdom and immediately rebuilds it in place, not caring about the RealDom to write into.
+    /// Get the [`Scope`] for the root component.
     ///
     ///
-    pub fn launch_with_props_in_place<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
-        let mut s = Self::new_with_props(root, root_props);
-        s.rebuild_in_place().unwrap();
-        s
-    }
-
+    /// This is useful for traversing the tree from the root for heuristics or altnerative renderers that use Dioxus
+    /// directly.
     pub fn base_scope(&self) -> &Scope {
     pub fn base_scope(&self) -> &Scope {
-        unsafe { self.shared.get_scope(self.base_scope).unwrap() }
+        self.scheduler.pool.get_scope(self.base_scope).unwrap()
     }
     }
 
 
+    /// Get the [`Scope`] for a component given its [`ScopeId`]
     pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> {
     pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> {
-        unsafe { self.shared.get_scope(id) }
+        self.scheduler.pool.get_scope(id)
     }
     }
 
 
-    /// Rebuilds the VirtualDOM from scratch, but uses a "dummy" RealDom.
+    /// Update the root props of this VirtualDOM.
     ///
     ///
-    /// Used in contexts where a real copy of the  structure doesn't matter, and the VirtualDOM is the source of truth.
+    /// This method retuns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
+    /// so calling this method will block the main thread until computation is done.
     ///
     ///
-    /// ## Why?
+    /// ## Example
     ///
     ///
-    /// This method uses the `DebugDom` under the hood - essentially making the VirtualDOM's diffing patches a "no-op".
+    /// ```rust
+    /// #[derive(Props, PartialEq)]
+    /// struct AppProps {
+    ///     route: &'static str
+    /// }
+    /// static App: FC<AppProps> = |cx| cx.render(rsx!{ "route is {cx.route}" });
     ///
     ///
-    /// SSR takes advantage of this by using Dioxus itself as the source of truth, and rendering from the tree directly.
-    pub fn rebuild_in_place(&mut self) -> Result<Vec<DomEdit>> {
-        todo!();
-        // let mut realdom = DebugDom::new();
-        // let mut edits = Vec::new();
-        // self.rebuild(&mut realdom, &mut edits)?;
-        // Ok(edits)
+    /// let mut dom = VirtualDom::new_with_props(App, AppProps { route: "start" });
+    ///
+    /// let mutations = dom.update_root_props(AppProps { route: "end" }).unwrap();
+    /// ```
+    pub fn update_root_props<'s, P: 'static>(&'s mut self, root_props: P) -> Option<Mutations<'s>> {
+        let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
+        root_scope.ensure_drop_safety(&self.scheduler.pool);
+
+        let mut root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
+
+        if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
+            std::mem::swap(&mut self.root_props, &mut root_props);
+
+            let root = *self.root_fc.downcast_ref::<FC<P>>().unwrap();
+
+            let new_caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
+
+            root_scope.update_scope_dependencies(new_caller, ScopeChildren(&[]));
+
+            Some(self.rebuild())
+        } else {
+            None
+        }
     }
     }
 
 
     /// 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 rom scratch
@@ -193,21 +183,40 @@ impl VirtualDom {
     /// 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
     /// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
-    /// through "run"
+    /// 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
     ///
     ///
-    pub fn rebuild<'s>(&'s mut self) -> Result<Vec<DomEdit<'s>>> {
-        let mut edits = Vec::new();
-        let mutations = Mutations { edits: Vec::new() };
-        let mut diff_machine = DiffMachine::new(mutations, self.base_scope, &self.shared);
+    /// 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> {
+        let mut shared = self.scheduler.pool.clone();
+        let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
 
 
-        let cur_component = diff_machine
-            .get_scope_mut(&self.base_scope)
+        let cur_component = self
+            .scheduler
+            .pool
+            .get_scope_mut(self.base_scope)
             .expect("The base scope should never be moved");
             .expect("The base scope should never be moved");
 
 
         // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
         // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
-        if cur_component.run_scope().is_ok() {
-            let meta = diff_machine.create_vnode(cur_component.frames.fin_head());
-            diff_machine.edit_append_children(meta.added_to_stack);
+        if cur_component.run_scope(&self.scheduler.pool) {
+            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();
         } else {
         } else {
             // todo: should this be a hard error?
             // todo: should this be a hard error?
             log::warn!(
             log::warn!(
@@ -216,65 +225,42 @@ impl VirtualDom {
             );
             );
         }
         }
 
 
-        Ok(edits)
+        unsafe { std::mem::transmute(diff_machine.mutations) }
     }
     }
 
 
-    // async fn select_next_event(&mut self) -> Option<EventTrigger> {
-    //     let mut receiver = self.shared.task_receiver.borrow_mut();
-
-    //     // drain the in-flight events so that we can sort them out with the current events
-    //     while let Ok(Some(trigger)) = receiver.try_next() {
-    //         log::info!("retrieving event from receiver");
-    //         let key = self.shared.make_trigger_key(&trigger);
-    //         self.pending_events.insert(key, trigger);
-    //     }
-
-    //     if self.pending_events.is_empty() {
-    //         // Continuously poll the future pool and the event receiver for work
-    //         let mut tasks = self.shared.async_tasks.borrow_mut();
-    //         let tasks_tasks = tasks.next();
-
-    //         let mut receiver = self.shared.task_receiver.borrow_mut();
-    //         let reciv_task = receiver.next();
-
-    //         futures_util::pin_mut!(tasks_tasks);
-    //         futures_util::pin_mut!(reciv_task);
-
-    //         let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await {
-    //             futures_util::future::Either::Left((trigger, _)) => trigger,
-    //             futures_util::future::Either::Right((trigger, _)) => trigger,
-    //         }
-    //         .unwrap();
-    //         let key = self.shared.make_trigger_key(&trigger);
-    //         self.pending_events.insert(key, trigger);
-    //     }
-
-    //     // pop the most important event off
-    //     let key = self.pending_events.keys().next().unwrap().clone();
-    //     let trigger = self.pending_events.remove(&key).unwrap();
-
-    //     Some(trigger)
-    // }
-
-    /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
+    /// Compute a manual diff of the VirtualDOM between states.
     ///
     ///
-    /// This method will not wait for any suspended tasks, completely skipping over
-    pub fn run_immediate<'s>(&'s mut self) -> Result<Mutations<'s>> {
-        //
+    /// This can be useful when state inside the DOM is remotely changed from the outside, but not propogated as an event.
+    pub fn diff<'s>(&'s mut self) -> Mutations<'s> {
+        let cur_component = self
+            .scheduler
+            .pool
+            .get_scope_mut(self.base_scope)
+            .expect("The base scope should never be moved");
 
 
-        todo!()
+        if cur_component.run_scope(&self.scheduler.pool) {
+            let mut diff_machine: DiffMachine<'s> =
+                DiffMachine::new(Mutations::new(), &mut self.scheduler.pool);
+            diff_machine.diff_scope(self.base_scope);
+            diff_machine.mutations
+        } else {
+            Mutations::new()
+        }
     }
     }
 
 
-    /// Runs the virtualdom with no time limit.
+    /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
     ///
     ///
-    /// If there are pending tasks, they will be progressed before returning. This is useful when rendering an application
-    /// that has suspended nodes or suspended tasks. Be warned - any async tasks running forever will prevent this method
-    /// from completing. Consider using `run` and specifing a deadline.
-    pub async fn run_unbounded<'s>(&'s mut self) -> Result<Mutations<'s>> {
-        self.run_with_deadline(|| false).await
+    /// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
+    /// return "None"
+    pub fn run_immediate<'s>(&'s mut self) -> Option<Vec<Mutations<'s>>> {
+        if self.scheduler.has_any_work() {
+            Some(self.scheduler.work_sync())
+        } else {
+            None
+        }
     }
     }
 
 
-    /// Run the virtualdom with a time limit.
+    /// Run the virtualdom with a deadline.
     ///
     ///
     /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
     /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
     /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
     /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
@@ -283,189 +269,59 @@ impl VirtualDom {
     /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
     /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
     /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
     /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
     ///
     ///
-    /// Due to platform differences in how time is handled, this method accepts a closure that must return true when the
-    /// deadline is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room
-    /// into the deadline closure manually.
+    /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
+    /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
+    /// deadline closure manually.
     ///
     ///
-    /// The deadline is checked before starting to diff components. This strikes a balance between the overhead of checking
+    /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
     /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
     /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
     /// the screen will "jank" up. In debug, this will trigger an alert.
     /// the screen will "jank" up. In debug, this will trigger an alert.
     ///
     ///
+    /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
+    /// the provided deadline future resolves.
+    ///
+    /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
+    /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
+    /// entirely jank-free applications that perform a ton of work.
+    ///
     /// # Example
     /// # Example
     ///
     ///
     /// ```no_run
     /// ```no_run
-    /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!( div {"hello"} )));
+    /// static App: FC<()> = |cx| rsx!(in cx, div {"hello"} );
+    /// let mut dom = VirtualDom::new(App);
     /// loop {
     /// loop {
-    ///     let started = std::time::Instant::now();
-    ///     let deadline = move || std::time::Instant::now() - started > std::time::Duration::from_millis(16);
-    ///     
+    ///     let deadline = TimeoutFuture::from_ms(16);
     ///     let mutations = dom.run_with_deadline(deadline).await;
     ///     let mutations = dom.run_with_deadline(deadline).await;
     ///     apply_mutations(mutations);
     ///     apply_mutations(mutations);
     /// }
     /// }
     /// ```
     /// ```
+    ///
+    /// ## Mutations
+    ///
+    /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
+    /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
+    /// applied the edits.
+    ///
+    /// Mutations are the only link between the RealDOM and the VirtualDOM.
     pub async fn run_with_deadline<'s>(
     pub async fn run_with_deadline<'s>(
         &'s mut self,
         &'s mut self,
-        mut deadline_exceeded: impl FnMut() -> bool,
-    ) -> Result<Mutations<'s>> {
-        let cur_component = self.base_scope;
-
-        let mut diff_machine =
-            DiffMachine::new(Mutations { edits: Vec::new() }, cur_component, &self.shared);
-
-        /*
-        Strategy:
-        1. Check if there are any events in the receiver.
-        2. If there are, process them and create a new fiber.
-        3. If there are no events, then choose a fiber to work on.
-        4. If there are no fibers, then wait for the next event from the receiver.
-        5. While processing a fiber, periodically check if we're out of time
-        6. If we are almost out of time, then commit our edits to the realdom
-        7. Whenever a fiber is finished, immediately commit it. (IE so deadlines can be infinite if unsupported)
-        */
-
-        // 1. Consume any pending events and create new fibers
-        let mut receiver = self.shared.task_receiver.borrow_mut();
-        while let Ok(Some(trigger)) = receiver.try_next() {
-            // todo: cache the fibers
-            let mut fiber = Fiber::new();
-
-            match &trigger.event {
-                // If any input event is received, then we need to create a new fiber
-                VirtualEvent::ClipboardEvent(_)
-                | VirtualEvent::CompositionEvent(_)
-                | VirtualEvent::KeyboardEvent(_)
-                | VirtualEvent::FocusEvent(_)
-                | VirtualEvent::FormEvent(_)
-                | VirtualEvent::SelectionEvent(_)
-                | VirtualEvent::TouchEvent(_)
-                | VirtualEvent::UIEvent(_)
-                | VirtualEvent::WheelEvent(_)
-                | VirtualEvent::MediaEvent(_)
-                | VirtualEvent::AnimationEvent(_)
-                | VirtualEvent::TransitionEvent(_)
-                | VirtualEvent::ToggleEvent(_)
-                | VirtualEvent::MouseEvent(_)
-                | VirtualEvent::PointerEvent(_) => {
-                    if let Some(scope) = self.shared.get_scope_mut(trigger.originator) {
-                        scope.call_listener(trigger)?;
-                    }
-                }
-
-                VirtualEvent::AsyncEvent { .. } => {
-                    while let Ok(Some(event)) = receiver.try_next() {
-                        fiber.pending_scopes.push(event.originator);
-                    }
-                }
-
-                // These shouldn't normally be received, but if they are, it's done because some task set state manually
-                // Instead of batching the results,
-                VirtualEvent::ScheduledUpdate { height: u32 } => {}
-
-                // Suspense Events! A component's suspended node is updated
-                VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
-                    // Safety: this handler is the only thing that can mutate shared items at this moment in tim
-                    let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
-
-                    // safety: we are sure that there are no other references to the inner content of suspense hooks
-                    let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
-
-                    let cx = Context { scope, props: &() };
-                    let scx = SuspendedContext { inner: cx };
-
-                    // generate the new node!
-                    let nodes: Option<VNode> = (&hook.callback)(scx);
-                    match nodes {
-                        None => {
-                            log::warn!(
-                                "Suspense event came through, but there were no generated nodes >:(."
-                            );
-                        }
-                        Some(nodes) => {
-                            // allocate inside the finished frame - not the WIP frame
-                            let nodes = scope.frames.finished_frame().bump.alloc(nodes);
-
-                            // push the old node's root onto the stack
-                            let real_id = domnode.get().ok_or(Error::NotMounted)?;
-                            diff_machine.edit_push_root(real_id);
-
-                            // push these new nodes onto the diff machines stack
-                            let meta = diff_machine.create_vnode(&*nodes);
-
-                            // replace the placeholder with the new nodes we just pushed on the stack
-                            diff_machine.edit_replace_with(1, meta.added_to_stack);
-                        }
-                    }
-                }
-
-                // Collecting garabge is not currently interruptible.
-                //
-                // In the future, it could be though
-                VirtualEvent::GarbageCollection => {
-                    let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
-
-                    let mut garbage_list = scope.consume_garbage();
-
-                    let mut scopes_to_kill = Vec::new();
-                    while let Some(node) = garbage_list.pop() {
-                        match &node.kind {
-                            VNodeKind::Text(_) => {
-                                self.shared.collect_garbage(node.direct_id());
-                            }
-                            VNodeKind::Anchor(_) => {
-                                self.shared.collect_garbage(node.direct_id());
-                            }
-                            VNodeKind::Suspended(_) => {
-                                self.shared.collect_garbage(node.direct_id());
-                            }
-
-                            VNodeKind::Element(el) => {
-                                self.shared.collect_garbage(node.direct_id());
-                                for child in el.children {
-                                    garbage_list.push(child);
-                                }
-                            }
-
-                            VNodeKind::Fragment(frag) => {
-                                for child in frag.children {
-                                    garbage_list.push(child);
-                                }
-                            }
-
-                            VNodeKind::Component(comp) => {
-                                // TODO: run the hook destructors and then even delete the scope
-
-                                let scope_id = comp.ass_scope.get().unwrap();
-                                let scope = self.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 {
-                        // oy kill em
-                        log::debug!("should be removing scope {:#?}", scope);
-                    }
-                }
-            }
-        }
-
-        while !deadline_exceeded() {
-            let mut receiver = self.shared.task_receiver.borrow_mut();
-
-            // no messages to receive, just work on the fiber
-        }
+        deadline: impl Future<Output = ()>,
+    ) -> Vec<Mutations<'s>> {
+        let mut deadline = Box::pin(deadline.fuse());
+        self.scheduler.work_with_deadline(deadline).await
+    }
 
 
-        Ok(diff_machine.edits)
+    pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
+        self.scheduler.pool.channel.sender.clone()
     }
     }
 
 
-    pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
-        self.shared.task_sender.clone()
+    pub fn has_work(&self) -> bool {
+        true
     }
     }
 
 
-    fn get_scope_mut(&mut self, id: ScopeId) -> Option<&mut Scope> {
-        unsafe { self.shared.get_scope_mut(id) }
+    pub async fn wait_for_any_work(&mut self) {
+        let mut timeout = Box::pin(futures_util::future::pending().fuse());
+        self.scheduler.wait_for_any_trigger(&mut timeout).await;
     }
     }
 }
 }
 
 
@@ -473,36 +329,3 @@ impl VirtualDom {
 // These impls are actually wrong. The DOM needs to have a mutex implemented.
 // These impls are actually wrong. The DOM needs to have a mutex implemented.
 unsafe impl Sync for VirtualDom {}
 unsafe impl Sync for VirtualDom {}
 unsafe impl Send for VirtualDom {}
 unsafe impl Send for VirtualDom {}
-
-struct Fiber<'a> {
-    // scopes that haven't been updated yet
-    pending_scopes: Vec<ScopeId>,
-
-    pending_nodes: Vec<*const VNode<'a>>,
-
-    // WIP edits
-    edits: Vec<DomEdit<'a>>,
-
-    started: bool,
-
-    completed: bool,
-}
-
-impl Fiber<'_> {
-    fn new() -> Self {
-        Self {
-            pending_scopes: Vec::new(),
-            pending_nodes: Vec::new(),
-            edits: Vec::new(),
-            started: false,
-            completed: false,
-        }
-    }
-}
-
-/// The "Mutations" object holds the changes that need to be made to the DOM.
-pub struct Mutations<'s> {
-    // todo: apply node refs
-    // todo: apply effects
-    pub edits: Vec<DomEdit<'s>>,
-}

+ 263 - 0
packages/core/tests/create_iterative.rs

@@ -0,0 +1,263 @@
+//! tests to prove that the iterative implementation works
+
+use anyhow::{Context, Result};
+use dioxus::{prelude::*, DomEdit, Mutations};
+mod test_logging;
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+use DomEdit::*;
+
+const LOGGING_ENABLED: bool = false;
+
+#[test]
+fn test_original_diff() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            div {
+                div {
+                    "Hello, world!"
+                }
+            }
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let mutations = dom.rebuild();
+    assert_eq!(
+        mutations.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateElement { id: 1, tag: "div" },
+            CreateTextNode {
+                id: 2,
+                text: "Hello, world!"
+            },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 1 },
+        ]
+    );
+}
+
+#[async_std::test]
+async fn create() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            div {
+                div {
+                    "Hello, world!"
+                    div {
+                        div {
+                            Fragment {
+                                "hello"
+                                "world"
+                            }
+                        }
+                    }
+                }
+            }
+        })
+    };
+
+    test_logging::set_up_logging(LOGGING_ENABLED);
+    let mut dom = VirtualDom::new(App);
+    let mutations = dom.rebuild_async().await;
+    assert_eq!(
+        mutations.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateElement { id: 1, tag: "div" },
+            CreateTextNode {
+                id: 2,
+                text: "Hello, world!"
+            },
+            CreateElement { id: 3, tag: "div" },
+            CreateElement { id: 4, tag: "div" },
+            CreateTextNode {
+                id: 5,
+                text: "hello"
+            },
+            CreateTextNode {
+                id: 6,
+                text: "world"
+            },
+            AppendChildren { many: 2 },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 2 },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 1 },
+        ]
+    );
+}
+
+#[async_std::test]
+async fn create_list() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            {(0..3).map(|f| rsx!{ div {
+                "hello"
+            }})}
+        })
+    };
+
+    test_logging::set_up_logging(LOGGING_ENABLED);
+
+    let mut dom = VirtualDom::new(App);
+    let mutations = dom.rebuild_async().await;
+
+    // copilot wrote this test :P
+    assert_eq!(
+        mutations.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateTextNode {
+                id: 1,
+                text: "hello"
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 2, tag: "div" },
+            CreateTextNode {
+                id: 3,
+                text: "hello"
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 4, tag: "div" },
+            CreateTextNode {
+                id: 5,
+                text: "hello"
+            },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 3 },
+        ]
+    );
+}
+
+#[async_std::test]
+async fn create_simple() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            div {}
+            div {}
+            div {}
+            div {}
+        })
+    };
+
+    test_logging::set_up_logging(LOGGING_ENABLED);
+
+    let mut dom = VirtualDom::new(App);
+    let mutations = dom.rebuild_async().await;
+
+    // copilot wrote this test :P
+    assert_eq!(
+        mutations.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateElement { id: 1, tag: "div" },
+            CreateElement { id: 2, tag: "div" },
+            CreateElement { id: 3, tag: "div" },
+            AppendChildren { many: 4 },
+        ]
+    );
+}
+
+#[async_std::test]
+async fn create_components() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            Child { "abc1" }
+            Child { "abc2" }
+            Child { "abc3" }
+        })
+    };
+
+    static Child: FC<()> = |cx| {
+        cx.render(rsx! {
+            h1 {}
+            div { {cx.children()} }
+            p {}
+        })
+    };
+
+    test_logging::set_up_logging(LOGGING_ENABLED);
+
+    let mut dom = VirtualDom::new(App);
+    let mutations = dom.rebuild_async().await;
+
+    assert_eq!(
+        mutations.edits,
+        [
+            CreateElement { id: 0, tag: "h1" },
+            CreateElement { id: 1, tag: "div" },
+            CreateTextNode {
+                id: 2,
+                text: "abc1"
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 3, tag: "p" },
+            CreateElement { id: 4, tag: "h1" },
+            CreateElement { id: 5, tag: "div" },
+            CreateTextNode {
+                id: 6,
+                text: "abc2"
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 7, tag: "p" },
+            CreateElement { id: 8, tag: "h1" },
+            CreateElement { id: 9, tag: "div" },
+            CreateTextNode {
+                id: 10,
+                text: "abc3"
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 11, tag: "p" },
+            AppendChildren { many: 9 },
+        ]
+    );
+}
+
+#[async_std::test]
+async fn anchors() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            {true.then(|| rsx!{ div { "hello" } })}
+            {false.then(|| rsx!{ div { "goodbye" } })}
+        })
+    };
+
+    test_logging::set_up_logging(LOGGING_ENABLED);
+
+    let mut dom = VirtualDom::new(App);
+    let mutations = dom.rebuild_async().await;
+    assert_eq!(
+        mutations.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateTextNode {
+                id: 1,
+                text: "hello"
+            },
+            AppendChildren { many: 1 },
+            CreatePlaceholder { id: 2 },
+            AppendChildren { many: 2 },
+        ]
+    );
+}
+
+#[async_std::test]
+async fn suspended() {
+    static App: FC<()> = |cx| {
+        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;
+
+    assert_eq!(
+        mutations.edits,
+        [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 },]
+    );
+}

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

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

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

@@ -0,0 +1,48 @@
+//! 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);
+}

+ 524 - 162
packages/core/tests/diffing.rs

@@ -1,85 +1,23 @@
 //! Diffing Tests
 //! Diffing Tests
-//! -------------
 //!
 //!
-//! These should always compile and run, but the result is not validated for each test.
-//! TODO: Validate the results beyond visual inspection.
-
-use bumpalo::Bump;
-
-use anyhow::{Context, Result};
-use dioxus::{
-    arena::SharedResources,
-    diff::{CreateMeta, DiffMachine},
-    prelude::*,
-    DomEdit,
-};
+//! These tests only verify that the diffing algorithm works properly for single components.
+//!
+//! It does not validated that component lifecycles work properly. This is done in another test file.
+
+use dioxus::{prelude::*, DomEdit, TestDom};
 use dioxus_core as dioxus;
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;
 use dioxus_html as dioxus_elements;
 
 
-struct TestDom {
-    bump: Bump,
-    resources: SharedResources,
-}
-impl TestDom {
-    fn new() -> TestDom {
-        let bump = Bump::new();
-        let resources = SharedResources::new();
-        TestDom { bump, resources }
-    }
-    fn new_factory<'a>(&'a self) -> NodeFactory<'a> {
-        NodeFactory::new(&self.bump)
-    }
-
-    fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
-    where
-        F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
-    {
-        use dioxus_core::nodes::{IntoVNode, IntoVNodeList};
-        lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
-    }
-
-    fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Vec<DomEdit<'a>> {
-        let mut edits = Vec::new();
-        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
-        machine.diff_node(old, new);
-        edits
-    }
-
-    fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> (CreateMeta, Vec<DomEdit<'a>>)
-    where
-        F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
-    {
-        let old = self.bump.alloc(self.render(left));
-        let mut edits = Vec::new();
-
-        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
-        let meta = machine.create_vnode(old);
-        (meta, edits)
-    }
-
-    fn lazy_diff<'a, F1, F2>(
-        &'a self,
-        left: LazyNodes<'a, F1>,
-        right: LazyNodes<'a, F2>,
-    ) -> (Vec<DomEdit<'a>>, Vec<DomEdit<'a>>)
-    where
-        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 mut create_edits = Vec::new();
-
-        let mut machine = DiffMachine::new_headless(&mut create_edits, &self.resources);
-        machine.create_vnode(old);
-
-        let mut edits = Vec::new();
-        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
-        machine.diff_node(old, new);
-        (create_edits, edits)
-    }
+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 {
+    test_logging::set_up_logging(IS_LOGGING_ENABLED);
+    TestDom::new()
 }
 }
 
 
 #[test]
 #[test]
@@ -88,78 +26,170 @@ fn diffing_works() {}
 /// Should push the text node onto the stack and modify it
 /// Should push the text node onto the stack and modify it
 #[test]
 #[test]
 fn html_and_rsx_generate_the_same_output() {
 fn html_and_rsx_generate_the_same_output() {
-    let dom = TestDom::new();
-    let edits = dom.lazy_diff(
+    let dom = new_dom();
+    let (create, change) = dom.lazy_diff(
         rsx! ( div { "Hello world" } ),
         rsx! ( div { "Hello world" } ),
         rsx! ( div { "Goodbye world" } ),
         rsx! ( div { "Goodbye world" } ),
     );
     );
-    dbg!(edits);
+    assert_eq!(
+        create.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateTextNode {
+                id: 1,
+                text: "Hello world"
+            },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 1 },
+        ]
+    );
+
+    assert_eq!(
+        change.edits,
+        [
+            PushRoot { id: 1 },
+            SetText {
+                text: "Goodbye world"
+            },
+            PopRoot
+        ]
+    );
 }
 }
 
 
 /// Should result in 3 elements on the stack
 /// Should result in 3 elements on the stack
 #[test]
 #[test]
 fn fragments_create_properly() {
 fn fragments_create_properly() {
-    let dom = TestDom::new();
-    let (meta, edits) = dom.create(rsx! {
+    let dom = new_dom();
+
+    let create = dom.create(rsx! {
         div { "Hello a" }
         div { "Hello a" }
         div { "Hello b" }
         div { "Hello b" }
         div { "Hello c" }
         div { "Hello c" }
     });
     });
-    assert!(&edits[0].is("CreateElement"));
-    assert!(&edits[3].is("CreateElement"));
-    assert!(&edits[6].is("CreateElement"));
 
 
-    assert_eq!(meta.added_to_stack, 3);
-    dbg!(edits);
+    assert_eq!(
+        create.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateTextNode {
+                id: 1,
+                text: "Hello a"
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 2, tag: "div" },
+            CreateTextNode {
+                id: 3,
+                text: "Hello b"
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 4, tag: "div" },
+            CreateTextNode {
+                id: 5,
+                text: "Hello c"
+            },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 3 },
+        ]
+    );
 }
 }
 
 
 /// Should result in the creation of an anchor (placeholder) and then a replacewith
 /// Should result in the creation of an anchor (placeholder) and then a replacewith
 #[test]
 #[test]
 fn empty_fragments_create_anchors() {
 fn empty_fragments_create_anchors() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
     let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
     let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
     let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(edits);
+    let (create, change) = dom.lazy_diff(left, right);
+
+    assert_eq!(
+        create.edits,
+        [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
+    );
+    assert_eq!(
+        change.edits,
+        [
+            CreateElement { id: 1, tag: "div" },
+            ReplaceWith { m: 1, root: 0 }
+        ]
+    );
 }
 }
 
 
 /// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
 /// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
 #[test]
 #[test]
 fn empty_fragments_create_many_anchors() {
 fn empty_fragments_create_many_anchors() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
     let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
     let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
     let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(edits);
+    let (create, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        create.edits,
+        [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
+    );
+    assert_eq!(
+        change.edits,
+        [
+            CreateElement { id: 1, tag: "div" },
+            CreateElement { id: 2, tag: "div" },
+            CreateElement { id: 3, tag: "div" },
+            CreateElement { id: 4, tag: "div" },
+            CreateElement { id: 5, tag: "div" },
+            ReplaceWith { m: 5, root: 0 }
+        ]
+    );
 }
 }
 
 
 /// Should result in the creation of an anchor (placeholder) and then a replacewith
 /// Should result in the creation of an anchor (placeholder) and then a replacewith
 /// Includes child nodes inside the fragment
 /// Includes child nodes inside the fragment
 #[test]
 #[test]
 fn empty_fragments_create_anchors_with_many_children() {
 fn empty_fragments_create_anchors_with_many_children() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
     let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
     let right = rsx!({
     let right = rsx!({
-        (0..5).map(|f| {
-            rsx! { div { "hello" }}
+        (0..3).map(|f| {
+            rsx! { div { "hello: {f}" }}
         })
         })
     });
     });
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
-    let last_edit = edits.1.last().unwrap();
-    assert!(last_edit.is("ReplaceWith"));
+    let (create, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        create.edits,
+        [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
+    );
+    assert_eq!(
+        change.edits,
+        [
+            CreateElement { id: 1, tag: "div" },
+            CreateTextNode {
+                text: "hello: 0",
+                id: 2
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 3, tag: "div" },
+            CreateTextNode {
+                text: "hello: 1",
+                id: 4
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 5, tag: "div" },
+            CreateTextNode {
+                text: "hello: 2",
+                id: 6
+            },
+            AppendChildren { many: 1 },
+            ReplaceWith { m: 3, root: 0 }
+        ]
+    );
 }
 }
 
 
 /// Should result in every node being pushed and then replaced with an anchor
 /// Should result in every node being pushed and then replaced with an anchor
 #[test]
 #[test]
 fn many_items_become_fragment() {
 fn many_items_become_fragment() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!({
     let left = rsx!({
         (0..2).map(|f| {
         (0..2).map(|f| {
@@ -168,14 +198,41 @@ fn many_items_become_fragment() {
     });
     });
     let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
     let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    let (create, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        create.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateTextNode {
+                text: "hello",
+                id: 1
+            },
+            AppendChildren { many: 1 },
+            CreateElement { id: 2, tag: "div" },
+            CreateTextNode {
+                text: "hello",
+                id: 3
+            },
+            AppendChildren { many: 1 },
+            AppendChildren { many: 2 },
+        ]
+    );
+
+    // hmmmmmmmmm worried about reusing IDs that we shouldnt be
+    assert_eq!(
+        change.edits,
+        [
+            Remove { root: 2 },
+            CreatePlaceholder { id: 4 },
+            ReplaceWith { root: 0, m: 1 },
+        ]
+    );
 }
 }
 
 
 /// Should result in no edits
 /// Should result in no edits
 #[test]
 #[test]
 fn two_equal_fragments_are_equal() {
 fn two_equal_fragments_are_equal() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!({
     let left = rsx!({
         (0..2).map(|f| {
         (0..2).map(|f| {
@@ -188,33 +245,47 @@ fn two_equal_fragments_are_equal() {
         })
         })
     });
     });
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
-    assert!(edits.1.is_empty());
+    let (create, change) = dom.lazy_diff(left, right);
+    assert!(change.edits.is_empty());
 }
 }
 
 
 /// Should result the creation of more nodes appended after the old last node
 /// Should result the creation of more nodes appended after the old last node
 #[test]
 #[test]
 fn two_fragments_with_differrent_elements_are_differet() {
 fn two_fragments_with_differrent_elements_are_differet() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!(
     let left = rsx!(
-        {(0..2).map(|f| {rsx! { div {  }}})}
+        { (0..2).map(|_| rsx! { div {  }} ) }
         p {}
         p {}
     );
     );
     let right = rsx!(
     let right = rsx!(
-        {(0..5).map(|f| {rsx! { h1 {  }}})}
+        { (0..5).map(|_| rsx! (h1 {  }) ) }
         p {}
         p {}
     );
     );
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    let (create, changes) = dom.lazy_diff(left, right);
+    log::debug!("{:#?}", &changes);
+    assert_eq!(
+        changes.edits,
+        [
+            // create the new h1s
+            CreateElement { tag: "h1", id: 3 },
+            CreateElement { tag: "h1", id: 4 },
+            CreateElement { tag: "h1", id: 5 },
+            InsertAfter { root: 1, n: 3 },
+            // replace the divs with new h1s
+            CreateElement { tag: "h1", id: 6 },
+            ReplaceWith { root: 0, m: 1 },
+            CreateElement { tag: "h1", id: 7 },
+            ReplaceWith { root: 1, m: 1 },
+        ]
+    );
 }
 }
 
 
 /// Should result in multiple nodes destroyed - with changes to the first nodes
 /// Should result in multiple nodes destroyed - with changes to the first nodes
 #[test]
 #[test]
 fn two_fragments_with_differrent_elements_are_differet_shorter() {
 fn two_fragments_with_differrent_elements_are_differet_shorter() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!(
     let left = rsx!(
         {(0..5).map(|f| {rsx! { div {  }}})}
         {(0..5).map(|f| {rsx! { div {  }}})}
@@ -225,14 +296,37 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
         p {}
         p {}
     );
     );
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    let (create, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        create.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateElement { id: 1, tag: "div" },
+            CreateElement { id: 2, tag: "div" },
+            CreateElement { id: 3, tag: "div" },
+            CreateElement { id: 4, tag: "div" },
+            CreateElement { id: 5, tag: "p" },
+            AppendChildren { many: 6 },
+        ]
+    );
+    assert_eq!(
+        change.edits,
+        [
+            Remove { root: 2 },
+            Remove { root: 3 },
+            Remove { root: 4 },
+            CreateElement { id: 6, tag: "h1" },
+            ReplaceWith { root: 0, m: 1 },
+            CreateElement { id: 7, tag: "h1" },
+            ReplaceWith { root: 1, m: 1 },
+        ]
+    );
 }
 }
 
 
 /// Should result in multiple nodes destroyed - with no changes
 /// Should result in multiple nodes destroyed - with no changes
 #[test]
 #[test]
 fn two_fragments_with_same_elements_are_differet() {
 fn two_fragments_with_same_elements_are_differet() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
     let left = rsx!(
     let left = rsx!(
         {(0..2).map(|f| {rsx! { div {  }}})}
         {(0..2).map(|f| {rsx! { div {  }}})}
@@ -243,98 +337,366 @@ fn two_fragments_with_same_elements_are_differet() {
         p {}
         p {}
     );
     );
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    let (create, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        create.edits,
+        [
+            CreateElement { id: 0, tag: "div" },
+            CreateElement { id: 1, tag: "div" },
+            CreateElement { id: 2, tag: "p" },
+            AppendChildren { many: 3 },
+        ]
+    );
+    assert_eq!(
+        change.edits,
+        [
+            CreateElement { id: 3, tag: "div" },
+            CreateElement { id: 4, tag: "div" },
+            CreateElement { id: 5, tag: "div" },
+            InsertAfter { root: 1, n: 3 },
+        ]
+    );
 }
 }
 
 
-// Similar test from above, but with extra child nodes
+/// should result in the removal of elements
 #[test]
 #[test]
-fn two_fragments_with_same_elements_are_differet_shorter() {
-    let dom = TestDom::new();
+fn keyed_diffing_order() {
+    let dom = new_dom();
 
 
     let left = rsx!(
     let left = rsx!(
-        {(0..5).map(|f| {rsx! { div {  }}})}
+        {(0..5).map(|f| {rsx! { div { key: "{f}"  }}})}
         p {"e"}
         p {"e"}
     );
     );
     let right = rsx!(
     let right = rsx!(
-        {(0..2).map(|f| {rsx! { div {  }}})}
+        {(0..2).map(|f| {rsx! { div { key: "{f}" }}})}
         p {"e"}
         p {"e"}
     );
     );
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    let (create, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        change.edits,
+        [Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 },]
+    );
 }
 }
 
 
-/// should result in the removal of elements
+/// Should result in moves, but not removals or additions
 #[test]
 #[test]
-fn keyed_diffing_order() {
-    let dom = TestDom::new();
+fn keyed_diffing_out_of_order() {
+    let dom = new_dom();
 
 
-    let left = rsx!(
-        {(0..5).map(|f| {rsx! { div { key: "{f}"  }}})}
-        p {"e"}
+    let left = rsx!({
+        [0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, changes) = dom.lazy_diff(left, right);
+    log::debug!("{:?}", &changes);
+    assert_eq!(
+        changes.edits,
+        [PushRoot { id: 6 }, InsertBefore { root: 4, n: 1 }]
     );
     );
-    let right = rsx!(
-        {(0..2).map(|f| {rsx! { div { key: "{f}" }}})}
-        p {"e"}
+}
+
+/// Should result in moves only
+#[test]
+fn keyed_diffing_out_of_order_adds() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        change.edits,
+        [
+            PushRoot { id: 4 },
+            PushRoot { id: 3 },
+            InsertBefore { n: 2, root: 0 }
+        ]
+    );
+}
+/// Should result in moves onl
+#[test]
+fn keyed_diffing_out_of_order_adds_2() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        change.edits,
+        [
+            PushRoot { id: 3 },
+            PushRoot { id: 4 },
+            InsertBefore { n: 2, root: 0 }
+        ]
+    );
+}
+
+/// Should result in moves onl
+#[test]
+fn keyed_diffing_out_of_order_adds_3() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        change.edits,
+        [
+            PushRoot { id: 4 },
+            PushRoot { id: 3 },
+            InsertBefore { n: 2, root: 1 }
+        ]
     );
     );
+}
+
+/// Should result in moves onl
+#[test]
+fn keyed_diffing_out_of_order_adds_4() {
+    let dom = new_dom();
 
 
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    let left = rsx!({
+        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        change.edits,
+        [
+            PushRoot { id: 4 },
+            PushRoot { id: 3 },
+            InsertBefore { n: 2, root: 2 }
+        ]
+    );
 }
 }
 
 
+/// Should result in moves onl
 #[test]
 #[test]
-fn fragment_keys() {
-    let r = 1;
-    let p = rsx! {
-        Fragment { key: "asd {r}" }
-    };
+fn keyed_diffing_out_of_order_adds_5() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        change.edits,
+        [PushRoot { id: 4 }, InsertBefore { n: 1, root: 3 }]
+    );
 }
 }
 
 
-/// Should result in moves, but not removals or additions
 #[test]
 #[test]
-fn keyed_diffing_out_of_order() {
-    let dom = TestDom::new();
+fn keyed_diffing_additions() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, change) = dom.lazy_diff(left, right);
+    assert_eq!(
+        change.edits,
+        [
+            CreateElement { id: 5, tag: "div" },
+            CreateElement { id: 6, tag: "div" },
+            InsertAfter { n: 2, root: 4 }
+        ]
+    );
+}
+
+#[test]
+fn keyed_diffing_additions_and_moves_on_ends() {
+    let dom = new_dom();
 
 
-    // 0, 1, 2, 3, 4, 5, 6, 7, 8,
     let left = rsx!({
     let left = rsx!({
-        (0..3).chain(3..6).chain(6..9).map(|f| {
+        [/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
             rsx! { div { key: "{f}"  }}
             rsx! { div { key: "{f}"  }}
         })
         })
     });
     });
 
 
-    // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
     let right = rsx!({
     let right = rsx!({
-        (0..3).chain((3..7).rev()).chain(7..10).map(|f| {
+        [/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
             rsx! { div { key: "{f}"  }}
             rsx! { div { key: "{f}"  }}
         })
         })
     });
     });
 
 
-    // LIS: 3, 7, 8,
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    let (_, change) = dom.lazy_diff(left, right);
+    log::debug!("{:?}", change);
+    assert_eq!(
+        change.edits,
+        [
+            // create 11, 12
+            CreateElement { tag: "div", id: 4 },
+            CreateElement { tag: "div", id: 5 },
+            InsertAfter { root: 2, n: 2 },
+            // move 7 to the front
+            PushRoot { id: 3 },
+            InsertBefore { root: 0, n: 1 }
+        ]
+    );
+}
+
+#[test]
+fn keyed_diffing_additions_and_moves_in_middle() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        [/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    // LIS: 4, 5, 6
+    let (_, change) = dom.lazy_diff(left, right);
+    log::debug!("{:#?}", change);
+    assert_eq!(
+        change.edits,
+        [
+            // create 13, 17
+            CreateElement { tag: "div", id: 4 },
+            CreateElement { tag: "div", id: 5 },
+            InsertBefore { root: 1, n: 2 },
+            // create 11, 12
+            CreateElement { tag: "div", id: 6 },
+            CreateElement { tag: "div", id: 7 },
+            InsertBefore { root: 2, n: 2 },
+            // move 7
+            PushRoot { id: 3 },
+            InsertBefore { root: 0, n: 1 }
+        ]
+    );
 }
 }
 
 
 #[test]
 #[test]
 fn controlled_keyed_diffing_out_of_order() {
 fn controlled_keyed_diffing_out_of_order() {
-    let dom = TestDom::new();
+    let dom = new_dom();
 
 
-    let left = [4, 5, 6, 7];
     let left = rsx!({
     let left = rsx!({
-        left.iter().map(|f| {
-            rsx! { div { key: "{f}" "{f}" }}
+        [4, 5, 6, 7].iter().map(|f| {
+            rsx! { div { key: "{f}" }}
         })
         })
     });
     });
 
 
-    // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
-    let right = [0, 5, 9, 6, 4];
     let right = rsx!({
     let right = rsx!({
-        right.iter().map(|f| {
-            rsx! { div { key: "{f}" "{f}" }}
+        [0, 5, 9, 6, 4].iter().map(|f| {
+            rsx! { div { key: "{f}" }}
         })
         })
     });
     });
 
 
-    // LIS: 3, 7, 8,
-    let edits = dom.lazy_diff(left, right);
-    dbg!(&edits);
+    // LIS: 5, 6
+    let (_, changes) = dom.lazy_diff(left, right);
+    log::debug!("{:#?}", &changes);
+    assert_eq!(
+        changes.edits,
+        [
+            // move 4 to after 6
+            PushRoot { id: 0 },
+            InsertAfter { n: 1, root: 2 },
+            // remove 7
+
+            // create 9 and insert before 6
+            CreateElement { id: 4, tag: "div" },
+            InsertBefore { n: 1, root: 2 },
+            // create 0 and insert before 5
+            CreateElement { id: 5, tag: "div" },
+            InsertBefore { n: 1, root: 1 },
+        ]
+    );
+}
+
+#[test]
+fn controlled_keyed_diffing_out_of_order_max_test() {
+    let dom = new_dom();
+
+    let left = rsx!({
+        [0, 1, 2, 3, 4].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let right = rsx!({
+        [3, 0, 1, 10, 2].iter().map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    let (_, changes) = dom.lazy_diff(left, right);
+    log::debug!("{:#?}", &changes);
+    assert_eq!(
+        changes.edits,
+        [
+            CreateElement { id: 5, tag: "div" },
+            InsertBefore { n: 1, root: 2 },
+            PushRoot { id: 3 },
+            InsertBefore { n: 1, root: 0 },
+        ]
+    );
 }
 }

+ 3 - 13
packages/core/tests/eventsystem.rs

@@ -1,12 +1,7 @@
 use bumpalo::Bump;
 use bumpalo::Bump;
 
 
 use anyhow::{Context, Result};
 use anyhow::{Context, Result};
-use dioxus::{
-    arena::SharedResources,
-    diff::{CreateMeta, DiffMachine},
-    prelude::*,
-    DomEdit,
-};
+use dioxus::{prelude::*, DomEdit};
 use dioxus_core as dioxus;
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;
 use dioxus_html as dioxus_elements;
 
 
@@ -19,14 +14,9 @@ async fn event_queue_works() {
     };
     };
 
 
     let mut dom = VirtualDom::new(App);
     let mut dom = VirtualDom::new(App);
-    let edits = dom.rebuild().unwrap();
+    let edits = dom.rebuild();
 
 
     async_std::task::spawn_local(async move {
     async_std::task::spawn_local(async move {
-        match dom.run_unbounded().await {
-            Err(_) => todo!(),
-            Ok(mutations) => {
-                //
-            }
-        }
+        // let mutations = dom.run_unbounded().await;
     });
     });
 }
 }

+ 21 - 0
packages/core/tests/hooks.rs

@@ -0,0 +1,21 @@
+use anyhow::{Context, Result};
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+
+#[test]
+fn sample_refs() {
+    // static App: FC<()> = |cx| {
+    //     let div_ref = use_node_ref::<MyRef, _>(cx);
+
+    //     cx.render(rsx! {
+    //         div {
+    //             style: { color: "red" },
+    //             node_ref: div_ref,
+    //             onmouseover: move |_| {
+    //                 div_ref.borrow_mut().focus();
+    //             },
+    //         },
+    //     })
+    // };
+}

+ 41 - 0
packages/core/tests/set_state_batch.rs

@@ -0,0 +1,41 @@
+use futures_util::StreamExt;
+
+/*
+furtures_channel provides us some batching simply due to how Rust's async works.
+
+Any hook that uses schedule_update is simply deferring to unbounded_send. Multiple
+unbounded_sends can be linked together in succession provided there isn't an "await"
+between them. Our internal batching mechanism simply waits for the "schedule_update"
+to fire and then pulls any messages off the unbounded_send queue.
+
+Additionally, due to how our "time slicing" works we'll always come back and check
+in for new work if the deadline hasn't expired. On average, our deadline should be
+about 10ms, which is way more than enough for diffing/creating to happen.
+*/
+#[async_std::test]
+async fn batch() {
+    let (sender, mut recver) = futures_channel::mpsc::unbounded::<i32>();
+
+    let _handle = async_std::task::spawn(async move {
+        let _msg = recver.next().await;
+        while let Ok(msg) = recver.try_next() {
+            println!("{:#?}", msg);
+        }
+        let _msg = recver.next().await;
+        while let Ok(msg) = recver.try_next() {
+            println!("{:#?}", msg);
+        }
+    });
+
+    sender.unbounded_send(1).unwrap();
+    sender.unbounded_send(2).unwrap();
+    sender.unbounded_send(3).unwrap();
+    sender.unbounded_send(4).unwrap();
+
+    async_std::task::sleep(std::time::Duration::from_millis(100)).await;
+
+    sender.unbounded_send(5).unwrap();
+    sender.unbounded_send(6).unwrap();
+    sender.unbounded_send(7).unwrap();
+    sender.unbounded_send(8).unwrap();
+}

+ 52 - 0
packages/core/tests/test_logging.rs

@@ -0,0 +1,52 @@
+pub fn set_up_logging(enabled: bool) {
+    use fern::colors::{Color, ColoredLevelConfig};
+    
+
+    if !enabled {
+        return;
+    }
+
+    // configure colors for the whole line
+    let colors_line = ColoredLevelConfig::new()
+        .error(Color::Red)
+        .warn(Color::Yellow)
+        // we actually don't need to specify the color for debug and info, they are white by default
+        .info(Color::White)
+        .debug(Color::White)
+        // depending on the terminals color scheme, this is the same as the background color
+        .trace(Color::BrightBlack);
+
+    // configure colors for the name of the level.
+    // since almost all of them are the same as the color for the whole line, we
+    // just clone `colors_line` and overwrite our changes
+    let colors_level = colors_line.clone().info(Color::Green);
+    // here we set up our fern Dispatch
+
+    // when running tests in batch, the logger is re-used, so ignore the logger error
+    let _ = fern::Dispatch::new()
+        .format(move |out, message, record| {
+            out.finish(format_args!(
+                "{color_line}[{level}{color_line}] {message}\x1B[0m",
+                color_line = format_args!(
+                    "\x1B[{}m",
+                    colors_line.get_color(&record.level()).to_fg_str()
+                ),
+                level = colors_level.color(record.level()),
+                message = message,
+            ));
+        })
+        // set the default log level. to filter out verbose log messages from dependencies, set
+        // this to Warn and overwrite the log level for your crate.
+        .level(log::LevelFilter::Debug)
+        // .level(log::LevelFilter::Warn)
+        // change log levels for individual modules. Note: This looks for the record's target
+        // field which defaults to the module path but can be overwritten with the `target`
+        // parameter:
+        // `info!(target="special_target", "This log message is about special_target");`
+        // .level_for("dioxus", log::LevelFilter::Debug)
+        // .level_for("dioxus", log::LevelFilter::Info)
+        // .level_for("pretty_colored", log::LevelFilter::Trace)
+        // output to stdout
+        .chain(std::io::stdout())
+        .apply();
+}

+ 8 - 8
packages/core/tests/vdom_rebuild.rs

@@ -19,7 +19,7 @@ fn app_runs() {
         cx.render(rsx!( div{"hello"} ))
         cx.render(rsx!( div{"hello"} ))
     };
     };
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);
-    let edits = vdom.rebuild_in_place().unwrap();
+    let edits = vdom.rebuild();
     dbg!(edits);
     dbg!(edits);
 }
 }
 
 
@@ -32,7 +32,7 @@ fn fragments_work() {
         ))
         ))
     };
     };
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);
-    let edits = vdom.rebuild_in_place().unwrap();
+    let edits = vdom.rebuild();
     // should result in a final "appendchildren n=2"
     // should result in a final "appendchildren n=2"
     dbg!(edits);
     dbg!(edits);
 }
 }
@@ -46,7 +46,7 @@ fn lists_work() {
         ))
         ))
     };
     };
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);
-    let edits = vdom.rebuild_in_place().unwrap();
+    let edits = vdom.rebuild();
     dbg!(edits);
     dbg!(edits);
 }
 }
 
 
@@ -61,10 +61,10 @@ fn conditional_rendering() {
     };
     };
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);
 
 
-    let edits = vdom.rebuild_in_place().unwrap();
-    dbg!(&edits);
+    let mutations = vdom.rebuild();
+    dbg!(&mutations);
     // the "false" fragment should generate an empty placeholder to re-visit
     // the "false" fragment should generate an empty placeholder to re-visit
-    assert!(edits[edits.len() - 2].is("CreatePlaceholder"));
+    assert!(mutations.edits[mutations.edits.len() - 2].is("CreatePlaceholder"));
 }
 }
 
 
 #[test]
 #[test]
@@ -82,7 +82,7 @@ fn child_components() {
         ))
         ))
     };
     };
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);
-    let edits = vdom.rebuild_in_place().unwrap();
+    let edits = vdom.rebuild();
     dbg!(edits);
     dbg!(edits);
 }
 }
 
 
@@ -94,6 +94,6 @@ fn suspended_works() {
     };
     };
 
 
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);
-    let edits = vdom.rebuild_in_place().unwrap();
+    let edits = vdom.rebuild();
     dbg!(edits);
     dbg!(edits);
 }
 }

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

@@ -0,0 +1,30 @@
+//! 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);
+}

+ 1 - 7
packages/desktop/src/dom.rs

@@ -1,6 +1,6 @@
 //! webview dom
 //! webview dom
 
 
-use dioxus_core::{DomEdit, RealDom};
+use dioxus_core::DomEdit;
 
 
 // pub struct WebviewRegistry {}
 // pub struct WebviewRegistry {}
 
 
@@ -29,9 +29,3 @@ impl WebviewDom<'_> {
     //     self.registry
     //     self.registry
     // }
     // }
 }
 }
-impl RealDom for WebviewDom<'_> {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-        // self.edits.push(PushRoot { root });
-    }
-}

+ 10 - 6
packages/desktop/src/events.rs

@@ -6,9 +6,9 @@ use std::rc::Rc;
 use dioxus_core::{
 use dioxus_core::{
     events::{
     events::{
         on::{MouseEvent, MouseEventInner},
         on::{MouseEvent, MouseEventInner},
-        VirtualEvent,
+        SyntheticEvent,
     },
     },
-    ElementId, EventPriority, EventTrigger, ScopeId,
+    ElementId, EventPriority, ScopeId, UserEvent,
 };
 };
 
 
 #[derive(serde::Serialize, serde::Deserialize)]
 #[derive(serde::Serialize, serde::Deserialize)]
@@ -17,15 +17,19 @@ struct ImEvent {
     mounted_dom_id: u64,
     mounted_dom_id: u64,
     scope: u64,
     scope: u64,
 }
 }
-pub fn trigger_from_serialized(val: serde_json::Value) -> EventTrigger {
+pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
     let mut data: Vec<ImEvent> = serde_json::from_value(val).unwrap();
     let mut data: Vec<ImEvent> = serde_json::from_value(val).unwrap();
     let data = data.drain(..).next().unwrap();
     let data = data.drain(..).next().unwrap();
 
 
-    let event = VirtualEvent::MouseEvent(MouseEvent(Rc::new(WebviewMouseEvent)));
+    let event = SyntheticEvent::MouseEvent(MouseEvent(Rc::new(WebviewMouseEvent)));
     let scope = ScopeId(data.scope as usize);
     let scope = ScopeId(data.scope as usize);
     let mounted_dom_id = Some(ElementId(data.mounted_dom_id as usize));
     let mounted_dom_id = Some(ElementId(data.mounted_dom_id as usize));
-    let priority = EventPriority::High;
-    EventTrigger::new(event, scope, mounted_dom_id, priority)
+    UserEvent {
+        name: todo!(),
+        event,
+        scope,
+        mounted_dom_id,
+    }
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]

+ 3 - 0
packages/html/src/attrval.s → packages/html/src/attrval.rs

@@ -1,3 +1,6 @@
+//! This module is not included anywhere.
+//!
+//! It is a prototype for a system that supports non-string attribute values.
 
 
 trait AsAttributeValue: Sized {
 trait AsAttributeValue: Sized {
     fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;
     fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;

+ 1 - 7
packages/mobile/src/dom.rs

@@ -1,6 +1,6 @@
 //! webview dom
 //! webview dom
 
 
-use dioxus_core::{DomEdit, ElementId, RealDom, ScopeId};
+use dioxus_core::{DomEdit, ElementId, ScopeId};
 use DomEdit::*;
 use DomEdit::*;
 
 
 pub struct WebviewRegistry {}
 pub struct WebviewRegistry {}
@@ -30,9 +30,3 @@ impl WebviewDom<'_> {
         self.registry
         self.registry
     }
     }
 }
 }
-impl<'bump> RealDom for WebviewDom<'bump> {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-        // self.edits.push(PushRoot { root });
-    }
-}

+ 4 - 4
packages/mobile/src/lib.rs

@@ -117,8 +117,8 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
 
 
                                         // Serialize the edit stream
                                         // Serialize the edit stream
                                         let edits = {
                                         let edits = {
-                                            let mut edits = Vec::new();
-                                            lock.rebuild(&mut edits).unwrap();
+                                            let mut edits = Vec::<DomEdit>::new();
+                                            // lock.rebuild(&mut edits).unwrap();
                                             serde_json::to_value(edits).unwrap()
                                             serde_json::to_value(edits).unwrap()
                                         };
                                         };
 
 
@@ -139,8 +139,8 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
 
 
                                     // Serialize the edit stream
                                     // Serialize the edit stream
                                     let edits = {
                                     let edits = {
-                                        let mut edits = Vec::new();
-                                        lock.rebuild(&mut edits).unwrap();
+                                        let mut edits = Vec::<DomEdit>::new();
+                                        // lock.rebuild(&mut edits).unwrap();
                                         serde_json::to_value(edits).unwrap()
                                         serde_json::to_value(edits).unwrap()
                                     };
                                     };
 
 

+ 25 - 0
packages/ssr/examples/basic.rs

@@ -0,0 +1,25 @@
+use dioxus::virtual_dom::VirtualDom;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    let mut dom = VirtualDom::new(App);
+    dom.rebuild();
+    println!(
+        "{}",
+        dioxus_ssr::render_vdom(&dom, |c| c.newline(true).indent(true))
+    )
+}
+
+pub static App: FC<()> = |cx| {
+    cx.render(rsx!(
+        div {
+            class: "overflow-hidden"
+            ul {
+                {(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})}
+            }
+            "hello world!"
+        }
+    ))
+};

+ 3 - 1
packages/ssr/examples/tide.rs

@@ -19,7 +19,9 @@ async fn main() -> Result<(), std::io::Error> {
             .map(|f| f.parse().unwrap_or("...?".to_string()))
             .map(|f| f.parse().unwrap_or("...?".to_string()))
             .unwrap_or("...?".to_string());
             .unwrap_or("...?".to_string());
 
 
-        let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
+        let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
+
+        dom.rebuild();
 
 
         Ok(Response::builder(200)
         Ok(Response::builder(200)
             .body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
             .body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))

+ 2 - 1
packages/ssr/examples/tofile.rs

@@ -13,7 +13,8 @@ fn main() {
     let mut file = File::create("example.html").unwrap();
     let mut file = File::create("example.html").unwrap();
 
 
     let mut dom = VirtualDom::new(App);
     let mut dom = VirtualDom::new(App);
-    dom.rebuild_in_place().expect("failed to run virtualdom");
+
+    dom.rebuild();
 
 
     file.write_fmt(format_args!(
     file.write_fmt(format_args!(
         "{}",
         "{}",

+ 16 - 16
packages/ssr/src/lib.rs

@@ -46,7 +46,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
 /// ```ignore
 /// ```ignore
 /// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
 /// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
 /// let mut vdom = VirtualDom::new(App);
 /// let mut vdom = VirtualDom::new(App);
-/// vdom.rebuild_in_place();
+/// vdom.rebuild();
 ///
 ///
 /// let renderer = TextRenderer::new(&vdom);
 /// let renderer = TextRenderer::new(&vdom);
 /// let output = format!("{}", renderer);
 /// let output = format!("{}", renderer);
@@ -74,8 +74,8 @@ impl<'a> TextRenderer<'a> {
     }
     }
 
 
     fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result {
     fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result {
-        match &node.kind {
-            VNodeKind::Text(text) => {
+        match &node {
+            VNode::Text(text) => {
                 if self.cfg.indent {
                 if self.cfg.indent {
                     for _ in 0..il {
                     for _ in 0..il {
                         write!(f, "    ")?;
                         write!(f, "    ")?;
@@ -83,7 +83,7 @@ impl<'a> TextRenderer<'a> {
                 }
                 }
                 write!(f, "{}", text.text)?
                 write!(f, "{}", text.text)?
             }
             }
-            VNodeKind::Anchor(anchor) => {
+            VNode::Anchor(anchor) => {
                 //
                 //
                 if self.cfg.indent {
                 if self.cfg.indent {
                     for _ in 0..il {
                     for _ in 0..il {
@@ -92,7 +92,7 @@ impl<'a> TextRenderer<'a> {
                 }
                 }
                 write!(f, "<!-- -->")?;
                 write!(f, "<!-- -->")?;
             }
             }
-            VNodeKind::Element(el) => {
+            VNode::Element(el) => {
                 if self.cfg.indent {
                 if self.cfg.indent {
                     for _ in 0..il {
                     for _ in 0..il {
                         write!(f, "    ")?;
                         write!(f, "    ")?;
@@ -129,7 +129,7 @@ impl<'a> TextRenderer<'a> {
                 //
                 //
                 // when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add
                 // when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add
                 // them interpreter's stack
                 // them interpreter's stack
-                match (self.cfg.pre_render, node.try_direct_id()) {
+                match (self.cfg.pre_render, node.try_mounted_id()) {
                     (true, Some(id)) => {
                     (true, Some(id)) => {
                         write!(f, " dio_el=\"{}\"", id)?;
                         write!(f, " dio_el=\"{}\"", id)?;
                         //
                         //
@@ -163,13 +163,13 @@ impl<'a> TextRenderer<'a> {
                     write!(f, "\n")?;
                     write!(f, "\n")?;
                 }
                 }
             }
             }
-            VNodeKind::Fragment(frag) => {
+            VNode::Fragment(frag) => {
                 for child in frag.children {
                 for child in frag.children {
                     self.html_render(child, f, il + 1)?;
                     self.html_render(child, f, il + 1)?;
                 }
                 }
             }
             }
-            VNodeKind::Component(vcomp) => {
-                let idx = vcomp.ass_scope.get().unwrap();
+            VNode::Component(vcomp) => {
+                let idx = vcomp.associated_scope.get().unwrap();
                 match (self.vdom, self.cfg.skip_components) {
                 match (self.vdom, self.cfg.skip_components) {
                     (Some(vdom), false) => {
                     (Some(vdom), false) => {
                         let new_node = vdom.get_scope(idx).unwrap().root();
                         let new_node = vdom.get_scope(idx).unwrap().root();
@@ -180,7 +180,7 @@ impl<'a> TextRenderer<'a> {
                     }
                     }
                 }
                 }
             }
             }
-            VNodeKind::Suspended { .. } => {
+            VNode::Suspended { .. } => {
                 // we can't do anything with suspended nodes
                 // we can't do anything with suspended nodes
             }
             }
         }
         }
@@ -286,28 +286,28 @@ mod tests {
     #[test]
     #[test]
     fn to_string_works() {
     fn to_string_works() {
         let mut dom = VirtualDom::new(SIMPLE_APP);
         let mut dom = VirtualDom::new(SIMPLE_APP);
-        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dom.rebuild();
         dbg!(render_vdom(&dom, |c| c));
         dbg!(render_vdom(&dom, |c| c));
     }
     }
 
 
     #[test]
     #[test]
     fn hydration() {
     fn hydration() {
         let mut dom = VirtualDom::new(NESTED_APP);
         let mut dom = VirtualDom::new(NESTED_APP);
-        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dom.rebuild();
         dbg!(render_vdom(&dom, |c| c.pre_render(true)));
         dbg!(render_vdom(&dom, |c| c.pre_render(true)));
     }
     }
 
 
     #[test]
     #[test]
     fn nested() {
     fn nested() {
         let mut dom = VirtualDom::new(NESTED_APP);
         let mut dom = VirtualDom::new(NESTED_APP);
-        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dom.rebuild();
         dbg!(render_vdom(&dom, |c| c));
         dbg!(render_vdom(&dom, |c| c));
     }
     }
 
 
     #[test]
     #[test]
     fn fragment_app() {
     fn fragment_app() {
         let mut dom = VirtualDom::new(FRAGMENT_APP);
         let mut dom = VirtualDom::new(FRAGMENT_APP);
-        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dom.rebuild();
         dbg!(render_vdom(&dom, |c| c));
         dbg!(render_vdom(&dom, |c| c));
     }
     }
 
 
@@ -319,7 +319,7 @@ mod tests {
         let mut file = File::create("index.html").unwrap();
         let mut file = File::create("index.html").unwrap();
 
 
         let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
         let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
-        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dom.rebuild();
 
 
         file.write_fmt(format_args!(
         file.write_fmt(format_args!(
             "{}",
             "{}",
@@ -337,7 +337,7 @@ mod tests {
         };
         };
 
 
         let mut dom = VirtualDom::new(STLYE_APP);
         let mut dom = VirtualDom::new(STLYE_APP);
-        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dom.rebuild();
         dbg!(render_vdom(&dom, |c| c));
         dbg!(render_vdom(&dom, |c| c));
     }
     }
 }
 }

+ 2 - 3
packages/web/Cargo.toml

@@ -14,16 +14,16 @@ js-sys = "0.3"
 wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] }
 wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] }
 lazy_static = "1.4.0"
 lazy_static = "1.4.0"
 wasm-bindgen-futures = "0.4.20"
 wasm-bindgen-futures = "0.4.20"
-wasm-logger = "0.2.0"
 log = "0.4.14"
 log = "0.4.14"
 fxhash = "0.2.1"
 fxhash = "0.2.1"
+wasm-logger = "0.2.0"
 console_error_panic_hook = "0.1.6"
 console_error_panic_hook = "0.1.6"
 generational-arena = "0.2.8"
 generational-arena = "0.2.8"
 wasm-bindgen-test = "0.3.21"
 wasm-bindgen-test = "0.3.21"
 once_cell = "1.8"
 once_cell = "1.8"
 async-channel = "1.6.1"
 async-channel = "1.6.1"
 anyhow = "1.0"
 anyhow = "1.0"
-
+gloo-timers = { version = "0.2.1", features = ["futures"] }
 futures-util = "0.3.15"
 futures-util = "0.3.15"
 
 
 [dependencies.web-sys]
 [dependencies.web-sys]
@@ -78,7 +78,6 @@ im-rc = "15.0.0"
 separator = "0.4.1"
 separator = "0.4.1"
 uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
 uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
 dioxus-hooks = { path = "../hooks" }
 dioxus-hooks = { path = "../hooks" }
-gloo-timers = { version = "0.2.1", features = ["futures"] }
 serde = { version = "1.0.126", features = ["derive"] }
 serde = { version = "1.0.126", features = ["derive"] }
 surf = { git = "https://github.com/http-rs/surf", rev = "1ffaba8873", default-features = false, features = [
 surf = { git = "https://github.com/http-rs/surf", rev = "1ffaba8873", default-features = false, features = [
     "wasm-client",
     "wasm-client",

+ 21 - 13
packages/web/examples/basic.rs

@@ -1,19 +1,17 @@
 //! Basic example that renders a simple VNode to the browser.
 //! Basic example that renders a simple VNode to the browser.
 
 
+// all these imports are done automatically with the `dioxus` crate and `prelude`
+// need to do them manually for this example
 use dioxus::events::on::MouseEvent;
 use dioxus::events::on::MouseEvent;
 use dioxus_core as dioxus;
 use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_core::prelude::*;
 use dioxus_hooks::*;
 use dioxus_hooks::*;
 use dioxus_html as dioxus_elements;
 use dioxus_html as dioxus_elements;
-// use wasm_timer;
-
-use std::future::Future;
-
-use std::{pin::Pin, time::Duration};
 
 
 use dioxus::prelude::*;
 use dioxus::prelude::*;
-
 use dioxus_web::*;
 use dioxus_web::*;
+use std::future::Future;
+use std::{pin::Pin, time::Duration};
 
 
 fn main() {
 fn main() {
     // Setup logging
     // Setup logging
@@ -21,18 +19,15 @@ fn main() {
     console_error_panic_hook::set_once();
     console_error_panic_hook::set_once();
 
 
     // Run the app
     // Run the app
-    dioxus_web::launch(App, |c| c)
+    dioxus_web::launch(APP, |c| c)
 }
 }
 
 
-static App: FC<()> = |cx| {
-    let mut count = use_state(cx, || 0);
+static APP: FC<()> = |cx| {
+    let mut count = use_state(cx, || 3);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
-            button {
-                "add"
-                onclick: move |_| count += 1
-            }
+            button { onclick: move |_| count += 1, "add"  }
             ul {
             ul {
                 {(0..*count).map(|f| rsx!{
                 {(0..*count).map(|f| rsx!{
                     li { "a - {f}" }
                     li { "a - {f}" }
@@ -40,6 +35,19 @@ static App: FC<()> = |cx| {
                     li { "c - {f}" }
                     li { "c - {f}" }
                 })}
                 })}
             }
             }
+            Child {}
+        }
+    })
+};
+
+static Child: FC<()> = |cx| {
+    cx.render(rsx! {
+        div {
+            div {
+                div {
+                    "hello child"
+                }
+            }
         }
         }
     })
     })
 };
 };

+ 248 - 365
packages/web/src/dom.rs

@@ -1,14 +1,16 @@
-use std::{collections::HashMap, rc::Rc, sync::Arc};
+use std::{collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
 
 
 use dioxus_core::{
 use dioxus_core::{
-    events::{EventTrigger, VirtualEvent},
+    events::{on::GenericEventInner, SyntheticEvent, UserEvent},
+    mutations::NodeRefMutation,
+    scheduler::SchedulerMsg,
     DomEdit, ElementId, ScopeId,
     DomEdit, ElementId, ScopeId,
 };
 };
 use fxhash::FxHashMap;
 use fxhash::FxHashMap;
 use wasm_bindgen::{closure::Closure, JsCast};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{
 use web_sys::{
     window, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
     window, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
-    HtmlOptionElement, Node, NodeList,
+    HtmlOptionElement, Node, NodeList, UiEvent,
 };
 };
 
 
 use crate::{nodeslab::NodeSlab, WebConfig};
 use crate::{nodeslab::NodeSlab, WebConfig};
@@ -23,7 +25,7 @@ pub struct WebsysDom {
 
 
     root: Element,
     root: Element,
 
 
-    sender_callback: Rc<dyn Fn(EventTrigger)>,
+    sender_callback: Rc<dyn Fn(SchedulerMsg)>,
 
 
     // map of listener types to number of those listeners
     // map of listener types to number of those listeners
     // This is roughly a delegater
     // This is roughly a delegater
@@ -38,7 +40,7 @@ pub struct WebsysDom {
     last_node_was_text: bool,
     last_node_was_text: bool,
 }
 }
 impl WebsysDom {
 impl WebsysDom {
-    pub fn new(root: Element, cfg: WebConfig, sender_callback: Rc<dyn Fn(EventTrigger)>) -> Self {
+    pub fn new(root: Element, cfg: WebConfig, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
         let document = load_document();
         let document = load_document();
 
 
         let mut nodes = NodeSlab::new(2000);
         let mut nodes = NodeSlab::new(2000);
@@ -77,6 +79,18 @@ impl WebsysDom {
         }
         }
     }
     }
 
 
+    pub fn apply_refs(&mut self, refs: &[NodeRefMutation]) {
+        for item in refs {
+            if let Some(bla) = &item.element {
+                let node = self.nodes[item.element_id.as_u64() as usize]
+                    .as_ref()
+                    .unwrap()
+                    .clone();
+                bla.set(Box::new(node)).unwrap();
+            }
+        }
+    }
+
     pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) {
     pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) {
         for edit in edits.drain(..) {
         for edit in edits.drain(..) {
             log::info!("Handling edit: {:#?}", edit);
             log::info!("Handling edit: {:#?}", edit);
@@ -84,26 +98,27 @@ impl WebsysDom {
                 DomEdit::PushRoot { id: root } => self.push(root),
                 DomEdit::PushRoot { id: root } => self.push(root),
                 DomEdit::PopRoot => self.pop(),
                 DomEdit::PopRoot => self.pop(),
                 DomEdit::AppendChildren { many } => self.append_children(many),
                 DomEdit::AppendChildren { many } => self.append_children(many),
-                DomEdit::ReplaceWith { n, m } => self.replace_with(n, m),
-                DomEdit::Remove => self.remove(),
+                DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
+                DomEdit::Remove { root } => self.remove(root),
                 DomEdit::RemoveAllChildren => self.remove_all_children(),
                 DomEdit::RemoveAllChildren => self.remove_all_children(),
                 DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id),
                 DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id),
                 DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id),
                 DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id),
                 DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id),
                 DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id),
                 DomEdit::CreatePlaceholder { id } => self.create_placeholder(id),
                 DomEdit::CreatePlaceholder { id } => self.create_placeholder(id),
                 DomEdit::NewEventListener {
                 DomEdit::NewEventListener {
-                    event_name: event,
+                    event_name,
                     scope,
                     scope,
-                    mounted_node_id: node,
-                } => self.new_event_listener(event, scope, node),
+                    mounted_node_id,
+                } => self.new_event_listener(event_name, scope, mounted_node_id),
 
 
                 DomEdit::RemoveEventListener { event } => todo!(),
                 DomEdit::RemoveEventListener { event } => todo!(),
+
                 DomEdit::SetText { text } => self.set_text(text),
                 DomEdit::SetText { text } => self.set_text(text),
                 DomEdit::SetAttribute { field, value, ns } => self.set_attribute(field, value, ns),
                 DomEdit::SetAttribute { field, value, ns } => self.set_attribute(field, value, ns),
                 DomEdit::RemoveAttribute { name } => self.remove_attribute(name),
                 DomEdit::RemoveAttribute { name } => self.remove_attribute(name),
 
 
-                DomEdit::InsertAfter { n } => self.insert_after(n),
-                DomEdit::InsertBefore { n } => self.insert_before(n),
+                DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
+                DomEdit::InsertBefore { n, root } => self.insert_before(n, root),
             }
             }
         }
         }
     }
     }
@@ -118,14 +133,13 @@ impl WebsysDom {
 
 
         self.stack.push(real_node);
         self.stack.push(real_node);
     }
     }
+
     // drop the node off the stack
     // drop the node off the stack
     fn pop(&mut self) {
     fn pop(&mut self) {
         self.stack.pop();
         self.stack.pop();
     }
     }
 
 
     fn append_children(&mut self, many: u32) {
     fn append_children(&mut self, many: u32) {
-        log::debug!("Called [`append_child`]");
-
         let root: Node = self
         let root: Node = self
             .stack
             .stack
             .list
             .list
@@ -133,9 +147,11 @@ impl WebsysDom {
             .unwrap()
             .unwrap()
             .clone();
             .clone();
 
 
-        for _ in 0..many {
-            let child = self.stack.pop();
-
+        for child in self
+            .stack
+            .list
+            .drain((self.stack.list.len() - many as usize)..)
+        {
             if child.dyn_ref::<web_sys::Text>().is_some() {
             if child.dyn_ref::<web_sys::Text>().is_some() {
                 if self.last_node_was_text {
                 if self.last_node_was_text {
                     let comment_node = self
                     let comment_node = self
@@ -143,75 +159,46 @@ impl WebsysDom {
                         .create_comment("dioxus")
                         .create_comment("dioxus")
                         .dyn_into::<Node>()
                         .dyn_into::<Node>()
                         .unwrap();
                         .unwrap();
-                    self.stack.top().append_child(&comment_node).unwrap();
+                    root.append_child(&comment_node).unwrap();
                 }
                 }
                 self.last_node_was_text = true;
                 self.last_node_was_text = true;
             } else {
             } else {
                 self.last_node_was_text = false;
                 self.last_node_was_text = false;
             }
             }
-
             root.append_child(&child).unwrap();
             root.append_child(&child).unwrap();
         }
         }
     }
     }
 
 
-    fn replace_with(&mut self, n: u32, m: u32) {
-        log::debug!("Called [`replace_with`]");
-
-        let mut new_nodes = vec![];
-        for _ in 0..m {
-            new_nodes.push(self.stack.pop());
-        }
-
-        let mut old_nodes = vec![];
-        for _ in 0..n {
-            old_nodes.push(self.stack.pop());
-        }
-
-        for node in &old_nodes[1..] {
-            node.dyn_ref::<Element>().unwrap().remove();
-        }
-
-        let old = old_nodes[0].clone();
-        let arr: js_sys::Array = new_nodes.iter().collect();
-        let el = old.dyn_into::<Element>().unwrap();
-        el.replace_with_with_node(&arr).unwrap();
-        // let arr = js_sys::Array::from();
-
-        // TODO: use different-sized replace withs
-        // if m == 1 {
-        //     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);
-        //     }
-        // }
-
-        // self.stack.push(new_node);
+    fn replace_with(&mut self, m: u32, root: u64) {
+        let old = self.nodes[root as usize].as_ref().unwrap();
+
+        let arr: js_sys::Array = self
+            .stack
+            .list
+            .drain((self.stack.list.len() - m as usize)..)
+            .collect();
+
+        if let Some(el) = old.dyn_ref::<Element>() {
+            el.replace_with_with_node(&arr).unwrap();
+        } else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
+            el.replace_with_with_node(&arr).unwrap();
+        } else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
+            el.replace_with_with_node(&arr).unwrap();
+        }
     }
     }
 
 
-    fn remove(&mut self) {
-        log::debug!("Called [`remove`]");
-        todo!()
+    fn remove(&mut self, root: u64) {
+        let node = self.nodes[root as usize].as_ref().unwrap();
+        if let Some(element) = node.dyn_ref::<Element>() {
+            element.remove();
+        } else {
+            if let Some(parent) = node.parent_node() {
+                parent.remove_child(&node).unwrap();
+            }
+        }
     }
     }
 
 
     fn remove_all_children(&mut self) {
     fn remove_all_children(&mut self) {
-        log::debug!("Called [`remove_all_children`]");
         todo!()
         todo!()
     }
     }
 
 
@@ -219,22 +206,22 @@ impl WebsysDom {
         self.create_element("pre", None, id);
         self.create_element("pre", None, id);
         self.set_attribute("hidden", "", None);
         self.set_attribute("hidden", "", None);
     }
     }
-    fn create_text_node(&mut self, text: &str, id: u64) {
-        // let nid = self.node_counter.next();
 
 
+    fn create_text_node(&mut self, text: &str, id: u64) {
         let textnode = self
         let textnode = self
             .document
             .document
             .create_text_node(text)
             .create_text_node(text)
             .dyn_into::<Node>()
             .dyn_into::<Node>()
             .unwrap();
             .unwrap();
 
 
-        let id = id as usize;
         self.stack.push(textnode.clone());
         self.stack.push(textnode.clone());
-        self.nodes[id] = Some(textnode);
+
+        self.nodes[(id as usize)] = Some(textnode);
     }
     }
 
 
     fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
     fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
         let tag = wasm_bindgen::intern(tag);
         let tag = wasm_bindgen::intern(tag);
+
         let el = match ns {
         let el = match ns {
             Some(ns) => self
             Some(ns) => self
                 .document
                 .document
@@ -249,18 +236,12 @@ impl WebsysDom {
                 .dyn_into::<Node>()
                 .dyn_into::<Node>()
                 .unwrap(),
                 .unwrap(),
         };
         };
-        let id = id as usize;
 
 
         self.stack.push(el.clone());
         self.stack.push(el.clone());
-        self.nodes[id] = Some(el);
-        // let nid = self.node_counter.?next();
-        // let nid = self.nodes.insert(el).data().as_ffi();
-        // log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
-        // ElementId::new(nid)
+        self.nodes[(id as usize)] = Some(el);
     }
     }
 
 
     fn new_event_listener(&mut self, event: &'static str, scope: ScopeId, real_id: u64) {
     fn new_event_listener(&mut self, event: &'static str, scope: ScopeId, real_id: u64) {
-        // let (_on, event) = event.split_at(2);
         let event = wasm_bindgen::intern(event);
         let event = wasm_bindgen::intern(event);
 
 
         // attach the correct attributes to the element
         // attach the correct attributes to the element
@@ -297,7 +278,7 @@ impl WebsysDom {
                 // "Result" cannot be received from JS
                 // "Result" cannot be received from JS
                 // Instead, we just build and immediately execute a closure that returns result
                 // Instead, we just build and immediately execute a closure that returns result
                 match decode_trigger(event) {
                 match decode_trigger(event) {
-                    Ok(synthetic_event) => trigger.as_ref()(synthetic_event),
+                    Ok(synthetic_event) => trigger.as_ref()(SchedulerMsg::UiEvent(synthetic_event)),
                     Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
                     Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
                 };
                 };
             }) as Box<dyn FnMut(&Event)>);
             }) as Box<dyn FnMut(&Event)>);
@@ -320,7 +301,8 @@ impl WebsysDom {
     }
     }
 
 
     fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
     fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
-        if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+        let node = self.stack.top();
+        if let Some(el) = node.dyn_ref::<Element>() {
             match ns {
             match ns {
                 // inline style support
                 // inline style support
                 Some("style") => {
                 Some("style") => {
@@ -330,11 +312,20 @@ impl WebsysDom {
                 }
                 }
                 _ => el.set_attribute(name, value).unwrap(),
                 _ => el.set_attribute(name, value).unwrap(),
             }
             }
-            match name {
-                "value" => {}
-                "checked" => {}
-                "selected" => {}
-                _ => {}
+        }
+
+        if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
+            if name == "value" {
+                node.set_value(value);
+            }
+            if name == "checked" {
+                node.set_checked(true);
+            }
+        }
+
+        if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
+            if name == "selected" {
+                node.set_selected(true);
             }
             }
         }
         }
     }
     }
@@ -361,49 +352,52 @@ impl WebsysDom {
         }
         }
     }
     }
 
 
-    fn insert_after(&mut self, n: u32) {
-        let mut new_nodes = vec![];
-        for _ in 0..n {
-            new_nodes.push(self.stack.pop());
-        }
-
-        let after = self.stack.top().clone();
-        let arr: js_sys::Array = new_nodes.iter().collect();
-
-        let el = after.dyn_into::<Element>().unwrap();
-        el.after_with_node(&arr).unwrap();
-        // let mut old_nodes = vec![];
-        // for _ in 0..n {
-        //     old_nodes.push(self.stack.pop());
-        // }
-
-        // let el = self.stack.top();
-    }
+    fn insert_after(&mut self, n: u32, root: u64) {
+        let old = self.nodes[root as usize].as_ref().unwrap();
 
 
-    fn insert_before(&mut self, n: u32) {
-        let n = n as usize;
-        let root = self
+        let arr: js_sys::Array = self
             .stack
             .stack
             .list
             .list
-            .get(self.stack.list.len() - n)
-            .unwrap()
-            .clone();
-        for _ in 0..n {
-            let el = self.stack.pop();
-            root.insert_before(&el, None).unwrap();
+            .drain((self.stack.list.len() - n as usize)..)
+            .collect();
+
+        if let Some(el) = old.dyn_ref::<Element>() {
+            el.after_with_node(&arr).unwrap();
+        } else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
+            el.after_with_node(&arr).unwrap();
+        } else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
+            el.after_with_node(&arr).unwrap();
         }
         }
     }
     }
-}
 
 
-impl<'a> dioxus_core::diff::RealDom for WebsysDom {
-    // fn request_available_node(&mut self) -> ElementId {
-    //     let key = self.nodes.insert(None);
-    //     log::debug!("making new key: {:#?}", key);
-    //     ElementId(key.data().as_ffi())
-    // }
+    fn insert_before(&mut self, n: u32, root: u64) {
+        let after = self.nodes[root as usize].as_ref().unwrap();
 
 
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
+        if n == 1 {
+            let before = self.stack.pop();
+
+            after
+                .parent_node()
+                .unwrap()
+                .insert_before(&before, Some(&after))
+                .unwrap();
+
+            after.insert_before(&before, None).unwrap();
+        } else {
+            let arr: js_sys::Array = self
+                .stack
+                .list
+                .drain((self.stack.list.len() - n as usize)..)
+                .collect();
+
+            if let Some(el) = after.dyn_ref::<Element>() {
+                el.before_with_node(&arr).unwrap();
+            } else if let Some(el) = after.dyn_ref::<web_sys::CharacterData>() {
+                el.before_with_node(&arr).unwrap();
+            } else if let Some(el) = after.dyn_ref::<web_sys::DocumentType>() {
+                el.before_with_node(&arr).unwrap();
+            }
+        }
     }
     }
 }
 }
 
 
@@ -443,287 +437,89 @@ impl Stack {
     }
     }
 }
 }
 
 
-fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
+fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent {
+    use crate::events::*;
     use dioxus_core::events::on::*;
     use dioxus_core::events::on::*;
     match event.type_().as_str() {
     match event.type_().as_str() {
         "copy" | "cut" | "paste" => {
         "copy" | "cut" | "paste" => {
-            struct WebsysClipboardEvent();
-            impl ClipboardEventInner for WebsysClipboardEvent {}
-            VirtualEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent())))
+            SyntheticEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent(event))))
         }
         }
-
         "compositionend" | "compositionstart" | "compositionupdate" => {
         "compositionend" | "compositionstart" | "compositionupdate" => {
             let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
             let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
-            struct WebsysCompositionEvent(web_sys::CompositionEvent);
-            impl CompositionEventInner for WebsysCompositionEvent {
-                fn data(&self) -> String {
-                    todo!()
-                }
-            }
-            VirtualEvent::CompositionEvent(CompositionEvent(Rc::new(WebsysCompositionEvent(evt))))
+            SyntheticEvent::CompositionEvent(CompositionEvent(Rc::new(WebsysCompositionEvent(evt))))
         }
         }
-
         "keydown" | "keypress" | "keyup" => {
         "keydown" | "keypress" | "keyup" => {
-            struct Event(web_sys::KeyboardEvent);
-            impl KeyboardEventInner for Event {
-                fn char_code(&self) -> u32 {
-                    todo!()
-                }
-                fn key_code(&self) -> KeyCode {
-                    todo!()
-                }
-                fn ctrl_key(&self) -> bool {
-                    todo!()
-                }
-
-                fn key(&self) -> String {
-                    todo!()
-                }
-
-                fn locale(&self) -> String {
-                    todo!()
-                }
-
-                fn location(&self) -> usize {
-                    todo!()
-                }
-
-                fn meta_key(&self) -> bool {
-                    todo!()
-                }
-
-                fn repeat(&self) -> bool {
-                    todo!()
-                }
-
-                fn shift_key(&self) -> bool {
-                    todo!()
-                }
-
-                fn which(&self) -> usize {
-                    todo!()
-                }
-
-                fn get_modifier_state(&self, key_code: usize) -> bool {
-                    todo!()
-                }
-            }
-            let evt: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
-            VirtualEvent::KeyboardEvent(KeyboardEvent(Rc::new(Event(evt))))
+            let evt: web_sys::KeyboardEvent = event.dyn_into().unwrap();
+            SyntheticEvent::KeyboardEvent(KeyboardEvent(Rc::new(WebsysKeyboardEvent(evt))))
         }
         }
-
         "focus" | "blur" => {
         "focus" | "blur" => {
-            struct Event(web_sys::FocusEvent);
-            impl FocusEventInner for Event {}
-            let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap();
-            VirtualEvent::FocusEvent(FocusEvent(Rc::new(Event(evt))))
+            let evt: web_sys::FocusEvent = event.dyn_into().unwrap();
+            SyntheticEvent::FocusEvent(FocusEvent(Rc::new(WebsysFocusEvent(evt))))
         }
         }
-
         "change" => {
         "change" => {
-            // struct Event(web_sys::Event);
-            // impl GenericEventInner for Event {
-            //     fn bubbles(&self) -> bool {
-            //         todo!()
-            //     }
-
-            //     fn cancel_bubble(&self) {
-            //         todo!()
-            //     }
-
-            //     fn cancelable(&self) -> bool {
-            //         todo!()
-            //     }
-
-            //     fn composed(&self) -> bool {
-            //         todo!()
-            //     }
-
-            //     fn composed_path(&self) -> String {
-            //         todo!()
-            //     }
-
-            //     fn current_target(&self) {
-            //         todo!()
-            //     }
-
-            //     fn default_prevented(&self) -> bool {
-            //         todo!()
-            //     }
-
-            //     fn event_phase(&self) -> usize {
-            //         todo!()
-            //     }
-
-            //     fn is_trusted(&self) -> bool {
-            //         todo!()
-            //     }
-
-            //     fn prevent_default(&self) {
-            //         todo!()
-            //     }
-
-            //     fn stop_immediate_propagation(&self) {
-            //         todo!()
-            //     }
-
-            //     fn stop_propagation(&self) {
-            //         todo!()
-            //     }
-
-            //     fn target(&self) {
-            //         todo!()
-            //     }
-
-            //     fn time_stamp(&self) -> usize {
-            //         todo!()
-            //     }
-            // }
-            // let evt: web_sys::Event = event.clone().dyn_into().expect("wrong error typ");
-            // VirtualEvent::Event(GenericEvent(Rc::new(Event(evt))))
-            todo!()
+            let evt = event.dyn_into().unwrap();
+            SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
         }
         }
-
         "input" | "invalid" | "reset" | "submit" => {
         "input" | "invalid" | "reset" | "submit" => {
-            // is a special react events
-            let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
-            let this: web_sys::EventTarget = evt.target().unwrap();
-
-            let value = (&this)
-                .dyn_ref()
-                .map(|input: &web_sys::HtmlInputElement| input.value())
-                .or_else(|| {
-                    (&this)
-                        .dyn_ref()
-                        .map(|input: &web_sys::HtmlTextAreaElement| input.value())
-                })
-                .or_else(|| {
-                    (&this)
-                        .dyn_ref::<web_sys::HtmlElement>()
-                        .unwrap()
-                        .text_content()
-                })
-                .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
-
-            todo!()
-            // VirtualEvent::FormEvent(FormEvent { value })
+            let evt: web_sys::InputEvent = event.clone().dyn_into().unwrap();
+            SyntheticEvent::FormEvent(FormEvent(Rc::new(WebsysFormEvent(evt))))
         }
         }
-
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
             let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap();
             let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap();
-
-            #[derive(Debug)]
-            pub struct CustomMouseEvent(web_sys::MouseEvent);
-            impl dioxus_core::events::on::MouseEventInner for CustomMouseEvent {
-                fn alt_key(&self) -> bool {
-                    self.0.alt_key()
-                }
-                fn button(&self) -> i16 {
-                    self.0.button()
-                }
-                fn buttons(&self) -> u16 {
-                    self.0.buttons()
-                }
-                fn client_x(&self) -> i32 {
-                    self.0.client_x()
-                }
-                fn client_y(&self) -> i32 {
-                    self.0.client_y()
-                }
-                fn ctrl_key(&self) -> bool {
-                    self.0.ctrl_key()
-                }
-                fn meta_key(&self) -> bool {
-                    self.0.meta_key()
-                }
-                fn page_x(&self) -> i32 {
-                    self.0.page_x()
-                }
-                fn page_y(&self) -> i32 {
-                    self.0.page_y()
-                }
-                fn screen_x(&self) -> i32 {
-                    self.0.screen_x()
-                }
-                fn screen_y(&self) -> i32 {
-                    self.0.screen_y()
-                }
-                fn shift_key(&self) -> bool {
-                    self.0.shift_key()
-                }
-
-                // yikes
-                // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
-                fn get_modifier_state(&self, key_code: &str) -> bool {
-                    self.0.get_modifier_state(key_code)
-                }
-            }
-            VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
+            SyntheticEvent::MouseEvent(MouseEvent(Rc::new(WebsysMouseEvent(evt))))
         }
         }
-
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
             let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap();
             let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            SyntheticEvent::PointerEvent(PointerEvent(Rc::new(WebsysPointerEvent(evt))))
         }
         }
-
         "select" => {
         "select" => {
-            // let evt: web_sys::SelectionEvent = event.clone().dyn_into().unwrap();
-            // not required to construct anything special beyond standard event stuff
-            todo!()
+            let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap();
+            SyntheticEvent::SelectionEvent(SelectionEvent(Rc::new(WebsysGenericUiEvent(evt))))
         }
         }
-
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
             let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap();
             let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            SyntheticEvent::TouchEvent(TouchEvent(Rc::new(WebsysTouchEvent(evt))))
         }
         }
-
         "scroll" => {
         "scroll" => {
-            // let evt: web_sys::UIEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap();
+            SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
         }
         }
-
         "wheel" => {
         "wheel" => {
             let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
             let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            SyntheticEvent::WheelEvent(WheelEvent(Rc::new(WebsysWheelEvent(evt))))
         }
         }
         "animationstart" | "animationend" | "animationiteration" => {
         "animationstart" | "animationend" | "animationiteration" => {
             let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
             let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            SyntheticEvent::AnimationEvent(AnimationEvent(Rc::new(WebsysAnimationEvent(evt))))
         }
         }
-
         "transitionend" => {
         "transitionend" => {
             let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
             let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            SyntheticEvent::TransitionEvent(TransitionEvent(Rc::new(WebsysTransitionEvent(evt))))
         }
         }
-
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
         | "timeupdate" | "volumechange" | "waiting" => {
         | "timeupdate" | "volumechange" | "waiting" => {
-            // not required to construct anything special beyond standard event stuff
-
-            // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
-            // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap();
+            SyntheticEvent::MediaEvent(MediaEvent(Rc::new(WebsysMediaEvent(evt))))
         }
         }
-
         "toggle" => {
         "toggle" => {
-            // not required to construct anything special beyond standard event stuff (target)
-
-            // let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap();
+            SyntheticEvent::ToggleEvent(ToggleEvent(Rc::new(WebsysToggleEvent(evt))))
         }
         }
         _ => {
         _ => {
-            todo!()
+            let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap();
+            SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt))))
         }
         }
     }
     }
 }
 }
 
 
 /// This function decodes a websys event and produces an EventTrigger
 /// This function decodes a websys event and produces an EventTrigger
 /// With the websys implementation, we attach a unique key to the nodes
 /// With the websys implementation, we attach a unique key to the nodes
-fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
+fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
     log::debug!("Handling event!");
     log::debug!("Handling event!");
 
 
     let target = event
     let target = event
@@ -734,13 +530,13 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
 
 
     let typ = event.type_();
     let typ = event.type_();
 
 
-    use anyhow::Context;
+    // let attrs = target.attributes();
+    // for x in 0..attrs.length() {
+    //     let attr = attrs.item(x).unwrap();
+    //     log::debug!("attrs include: {:#?}", attr);
+    // }
 
 
-    let attrs = target.attributes();
-    for x in 0..attrs.length() {
-        let attr = attrs.item(x).unwrap();
-        log::debug!("attrs include: {:#?}", attr);
-    }
+    use anyhow::Context;
 
 
     // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
     // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
     let val: String = target
     let val: String = target
@@ -765,12 +561,12 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
     let triggered_scope = gi_id;
     let triggered_scope = gi_id;
     // let triggered_scope: ScopeId = KeyData::from_ffi(gi_id).into();
     // let triggered_scope: ScopeId = KeyData::from_ffi(gi_id).into();
     log::debug!("Triggered scope is {:#?}", triggered_scope);
     log::debug!("Triggered scope is {:#?}", triggered_scope);
-    Ok(EventTrigger::new(
-        virtual_event_from_websys_event(event),
-        ScopeId(triggered_scope as usize),
-        Some(ElementId(real_id as usize)),
-        dioxus_core::events::EventPriority::High,
-    ))
+    Ok(UserEvent {
+        name: event_name_from_typ(&typ),
+        event: virtual_event_from_websys_event(event.clone()),
+        mounted_dom_id: Some(ElementId(real_id as usize)),
+        scope: ScopeId(triggered_scope as usize),
+    })
 }
 }
 
 
 pub fn prepare_websys_dom() -> Element {
 pub fn prepare_websys_dom() -> Element {
@@ -783,3 +579,90 @@ pub fn load_document() -> Document {
         .document()
         .document()
         .expect("should have access to the Document")
         .expect("should have access to the 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",
+        _ => {
+            panic!("unsupported event type")
+        }
+    }
+}

+ 478 - 0
packages/web/src/events.rs

@@ -0,0 +1,478 @@
+//! Ported events into Dioxus Synthetic Event system
+//!
+//! event porting is pretty boring, sorry.
+
+use dioxus_core::events::on::*;
+use wasm_bindgen::JsCast;
+use web_sys::{Event, UiEvent};
+
+/// All events implement the generic event type - they're all UI events
+trait WebsysGenericEvent {
+    fn as_ui_event(&self) -> &UiEvent;
+}
+
+impl GenericEventInner for &dyn WebsysGenericEvent {
+    /// On WebSys, this returns an &UiEvent which can be casted via dyn_ref into the correct sub type.
+    fn raw_event(&self) -> &dyn std::any::Any {
+        self.as_ui_event()
+    }
+
+    fn bubbles(&self) -> bool {
+        self.as_ui_event().bubbles()
+    }
+
+    fn cancel_bubble(&self) {
+        self.as_ui_event().cancel_bubble();
+    }
+
+    fn cancelable(&self) -> bool {
+        self.as_ui_event().cancelable()
+    }
+
+    fn composed(&self) -> bool {
+        self.as_ui_event().composed()
+    }
+
+    fn current_target(&self) {
+        if cfg!(debug_assertions) {
+            todo!("Current target does not return anything useful.\nPlease try casting the event directly.");
+        }
+        // self.as_ui_event().current_target();
+    }
+
+    fn default_prevented(&self) -> bool {
+        self.as_ui_event().default_prevented()
+    }
+
+    fn event_phase(&self) -> u16 {
+        self.as_ui_event().event_phase()
+    }
+
+    fn is_trusted(&self) -> bool {
+        self.as_ui_event().is_trusted()
+    }
+
+    fn prevent_default(&self) {
+        self.as_ui_event().prevent_default()
+    }
+
+    fn stop_immediate_propagation(&self) {
+        self.as_ui_event().stop_immediate_propagation()
+    }
+
+    fn stop_propagation(&self) {
+        self.as_ui_event().stop_propagation()
+    }
+
+    fn target(&self) {
+        todo!()
+    }
+
+    fn time_stamp(&self) -> f64 {
+        self.as_ui_event().time_stamp()
+    }
+}
+
+macro_rules! implement_generic_event {
+    (
+        $($event:ident),*
+    ) => {
+        $(
+            impl WebsysGenericEvent for $event {
+                fn as_ui_event(&self) -> &UiEvent {
+                    self.0.dyn_ref().unwrap()
+                }
+            }
+        )*
+    };
+}
+
+implement_generic_event! {
+    WebsysClipboardEvent,
+    WebsysCompositionEvent,
+    WebsysKeyboardEvent,
+    WebsysGenericUiEvent,
+    WebsysFocusEvent,
+    WebsysFormEvent,
+    WebsysMouseEvent,
+    WebsysPointerEvent,
+    WebsysWheelEvent,
+    WebsysAnimationEvent,
+    WebsysTransitionEvent,
+    WebsysTouchEvent,
+    WebsysMediaEvent,
+    WebsysToggleEvent
+}
+
+// unfortunately, currently experimental, and web_sys needs to be configured to use it :>(
+pub struct WebsysClipboardEvent(pub Event);
+
+impl ClipboardEventInner for WebsysClipboardEvent {}
+
+pub struct WebsysCompositionEvent(pub web_sys::CompositionEvent);
+
+impl CompositionEventInner for WebsysCompositionEvent {
+    fn data(&self) -> String {
+        self.0.data().unwrap_or_else(|| String::new())
+    }
+}
+
+pub struct WebsysKeyboardEvent(pub web_sys::KeyboardEvent);
+impl KeyboardEventInner for WebsysKeyboardEvent {
+    fn alt_key(&self) -> bool {
+        self.0.alt_key()
+    }
+    fn char_code(&self) -> u32 {
+        self.0.char_code()
+    }
+    fn key(&self) -> String {
+        self.0.key()
+    }
+
+    fn key_code(&self) -> KeyCode {
+        KeyCode::from_raw_code(self.0.key_code() as u8)
+    }
+
+    fn ctrl_key(&self) -> bool {
+        self.0.ctrl_key()
+    }
+
+    fn get_modifier_state(&self, key_code: &str) -> bool {
+        self.0.get_modifier_state(key_code)
+    }
+
+    fn locale(&self) -> String {
+        if cfg!(debug_assertions) {
+            todo!("Locale is currently not supported. :(")
+        } else {
+            String::from("en-US")
+        }
+    }
+
+    fn location(&self) -> usize {
+        self.0.location() as usize
+    }
+
+    fn meta_key(&self) -> bool {
+        self.0.meta_key()
+    }
+
+    fn repeat(&self) -> bool {
+        self.0.repeat()
+    }
+
+    fn shift_key(&self) -> bool {
+        self.0.shift_key()
+    }
+
+    fn which(&self) -> usize {
+        self.0.which() as usize
+    }
+}
+
+pub struct WebsysGenericUiEvent(pub UiEvent);
+impl GenericEventInner for WebsysGenericUiEvent {
+    fn raw_event(&self) -> &dyn std::any::Any {
+        // self.0.raw_event()
+        todo!()
+    }
+
+    fn bubbles(&self) -> bool {
+        self.0.bubbles()
+    }
+
+    fn cancel_bubble(&self) {
+        self.0.cancel_bubble();
+    }
+
+    fn cancelable(&self) -> bool {
+        self.0.cancelable()
+    }
+
+    fn composed(&self) -> bool {
+        self.0.composed()
+    }
+
+    fn current_target(&self) {
+        // self.0.current_target()
+    }
+
+    fn default_prevented(&self) -> bool {
+        self.0.default_prevented()
+    }
+
+    fn event_phase(&self) -> u16 {
+        self.0.event_phase()
+    }
+
+    fn is_trusted(&self) -> bool {
+        self.0.is_trusted()
+    }
+
+    fn prevent_default(&self) {
+        self.0.prevent_default()
+    }
+
+    fn stop_immediate_propagation(&self) {
+        self.0.stop_immediate_propagation()
+    }
+
+    fn stop_propagation(&self) {
+        self.0.stop_propagation()
+    }
+
+    fn target(&self) {
+        // self.0.target()
+    }
+
+    fn time_stamp(&self) -> f64 {
+        self.0.time_stamp()
+    }
+}
+
+impl UIEventInner for WebsysGenericUiEvent {
+    fn detail(&self) -> i32 {
+        todo!()
+    }
+}
+
+impl SelectionEventInner for WebsysGenericUiEvent {}
+
+pub struct WebsysFocusEvent(pub web_sys::FocusEvent);
+impl FocusEventInner for WebsysFocusEvent {}
+
+pub struct WebsysFormEvent(pub web_sys::InputEvent);
+impl FormEventInner for WebsysFormEvent {
+    // technically a controlled component, so we need to manually grab out the target data
+    fn value(&self) -> String {
+        let this: web_sys::EventTarget = self.0.target().unwrap();
+        (&this)
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlInputElement| input.value())
+                .or_else(|| {
+                    this
+                        .dyn_ref()
+                        .map(|input: &web_sys::HtmlTextAreaElement| input.value())
+                })
+                .or_else(|| {
+                    this
+                        .dyn_ref::<web_sys::HtmlElement>()
+                        .unwrap()
+                        .text_content()
+                })
+                .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
+    }
+}
+
+pub struct WebsysMouseEvent(pub web_sys::MouseEvent);
+impl MouseEventInner for WebsysMouseEvent {
+    fn alt_key(&self) -> bool {
+        self.0.alt_key()
+    }
+    fn button(&self) -> i16 {
+        self.0.button()
+    }
+    fn buttons(&self) -> u16 {
+        self.0.buttons()
+    }
+    fn client_x(&self) -> i32 {
+        self.0.client_x()
+    }
+    fn client_y(&self) -> i32 {
+        self.0.client_y()
+    }
+    fn ctrl_key(&self) -> bool {
+        self.0.ctrl_key()
+    }
+    fn meta_key(&self) -> bool {
+        self.0.meta_key()
+    }
+    fn page_x(&self) -> i32 {
+        self.0.page_x()
+    }
+    fn page_y(&self) -> i32 {
+        self.0.page_y()
+    }
+    fn screen_x(&self) -> i32 {
+        self.0.screen_x()
+    }
+    fn screen_y(&self) -> i32 {
+        self.0.screen_y()
+    }
+    fn shift_key(&self) -> bool {
+        self.0.shift_key()
+    }
+
+    // yikes
+    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
+    fn get_modifier_state(&self, key_code: &str) -> bool {
+        self.0.get_modifier_state(key_code)
+    }
+}
+
+pub struct WebsysPointerEvent(pub web_sys::PointerEvent);
+impl PointerEventInner for WebsysPointerEvent {
+    fn alt_key(&self) -> bool {
+        self.0.alt_key()
+    }
+    fn button(&self) -> i16 {
+        self.0.button()
+    }
+    fn buttons(&self) -> u16 {
+        self.0.buttons()
+    }
+    fn client_x(&self) -> i32 {
+        self.0.client_x()
+    }
+    fn client_y(&self) -> i32 {
+        self.0.client_y()
+    }
+    fn ctrl_key(&self) -> bool {
+        self.0.ctrl_key()
+    }
+    fn meta_key(&self) -> bool {
+        self.0.meta_key()
+    }
+    fn page_x(&self) -> i32 {
+        self.0.page_x()
+    }
+    fn page_y(&self) -> i32 {
+        self.0.page_y()
+    }
+    fn screen_x(&self) -> i32 {
+        self.0.screen_x()
+    }
+    fn screen_y(&self) -> i32 {
+        self.0.screen_y()
+    }
+    fn shift_key(&self) -> bool {
+        self.0.shift_key()
+    }
+
+    // yikes
+    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
+    fn get_modifier_state(&self, key_code: &str) -> bool {
+        self.0.get_modifier_state(key_code)
+    }
+
+    fn pointer_id(&self) -> i32 {
+        self.0.pointer_id()
+    }
+
+    fn width(&self) -> i32 {
+        self.0.width()
+    }
+
+    fn height(&self) -> i32 {
+        self.0.height()
+    }
+
+    fn pressure(&self) -> f32 {
+        self.0.pressure()
+    }
+
+    fn tangential_pressure(&self) -> f32 {
+        self.0.tangential_pressure()
+    }
+
+    fn tilt_x(&self) -> i32 {
+        self.0.tilt_x()
+    }
+
+    fn tilt_y(&self) -> i32 {
+        self.0.tilt_y()
+    }
+
+    fn twist(&self) -> i32 {
+        self.0.twist()
+    }
+
+    fn pointer_type(&self) -> String {
+        self.0.pointer_type()
+    }
+
+    fn is_primary(&self) -> bool {
+        self.0.is_primary()
+    }
+}
+
+pub struct WebsysWheelEvent(pub web_sys::WheelEvent);
+impl WheelEventInner for WebsysWheelEvent {
+    fn delta_mode(&self) -> u32 {
+        self.0.delta_mode()
+    }
+
+    fn delta_x(&self) -> f64 {
+        self.0.delta_x()
+    }
+
+    fn delta_y(&self) -> f64 {
+        self.0.delta_y()
+    }
+
+    fn delta_z(&self) -> f64 {
+        self.0.delta_z()
+    }
+}
+pub struct WebsysAnimationEvent(pub web_sys::AnimationEvent);
+impl AnimationEventInner for WebsysAnimationEvent {
+    fn animation_name(&self) -> String {
+        self.0.animation_name()
+    }
+
+    fn pseudo_element(&self) -> String {
+        self.0.pseudo_element()
+    }
+
+    fn elapsed_time(&self) -> f32 {
+        self.0.elapsed_time()
+    }
+}
+
+pub struct WebsysTransitionEvent(pub web_sys::TransitionEvent);
+impl TransitionEventInner for WebsysTransitionEvent {
+    fn property_name(&self) -> String {
+        self.0.property_name()
+    }
+
+    fn pseudo_element(&self) -> String {
+        self.0.pseudo_element()
+    }
+
+    fn elapsed_time(&self) -> f32 {
+        self.0.elapsed_time()
+    }
+}
+
+pub struct WebsysTouchEvent(pub web_sys::TouchEvent);
+impl TouchEventInner for WebsysTouchEvent {
+    fn alt_key(&self) -> bool {
+        self.0.alt_key()
+    }
+
+    fn ctrl_key(&self) -> bool {
+        self.0.ctrl_key()
+    }
+
+    fn meta_key(&self) -> bool {
+        self.0.meta_key()
+    }
+
+    fn shift_key(&self) -> bool {
+        self.0.shift_key()
+    }
+
+    fn get_modifier_state(&self, key_code: &str) -> bool {
+        if cfg!(debug_assertions) {
+            todo!("get_modifier_state is not currently supported for touch events");
+        } else {
+            false
+        }
+    }
+}
+
+pub struct WebsysMediaEvent(pub web_sys::UiEvent);
+impl MediaEventInner for WebsysMediaEvent {}
+
+pub struct WebsysToggleEvent(pub web_sys::UiEvent);
+impl ToggleEventInner for WebsysToggleEvent {}

+ 81 - 70
packages/web/src/lib.rs

@@ -1,49 +1,73 @@
 //! Dioxus WebSys
 //! Dioxus WebSys
-//! --------------
-//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
-
-/*
-From Google's guide on rAF and rIC:
---------
-
-If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
-which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
- of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
- frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
- which is a potential performance bottleneck.
-
-Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
-and as such we could easily go past the deadline the browser provided.
-
-The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
-browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
-be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
-to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
-
-Essentially:
-------------
-- Do the VDOM work during the idlecallback
-- Do DOM work in the next requestAnimationFrame callback
-*/
+//!
+//! ## Overview
+//! ------------
+//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using WebSys. This web render for
+//! Dioxus is one of the more advanced renderers, supporting:
+//! - idle work
+//! - animations
+//! - jank-free rendering
+//! - noderefs
+//! - controlled components
+//! - re-hydration
+//! - and more.
+//!
+//! The actual implementation is farily thin, with the heavy lifting happening inside the Dioxus Core crate.
+//!
+//! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
+//! validation of websys-specific features and not the general use of Dioxus.
+//!
+//! ## RequestAnimationFrame and RequestIdleCallback
+//! ------------------------------------------------
+//! React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
+//! running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
+//! main thread.
+//!
+//! React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus,
+//! the diff phase is non-blocking, using "yield_now" to allow the browser to process other events. When the diff phase
+//! is  finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply.
+//!
+//! Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then
+//! setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame
+//!
+//!     From Google's guide on rAF and rIC:
+//!     -----------------------------------
+//!
+//!     If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
+//!     which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
+//!      of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
+//!      frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
+//!      which is a potential performance bottleneck.
+//!
+//!     Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
+//!     and as such we could easily go past the deadline the browser provided.
+//!
+//!     The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
+//!     browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
+//!     be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
+//!     to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
+//!
+//!     Essentially:
+//!     ------------
+//!     - Do the VDOM work during the idlecallback
+//!     - Do DOM work in the next requestAnimationFrame callback
 
 
 use std::rc::Rc;
 use std::rc::Rc;
 
 
 pub use crate::cfg::WebConfig;
 pub use crate::cfg::WebConfig;
 use crate::dom::load_document;
 use crate::dom::load_document;
-use dioxus::prelude::{Context, Properties, VNode};
+use cache::intern_cache;
+use dioxus::prelude::Properties;
 use dioxus::virtual_dom::VirtualDom;
 use dioxus::virtual_dom::VirtualDom;
 pub use dioxus_core as dioxus;
 pub use dioxus_core as dioxus;
-use dioxus_core::error::Result;
-use dioxus_core::{events::EventTrigger, prelude::FC};
-use futures_util::{pin_mut, Stream, StreamExt};
-use fxhash::FxHashMap;
-use js_sys::Iterator;
-use web_sys::{window, Document, Element, Event, Node, NodeList};
+use dioxus_core::prelude::FC;
 
 
 mod cache;
 mod cache;
 mod cfg;
 mod cfg;
 mod dom;
 mod dom;
+mod events;
 mod nodeslab;
 mod nodeslab;
+mod ric_raf;
 
 
 /// Launches the VirtualDOM from the specified component function.
 /// Launches the VirtualDOM from the specified component function.
 ///
 ///
@@ -73,16 +97,8 @@ where
     F: FnOnce(WebConfig) -> WebConfig,
     F: FnOnce(WebConfig) -> WebConfig,
 {
 {
     let config = config(WebConfig::default());
     let config = config(WebConfig::default());
-    let fut = run_with_props(root, root_props, config);
-
-    wasm_bindgen_futures::spawn_local(async {
-        match fut.await {
-            Ok(_) => log::error!("Your app completed running... somehow?"),
-            Err(e) => log::error!("Your app crashed! {}", e),
-        }
-    });
+    wasm_bindgen_futures::spawn_local(run_with_props(root, root_props, config));
 }
 }
-
 /// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
 /// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
 /// See DioxusErrors for more information on how these errors could occour.
 /// See DioxusErrors for more information on how these errors could occour.
 ///
 ///
@@ -96,43 +112,38 @@ where
 ///
 ///
 /// Run the app to completion, panicing if any error occurs while rendering.
 /// Run the app to completion, panicing if any error occurs while rendering.
 /// Pairs well with the wasm_bindgen async handler
 /// Pairs well with the wasm_bindgen async handler
-pub async fn run_with_props<T: Properties + 'static>(
-    root: FC<T>,
-    root_props: T,
-    cfg: WebConfig,
-) -> Result<()> {
+pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T, cfg: WebConfig) {
     let mut dom = VirtualDom::new_with_props(root, root_props);
     let mut dom = VirtualDom::new_with_props(root, root_props);
 
 
-    let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
+    intern_cache();
 
 
-    let tasks = dom.get_event_sender();
+    let hydrating = cfg.hydrate;
 
 
-    let mut real = RealDomWebsys {};
+    let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
+    let tasks = dom.get_event_sender();
+    let sender_callback = Rc::new(move |event| tasks.unbounded_send(event).unwrap());
 
 
-    // initialize the virtualdom first
-    if cfg.hydrate {
-        dom.rebuild_in_place()?;
-    }
+    let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
 
 
-    let mut websys_dom = dom::WebsysDom::new(
-        root_el,
-        cfg,
-        Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
-    );
+    let mut mutations = dom.rebuild();
 
 
-    dom.run_with_deadline(&mut websys_dom).await?;
+    // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
+    // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
+    if !hydrating {
+        websys_dom.process_edits(&mut mutations.edits);
+    }
 
 
-    Ok(())
-}
+    let work_loop = ric_raf::RafLoop::new();
+    loop {
+        // if virtualdom has nothing, wait for it to have something before requesting idle time
+        if !dom.has_work() {
+            dom.wait_for_any_work().await;
+        }
 
 
-struct HydrationNode {
-    id: usize,
-    node: Node,
-}
+        let deadline = work_loop.wait_for_idle_time().await;
 
 
-struct RealDomWebsys {}
-impl dioxus::RealDom for RealDomWebsys {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
+        let mut mutations = dom.run_with_deadline(deadline).await;
+        work_loop.wait_for_raf().await;
+        websys_dom.process_edits(&mut mutations[0].edits);
     }
     }
 }
 }

+ 58 - 0
packages/web/src/ric_raf.rs

@@ -0,0 +1,58 @@
+//! RequestAnimationFrame and RequestIdleCallback port and polyfill.
+
+use gloo_timers::future::TimeoutFuture;
+use js_sys::Function;
+use wasm_bindgen::JsCast;
+use wasm_bindgen::{prelude::Closure, JsValue};
+use web_sys::Window;
+
+pub struct RafLoop {
+    window: Window,
+    ric_receiver: async_channel::Receiver<()>,
+    raf_receiver: async_channel::Receiver<()>,
+    ric_closure: Closure<dyn Fn(JsValue)>,
+    raf_closure: Closure<dyn Fn(JsValue)>,
+}
+
+impl RafLoop {
+    pub fn new() -> Self {
+        let (raf_sender, raf_receiver) = async_channel::unbounded();
+
+        let raf_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |_v: JsValue| {
+            raf_sender.try_send(()).unwrap()
+        }));
+
+        let (ric_sender, ric_receiver) = async_channel::unbounded();
+
+        let ric_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |_v: JsValue| {
+            ric_sender.try_send(()).unwrap()
+        }));
+
+        // execute the polyfill for safari
+        Function::new_no_args(include_str!("./ricpolyfill.js"))
+            .call0(&JsValue::NULL)
+            .unwrap();
+
+        Self {
+            window: web_sys::window().unwrap(),
+            raf_receiver,
+            raf_closure,
+            ric_receiver,
+            ric_closure,
+        }
+    }
+    /// waits for some idle time and returns a timeout future that expires after the idle time has passed
+    pub async fn wait_for_idle_time(&self) -> TimeoutFuture {
+        let ric_fn = self.ric_closure.as_ref().dyn_ref::<Function>().unwrap();
+        let deadline: u32 = self.window.request_idle_callback(ric_fn).unwrap();
+        self.ric_receiver.recv().await.unwrap();
+        let deadline = TimeoutFuture::new(deadline);
+        deadline
+    }
+
+    pub async fn wait_for_raf(&self) {
+        let raf_fn = self.raf_closure.as_ref().dyn_ref::<Function>().unwrap();
+        let _id: i32 = self.window.request_animation_frame(raf_fn).unwrap();
+        self.raf_receiver.recv().await.unwrap();
+    }
+}

+ 28 - 0
packages/web/src/ricpolyfill.js

@@ -0,0 +1,28 @@
+const requestIdleCallback =
+  (typeof self !== 'undefined' &&
+    self.requestIdleCallback &&
+    self.requestIdleCallback.bind(window)) ||
+  function (cb) {
+    const start = Date.now();
+    return setTimeout(() => {
+      cb({
+        didTimeout: false,
+        timeRemaining: function () {
+          return Math.max(0, 50 - (Date.now() - start));
+        },
+      });
+    }, 1);
+  };
+
+const cancelIdleCallback =
+  (typeof self !== 'undefined' &&
+    self.cancelIdleCallback &&
+    self.cancelIdleCallback.bind(window)) ||
+  function (id) {
+    return clearTimeout(id);
+  };
+
+if (typeof window !== 'undefined') {
+  window.requestIdleCallback = requestIdleCallback;
+  window.cancelIdleCallback = cancelIdleCallback;
+}