Bläddra i källkod

Merge pull request #16 from jkelleyrtp/jk/html-macro-bump

feat: webview basics
Jonathan Kelley 3 år sedan
förälder
incheckning
f66630c935
96 ändrade filer med 5359 tillägg och 3303 borttagningar
  1. 17 4
      Cargo.toml
  2. 2 1
      README.md
  3. 1 1
      docs/advanced-guides/custom-renderer.md
  4. 1 1
      examples/async.rs
  5. 14 0
      examples/basic.rs
  6. 86 0
      examples/borrowed.rs
  7. 2 1
      examples/calculator.rs
  8. 71 0
      examples/coroutine.rs
  9. 115 0
      examples/file_explorer.rs
  10. 33 0
      examples/hydration.rs
  11. 10 2
      examples/model.rs
  12. 1 0
      examples/reducer.rs
  13. 2 1
      examples/reference/suspense.rs
  14. 2 2
      examples/reference/task.rs
  15. 1 1
      examples/reference/tostring.rs
  16. 5 3
      examples/ssr.rs
  17. 6 4
      examples/tailwind.rs
  18. 2 1
      examples/testbed.rs
  19. 3 7
      examples/todomvc.rs
  20. 27 7
      examples/webview.rs
  21. 30 0
      examples/webview_web.rs
  22. 7 0
      packages/core-macro/README.md
  23. 1 0
      packages/core-macro/examples/prop_test.rs
  24. 5 7
      packages/core-macro/src/htm.rs
  25. 0 27
      packages/core-macro/src/html/mod.rs
  26. 12 58
      packages/core-macro/src/lib.rs
  27. 6 8
      packages/core-macro/src/props/mod.rs
  28. 50 9
      packages/core-macro/src/rsx/ambiguous.rs
  29. 83 0
      packages/core-macro/src/rsx/body.rs
  30. 180 70
      packages/core-macro/src/rsx/component.rs
  31. 162 38
      packages/core-macro/src/rsx/element.rs
  32. 27 6
      packages/core-macro/src/rsx/fragment.rs
  33. 5 89
      packages/core-macro/src/rsx/mod.rs
  34. 27 5
      packages/core-macro/src/rsx/node.rs
  35. 0 179
      packages/core-macro/src/util.rs
  36. 0 0
      packages/core-macro/styles/calc.rs
  37. 0 0
      packages/core-macro/styles/codegen.rs
  38. 0 0
      packages/core-macro/styles/color.rs
  39. 0 0
      packages/core-macro/styles/mod.rs
  40. 0 0
      packages/core-macro/styles/string/lexer.rs
  41. 0 0
      packages/core-macro/styles/string/mod.rs
  42. 0 0
      packages/core-macro/styles/syn_parse.rs
  43. 1 1
      packages/core/.vscode/settings.json
  44. 12 4
      packages/core/Cargo.toml
  45. 6 4
      packages/core/README.md
  46. 2 4
      packages/core/examples/alternative.rs
  47. 2 2
      packages/core/examples/async.rs
  48. 1 0
      packages/core/examples/borrowed.rs
  49. 2 1
      packages/core/examples/fragment_from_iter.rs
  50. 148 39
      packages/core/src/arena.rs
  51. 75 47
      packages/core/src/bumpframe.rs
  52. 0 91
      packages/core/src/childiter.rs
  53. 44 2
      packages/core/src/component.rs
  54. 104 352
      packages/core/src/context.rs
  55. 1066 790
      packages/core/src/diff.rs
  56. 35 124
      packages/core/src/editor.rs
  57. 160 62
      packages/core/src/events.rs
  58. 14 5
      packages/core/src/heuristics.rs
  59. 258 0
      packages/core/src/hooks.rs
  60. 7 5
      packages/core/src/lib.rs
  61. 233 117
      packages/core/src/nodes.rs
  62. 136 88
      packages/core/src/scope.rs
  63. 0 137
      packages/core/src/tasks.rs
  64. 25 98
      packages/core/src/util.rs
  65. 312 199
      packages/core/src/virtual_dom.rs
  66. 19 0
      packages/core/tests/channels.rs
  67. 340 0
      packages/core/tests/diffing.rs
  68. 32 0
      packages/core/tests/eventsystem.rs
  69. 99 0
      packages/core/tests/vdom_rebuild.rs
  70. 3 2
      packages/desktop/Cargo.toml
  71. 2 11
      packages/desktop/examples/test.rs
  72. 45 0
      packages/desktop/src/cfg.rs
  73. 15 21
      packages/desktop/src/dom.rs
  74. 85 0
      packages/desktop/src/events.rs
  75. 190 114
      packages/desktop/src/index.html
  76. 130 121
      packages/desktop/src/lib.rs
  77. 3 3
      packages/hooks/src/usestate.rs
  78. 30 0
      packages/html/src/attrval.s
  79. 1 30
      packages/html/src/lib.rs
  80. 1 1
      packages/mobile/Cargo.toml
  81. 2 7
      packages/mobile/src/dom.rs
  82. 3 2
      packages/mobile/src/lib.rs
  83. 1 1
      packages/ssr/Cargo.toml
  84. 6 0
      packages/ssr/README.md
  85. 10 0
      packages/ssr/examples/hydration.rs
  86. 1 1
      packages/ssr/examples/tide.rs
  87. 5 2
      packages/ssr/examples/tofile.rs
  88. 113 51
      packages/ssr/src/lib.rs
  89. 5 4
      packages/web/Cargo.toml
  90. 2 1
      packages/web/examples/async_web.rs
  91. 45 0
      packages/web/examples/basic.rs
  92. 68 0
      packages/web/examples/btns.rs
  93. 30 0
      packages/web/src/cfg.rs
  94. 308 162
      packages/web/src/dom.rs
  95. 89 65
      packages/web/src/lib.rs
  96. 37 0
      packages/web/src/nodeslab.rs

+ 17 - 4
Cargo.toml

@@ -37,18 +37,31 @@ mobile = ["dioxus-mobile"]
 
 
 [dev-dependencies]
-futures = "0.3.15"
+futures-util = "0.3.16"
 log = "0.4.14"
 num-format = "0.4.0"
 separator = "0.4.1"
 serde = { version = "1.0.126", features = ["derive"] }
-surf = "2.2.0"
+im-rc = "15.0.0"
+fxhash = "0.2.1"
+anyhow = "1.0.42"
+
+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
+argh = "0.1.5"
 env_logger = "*"
 async-std = { version = "1.9.0", features = ["attributes"] }
-im-rc = "15.0.0"
 rand = { version = "0.8.4", features = ["small_rng"] }
-fxhash = "0.2.1"
+surf = {version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
+
+[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 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"}
+
+
+[dependencies.getrandom]
+version = "0.2"
+features = ["js"]
+
 
 [workspace]
 members = [

+ 2 - 1
README.md

@@ -84,6 +84,7 @@ If you know React, then you already know Dioxus.
 - Starting a new app takes zero templates or special tools - get a new app running in just seconds.
 - Desktop apps running natively (no Electron!) in less than 10 lines of code.
 - The most ergonomic and powerful state management of any Rust UI toolkit.
+- Multithreaded asynchronous coroutine scheduler for powerful async code.
 - And more! Read the full release post here.
 
 ## Get Started with...
@@ -165,7 +166,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 | 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       |
+| 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                         |

+ 1 - 1
docs/advanced-guides/custom-renderer.md

@@ -25,7 +25,7 @@ The current `RealDom` trait lives in `dioxus_core/diff`. A version of it is prov
 ```rust
 pub trait RealDom<'a> {
     fn handle_edit(&mut self, edit: DomEdit);
-    fn request_available_node(&mut self) -> RealDomNode;
+    fn request_available_node(&mut self) -> ElementId;
     fn raw_node_as_any(&self) -> &mut dyn Any;
 }
 ```

+ 1 - 1
examples/async.rs

@@ -12,7 +12,7 @@ pub static App: FC<()> = |cx| {
     let mut direction = use_state(cx, || 1);
 
     let (async_count, dir) = (count.for_async(), *direction);
-    let (task, _result) = cx.use_task(move || async move {
+    let (task, _result) = use_task(cx, move || async move {
         loop {
             gloo_timers::future::TimeoutFuture::new(250).await;
             *async_count.get_mut() += dir;

+ 14 - 0
examples/basic.rs

@@ -0,0 +1,14 @@
+use dioxus::prelude::*;
+
+fn main() {
+    let g = dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory| {
+        use dioxus_elements::{GlobalAttributes, SvgAttributes};
+        __cx.element(
+            dioxus_elements::button,
+            [dioxus::events::on::onclick(__cx, move |_| {})],
+            [],
+            [],
+            None,
+        )
+    });
+}

+ 86 - 0
examples/borrowed.rs

@@ -0,0 +1,86 @@
+#![allow(non_snake_case)]
+//! Example: Extremely nested borrowing
+//! -----------------------------------
+//!
+//! Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible
+//! to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
+//!
+//! How does it work?
+//!
+//! Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done
+//! "bottom up" from the lowest child all the way to the initiating parent. As it traverses each listener and prop, the
+//! drop implementation is manually called, freeing any memory and ensuring that memory is not leaked.
+//!
+//! We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references
+//! might be broken since we take an &mut T and and &T to the data. Instead, we work bottom up, making sure to remove any
+//! potential references to the data before finally giving out an &mut T. This prevents us from mutably aliasing the data,
+//! and is proven to be safe with MIRI.
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+fn App<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
+    let text: &'a mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {});
+
+    let first = text.get_mut(0).unwrap();
+
+    cx.render(rsx! {
+        div {
+            Child1 {
+                text: first
+            }
+        }
+    })
+}
+
+#[derive(Props)]
+struct C1Props<'a> {
+    text: &'a mut String,
+}
+
+impl<'a> Drop for C1Props<'a> {
+    fn drop(&mut self) {}
+}
+
+fn Child1<'a>(cx: Context<'a, C1Props>) -> DomTree<'a> {
+    let (left, right) = cx.text.split_once("=").unwrap();
+
+    cx.render(rsx! {
+        div {
+            Child2 { text: left  }
+            Child2 { text: right  }
+        }
+    })
+}
+
+#[derive(Props)]
+struct C2Props<'a> {
+    text: &'a str,
+}
+impl<'a> Drop for C2Props<'a> {
+    fn drop(&mut self) {
+        todo!()
+    }
+}
+
+fn Child2<'a>(cx: Context<'a, C2Props>) -> DomTree<'a> {
+    cx.render(rsx! {
+        Child3 {
+            text: cx.text
+        }
+    })
+}
+
+#[derive(Props)]
+struct C3Props<'a> {
+    text: &'a str,
+}
+
+fn Child3<'a>(cx: Context<'a, C3Props>) -> DomTree<'a> {
+    cx.render(rsx! {
+        div { "{cx.text}"}
+    })
+}

+ 2 - 1
examples/calculator.rs

@@ -5,6 +5,7 @@
 // use dioxus::prelude::*;
 
 fn main() {
+    env_logger::init();
     dioxus::desktop::launch(App, |cfg| cfg);
 }
 
@@ -23,7 +24,7 @@ const App: FC<()> = |cx| {
     let operator = use_state(cx, || None as Option<Operator>);
     let display_value = use_state(cx, || "".to_string());
 
-    let clear_display = display_value.eq("0");
+    let clear_display = display_value == "0";
     let clear_text = if clear_display { "C" } else { "AC" };
 
     let input_digit = move |num: u8| display_value.get_mut().push_str(num.to_string().as_str());

+ 71 - 0
examples/coroutine.rs

@@ -0,0 +1,71 @@
+//! Example: Coroutines!
+//! --------------------
+//!
+//! Coroutines are an awesome way to write concurrent code. Dioxus heavily leverages coroutines to make sense of complex
+//! ongoing asynchronous tasks. The async scheduler of Dioxus supports both single-threaded and multi-threaded coroutines,
+//! so you can drop in code to run across multiple threads without blocking the main thread.
+//!
+//! Dioxus cannot simply abstract away the threading model for the web, unfortunately. If you want to use "web threads"
+//! you either need to limit support for Chrome, or you need to use a Web Workers and message passing. This is easy enough
+//! to do in your own code, and doesn't require 1st-party support from Dioxus itself.
+//!
+//! UseState and friends work fine with coroutines, but coroutines might be easier to use with the Dirac global state
+//! management API. This lets you easily drive global state from a coroutine without having to subscribe to the state.
+//!
+//! For now, this example shows how to use coroutines used with use_state.
+//!
+//!
+//! ## What is a Couroutine?
+//!
+//! A coroutine is a function that can be paused and resumed. It can be paused internally through "await" or externally
+//! using the `TaskHandle` API. Within a coroutine, you may execute asynchonous code, that modifies values captured when
+//! the coroutine was initiated. `use_state` always returns the same setter, so you don't need to worry about
+
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+use dioxus::prelude::*;
+
+static App: FC<()> = |cx| {
+    let p1 = use_state(cx, || 0);
+    let p2 = use_state(cx, || 0);
+
+    let (mut p1_async, mut p2_async) = (p1.for_async(), p2.for_async());
+    let (p1_handle, _) = use_task(cx, || async move {
+        loop {
+            *p1_async.get_mut() += 1;
+            async_std::task::sleep(std::time::Duration::from_millis(75)).await;
+        }
+    });
+    let (p2_handle, _) = use_task(cx, || async move {
+        loop {
+            *p2_async.get_mut() += 1;
+            async_std::task::sleep(std::time::Duration::from_millis(100)).await;
+        }
+    });
+
+    cx.render(rsx! {
+        div { style: { width: "400px", height: "400px", position: "relative", background: "yellow" }
+            button { "reset", onclick: move |_| {} }
+            Horsey { pos: *p1, "horsey 1" }
+            Horsey { pos: *p2, "horsey 2" }
+        }
+    })
+};
+
+#[derive(PartialEq, Props)]
+struct HorseyProps {
+    pos: i32,
+}
+
+static Horsey: FC<HorseyProps> = |cx| {
+    cx.render(rsx! {
+    div {
+        button { "pause" }
+        div {
+            {cx.children()}
+        }
+      }
+    })
+};

+ 115 - 0
examples/file_explorer.rs

@@ -0,0 +1,115 @@
+//! Example: File Explorer
+//! -------------------------
+//!
+//! This is a fun little desktop application that lets you explore the file system.
+//!
+//! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
+
+use dioxus::desktop::wry::application::dpi::LogicalSize;
+use dioxus::prelude::*;
+use std::fs::{self, DirEntry};
+
+fn main() {
+    env_logger::init();
+    dioxus::desktop::launch(App, |c| {
+        c.with_window(|w| {
+            w.with_resizable(false)
+                .with_inner_size(LogicalSize::new(800.0, 400.0))
+        })
+    })
+    .unwrap();
+}
+
+static App: FC<()> = |cx| {
+    let files = use_state(cx, || Files::new());
+
+    let file_list = files.path_names.iter().enumerate().map(|(dir_id, path)| {
+        rsx! (
+            li { a {"{path}", onclick: move |_| files.get_mut().enter_dir(dir_id), href: "#"} }
+        )
+    });
+
+    let err_disp = files.err.as_ref().map(|err| {
+        rsx! {
+            div {
+                code {"{err}"}
+                button {"x", onclick: move |_| files.get_mut().clear_err() }
+            }
+        }
+    });
+
+    let cur = files.current();
+    cx.render(rsx! {
+        div {
+            h1 {"Files: "}
+            h3 {"Cur dir: {cur}"}
+            button { "go up", onclick: move |_| files.get_mut().go_up() }
+            ol { {file_list} }
+            {err_disp}
+        }
+    })
+};
+
+// right now, this gets cloned every time. It might be a bit better to use im_rc's collections instead
+#[derive(Clone)]
+struct Files {
+    path_stack: Vec<String>,
+    path_names: Vec<String>,
+    err: Option<String>,
+}
+
+impl Files {
+    fn new() -> Self {
+        let mut files = Self {
+            path_stack: vec!["./".to_string()],
+            path_names: vec![],
+            err: None,
+        };
+
+        files.reload_path_list();
+
+        files
+    }
+
+    fn reload_path_list(&mut self) {
+        let cur_path = self.path_stack.last().unwrap();
+        let paths = match fs::read_dir(cur_path) {
+            Ok(e) => e,
+            Err(err) => {
+                let err = format!("An error occured: {:?}", err);
+                self.err = Some(err);
+                self.path_stack.pop();
+                return;
+            }
+        };
+
+        // clear the current state
+        self.clear_err();
+        self.path_names.clear();
+
+        for path in paths {
+            self.path_names
+                .push(path.unwrap().path().display().to_string());
+        }
+    }
+
+    fn go_up(&mut self) {
+        if self.path_stack.len() > 1 {
+            self.path_stack.pop();
+        }
+        self.reload_path_list();
+    }
+
+    fn enter_dir(&mut self, dir_id: usize) {
+        let path = &self.path_names[dir_id];
+        self.path_stack.push(path.clone());
+        self.reload_path_list();
+    }
+
+    fn current(&self) -> &str {
+        self.path_stack.last().unwrap()
+    }
+    fn clear_err(&mut self) {
+        self.err = None;
+    }
+}

+ 33 - 0
examples/hydration.rs

@@ -0,0 +1,33 @@
+//! Example: realworld usage of hydration
+//! ------------------------------------
+//!
+//! This example shows how to pre-render a page using dioxus SSR and then how to rehydrate it on the client side.
+//!
+//! To accomplish hydration on the web, you'll want to set up a slightly more sophisticated build & bundle strategy. In
+//! the official docs, we have a guide for using DioxusStudio as a build tool with pre-rendering and hydration.
+//!
+//! In this example, we pre-render the page to HTML and then pass it into the desktop configuration. This serves as a
+//! proof-of-concept for the hydration feature, but you'll probably only want to use hydration for the web.
+
+use dioxus::prelude::*;
+use dioxus::ssr;
+
+fn main() {
+    let mut vdom = VirtualDom::launch_in_place(App);
+    let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
+
+    dioxus::desktop::launch(App, |c| c.with_prerendered(content)).unwrap();
+}
+
+static App: FC<()> = |cx| {
+    let mut val = use_state(cx, || 0);
+    cx.render(rsx! {
+        div {
+            h1 {"hello world. Count: {val}"}
+            button {
+                "click to increment"
+                onclick: move |_| val += 1
+            }
+        }
+    })
+};

+ 10 - 2
examples/model.rs

@@ -15,13 +15,21 @@
 //! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
 //! RefMuts at the same time.
 
+use dioxus::desktop::wry::application::dpi::LogicalSize;
 use dioxus::events::on::*;
 use dioxus::prelude::*;
 
 const STYLE: &str = include_str!("./assets/calculator.css");
 fn main() {
-    dioxus::desktop::launch(App, |cfg| cfg.with_title("Calculator Demo"))
-        .expect("failed to launch dioxus app");
+    env_logger::init();
+    dioxus::desktop::launch(App, |cfg| {
+        cfg.with_window(|w| {
+            w.with_title("Calculator Demo")
+                .with_resizable(false)
+                .with_inner_size(LogicalSize::new(320.0, 530.0))
+        })
+    })
+    .expect("failed to launch dioxus app");
 }
 
 static App: FC<()> = |cx| {

+ 1 - 0
examples/reducer.rs

@@ -7,6 +7,7 @@
 
 use dioxus::prelude::*;
 fn main() {
+    env_logger::init();
     dioxus::desktop::launch(App, |c| c);
 }
 

+ 2 - 1
examples/reference/suspense.rs

@@ -15,7 +15,8 @@ struct DogApi {
 const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
 
 pub static Example: FC<()> = |cx| {
-    let doggo = cx.use_suspense(
+    let doggo = use_suspense(
+        cx,
         || surf::get(ENDPOINT).recv_json::<DogApi>(),
         |cx, res| match res {
             Ok(res) => rsx!(in cx, img { src: "{res.message}" }),

+ 2 - 2
examples/reference/task.rs

@@ -30,8 +30,8 @@ pub static Example: FC<()> = |cx| {
 
     // Tasks are 'static, so we need to copy relevant items in
     let (async_count, dir) = (count.for_async(), *direction);
-    let (task, result) = cx.use_task(move || async move {
-        // Count infinitely!
+    
+    let (task, result) = use_task(cx, move || async move {
         loop {
             gloo_timers::future::TimeoutFuture::new(250).await;
             *async_count.get_mut() += dir;

+ 1 - 1
examples/reference/tostring.rs

@@ -7,7 +7,7 @@ pub static Example: FC<()> = |cx| {
         // This is an easy/low hanging fruit to improve upon
         let mut dom = VirtualDom::new(SomeApp);
         dom.rebuild_in_place().unwrap();
-        ssr::render_vdom(&dom)
+        ssr::render_vdom(&dom, |c| c)
     });
 
     cx.render(rsx! {

+ 5 - 3
examples/ssr.rs

@@ -1,13 +1,15 @@
+#![allow(non_upper_case_globals)]
+
 use dioxus::prelude::*;
 use dioxus::ssr;
 
 fn main() {
     let mut vdom = VirtualDom::new(App);
-    vdom.rebuild_in_place();
-    println!("{}", ssr::render_vdom(&vdom));
+    vdom.rebuild_in_place().expect("Rebuilding failed");
+    println!("{}", ssr::render_vdom(&vdom, |c| c));
 }
 
-const App: FC<()> = |cx| {
+static App: FC<()> = |cx| {
     cx.render(rsx!(
         div {
             h1 { "Title" }

+ 6 - 4
examples/tailwind.rs

@@ -3,10 +3,12 @@ use dioxus::prelude::*;
 fn main() {
     use dioxus::desktop::wry::application::platform::macos::*;
     dioxus::desktop::launch(App, |c| {
-        c.with_fullsize_content_view(true)
-            .with_titlebar_buttons_hidden(false)
-            .with_titlebar_transparent(true)
-            .with_movable_by_window_background(true)
+        c.with_window(|w| {
+            w.with_fullsize_content_view(true)
+                .with_titlebar_buttons_hidden(false)
+                .with_titlebar_transparent(true)
+                .with_movable_by_window_background(true)
+        })
     });
 }
 

+ 2 - 1
examples/testbed.rs

@@ -3,7 +3,7 @@ use std::cell::Cell;
 use dioxus::prelude::*;
 use dioxus_core::{
     nodes::{VElement, VText},
-    RealDomNode,
+    ElementId,
 };
 
 fn main() {
@@ -33,6 +33,7 @@ const Example: FC<()> = |cx| {
         }
         "h4"
         div { "h5" }
+        button { }
         Child {}
     })
 };

+ 3 - 7
examples/todomvc.rs

@@ -1,14 +1,10 @@
+#![allow(non_upper_case_globals, non_snake_case)]
 use dioxus::prelude::*;
 use im_rc::HashMap;
 use std::rc::Rc;
 
-fn main() {
-    #[cfg(feature = "desktop")]
-    // #[cfg(not(target_arch = "wasm32"))]
-    dioxus::desktop::launch(App, |c| c);
-
-    #[cfg(feature = "desktop")]
-    dioxus::web::launch(App, |c| c);
+fn main() -> anyhow::Result<()> {
+    dioxus::desktop::launch(App, |c| c)
 }
 
 #[derive(PartialEq)]

+ 27 - 7
examples/webview.rs

@@ -8,21 +8,41 @@
 //! into the native VDom instance.
 //!
 //! Currently, NodeRefs won't work properly, but all other event functionality will.
+#![allow(non_upper_case_globals, non_snake_case)]
 
-use dioxus::prelude::*;
+use dioxus::{events::on::MouseEvent, prelude::*};
 
-fn main() {
-    dioxus::desktop::launch(App, |c| c);
+fn main() -> anyhow::Result<()> {
+    env_logger::init();
+    dioxus::desktop::launch(App, |c| c)
 }
 
 static App: FC<()> = |cx| {
-    let mut count = use_state(cx, || 0);
+    let state = use_state(cx, || String::from("hello"));
+    let clear_text = state == "hello";
 
+    dbg!("rednering parent");
     cx.render(rsx! {
         div {
-            h1 { "Hifive counter: {count}" }
-            button { onclick: move |_| count += 1, "Up high!" }
-            button { onclick: move |_| count -= 1, "Down low!" }
+            h1 {"{state}"}
+            CalculatorKey { name: "key-clear", onclick: move |_| state.get_mut().push_str("hello"), "{clear_text}" }
+            CalculatorKey { name: "key-sign", onclick: move |_| { state.get_mut().pop(); }, "±"}
         }
     })
 };
+
+#[derive(Props)]
+struct CalculatorKeyProps<'a> {
+    name: &'static str,
+    onclick: &'a dyn Fn(MouseEvent),
+}
+
+fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
+    cx.render(rsx! {
+        button {
+            class: "calculator-key {cx.name}"
+            onclick: {cx.onclick}
+            {cx.children()}
+        }
+    })
+}

+ 30 - 0
examples/webview_web.rs

@@ -0,0 +1,30 @@
+#![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() {
+    dioxus::web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx| {
+    let mut count = use_state(cx, || 0);
+
+    cx.render(rsx! {
+        div {
+            h1 { "Hifive counter: {count}" }
+            {cx.children()}
+            button { onclick: move |_| count += 1, "Up high!" }
+            button { onclick: move |_| count -= 1, "Down low!" }
+        }
+    })
+};

+ 7 - 0
packages/core-macro/README.md

@@ -0,0 +1,7 @@
+# core-macro
+
+This crate implements these macros:
+- `format_args_f`: for f-string interpolation inside of text blocks
+- `Props`: derive macro for typed-builder with props configurations
+- 
+- 

+ 1 - 0
packages/core-macro/examples/prop_test.rs

@@ -4,6 +4,7 @@ pub mod dioxus {
     pub mod prelude {
         pub trait Properties {
             type Builder;
+            const IS_STATIC: bool;
             fn builder() -> Self::Builder;
             unsafe fn memoize(&self, other: &Self) -> bool;
         }

+ 5 - 7
packages/core-macro/src/htm.rs

@@ -13,8 +13,6 @@
 //!
 //!
 
-use crate::util::is_valid_svg_tag;
-
 use {
     proc_macro::TokenStream,
     proc_macro2::{Span, TokenStream as TokenStream2},
@@ -163,11 +161,11 @@ impl ToTokens for ToToksCtx<&Element> {
             self.recurse(attr).to_tokens(tokens);
         }
 
-        if is_valid_svg_tag(&name.to_string()) {
-            tokens.append_all(quote! {
-                .namespace(Some("http://www.w3.org/2000/svg"))
-            });
-        }
+        // if is_valid_svg_tag(&name.to_string()) {
+        //     tokens.append_all(quote! {
+        //         .namespace(Some("http://www.w3.org/2000/svg"))
+        //     });
+        // }
 
         match &self.inner.children {
             MaybeExpr::Expr(expr) => tokens.append_all(quote! {

+ 0 - 27
packages/core-macro/src/html/mod.rs

@@ -1,27 +0,0 @@
-//! Html body
-//! -------
-//!
-//!
-//! Since both HTML and RSX serialize to the same node structure, the HTML parser uses the same types as RSX,
-//! but has a different Parse implementation.
-
-use crate::rsx::*;
-use quote::ToTokens;
-use syn::parse::Parse;
-
-pub struct HtmlBody(RsxBody);
-
-impl Parse for HtmlBody {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        todo!()
-    }
-}
-impl ToTokens for HtmlBody {
-    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
-        self.0.to_tokens(tokens)
-    }
-}
-
-pub struct HtmlNode(BodyNode);
-pub struct HtmlAmbigiousElement(AmbiguousElement);
-pub struct HtmlComponent(Component);

+ 12 - 58
packages/core-macro/src/lib.rs

@@ -1,68 +1,12 @@
 use proc_macro::TokenStream;
 use quote::ToTokens;
+use rsx::{AS_HTML, AS_RSX};
 use syn::parse_macro_input;
 
-pub(crate) mod fc;
 pub(crate) mod htm;
-pub(crate) mod html;
 pub(crate) mod ifmt;
 pub(crate) mod props;
 pub(crate) mod rsx;
-pub(crate) mod rsxtemplate;
-pub(crate) mod util;
-
-/// The html! macro makes it easy for developers to write jsx-style markup in their components.
-/// We aim to keep functional parity with html templates.
-#[proc_macro]
-pub fn html(s: TokenStream) -> TokenStream {
-    match syn::parse::<htm::HtmlRender>(s) {
-        Err(e) => e.to_compile_error().into(),
-        Ok(s) => s.to_token_stream().into(),
-    }
-}
-
-/// The html! macro makes it easy for developers to write jsx-style markup in their components.
-/// We aim to keep functional parity with html templates.
-#[proc_macro]
-pub fn rsx_template(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsxtemplate::RsxTemplate>(s) {
-        Err(e) => e.to_compile_error().into(),
-        Ok(s) => s.to_token_stream().into(),
-    }
-}
-
-/// The html! macro makes it easy for developers to write jsx-style markup in their components.
-/// We aim to keep functional parity with html templates.
-#[proc_macro]
-pub fn html_template(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsxtemplate::RsxTemplate>(s) {
-        Err(e) => e.to_compile_error().into(),
-        Ok(s) => s.to_token_stream().into(),
-    }
-}
-
-// #[proc_macro_attribute]
-// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
-
-/// Label a function or static closure as a functional component.
-/// This macro reduces the need to create a separate properties struct.
-///
-/// Using this macro is fun and simple
-///
-/// ```ignore
-///
-/// #[fc]
-/// fn Example(cx: Context, name: &str) -> DomTree {
-///     cx.render(rsx! { h1 {"hello {name}"} })
-/// }
-/// ```
-#[proc_macro_attribute]
-pub fn fc(_attr: TokenStream, item: TokenStream) -> TokenStream {
-    match syn::parse::<fc::FunctionComponent>(item) {
-        Err(e) => e.to_compile_error().into(),
-        Ok(s) => s.to_token_stream().into(),
-    }
-}
 
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
@@ -238,7 +182,17 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 /// ```
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsx::RsxBody>(s) {
+    match syn::parse::<rsx::RsxBody<AS_RSX>>(s) {
+        Err(e) => e.to_compile_error().into(),
+        Ok(s) => s.to_token_stream().into(),
+    }
+}
+
+/// The html! macro makes it easy for developers to write jsx-style markup in their components.
+/// We aim to keep functional parity with html templates.
+#[proc_macro]
+pub fn html(s: TokenStream) -> TokenStream {
+    match syn::parse::<rsx::RsxBody<AS_HTML>>(s) {
         Err(e) => e.to_compile_error().into(),
         Ok(s) => s.to_token_stream().into(),
     }

+ 6 - 8
packages/core-macro/src/props/mod.rs

@@ -502,7 +502,6 @@ mod field_info {
 
 mod struct_info {
     use proc_macro2::TokenStream;
-    use quote::__private::ext::RepToTokensExt;
     use quote::quote;
     use syn::parse::Error;
 
@@ -663,6 +662,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                 false => quote! { self == other },
             };
 
+            let is_static = match are_there_generics {
+                true => quote! { false  },
+                false => quote! { true },
+            };
+
             Ok(quote! {
                 impl #impl_generics #name #ty_generics #where_clause {
                     #[doc = #builder_method_doc]
@@ -694,6 +698,7 @@ Finally, call `.build()` to create the instance of `{name}`.
 
                 impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
                     type Builder = #builder_name #generics_with_empty;
+                    const IS_STATIC: bool = #is_static;
                     fn builder() -> Self::Builder {
                         #name::builder()
                     }
@@ -1095,13 +1100,6 @@ Finally, call `.build()` to create the instance of `{name}`.
                 }
             )
         }
-
-        pub fn build_props_impl(&self) -> TokenStream {
-            // SomeProps: #name
-            // #builder_name
-            // #generics_with_empty
-            quote! {}
-        }
     }
 
     #[derive(Debug, Default)]

+ 50 - 9
packages/core-macro/src/rsx/ambiguous.rs

@@ -15,17 +15,17 @@ use syn::{
     Error, Ident, LitStr, Result, Token,
 };
 
-pub enum AmbiguousElement {
-    Element(Element),
-    Component(Component),
+pub enum AmbiguousElement<const AS: HTML_OR_RSX> {
+    Element(Element<AS>),
+    Component(Component<AS>),
 }
 
-impl Parse for AmbiguousElement {
+impl Parse for AmbiguousElement<AS_RSX> {
     fn parse(input: ParseStream) -> Result<Self> {
         // Try to parse as an absolute path and immediately defer to the componetn
         if input.peek(Token![::]) {
             return input
-                .parse::<Component>()
+                .parse::<Component<AS_RSX>>()
                 .map(|c| AmbiguousElement::Component(c));
         }
 
@@ -34,7 +34,7 @@ impl Parse for AmbiguousElement {
         if let Ok(pat) = input.fork().parse::<syn::Path>() {
             if pat.segments.len() > 1 {
                 return input
-                    .parse::<Component>()
+                    .parse::<Component<AS_RSX>>()
                     .map(|c| AmbiguousElement::Component(c));
             }
         }
@@ -45,11 +45,11 @@ impl Parse for AmbiguousElement {
             let first_char = name_str.chars().next().unwrap();
             if first_char.is_ascii_uppercase() {
                 input
-                    .parse::<Component>()
+                    .parse::<Component<AS_RSX>>()
                     .map(|c| AmbiguousElement::Component(c))
             } else {
                 input
-                    .parse::<Element>()
+                    .parse::<Element<AS_RSX>>()
                     .map(|c| AmbiguousElement::Element(c))
             }
         } else {
@@ -61,7 +61,48 @@ impl Parse for AmbiguousElement {
     }
 }
 
-impl ToTokens for AmbiguousElement {
+impl Parse for AmbiguousElement<AS_HTML> {
+    fn parse(input: ParseStream) -> Result<Self> {
+        // Try to parse as an absolute path and immediately defer to the componetn
+        if input.peek(Token![::]) {
+            return input
+                .parse::<Component<AS_HTML>>()
+                .map(|c| AmbiguousElement::Component(c));
+        }
+
+        // If not an absolute path, then parse the ident and check if it's a valid tag
+
+        if let Ok(pat) = input.fork().parse::<syn::Path>() {
+            if pat.segments.len() > 1 {
+                return input
+                    .parse::<Component<AS_HTML>>()
+                    .map(|c| AmbiguousElement::Component(c));
+            }
+        }
+
+        if let Ok(name) = input.fork().parse::<Ident>() {
+            let name_str = name.to_string();
+
+            let first_char = name_str.chars().next().unwrap();
+            if first_char.is_ascii_uppercase() {
+                input
+                    .parse::<Component<AS_HTML>>()
+                    .map(|c| AmbiguousElement::Component(c))
+            } else {
+                input
+                    .parse::<Element<AS_HTML>>()
+                    .map(|c| AmbiguousElement::Element(c))
+            }
+        } else {
+            if input.peek(LitStr) {
+                panic!("it's actually a litstr");
+            }
+            Err(Error::new(input.span(), "Not a valid Html tag"))
+        }
+    }
+}
+
+impl<const AS: HTML_OR_RSX> ToTokens for AmbiguousElement<AS> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
             AmbiguousElement::Element(el) => el.to_tokens(tokens),

+ 83 - 0
packages/core-macro/src/rsx/body.rs

@@ -0,0 +1,83 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+    parse::{Parse, ParseStream},
+    Error, Ident, Result, Token,
+};
+
+use super::*;
+
+pub struct RsxBody<const AS: HTML_OR_RSX> {
+    custom_context: Option<Ident>,
+    roots: Vec<BodyNode<AS>>,
+}
+
+/// The custom rusty variant of parsing rsx!
+impl Parse for RsxBody<AS_RSX> {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let custom_context = try_parse_custom_context(input)?;
+        let (_, roots, _) =
+            BodyParseConfig::<AS_RSX>::new_as_body().parse_component_body(&input)?;
+        Ok(Self {
+            custom_context,
+            roots,
+        })
+    }
+}
+
+/// The HTML variant of parsing rsx!
+impl Parse for RsxBody<AS_HTML> {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let custom_context = try_parse_custom_context(input)?;
+        let (_, roots, _) =
+            BodyParseConfig::<AS_HTML>::new_as_body().parse_component_body(&input)?;
+        Ok(Self {
+            custom_context,
+            roots,
+        })
+    }
+}
+
+fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
+    let res = if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
+        let _ = input.parse::<Token![in]>()?;
+        let name = input.parse::<Ident>()?;
+        input.parse::<Token![,]>()?;
+        Some(name)
+    } else {
+        None
+    };
+    Ok(res)
+}
+
+/// Serialize the same way, regardless of flavor
+impl<const A: HTML_OR_RSX> ToTokens for RsxBody<A> {
+    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
+        let inner = if self.roots.len() == 1 {
+            let inner = &self.roots[0];
+            quote! {#inner}
+        } else {
+            let childs = &self.roots;
+            quote! { __cx.fragment_from_iter([ #(#childs),* ]) }
+        };
+
+        match &self.custom_context {
+            // The `in cx` pattern allows directly rendering
+            Some(ident) => out_tokens.append_all(quote! {
+                #ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
+                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
+
+                    #inner
+                }))
+            }),
+            // Otherwise we just build the LazyNode wrapper
+            None => out_tokens.append_all(quote! {
+                dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
+                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
+
+                    #inner
+                 })
+            }),
+        };
+    }
+}

+ 180 - 70
packages/core-macro/src/rsx/component.rs

@@ -19,18 +19,18 @@ use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     ext::IdentExt,
     parse::{Parse, ParseBuffer, ParseStream},
-    token, Expr, ExprClosure, Ident, Result, Token,
+    token, Error, Expr, ExprClosure, Ident, Result, Token,
 };
 
-pub struct Component {
+pub struct Component<const AS: HTML_OR_RSX> {
     // accept any path-like argument
     name: syn::Path,
-    body: Vec<ComponentField>,
-    children: Vec<BodyNode>,
+    body: Vec<ComponentField<AS>>,
+    children: Vec<BodyNode<AS>>,
     manual_props: Option<Expr>,
 }
 
-impl Parse for Component {
+impl Parse for Component<AS_RSX> {
     fn parse(stream: ParseStream) -> Result<Self> {
         // let name = s.parse::<syn::ExprPath>()?;
         // todo: look into somehow getting the crate/super/etc
@@ -41,21 +41,37 @@ impl Parse for Component {
         let content: ParseBuffer;
         syn::braced!(content in stream);
 
-        let mut body: Vec<ComponentField> = Vec::new();
-        let mut children: Vec<BodyNode> = Vec::new();
-        let mut manual_props = None;
+        let cfg: BodyParseConfig<AS_RSX> = BodyParseConfig {
+            allow_children: true,
+            allow_fields: true,
+            allow_manual_props: true,
+        };
+
+        let (body, children, manual_props) = cfg.parse_component_body(&content)?;
+
+        Ok(Self {
+            name,
+            body,
+            children,
+            manual_props,
+        })
+    }
+}
+impl Parse for Component<AS_HTML> {
+    fn parse(stream: ParseStream) -> Result<Self> {
+        let name = syn::Path::parse_mod_style(stream)?;
+
+        // parse the guts
+        let content: ParseBuffer;
+        syn::braced!(content in stream);
+
+        let cfg: BodyParseConfig<AS_HTML> = BodyParseConfig {
+            allow_children: true,
+            allow_fields: true,
+            allow_manual_props: true,
+        };
 
-        parse_component_body(
-            &content,
-            &BodyParseConfig {
-                allow_children: true,
-                allow_fields: true,
-                allow_manual_props: true,
-            },
-            &mut body,
-            &mut children,
-            &mut manual_props,
-        )?;
+        let (body, children, manual_props) = cfg.parse_component_body(&content)?;
 
         Ok(Self {
             name,
@@ -66,64 +82,139 @@ impl Parse for Component {
     }
 }
 
-pub struct BodyParseConfig {
+pub struct BodyParseConfig<const AS: HTML_OR_RSX> {
     pub allow_fields: bool,
     pub allow_children: bool,
     pub allow_manual_props: bool,
 }
 
-// todo: unify this body parsing for both elements and components
-// both are style rather ad-hoc, though components are currently more configured
-pub fn parse_component_body(
-    content: &ParseBuffer,
-    cfg: &BodyParseConfig,
-    body: &mut Vec<ComponentField>,
-    children: &mut Vec<BodyNode>,
-    manual_props: &mut Option<Expr>,
-) -> Result<()> {
-    'parsing: loop {
-        // [1] Break if empty
-        if content.is_empty() {
-            break 'parsing;
+impl<const AS: HTML_OR_RSX> BodyParseConfig<AS> {
+    /// The configuration to parse the root
+    pub fn new_as_body() -> Self {
+        Self {
+            allow_children: true,
+            allow_fields: false,
+            allow_manual_props: false,
         }
+    }
+}
+
+impl BodyParseConfig<AS_RSX> {
+    // todo: unify this body parsing for both elements and components
+    // both are style rather ad-hoc, though components are currently more configured
+    pub fn parse_component_body(
+        &self,
+        content: &ParseBuffer,
+    ) -> Result<(
+        Vec<ComponentField<AS_RSX>>,
+        Vec<BodyNode<AS_RSX>>,
+        Option<Expr>,
+    )> {
+        let mut body = Vec::new();
+        let mut children = Vec::new();
+        let mut manual_props = None;
 
-        if content.peek(Token![..]) {
-            if !cfg.allow_manual_props {
-                return Err(Error::new(
-                    content.span(),
-                    "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
-                ));
+        'parsing: loop {
+            // [1] Break if empty
+            if content.is_empty() {
+                break 'parsing;
             }
-            content.parse::<Token![..]>()?;
-            *manual_props = Some(content.parse::<Expr>()?);
-        } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-            if !cfg.allow_fields {
-                return Err(Error::new(
-                    content.span(),
-                    "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
-                ));
+
+            if content.peek(Token![..]) {
+                if !self.allow_manual_props {
+                    return Err(Error::new(
+                        content.span(),
+                        "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
+                    ));
+                }
+                content.parse::<Token![..]>()?;
+                manual_props = Some(content.parse::<Expr>()?);
+            } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+                if !self.allow_fields {
+                    return Err(Error::new(
+                        content.span(),
+                        "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
+                    ));
+                }
+                body.push(content.parse::<ComponentField<AS_RSX>>()?);
+            } else {
+                if !self.allow_children {
+                    return Err(Error::new(
+                        content.span(),
+                        "This item is not allowed to accept children.",
+                    ));
+                }
+                children.push(content.parse::<BodyNode<AS_RSX>>()?);
             }
-            body.push(content.parse::<ComponentField>()?);
-        } else {
-            if !cfg.allow_children {
-                return Err(Error::new(
-                    content.span(),
-                    "This item is not allowed to accept children.",
-                ));
+
+            // consume comma if it exists
+            // we don't actually care if there *are* commas between attrs
+            if content.peek(Token![,]) {
+                let _ = content.parse::<Token![,]>();
             }
-            children.push(content.parse::<BodyNode>()?);
         }
+        Ok((body, children, manual_props))
+    }
+}
+impl BodyParseConfig<AS_HTML> {
+    // todo: unify this body parsing for both elements and components
+    // both are style rather ad-hoc, though components are currently more configured
+    pub fn parse_component_body(
+        &self,
+        content: &ParseBuffer,
+    ) -> Result<(
+        Vec<ComponentField<AS_HTML>>,
+        Vec<BodyNode<AS_HTML>>,
+        Option<Expr>,
+    )> {
+        let mut body = Vec::new();
+        let mut children = Vec::new();
+        let mut manual_props = None;
+
+        'parsing: loop {
+            // [1] Break if empty
+            if content.is_empty() {
+                break 'parsing;
+            }
+
+            if content.peek(Token![..]) {
+                if !self.allow_manual_props {
+                    return Err(Error::new(
+                        content.span(),
+                        "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
+                    ));
+                }
+                content.parse::<Token![..]>()?;
+                manual_props = Some(content.parse::<Expr>()?);
+            } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+                if !self.allow_fields {
+                    return Err(Error::new(
+                        content.span(),
+                        "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
+                    ));
+                }
+                body.push(content.parse::<ComponentField<AS_HTML>>()?);
+            } else {
+                if !self.allow_children {
+                    return Err(Error::new(
+                        content.span(),
+                        "This item is not allowed to accept children.",
+                    ));
+                }
+                children.push(content.parse::<BodyNode<AS_HTML>>()?);
+            }
 
-        // consume comma if it exists
-        // we don't actually care if there *are* commas between attrs
-        if content.peek(Token![,]) {
-            let _ = content.parse::<Token![,]>();
+            // consume comma if it exists
+            // we don't actually care if there *are* commas between attrs
+            if content.peek(Token![,]) {
+                let _ = content.parse::<Token![,]>();
+            }
         }
+        Ok((body, children, manual_props))
     }
-    Ok(())
 }
 
-impl ToTokens for Component {
+impl<const AS: HTML_OR_RSX> ToTokens for Component<AS> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
 
@@ -170,12 +261,10 @@ impl ToTokens for Component {
 
         let key_token = match has_key {
             Some(field) => {
-                let inners = field.content.to_token_stream();
-                quote! {
-                    Some(#inners)
-                }
+                let inners = &field.content;
+                quote! { Some(format_args_f!(#inners)) }
             }
-            None => quote! {None},
+            None => quote! { None },
         };
 
         let childs = &self.children;
@@ -188,14 +277,14 @@ impl ToTokens for Component {
                 #name,
                 #builder,
                 #key_token,
-                __cx.bump().alloc(#children)
+                #children
             )
         })
     }
 }
 
 // the struct's fields info
-pub struct ComponentField {
+pub struct ComponentField<const AS: HTML_OR_RSX> {
     name: Ident,
     content: ContentField,
 }
@@ -222,7 +311,28 @@ impl ToTokens for ContentField {
     }
 }
 
-impl Parse for ComponentField {
+impl Parse for ComponentField<AS_RSX> {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let name = Ident::parse_any(input)?;
+        input.parse::<Token![:]>()?;
+
+        let name_str = name.to_string();
+        let content = if name_str.starts_with("on") {
+            if input.peek(token::Brace) {
+                let content;
+                syn::braced!(content in input);
+                ContentField::OnHandlerRaw(content.parse()?)
+            } else {
+                ContentField::OnHandler(input.parse()?)
+            }
+        } else {
+            ContentField::ManExpr(input.parse::<Expr>()?)
+        };
+
+        Ok(Self { name, content })
+    }
+}
+impl Parse for ComponentField<AS_HTML> {
     fn parse(input: ParseStream) -> Result<Self> {
         let name = Ident::parse_any(input)?;
         input.parse::<Token![:]>()?;
@@ -244,7 +354,7 @@ impl Parse for ComponentField {
     }
 }
 
-impl ToTokens for ComponentField {
+impl<const AS: HTML_OR_RSX> ToTokens for ComponentField<AS> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let ComponentField { name, content, .. } = self;
         tokens.append_all(quote! {

+ 162 - 38
packages/core-macro/src/rsx/element.rs

@@ -11,35 +11,16 @@ use syn::{
 // =======================================
 // Parse the VNode::Element type
 // =======================================
-pub struct Element {
+pub struct Element<const AS: HTML_OR_RSX> {
     name: Ident,
-    key: Option<AttrType>,
-    attributes: Vec<ElementAttr>,
-    listeners: Vec<ElementAttr>,
-    children: Vec<BodyNode>,
+    key: Option<LitStr>,
+    attributes: Vec<ElementAttr<AS>>,
+    listeners: Vec<ElementAttr<AS>>,
+    children: Vec<BodyNode<AS>>,
     is_static: bool,
 }
 
-impl ToTokens for Element {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name;
-        let attr = &self.attributes;
-        let childs = &self.children;
-        let listeners = &self.listeners;
-
-        tokens.append_all(quote! {
-            __cx.element(
-                dioxus_elements::#name,
-                __cx.bump().alloc([ #(#listeners),* ]),
-                __cx.bump().alloc([ #(#attr),* ]),
-                __cx.bump().alloc([ #(#childs),* ]),
-                None,
-            )
-        });
-    }
-}
-
-impl Parse for Element {
+impl Parse for Element<AS_RSX> {
     fn parse(stream: ParseStream) -> Result<Self> {
         let name = Ident::parse(stream)?;
 
@@ -47,9 +28,9 @@ impl Parse for Element {
         let content: ParseBuffer;
         syn::braced!(content in stream);
 
-        let mut attributes: Vec<ElementAttr> = vec![];
-        let mut listeners: Vec<ElementAttr> = vec![];
-        let mut children: Vec<BodyNode> = vec![];
+        let mut attributes: Vec<ElementAttr<AS_RSX>> = vec![];
+        let mut listeners: Vec<ElementAttr<AS_RSX>> = vec![];
+        let mut children: Vec<BodyNode<AS_RSX>> = vec![];
         let mut key = None;
 
         'parsing: loop {
@@ -59,7 +40,7 @@ impl Parse for Element {
             }
 
             if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                parse_element_body(
+                parse_rsx_element_field(
                     &content,
                     &mut attributes,
                     &mut listeners,
@@ -67,7 +48,7 @@ impl Parse for Element {
                     name.clone(),
                 )?;
             } else {
-                children.push(content.parse::<BodyNode>()?);
+                children.push(content.parse::<BodyNode<AS_RSX>>()?);
             }
 
             // consume comma if it exists
@@ -88,10 +69,153 @@ impl Parse for Element {
     }
 }
 
+impl Parse for Element<AS_HTML> {
+    fn parse(stream: ParseStream) -> Result<Self> {
+        let l_tok = stream.parse::<Token![<]>()?;
+        let el_name = Ident::parse(stream)?;
+
+        // parse the guts
+        // let content: ParseBuffer;
+        // syn::braced!(content in stream);
+
+        let mut attributes: Vec<ElementAttr<AS_HTML>> = vec![];
+        let mut listeners: Vec<ElementAttr<AS_HTML>> = vec![];
+        let mut children: Vec<BodyNode<AS_HTML>> = vec![];
+        let mut key = None;
+
+        // loop {
+        //     if stream.peek(Token![>]) {
+        //         break;
+        //     } else {
+        //     }
+        // }
+        while !stream.peek(Token![>]) {
+            // self-closing
+            if stream.peek(Token![/]) {
+                stream.parse::<Token![/]>()?;
+                stream.parse::<Token![>]>()?;
+
+                return Ok(Self {
+                    name: el_name,
+                    key: None,
+                    attributes,
+                    is_static: false,
+                    listeners,
+                    children,
+                });
+            }
+
+            let name = Ident::parse_any(stream)?;
+            let name_str = name.to_string();
+            stream.parse::<Token![=]>()?;
+            if name_str.starts_with("on") {
+                let inner;
+                syn::braced!(inner in stream);
+                let toks = inner.parse::<Expr>()?;
+                let ty = AttrType::EventTokens(toks);
+                listeners.push(ElementAttr {
+                    element_name: el_name.clone(),
+                    name,
+                    value: ty,
+                    namespace: None,
+                })
+            } else {
+                match name_str.as_str() {
+                    "style" => {}
+                    "key" => {}
+                    "classes" | "namespace" | "ref" | _ => {
+                        let ty = if stream.peek(LitStr) {
+                            let rawtext = stream.parse::<LitStr>().unwrap();
+                            AttrType::BumpText(rawtext)
+                        } else {
+                            // like JSX, we expect raw expressions
+                            let inner;
+                            syn::braced!(inner in stream);
+                            let toks = inner.parse::<Expr>()?;
+                            AttrType::FieldTokens(toks)
+                        };
+                        attributes.push(ElementAttr {
+                            element_name: el_name.clone(),
+                            name,
+                            value: ty,
+                            namespace: None,
+                        })
+                    }
+                }
+            };
+        }
+        stream.parse::<Token![>]>()?;
+
+        // closing element
+        stream.parse::<Token![<]>()?;
+        stream.parse::<Token![/]>()?;
+        let close = Ident::parse_any(stream)?;
+        if close.to_string() != el_name.to_string() {
+            return Err(Error::new_spanned(
+                close,
+                "closing element does not match opening",
+            ));
+        }
+        stream.parse::<Token![>]>()?;
+        // 'parsing: loop {
+        //     // if stream.peek(Token![>]) {}
+
+        //     // // [1] Break if empty
+        //     // if content.is_empty() {
+        //     //     break 'parsing;
+        //     // }
+
+        //     if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+        //         parse_element_body(
+        //             &content,
+        //             &mut attributes,
+        //             &mut listeners,
+        //             &mut key,
+        //             name.clone(),
+        //         )?;
+        //     } else {
+        //         children.push(stream.parse::<BodyNode<AS_HTML>>()?);
+        //     }
+        // }
+
+        Ok(Self {
+            key,
+            name: el_name,
+            attributes,
+            children,
+            listeners,
+            is_static: false,
+        })
+    }
+}
+
+impl<const AS: HTML_OR_RSX> ToTokens for Element<AS> {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let name = &self.name;
+        let attr = &self.attributes;
+        let childs = &self.children;
+        let listeners = &self.listeners;
+        let key = match &self.key {
+            Some(ty) => quote! { Some(format_args_f!(#ty)) },
+            None => quote! { None },
+        };
+
+        tokens.append_all(quote! {
+            __cx.element(
+                dioxus_elements::#name,
+                [ #(#listeners),* ],
+                [ #(#attr),* ],
+                [ #(#childs),* ],
+                #key,
+            )
+        });
+    }
+}
+
 /// =======================================
 /// Parse a VElement's Attributes
 /// =======================================
-struct ElementAttr {
+struct ElementAttr<const AS: HTML_OR_RSX> {
     element_name: Ident,
     name: Ident,
     value: AttrType,
@@ -108,14 +232,14 @@ enum AttrType {
 // We parse attributes and dump them into the attribute vec
 // This is because some tags might be namespaced (IE style)
 // These dedicated tags produce multiple name-spaced attributes
-fn parse_element_body(
+fn parse_rsx_element_field(
     stream: ParseStream,
-    attrs: &mut Vec<ElementAttr>,
-    listeners: &mut Vec<ElementAttr>,
-    key: &mut Option<AttrType>,
+    attrs: &mut Vec<ElementAttr<AS_RSX>>,
+    listeners: &mut Vec<ElementAttr<AS_RSX>>,
+    key: &mut Option<LitStr>,
     element_name: Ident,
 ) -> Result<()> {
-    let mut name = Ident::parse_any(stream)?;
+    let name = Ident::parse_any(stream)?;
     let name_str = name.to_string();
     stream.parse::<Token![:]>()?;
 
@@ -178,7 +302,7 @@ fn parse_element_body(
             return Ok(());
         }
         "key" => {
-            *key = Some(AttrType::BumpText(stream.parse::<LitStr>()?));
+            *key = Some(stream.parse::<LitStr>()?);
             return Ok(());
         }
         "classes" => {
@@ -218,7 +342,7 @@ fn parse_element_body(
     Ok(())
 }
 
-impl ToTokens for ElementAttr {
+impl<const AS: HTML_OR_RSX> ToTokens for ElementAttr<AS> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let el_name = &self.element_name;
         let name_str = self.name.to_string();

+ 27 - 6
packages/core-macro/src/rsx/fragment.rs

@@ -10,7 +10,7 @@
 
 use syn::parse::ParseBuffer;
 
-use super::AmbiguousElement;
+use super::{AmbiguousElement, AS_HTML, AS_RSX, HTML_OR_RSX};
 
 use {
     proc_macro::TokenStream,
@@ -23,10 +23,31 @@ use {
     },
 };
 
-pub struct Fragment {
-    children: Vec<AmbiguousElement>,
+pub struct Fragment<const AS: HTML_OR_RSX> {
+    children: Vec<AmbiguousElement<AS>>,
 }
-impl Parse for Fragment {
+
+impl Parse for Fragment<AS_RSX> {
+    fn parse(input: ParseStream) -> Result<Self> {
+        input.parse::<Ident>()?;
+
+        let children = Vec::new();
+
+        // parse the guts
+        let content: ParseBuffer;
+        syn::braced!(content in input);
+        while !content.is_empty() {
+            content.parse::<AmbiguousElement<AS_RSX>>()?;
+
+            if content.peek(Token![,]) {
+                let _ = content.parse::<Token![,]>();
+            }
+        }
+        Ok(Self { children })
+    }
+}
+
+impl Parse for Fragment<AS_HTML> {
     fn parse(input: ParseStream) -> Result<Self> {
         input.parse::<Ident>()?;
 
@@ -36,7 +57,7 @@ impl Parse for Fragment {
         let content: ParseBuffer;
         syn::braced!(content in input);
         while !content.is_empty() {
-            content.parse::<AmbiguousElement>()?;
+            content.parse::<AmbiguousElement<AS_HTML>>()?;
 
             if content.peek(Token![,]) {
                 let _ = content.parse::<Token![,]>();
@@ -46,7 +67,7 @@ impl Parse for Fragment {
     }
 }
 
-impl ToTokens for Fragment {
+impl<const AS: HTML_OR_RSX> ToTokens for Fragment<AS> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let childs = &self.children;
         let children = quote! {

+ 5 - 89
packages/core-macro/src/rsx/mod.rs

@@ -12,6 +12,7 @@
 //! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
 
 mod ambiguous;
+mod body;
 mod component;
 mod element;
 mod fragment;
@@ -19,97 +20,12 @@ mod node;
 
 // Re-export the namespaces into each other
 pub use ambiguous::*;
+pub use body::*;
 pub use component::*;
 pub use element::*;
 pub use fragment::*;
 pub use node::*;
 
-use crate::util::is_valid_tag;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{
-    parse::{Parse, ParseStream},
-    Error, Ident, LitStr, Result, Token,
-};
-
-pub struct RsxBody {
-    custom_context: Option<Ident>,
-    roots: Vec<BodyNode>,
-}
-
-impl Parse for RsxBody {
-    fn parse(input: ParseStream) -> Result<Self> {
-        // if input.peek(LitStr) {
-        //     return input.parse::<LitStr>()?.parse::<RsxRender>();
-        // }
-
-        // try to parse the first ident and comma
-        let custom_context =
-            if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
-                let _ = input.parse::<Token![in]>()?;
-                let name = input.parse::<Ident>()?;
-                if is_valid_tag(&name.to_string()) {
-                    return Err(Error::new(
-                        input.span(),
-                        "Custom context cannot be an html element name",
-                    ));
-                } else {
-                    input.parse::<Token![,]>().unwrap();
-                    Some(name)
-                }
-            } else {
-                None
-            };
-
-        let mut body = Vec::new();
-        let mut children = Vec::new();
-        let mut manual_props = None;
-        parse_component_body(
-            input,
-            &BodyParseConfig {
-                allow_children: true,
-                allow_fields: false,
-                allow_manual_props: false,
-            },
-            &mut body,
-            &mut children,
-            &mut manual_props,
-        )?;
-
-        Ok(Self {
-            roots: children,
-            custom_context,
-        })
-    }
-}
-
-impl ToTokens for RsxBody {
-    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let inner = if self.roots.len() == 1 {
-            let inner = &self.roots[0];
-            quote! {#inner}
-        } else {
-            let childs = &self.roots;
-            quote! { __cx.fragment_from_iter([ #(#childs),* ]) }
-        };
-
-        match &self.custom_context {
-            // The `in cx` pattern allows directly rendering
-            Some(ident) => out_tokens.append_all(quote! {
-                #ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
-                    use dioxus_elements::GlobalAttributes;
-
-                    #inner
-                }))
-            }),
-            // Otherwise we just build the LazyNode wrapper
-            None => out_tokens.append_all(quote! {
-                dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
-                    use dioxus_elements::GlobalAttributes;
-
-                    #inner
-                 })
-            }),
-        };
-    }
-}
+pub type HTML_OR_RSX = bool;
+pub const AS_HTML: bool = true;
+pub const AS_RSX: bool = false;

+ 27 - 5
packages/core-macro/src/rsx/node.rs

@@ -10,13 +10,13 @@ use syn::{
 // ==============================================
 // Parse any div {} as a VElement
 // ==============================================
-pub enum BodyNode {
-    Element(AmbiguousElement),
+pub enum BodyNode<const AS: HTML_OR_RSX> {
+    Element(AmbiguousElement<AS>),
     Text(TextNode),
     RawExpr(Expr),
 }
 
-impl Parse for BodyNode {
+impl Parse for BodyNode<AS_RSX> {
     fn parse(stream: ParseStream) -> Result<Self> {
         // Supposedly this approach is discouraged due to inability to return proper errors
         // TODO: Rework this to provide more informative errors
@@ -31,11 +31,33 @@ impl Parse for BodyNode {
             return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
         }
 
-        Ok(BodyNode::Element(stream.parse::<AmbiguousElement>()?))
+        Ok(BodyNode::Element(
+            stream.parse::<AmbiguousElement<AS_RSX>>()?,
+        ))
+    }
+}
+impl Parse for BodyNode<AS_HTML> {
+    fn parse(stream: ParseStream) -> Result<Self> {
+        // Supposedly this approach is discouraged due to inability to return proper errors
+        // TODO: Rework this to provide more informative errors
+
+        if stream.peek(token::Brace) {
+            let content;
+            syn::braced!(content in stream);
+            return Ok(BodyNode::RawExpr(content.parse::<Expr>()?));
+        }
+
+        if stream.peek(LitStr) {
+            return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
+        }
+
+        Ok(BodyNode::Element(
+            stream.parse::<AmbiguousElement<AS_HTML>>()?,
+        ))
     }
 }
 
-impl ToTokens for BodyNode {
+impl<const AS: HTML_OR_RSX> ToTokens for BodyNode<AS> {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match &self {
             BodyNode::Element(el) => el.to_tokens(tokens),

+ 0 - 179
packages/core-macro/src/util.rs

@@ -1,179 +0,0 @@
-// use lazy_static::lazy_static;
-use once_cell::sync::Lazy;
-use std::collections::hash_set::HashSet;
-use syn::{parse::ParseBuffer, Expr};
-
-pub fn try_parse_bracketed(stream: &ParseBuffer) -> syn::Result<Expr> {
-    let content;
-    syn::braced!(content in stream);
-    content.parse()
-}
-
-/// rsx! and html! macros support the html namespace as well as svg namespace
-static HTML_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
-    [
-        "a",
-        "abbr",
-        "address",
-        "area",
-        "article",
-        "aside",
-        "audio",
-        "b",
-        "base",
-        "bdi",
-        "bdo",
-        "big",
-        "blockquote",
-        "body",
-        "br",
-        "button",
-        "canvas",
-        "caption",
-        "cite",
-        "code",
-        "col",
-        "colgroup",
-        "command",
-        "data",
-        "datalist",
-        "dd",
-        "del",
-        "details",
-        "dfn",
-        "dialog",
-        "div",
-        "dl",
-        "dt",
-        "em",
-        "embed",
-        "fieldset",
-        "figcaption",
-        "figure",
-        "footer",
-        "form",
-        "h1",
-        "h2",
-        "h3",
-        "h4",
-        "h5",
-        "h6",
-        "head",
-        "header",
-        "hr",
-        "html",
-        "i",
-        "iframe",
-        "img",
-        "input",
-        "ins",
-        "kbd",
-        "keygen",
-        "label",
-        "legend",
-        "li",
-        "link",
-        "main",
-        "map",
-        "mark",
-        "menu",
-        "menuitem",
-        "meta",
-        "meter",
-        "nav",
-        "noscript",
-        "object",
-        "ol",
-        "optgroup",
-        "option",
-        "output",
-        "p",
-        "param",
-        "picture",
-        "pre",
-        "progress",
-        "q",
-        "rp",
-        "rt",
-        "ruby",
-        "s",
-        "samp",
-        "script",
-        "section",
-        "select",
-        "small",
-        "source",
-        "span",
-        "strong",
-        "style",
-        "sub",
-        "summary",
-        "sup",
-        "table",
-        "tbody",
-        "td",
-        "textarea",
-        "tfoot",
-        "th",
-        "thead",
-        "time",
-        "title",
-        "tr",
-        "track",
-        "u",
-        "ul",
-        "var",
-        "video",
-        "wbr",
-    ]
-    .iter()
-    .cloned()
-    .collect()
-});
-
-static SVG_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
-    [
-        // SVTG
-        "svg", "path", "g", "text",
-    ]
-    .iter()
-    .cloned()
-    .collect()
-});
-
-// these tags are reserved by dioxus for any reason
-// They might not all be used
-static RESERVED_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
-    [
-        // a fragment
-        "fragment",
-    ]
-    .iter()
-    .cloned()
-    .collect()
-});
-
-/// Whether or not this tag is valid
-///
-/// ```
-/// use html_validation::is_valid_tag;
-///
-/// assert_eq!(is_valid_tag("br"), true);
-///
-/// assert_eq!(is_valid_tag("random"), false);
-/// ```
-pub fn is_valid_tag(tag: &str) -> bool {
-    is_valid_html_tag(tag) || is_valid_svg_tag(tag) || is_valid_reserved_tag(tag)
-}
-
-pub fn is_valid_html_tag(tag: &str) -> bool {
-    HTML_TAGS.contains(tag)
-}
-
-pub fn is_valid_svg_tag(tag: &str) -> bool {
-    SVG_TAGS.contains(tag)
-}
-
-pub fn is_valid_reserved_tag(tag: &str) -> bool {
-    RESERVED_TAGS.contains(tag)
-}

+ 0 - 0
packages/core-macro/src/styles/calc.rs → packages/core-macro/styles/calc.rs


+ 0 - 0
packages/core-macro/src/styles/codegen.rs → packages/core-macro/styles/codegen.rs


+ 0 - 0
packages/core-macro/src/styles/color.rs → packages/core-macro/styles/color.rs


+ 0 - 0
packages/core-macro/src/styles/mod.rs → packages/core-macro/styles/mod.rs


+ 0 - 0
packages/core-macro/src/styles/string/lexer.rs → packages/core-macro/styles/string/lexer.rs


+ 0 - 0
packages/core-macro/src/styles/string/mod.rs → packages/core-macro/styles/string/mod.rs


+ 0 - 0
packages/core-macro/src/styles/syn_parse.rs → packages/core-macro/styles/syn_parse.rs


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

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

+ 12 - 4
packages/core/Cargo.toml

@@ -31,15 +31,23 @@ log = "0.4"
 # # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
 
-# Backs scopes and unique keys
-slotmap = "1.0.3"
-
 appendlist = "1.4.0"
 
 futures-util = "0.3.15"
+
 smallvec = "1.6.1"
 
+slab = "0.4.3"
+
+futures-channel = "0.3.16"
+
+
+[dev-dependencies]
+anyhow = "1.0.42"
+async-std = { version = "1.9.0", features = ["attributes"] }
+dioxus-html = { path = "../html" }
+
 
 [features]
 default = ["serialize"]
-serialize = ["slotmap/serde", "serde"]
+serialize = ["serde"]

+ 6 - 4
packages/core/README.md

@@ -2,18 +2,18 @@
 
 This is the core crate for the Dioxus Virtual DOM. This README will focus on the technical design and layout of this Virtual DOM implementation. If you want to read more about using Dioxus, then check out the Dioxus crate, documentation, and website.
 
-We reserve the "dioxus" name and aggregate all the various renderers under it. If you want just a single dioxus renderer, then chose from "dioxus-web", "dioxus-desktop", etc.
+To build new apps with Dioxus or to extend the ecosystem with new hooks or components, use the `Dioxus` crate with the appropriate feature flags.
 
 ## Internals
 
 Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:
 
 - React: hooks, concurrency, suspense
-- Dodrio: bump allocation, double buffering, and source code for NodeBuilder
+- Dodrio: bump allocation, double buffering, and some diffing architecture
 - Percy: html! macro architecture, platform-agnostic edits
-- Yew: passion and inspiration ❤️
-- InfernoJS: approach to fragments and node diffing
+- InfernoJS: approach to keyed diffing
 - Preact: approach for normalization and ref
+- Yew: passion and inspiration ❤️
 
 Dioxus-core leverages some really cool techniques and hits a very high level of parity with mature frameworks. Some unique features include:
 
@@ -21,6 +21,8 @@ Dioxus-core leverages some really cool techniques and hits a very high level of
 - suspended nodes (task/fiber endpoints) for asynchronous vnodes
 - custom memory allocator for vnodes and all text content
 - support for fragments w/ lazy normalization
+- slab allocator for scopes
+- mirrored-slab approach for remote vdoms
 
 There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, it is likely that zero allocations will need to be performed once the app has been mounted. Only when new components are added to the dom will allocations occur - and only en mass. The space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
 

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

@@ -1,9 +1,7 @@
-fn main() {}
-
-use dioxus::*;
-use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 
+fn main() {}
+
 pub static Example: FC<()> = |cx| {
     let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
 

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

@@ -21,7 +21,7 @@ const App: FC<()> = |cx| {
 };
 
 const Task: FC<()> = |cx| {
-    let (task, res) = cx.use_task(|| async { true });
+    let (task, res) = use_task(cx, || async { true });
     // task.pause();
     // task.restart();
     // task.stop();
@@ -29,7 +29,7 @@ const Task: FC<()> = |cx| {
 
     //
 
-    let _s = cx.use_task(|| async { "hello world".to_string() });
+    let _s = use_task(cx, || async { "hello world".to_string() });
 
     todo!()
 };

+ 1 - 0
packages/core/examples/borrowed.rs

@@ -67,6 +67,7 @@ impl PartialEq for ChildProps {
 }
 impl Properties for ChildProps {
     type Builder = ();
+    const IS_STATIC: bool = true;
     fn builder() -> Self::Builder {
         ()
     }

+ 2 - 1
packages/core/examples/fragment_from_iter.rs

@@ -4,7 +4,8 @@ use dioxus_core::prelude::*;
 
 fn App(cx: Context<()>) -> DomTree {
     //
-    let vak = cx.use_suspense(
+    let vak = use_suspense(
+        cx,
         || async {},
         |c, res| {
             //

+ 148 - 39
packages/core/src/arena.rs

@@ -1,53 +1,118 @@
+use std::cell::{RefCell, RefMut};
+use std::fmt::Display;
 use std::{cell::UnsafeCell, rc::Rc};
 
+use crate::heuristics::*;
 use crate::innerlude::*;
-use slotmap::SlotMap;
+use futures_util::stream::FuturesUnordered;
+use fxhash::{FxHashMap, FxHashSet};
+use slab::Slab;
+use smallvec::SmallVec;
 
-#[derive(Clone)]
-pub struct SharedArena {
-    pub components: Rc<UnsafeCell<ScopeMap>>,
-}
-pub type ScopeMap = SlotMap<ScopeId, Scope>;
+// 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);
 
-enum MutStatus {
-    Immut,
-    Mut,
+#[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 SharedArena {
-    pub fn new(arena: ScopeMap) -> Self {
-        let components = Rc::new(UnsafeCell::new(arena));
-        SharedArena { components }
+impl ElementId {
+    pub fn as_u64(self) -> u64 {
+        self.0 as u64
     }
+}
 
-    /// THIS METHOD IS CURRENTLY UNSAFE
-    /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
-    pub fn get(&self, idx: ScopeId) -> Option<&Scope> {
-        let inner = unsafe { &*self.components.get() };
-        inner.get(idx)
-    }
+type Shared<T> = Rc<RefCell<T>>;
+type TaskReceiver = futures_channel::mpsc::UnboundedReceiver<EventTrigger>;
+type TaskSender = futures_channel::mpsc::UnboundedSender<EventTrigger>;
 
-    /// THIS METHOD IS CURRENTLY UNSAFE
-    /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
-    pub fn get_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
-        let inner = unsafe { &mut *self.components.get() };
-        inner.get_mut(idx)
-    }
+/// These are resources shared among all the components and the virtualdom itself
+#[derive(Clone)]
+pub struct SharedResources {
+    pub components: Rc<UnsafeCell<Slab<Scope>>>,
 
-    fn inner(&self) -> &ScopeMap {
-        todo!()
+    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,
+        }
     }
 
-    fn inner_mut(&mut self) -> &mut ScopeMap {
-        todo!()
+    /// 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 METHOD IS CURRENTLY UNSAFE
-    /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
-    pub fn with<T>(&self, f: impl FnOnce(&mut ScopeMap) -> T) -> Result<T> {
+    /// 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() };
-        Ok(f(inner))
-        // todo!()
+        inner.get_mut(idx.0)
     }
 
     pub fn with_scope<'b, O: 'static>(
@@ -70,12 +135,56 @@ impl SharedArena {
 
     pub fn try_remove(&self, id: ScopeId) -> Result<Scope> {
         let inner = unsafe { &mut *self.components.get() };
-        inner
-            .remove(id)
-            .ok_or_else(|| Error::FatalInternal("Scope not found"))
+        Ok(inner.remove(id.0))
+        // .try_remove(id.0)
+        // .ok_or_else(|| Error::FatalInternal("Scope not found"))
     }
 
-    unsafe fn inner_unchecked<'s>() -> &'s mut ScopeMap {
-        todo!()
+    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) {}
 }

+ 75 - 47
packages/core/src/bumpframe.rs

@@ -1,10 +1,10 @@
 use crate::innerlude::*;
 use bumpalo::Bump;
+use std::cell::Cell;
 
-use std::cell::RefCell;
 pub struct ActiveFrame {
     // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
-    pub generation: RefCell<usize>,
+    pub generation: Cell<usize>,
 
     // The double-buffering situation that we will use
     pub frames: [BumpFrame; 2],
@@ -12,79 +12,107 @@ pub struct ActiveFrame {
 
 pub struct BumpFrame {
     pub bump: Bump,
-    pub head_node: VNode<'static>,
+    pub(crate) head_node: VNode<'static>,
+
+    // used internally for debugging
+    _name: &'static str,
 }
 
 impl ActiveFrame {
     pub fn new() -> Self {
-        Self::from_frames(
-            BumpFrame {
-                bump: Bump::new(),
-                head_node: NodeFactory::static_text(""),
-            },
-            BumpFrame {
-                bump: Bump::new(),
-                head_node: NodeFactory::static_text(""),
-            },
-        )
-    }
-
-    pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
+        let frame_a = BumpFrame {
+            bump: Bump::new(),
+            head_node: NodeFactory::unstable_place_holder(),
+            _name: "wip",
+        };
+        let frame_b = BumpFrame {
+            bump: Bump::new(),
+            head_node: NodeFactory::unstable_place_holder(),
+            _name: "fin",
+        };
         Self {
             generation: 0.into(),
-            frames: [a, b],
+            frames: [frame_a, frame_b],
         }
     }
 
-    pub fn cur_frame(&self) -> &BumpFrame {
-        match *self.generation.borrow() & 1 == 0 {
+    pub unsafe fn reset_wip_frame(&mut self) {
+        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.
+    pub fn wip_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 == 0 {
             true => &self.frames[0],
             false => &self.frames[1],
         }
     }
-    pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
-        match *self.generation.borrow() & 1 == 0 {
+
+    pub fn wip_frame_mut(&mut self) -> &mut BumpFrame {
+        match self.generation.get() & 1 == 0 {
             true => &mut self.frames[0],
             false => &mut self.frames[1],
         }
     }
 
-    pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
-        let raw_node = match *self.generation.borrow() & 1 == 0 {
+    /// The finished frame represents the frame that has been "finished" and cannot be modified again
+    pub fn finished_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 == 1 {
             true => &self.frames[0],
             false => &self.frames[1],
-        };
-
-        // Give out our self-referential item with our own borrowed lifetime
-        unsafe {
-            let unsafe_head = &raw_node.head_node;
-            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
-            safe_node
         }
     }
 
-    pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
-        let raw_node = match *self.generation.borrow() & 1 != 0 {
-            true => &self.frames[0],
-            false => &self.frames[1],
-        };
-
-        // Give out our self-referential item with our own borrowed lifetime
-        unsafe {
-            let unsafe_head = &raw_node.head_node;
-            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
-            safe_node
+    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
+    pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
+        let cur_head = &self.finished_frame().head_node;
+        unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
+    }
 
-    pub fn old_frame_mut(&mut self) -> &mut BumpFrame {
-        match *self.generation.borrow() & 1 == 0 {
-            true => &mut self.frames[1],
-            false => &mut self.frames[0],
-        }
+    /// Give out our self-referential item with our own borrowed lifetime
+    pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> {
+        let cur_head = &self.wip_frame().head_node;
+        unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
     }
 
     pub fn cycle_frame(&mut self) {
-        *self.generation.borrow_mut() += 1;
+        self.generation.set(self.generation.get() + 1);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    //! These tests are bad. I don't have a good way of properly testing the ActiveFrame stuff
+    use super::*;
+
+    #[test]
+    fn test_bump_frame() {
+        let mut frames = ActiveFrame::new();
+
+        // just cycle a few times and make sure we get the right frames out
+        for _ in 0..5 {
+            let fin = frames.finished_frame();
+            let wip = frames.wip_frame();
+            assert_eq!(wip._name, "wip");
+            assert_eq!(fin._name, "fin");
+            frames.cycle_frame();
+
+            let fin = frames.finished_frame();
+            let wip = frames.wip_frame();
+            assert_eq!(wip._name, "fin");
+            assert_eq!(fin._name, "wip");
+            frames.cycle_frame();
+        }
+        assert_eq!(frames.generation.get(), 10);
     }
 }

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

@@ -1,91 +0,0 @@
-/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
-///
-/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
-/// "InsertBefore".
-struct RealChildIterator<'a> {
-    scopes: &'a SharedArena,
-
-    // Heuristcally we should never bleed into 5 completely nested fragments/components
-    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
-    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
-}
-
-impl<'a> RealChildIterator<'a> {
-    fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> 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 = 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(_) => {
-                        // 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]);
-                        }
-                    }
-
-                    // Immediately abort suspended nodes - can't do anything with them yet
-                    // VNode::Suspended => should_pop = true,
-                    VNode::Suspended { real } => todo!(),
-
-                    // For components, we load their root and push them onto the stack
-                    VNode::Component(sc) => {
-                        let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
-
-                        // Simply swap the current node on the stack with the root of the component
-                        *node = scope.root();
-                    }
-                }
-            } 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
-    }
-}

+ 44 - 2
packages/core/src/component.rs

@@ -5,11 +5,43 @@
 //! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
 //! that ensures compile-time required and optional fields on cx.
 
-use crate::innerlude::{Context, DomTree, LazyNodes, VNode, FC};
+use crate::innerlude::{Context, DomTree, LazyNodes, FC};
 
+/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
+/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
+/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
+///
+/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
+/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
+/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
+///
+/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
+/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
+///
+/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
+/// we advise the use of smart pointers in cases where memoization is important.
+///
+/// ## Example
+///
+/// For props that are 'static:
+/// ```rust ignore
+/// #[derive(Props, PartialEq)]
+/// struct MyProps {
+///     data: String
+/// }
+/// ```
+///
+/// For props that borrow:
+///
+/// ```rust ignore
+/// #[derive(Props)]
+/// struct MyProps<'a >{
+///     data: &'a str
+/// }
+/// ```
 pub trait Properties: Sized {
     type Builder;
-    const IS_STATIC: bool = false;
+    const IS_STATIC: bool;
     fn builder() -> Self::Builder;
 
     /// Memoization can only happen if the props are 'static
@@ -50,6 +82,16 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
 ///
 /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
 /// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
+///
+/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+///
+/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+///
+/// ```rust
+/// rsx!{
+///     Fragment { key: "abc" }
+/// }
+/// ```
 #[allow(non_upper_case_globals, non_snake_case)]
 pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
     cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))

+ 104 - 352
packages/core/src/context.rs

@@ -1,25 +1,24 @@
-use crate::innerlude::*;
+//! Public APIs for managing component state, tasks, and lifecycles.
 
-use futures_util::FutureExt;
-use std::{
-    any::{Any, TypeId},
-    cell::{Cell, RefCell},
-    future::Future,
-    marker::PhantomData,
-    ops::Deref,
-    rc::{Rc, Weak},
-};
+use crate::innerlude::*;
+use std::{any::TypeId, ops::Deref, rc::Rc};
 
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
-/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
 ///
-/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
+/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
+///
+/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
+/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
+/// exists for the `use_provide_state` hook to provide a shared state object.
+///
+/// For the most part, the only method you should be using regularly is `render`.
+///
+/// ## Example
 ///
 /// ```ignore
 /// #[derive(Properties)]
 /// struct Props {
 ///     name: String
-///
 /// }
 ///
 /// fn example(cx: Context<Props>) -> VNode {
@@ -28,16 +27,6 @@ use std::{
 ///     }
 /// }
 /// ```
-///
-/// ## Available Methods:
-/// - render
-/// - use_hook
-/// - use_task
-/// - use_suspense
-/// - submit_task
-/// - children
-/// - use_effect
-///
 pub struct Context<'src, T> {
     pub props: &'src T,
     pub scope: &'src Scope,
@@ -98,13 +87,22 @@ impl<'src, P> Context<'src, P> {
     ///     })
     /// }
     /// ```
-    pub fn children(&self) -> &'src [VNode<'src>] {
-        self.scope.child_nodes
+    pub fn children(&self) -> ScopeChildren<'src> {
+        self.scope.child_nodes()
     }
 
     /// Create a subscription that schedules a future render for the reference component
+    ///
+    /// ## Notice: you should prefer using prepare_update and get_scope_id
+    ///
     pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        self.scope.event_channel.clone()
+        let cb = self.scope.vdom.schedule_update();
+        let id = self.get_scope_id();
+        Rc::new(move || cb(id))
+    }
+
+    pub fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
+        self.scope.vdom.schedule_update()
     }
 
     pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
@@ -115,7 +113,8 @@ impl<'src, P> Context<'src, P> {
         todo!()
     }
 
-    /// Get's this context's ScopeId
+    /// Get's this component's unique identifier.
+    ///
     pub fn get_scope_id(&self) -> ScopeId {
         self.scope.our_arena_idx.clone()
     }
@@ -139,16 +138,88 @@ impl<'src, P> Context<'src, P> {
         self,
         lazy_nodes: LazyNodes<'src, F>,
     ) -> DomTree<'src> {
-        let scope_ref = self.scope;
-        let listener_id = &scope_ref.listener_idx;
-        Some(lazy_nodes.into_vnode(NodeFactory {
-            scope_ref,
-            listener_id,
-        }))
+        let bump = &self.scope.frames.wip_frame().bump;
+        Some(lazy_nodes.into_vnode(NodeFactory { bump }))
+    }
+
+    /// `submit_task` will submit the future to be polled.
+    ///
+    /// This is useful when you have some async task that needs to be progressed.
+    ///
+    /// This method takes ownership over the task you've provided, and must return (). This means any work that needs to
+    /// happen must occur within the future or scheduled for after the future completes (through schedule_update )
+    ///
+    /// ## Explanation
+    /// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
+    ///
+    /// Tasks can't return anything, but they can be controlled with the returned handle
+    ///
+    /// Tasks will only run until the component renders again. Because `submit_task` is valid for the &'src lifetime, it
+    /// is considered "stable"
+    ///
+    ///
+    ///
+    pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
+        self.scope.vdom.submit_task(task)
+    }
+
+    /// Add a state globally accessible to child components via tree walking
+    pub fn add_shared_state<T: 'static>(self, val: T) {
+        self.scope
+            .shared_contexts
+            .borrow_mut()
+            .insert(TypeId::of::<T>(), Rc::new(val))
+            .map(|_| {
+                log::warn!("A shared state was replaced with itself. This is does not result in a panic, but is probably not what you are trying to do");
+            });
+    }
+
+    /// 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>> {
+        let mut scope = Some(self.scope);
+        let mut parent = None;
+
+        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
     }
 
     /// Store a value between renders
     ///
+    /// This is *the* foundational hook for all other hooks.
+    ///
     /// - Initializer: closure used to create the initial hook state
     /// - Runner: closure used to output a value every time the hook is used
     /// - Cleanup: closure used to teardown the hook once the dom is cleaned up
@@ -192,323 +263,4 @@ Any function prefixed with "use" should not be called conditionally.
 
         runner(self.scope.hooks.next::<State>().expect(ERR_MSG))
     }
-
-    /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
-    ///
-    /// This is a hook, so it may not be called conditionally!
-    ///
-    /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
-    /// so don't put it in a conditional.
-    ///
-    /// When the component is dropped, so is the context. Be aware of this behavior when consuming
-    /// the context via Rc/Weak.
-    ///
-    ///
-    ///
-    pub fn use_provide_context<T, F>(self, init: F) -> &'src Rc<T>
-    where
-        T: 'static,
-        F: FnOnce() -> T,
-    {
-        let ty = TypeId::of::<T>();
-        let contains_key = self.scope.shared_contexts.borrow().contains_key(&ty);
-
-        let is_initialized = self.use_hook(
-            |_| false,
-            |s| {
-                let i = s.clone();
-                *s = true;
-                i
-            },
-            |_| {},
-        );
-
-        match (is_initialized, contains_key) {
-            // Do nothing, already initialized and already exists
-            (true, true) => {}
-
-            // Needs to be initialized
-            (false, false) => {
-                log::debug!("Initializing context...");
-                let initialized = Rc::new(init());
-                let p = self
-                    .scope
-                    .shared_contexts
-                    .borrow_mut()
-                    .insert(ty, initialized);
-                log::info!(
-                    "There are now {} shared contexts for scope {:?}",
-                    self.scope.shared_contexts.borrow().len(),
-                    self.scope.our_arena_idx,
-                );
-            }
-
-            _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
-        };
-
-        self.use_context::<T>()
-    }
-
-    /// There are hooks going on here!
-    pub fn use_context<T: 'static>(self) -> &'src Rc<T> {
-        self.try_use_context().unwrap()
-    }
-
-    /// Uses a context, storing the cached value around
-    ///
-    /// If a context is not found on the first search, then this call will be  "dud", always returning "None" even if a
-    /// context was added later. This allows using another hook as a fallback
-    ///
-    pub fn try_use_context<T: 'static>(self) -> Option<&'src Rc<T>> {
-        struct UseContextHook<C> {
-            par: Option<Rc<C>>,
-        }
-
-        self.use_hook(
-            move |_| {
-                let mut scope = Some(self.scope);
-                let mut par = None;
-
-                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");
-
-                        par = Some(rc);
-                        break;
-                    } else {
-                        match inner.parent_idx {
-                            Some(parent_id) => {
-                                scope = inner.arena_link.get(parent_id);
-                            }
-                            None => break,
-                        }
-                    }
-                }
-                //
-                UseContextHook { par }
-            },
-            move |hook| hook.par.as_ref(),
-            |_| {},
-        )
-    }
-
-    /// `submit_task` will submit the future to be polled.
-    ///
-    /// This is useful when you have some async task that needs to be progressed.
-    ///
-    /// This method takes ownership over the task you've provided, and must return (). This means any work that needs to
-    /// happen must occur within the future or scheduled for after the future completes (through schedule_update )
-    ///
-    /// ## Explanation
-    /// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
-    ///
-    /// Tasks can't return anything, but they can be controlled with the returned handle
-    ///
-    /// Tasks will only run until the component renders again. Because `submit_task` is valid for the &'src lifetime, it
-    /// is considered "stable"
-    ///
-    ///
-    ///
-    pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        (self.scope.task_submitter)(task);
-        TaskHandle { _p: PhantomData {} }
-    }
-
-    /// Awaits the given task, forcing the component to re-render when the value is ready.
-    ///
-    ///
-    ///
-    ///
-    pub fn use_task<Out, Fut, Init>(
-        self,
-        task_initializer: Init,
-    ) -> (TaskHandle<'src>, &'src Option<Out>)
-    where
-        Out: 'static,
-        Fut: Future<Output = Out> + 'static,
-        Init: FnOnce() -> Fut + 'src,
-    {
-        struct TaskHook<T> {
-            task_dump: Rc<RefCell<Option<T>>>,
-            value: Option<T>,
-        }
-
-        // whenever the task is complete, save it into th
-        self.use_hook(
-            move |hook_idx| {
-                let task_fut = task_initializer();
-
-                let task_dump = Rc::new(RefCell::new(None));
-
-                let slot = task_dump.clone();
-                let update = self.schedule_update();
-                let originator = self.scope.our_arena_idx.clone();
-
-                self.submit_task(Box::pin(task_fut.then(move |output| async move {
-                    *slot.as_ref().borrow_mut() = Some(output);
-                    update();
-                    EventTrigger {
-                        event: VirtualEvent::AsyncEvent { hook_idx },
-                        originator,
-                        priority: EventPriority::Low,
-                        real_node_id: None,
-                    }
-                })));
-
-                TaskHook {
-                    task_dump,
-                    value: None,
-                }
-            },
-            |hook| {
-                if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
-                    hook.value = Some(val);
-                }
-                (TaskHandle { _p: PhantomData }, &hook.value)
-            },
-            |_| {},
-        )
-    }
-}
-
-pub(crate) struct SuspenseHook {
-    pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
-    pub callback: SuspendedCallback,
-    pub dom_node_id: Rc<Cell<RealDomNode>>,
-}
-type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
-
-impl<'src, P> Context<'src, P> {
-    /// Asynchronously render new nodes once the given future has completed.
-    ///
-    /// # Easda
-    ///
-    ///
-    ///
-    ///
-    /// # Example
-    ///
-    ///
-    pub fn use_suspense<Out, Fut, Cb>(
-        self,
-        task_initializer: impl FnOnce() -> Fut,
-        user_callback: Cb,
-    ) -> DomTree<'src>
-    where
-        Fut: Future<Output = Out> + 'static,
-        Out: 'static,
-        Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
-    {
-        self.use_hook(
-            move |hook_idx| {
-                let value = Rc::new(RefCell::new(None));
-
-                let dom_node_id = Rc::new(RealDomNode::empty_cell());
-                let domnode = dom_node_id.clone();
-
-                let slot = 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 {
-                                dom_id: RealDomNode::empty_cell(),
-                                key: None,
-                                kind: VNodeKind::Suspended {
-                                    node: domnode.clone(),
-                                },
-                            })
-                        }
-                    }
-                });
-
-                let originator = self.scope.our_arena_idx.clone();
-                let task_fut = task_initializer();
-                let domnode = dom_node_id.clone();
-
-                let slot = value.clone();
-                self.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,
-                    }
-                })));
-
-                SuspenseHook {
-                    value,
-                    callback,
-                    dom_node_id,
-                }
-            },
-            move |hook| {
-                let cx = Context {
-                    scope: &self.scope,
-                    props: &(),
-                };
-                let csx = SuspendedContext { inner: cx };
-                (&hook.callback)(csx)
-            },
-            |_| {},
-        )
-    }
-}
-
-pub struct SuspendedContext<'a> {
-    pub(crate) inner: Context<'a, ()>,
-}
-impl<'src> SuspendedContext<'src> {
-    pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
-        self,
-        lazy_nodes: LazyNodes<'src, F>,
-    ) -> DomTree<'src> {
-        let scope_ref = self.inner.scope;
-        let listener_id = &scope_ref.listener_idx;
-        Some(lazy_nodes.into_vnode(NodeFactory {
-            scope_ref,
-            listener_id,
-        }))
-    }
-}
-
-#[derive(Clone, Copy)]
-pub struct TaskHandle<'src> {
-    _p: PhantomData<&'src ()>,
-}
-
-impl<'src> TaskHandle<'src> {
-    pub fn toggle(&self) {}
-    pub fn start(&self) {}
-    pub fn stop(&self) {}
-    pub fn restart(&self) {}
 }

+ 1066 - 790
packages/core/src/diff.rs

@@ -15,9 +15,17 @@
 //! whose keys can be converted to u64 on FFI boundaries.
 //!
 //! 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 realdomnode will be populated incorrectly and
+//! 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
+//!
+//! 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
+//! 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.
+//!
 //! ## Subtree Memoization
 //! -----------------------
 //! We also employ "subtree memoization" which saves us from having to check trees which take no dynamic content. We can
@@ -34,13 +42,21 @@
 //! 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.
 //!
+//!
 //! ## Garbage Collection
 //! ---------------------
-//! We roughly place the role of garbage collection onto the reconciler. Dioxus needs to manage the lifecycle of components
-//! but will not spend any time cleaning up old elements. It's the Reconciler's duty to understand which elements need to
-//! be cleaned up *after* the diffing is completed. The reconciler should schedule this garbage collection as the absolute
-//! lowest priority task, after all edits have been applied.
+//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garabge
+//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then
+//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient.
+//!
+//! The IDs used by the key/map are just an index into a vec. This means that Dioxus will drive the key allocation strategy
+//! 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.
 //!
+//! 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
 //! ----------------------------
@@ -48,63 +64,69 @@
 //! More info on how to improve this diffing algorithm:
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
 
-use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
-use fxhash::FxHashSet;
+use crate::{arena::SharedResources, innerlude::*};
+use futures_util::Future;
+use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
 
-use std::any::Any;
+use std::{any::Any, cell::Cell, cmp::Ordering, marker::PhantomData, pin::Pin};
+use DomEdit::*;
 
-/// The accompanying "real dom" exposes an imperative API for controlling the UI layout
-///
-/// Instead of having handles directly over nodes, Dioxus uses simple u64s as node IDs.
-/// The expectation is that the underlying renderer will mainain their Nodes in something like slotmap or an ECS memory
-/// where indexing is very fast. For reference, the slotmap in the WebSys renderer takes about 3ns to randomly access any
-/// node.
-///
-/// The "RealDom" abstracts over the... real dom. The RealDom trait assumes that the renderer maintains a stack of real
-/// nodes as the diffing algorithm descenes through the tree. This means that whatever is on top of the stack will receive
-/// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any
-/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
-pub trait RealDom<'a> {
-    fn request_available_node(&mut self) -> RealDomNode;
-    fn raw_node_as_any(&self) -> &mut dyn Any;
-}
+pub struct DiffMachine<'r, 'bump> {
+    pub vdom: &'bump SharedResources,
+
+    pub edits: Mutations<'bump>,
+
+    pub scope_stack: SmallVec<[ScopeId; 5]>,
 
-pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
-    pub dom: &'real mut Dom,
-    pub edits: DomEditor<'real, 'bump>,
-    pub components: &'bump SharedArena,
-    pub task_queue: &'bump TaskQueue,
-    pub cur_idxs: SmallVec<[ScopeId; 5]>,
     pub diffed: FxHashSet<ScopeId>,
-    pub event_queue: EventQueue,
-    pub seen_nodes: FxHashSet<ScopeId>,
+
+    // will be used later for garbage collection
+    // we check every seen node and then schedule its eventual deletion
+    pub seen_scopes: FxHashSet<ScopeId>,
+
+    _r: PhantomData<&'r ()>,
 }
 
-impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
-where
-    Dom: RealDom<'bump>,
-{
-    pub fn new(
-        edits: &'real mut Vec<DomEdit<'bump>>,
-        dom: &'real mut Dom,
-        components: &'bump SharedArena,
-        cur_idx: ScopeId,
-        event_queue: EventQueue,
-        task_queue: &'bump TaskQueue,
+impl<'r, 'bump> DiffMachine<'r, 'bump> {
+    pub(crate) fn new(
+        edits: Mutations<'bump>,
+        cur_scope: ScopeId,
+        shared: &'bump SharedResources,
     ) -> Self {
         Self {
-            edits: DomEditor::new(edits),
-            components,
-            dom,
-            cur_idxs: smallvec![cur_idx],
-            event_queue,
-            task_queue,
+            edits,
+            scope_stack: smallvec![cur_scope],
+            vdom: shared,
             diffed: FxHashSet::default(),
-            seen_nodes: 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 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(())
+    }
+
     // Diff the `old` node with the `new` node. Emits instructions to modify a
     // physical DOM node that reflects `old` into something that reflects `new`.
     //
@@ -117,93 +139,152 @@ where
             // 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.dom_id.get();
+                let root = old_node.direct_id();
+
                 if old.text != new.text {
-                    self.edits.push(root);
-                    log::debug!("Text has changed {}, {}", old.text, new.text);
-                    self.edits.set_text(new.text);
-                    self.edits.pop();
+                    self.edit_push_root(root);
+                    self.edit_set_text(new.text);
+                    self.edit_pop();
                 }
 
-                new_node.dom_id.set(root);
+                new.dom_id.set(Some(root));
             }
 
             (VNodeKind::Element(old), VNodeKind::Element(new)) => {
+                let root = old_node.direct_id();
+
                 // 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
                 //
-                // In Dioxus, this is less likely to occur unless through a fragment
-                let root = old_node.dom_id.get();
+                // This case is rather rare (typically only in non-keyed lists)
                 if new.tag_name != old.tag_name || new.namespace != old.namespace {
-                    self.edits.push(root);
-                    let meta = self.create(new_node);
-                    self.edits.replace_with(meta.added_to_stack);
-                    self.edits.pop();
+                    self.replace_node_with_node(root, old_node, new_node);
                     return;
                 }
 
-                new_node.dom_id.set(root);
+                new.dom_id.set(Some(root));
+
+                // Don't push the root if we don't have to
+                let mut has_comitted = false;
+                let mut please_commit = |edits: &mut Vec<DomEdit>| {
+                    if !has_comitted {
+                        has_comitted = true;
+                        edits.push(PushRoot { id: root.as_u64() });
+                    }
+                };
+
+                // 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)
+                    }
+                }
+
+                // 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);
+
+                        // Make sure the listener gets attached to the scope list
+                        self.fix_listener(listener);
+                    }
+                }
+
+                if has_comitted {
+                    self.edit_pop();
+                }
 
-                // push it just in case
-                // TODO: remove this - it clogs up things and is inefficient
-                self.edits.push(root);
-                self.diff_listeners(old.listeners, new.listeners);
-                self.diff_attr(old.attributes, new.attributes, new.namespace);
                 self.diff_children(old.children, new.children);
-                self.edits.pop();
             }
 
             (VNodeKind::Component(old), VNodeKind::Component(new)) => {
-                log::warn!("diffing components? {:#?}", new.user_fc);
+                let scope_addr = old.ass_scope.get().unwrap();
+
+                // Make sure we're dealing with the same component (by function pointer)
                 if old.user_fc == new.user_fc {
-                    // Make sure we're dealing with the same component (by function pointer)
-                    self.cur_idxs.push(old.ass_scope.get().unwrap());
+                    //
+                    self.scope_stack.push(scope_addr);
 
                     // Make sure the new component vnode is referencing the right scope id
-                    let scope_id = old.ass_scope.get();
-                    new.ass_scope.set(scope_id);
+                    new.ass_scope.set(Some(scope_addr));
 
                     // make sure the component's caller function is up to date
-                    let scope = self.components.get_mut(scope_id.unwrap()).unwrap();
-
-                    scope.caller = new.caller.clone();
+                    let scope = self.get_scope_mut(&scope_addr).unwrap();
 
-                    // ack - this doesn't work on its own!
-                    scope.update_children(new.children);
+                    scope
+                        .update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
 
                     // React doesn't automatically memoize, but we do.
-                    let should_render = match old.comparator {
-                        Some(comparator) => comparator(new),
-                        None => true,
-                    };
-
-                    if should_render {
-                        scope.run_scope().unwrap();
-                        self.diff_node(scope.old_frame(), scope.next_frame());
-                    } else {
-                        //
+                    let compare = old.comparator.unwrap();
+
+                    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());
+                        }
                     }
-                    self.cur_idxs.pop();
 
-                    self.seen_nodes.insert(scope_id.unwrap());
+                    self.scope_stack.pop();
+
+                    self.seen_scopes.insert(scope_addr);
                 } else {
-                    // this seems to be a fairy common code path that we could
-                    let mut old_iter = RealChildIterator::new(old_node, &self.components);
+                    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.edits.push(to_remove);
-                        self.edits.remove();
+                        self.edit_push_root(to_remove.direct_id());
+                        self.edit_remove();
                     }
 
                     // seems like we could combine this into a single instruction....
-                    self.edits.push(first);
-                    let meta = self.create(new_node);
-                    self.edits.replace_with(meta.added_to_stack);
-                    self.edits.pop();
+                    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();
@@ -219,88 +300,48 @@ where
                     return;
                 }
 
-                // Diff using the approach where we're looking for added or removed nodes.
-                if old.children.len() != new.children.len() {}
-
-                // Diff where we think the elements are the same
-                if old.children.len() == new.children.len() {}
-
                 self.diff_children(old.children, new.children);
             }
 
+            (VNodeKind::Anchor(old), VNodeKind::Anchor(new)) => {
+                new.dom_id.set(old.dom_id.get());
+            }
+
             // 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::Element(_)
+                | VNodeKind::Anchor(_),
                 VNodeKind::Component(_)
                 | VNodeKind::Fragment(_)
                 | VNodeKind::Text(_)
-                | VNodeKind::Element(_),
+                | VNodeKind::Element(_)
+                | VNodeKind::Anchor(_),
             ) => {
-                // Choose the node to use as the placeholder for replacewith
-                let back_node = match old_node.kind {
-                    // We special case these two types to avoid allocating the small-vecs
-                    VNodeKind::Element(_) | VNodeKind::Text(_) => old_node.dom_id.get(),
-
-                    _ => {
-                        let mut old_iter = RealChildIterator::new(old_node, &self.components);
-
-                        let back_node = old_iter
-                            .next()
-                            .expect("Empty fragments should generate a placeholder.");
-
-                        // remove any leftovers
-                        for to_remove in old_iter {
-                            self.edits.push(to_remove);
-                            self.edits.remove();
-                        }
-
-                        back_node
-                    }
-                };
-
-                // replace the placeholder or first node with the nodes generated from the "new"
-                self.edits.push(back_node);
-                let meta = self.create(new_node);
-                self.edits.replace_with(meta.added_to_stack);
-
-                // todo use the is_static metadata to update this subtree
+                self.replace_and_create_many_with_many([old_node], [new_node]);
             }
 
             // TODO
-            (VNodeKind::Suspended { node }, new) => todo!(),
-            (old, VNodeKind::Suspended { .. }) => {
-                // a node that was once real is now suspended
+            (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]);
             }
         }
     }
-}
-
-// 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.
-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,
-        }
-    }
-}
 
-impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
-where
-    Dom: RealDom<'bump>,
-{
     // Emit instructions to create the given virtual node.
     //
     // The change list stack may have any shape upon entering this function:
@@ -310,15 +351,22 @@ where
     // When this function returns, the new node is on top of the change list stack:
     //
     //     [... node]
-    pub fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
-        log::warn!("Creating node! ... {:#?}", node);
+    pub fn create_vnode(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
         match &node.kind {
             VNodeKind::Text(text) => {
-                let real_id = self.dom.request_available_node();
-                self.edits.create_text_node(text.text, real_id);
-                node.dom_id.set(real_id);
+                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(!)
                 //
@@ -336,22 +384,19 @@ where
                     static_attrs: _,
                     static_children: _,
                     static_listeners: _,
+                    dom_id,
                 } = el;
 
-                let real_id = self.dom.request_available_node();
-                if let Some(namespace) = namespace {
-                    self.edits
-                        .create_element(tag_name, Some(namespace), real_id)
-                } else {
-                    self.edits.create_element(tag_name, None, real_id)
-                };
-                node.dom_id.set(real_id);
+                let real_id = self.vdom.reserve_node();
+                self.edit_create_element(tag_name, *namespace, real_id);
+                dom_id.set(Some(real_id));
 
-                listeners.iter().enumerate().for_each(|(idx, listener)| {
-                    log::info!("setting listener id to {:#?}", real_id);
-                    listener.mounted_node.set(real_id);
-                    self.edits
-                        .new_event_listener(listener.event, listener.scope, idx, 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;
@@ -359,8 +404,7 @@ where
 
                 for attr in *attributes {
                     is_static = is_static && attr.is_static;
-                    self.edits
-                        .set_attribute(&attr.name, &attr.value, *namespace);
+                    self.edit_set_attribute(attr);
                 }
 
                 // Fast path: if there is a single text child, it is faster to
@@ -375,85 +419,88 @@ where
                 // TODO move over
                 // if children.len() == 1 {
                 //     if let VNodeKind::Text(text) = &children[0].kind {
-                //         self.edits.set_text(text.text);
+                //         self.set_text(text.text);
                 //         return CreateMeta::new(is_static, 1);
                 //     }
                 // }
 
                 for child in *children {
-                    let child_meta = self.create(child);
+                    let child_meta = self.create_vnode(child);
                     is_static = is_static && child_meta.is_static;
 
                     // append whatever children were generated by this call
-                    self.edits.append_children(child_meta.added_to_stack);
+                    self.edit_append_children(child_meta.added_to_stack);
                 }
 
-                // if is_static {
-                //     log::debug!("created a static node {:#?}", node);
-                // } else {
-                //     log::debug!("created a dynamic node {:#?}", node);
-                // }
-
-                // el_is_static.set(is_static);
                 CreateMeta::new(is_static, 1)
             }
 
             VNodeKind::Component(vcomponent) => {
-                log::debug!("Mounting a new component");
                 let caller = vcomponent.caller.clone();
 
-                let parent_idx = self.cur_idxs.last().unwrap().clone();
+                let parent_idx = self.scope_stack.last().unwrap().clone();
 
                 // Insert a new scope into our component list
-                let new_idx = self
-                    .components
-                    .with(|components| {
-                        components.insert_with_key(|new_idx| {
-                            let parent_scope = self.components.get(parent_idx).unwrap();
-                            let height = parent_scope.height + 1;
-                            Scope::new(
-                                caller,
-                                new_idx,
-                                Some(parent_idx),
-                                height,
-                                self.event_queue.new_channel(height, new_idx),
-                                self.components.clone(),
-                                vcomponent.children,
-                                self.task_queue.new_submitter(),
-                            )
-                        })
-                    })
-                    .unwrap();
-
-                // This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work.
-                // This is mostly used for cleanup - to remove old scopes when components are destroyed.
-                // TODO
-                //
-                // self.components
-                //     .try_get_mut(idx)
-                //     .unwrap()
-                //     .descendents
-                //     .borrow_mut()
-                //     .insert(idx);
-
-                // TODO: abstract this unsafe into the arena abstraction
-                let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() };
-                let new_component = inner.get_mut(new_idx).unwrap();
+                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(),
+                    )
+                });
 
                 // Actually initialize the caller's slot with the right address
                 vcomponent.ass_scope.set(Some(new_idx));
 
+                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);
+                }
+
+                // TODO:
+                //  add noderefs to current noderef list Noderefs
+                //  add effects to current effect list Effects
+
+                let new_component = self.get_scope_mut(&new_idx).unwrap();
+
                 // Run the scope for one iteration to initialize it
-                new_component.run_scope().unwrap();
+                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");
+                    }
+                }
+
+                // Take the node that was just generated from running the component
+                let nextnode = new_component.frames.fin_head();
 
-                // TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely)
-                let nextnode = new_component.next_frame();
-                self.cur_idxs.push(new_idx);
-                let meta = self.create(nextnode);
-                self.cur_idxs.pop();
+                // Push the new scope onto the stack
+                self.scope_stack.push(new_idx);
 
-                // Finally, insert this node as a seen node.
-                self.seen_nodes.insert(new_idx);
+                // Run the creation algorithm with this scope on the stack
+                let meta = self.create_vnode(nextnode);
+
+                // pop the scope off the stack
+                self.scope_stack.pop();
+
+                if meta.added_to_stack == 0 {
+                    panic!("Components should *always* generate nodes - even if they fail");
+                }
+
+                // Finally, insert this scope as a seen node.
+                self.seen_scopes.insert(new_idx);
 
                 CreateMeta::new(vcomponent.is_static, meta.added_to_stack)
             }
@@ -461,33 +508,34 @@ where
             // 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) => {
-                let mut nodes_added = 0;
-                for child in frag.children.iter().rev() {
-                    // different types of nodes will generate different amounts on the stack
-                    // nested fragments will spew a ton of nodes onto the stack
-                    // TODO: make sure that our order (.rev) makes sense in a nested situation
-                    let new_meta = self.create(child);
-                    nodes_added += new_meta.added_to_stack;
-                }
-                log::info!("This fragment added {} nodes to the stack", nodes_added);
-
-                // Never ignore
-                CreateMeta::new(false, nodes_added)
-            }
+            VNodeKind::Fragment(frag) => self.create_children(frag.children),
 
-            VNodeKind::Suspended { node: real_node } => {
-                let id = self.dom.request_available_node();
-                self.edits.create_placeholder(id);
-                node.dom_id.set(id);
-                real_node.set(id);
+            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)
             }
         }
     }
-}
 
-impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
+    fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta {
+        let mut is_static = true;
+        let mut added_to_stack = 0;
+
+        // 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;
+        }
+
+        CreateMeta {
+            is_static,
+            added_to_stack,
+        }
+    }
+
     /// Destroy a scope and all of its descendents.
     ///
     /// Calling this will run the destuctors on all hooks in the tree.
@@ -499,8 +547,8 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
         // 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_nodes.insert(scope_id);
-            let scope = self.components.get(scope_id).unwrap();
+            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());
@@ -513,118 +561,13 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
         // 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.components.try_remove(node).unwrap();
+            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
         }
     }
 
-    // Diff event listeners between `old` and `new`.
-    //
-    // The listeners' node must be on top of the change list stack:
-    //
-    //     [... node]
-    //
-    // The change list stack is left unchanged.
-    fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) {
-        if !old.is_empty() || !new.is_empty() {
-            // self.edits.commit_traversal();
-        }
-        // TODO
-        // what does "diffing listeners" even mean?
-
-        'outer1: for (_l_idx, new_l) in new.iter().enumerate() {
-            // go through each new listener
-            // find its corresponding partner in the old list
-            // if any characteristics changed, remove and then re-add
-
-            // if nothing changed, then just move on
-            let _event_type = new_l.event;
-
-            for old_l in old {
-                if new_l.event == old_l.event {
-                    new_l.mounted_node.set(old_l.mounted_node.get());
-                    // if new_l.id != old_l.id {
-                    //     self.edits.remove_event_listener(event_type);
-                    //     // TODO! we need to mess with events and assign them by RealDomNode
-                    //     // self.edits
-                    //     //     .update_event_listener(event_type, new_l.scope, new_l.id)
-                    // }
-
-                    continue 'outer1;
-                }
-            }
-
-            // self.edits
-            //     .new_event_listener(event_type, new_l.scope, new_l.id);
-        }
-
-        // 'outer2: for old_l in old {
-        //     for new_l in new {
-        //         if new_l.event == old_l.event {
-        //             continue 'outer2;
-        //         }
-        //     }
-        //     self.edits.remove_event_listener(old_l.event);
-        // }
-    }
-
-    // Diff a node's attributes.
-    //
-    // The attributes' node must be on top of the change list stack:
-    //
-    //     [... node]
-    //
-    // The change list stack is left unchanged.
-    fn diff_attr(
-        &mut self,
-        old: &'bump [Attribute<'bump>],
-        new: &'bump [Attribute<'bump>],
-        namespace: Option<&'static str>,
-    ) {
-        // Do O(n^2) passes to add/update and remove attributes, since
-        // there are almost always very few attributes.
-        //
-        // The "fast" path is when the list of attributes name is identical and in the same order
-        // With the Rsx and Html macros, this will almost always be the case
-        'outer: for new_attr in new {
-            if new_attr.is_volatile {
-                // self.edits.commit_traversal();
-                self.edits
-                    .set_attribute(new_attr.name, new_attr.value, namespace);
-            } else {
-                for old_attr in old {
-                    if old_attr.name == new_attr.name {
-                        if old_attr.value != new_attr.value {
-                            // self.edits.commit_traversal();
-                            self.edits
-                                .set_attribute(new_attr.name, new_attr.value, namespace);
-                        }
-                        continue 'outer;
-                    } else {
-                        // names are different, a varying order of attributes has arrived
-                    }
-                }
-
-                // self.edits.commit_traversal();
-                self.edits
-                    .set_attribute(new_attr.name, new_attr.value, namespace);
-            }
-        }
-
-        'outer2: for old_attr in old {
-            for new_attr in new {
-                if old_attr.name == new_attr.name {
-                    continue 'outer2;
-                }
-            }
-
-            // self.edits.commit_traversal();
-            self.edits.remove_attribute(old_attr.name);
-        }
-    }
-
     // Diff the given set of old and new children.
     //
     // The parent must be on top of the change list stack when this function is
@@ -633,65 +576,80 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     //     [... parent]
     //
     // the change list stack is in the same state when this function returns.
+    //
+    // 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.
     fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
-        if new.is_empty() {
-            if !old.is_empty() {
-                // self.edits.commit_traversal();
-                self.remove_all_children(old);
-            }
-            return;
-        }
+        const IS_EMPTY: bool = true;
+        const IS_NOT_EMPTY: bool = false;
 
-        if new.len() == 1 {
-            match (&old.first(), &new[0]) {
-                // (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext))
-                //     if old_vtext.text == new_vtext.text =>
-                // {
-                //     // Don't take this fast path...
-                // }
+        match (old.is_empty(), new.is_empty()) {
+            (IS_EMPTY, IS_EMPTY) => {}
 
-                // (_, VNodeKind::Text(text)) => {
-                //     // self.edits.commit_traversal();
-                //     log::debug!("using optimized text set");
-                //     self.edits.set_text(text.text);
-                //     return;
-                // }
-
-                // todo: any more optimizations
-                (_, _) => {}
+            // 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);
             }
-        }
 
-        if old.is_empty() {
-            if !new.is_empty() {
-                // self.edits.commit_traversal();
-                self.create_and_append_children(new);
+            // 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 {
+                    self.remove_vnode(node);
+                }
             }
-            return;
-        }
 
-        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());
+                    }
+
+                    // 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);
+                    }
 
-        if new_is_keyed && old_is_keyed {
-            log::warn!("using the wrong approach");
-            self.diff_non_keyed_children(old, new);
-            // todo!("Not yet implemented a migration away from temporaries");
-            // let t = self.edits.next_temporary();
-            // self.diff_keyed_children(old, new);
-            // self.edits.set_next_temporary(t);
-        } else {
-            // log::debug!("diffing non keyed children");
-            self.diff_non_keyed_children(old, new);
+                    // Replace whatever nodes are sitting there with the anchor
+                    (_, VNodeKind::Anchor(anchor)) => {
+                        self.replace_and_create_many_with_many(old, [first_new]);
+                    }
+
+                    // 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);
+                        }
+                    }
+                }
+            }
         }
     }
 
@@ -710,14 +668,8 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     //
     // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
     //
-    // When entering this function, the parent must be on top of the change list
-    // stack:
-    //
-    //     [... parent]
-    //
-    // Upon exiting, the change list stack is in the same state.
-    fn diff_keyed_children(&self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
-        // todo!();
+    // The stack is empty upon entry.
+    fn diff_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
         if cfg!(debug_assertions) {
             let mut keys = fxhash::FxHashSet::default();
             let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
@@ -745,16 +697,13 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
         //
         // `shared_prefix_count` is the count of how many nodes at the start of
         // `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,
         };
 
-        match self.diff_keyed_prefix(old, new) {
-            KeyedPrefixResult::Finished => return,
-            KeyedPrefixResult::MoreWorkToDo(count) => count,
-        };
-
         // 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
@@ -795,71 +744,45 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     // Diff the prefix of children in `new` and `old` that share the same keys in
     // the same order.
     //
-    // Upon entry of this function, the change list stack must be:
-    //
-    //     [... parent]
-    //
-    // Upon exit, the change list stack is the same.
+    // The stack is empty upon entry.
     fn diff_keyed_prefix(
-        &self,
-        _old: &'bump [VNode<'bump>],
-        _new: &'bump [VNode<'bump>],
+        &mut self,
+        old: &'bump [VNode<'bump>],
+        new: &'bump [VNode<'bump>],
     ) -> KeyedPrefixResult {
-        todo!()
-        // self.edits.go_down();
-        // let mut shared_prefix_count = 0;
-
-        // for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
-        //     if old.key() != new.key() {
-        //         break;
-        //     }
-
-        //     self.edits.go_to_sibling(i);
+        let mut shared_prefix_count = 0;
 
-        //     self.diff_node(old, new);
+        for (old, new) in old.iter().zip(new.iter()) {
+            // abort early if we finally run into nodes with different keys
+            if old.key() != new.key() {
+                break;
+            }
+            self.diff_node(old, new);
+            shared_prefix_count += 1;
+        }
 
-        //     shared_prefix_count += 1;
-        // }
+        // If that was all of the old children, then create and append the remaining
+        // 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);
 
-        // // If that was all of the old children, then create and append the remaining
-        // // new children and we're finished.
-        // if shared_prefix_count == old.len() {
-        //     self.edits.go_up();
-        //     // self.edits.commit_traversal();
-        //     self.create_and_append_children(&new[shared_prefix_count..]);
-        //     return KeyedPrefixResult::Finished;
-        // }
+            // 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);
 
-        // // And if that was all of the new children, then remove all of the remaining
-        // // old children and we're finished.
-        // if shared_prefix_count == new.len() {
-        //     self.edits.go_to_sibling(shared_prefix_count);
-        //     // self.edits.commit_traversal();
-        //     self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
-        //     return KeyedPrefixResult::Finished;
-        // }
-
-        // self.edits.go_up();
-        // KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
-    }
+            return KeyedPrefixResult::Finished;
+        }
 
-    // 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.
-    pub fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) {
-        // debug_assert!(self.edits.traversal_is_committed());
-        log::debug!("REMOVING CHILDREN");
-        for _child in old {
-            // registry.remove_subtree(child);
+        // And if that was all of the new children, then remove all of the remaining
+        // old children and we're finished.
+        if shared_prefix_count == new.len() {
+            self.remove_children(&old[shared_prefix_count..]);
+            return KeyedPrefixResult::Finished;
         }
-        // Fast way to remove all children: set the node's textContent to an empty
-        // string.
-        todo!()
-        // self.edits.set_inner_text("");
+
+        KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
     }
 
     // Create the given children and append them to the parent node.
@@ -871,8 +794,8 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     // 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(child);
-            self.edits.append_children(meta.added_to_stack);
+            let meta = self.create_vnode(child);
+            self.edit_append_children(meta.added_to_stack);
         }
     }
 
@@ -884,23 +807,22 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     // this subsequence will remain in place, minimizing the number of DOM moves we
     // will have to do.
     //
-    // Upon entry to this function, the change list stack must be:
+    // Upon entry to this function, the change list stack must be empty.
     //
-    //     [... parent]
+    // 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.
     fn diff_keyed_middle(
-        &self,
-        _old: &[VNode<'bump>],
-        _new: &[VNode<'bump>],
-        _shared_prefix_count: usize,
-        _shared_suffix_count: usize,
-        _old_shared_suffix_start: usize,
+        &mut self,
+        old: &'bump [VNode<'bump>],
+        mut new: &'bump [VNode<'bump>],
+        shared_prefix_count: usize,
+        shared_suffix_count: usize,
+        old_shared_suffix_start: usize,
     ) {
-        todo!()
-        // // 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.last().map(|n| n.key()), old.last().map(|o| o.key()));
+        // 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.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
@@ -909,205 +831,203 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
         // // in memory.
         // debug_assert!(new.len() < u32::MAX as usize);
 
-        // // Map from each `old` node's key to its index within `old`.
-        // let mut old_key_to_old_index = FxHashMap::default();
-        // old_key_to_old_index.reserve(old.len());
-        // old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
-
-        // // The set of shared keys between `new` and `old`.
-        // let mut shared_keys = 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 = Vec::with_capacity(new.len());
-        // new_index_to_old_index.extend(new.iter().map(|n| {
-        //     let key = n.key();
-        //     if let Some(&i) = old_key_to_old_index.get(&key) {
-        //         shared_keys.insert(key);
-        //         i
-        //     } else {
-        //         u32::MAX as usize
-        //     }
-        // }));
-
-        // // 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 shared_prefix_count == 0 {
-        //         // self.edits.commit_traversal();
-        //         self.remove_all_children(old);
-        //     } else {
-        //         self.edits.go_down_to_child(shared_prefix_count);
-        //         // self.edits.commit_traversal();
-        //         self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
-        //     }
-
-        //     self.create_and_append_children(new);
-
-        //     return;
-        // }
+        // Map from each `old` node's key to its index within `old`.
+        // 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
+            .iter()
+            .enumerate()
+            .map(|(i, o)| (o.key().unwrap(), i))
+            .collect::<FxHashMap<_, _>>();
+
+        // The set of shared keys between `new` and `old`.
+        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
+            .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
+                    }
+                }
+            })
+            .collect::<Vec<_>>();
 
-        // // Save each of the old children whose keys are reused in the new
-        // // children.
-        // let mut old_index_to_temp = vec![u32::MAX; old.len()];
-        // let mut start = 0;
-        // loop {
-        //     let end = (start..old.len())
-        //         .find(|&i| {
-        //             let key = old[i].key();
-        //             !shared_keys.contains(&key)
-        //         })
-        //         .unwrap_or(old.len());
-
-        //     if end - start > 0 {
-        //         // self.edits.commit_traversal();
-        //         let mut t = self.edits.save_children_to_temporaries(
-        //             shared_prefix_count + start,
-        //             shared_prefix_count + end,
-        //         );
-        //         for i in start..end {
-        //             old_index_to_temp[i] = t;
-        //             t += 1;
-        //         }
-        //     }
+        dbg!(&shared_keys);
+        dbg!(&to_add);
 
-        //     debug_assert!(end <= old.len());
-        //     if end == old.len() {
-        //         break;
-        //     } else {
-        //         start = end + 1;
-        //     }
-        // }
+        // 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() {
+            self.replace_and_create_many_with_many(old, new);
+            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.
-        // let mut removed_count = 0;
-        // for (i, old_child) in old.iter().enumerate().rev() {
+        // for old_child in old.iter().rev() {
         //     if !shared_keys.contains(&old_child.key()) {
-        //         // registry.remove_subtree(old_child);
-        //         // todo
-        //         // self.edits.commit_traversal();
-        //         self.edits.remove_child(i + shared_prefix_count);
-        //         removed_count += 1;
+        //         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());
-        // let mut predecessors = vec![0; new_index_to_old_index.len()];
-        // let mut starts = vec![0; new_index_to_old_index.len()];
-        // longest_increasing_subsequence::lis_with(
-        //     &new_index_to_old_index,
-        //     &mut new_index_is_in_lis,
-        //     |a, b| a < b,
-        //     &mut predecessors,
-        //     &mut starts,
-        // );
-
-        // // Now we will iterate from the end of the new children back to the
-        // // beginning, diffing old children we are reusing and if they aren't in the
-        // // LIS moving them to their new destination, or creating new children. Note
-        // // that iterating in reverse order lets us use `Node.prototype.insertBefore`
-        // // to move/insert children.
-        // //
-        // // But first, we ensure that we have a child on the change list stack that
-        // // we can `insertBefore`. We handle this once before looping over `new`
-        // // children, so that we don't have to keep checking on every loop iteration.
-        // if shared_suffix_count > 0 {
-        //     // There is a shared suffix after these middle children. We will be
-        //     // inserting before that shared suffix, so add the first child of that
-        //     // shared suffix to the change list stack.
-        //     //
-        //     // [... parent]
-        //     self.edits
-        //         .go_down_to_child(old_shared_suffix_start - removed_count);
-        // // [... parent first_child_of_shared_suffix]
-        // } else {
-        //     // There is no shared suffix coming after these middle children.
-        //     // Therefore we have to process the last child in `new` and move it to
-        //     // the end of the parent's children if it isn't already there.
-        //     let last_index = new.len() - 1;
-        //     // uhhhh why an unwrap?
-        //     let last = new.last().unwrap();
-        //     // let last = new.last().unwrap_throw();
-        //     new = &new[..new.len() - 1];
-        //     if shared_keys.contains(&last.key()) {
-        //         let old_index = new_index_to_old_index[last_index];
-        //         let temp = old_index_to_temp[old_index];
-        //         // [... parent]
-        //         self.edits.go_down_to_temp_child(temp);
-        //         // [... parent last]
-        //         self.diff_node(&old[old_index], last);
-
-        //         if new_index_is_in_lis.contains(&last_index) {
-        //             // Don't move it, since it is already where it needs to be.
-        //         } else {
-        //             // self.edits.commit_traversal();
-        //             // [... parent last]
-        //             self.edits.append_child();
-        //             // [... parent]
-        //             self.edits.go_down_to_temp_child(temp);
-        //             // [... parent last]
-        //         }
-        //     } else {
-        //         // self.edits.commit_traversal();
-        //         // [... parent]
-        //         self.create(last);
-
-        //         // [... parent last]
-        //         self.edits.append_child();
-        //         // [... parent]
-        //         self.edits.go_down_to_reverse_child(0);
-        //         // [... parent last]
-        //     }
-        // }
+        // 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());
+
+        let mut predecessors = vec![0; new_index_to_old_index.len()];
+        let mut starts = vec![0; new_index_to_old_index.len()];
+
+        longest_increasing_subsequence::lis_with(
+            &new_index_to_old_index,
+            &mut new_index_is_in_lis,
+            |a, b| a < b,
+            &mut predecessors,
+            &mut starts,
+        );
 
-        // for (new_index, new_child) in new.iter().enumerate().rev() {
-        //     let old_index = new_index_to_old_index[new_index];
-        //     if old_index == u32::MAX as usize {
-        //         debug_assert!(!shared_keys.contains(&new_child.key()));
-        //         // self.edits.commit_traversal();
-        //         // [... parent successor]
-        //         self.create(new_child);
-        //         // [... parent successor new_child]
-        //         self.edits.insert_before();
-        //     // [... parent new_child]
-        //     } else {
-        //         debug_assert!(shared_keys.contains(&new_child.key()));
-        //         let temp = old_index_to_temp[old_index];
-        //         debug_assert_ne!(temp, u32::MAX);
-
-        //         if new_index_is_in_lis.contains(&new_index) {
-        //             // [... parent successor]
-        //             self.edits.go_to_temp_sibling(temp);
-        //         // [... parent new_child]
-        //         } else {
-        //             // self.edits.commit_traversal();
-        //             // [... parent successor]
-        //             self.edits.push_temporary(temp);
-        //             // [... parent successor new_child]
-        //             self.edits.insert_before();
-        //             // [... parent new_child]
-        //         }
-
-        //         self.diff_node(&old[old_index], new_child);
-        //     }
-        // }
+        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);
 
-        // // [... parent child]
-        // self.edits.go_up();
-        // [... parent]
+        // we walk front to back, creating the head node
+
+        // 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);
+        }
+
+        // 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 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);
+                    }
+                }
+            }
+
+            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);
+                }
+            } 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);
+            }
+        }
+        self.edit_pop();
     }
 
     // Diff the suffix of keyed children that share the same keys in the same order.
@@ -1118,26 +1038,17 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     //
     // When this function exits, the change list stack remains the same.
     fn diff_keyed_suffix(
-        &self,
-        _old: &[VNode<'bump>],
-        _new: &[VNode<'bump>],
-        _new_shared_suffix_start: usize,
+        &mut self,
+        old: &'bump [VNode<'bump>],
+        new: &'bump [VNode<'bump>],
+        new_shared_suffix_start: usize,
     ) {
-        todo!()
-        //     debug_assert_eq!(old.len(), new.len());
-        //     debug_assert!(!old.is_empty());
-
-        //     // [... parent]
-        //     self.edits.go_down();
-        //     // [... parent new_child]
-
-        //     for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
-        //         self.edits.go_to_sibling(new_shared_suffix_start + i);
-        //         self.diff_node(old_child, new_child);
-        //     }
+        debug_assert_eq!(old.len(), new.len());
+        debug_assert!(!old.is_empty());
 
-        //     // [... parent]
-        //     self.edits.go_up();
+        for (old_child, new_child) in old.iter().zip(new.iter()) {
+            self.diff_node(old_child, new_child);
+        }
     }
 
     // Diff children that are not keyed.
@@ -1150,72 +1061,79 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     // 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());
 
-        //     [... parent]
-        // self.edits.go_down();
-        // self.edits.push_root()
-        //     [... parent child]
-
-        // todo!()
-        for (_i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
-            // [... parent prev_child]
-            // self.edits.go_to_sibling(i);
-            // [... parent this_child]
-
-            // let did = old_child.get_mounted_id(self.components).unwrap();
-            // if did.0 == 0 {
-            //     log::debug!("Root is bad: {:#?}", old_child);
-            // }
-            // self.edits.push_root(did);
-            self.diff_node(old_child, new_child);
+        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);
+                }
 
-            // let old_id = old_child.get_mounted_id(self.components).unwrap();
-            // let new_id = new_child.get_mounted_id(self.components).unwrap();
-
-            // log::debug!(
-            //     "pushed root. {:?}, {:?}",
-            //     old_child.get_mounted_id(self.components).unwrap(),
-            //     new_child.get_mounted_id(self.components).unwrap()
-            // );
-            // if old_id != new_id {
-            //     log::debug!("Mismatch: {:?}", 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();
+                    }
+                }
+            }
 
-        // match old.len().cmp(&new.len()) {
-        //     // old.len > new.len -> removing some nodes
-        //     Ordering::Greater => {
-        //         // [... parent prev_child]
-        //         self.edits.go_to_sibling(new.len());
-        //         // [... parent first_child_to_remove]
-        //         // self.edits.commit_traversal();
-        //         // support::remove_self_and_next_siblings(state, &old[new.len()..]);
-        //         self.remove_self_and_next_siblings(&old[new.len()..]);
-        //         // [... parent]
-        //     }
-        //     // old.len < new.len -> adding some nodes
-        //     Ordering::Less => {
-        //         // [... parent last_child]
-        //         self.edits.go_up();
-        //         // [... parent]
-        //         // self.edits.commit_traversal();
-        //         self.create_and_append_children(&new[old.len()..]);
-        //     }
-        //     // old.len == new.len -> no nodes added/removed, but πerhaps changed
-        //     Ordering::Equal => {
-        //         // [... parent child]
-        //         self.edits.go_up();
-        //         // [... parent]
-        //     }
-        // }
+            // 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);
+                }
+            }
+        }
     }
 
     // ======================
     // 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);
+        }
+        // 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:
@@ -1225,34 +1143,350 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     // After the function returns, the child is no longer on the change list stack:
     //
     //     [... parent]
-    pub fn remove_self_and_next_siblings(&self, old: &[VNode<'bump>]) {
-        // debug_assert!(self.edits.traversal_is_committed());
-        for child in old {
-            if let VNodeKind::Component(_vcomp) = child.kind {
-                // dom
-                //     .create_text_node("placeholder for vcomponent");
-
-                todo!()
-                // let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
-                // self.lifecycle_events.push_back(LifeCycleEvent::Remove {
-                //     root_id,
-                //     stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
-                // })
-                // let id = get_id();
-                // *component.stable_addr.as_ref().borrow_mut() = Some(id);
-                // self.edits.save_known_root(id);
-                // let scope = Rc::downgrade(&component.ass_scope);
-                // self.lifecycle_events.push_back(LifeCycleEvent::Mount {
-                //     caller: Rc::downgrade(&component.caller),
-                //     root_id: id,
-                //     stable_scope_addr: scope,
-                // });
+    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> {
+        let mut search_node = Some(vnode);
+
+        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,
+
+                VNodeKind::Fragment(frag) => {
+                    search_node = frag.children.last();
+                }
+                VNodeKind::Component(el) => {
+                    let scope_id = el.ass_scope.get().unwrap();
+                    let scope = self.get_scope(&scope_id).unwrap();
+                    search_node = Some(scope.root());
+                }
             }
+        }
+    }
 
-            // registry.remove_subtree(child);
+    fn find_first_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> {
+        let mut search_node = Some(vnode);
+
+        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,
+
+                VNodeKind::Fragment(frag) => {
+                    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();
+                    search_node = Some(scope.root());
+                }
+            }
+        }
+    }
+
+    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(
+        &mut self,
+        old_nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
+        new_nodes: impl IntoIterator<Item = &'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);
+                    }
+                }
+
+                // 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();
+                    let root = scope.root();
+                    nodes_to_search.push(root);
+                    scopes_obliterated.push(scope_id);
+                }
+            }
+            // 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);
+        }
+    }
+
+    fn create_garbage(&mut self, node: &'bump VNode<'bump>) {
+        match self.current_scope().and_then(|id| self.get_scope(&id)) {
+            Some(scope) => {
+                let garbage: &'bump VNode<'static> = unsafe { std::mem::transmute(node) };
+                scope.pending_garbage.borrow_mut().push(garbage);
+            }
+            None => {
+                log::info!("No scope to collect garbage into")
+            }
+        }
+    }
+
+    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,
         }
-        todo!()
-        // self.edits.remove_self_and_next_siblings();
     }
 }
 
@@ -1265,12 +1499,26 @@ enum KeyedPrefixResult {
     MoreWorkToDo(usize),
 }
 
-/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
+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);
+        }
+    }
+
+    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".
-struct RealChildIterator<'a> {
-    scopes: &'a SharedArena,
+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
@@ -1279,20 +1527,25 @@ struct RealChildIterator<'a> {
 }
 
 impl<'a> RealChildIterator<'a> {
-    fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
+    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 = RealDomNode;
+    type Item = &'a VNode<'a>;
 
-    fn next(&mut self) -> Option<RealDomNode> {
+    fn next(&mut self) -> Option<&'a VNode<'a>> {
         let mut should_pop = false;
-        let mut returned_node = None;
+        let mut returned_node: Option<&'a VNode<'a>> = None;
         let mut should_push = None;
 
         while returned_node.is_none() {
@@ -1304,7 +1557,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
                         // 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.dom_id.get());
+                        returned_node = Some(&*node);
                     }
 
                     // If we get a fragment we push the next child
@@ -1313,7 +1566,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
 
                         if frag.children.len() == 0 {
                             should_pop = true;
-                            returned_node = Some(node.dom_id.get());
+                            returned_node = Some(&*node);
                         }
 
                         if subcount >= frag.children.len() {
@@ -1322,17 +1575,40 @@ impl<'a> Iterator for RealChildIterator<'a> {
                             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 => should_pop = true,
-                    VNodeKind::Suspended { .. } => todo!(),
+                    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 = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
+                        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.root();
+                        *node = scope.frames.fin_head();
                     }
                 }
             } else {

+ 35 - 124
packages/core/src/editor.rs

@@ -5,128 +5,7 @@
 //!
 //!
 
-use crate::{innerlude::ScopeId, RealDomNode};
-
-/// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes.
-///
-/// However, the DomEditor only builds a change list - it does not apply them. In contrast with the "RealDom", the DomEditor
-/// is cancellable and flushable. At any moment in time, Dioxus may choose to completely clear the edit list and start over.
-///
-/// This behavior is used in the cooperative scheduling algorithm.
-pub struct DomEditor<'real, 'bump> {
-    pub edits: &'real mut Vec<DomEdit<'bump>>,
-}
-
-use DomEdit::*;
-impl<'real, 'bump> DomEditor<'real, 'bump> {
-    pub fn new(edits: &'real mut Vec<DomEdit<'bump>>) -> Self {
-        Self { edits }
-    }
-
-    // Navigation
-    pub(crate) fn push(&mut self, root: RealDomNode) {
-        let id = root.as_u64();
-        self.edits.push(PushRoot { id });
-    }
-
-    #[inline]
-    pub(crate) fn pop(&mut self) {
-        self.edits.push(PopRoot {});
-    }
-
-    // Add Nodes to the dom
-    // add m nodes from the stack
-    #[inline]
-    pub(crate) fn append_children(&mut self, many: u32) {
-        self.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
-    #[inline]
-    pub(crate) fn replace_with(&mut self, many: u32) {
-        self.edits.push(ReplaceWith { many });
-    }
-
-    // Remove Nodesfrom the dom
-    #[inline]
-    pub(crate) fn remove(&mut self) {
-        self.edits.push(Remove);
-    }
-
-    #[inline]
-    pub(crate) fn remove_all_children(&mut self) {
-        self.edits.push(RemoveAllChildren);
-    }
-
-    // Create
-    #[inline]
-    pub(crate) fn create_text_node(&mut self, text: &'bump str, id: RealDomNode) {
-        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: RealDomNode,
-    ) {
-        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: RealDomNode) {
-        let id = id.as_u64();
-        self.edits.push(CreatePlaceholder { id });
-    }
-
-    // events
-    pub(crate) fn new_event_listener(
-        &mut self,
-        event: &'static str,
-        scope: ScopeId,
-        element_id: usize,
-        realnode: RealDomNode,
-    ) {
-        self.edits.push(NewEventListener {
-            scope,
-            event_name: event,
-            element_id,
-            mounted_node_id: realnode.as_u64(),
-        });
-    }
-
-    #[inline]
-    pub(crate) fn remove_event_listener(&mut self, event: &'static str) {
-        self.edits.push(RemoveEventListener { event });
-    }
-
-    // modify
-    #[inline]
-    pub(crate) fn set_text(&mut self, text: &'bump str) {
-        self.edits.push(SetText { text });
-    }
-
-    #[inline]
-    pub(crate) fn set_attribute(
-        &mut self,
-        field: &'static str,
-        value: &'bump str,
-        ns: Option<&'static str>,
-    ) {
-        self.edits.push(SetAttribute { field, value, ns });
-    }
-
-    #[inline]
-    pub(crate) fn remove_attribute(&mut self, name: &'static str) {
-        self.edits.push(RemoveAttribute { name });
-    }
-}
+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.
@@ -145,7 +24,17 @@ pub enum DomEdit<'bump> {
         many: u32,
     },
     ReplaceWith {
-        many: u32,
+        // the first n elements
+        n: u32,
+
+        // the last m elements
+        m: u32,
+    },
+    InsertAfter {
+        n: u32,
+    },
+    InsertBefore {
+        n: u32,
     },
     Remove,
     RemoveAllChildren,
@@ -169,7 +58,6 @@ pub enum DomEdit<'bump> {
         event_name: &'static str,
         scope: ScopeId,
         mounted_node_id: u64,
-        element_id: usize,
     },
     RemoveEventListener {
         event: &'static str,
@@ -186,3 +74,26 @@ pub enum DomEdit<'bump> {
         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",
+        }
+    }
+}

+ 160 - 62
packages/core/src/events.rs

@@ -4,9 +4,15 @@
 //! 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.
 
-use std::{cell::Cell, rc::Rc};
+use std::{
+    cell::{Cell, RefCell},
+    rc::Rc,
+};
 
-use crate::innerlude::{RealDomNode, ScopeId};
+use crate::{
+    innerlude::{ElementId, ScopeId},
+    VNode,
+};
 
 #[derive(Debug)]
 pub struct EventTrigger {
@@ -14,7 +20,7 @@ pub struct EventTrigger {
     pub originator: ScopeId,
 
     /// The optional real node associated with the trigger
-    pub real_node_id: Option<RealDomNode>,
+    pub real_node_id: Option<ElementId>,
 
     /// The type of event
     pub event: VirtualEvent,
@@ -23,14 +29,25 @@ pub struct EventTrigger {
     pub priority: EventPriority,
 }
 
-impl EventTrigger {
-    pub fn new_from_task(originator: ScopeId, hook_idx: usize) -> Self {
-        Self {
-            originator,
-            event: VirtualEvent::AsyncEvent { hook_idx },
-            priority: EventPriority::Low,
-            real_node_id: None,
-        }
+#[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
+}
+
+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!()
     }
 }
 
@@ -41,17 +58,18 @@ impl EventTrigger {
 /// 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)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
 pub enum EventPriority {
-    /// "Immediate" work will interrupt whatever work is currently being done and force its way through. This type of work
-    /// is typically reserved for small changes to single elements.
+    /// 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 primary user of the "Immediate" priority is the `Signal` API which performs surgical mutations to the DOM.
-    Immediate,
+    /// Garbage collection mixes with the safety aspects of the virtualdom so it's very important to get it done before
+    /// other work.
+    GarbageCollection,
 
     /// "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,
 
@@ -72,7 +90,7 @@ impl EventTrigger {
     pub fn new(
         event: VirtualEvent,
         scope: ScopeId,
-        mounted_dom_id: Option<RealDomNode>,
+        mounted_dom_id: Option<ElementId>,
         priority: EventPriority,
     ) -> Self {
         Self {
@@ -84,8 +102,47 @@ impl EventTrigger {
     }
 }
 
-#[derive(Debug)]
 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
     ClipboardEvent(on::ClipboardEvent),
     CompositionEvent(on::CompositionEvent),
@@ -102,25 +159,60 @@ pub enum VirtualEvent {
     ToggleEvent(on::ToggleEvent),
     MouseEvent(on::MouseEvent),
     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,
+        }
+    }
+}
 
-    // image event has conflicting method types
-    // ImageEvent(event_data::ImageEvent),
-
-    // 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.
-    AsyncEvent {
-        hook_idx: usize,
-    },
-
-    // These are more intrusive than the rest
-    SuspenseEvent {
-        hook_idx: usize,
-        domnode: Rc<Cell<RealDomNode>>,
-    },
+impl std::fmt::Debug for VirtualEvent {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        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",
+        };
 
-    OtherEvent,
+        f.debug_struct("VirtualEvent").field("type", &name).finish()
+    }
 }
 
 pub mod on {
@@ -134,11 +226,12 @@ pub mod on {
     //!
 
     #![allow(unused)]
-    use std::{fmt::Debug, ops::Deref, rc::Rc};
+    use bumpalo::boxed::Box as BumpBox;
+    use std::{cell::RefCell, fmt::Debug, ops::Deref, rc::Rc};
 
     use crate::{
         innerlude::NodeFactory,
-        innerlude::{Attribute, Listener, RealDomNode, VNode},
+        innerlude::{Attribute, ElementId, Listener, VNode},
     };
     use std::cell::Cell;
 
@@ -156,7 +249,6 @@ pub mod on {
         )* ) => {
             $(
                 $(#[$attr])*
-                #[derive(Debug)]
                 pub struct $wrapper(pub Rc<dyn $eventdata>);
 
                 // todo: derefing to the event is fine (and easy) but breaks some IDE stuff like (go to source)
@@ -178,14 +270,20 @@ pub mod on {
                         where F: FnMut($wrapper) + 'a
                     {
                         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 callback: BumpBox<dyn FnMut(VirtualEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
+
+                        let event_name = stringify!($name);
+                        let shortname: &'static str = &event_name[2..];
                         Listener {
-                            event: stringify!($name),
-                            mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
-                            scope: c.scope_ref.our_arena_idx,
-                            callback: bump.alloc(move |evt: VirtualEvent| match evt {
-                                VirtualEvent::$wrapper(event) => callback(event),
-                                _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
-                            }),
+                            event: shortname,
+                            mounted_node: Cell::new(None),
+                            callback: RefCell::new(Some(callback)),
                         }
                     }
                 )*
@@ -504,15 +602,15 @@ pub mod on {
         fn time_stamp(&self) -> usize;
     }
 
-    pub trait ClipboardEventInner: Debug + GenericEventInner {
+    pub trait ClipboardEventInner {
         // DOMDataTransfer clipboardData
     }
 
-    pub trait CompositionEventInner: Debug {
+    pub trait CompositionEventInner {
         fn data(&self) -> String;
     }
 
-    pub trait KeyboardEventInner: Debug {
+    pub trait KeyboardEventInner {
         fn char_code(&self) -> u32;
 
         /// Get the key code as an enum Variant.
@@ -569,15 +667,15 @@ pub mod on {
         fn get_modifier_state(&self, key_code: usize) -> bool;
     }
 
-    pub trait FocusEventInner: Debug {
+    pub trait FocusEventInner {
         /* DOMEventInnerTarget relatedTarget */
     }
 
-    pub trait FormEventInner: Debug {
+    pub trait FormEventInner {
         fn value(&self) -> String;
     }
 
-    pub trait MouseEventInner: Debug {
+    pub trait MouseEventInner {
         fn alt_key(&self) -> bool;
         fn button(&self) -> i16;
         fn buttons(&self) -> u16;
@@ -594,7 +692,7 @@ pub mod on {
         fn get_modifier_state(&self, key_code: &str) -> bool;
     }
 
-    pub trait PointerEventInner: Debug {
+    pub trait PointerEventInner {
         // Mouse only
         fn alt_key(&self) -> bool;
         fn button(&self) -> usize;
@@ -621,9 +719,9 @@ pub mod on {
         fn is_primary(&self) -> bool;
     }
 
-    pub trait SelectionEventInner: Debug {}
+    pub trait SelectionEventInner {}
 
-    pub trait TouchEventInner: Debug {
+    pub trait TouchEventInner {
         fn alt_key(&self) -> bool;
         fn ctrl_key(&self) -> bool;
         fn meta_key(&self) -> bool;
@@ -634,37 +732,37 @@ pub mod on {
         // touches: DOMTouchList,
     }
 
-    pub trait UIEventInner: Debug {
+    pub trait UIEventInner {
         // DOMAbstractView view
         fn detail(&self) -> i32;
     }
 
-    pub trait WheelEventInner: Debug {
+    pub trait WheelEventInner {
         fn delta_mode(&self) -> i32;
         fn delta_x(&self) -> i32;
         fn delta_y(&self) -> i32;
         fn delta_z(&self) -> i32;
     }
 
-    pub trait MediaEventInner: Debug {}
+    pub trait MediaEventInner {}
 
-    pub trait ImageEventInner: Debug {
+    pub trait ImageEventInner {
         //     load error
     }
 
-    pub trait AnimationEventInner: Debug {
+    pub trait AnimationEventInner {
         fn animation_name(&self) -> String;
         fn pseudo_element(&self) -> String;
         fn elapsed_time(&self) -> f32;
     }
 
-    pub trait TransitionEventInner: Debug {
+    pub trait TransitionEventInner {
         fn property_name(&self) -> String;
         fn pseudo_element(&self) -> String;
         fn elapsed_time(&self) -> f32;
     }
 
-    pub trait ToggleEventInner: Debug {}
+    pub trait ToggleEventInner {}
 
     pub use util::KeyCode;
     mod util {

+ 14 - 5
packages/core/src/heuristics.rs

@@ -1,14 +1,23 @@
 use std::collections::HashMap;
 
+use fxhash::FxHashMap;
+
 use crate::FC;
 
-pub(crate) struct HeuristicsEngine {
-    heuristics: HashMap<FcSlot, Heuristic>,
+/// Provides heuristics to the "SharedResources" object for improving allocation performance.
+///
+/// This heueristic engine records the memory footprint of bump arenas and hook lists for each component. These records are
+/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
+/// that would slow down the diffing and initializion process.
+///
+///
+pub struct HeuristicsEngine {
+    heuristics: FxHashMap<FcSlot, Heuristic>,
 }
 
-pub(crate) type FcSlot = *const ();
+pub type FcSlot = *const ();
 
-pub(crate) struct Heuristic {
+pub struct Heuristic {
     hooks: u32,
     bump_size: u64,
 }
@@ -16,7 +25,7 @@ pub(crate) struct Heuristic {
 impl HeuristicsEngine {
     pub(crate) fn new() -> Self {
         Self {
-            heuristics: HashMap::new(),
+            heuristics: FxHashMap::default(),
         }
     }
 

+ 258 - 0
packages/core/src/hooks.rs

@@ -0,0 +1,258 @@
+//! Built-in hooks
+//!
+//! This module contains all the low-level built-in hooks that require 1st party support to work.
+//!
+//! Hooks:
+//! - use_hook
+//! - use_state_provider
+//! - use_state_consumer
+//! - use_task
+//! - use_suspense
+
+use crate::innerlude::*;
+use futures_util::FutureExt;
+use std::{
+    any::{Any, TypeId},
+    cell::{Cell, RefCell},
+    future::Future,
+    rc::Rc,
+};
+
+/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
+///
+/// This is a hook, so it may not be called conditionally!
+///
+/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
+/// so don't put it in a conditional.
+///
+/// When the component is dropped, so is the context. Be aware of this behavior when consuming
+/// the context via Rc/Weak.
+///
+///
+///
+pub fn use_provide_state<'src, Pr, T, F>(cx: Context<'src, Pr>, init: F) -> &'src Rc<T>
+where
+    T: 'static,
+    F: FnOnce() -> T,
+{
+    let ty = TypeId::of::<T>();
+    let contains_key = cx.scope.shared_contexts.borrow().contains_key(&ty);
+
+    let is_initialized = cx.use_hook(
+        |_| false,
+        |s| {
+            let i = s.clone();
+            *s = true;
+            i
+        },
+        |_| {},
+    );
+
+    match (is_initialized, contains_key) {
+        // Do nothing, already initialized and already exists
+        (true, true) => {}
+
+        // 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,
+            );
+        }
+
+        _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
+    };
+
+    use_consume_state::<T, _>(cx)
+}
+
+/// There are hooks going on here!
+pub fn use_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> &'src Rc<T> {
+    use_try_consume_state::<T, _>(cx).unwrap()
+}
+
+/// Uses a context, storing the cached value around
+///
+/// If a context is not found on the first search, then this call will be  "dud", always returning "None" even if a
+/// context was added later. This allows using another hook as a fallback
+///
+pub fn use_try_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> Option<&'src Rc<T>> {
+    struct UseContextHook<C>(Option<Rc<C>>);
+
+    cx.use_hook(
+        move |_| UseContextHook(cx.consume_shared_state::<T>()),
+        move |hook| hook.0.as_ref(),
+        |_| {},
+    )
+}
+
+/// Awaits the given task, forcing the component to re-render when the value is ready.
+///
+///
+///
+///
+pub fn use_task<'src, Out, Fut, Init, P>(
+    cx: Context<'src, P>,
+    task_initializer: Init,
+) -> (&'src TaskHandle, &'src Option<Out>)
+where
+    Out: 'static,
+    Fut: Future<Output = Out> + 'static,
+    Init: FnOnce() -> Fut + 'src,
+{
+    struct TaskHook<T> {
+        handle: TaskHandle,
+        task_dump: Rc<RefCell<Option<T>>>,
+        value: Option<T>,
+    }
+
+    // whenever the task is complete, save it into th
+    cx.use_hook(
+        move |_| {
+            let task_fut = task_initializer();
+
+            let task_dump = Rc::new(RefCell::new(None));
+
+            let slot = task_dump.clone();
+
+            let updater = cx.prepare_update();
+            let update_id = cx.get_scope_id();
+
+            let originator = cx.scope.our_arena_idx.clone();
+
+            let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
+                *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,
+                }
+            })));
+
+            TaskHook {
+                task_dump,
+                value: None,
+                handle,
+            }
+        },
+        |hook| {
+            if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
+                hook.value = Some(val);
+            }
+            (&hook.handle, &hook.value)
+        },
+        |_| {},
+    )
+}
+
+/// Asynchronously render new nodes once the given future has completed.
+///
+/// # Easda
+///
+///
+///
+///
+/// # Example
+///
+///
+pub fn use_suspense<'src, Out, Fut, Cb, P>(
+    cx: Context<'src, P>,
+    task_initializer: impl FnOnce() -> Fut,
+    user_callback: Cb,
+) -> DomTree<'src>
+where
+    Fut: Future<Output = Out> + 'static,
+    Out: 'static,
+    Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
+{
+    cx.use_hook(
+        move |hook_idx| {
+            let value = Rc::new(RefCell::new(None));
+
+            let dom_node_id = Rc::new(empty_cell());
+            let domnode = dom_node_id.clone();
+
+            let slot = 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(),
+                            }),
+                        })
+                    }
+                }
+            });
+
+            let originator = cx.scope.our_arena_idx.clone();
+            let task_fut = task_initializer();
+            let domnode = dom_node_id.clone();
+
+            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,
+                }
+            })));
+
+            SuspenseHook {
+                value,
+                callback,
+                dom_node_id,
+            }
+        },
+        move |hook| {
+            let cx = Context {
+                scope: &cx.scope,
+                props: &(),
+            };
+            let csx = SuspendedContext { inner: cx };
+            (&hook.callback)(csx)
+        },
+        |_| {},
+    )
+}
+
+pub(crate) struct SuspenseHook {
+    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>>;
+pub struct SuspendedContext<'a> {
+    pub(crate) inner: Context<'a, ()>,
+}
+impl<'src> SuspendedContext<'src> {
+    pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
+        self,
+        lazy_nodes: LazyNodes<'src, F>,
+    ) -> DomTree<'src> {
+        let scope_ref = self.inner.scope;
+        let bump = &self.inner.scope.frames.wip_frame().bump;
+
+        Some(lazy_nodes.into_vnode(NodeFactory { bump }))
+    }
+}

+ 7 - 5
packages/core/src/lib.rs

@@ -1,4 +1,5 @@
 #![allow(non_snake_case)]
+#![doc = include_str!("../README.md")]
 //! Dioxus Core
 //! ----------
 //!
@@ -10,14 +11,15 @@
 //!
 
 pub use crate::innerlude::{
-    format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, EventTrigger, LazyNodes,
-    NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom,
-    VirtualEvent, FC,
+    format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
+    EventTrigger, LazyNodes, NodeFactory, Properties, ScopeId, SuspendedContext, VNode, VNodeKind,
+    VirtualDom, VirtualEvent, 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, NodeFactory, FC};
     pub use crate::nodes::VNode;
     pub use crate::VirtualDom;
@@ -36,9 +38,9 @@ pub(crate) mod innerlude {
     pub use crate::events::*;
     pub use crate::heuristics::*;
     pub use crate::hooklist::*;
+    pub use crate::hooks::*;
     pub use crate::nodes::*;
     pub use crate::scope::*;
-    pub use crate::tasks::*;
     pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
@@ -63,9 +65,9 @@ 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 tasks;
 pub mod util;
 pub mod virtual_dom;

+ 233 - 117
packages/core/src/nodes.rs

@@ -5,24 +5,40 @@
 //! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
 use crate::{
     events::VirtualEvent,
-    innerlude::{Context, DomTree, Properties, RealDomNode, Scope, ScopeId, FC},
+    innerlude::{empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, FC},
 };
+use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
-    cell::Cell,
+    cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
     marker::PhantomData,
+    mem::ManuallyDrop,
     rc::Rc,
 };
 
 pub struct VNode<'src> {
     pub kind: VNodeKind<'src>,
-    pub(crate) dom_id: Cell<RealDomNode>,
+
     pub(crate) key: Option<&'src str>,
 }
-impl VNode<'_> {
-    fn key(&self) -> Option<&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
@@ -39,18 +55,24 @@ pub enum VNodeKind<'src> {
 
     Component(&'src VComponent<'src>),
 
-    Suspended { node: Rc<Cell<RealDomNode>> },
+    Suspended(VSuspended),
+
+    Anchor(VAnchor),
+}
+
+pub struct VAnchor {
+    pub dom_id: Cell<Option<ElementId>>,
 }
 
 pub struct VText<'src> {
     pub text: &'src str,
+    pub dom_id: Cell<Option<ElementId>>,
     pub is_static: bool,
 }
 
 pub struct VFragment<'src> {
     pub children: &'src [VNode<'src>],
     pub is_static: bool,
-    pub is_error: bool,
 }
 
 pub trait DioxusElement {
@@ -70,6 +92,7 @@ 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>],
@@ -103,11 +126,19 @@ pub struct Listener<'bump> {
     /// The type of event to listen for.
     pub(crate) event: &'static str,
 
-    pub scope: ScopeId,
+    pub mounted_node: Cell<Option<ElementId>>,
 
-    pub mounted_node: &'bump mut Cell<RealDomNode>,
+    pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(VirtualEvent) + 'bump>>>,
+}
 
-    pub(crate) callback: &'bump dyn FnMut(VirtualEvent),
+impl Listener<'_> {
+    // serialize the listener event stuff to a string
+    pub fn serialize(&self) {
+        //
+    }
+    pub fn deserialize() {
+        //
+    }
 }
 
 /// Virtual Components for custom user-defined components
@@ -121,8 +152,12 @@ pub struct VComponent<'src> {
 
     pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
 
+    pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
+
     pub is_static: bool,
 
+    pub can_memoize: bool,
+
     // a pointer into the bump arena (given by the 'src lifetime)
     pub(crate) raw_props: *const (),
 
@@ -130,28 +165,53 @@ pub struct VComponent<'src> {
     pub(crate) user_fc: *const (),
 }
 
+pub struct VSuspended {
+    pub node: Rc<Cell<Option<ElementId>>>,
+}
+
 /// This struct provides an ergonomic API to quickly build VNodes.
 ///
 /// NodeFactory is used to build VNodes in the component's memory space.
 /// This struct adds metadata to the final VNode about listeners, attributes, and children
 #[derive(Copy, Clone)]
 pub struct NodeFactory<'a> {
-    pub scope_ref: &'a Scope,
-    pub listener_id: &'a Cell<usize>,
+    pub(crate) bump: &'a Bump,
 }
 
 impl<'a> NodeFactory<'a> {
+    pub fn new(bump: &'a Bump) -> NodeFactory<'a> {
+        NodeFactory { bump }
+    }
+
     #[inline]
     pub fn bump(&self) -> &'a bumpalo::Bump {
-        &self.scope_ref.cur_frame().bump
+        &self.bump
+    }
+
+    pub fn render_directly<F>(&self, lazy_nodes: LazyNodes<'a, F>) -> DomTree<'a>
+    where
+        F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
+    {
+        Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump }))
+    }
+
+    pub fn unstable_place_holder() -> VNode<'static> {
+        VNode {
+            key: None,
+            kind: VNodeKind::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
-    pub fn static_text(text: &'static str) -> VNode {
+    pub fn static_text(&self, text: &'static str) -> VNode<'a> {
         VNode {
-            dom_id: RealDomNode::empty_cell(),
             key: None,
             kind: VNodeKind::Text(VText {
+                dom_id: empty_cell(),
                 text,
                 is_static: true,
             }),
@@ -178,20 +238,28 @@ impl<'a> NodeFactory<'a> {
     pub fn text(&self, args: Arguments) -> VNode<'a> {
         let (text, is_static) = self.raw_text(args);
         VNode {
-            dom_id: RealDomNode::empty_cell(),
             key: None,
-            kind: VNodeKind::Text(VText { text, is_static }),
+            kind: VNodeKind::Text(VText {
+                text,
+                is_static,
+                dom_id: empty_cell(),
+            }),
         }
     }
 
-    pub fn element(
+    pub fn element<L, A, V>(
         &self,
         el: impl DioxusElement,
-        listeners: &'a mut [Listener<'a>],
-        attributes: &'a [Attribute<'a>],
-        children: &'a [VNode<'a>],
-        key: Option<&'a str>,
-    ) -> VNode<'a> {
+        listeners: L,
+        attributes: A,
+        children: V,
+        key: Option<Arguments>,
+    ) -> VNode<'a>
+    where
+        L: 'a + AsRef<[Listener<'a>]>,
+        A: 'a + AsRef<[Attribute<'a>]>,
+        V: 'a + AsRef<[VNode<'a>]>,
+    {
         self.raw_element(
             el.tag_name(),
             el.namespace(),
@@ -202,28 +270,32 @@ impl<'a> NodeFactory<'a> {
         )
     }
 
-    pub fn raw_element(
+    pub fn raw_element<L, A, V>(
         &self,
         tag: &'static str,
         namespace: Option<&'static str>,
-        listeners: &'a mut [Listener],
-        attributes: &'a [Attribute],
-        children: &'a [VNode<'a>],
-        key: Option<&'a str>,
-    ) -> VNode<'a> {
-        // We take the references directly from the bump arena
-        // TODO: this code shouldn't necessarily be here of all places
-        // It would make more sense to do this in diffing
-
-        let mut queue = self.scope_ref.listeners.borrow_mut();
-        for listener in listeners.iter_mut() {
-            let mounted = listener.mounted_node as *mut _;
-            let callback = listener.callback as *const _ as *mut _;
-            queue.push((mounted, callback))
-        }
+        listeners: L,
+        attributes: A,
+        children: V,
+        key: Option<Arguments>,
+    ) -> VNode<'a>
+    where
+        L: 'a + AsRef<[Listener<'a>]>,
+        A: 'a + AsRef<[Attribute<'a>]>,
+        V: 'a + AsRef<[VNode<'a>]>,
+    {
+        let listeners: &'a L = self.bump().alloc(listeners);
+        let listeners = listeners.as_ref();
+
+        let attributes: &'a A = self.bump().alloc(attributes);
+        let attributes = attributes.as_ref();
+
+        let children: &'a V = self.bump().alloc(children);
+        let children = children.as_ref();
+
+        let key = key.map(|f| self.raw_text(f).0);
 
         VNode {
-            dom_id: RealDomNode::empty_cell(),
             key,
             kind: VNodeKind::Element(self.bump().alloc(VElement {
                 tag_name: tag,
@@ -231,6 +303,7 @@ impl<'a> NodeFactory<'a> {
                 listeners,
                 attributes,
                 children,
+                dom_id: empty_cell(),
 
                 // todo: wire up more constization
                 static_listeners: false,
@@ -242,11 +315,10 @@ impl<'a> NodeFactory<'a> {
 
     pub fn suspended() -> VNode<'static> {
         VNode {
-            dom_id: RealDomNode::empty_cell(),
             key: None,
-            kind: VNodeKind::Suspended {
-                node: Rc::new(RealDomNode::empty_cell()),
-            },
+            kind: VNodeKind::Suspended(VSuspended {
+                node: Rc::new(empty_cell()),
+            }),
         }
     }
 
@@ -283,31 +355,44 @@ impl<'a> NodeFactory<'a> {
         }
     }
 
-    pub fn component<P>(
+    pub fn component<P, V>(
         &self,
         component: FC<P>,
         props: P,
-        key: Option<&'a str>,
-        children: &'a [VNode<'a>],
+        key: Option<Arguments>,
+        children: V,
     ) -> VNode<'a>
     where
         P: Properties + 'a,
+        V: 'a + AsRef<[VNode<'a>]>,
     {
-        // TODO
-        // It's somewhat wrong to go about props like this
+        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 props = self.bump().alloc(props);
-        let raw_props = props as *const P as *const ();
+        let children: &'a V = bump.alloc(children);
+        let children = children.as_ref();
+
+        let props = bump.alloc(props);
 
+        let raw_props = props as *mut P as *mut ();
         let user_fc = component as *const ();
 
-        let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(self.bump().alloc_with(|| {
+        let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
             move |other: &VComponent| {
                 if user_fc == other.user_fc {
-                    let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
-                    let props_memoized = unsafe { props.memoize(&real_other) };
+                    // Safety
+                    // - We guarantee that FC<P> is the same by function pointer
+                    // - Because FC<P> is the same, then P must be the same (even with generics)
+                    // - Non-static P are autoderived to memoize as false
+                    // - This comparator is only called on a corresponding set of bumpframes
+                    let props_memoized = unsafe {
+                        let real_other: &P = &*(other.raw_props as *const _ as *const P);
+                        props.memoize(&real_other)
+                    };
+
+                    // It's only okay to memoize if there are no children and the props can be memoized
+                    // Implementing memoize is unsafe and done automatically with the props trait
                     match (props_memoized, children.len() == 0) {
                         (true, true) => true,
                         _ => false,
@@ -318,18 +403,36 @@ 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);
+
+                    has_dropped = true;
+                }
+            }
+        });
+        let drop_props = unsafe { BumpBox::from_raw(drop_props) };
+
         let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
 
+        let key = key.map(|f| self.raw_text(f).0);
+
         VNode {
             key,
-            dom_id: Cell::new(RealDomNode::empty()),
-            kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
+            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),
             })),
         }
@@ -357,38 +460,14 @@ impl<'a> NodeFactory<'a> {
         unsafe { std::mem::transmute::<_, Captured<'static>>(caller) }
     }
 
-    pub fn fragment_from_iter(
-        self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
-    ) -> VNode<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
+    pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> {
+        let children = node_iter.into_vnode_list(self);
 
-        for node in node_iter.into_iter() {
-            nodes.push(node.into_vnode(self));
-        }
-
-        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.
-                        "#,
-                    );
-                }
-            }
-        }
         VNode {
-            dom_id: RealDomNode::empty_cell(),
             key: None,
             kind: VNodeKind::Fragment(VFragment {
-                children: nodes.into_bump_slice(),
+                children,
                 is_static: false,
-                is_error: false,
             }),
         }
     }
@@ -413,6 +492,72 @@ pub trait IntoVNode<'a> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
 }
 
+pub trait IntoVNodeList<'a> {
+    fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>];
+}
+
+impl<'a, T, V> IntoVNodeList<'a> for T
+where
+    T: IntoIterator<Item = V>,
+    V: IntoVNode<'a>,
+{
+    fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] {
+        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
+
+        for node in self.into_iter() {
+            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 {
+            nodes.push(VNode {
+                kind: VNodeKind::Anchor(VAnchor {
+                    dom_id: empty_cell(),
+                }),
+                key: None,
+            });
+        }
+
+        nodes.into_bump_slice()
+    }
+}
+
+pub struct ScopeChildren<'a>(pub &'a [VNode<'a>]);
+impl Copy for ScopeChildren<'_> {}
+impl<'a> Clone for ScopeChildren<'a> {
+    fn clone(&self) -> Self {
+        ScopeChildren(self.0)
+    }
+}
+impl ScopeChildren<'_> {
+    pub unsafe fn extend_lifetime(self) -> ScopeChildren<'static> {
+        std::mem::transmute(self)
+    }
+    pub unsafe fn unextend_lfetime<'a>(self) -> ScopeChildren<'a> {
+        std::mem::transmute(self)
+    }
+}
+impl<'a> IntoVNodeList<'a> for ScopeChildren<'a> {
+    fn into_vnode_list(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] {
+        self.0
+    }
+}
+
 // For the case where a rendered VNode is passed into the rsx! macro through curly braces
 impl<'a> IntoIterator for VNode<'a> {
     type Item = VNode<'a>;
@@ -429,37 +574,6 @@ impl<'a> IntoVNode<'a> for VNode<'a> {
     }
 }
 
-// For the case where a rendered VNode is by reference passed into the rsx! macro through curly braces
-// This behavior is designed for the cx.children method where child nodes are passed by reference.
-//
-// Designed to support indexing
-impl<'a> IntoVNode<'a> for &VNode<'a> {
-    fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
-        let kind = match &self.kind {
-            VNodeKind::Element(element) => VNodeKind::Element(element),
-            VNodeKind::Text(old) => VNodeKind::Text(VText {
-                text: old.text,
-                is_static: old.is_static,
-            }),
-            VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
-                children: fragment.children,
-                is_static: fragment.is_static,
-                is_error: false,
-            }),
-            VNodeKind::Component(component) => VNodeKind::Component(component),
-
-            // todo: it doesn't make much sense to pass in suspended nodes
-            // I think this is right but I'm not too sure.
-            VNodeKind::Suspended { node } => VNodeKind::Suspended { node: node.clone() },
-        };
-        VNode {
-            kind,
-            dom_id: self.dom_id.clone(),
-            key: self.key.clone(),
-        }
-    }
-}
-
 /// A concrete type provider for closures that build VNode structures.
 ///
 /// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over
@@ -535,7 +649,7 @@ impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
 
 impl IntoVNode<'_> for &'static str {
     fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        NodeFactory::static_text(self)
+        cx.static_text(self)
     }
 }
 impl IntoVNode<'_> for Arguments<'_> {
@@ -553,8 +667,10 @@ impl Debug for NodeFactory<'_> {
 impl Debug for VNode<'_> {
     fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
         match &self.kind {
-            VNodeKind::Element(el) => write!(s, "element, {}", el.tag_name),
-            VNodeKind::Text(t) => write!(s, "text, {}", t.text),
+            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"),

+ 136 - 88
packages/core/src/scope.rs

@@ -1,6 +1,9 @@
 use crate::innerlude::*;
+use bumpalo::boxed::Box as BumpBox;
+use fxhash::FxHashSet;
 use std::{
     any::{Any, TypeId},
+    borrow::BorrowMut,
     cell::{Cell, RefCell},
     collections::{HashMap, HashSet},
     future::Future,
@@ -18,40 +21,32 @@ use std::{
 /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
 /// usecase they might have.
 pub struct Scope {
-    // Book-keeping about the arena
+    // Book-keeping about our spot in the arena
     pub(crate) parent_idx: Option<ScopeId>,
-    pub(crate) descendents: RefCell<HashSet<ScopeId>>,
     pub(crate) our_arena_idx: ScopeId,
     pub(crate) height: u32,
+    pub(crate) descendents: RefCell<FxHashSet<ScopeId>>,
 
     // Nodes
     // an internal, highly efficient storage of vnodes
+    // lots of safety condsiderations
     pub(crate) frames: ActiveFrame,
-    pub(crate) child_nodes: &'static [VNode<'static>],
     pub(crate) caller: Rc<WrappedCaller>,
+    pub(crate) child_nodes: ScopeChildren<'static>,
+    pub(crate) pending_garbage: RefCell<Vec<*const VNode<'static>>>,
 
     // Listeners
-    pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
-    pub(crate) listener_idx: Cell<usize>,
+    pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
+    pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
 
     // State
     pub(crate) hooks: HookList,
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
-    // Events
-    pub(crate) event_channel: Rc<dyn Fn() + 'static>,
-
-    // Tasks
-    pub(crate) task_submitter: TaskSubmitter,
-
-    // A reference to the list of components.
-    // This lets us traverse the component list whenever we need to access our parent or children.
-    pub(crate) arena_link: SharedArena,
+    // A reference to the resources shared by all the comonents
+    pub(crate) vdom: SharedResources,
 }
 
-// The type of the channel function
-type EventChannel = Rc<dyn Fn()>;
-
 // The type of closure that wraps calling components
 pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
 
@@ -68,42 +63,52 @@ impl Scope {
     // Therefore, their lifetimes are connected exclusively to the virtual dom
     pub fn new<'creator_node>(
         caller: Rc<WrappedCaller>,
+
         arena_idx: ScopeId,
+
         parent: Option<ScopeId>,
+
         height: u32,
-        event_channel: EventChannel,
-        arena_link: SharedArena,
-        child_nodes: &'creator_node [VNode<'creator_node>],
-        task_submitter: TaskSubmitter,
+
+        child_nodes: ScopeChildren,
+
+        vdom: SharedResources,
     ) -> Self {
-        let child_nodes = unsafe { std::mem::transmute(child_nodes) };
+        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);
+        }
+
         Self {
             child_nodes,
             caller,
             parent_idx: parent,
             our_arena_idx: arena_idx,
             height,
-            event_channel,
-            arena_link,
-            task_submitter,
-            listener_idx: Default::default(),
+            vdom,
             frames: ActiveFrame::new(),
+
             hooks: Default::default(),
             shared_contexts: Default::default(),
             listeners: Default::default(),
+            borrowed_props: Default::default(),
             descendents: Default::default(),
+            pending_garbage: Default::default(),
         }
     }
 
-    pub(crate) fn update_caller<'creator_node>(&mut self, caller: Rc<WrappedCaller>) {
-        self.caller = caller;
-    }
-
-    pub(crate) fn update_children<'creator_node>(
+    pub(crate) fn update_scope_dependencies<'creator_node>(
         &mut self,
-        child_nodes: &'creator_node [VNode<'creator_node>],
+        caller: Rc<WrappedCaller>,
+        child_nodes: ScopeChildren,
     ) {
-        let child_nodes = unsafe { std::mem::transmute(child_nodes) };
+        self.caller = caller;
+        // let child_nodes = unsafe { std::mem::transmute(child_nodes) };
+        let child_nodes = unsafe { child_nodes.extend_lifetime() };
         self.child_nodes = child_nodes;
     }
 
@@ -112,19 +117,21 @@ impl Scope {
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
 
-        // This is a very dangerous operation
-        let next_frame = self.frames.old_frame_mut();
-        next_frame.bump.reset();
-
-        self.listeners.borrow_mut().clear();
+        self.ensure_drop_safety();
 
+        // Safety:
+        // - We dropped the listeners, so no more &mut T can be used while these are held
+        // - All children nodes that rely on &mut T are replaced with a new reference
         unsafe { self.hooks.reset() };
-        self.listener_idx.set(0);
+
+        // Safety:
+        // - We've dropped all references to the wip bump frame
+        unsafe { self.frames.reset_wip_frame() };
 
         // Cast the caller ptr from static to one with our own reference
-        let c3: &WrappedCaller = self.caller.as_ref();
+        let render: &WrappedCaller = self.caller.as_ref();
 
-        match c3(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.");
@@ -132,13 +139,59 @@ impl Scope {
             }
             Some(new_head) => {
                 // the user's component succeeded. We can safely cycle to the next frame
-                self.frames.old_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();
+                log::debug!("Cycle okay");
                 Ok(())
             }
         }
     }
 
+    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
+    /// causuing UB in our tree.
+    ///
+    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
+    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
+    /// any possible references to the data in the hook list.
+    ///
+    /// 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.
+    fn ensure_drop_safety(&mut self) {
+        // make sure all garabge is collected before trying to proceed with anything else
+        debug_assert!(
+            self.pending_garbage.borrow().is_empty(),
+            "clean up your garabge please"
+        );
+
+        // 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)
+        // right now, we don't drop
+        let vdom = &self.vdom;
+        self.borrowed_props
+            .get_mut()
+            .drain(..)
+            .map(|li| unsafe { &*li })
+            .for_each(|comp| {
+                // 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();
+
+                // Now, drop our own reference
+                let mut dropper = comp.drop_props.borrow_mut().take().unwrap();
+                dropper();
+            });
+
+        // Now that all the references are gone, we can safely drop our own references in our listeners.
+        self.listeners
+            .get_mut()
+            .drain(..)
+            .map(|li| unsafe { &*li })
+            .for_each(|listener| {
+                listener.callback.borrow_mut().take();
+            });
+    }
+
     // 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
@@ -162,63 +215,58 @@ impl Scope {
 
         let listners = self.listeners.borrow_mut();
 
-        let raw_listener = listners.iter().find(|(domptr, _)| {
-            let search = unsafe { &**domptr };
-            let search_id = search.get();
-            log::info!("searching listener {:#?}", search_id);
-            match real_node_id {
-                Some(e) => search_id == e,
-                None => false,
+        let raw_listener = listners.iter().find(|lis| {
+            let search = unsafe { &***lis };
+            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,
             }
         });
 
-        match raw_listener {
-            Some((_node, listener)) => unsafe {
-                // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
-                let listener_fn = &mut **listener;
-                listener_fn(event);
-            },
-            None => todo!(),
+        if let Some(raw_listener) = raw_listener {
+            let listener = unsafe { &**raw_listener };
+
+            // log::info!(
+            //     "calling listener {:?}, {:?}",
+            //     listener.event,
+            //     // listener.scope
+            // );
+            let mut cb = listener.callback.borrow_mut();
+            if let Some(cb) = cb.as_mut() {
+                (cb)(event);
+            }
+        } else {
+            log::warn!("An event was triggered but there was no listener to handle it");
         }
 
         Ok(())
     }
 
-    pub(crate) fn submit_task(&self, task: FiberTask) {
-        log::debug!("Task submitted into scope");
-        (self.task_submitter)(task);
+    pub fn root(&self) -> &VNode {
+        self.frames.fin_head()
     }
 
-    #[inline]
-    pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
-        self.frames.current_head_node()
+    pub fn child_nodes<'a>(&'a self) -> ScopeChildren {
+        unsafe { self.child_nodes.unextend_lfetime() }
     }
 
-    #[inline]
-    pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
-        self.frames.prev_head_node()
-    }
-
-    #[inline]
-    pub(crate) fn cur_frame(&self) -> &BumpFrame {
-        self.frames.cur_frame()
-    }
-
-    /// Get the root VNode of this component
-    #[inline]
-    pub fn root<'a>(&'a self) -> &'a VNode<'a> {
-        &self.frames.cur_frame().head_node
-    }
-}
-
-pub fn errored_fragment() -> VNode<'static> {
-    VNode {
-        dom_id: RealDomNode::empty_cell(),
-        key: None,
-        kind: VNodeKind::Fragment(VFragment {
-            children: &[],
-            is_static: false,
-            is_error: true,
-        }),
+    pub fn consume_garbage(&self) -> Vec<&VNode> {
+        let mut garbage = self.pending_garbage.borrow_mut();
+        garbage
+            .drain(..)
+            .map(|node| {
+                // safety: scopes cannot cycle without their garbage being collected. these nodes are safe
+                let node: &VNode<'static> = unsafe { &*node };
+                let node: &VNode = unsafe { std::mem::transmute(node) };
+                node
+            })
+            .collect::<Vec<_>>()
     }
 }

+ 0 - 137
packages/core/src/tasks.rs

@@ -1,137 +0,0 @@
-//! The TaskQueue serves as a centralized async store for all tasks in Dioxus.
-//! When a component renders, it may submit an async task to the queue.
-//!
-//! Then the task complete, it is emitted from the virtual dom in the event loop, which is then fed back into the virtualdom
-//! as an event trigger.
-//!
-//! When a component is scheduled to re-render, the awaing task must be dumped from the queue.
-//!
-//! This is all pretty unsafe stuff.
-//! The major invariant here is that tasks that enter the queue may be invalidated during transitions.
-
-use std::{
-    cell::Cell,
-    sync::{Arc, RwLock},
-};
-
-use futures_util::{stream::FuturesUnordered, Future, Stream, StreamExt};
-use slotmap::{DefaultKey, SlotMap};
-
-use crate::innerlude::{EventTrigger, FiberTask, ScopeId};
-
-pub type TaskSubmitter = Arc<dyn Fn(FiberTask)>;
-
-pub struct TaskQueue {
-    slots: Arc<RwLock<FuturesUnordered<FiberTask>>>,
-    // slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
-    submitter: TaskSubmitter,
-}
-
-impl TaskQueue {
-    pub fn new() -> Self {
-        let slots = Arc::new(RwLock::new(FuturesUnordered::new()));
-        let slots2 = slots.clone();
-
-        let submitter = Arc::new(move |task| {
-            let mut slots = slots2.write().unwrap();
-            log::debug!("Task submitted into global task queue");
-            slots.push(task);
-        });
-        Self { slots, submitter }
-    }
-
-    pub fn new_submitter(&self) -> TaskSubmitter {
-        self.submitter.clone()
-    }
-
-    pub fn submit_task(&mut self, task: FiberTask) {
-        self.slots.write().unwrap().push(task);
-        // TaskHandle { key }
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.slots.read().unwrap().is_empty()
-    }
-    pub fn len(&self) -> usize {
-        self.slots.read().unwrap().len()
-    }
-
-    pub async fn next(&mut self) -> Option<EventTrigger> {
-        let mut slots = self.slots.write().unwrap();
-        slots.next().await
-    }
-}
-
-// impl Stream for TaskQueue {
-//     type Item = EventTrigger;
-
-//     /// We can never be finished polling
-//     fn poll_next(
-//         self: Pin<&mut Self>,
-//         cx: &mut std::task::Context<'_>,
-//     ) -> std::task::Poll<Option<Self::Item>> {
-//         // let yield_every = self.len();
-//         // let mut polled = 0;
-
-//         let mut slots = self.slots.write().unwrap();
-//         for (_key, slot) in slots.iter_mut() {
-//             if slot.dead.get() {
-//                 continue;
-//             }
-//             let r = slot.fut;
-//             // let fut = unsafe { &mut *r };
-//             // use futures::{future::Future, poll, FutureExt};
-
-//             let f2 = fut.as_mut();
-//             let w = cx.waker();
-//             let mut cx = Context::from_waker(&w);
-
-//             // Pin::new_unchecked(pointer)
-//             // use std::future::Future;
-//             match f2.poll(&mut cx) {
-//                 Poll::Ready(_) => {
-//                     let trigger = EventTrigger::new_from_task(slot.originator);
-//                     slot.dead.set(true);
-//                     return Poll::Ready(Some(trigger));
-//                 }
-//                 Poll::Pending => continue,
-//             }
-//         }
-
-//         // we tried polling every active task.
-//         // give up and relinquish controlto the parent
-
-//         // We have polled a large number of futures in a row without yielding.
-//         // To ensure we do not starve other tasks waiting on the executor,
-//         // we yield here, but immediately wake ourselves up to continue.
-//         // cx.waker().wake_by_ref();
-//         return Poll::Pending;
-//     }
-// }
-
-pub struct TaskHandle {
-    key: DefaultKey,
-}
-
-pub struct DTask {
-    fut: FiberTask,
-    originator: ScopeId,
-    dead: Cell<bool>,
-}
-impl DTask {
-    pub fn new(fut: FiberTask, originator: ScopeId) -> Self {
-        Self {
-            fut,
-            originator,
-            dead: Cell::new(false),
-        }
-    }
-    pub fn debug_new(fut: FiberTask) -> Self {
-        let originator = ScopeId::default();
-        Self {
-            fut,
-            originator,
-            dead: Cell::new(false),
-        }
-    }
-}

+ 25 - 98
packages/core/src/util.rs

@@ -1,101 +1,28 @@
-use std::{
-    cell::{Cell, RefCell, RefMut},
-    rc::Rc,
-};
-
-use futures_util::StreamExt;
-use slotmap::{DefaultKey, Key, KeyData};
+use std::cell::Cell;
 
 use crate::innerlude::*;
 
-#[derive(PartialEq, Debug, Clone, Default)]
-pub struct EventQueue {
-    pub queue: Rc<RefCell<Vec<HeightMarker>>>,
-}
-
-impl EventQueue {
-    pub fn new_channel(&self, height: u32, idx: ScopeId) -> Rc<dyn Fn()> {
-        let inner = self.clone();
-        let marker = HeightMarker { height, idx };
-        Rc::new(move || {
-            log::debug!("channel updated {:#?}", marker);
-            inner.queue.as_ref().borrow_mut().push(marker)
-        })
-    }
-
-    pub fn sort_unstable(&self) {
-        self.queue.borrow_mut().sort_unstable()
-    }
-
-    pub fn borrow_mut(&self) -> RefMut<Vec<HeightMarker>> {
-        self.queue.borrow_mut()
-    }
-}
-
-/// 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))
-    }
-}
-
-/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node.
-///
-/// "u64" was chosen for two reasons
-/// - 0 cost hashing
-/// - use with slotmap and other versioned slot arenas
-
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub struct RealDomNode(pub u64);
-impl RealDomNode {
-    #[inline]
-    pub fn empty() -> Self {
-        Self(u64::MIN)
-    }
-    #[inline]
-    pub fn empty_cell() -> Cell<Self> {
-        Cell::new(Self::empty())
-    }
-    #[inline]
-    pub fn from_u64(id: u64) -> Self {
-        Self(id)
-    }
-
-    #[inline]
-    pub fn as_u64(&self) -> u64 {
-        self.0
-    }
-}
-
-pub struct DebugDom {
-    counter: u64,
-}
-impl DebugDom {
-    pub fn new() -> Self {
-        Self { counter: 0 }
-    }
-}
-
-impl<'a> RealDom<'a> for DebugDom {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-    }
-
-    fn request_available_node(&mut self) -> RealDomNode {
-        self.counter += 1;
-        RealDomNode::from_u64(self.counter)
-    }
-}
+// create a cell with a "none" value
+#[inline]
+pub fn empty_cell() -> Cell<Option<ElementId>> {
+    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))
+//     }
+// }

+ 312 - 199
packages/core/src/virtual_dom.rs

@@ -18,22 +18,20 @@
 //!
 //! This module includes just the barebones for a complete VirtualDOM API.
 //! Additional functionality is defined in the respective files.
+#![allow(unreachable_code)]
+use futures_util::StreamExt;
+use fxhash::FxHashMap;
 
-use crate::tasks::TaskQueue;
-use crate::{arena::SharedArena, innerlude::*};
+use crate::hooks::{SuspendedContext, SuspenseHook};
+use crate::{arena::SharedResources, innerlude::*};
 
-use slotmap::DefaultKey;
-use slotmap::SlotMap;
 use std::any::Any;
 
 use std::any::TypeId;
-use std::cell::RefCell;
+use std::cell::{Ref, RefCell, RefMut};
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet};
 use std::pin::Pin;
 
-slotmap::new_key_type! {
-    pub struct ScopeId;
-}
-
 /// 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.
 ///
@@ -49,34 +47,22 @@ pub struct VirtualDom {
     ///
     /// 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 components: SharedArena,
+    pub shared: SharedResources,
 
     /// The index of the root component
     /// Should always be the first (gen=0, id=0)
     pub base_scope: ScopeId,
 
-    pub triggers: RefCell<Vec<EventTrigger>>,
-
-    /// All components dump their updates into a queue to be processed
-    pub event_queue: EventQueue,
-
-    pub tasks: TaskQueue,
-
-    heuristics: HeuristicsEngine,
-
-    root_props: std::pin::Pin<Box<dyn std::any::Any>>,
+    active_fibers: Vec<Fiber<'static>>,
 
-    /// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic.
-    ///
-    /// Whenver props need to be updated, an Error will be thrown if the new props do not
-    /// match the props used to create the VirtualDom.
+    // 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>>,
 }
 
-// ======================================
-// Public Methods for the VirtualDom
-// ======================================
 impl VirtualDom {
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     ///
@@ -142,45 +128,30 @@ impl VirtualDom {
     /// let dom = VirtualDom::new(Example);
     /// ```
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
-        let components = SharedArena::new(SlotMap::<ScopeId, Scope>::with_key());
+        let components = SharedResources::new();
 
         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;
 
-        // Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel.
-        let event_queue = EventQueue::default();
-        let _event_queue = event_queue.clone();
-
         let link = components.clone();
 
-        let tasks = TaskQueue::new();
-        let submitter = tasks.new_submitter();
-
-        let base_scope = components
-            .with(|arena| {
-                arena.insert_with_key(move |myidx| {
-                    let event_channel = _event_queue.new_channel(0, myidx);
-                    let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
-                    Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
-                })
-            })
-            .unwrap();
+        let base_scope = components.insert_scope_with_key(move |myidx| {
+            let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
+            Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), link)
+        });
 
         Self {
             base_scope,
-            event_queue,
-            components,
-            root_props,
-            tasks,
-            heuristics: HeuristicsEngine::new(),
-            triggers: Default::default(),
+            _root_props: root_props,
+            shared: components,
+            active_fibers: Vec::new(),
             _root_prop_type: TypeId::of::<P>(),
         }
     }
 
     pub fn launch_in_place(root: FC<()>) -> Self {
         let mut s = Self::new(root);
-        s.rebuild_in_place();
+        s.rebuild_in_place().unwrap();
         s
     }
 
@@ -188,10 +159,18 @@ impl VirtualDom {
     ///
     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();
+        s.rebuild_in_place().unwrap();
         s
     }
 
+    pub fn base_scope(&self) -> &Scope {
+        unsafe { self.shared.get_scope(self.base_scope).unwrap() }
+    }
+
+    pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> {
+        unsafe { self.shared.get_scope(id) }
+    }
+
     /// Rebuilds the VirtualDOM from scratch, but uses a "dummy" RealDom.
     ///
     /// Used in contexts where a real copy of the  structure doesn't matter, and the VirtualDOM is the source of truth.
@@ -201,195 +180,329 @@ impl VirtualDom {
     /// This method uses the `DebugDom` under the hood - essentially making the VirtualDOM's diffing patches a "no-op".
     ///
     /// 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<()> {
-        let mut realdom = DebugDom::new();
-        let mut edits = Vec::new();
-        self.rebuild(&mut realdom, &mut edits)
+    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)
     }
 
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
     ///
-    /// Currently this doesn't do what we want it to do
-    ///
     /// The diff machine expects the RealDom's stack to be the root of the application
-    pub fn rebuild<'s, Dom: RealDom<'s>>(
-        &'s mut self,
-        realdom: &mut Dom,
-        edits: &mut Vec<DomEdit<'s>>,
-    ) -> Result<()> {
-        let mut diff_machine = DiffMachine::new(
-            edits,
-            realdom,
-            &self.components,
-            self.base_scope,
-            self.event_queue.clone(),
-            &self.tasks,
-        );
-
-        let cur_component = self.components.get_mut(self.base_scope).unwrap();
+    ///
+    /// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
+    /// through "run"
+    ///
+    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);
+
+        let cur_component = diff_machine
+            .get_scope_mut(&self.base_scope)
+            .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.
         if cur_component.run_scope().is_ok() {
-            let meta = diff_machine.create(cur_component.next_frame());
-            diff_machine.edits.append_children(meta.added_to_stack);
+            let meta = diff_machine.create_vnode(cur_component.frames.fin_head());
+            diff_machine.edit_append_children(meta.added_to_stack);
+        } else {
+            // todo: should this be a hard error?
+            log::warn!(
+                "Component failed to run succesfully during rebuild.
+                This does not result in a failed rebuild, but indicates a logic failure within your app."
+            );
         }
 
-        Ok(())
+        Ok(edits)
     }
 
+    // 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.
     ///
+    /// This method will not wait for any suspended tasks, completely skipping over
+    pub fn run_immediate<'s>(&'s mut self) -> Result<Mutations<'s>> {
+        //
+
+        todo!()
+    }
+
+    /// Runs the virtualdom with no time limit.
     ///
-    ///
-    ///
-    ///
-    pub fn queue_event(&self, trigger: EventTrigger) -> Result<()> {
-        let mut triggers = self.triggers.borrow_mut();
-        triggers.push(trigger);
-        Ok(())
+    /// 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 is the most sophisticated way of updating the virtual dom after an external event has been triggered.
-    ///  
-    /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
-    /// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
-    /// change list.
+    /// Run the virtualdom with a time limit.
     ///
-    /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
-    /// listeners, something like this:
+    /// 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
+    /// exhaust the deadline working on them.
     ///
-    /// ```ignore
-    /// while let Ok(event) = receiver.recv().await {
-    ///     let edits = self.internal_dom.progress_with_event(event)?;
-    ///     for edit in &edits {
-    ///         patch_machine.handle_edit(edit);
-    ///     }
-    /// }
-    /// ```
+    /// 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.
     ///
-    /// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
-    /// executor and handlers for suspense as show in the example.
+    /// 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.
     ///
-    /// ```ignore
-    /// let (sender, receiver) = channel::new();
-    /// sender.send(EventTrigger::start());
+    /// The deadline is checked 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 screen will "jank" up. In debug, this will trigger an alert.
     ///
-    /// let mut dom = VirtualDom::new();
-    /// dom.suspense_handler(|event| sender.send(event));
+    /// # Example
     ///
-    /// while let Ok(diffs) = dom.progress_with_event(receiver.recv().await) {
-    ///     render(diffs);
+    /// ```no_run
+    /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!( div {"hello"} )));
+    /// loop {
+    ///     let started = std::time::Instant::now();
+    ///     let deadline = move || std::time::Instant::now() - started > std::time::Duration::from_millis(16);
+    ///     
+    ///     let mutations = dom.run_with_deadline(deadline).await;
+    ///     apply_mutations(mutations);
     /// }
-    ///
     /// ```
-    //
-    // Developer notes:
-    // ----
-    // This method has some pretty complex safety guarantees to uphold.
-    // We interact with bump arenas, raw pointers, and use UnsafeCell to get a partial borrow of the arena.
-    // The final EditList has edits that pull directly from the Bump Arenas which add significant complexity
-    // in crafting a 100% safe solution with traditional lifetimes. Consider this method to be internally unsafe
-    // but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
-    //
-    // A good project would be to remove all unsafe from this crate and move the unsafety into safer abstractions.
-    pub async fn progress_with_event<'s, Dom: RealDom<'s>>(
+    pub async fn run_with_deadline<'s>(
         &'s mut self,
-        realdom: &'_ mut Dom,
-        edits: &mut Vec<DomEdit<'s>>,
-    ) -> Result<()> {
-        let trigger = self.triggers.borrow_mut().pop().expect("failed");
-
-        let mut diff_machine = DiffMachine::new(
-            edits,
-            realdom,
-            &self.components,
-            trigger.originator,
-            self.event_queue.clone(),
-            &self.tasks,
-        );
-
-        match &trigger.event {
-            VirtualEvent::OtherEvent => todo!(),
-
-            // Nothing yet
-            VirtualEvent::AsyncEvent { .. } => {}
+        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)?;
+                    }
+                }
 
-            // Suspense Events! A component's suspended node is updated
-            VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
-                let scope = self.components.get_mut(trigger.originator).unwrap();
+                VirtualEvent::AsyncEvent { .. } => {
+                    while let Ok(Some(event)) = receiver.try_next() {
+                        fiber.pending_scopes.push(event.originator);
+                    }
+                }
 
-                // safety: we are sure that there are no other references to the inner content of this hook
-                let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
+                // 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);
+                        }
+                    }
+                }
 
-                let cx = Context { scope, props: &() };
-                let scx = SuspendedContext { inner: cx };
+                // 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);
+                            }
+                        }
+                    }
 
-                // generate the new node!
-                let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
-                let nodes = nodes.unwrap_or_else(|| errored_fragment());
-                let nodes = scope.cur_frame().bump.alloc(nodes);
+                    for scope in scopes_to_kill {
+                        // oy kill em
+                        log::debug!("should be removing scope {:#?}", scope);
+                    }
+                }
+            }
+        }
 
-                // push the old node's root onto the stack
-                diff_machine.edits.push(domnode.get());
+        while !deadline_exceeded() {
+            let mut receiver = self.shared.task_receiver.borrow_mut();
 
-                // push these new nodes onto the diff machines stack
-                let meta = diff_machine.create(&*nodes);
+            // no messages to receive, just work on the fiber
+        }
 
-                // replace the placeholder with the new nodes we just pushed on the stack
-                diff_machine.edits.replace_with(meta.added_to_stack);
-            }
+        Ok(diff_machine.edits)
+    }
 
-            // This is the "meat" of our cooperative scheduler
-            // As updates flow in, we re-evalute the event queue and decide if we should be switching the type of work
-            //
-            // We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
-            _ => {
-                self.components
-                    .get_mut(trigger.originator)
-                    .map(|f| f.call_listener(trigger));
+    pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
+        self.shared.task_sender.clone()
+    }
 
-                // Now, there are events in the queue
-                let mut updates = self.event_queue.queue.as_ref().borrow_mut();
+    fn get_scope_mut(&mut self, id: ScopeId) -> Option<&mut Scope> {
+        unsafe { self.shared.get_scope_mut(id) }
+    }
+}
 
-                // Order the nodes by their height, we want the nodes with the smallest depth on top
-                // This prevents us from running the same component multiple times
-                updates.sort_unstable();
+// TODO!
+// These impls are actually wrong. The DOM needs to have a mutex implemented.
+unsafe impl Sync for VirtualDom {}
+unsafe impl Send for VirtualDom {}
 
-                log::debug!("There are: {:#?} updates to be processed", updates.len());
+struct Fiber<'a> {
+    // scopes that haven't been updated yet
+    pending_scopes: Vec<ScopeId>,
 
-                // Iterate through the triggered nodes (sorted by height) and begin to diff them
-                for update in updates.drain(..) {
-                    log::debug!("Running updates for: {:#?}", update);
+    pending_nodes: Vec<*const VNode<'a>>,
 
-                    // Make sure this isn't a node we've already seen, we don't want to double-render anything
-                    // If we double-renderer something, this would cause memory safety issues
-                    if diff_machine.seen_nodes.contains(&update.idx) {
-                        continue;
-                    }
+    // WIP edits
+    edits: Vec<DomEdit<'a>>,
 
-                    // Now, all the "seen nodes" are nodes that got notified by running this listener
-                    diff_machine.seen_nodes.insert(update.idx.clone());
+    started: bool,
 
-                    // Start a new mutable borrow to components
-                    // We are guaranteeed that this scope is unique because we are tracking which nodes have modified
-                    let cur_component = self.components.get_mut(update.idx).unwrap();
+    completed: bool,
+}
 
-                    if cur_component.run_scope().is_ok() {
-                        let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
-                        diff_machine.diff_node(old, new);
-                    }
-                }
-            }
+impl Fiber<'_> {
+    fn new() -> Self {
+        Self {
+            pending_scopes: Vec::new(),
+            pending_nodes: Vec::new(),
+            edits: Vec::new(),
+            started: false,
+            completed: false,
         }
-
-        Ok(())
-    }
-
-    pub fn base_scope(&self) -> &Scope {
-        self.components.get(self.base_scope).unwrap()
     }
 }
 
-// TODO!
-// These impls are actually wrong. The DOM needs to have a mutex implemented.
-unsafe impl Sync for VirtualDom {}
-unsafe impl Send for VirtualDom {}
+/// 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>>,
+}

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

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

+ 340 - 0
packages/core/tests/diffing.rs

@@ -0,0 +1,340 @@
+//! 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,
+};
+use dioxus_core as dioxus;
+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)
+    }
+}
+
+#[test]
+fn diffing_works() {}
+
+/// Should push the text node onto the stack and modify it
+#[test]
+fn html_and_rsx_generate_the_same_output() {
+    let dom = TestDom::new();
+    let edits = dom.lazy_diff(
+        rsx! ( div { "Hello world" } ),
+        rsx! ( div { "Goodbye world" } ),
+    );
+    dbg!(edits);
+}
+
+/// Should result in 3 elements on the stack
+#[test]
+fn fragments_create_properly() {
+    let dom = TestDom::new();
+    let (meta, edits) = dom.create(rsx! {
+        div { "Hello a" }
+        div { "Hello b" }
+        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);
+}
+
+/// Should result in the creation of an anchor (placeholder) and then a replacewith
+#[test]
+fn empty_fragments_create_anchors() {
+    let dom = TestDom::new();
+
+    let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
+    let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(edits);
+}
+
+/// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
+#[test]
+fn empty_fragments_create_many_anchors() {
+    let dom = TestDom::new();
+
+    let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
+    let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(edits);
+}
+
+/// Should result in the creation of an anchor (placeholder) and then a replacewith
+/// Includes child nodes inside the fragment
+#[test]
+fn empty_fragments_create_anchors_with_many_children() {
+    let dom = TestDom::new();
+
+    let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
+    let right = rsx!({
+        (0..5).map(|f| {
+            rsx! { div { "hello" }}
+        })
+    });
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+    let last_edit = edits.1.last().unwrap();
+    assert!(last_edit.is("ReplaceWith"));
+}
+
+/// Should result in every node being pushed and then replaced with an anchor
+#[test]
+fn many_items_become_fragment() {
+    let dom = TestDom::new();
+
+    let left = rsx!({
+        (0..2).map(|f| {
+            rsx! { div { "hello" }}
+        })
+    });
+    let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}
+
+/// Should result in no edits
+#[test]
+fn two_equal_fragments_are_equal() {
+    let dom = TestDom::new();
+
+    let left = rsx!({
+        (0..2).map(|f| {
+            rsx! { div { "hello" }}
+        })
+    });
+    let right = rsx!({
+        (0..2).map(|f| {
+            rsx! { div { "hello" }}
+        })
+    });
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+    assert!(edits.1.is_empty());
+}
+
+/// Should result the creation of more nodes appended after the old last node
+#[test]
+fn two_fragments_with_differrent_elements_are_differet() {
+    let dom = TestDom::new();
+
+    let left = rsx!(
+        {(0..2).map(|f| {rsx! { div {  }}})}
+        p {}
+    );
+    let right = rsx!(
+        {(0..5).map(|f| {rsx! { h1 {  }}})}
+        p {}
+    );
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}
+
+/// Should result in multiple nodes destroyed - with changes to the first nodes
+#[test]
+fn two_fragments_with_differrent_elements_are_differet_shorter() {
+    let dom = TestDom::new();
+
+    let left = rsx!(
+        {(0..5).map(|f| {rsx! { div {  }}})}
+        p {}
+    );
+    let right = rsx!(
+        {(0..2).map(|f| {rsx! { h1 {  }}})}
+        p {}
+    );
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}
+
+/// Should result in multiple nodes destroyed - with no changes
+#[test]
+fn two_fragments_with_same_elements_are_differet() {
+    let dom = TestDom::new();
+
+    let left = rsx!(
+        {(0..2).map(|f| {rsx! { div {  }}})}
+        p {}
+    );
+    let right = rsx!(
+        {(0..5).map(|f| {rsx! { div {  }}})}
+        p {}
+    );
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}
+
+// Similar test from above, but with extra child nodes
+#[test]
+fn two_fragments_with_same_elements_are_differet_shorter() {
+    let dom = TestDom::new();
+
+    let left = rsx!(
+        {(0..5).map(|f| {rsx! { div {  }}})}
+        p {"e"}
+    );
+    let right = rsx!(
+        {(0..2).map(|f| {rsx! { div {  }}})}
+        p {"e"}
+    );
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}
+
+/// should result in the removal of elements
+#[test]
+fn keyed_diffing_order() {
+    let dom = TestDom::new();
+
+    let left = rsx!(
+        {(0..5).map(|f| {rsx! { div { key: "{f}"  }}})}
+        p {"e"}
+    );
+    let right = rsx!(
+        {(0..2).map(|f| {rsx! { div { key: "{f}" }}})}
+        p {"e"}
+    );
+
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}
+
+#[test]
+fn fragment_keys() {
+    let r = 1;
+    let p = rsx! {
+        Fragment { key: "asd {r}" }
+    };
+}
+
+/// Should result in moves, but not removals or additions
+#[test]
+fn keyed_diffing_out_of_order() {
+    let dom = TestDom::new();
+
+    // 0, 1, 2, 3, 4, 5, 6, 7, 8,
+    let left = rsx!({
+        (0..3).chain(3..6).chain(6..9).map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
+    let right = rsx!({
+        (0..3).chain((3..7).rev()).chain(7..10).map(|f| {
+            rsx! { div { key: "{f}"  }}
+        })
+    });
+
+    // LIS: 3, 7, 8,
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}
+
+#[test]
+fn controlled_keyed_diffing_out_of_order() {
+    let dom = TestDom::new();
+
+    let left = [4, 5, 6, 7];
+    let left = rsx!({
+        left.iter().map(|f| {
+            rsx! { div { key: "{f}" "{f}" }}
+        })
+    });
+
+    // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
+    let right = [0, 5, 9, 6, 4];
+    let right = rsx!({
+        right.iter().map(|f| {
+            rsx! { div { key: "{f}" "{f}" }}
+        })
+    });
+
+    // LIS: 3, 7, 8,
+    let edits = dom.lazy_diff(left, right);
+    dbg!(&edits);
+}

+ 32 - 0
packages/core/tests/eventsystem.rs

@@ -0,0 +1,32 @@
+use bumpalo::Bump;
+
+use anyhow::{Context, Result};
+use dioxus::{
+    arena::SharedResources,
+    diff::{CreateMeta, DiffMachine},
+    prelude::*,
+    DomEdit,
+};
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+
+#[async_std::test]
+async fn event_queue_works() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            div { "hello world" }
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let edits = dom.rebuild().unwrap();
+
+    async_std::task::spawn_local(async move {
+        match dom.run_unbounded().await {
+            Err(_) => todo!(),
+            Ok(mutations) => {
+                //
+            }
+        }
+    });
+}

+ 99 - 0
packages/core/tests/vdom_rebuild.rs

@@ -0,0 +1,99 @@
+//! Rebuilding tests
+//! ----------------
+//!
+//! This tests module ensures that the initial build of the virtualdom is correct.
+//! This does not include dynamic tests or the diffing algorithm itself.
+//!
+//! It does prove that mounting works properly and the correct edit streams are generated.
+//!
+//! Don't have a good way to validate, everything is done manually ATM
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+
+#[test]
+fn app_runs() {
+    static App: FC<()> = |cx| {
+        //
+        cx.render(rsx!( div{"hello"} ))
+    };
+    let mut vdom = VirtualDom::new(App);
+    let edits = vdom.rebuild_in_place().unwrap();
+    dbg!(edits);
+}
+
+#[test]
+fn fragments_work() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx!(
+            div{"hello"}
+            div{"goodbye"}
+        ))
+    };
+    let mut vdom = VirtualDom::new(App);
+    let edits = vdom.rebuild_in_place().unwrap();
+    // should result in a final "appendchildren n=2"
+    dbg!(edits);
+}
+
+#[test]
+fn lists_work() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx!(
+            h1 {"hello"}
+            {(0..6).map(|f| rsx!(span{ "{f}" }))}
+        ))
+    };
+    let mut vdom = VirtualDom::new(App);
+    let edits = vdom.rebuild_in_place().unwrap();
+    dbg!(edits);
+}
+
+#[test]
+fn conditional_rendering() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx!(
+            h1 {"hello"}
+            {true.then(|| rsx!(span{ "a" }))}
+            {false.then(|| rsx!(span{ "b" }))}
+        ))
+    };
+    let mut vdom = VirtualDom::new(App);
+
+    let edits = vdom.rebuild_in_place().unwrap();
+    dbg!(&edits);
+    // the "false" fragment should generate an empty placeholder to re-visit
+    assert!(edits[edits.len() - 2].is("CreatePlaceholder"));
+}
+
+#[test]
+fn child_components() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx!(
+            {true.then(|| rsx!(Child { }))}
+            {false.then(|| rsx!(Child { }))}
+        ))
+    };
+    static Child: FC<()> = |cx| {
+        cx.render(rsx!(
+            h1 {"hello"}
+            h1 {"goodbye"}
+        ))
+    };
+    let mut vdom = VirtualDom::new(App);
+    let edits = vdom.rebuild_in_place().unwrap();
+    dbg!(edits);
+}
+
+#[test]
+fn suspended_works() {
+    static App: FC<()> = |cx| {
+        let title = use_suspense(cx, || async { "bob" }, |cx, f| cx.render(rsx! { "{f}"}));
+        cx.render(rsx!("hello" { title }))
+    };
+
+    let mut vdom = VirtualDom::new(App);
+    let edits = vdom.rebuild_in_place().unwrap();
+    dbg!(edits);
+}

+ 3 - 2
packages/desktop/Cargo.toml

@@ -11,7 +11,7 @@ license = "MIT/Apache-2.0"
 [dependencies]
 # web-view = { git = "https://github.com/Boscop/web-view" }
 dioxus-core = { path = "../core", version = "0.1.2", features = ["serialize"] }
-anyhow = "1.0.38"
+anyhow = "1.0"
 argh = "0.1.4"
 serde = "1.0.120"
 serde_json = "1.0.61"
@@ -20,12 +20,13 @@ log = "0.4.13"
 fern = { version = "0.6.0", features = ["colored"] }
 html-escape = "0.2.9"
 wry = "0.11.0"
+async-std = { version = "1.9.0", features = ["attributes"] }
 
 
 [dev-dependencies]
+dioxus-html = { path = "../html" }
 tide = "0.15.0"
 tide-websockets = "0.3.0"
-async-std = { version = "1.9.0", features = ["attributes"] }
 
 # thiserror = "1.0.23"
 # log = "0.4.13"

+ 2 - 11
packages/desktop/examples/test.rs

@@ -1,8 +1,9 @@
 use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
+use dioxus_html as dioxus_elements;
 
 fn main() {
-    dioxus_desktop::launch(App, |f| f.with_maximized(true)).expect("Failed");
+    dioxus_desktop::launch(App, |f| f.with_window(|w| w.with_maximized(true))).expect("Failed");
 }
 
 static App: FC<()> = |cx| {
@@ -13,13 +14,3 @@ static App: FC<()> = |cx| {
         }
     ))
 };
-
-mod dioxus_elements {
-    use super::*;
-    pub struct div;
-    impl DioxusElement for div {
-        const TAG_NAME: &'static str = "div";
-        const NAME_SPACE: Option<&'static str> = None;
-    }
-    pub trait GlobalAttributes {}
-}

+ 45 - 0
packages/desktop/src/cfg.rs

@@ -0,0 +1,45 @@
+use std::ops::{Deref, DerefMut};
+
+use dioxus_core::DomEdit;
+use wry::{
+    application::{
+        error::OsError,
+        event_loop::EventLoopWindowTarget,
+        menu::MenuBar,
+        window::{Fullscreen, Icon, Window, WindowBuilder},
+    },
+    webview::{RpcRequest, RpcResponse},
+};
+
+pub struct DesktopConfig<'a> {
+    pub window: WindowBuilder,
+    pub(crate) manual_edits: Option<DomEdit<'a>>,
+    pub(crate) pre_rendered: Option<String>,
+}
+
+impl DesktopConfig<'_> {
+    /// Initializes a new `WindowBuilder` with default values.
+    #[inline]
+    pub fn new() -> Self {
+        Self {
+            window: Default::default(),
+            pre_rendered: None,
+            manual_edits: None,
+        }
+    }
+
+    pub fn with_prerendered(&mut self, content: String) -> &mut Self {
+        self.pre_rendered = Some(content);
+        self
+    }
+
+    pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self {
+        // gots to do a swap because the window builder only takes itself as muy self
+        // I wish more people knew about returning &mut Self
+        let mut builder = WindowBuilder::default();
+        std::mem::swap(&mut self.window, &mut builder);
+        builder = f(builder);
+        std::mem::swap(&mut self.window, &mut builder);
+        self
+    }
+}

+ 15 - 21
packages/desktop/src/dom.rs

@@ -1,43 +1,37 @@
 //! webview dom
 
-use dioxus_core::{DomEdit, RealDom, RealDomNode, ScopeId};
-use DomEdit::*;
+use dioxus_core::{DomEdit, RealDom};
 
-pub struct WebviewRegistry {}
+// pub struct WebviewRegistry {}
 
-impl WebviewRegistry {
-    pub fn new() -> Self {
-        Self {}
-    }
-}
+// impl WebviewRegistry {
+//     pub fn new() -> Self {
+//         Self {}
+//     }
+// }
 
 pub struct WebviewDom<'bump> {
     pub edits: Vec<DomEdit<'bump>>,
     pub node_counter: u64,
-    pub registry: WebviewRegistry,
+    // pub registry: WebviewRegistry,
 }
 impl WebviewDom<'_> {
-    pub fn new(registry: WebviewRegistry) -> Self {
+    pub fn new() -> Self {
         Self {
             edits: Vec::new(),
             node_counter: 0,
-            registry,
+            // registry,
         }
     }
 
-    // Finish using the dom (for its edit list) and give back the node and event registry
-    pub fn consume(self) -> WebviewRegistry {
-        self.registry
-    }
+    // // Finish using the dom (for its edit list) and give back the node and event registry
+    // pub fn consume(self) -> WebviewRegistry {
+    //     self.registry
+    // }
 }
-impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
+impl RealDom for WebviewDom<'_> {
     fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
         todo!()
         // self.edits.push(PushRoot { root });
     }
-
-    fn request_available_node(&mut self) -> RealDomNode {
-        self.node_counter += 1;
-        RealDomNode::from_u64(self.node_counter)
-    }
 }

+ 85 - 0
packages/desktop/src/events.rs

@@ -0,0 +1,85 @@
+//! Convert a serialized event to an event Trigger
+//!
+
+use std::rc::Rc;
+
+use dioxus_core::{
+    events::{
+        on::{MouseEvent, MouseEventInner},
+        VirtualEvent,
+    },
+    ElementId, EventPriority, EventTrigger, ScopeId,
+};
+
+#[derive(serde::Serialize, serde::Deserialize)]
+struct ImEvent {
+    event: String,
+    mounted_dom_id: u64,
+    scope: u64,
+}
+pub fn trigger_from_serialized(val: serde_json::Value) -> EventTrigger {
+    let mut data: Vec<ImEvent> = serde_json::from_value(val).unwrap();
+    let data = data.drain(..).next().unwrap();
+
+    let event = VirtualEvent::MouseEvent(MouseEvent(Rc::new(WebviewMouseEvent)));
+    let scope = ScopeId(data.scope 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)
+}
+
+#[derive(Debug)]
+struct WebviewMouseEvent;
+impl MouseEventInner for WebviewMouseEvent {
+    fn alt_key(&self) -> bool {
+        todo!()
+    }
+
+    fn button(&self) -> i16 {
+        todo!()
+    }
+
+    fn buttons(&self) -> u16 {
+        todo!()
+    }
+
+    fn client_x(&self) -> i32 {
+        todo!()
+    }
+
+    fn client_y(&self) -> i32 {
+        todo!()
+    }
+
+    fn ctrl_key(&self) -> bool {
+        todo!()
+    }
+
+    fn meta_key(&self) -> bool {
+        todo!()
+    }
+
+    fn page_x(&self) -> i32 {
+        todo!()
+    }
+
+    fn page_y(&self) -> i32 {
+        todo!()
+    }
+
+    fn screen_x(&self) -> i32 {
+        todo!()
+    }
+
+    fn screen_y(&self) -> i32 {
+        todo!()
+    }
+
+    fn shift_key(&self) -> bool {
+        todo!()
+    }
+
+    fn get_modifier_state(&self, key_code: &str) -> bool {
+        todo!()
+    }
+}

+ 190 - 114
packages/desktop/src/index.html

@@ -2,143 +2,219 @@
 <html>
 
 <head>
-    <script>
-        class Interpreter {
-            constructor(root) {
-                this.stack = [root];
-                this.listeners = {};
-                this.lastNodeWasText = false;
-                this.nodes = {
-                    0: root
-                };
-            }
 
-            top() {
-                return this.stack[this.stack.length - 1];
-            }
+</head>
 
-            pop() {
-                return this.stack.pop();
-            }
+<body>
+    <div id="_dioxusroot">
+    </div>
+</body>
+<script>
+    class Interpreter {
+        constructor(root) {
+            this.root = root;
+            this.stack = [root];
+            this.listeners = {
+                "onclick": {}
+            };
+            this.lastNodeWasText = false;
+            this.nodes = [root, root, root, root];
         }
 
-        class OPTABLE {
-            PushRoot(self, edit) {
-                const id = edit.root;
-                const node = self.nodes[id];
-                self.stack.push(node);
-            }
-            AppendChildren(self, edit) {
-                let root = self.stack[self.stack.length - (edit.many + 1)];
-                for (let i = 0; i < edit.many; i++) {
-                    console.log("popping ", i, edit.many);
-                    let node = self.pop();
-                    root.appendChild(node);
-                }
+        top() {
+            return this.stack[this.stack.length - 1];
+        }
+
+        pop() {
+            return this.stack.pop();
+        }
+
+        PushRoot(edit) {
+            const id = edit.id;
+            const node = this.nodes[id];
+            console.log("pushing root ", node, "with id", id);
+            this.stack.push(node);
+        }
+
+        PopRoot(edit) {
+            this.stack.pop();
+        }
+
+        AppendChildren(edit) {
+            let root = this.stack[this.stack.length - (edit.many + 1)];
+            for (let i = 0; i < edit.many; i++) {
+                console.log("popping ", i, edit.many);
+                let node = this.pop();
+                root.appendChild(node);
             }
-            ReplaceWith(self, edit) {
+        }
 
-                let root = self.stack[self.stack.length - (edit.many + 1)];
-                let els = [];
+        ReplaceWith(edit) {
 
-                for (let i = 0; i < edit.many; i++) {
-                    els.push(self.pop());
-                }
+            let root = this.stack[this.stack.length - (edit.many + 1)];
+            let els = [];
 
-                root.replaceWith(...els);
+            for (let i = 0; i < edit.many; i++) {
+                els.push(this.pop());
             }
-            Remove(self, edit) {
-                const node = self.stack.pop();
-                node.remove();
+
+            root.replaceWith(...els);
+        }
+
+        Remove(edit) {
+            const node = this.stack.pop();
+            node.remove();
+        }
+
+        RemoveAllChildren(edit) {}
+
+        CreateTextNode(edit) {
+            const node = document.createTextNode(edit.text);
+            this.nodes[edit.id] = node;
+            this.stack.push(node);
+        }
+
+        CreateElement(edit) {
+            const tagName = edit.tag;
+            const el = document.createElement(tagName);
+            this.nodes[edit.id] = el;
+            console.log(`creating element: `, edit);
+            this.stack.push(el);
+        }
+
+        CreateElementNs(edit) {
+            const tagName = edit.tag;
+            console.log(`creating namespaced element: `, edit);
+            this.stack.push(document.createElementNS(edit.ns, edit.tag));
+        }
+
+        CreatePlaceholder(edit) {
+            const a = `this.stack.push(document.createElement(" pre"))`;
+            this.stack.push(document.createComment("vroot"));
+        }
+
+        NewEventListener(edit) {
+            const element_id = edit.element_id;
+            const event_name = edit.event_name;
+            const mounted_node_id = edit.mounted_node_id;
+            const scope = edit.scope;
+
+            const element = this.top();
+            element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
+
+            console.log("listener map is", this.listeners);
+            if (this.listeners[event_name] === undefined) {
+                console.log("adding listener!");
+                this.listeners[event_name] = "bla";
+                this.root.addEventListener(event_name, (event) => {
+                    const target = event.target;
+                    const type = event.type;
+                    const val = target.getAttribute(`dioxus-event-${event_name}`);
+                    const fields = val.split(".");
+                    const scope_id = parseInt(fields[0]);
+                    const real_id = parseInt(fields[1]);
+
+                    console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
+
+                    rpc.call('user_event', {
+                        event: event_name,
+                        scope: scope_id,
+                        mounted_dom_id: real_id,
+                    }).then((reply) => {
+                        console.log(reply);
+                        this.stack.push(this.root);
+
+                        let edits = reply.edits;
+
+                        for (let x = 0; x < edits.length; x++) {
+                            let edit = edits[x];
+                            console.log(edit);
+
+                            let f = this[edit.type];
+                            f.call(this, edit);
+                        }
+
+                        console.log("initiated");
+                    }).catch((err) => {
+                        console.log("failed to initiate", err);
+                    });
+                });
             }
-            RemoveAllChildren(self, edit) {}
-            CreateTextNode(self, edit) {
-                self.stack.push(document.createTextNode(edit.text));
+        }
+
+        RemoveEventListener(edit) {}
+
+        SetText(edit) {
+            this.top().textContent = edit.text;
+        }
+
+        SetAttribute(edit) {
+            const name = edit.field;
+            const value = edit.value;
+            const ns = edit.ns;
+            const node = this.top(this.stack);
+            if (ns == "style") {
+                node.style[name] = value;
+            } else if (ns !== undefined) {
+                node.setAttributeNS(ns, name, value);
+            } else {
+                node.setAttribute(name, value);
             }
-            CreateElement(self, edit) {
-                const tagName = edit.tag;
-                console.log(`creating element: `, edit);
-                self.stack.push(document.createElement(tagName));
+            if (name === "value") {
+                node.value = value;
             }
-            CreateElementNs(self, edit) {
-                const tagName = edit.tag;
-                console.log(`creating namespaced element: `, edit);
-                self.stack.push(document.createElementNS(edit.ns, edit.tag));
+            if (name === "checked") {
+                node.checked = true;
             }
-            CreatePlaceholder(self, edit) {
-                const a = `self.stack.push(document.createElement("pre"))`;
-                self.stack.push(document.createComment("vroot"));
+            if (name === "selected") {
+                node.selected = true;
             }
-            NewEventListener(self, edit) {}
-            RemoveEventListener(self, edit) {}
-            SetText(self, edit) {
-                self.top().textContent = edit.text;
+        }
+        RemoveAttribute(edit) {
+            const name = edit.field;
+            const node = this.top(this.stack);
+            node.removeAttribute(name);
+
+            if (name === "value") {
+                node.value = null;
             }
-            SetAttribute(self, edit) {
-                const name = edit.field;
-                const value = edit.value;
-                const ns = edit.ns;
-
-                const node = self.top(self.stack);
-                if (ns == "style") {
-                    node.style[name] = value;
-                } else if (ns !== undefined) {
-                    node.setAttributeNS(ns, name, value);
-                } else {
-                    node.setAttribute(name, value);
-                }
-
-                if ((name === "value", self)) {
-                    node.value = value;
-                }
-                if ((name === "checked", self)) {
-                    node.checked = true;
-                }
-                if ((name === "selected", self)) {
-                    node.selected = true;
-                }
+            if (name === "checked") {
+                node.checked = false;
             }
-            RemoveAttribute(self, edit) {
-                const name = edit.field;
-                const node = self.top(self.stack);
-
-                node.removeAttribute(name);
-
-                if ((name === "value", self)) {
-                    node.value = null;
-                }
-                if ((name === "checked", self)) {
-                    node.checked = false;
-                }
-                if ((name === "selected", self)) {
-                    node.selected = false;
-                }
+            if (name === "selected") {
+                node.selected = false;
             }
         }
+    }
 
-        const op_table = new OPTABLE();
 
-        async function initialize() {
-            const reply = await rpc.call('initiate');
-            const interpreter = new Interpreter(window.document.getElementById("_dioxusroot"));
-            console.log(reply);
 
-            for (let x = 0; x < reply.length; x++) {
-                let edit = reply[x];
-                console.log(edit);
-                op_table[edit.type](interpreter, edit);
-            }
 
-            console.log("stack completed: ", interpreter.stack);
+    async function initialize() {
+        const reply = await rpc.call('initiate');
+        let root = window.document.getElementById("_dioxusroot");
+        const interpreter = new Interpreter(root);
+        console.log(reply);
+
+        let pre_rendered = reply.pre_rendered;
+        if (pre_rendered !== undefined) {
+            root.innerHTML = pre_rendered;
         }
-        console.log("initializing...");
-        initialize();
-    </script>
-</head>
 
-<body>
-    <div id="_dioxusroot">
-    </div>
-</body>
+        const edits = reply.edits;
+
+        for (let x = 0; x < edits.length; x++) {
+            let edit = edits[x];
+            console.log(edit);
+
+            let f = interpreter[edit.type];
+            f.call(interpreter, edit);
+        }
+
+        console.log("stack completed: ", interpreter.stack);
+    }
+    console.log("initializing...");
+    initialize();
+</script>
 
 </html>

+ 130 - 121
packages/desktop/src/lib.rs

@@ -3,7 +3,9 @@ use std::ops::{Deref, DerefMut};
 use std::sync::mpsc::channel;
 use std::sync::{Arc, RwLock};
 
+use cfg::DesktopConfig;
 use dioxus_core::*;
+use serde::{Deserialize, Serialize};
 pub use wry;
 
 use wry::application::event::{Event, WindowEvent};
@@ -15,21 +17,24 @@ use wry::{
     webview::{RpcRequest, RpcResponse},
 };
 
+mod cfg;
 mod dom;
 mod escape;
+mod events;
+use events::*;
 
 static HTML_CONTENT: &'static str = include_str!("./index.html");
 
 pub fn launch(
     root: FC<()>,
-    builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+    builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
 ) -> anyhow::Result<()> {
     launch_with_props(root, (), builder)
 }
 pub fn launch_with_props<P: Properties + 'static>(
     root: FC<P>,
     props: P,
-    builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+    builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
 ) -> anyhow::Result<()> {
     WebviewRenderer::run(root, props, builder)
 }
@@ -44,11 +49,17 @@ enum RpcEvent<'a> {
     Initialize { edits: Vec<DomEdit<'a>> },
 }
 
+#[derive(Serialize)]
+struct Response<'a> {
+    pre_rendered: Option<String>,
+    edits: Vec<DomEdit<'a>>,
+}
+
 impl<T: Properties + 'static> WebviewRenderer<T> {
     pub fn run(
         root: FC<T>,
         props: T,
-        user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+        user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
     ) -> anyhow::Result<()> {
         Self::run_with_edits(root, props, user_builder, None)
     }
@@ -56,73 +67,131 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
     pub fn run_with_edits(
         root: FC<T>,
         props: T,
-        user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
+        user_builder: impl for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
         redits: Option<Vec<DomEdit<'static>>>,
     ) -> anyhow::Result<()> {
+        log::info!("hello edits");
         let event_loop = EventLoop::new();
 
-        let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
+        let mut cfg = DesktopConfig::new();
+        user_builder(&mut cfg);
 
-        let vir = VirtualDom::new_with_props(root, props);
+        let DesktopConfig {
+            window,
+            manual_edits,
+            pre_rendered,
+        } = cfg;
 
-        // todo: combine these or something
-        let vdom = Arc::new(RwLock::new(vir));
-        let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
+        let window = window.build(&event_loop)?;
 
-        let webview = WebViewBuilder::new(window)?
-            // .with_visible(false)
-            // .with_transparent(true)
-            .with_url(&format!("data:text/html,{}", HTML_CONTENT))?
-            .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
-                match req.method.as_str() {
-                    "initiate" => {
-                        let edits = if let Some(edits) = &redits {
-                            serde_json::to_value(edits).unwrap()
-                        } else {
-                            let mut lock = vdom.write().unwrap();
-                            let mut reg_lock = registry.write().unwrap();
-
-                            // Create the thin wrapper around the registry to collect the edits into
-                            let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
-
-                            // Serialize the edit stream
-                            let edits = {
-                                let mut edits = Vec::new();
-                                lock.rebuild(&mut real, &mut edits).unwrap();
-                                serde_json::to_value(edits).unwrap()
-                            };
-
-                            // Give back the registry into its slot
-                            *reg_lock = Some(real.consume());
-                            edits
-                        };
-
-                        // Return the edits into the webview runtime
-                        Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-                    }
-                    "user_event" => {
-                        let mut lock = vdom.write().unwrap();
-                        let mut reg_lock = registry.write().unwrap();
+        let mut vir = VirtualDom::new_with_props(root, props);
 
-                        // Create the thin wrapper around the registry to collect the edits into
-                        let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
+        let channel = vir.get_event_sender();
+        struct WebviewBridge {}
+        // impl RealDom for WebviewBridge {
+        //     fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
+        //         todo!()
+        //     }
 
-                        // Serialize the edit stream
-                        let edits = {
-                            let mut edits = Vec::new();
-                            lock.rebuild(&mut real, &mut edits).unwrap();
-                            serde_json::to_value(edits).unwrap()
-                        };
+        //     fn must_commit(&self) -> bool {
+        //         false
+        //     }
 
-                        // Give back the registry into its slot
-                        *reg_lock = Some(real.consume());
+        //     fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
 
-                        // Return the edits into the webview runtime
-                        Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-                    }
-                    _ => todo!("this message failed"),
-                }
-            })
+        //     fn wait_until_ready<'s>(
+        //         &'s mut self,
+        //     ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
+        //         //
+        //         Box::pin(async {
+        //             //
+        //         })
+        //     }
+        // }
+
+        let mut real_dom = WebviewBridge {};
+        // async_std::task::spawn_local(vir.run(&mut real_dom));
+
+        // todo: combine these or something
+        let vdom = Arc::new(RwLock::new(vir));
+        // let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
+
+        let webview = WebViewBuilder::new(window)?
+            .with_url(&format!("data:text/html,{}", HTML_CONTENT))?
+            // .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
+            //     match req.method.as_str() {
+            //         "initiate" => {
+            //             let edits = if let Some(edits) = &redits {
+            //                 serde_json::to_value(edits).unwrap()
+            //             } else {
+            //                 let mut lock = vdom.write().unwrap();
+            //                 // let mut reg_lock = registry.write().unwrap();
+            //                 // Create the thin wrapper around the registry to collect the edits into
+            //                 let mut real = dom::WebviewDom::new();
+            //                 let pre = pre_rendered.clone();
+            //                 let response = match pre {
+            //                     Some(content) => {
+            //                         lock.rebuild_in_place().unwrap();
+            //                         Response {
+            //                             edits: Vec::new(),
+            //                             pre_rendered: Some(content),
+            //                         }
+            //                     }
+            //                     None => {
+            //                         //
+            //                         let edits = {
+            //                             // let mut edits = Vec::new();
+            //                             todo!()
+            //                             // lock.rebuild(&mut real, &mut edits).unwrap();
+            //                             // edits
+            //                         };
+            //                         Response {
+            //                             edits,
+            //                             pre_rendered: None,
+            //                         }
+            //                     }
+            //                 };
+            //                 serde_json::to_value(&response).unwrap()
+            //             };
+            //             // Return the edits into the webview runtime
+            //             Some(RpcResponse::new_result(req.id.take(), Some(edits)))
+            //         }
+            //         "user_event" => {
+            //             log::debug!("User event received");
+            //             // let registry = registry.clone();
+            //             let vdom = vdom.clone();
+            //             let response = async_std::task::block_on(async move {
+            //                 let mut lock = vdom.write().unwrap();
+            //                 // let mut reg_lock = registry.write().unwrap();
+            //                 // a deserialized event
+            //                 let data = req.params.unwrap();
+            //                 log::debug!("Data: {:#?}", data);
+            //                 let event = trigger_from_serialized(data);
+            //                 // lock.queue_event(event);
+            //                 // Create the thin wrapper around the registry to collect the edits into
+            //                 let mut real = dom::WebviewDom::new();
+            //                 // Serialize the edit stream
+            //                 //
+            //                 let mut edits = Vec::new();
+            //                 // lock.run(&mut real, &mut edits)
+            //                 //     .await
+            //                 //     .expect("failed to progress");
+            //                 let response = Response {
+            //                     edits,
+            //                     pre_rendered: None,
+            //                 };
+            //                 let response = serde_json::to_value(&response).unwrap();
+            //                 // Give back the registry into its slot
+            //                 // *reg_lock = Some(real.consume());
+            //                 // Return the edits into the webview runtime
+            //                 Some(RpcResponse::new_result(req.id.take(), Some(response)))
+            //             });
+            //             response
+            //             // spawn a task to clean up the garbage
+            //         }
+            //         _ => todo!("this message failed"),
+            //     }
+            // })
             .build()?;
 
         event_loop.run(move |event, _, control_flow| {
@@ -138,33 +207,17 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                     }
                     _ => {}
                 },
+
                 Event::MainEventsCleared => {
                     webview.resize();
                     // window.request_redraw();
                 }
 
-                _ => {} // Event::WindowEvent { event, .. } => {
-                        //     //
-                        //     match event {
-                        //         WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                        //         _ => {}
-                        //     }
-                        // }
-                        // _ => {
-                        //     // let _ = webview.resize();
-                        // }
+                _ => {}
             }
         });
     }
 
-    /// 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(root: FC<T>, builder: impl FnOnce() -> WVResult<WebView<'static, ()>>) -> Self {
-    //     Self { root }
-    // }
-
     /// Create a new text renderer from an existing Virtual DOM.
     /// This will progress the existing VDom's events to completion.
     pub fn from_vdom() -> Self {
@@ -181,47 +234,3 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
         todo!()
     }
 }
-
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-
-use crate::dom::WebviewRegistry;
-
-#[derive(Debug, Serialize, Deserialize)]
-struct MessageParameters {
-    message: String,
-}
-
-fn HANDLER(window: &Window, mut req: RpcRequest) -> Option<RpcResponse> {
-    let mut response = None;
-    if &req.method == "fullscreen" {
-        if let Some(params) = req.params.take() {
-            if let Ok(mut args) = serde_json::from_value::<Vec<bool>>(params) {
-                if !args.is_empty() {
-                    if args.swap_remove(0) {
-                        window.set_fullscreen(Some(Fullscreen::Borderless(None)));
-                    } else {
-                        window.set_fullscreen(None);
-                    }
-                };
-                response = Some(RpcResponse::new_result(req.id.take(), None));
-            }
-        }
-    } else if &req.method == "send-parameters" {
-        if let Some(params) = req.params.take() {
-            if let Ok(mut args) = serde_json::from_value::<Vec<MessageParameters>>(params) {
-                let result = if !args.is_empty() {
-                    let msg = args.swap_remove(0);
-                    Some(Value::String(format!("Hello, {}!", msg.message)))
-                } else {
-                    // NOTE: in the real-world we should send an error response here!
-                    None
-                };
-                // Must always send a response as this is a `call()`
-                response = Some(RpcResponse::new_result(req.id.take(), result));
-            }
-        }
-    }
-
-    response
-}

+ 3 - 3
packages/hooks/src/usestate.rs

@@ -52,7 +52,7 @@ use std::{
 pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>(
     cx: Context<'a, P>,
     initial_state_fn: F,
-) -> UseState<T> {
+) -> UseState<'a, T> {
     cx.use_hook(
         move |_| UseStateInner {
             current_val: initial_state_fn(),
@@ -212,8 +212,8 @@ impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
         self.set(self.inner.current_val.div(rhs));
     }
 }
-impl<'a, T: PartialEq<T>> PartialEq<T> for UseState<'a, T> {
-    fn eq(&self, other: &T) -> bool {
+impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<'a, T> {
+    fn eq(&self, other: &V) -> bool {
         self.get() == other
     }
 }

+ 30 - 0
packages/html/src/attrval.s

@@ -0,0 +1,30 @@
+
+trait AsAttributeValue: Sized {
+    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;
+}
+enum AttributeValue<'a> {
+    Int(i32),
+    Float(f32),
+    Str(&'a str),
+    Bool(bool),
+}
+impl<'b> AsAttributeValue for Arguments<'b> {
+    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
+        todo!()
+    }
+}
+impl AsAttributeValue for &'static str {
+    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
+        todo!()
+    }
+}
+impl AsAttributeValue for f32 {
+    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
+        todo!()
+    }
+}
+impl AsAttributeValue for i32 {
+    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
+        todo!()
+    }
+}

+ 1 - 30
packages/html/src/lib.rs

@@ -1,3 +1,4 @@
+#![allow(non_snake_case)]
 //! # Dioxus Namespace for HTML
 //!
 //! This crate provides a set of compile-time correct HTML elements that can be used with the Rsx and Html macros.
@@ -2100,33 +2101,3 @@ summary
 slot
 template
 */
-
-trait AsAttributeValue: Sized {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;
-}
-enum AttributeValue<'a> {
-    Int(i32),
-    Float(f32),
-    Str(&'a str),
-    Bool(bool),
-}
-impl<'b> AsAttributeValue for Arguments<'b> {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}
-impl AsAttributeValue for &'static str {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}
-impl AsAttributeValue for f32 {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}
-impl AsAttributeValue for i32 {
-    fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
-        todo!()
-    }
-}

+ 1 - 1
packages/mobile/Cargo.toml

@@ -7,7 +7,7 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-anyhow = "1.0.42"
+anyhow = "1.0"
 # cacao = { git = "https://github.com/ryanmcgrath/cacao" }
 dioxus-core = { path = "../core", version = "0.1.2" }
 log = "0.4.14"

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

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

+ 3 - 2
packages/mobile/src/lib.rs

@@ -118,7 +118,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                                         // Serialize the edit stream
                                         let edits = {
                                             let mut edits = Vec::new();
-                                            lock.rebuild(&mut real, &mut edits).unwrap();
+                                            lock.rebuild(&mut edits).unwrap();
                                             serde_json::to_value(edits).unwrap()
                                         };
 
@@ -140,7 +140,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                                     // Serialize the edit stream
                                     let edits = {
                                         let mut edits = Vec::new();
-                                        lock.rebuild(&mut real, &mut edits).unwrap();
+                                        lock.rebuild(&mut edits).unwrap();
                                         serde_json::to_value(edits).unwrap()
                                     };
 
@@ -185,6 +185,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
         });
     }
 }
+// brad johnson go chat
 
 fn main() {
     init_logging();

+ 1 - 1
packages/ssr/Cargo.toml

@@ -17,7 +17,7 @@ tide-websockets = "*"
 thiserror = "1.0.23"
 log = "0.4.13"
 fern = { version = "0.6.0", features = ["colored"] }
-anyhow = "1.0.38"
+anyhow = "1.0"
 argh = "0.1.4"
 serde = "1.0.120"
 serde_json = "1.0.61"

+ 6 - 0
packages/ssr/README.md

@@ -16,3 +16,9 @@ vdom.rebuild_in_place();
 let text = dioxus_ssr::render_root(&vdom);
 assert_eq!(text, "<div>hello world!</div>")
 ```
+
+
+
+## Pre-rendering
+
+

+ 10 - 0
packages/ssr/examples/hydration.rs

@@ -0,0 +1,10 @@
+//! Example: realworld usage of hydration
+//!
+//!
+use dioxus::virtual_dom::VirtualDom;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_hooks::use_state;
+use dioxus_html as dioxus_elements;
+
+fn main() {}

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

@@ -22,7 +22,7 @@ async fn main() -> Result<(), std::io::Error> {
         let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
 
         Ok(Response::builder(200)
-            .body(format!("{}", dioxus_ssr::render_vdom(&dom)))
+            .body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
             .content_type(tide::http::mime::HTML)
             .build())
     });

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

@@ -15,8 +15,11 @@ fn main() {
     let mut dom = VirtualDom::new(App);
     dom.rebuild_in_place().expect("failed to run virtualdom");
 
-    file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
-        .unwrap();
+    file.write_fmt(format_args!(
+        "{}",
+        TextRenderer::from_vdom(&dom, Default::default())
+    ))
+    .unwrap();
 }
 
 pub static App: FC<()> = |cx| {

+ 113 - 51
packages/ssr/src/lib.rs

@@ -1,3 +1,7 @@
+//!
+//!
+//!
+//!
 //! This crate demonstrates how to implement a custom renderer for Dioxus VNodes via the `TextRenderer` renderer.
 //! The `TextRenderer` consumes a Dioxus Virtual DOM, progresses its event queue, and renders the VNodes to a String.
 //!
@@ -12,8 +16,11 @@ use dioxus_core::*;
 
 pub fn render_vnode(vnode: &VNode, string: &mut String) {}
 
-pub fn render_vdom(dom: &VirtualDom) -> String {
-    format!("{:}", TextRenderer::from_vdom(dom))
+pub fn render_vdom(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
+    format!(
+        "{:}",
+        TextRenderer::from_vdom(dom, cfg(SsrConfig::default()))
+    )
 }
 
 pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
@@ -21,35 +28,12 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
         "{:}",
         TextRenderer {
             cfg: SsrConfig::default(),
-            root: vdom.components.get(scope).unwrap().root(),
+            root: vdom.get_scope(scope).unwrap().root(),
             vdom: Some(vdom)
         }
     ))
 }
 
-pub struct SsrConfig {
-    // currently not supported - control if we indent the HTML output
-    indent: bool,
-
-    // Control if elements are written onto a new line
-    newline: bool,
-
-    // Currently not implemented
-    // Don't proceed onto new components. Instead, put the name of the component.
-    // TODO: components don't have names :(
-    _skip_components: bool,
-}
-
-impl Default for SsrConfig {
-    fn default() -> Self {
-        Self {
-            indent: false,
-
-            newline: false,
-            _skip_components: false,
-        }
-    }
-}
 /// A configurable text renderer for the Dioxus VirtualDOM.
 ///
 ///
@@ -60,7 +44,7 @@ impl Default for SsrConfig {
 ///
 /// ## Example
 /// ```ignore
-/// const 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);
 /// vdom.rebuild_in_place();
 ///
@@ -81,11 +65,11 @@ impl Display for TextRenderer<'_> {
 }
 
 impl<'a> TextRenderer<'a> {
-    pub fn from_vdom(vdom: &'a VirtualDom) -> Self {
+    pub fn from_vdom(vdom: &'a VirtualDom, cfg: SsrConfig) -> Self {
         Self {
+            cfg,
             root: vdom.base_scope().root(),
             vdom: Some(vdom),
-            cfg: SsrConfig::default(),
         }
     }
 
@@ -99,6 +83,15 @@ impl<'a> TextRenderer<'a> {
                 }
                 write!(f, "{}", text.text)?
             }
+            VNodeKind::Anchor(anchor) => {
+                //
+                if self.cfg.indent {
+                    for _ in 0..il {
+                        write!(f, "    ")?;
+                    }
+                }
+                write!(f, "<!-- -->")?;
+            }
             VNodeKind::Element(el) => {
                 if self.cfg.indent {
                     for _ in 0..il {
@@ -132,6 +125,21 @@ impl<'a> TextRenderer<'a> {
                     }
                 }
 
+                // we write the element's id as a data attribute
+                //
+                // when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add
+                // them interpreter's stack
+                match (self.cfg.pre_render, node.try_direct_id()) {
+                    (true, Some(id)) => {
+                        write!(f, " dio_el=\"{}\"", id)?;
+                        //
+                        for listener in el.listeners {
+                            // write the listeners
+                        }
+                    }
+                    _ => {}
+                }
+
                 match self.cfg.newline {
                     true => write!(f, ">\n")?,
                     false => write!(f, ">")?,
@@ -162,9 +170,14 @@ impl<'a> TextRenderer<'a> {
             }
             VNodeKind::Component(vcomp) => {
                 let idx = vcomp.ass_scope.get().unwrap();
-                if let Some(vdom) = self.vdom {
-                    let new_node = vdom.components.get(idx).unwrap().root();
-                    self.html_render(new_node, f, il + 1)?;
+                match (self.vdom, self.cfg.skip_components) {
+                    (Some(vdom), false) => {
+                        let new_node = vdom.get_scope(idx).unwrap().root();
+                        self.html_render(new_node, f, il + 1)?;
+                    }
+                    _ => {
+                        // render the component by name
+                    }
                 }
             }
             VNodeKind::Suspended { .. } => {
@@ -175,6 +188,52 @@ impl<'a> TextRenderer<'a> {
     }
 }
 
+pub struct SsrConfig {
+    // currently not supported - control if we indent the HTML output
+    indent: bool,
+
+    // Control if elements are written onto a new line
+    newline: bool,
+
+    // Choose to write ElementIDs into elements so the page can be re-hydrated later on
+    pre_render: bool,
+
+    // Currently not implemented
+    // Don't proceed onto new components. Instead, put the name of the component.
+    // TODO: components don't have names :(
+    skip_components: bool,
+}
+
+impl Default for SsrConfig {
+    fn default() -> Self {
+        Self {
+            indent: false,
+            pre_render: false,
+            newline: false,
+            skip_components: false,
+        }
+    }
+}
+
+impl SsrConfig {
+    pub fn indent(mut self, a: bool) -> Self {
+        self.indent = a;
+        self
+    }
+    pub fn newline(mut self, a: bool) -> Self {
+        self.newline = a;
+        self
+    }
+    pub fn pre_render(mut self, a: bool) -> Self {
+        self.pre_render = a;
+        self
+    }
+    pub fn skip_components(mut self, a: bool) -> Self {
+        self.skip_components = a;
+        self
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -182,15 +241,14 @@ mod tests {
     use dioxus_core as dioxus;
     use dioxus_core::prelude::*;
     use dioxus_html as dioxus_elements;
-    use dioxus_html::GlobalAttributes;
 
-    const SIMPLE_APP: FC<()> = |cx| {
+    static SIMPLE_APP: FC<()> = |cx| {
         cx.render(rsx!(div {
             "hello world!"
         }))
     };
 
-    const SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
+    static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
         cx.render(rsx! {
             div {
                 title: "About W3Schools"
@@ -209,14 +267,14 @@ mod tests {
         })
     };
 
-    const NESTED_APP: FC<()> = |cx| {
+    static NESTED_APP: FC<()> = |cx| {
         cx.render(rsx!(
             div {
                 SIMPLE_APP {}
             }
         ))
     };
-    const FRAGMENT_APP: FC<()> = |cx| {
+    static FRAGMENT_APP: FC<()> = |cx| {
         cx.render(rsx!(
             div { "f1" }
             div { "f2" }
@@ -229,21 +287,28 @@ mod tests {
     fn to_string_works() {
         let mut dom = VirtualDom::new(SIMPLE_APP);
         dom.rebuild_in_place().expect("failed to run virtualdom");
-        dbg!(render_vdom(&dom));
+        dbg!(render_vdom(&dom, |c| c));
+    }
+
+    #[test]
+    fn hydration() {
+        let mut dom = VirtualDom::new(NESTED_APP);
+        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dbg!(render_vdom(&dom, |c| c.pre_render(true)));
     }
 
     #[test]
     fn nested() {
         let mut dom = VirtualDom::new(NESTED_APP);
         dom.rebuild_in_place().expect("failed to run virtualdom");
-        dbg!(render_vdom(&dom));
+        dbg!(render_vdom(&dom, |c| c));
     }
 
     #[test]
     fn fragment_app() {
         let mut dom = VirtualDom::new(FRAGMENT_APP);
         dom.rebuild_in_place().expect("failed to run virtualdom");
-        dbg!(render_vdom(&dom));
+        dbg!(render_vdom(&dom, |c| c));
     }
 
     #[test]
@@ -256,26 +321,23 @@ mod tests {
         let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
         dom.rebuild_in_place().expect("failed to run virtualdom");
 
-        file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
-            .unwrap();
+        file.write_fmt(format_args!(
+            "{}",
+            TextRenderer::from_vdom(&dom, SsrConfig::default())
+        ))
+        .unwrap();
     }
 
     #[test]
     fn styles() {
-        const STLYE_APP: FC<()> = |cx| {
-            //
+        static STLYE_APP: FC<()> = |cx| {
             cx.render(rsx! {
-                div {
-                    style: {
-                        color: "blue",
-                        font_size: "46px"
-                    }
-                }
+                div { style: { color: "blue", font_size: "46px" } }
             })
         };
 
         let mut dom = VirtualDom::new(STLYE_APP);
         dom.rebuild_in_place().expect("failed to run virtualdom");
-        dbg!(render_vdom(&dom));
+        dbg!(render_vdom(&dom, |c| c));
     }
 }

+ 5 - 4
packages/web/Cargo.toml

@@ -22,18 +22,18 @@ generational-arena = "0.2.8"
 wasm-bindgen-test = "0.3.21"
 once_cell = "1.8"
 async-channel = "1.6.1"
-anyhow = "1.0.41"
-slotmap = "1.0.3"
+anyhow = "1.0"
 
 futures-util = "0.3.15"
 
 [dependencies.web-sys]
-version = "0.3.50"
+version = "0.3.51"
 features = [
     "Comment",
     "Attr",
     "Document",
     "Element",
+    "CssStyleDeclaration",
     "HtmlElement",
     "HtmlInputElement",
     "HtmlSelectElement",
@@ -57,6 +57,7 @@ features = [
     "PointerEvent",
     "FocusEvent",
     "CompositionEvent",
+    "ClipboardEvent",
     "DocumentType",
     "CharacterData",
     "SvgElement",
@@ -79,7 +80,7 @@ uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
 dioxus-hooks = { path = "../hooks" }
 gloo-timers = { version = "0.2.1", features = ["futures"] }
 serde = { version = "1.0.126", features = ["derive"] }
-surf = { version = "2.2.0", default-features = false, features = [
+surf = { git = "https://github.com/http-rs/surf", rev = "1ffaba8873", default-features = false, features = [
     "wasm-client",
 ] }
 # wasm-timer = "0.2.5"

+ 2 - 1
packages/web/examples/async_web.rs

@@ -31,7 +31,8 @@ const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random/";
 static App: FC<()> = |cx| {
     let state = use_state(cx, || 0);
 
-    let dog_node = cx.use_suspense(
+    let dog_node = use_suspense(
+        cx,
         || surf::get(ENDPOINT).recv_json::<DogApi>(),
         |cx, res| match res {
             Ok(res) => rsx!(in cx, img { src: "{res.message}" }),

+ 45 - 0
packages/web/examples/basic.rs

@@ -0,0 +1,45 @@
+//! Basic example that renders a simple VNode to the browser.
+
+use dioxus::events::on::MouseEvent;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_hooks::*;
+use dioxus_html as dioxus_elements;
+// use wasm_timer;
+
+use std::future::Future;
+
+use std::{pin::Pin, time::Duration};
+
+use dioxus::prelude::*;
+
+use dioxus_web::*;
+
+fn main() {
+    // Setup logging
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+
+    // Run the app
+    dioxus_web::launch(App, |c| c)
+}
+
+static App: FC<()> = |cx| {
+    let mut count = use_state(cx, || 0);
+
+    cx.render(rsx! {
+        div {
+            button {
+                "add"
+                onclick: move |_| count += 1
+            }
+            ul {
+                {(0..*count).map(|f| rsx!{
+                    li { "a - {f}" }
+                    li { "b - {f}" }
+                    li { "c - {f}" }
+                })}
+            }
+        }
+    })
+};

+ 68 - 0
packages/web/examples/btns.rs

@@ -0,0 +1,68 @@
+//! 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::*;
+use dioxus_core as dioxus;
+use dioxus_hooks::*;
+use dioxus_html as dioxus_elements;
+
+// #[cfg]
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    dioxus_web::launch(App, |c| c);
+    // env_logger::init();
+    // dioxus::web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx| {
+    dbg!("rednering parent");
+    cx.render(rsx! {
+        div {
+            But {
+                h1 {"he"}
+            }
+            // But {
+            //     h1 {"llo"}
+            // }
+            // But {
+            //     h1 {"world"}
+            // }
+        }
+    })
+};
+
+static But: FC<()> = |cx| {
+    let mut count = use_state(cx, || 0);
+
+    // let d = Dropper { name: "asd" };
+    // let handler = move |_| {
+    //     dbg!(d.name);
+    // };
+
+    cx.render(rsx! {
+        div {
+            h1 { "Hifive counter: {count}" }
+            {cx.children()}
+            button { onclick: move |_| count += 1, "Up high!" }
+            button { onclick: move |_| count -= 1, "Down low!" }
+            // button { onclick: {handler}, "Down low!" }
+        }
+    })
+};
+
+// struct Dropper {
+//     name: &'static str,
+// }
+// impl Drop for Dropper {
+//     fn drop(&mut self) {
+//         dbg!("dropped");
+//     }
+// }

+ 30 - 0
packages/web/src/cfg.rs

@@ -0,0 +1,30 @@
+pub struct WebConfig {
+    pub(crate) hydrate: bool,
+    pub(crate) rootname: String,
+}
+impl Default for WebConfig {
+    fn default() -> Self {
+        Self {
+            hydrate: false,
+            rootname: "dioxusroot".to_string(),
+        }
+    }
+}
+impl WebConfig {
+    /// Enable SSR hydration
+    ///
+    /// This enables Dioxus to pick up work from a pre-renderd HTML file. Hydration will completely skip over any async
+    /// work and suspended nodes.
+    ///
+    /// Dioxus will load up all the elements with the `dio_el` data attribute into memory when the page is loaded.
+    ///
+    pub fn hydrate(mut self, f: bool) -> Self {
+        self.hydrate = f;
+        self
+    }
+
+    pub fn rootname(mut self, name: impl Into<String>) -> Self {
+        self.rootname = name.into();
+        self
+    }
+}

+ 308 - 162
packages/web/src/new.rs → packages/web/src/dom.rs

@@ -2,24 +2,28 @@ use std::{collections::HashMap, rc::Rc, sync::Arc};
 
 use dioxus_core::{
     events::{EventTrigger, VirtualEvent},
-    DomEdit, RealDomNode, ScopeId,
+    DomEdit, ElementId, ScopeId,
 };
 use fxhash::FxHashMap;
-use slotmap::{DefaultKey, Key, KeyData};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{
-    window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
+    window, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
+    HtmlOptionElement, Node, NodeList,
 };
 
+use crate::{nodeslab::NodeSlab, WebConfig};
+
 pub struct WebsysDom {
-    pub stack: Stack,
-    nodes: slotmap::SlotMap<DefaultKey, Option<Node>>,
+    stack: Stack,
+
+    /// A map from ElementID (index) to Node
+    nodes: NodeSlab,
+
     document: Document,
-    root: Element,
 
-    event_receiver: async_channel::Receiver<EventTrigger>,
+    root: Element,
 
-    trigger: Arc<dyn Fn(EventTrigger)>,
+    sender_callback: Rc<dyn Fn(EventTrigger)>,
 
     // map of listener types to number of those listeners
     // This is roughly a delegater
@@ -34,42 +38,45 @@ pub struct WebsysDom {
     last_node_was_text: bool,
 }
 impl WebsysDom {
-    pub fn new(root: Element) -> Self {
-        let document = window()
-            .expect("must have access to the window")
-            .document()
-            .expect("must have access to the Document");
-
-        let (sender, receiver) = async_channel::unbounded::<EventTrigger>();
-
-        let sender_callback = Arc::new(move |ev| {
-            let c = sender.clone();
-            wasm_bindgen_futures::spawn_local(async move {
-                c.send(ev).await.unwrap();
-            });
-        });
+    pub fn new(root: Element, cfg: WebConfig, sender_callback: Rc<dyn Fn(EventTrigger)>) -> Self {
+        let document = load_document();
+
+        let mut nodes = NodeSlab::new(2000);
+        let mut listeners = FxHashMap::default();
+
+        // re-hydrate the page - only supports one virtualdom per page
+        if cfg.hydrate {
+            // Load all the elements into the arena
+            let node_list: NodeList = document.query_selector_all("dio_el").unwrap();
+            let len = node_list.length() as usize;
+
+            for x in 0..len {
+                let node: Node = node_list.get(x as u32).unwrap();
+                let el: &Element = node.dyn_ref::<Element>().unwrap();
+                let id: String = el.get_attribute("dio_el").unwrap();
+                let id = id.parse::<usize>().unwrap();
+                nodes[id] = Some(node);
+            }
 
-        let mut nodes = slotmap::SlotMap::with_capacity(1000);
+            // Load all the event listeners into our listener register
+            // TODO
+        }
 
-        let root_id = nodes.insert(Some(root.clone().dyn_into::<Node>().unwrap()));
+        let mut stack = Stack::with_capacity(10);
+        let root_node = root.clone().dyn_into::<Node>().unwrap();
+        stack.push(root_node);
 
         Self {
-            stack: Stack::with_capacity(10),
+            stack,
             nodes,
-            listeners: FxHashMap::default(),
+            listeners,
             document,
-            event_receiver: receiver,
-            trigger: sender_callback,
+            sender_callback,
             root,
             last_node_was_text: false,
         }
     }
 
-    pub async fn wait_for_event(&mut self) -> Option<EventTrigger> {
-        let v = self.event_receiver.recv().await.unwrap();
-        Some(v)
-    }
-
     pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) {
         for edit in edits.drain(..) {
             log::info!("Handling edit: {:#?}", edit);
@@ -77,7 +84,7 @@ impl WebsysDom {
                 DomEdit::PushRoot { id: root } => self.push(root),
                 DomEdit::PopRoot => self.pop(),
                 DomEdit::AppendChildren { many } => self.append_children(many),
-                DomEdit::ReplaceWith { many } => self.replace_with(many),
+                DomEdit::ReplaceWith { n, m } => self.replace_with(n, m),
                 DomEdit::Remove => self.remove(),
                 DomEdit::RemoveAllChildren => self.remove_all_children(),
                 DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id),
@@ -88,24 +95,28 @@ impl WebsysDom {
                     event_name: event,
                     scope,
                     mounted_node_id: node,
-                    element_id: idx,
-                } => self.new_event_listener(event, scope, idx, node),
+                } => self.new_event_listener(event, scope, node),
+
                 DomEdit::RemoveEventListener { event } => todo!(),
                 DomEdit::SetText { text } => self.set_text(text),
                 DomEdit::SetAttribute { field, value, ns } => self.set_attribute(field, value, ns),
                 DomEdit::RemoveAttribute { name } => self.remove_attribute(name),
+
+                DomEdit::InsertAfter { n } => self.insert_after(n),
+                DomEdit::InsertBefore { n } => self.insert_before(n),
             }
         }
     }
     fn push(&mut self, root: u64) {
-        let key = DefaultKey::from(KeyData::from_ffi(root));
-        let domnode = self.nodes.get_mut(key);
+        let key = root as usize;
+        let domnode = &self.nodes[key];
 
-        let domnode = domnode.unwrap().as_mut().unwrap();
-        // .expect(&format!("Failed to pop know root: {:#?}", key))
-        // .unwrap();
+        let real_node: Node = match domnode {
+            Some(n) => n.clone(),
+            None => todo!(),
+        };
 
-        self.stack.push(domnode.clone());
+        self.stack.push(real_node);
     }
     // drop the node off the stack
     fn pop(&mut self) {
@@ -143,37 +154,55 @@ impl WebsysDom {
         }
     }
 
-    fn replace_with(&mut self, many: u32) {
+    fn replace_with(&mut self, n: u32, m: u32) {
         log::debug!("Called [`replace_with`]");
-        let new_node = self.stack.pop();
-        let old_node = self.stack.pop();
 
-        // TODO: use different-sized replace withs
-        if many == 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);
-            }
+        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());
         }
 
-        self.stack.push(new_node);
+        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 remove(&mut self) {
@@ -187,7 +216,8 @@ impl WebsysDom {
     }
 
     fn create_placeholder(&mut self, id: u64) {
-        self.create_element("pre", None, id)
+        self.create_element("pre", None, id);
+        self.set_attribute("hidden", "", None);
     }
     fn create_text_node(&mut self, text: &str, id: u64) {
         // let nid = self.node_counter.next();
@@ -198,11 +228,9 @@ impl WebsysDom {
             .dyn_into::<Node>()
             .unwrap();
 
+        let id = id as usize;
         self.stack.push(textnode.clone());
-        *self
-            .nodes
-            .get_mut(DefaultKey::from(KeyData::from_ffi(id)))
-            .unwrap() = Some(textnode);
+        self.nodes[id] = Some(textnode);
     }
 
     fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
@@ -221,24 +249,18 @@ impl WebsysDom {
                 .dyn_into::<Node>()
                 .unwrap(),
         };
-        let id = DefaultKey::from(KeyData::from_ffi(id));
+        let id = id as usize;
 
         self.stack.push(el.clone());
-        *self.nodes.get_mut(id).unwrap() = Some(el);
+        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);
-        // RealDomNode::new(nid)
+        // ElementId::new(nid)
     }
 
-    fn new_event_listener(
-        &mut self,
-        event: &'static str,
-        scope: ScopeId,
-        _element_id: usize,
-        real_id: u64,
-    ) {
-        let (_on, event) = event.split_at(2);
+    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);
 
         // attach the correct attributes to the element
@@ -252,19 +274,24 @@ impl WebsysDom {
             .dyn_ref::<Element>()
             .expect(&format!("not an element: {:?}", el));
 
-        let scope_id = scope.data().as_ffi();
+        // let scope_id = scope.data().as_ffi();
+        let scope_id = scope.0 as u64;
+
         el.set_attribute(
             &format!("dioxus-event-{}", event),
             &format!("{}.{}", scope_id, real_id),
         )
         .unwrap();
 
+        el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
+            .unwrap();
+
         // Register the callback to decode
 
         if let Some(entry) = self.listeners.get_mut(event) {
             entry.0 += 1;
         } else {
-            let trigger = self.trigger.clone();
+            let trigger = self.sender_callback.clone();
 
             let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
                 // "Result" cannot be received from JS
@@ -293,25 +320,21 @@ impl WebsysDom {
     }
 
     fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
-        if name == "class" {
+        if let Some(el) = self.stack.top().dyn_ref::<Element>() {
             match ns {
-                Some("http://www.w3.org/2000/svg") => {
-                    //
-                    if let Some(el) = self.stack.top().dyn_ref::<web_sys::SvgElement>() {
-                        let r: web_sys::SvgAnimatedString = el.class_name();
-                        r.set_base_val(value);
-                        // el.set_class_name(value);
-                    }
-                }
-                _ => {
-                    if let Some(el) = self.stack.top().dyn_ref::<Element>() {
-                        el.set_class_name(value);
-                    }
+                // inline style support
+                Some("style") => {
+                    let el = el.dyn_ref::<HtmlElement>().unwrap();
+                    let style_dc: CssStyleDeclaration = el.style();
+                    style_dc.set_property(name, value).unwrap();
                 }
+                _ => el.set_attribute(name, value).unwrap(),
             }
-        } else {
-            if let Some(el) = self.stack.top().dyn_ref::<Element>() {
-                el.set_attribute(name, value).unwrap();
+            match name {
+                "value" => {}
+                "checked" => {}
+                "selected" => {}
+                _ => {}
             }
         }
     }
@@ -338,17 +361,46 @@ impl WebsysDom {
         }
     }
 
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
+    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();
     }
-}
 
-impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
-    fn request_available_node(&mut self) -> RealDomNode {
-        let key = self.nodes.insert(None);
-        log::debug!("making new key: {:#?}", key);
-        RealDomNode(key.data().as_ffi())
+    fn insert_before(&mut self, n: u32) {
+        let n = n as usize;
+        let root = self
+            .stack
+            .list
+            .get(self.stack.list.len() - n)
+            .unwrap()
+            .clone();
+        for _ in 0..n {
+            let el = self.stack.pop();
+            root.insert_before(&el, None).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 raw_node_as_any(&self) -> &mut dyn std::any::Any {
         todo!()
@@ -395,30 +447,140 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
     use dioxus_core::events::on::*;
     match event.type_().as_str() {
         "copy" | "cut" | "paste" => {
-            // let evt: web_sys::ClipboardEvent = event.clone().dyn_into().unwrap();
-
-            todo!()
+            struct WebsysClipboardEvent();
+            impl ClipboardEventInner for WebsysClipboardEvent {}
+            VirtualEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent())))
         }
 
         "compositionend" | "compositionstart" | "compositionupdate" => {
             let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            struct WebsysCompositionEvent(web_sys::CompositionEvent);
+            impl CompositionEventInner for WebsysCompositionEvent {
+                fn data(&self) -> String {
+                    todo!()
+                }
+            }
+            VirtualEvent::CompositionEvent(CompositionEvent(Rc::new(WebsysCompositionEvent(evt))))
         }
 
         "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();
-            todo!()
+            VirtualEvent::KeyboardEvent(KeyboardEvent(Rc::new(Event(evt))))
         }
 
         "focus" | "blur" => {
+            struct Event(web_sys::FocusEvent);
+            impl FocusEventInner for Event {}
             let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            VirtualEvent::FocusEvent(FocusEvent(Rc::new(Event(evt))))
         }
 
         "change" => {
-            let evt: web_sys::Event = event.clone().dyn_into().expect("wrong error typ");
+            // 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!()
-            // VirtualEvent::FormEvent(FormEvent {value:})
         }
 
         "input" | "invalid" | "reset" | "submit" => {
@@ -442,15 +604,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
                 })
                 .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
 
-            // let p2 = evt.data_transfer();
-
-            // let value: Option<String> = (&evt).data();
-            // let value = val;
-            // let value = value.unwrap_or_default();
-            // let value = (&evt).data().expect("No data to unwrap");
-
-            // todo - this needs to be a "controlled" event
-            // these events won't carry the right data with them
             todo!()
             // VirtualEvent::FormEvent(FormEvent { value })
         }
@@ -507,26 +660,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
                 }
             }
             VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
-            // MouseEvent(Box::new(RawMouseEvent {
-            //                 alt_key: evt.alt_key(),
-            //                 button: evt.button() as i32,
-            //                 buttons: evt.buttons() as i32,
-            //                 client_x: evt.client_x(),
-            //                 client_y: evt.client_y(),
-            //                 ctrl_key: evt.ctrl_key(),
-            //                 meta_key: evt.meta_key(),
-            //                 page_x: evt.page_x(),
-            //                 page_y: evt.page_y(),
-            //                 screen_x: evt.screen_x(),
-            //                 screen_y: evt.screen_y(),
-            //                 shift_key: evt.shift_key(),
-            //                 get_modifier_state: GetModifierKey(Box::new(|f| {
-            //                     // evt.get_modifier_state(f)
-            //                     todo!("This is not yet implemented properly, sorry :(");
-            //                 })),
-            //             }))
-            // todo!()
-            // VirtualEvent::MouseEvent()
         }
 
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
@@ -555,6 +688,15 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
             let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
             todo!()
         }
+        "animationstart" | "animationend" | "animationiteration" => {
+            let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "transitionend" => {
+            let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
 
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
@@ -567,23 +709,15 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
             todo!()
         }
 
-        "animationstart" | "animationend" | "animationiteration" => {
-            let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
-            todo!()
-        }
-
-        "transitionend" => {
-            let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
-            todo!()
-        }
-
         "toggle" => {
             // not required to construct anything special beyond standard event stuff (target)
 
             // let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap();
             todo!()
         }
-        _ => VirtualEvent::OtherEvent,
+        _ => {
+            todo!()
+        }
     }
 }
 
@@ -628,12 +762,24 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
     // Call the trigger
     log::debug!("decoded scope_id: {}, node_id: {:#?}", gi_id, real_id);
 
-    let triggered_scope: ScopeId = KeyData::from_ffi(gi_id).into();
+    let triggered_scope = gi_id;
+    // let triggered_scope: ScopeId = KeyData::from_ffi(gi_id).into();
     log::debug!("Triggered scope is {:#?}", triggered_scope);
     Ok(EventTrigger::new(
         virtual_event_from_websys_event(event),
-        triggered_scope,
-        Some(RealDomNode::from_u64(real_id)),
+        ScopeId(triggered_scope as usize),
+        Some(ElementId(real_id as usize)),
         dioxus_core::events::EventPriority::High,
     ))
 }
+
+pub fn prepare_websys_dom() -> Element {
+    load_document().get_element_by_id("dioxusroot").unwrap()
+}
+
+pub fn load_document() -> Document {
+    web_sys::window()
+        .expect("should have access to the Window")
+        .document()
+        .expect("should have access to the Document")
+}

+ 89 - 65
packages/web/src/lib.rs

@@ -2,38 +2,92 @@
 //! --------------
 //! 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
+*/
+
+use std::rc::Rc;
+
+pub use crate::cfg::WebConfig;
+use crate::dom::load_document;
 use dioxus::prelude::{Context, Properties, VNode};
 use dioxus::virtual_dom::VirtualDom;
 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 web_sys::{window, Document, Element, Event, Node};
+use js_sys::Iterator;
+use web_sys::{window, Document, Element, Event, Node, NodeList};
 
 mod cache;
-mod new;
+mod cfg;
+mod dom;
+mod nodeslab;
 
 /// Launches the VirtualDOM from the specified component function.
 ///
 /// This method will block the thread with `spawn_local`
+///
+/// # Example
+///
+///
+///
 pub fn launch<F>(root: FC<()>, config: F)
 where
-    F: FnOnce(()),
+    F: FnOnce(WebConfig) -> WebConfig,
 {
-    wasm_bindgen_futures::spawn_local(run(root))
+    launch_with_props(root, (), config)
 }
 
+/// Launches the VirtualDOM from the specified component function and props.
+///
+/// This method will block the thread with `spawn_local`
+///
+/// # Example
+///
+///
 pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
 where
     T: Properties + 'static,
-    F: FnOnce(()),
+    F: FnOnce(WebConfig) -> WebConfig,
 {
-    wasm_bindgen_futures::spawn_local(run_with_props(root, root_props))
+    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),
+        }
+    });
 }
 
 /// 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.
 ///
+/// # Example
+///
 /// ```ignore
 /// fn main() {
 ///     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
@@ -42,73 +96,43 @@ where
 ///
 /// Run the app to completion, panicing if any error occurs while rendering.
 /// Pairs well with the wasm_bindgen async handler
-pub async fn run(root: FC<()>) {
-    run_with_props(root, ()).await;
-}
+pub async fn run_with_props<T: Properties + 'static>(
+    root: FC<T>,
+    root_props: T,
+    cfg: WebConfig,
+) -> Result<()> {
+    let mut dom = VirtualDom::new_with_props(root, root_props);
 
-pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) {
-    let dom = VirtualDom::new_with_props(root, root_props);
-    event_loop(dom).await.expect("Event loop failed");
-}
-
-pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> {
-    use wasm_bindgen::JsCast;
-
-    let root = prepare_websys_dom();
-    let root_node = root.clone().dyn_into::<Node>().unwrap();
-
-    let mut websys_dom = crate::new::WebsysDom::new(root.clone());
-
-    websys_dom.stack.push(root_node.clone());
-    websys_dom.stack.push(root_node);
-
-    let mut edits = Vec::new();
-    internal_dom.rebuild(&mut websys_dom, &mut edits)?;
-    websys_dom.process_edits(&mut edits);
-
-    log::info!("Going into event loop");
-    loop {
-        let trigger = {
-            let real_queue = websys_dom.wait_for_event();
-            if internal_dom.tasks.is_empty() {
-                log::info!("tasks is empty, waiting for dom event to trigger soemthing");
-                real_queue.await
-            } else {
-                log::info!("tasks is not empty, waiting for either tasks or event system");
-                let task_queue = (&mut internal_dom.tasks).next();
+    let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
 
-                pin_mut!(real_queue);
-                pin_mut!(task_queue);
+    let tasks = dom.get_event_sender();
 
-                match futures_util::future::select(real_queue, task_queue).await {
-                    futures_util::future::Either::Left((trigger, _)) => trigger,
-                    futures_util::future::Either::Right((trigger, _)) => trigger,
-                }
-            }
-        };
+    let mut real = RealDomWebsys {};
 
-        if let Some(real_trigger) = trigger {
-            log::info!("event received");
+    // initialize the virtualdom first
+    if cfg.hydrate {
+        dom.rebuild_in_place()?;
+    }
 
-            internal_dom.queue_event(real_trigger)?;
+    let mut websys_dom = dom::WebsysDom::new(
+        root_el,
+        cfg,
+        Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
+    );
 
-            let mut edits = Vec::new();
-            internal_dom
-                .progress_with_event(&mut websys_dom, &mut edits)
-                .await?;
-            websys_dom.process_edits(&mut edits);
-        }
-    }
+    dom.run_with_deadline(&mut websys_dom).await?;
 
-    // should actually never return from this, should be an error, rustc just cant see it
     Ok(())
 }
 
-fn prepare_websys_dom() -> Element {
-    web_sys::window()
-        .expect("should have access to the Window")
-        .document()
-        .expect("should have access to the Document")
-        .get_element_by_id("dioxusroot")
-        .unwrap()
+struct HydrationNode {
+    id: usize,
+    node: Node,
+}
+
+struct RealDomWebsys {}
+impl dioxus::RealDom for RealDomWebsys {
+    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
+        todo!()
+    }
 }

+ 37 - 0
packages/web/src/nodeslab.rs

@@ -0,0 +1,37 @@
+use std::ops::{Index, IndexMut};
+
+use web_sys::Node;
+
+pub struct NodeSlab {
+    nodes: Vec<Option<Node>>,
+}
+
+impl NodeSlab {
+    pub fn new(capacity: usize) -> NodeSlab {
+        let mut nodes = Vec::with_capacity(capacity);
+        for x in 0..5 {
+            nodes.push(None);
+        }
+
+        NodeSlab { nodes }
+    }
+}
+impl Index<usize> for NodeSlab {
+    type Output = Option<Node>;
+    fn index(&self, index: usize) -> &Self::Output {
+        &self.nodes[index]
+    }
+}
+
+impl IndexMut<usize> for NodeSlab {
+    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+        if index >= self.nodes.capacity() * 3 {
+            panic!("Trying to mutate an element way too far out of bounds");
+        }
+
+        if index + 1 > self.nodes.len() {
+            self.nodes.resize_with(index + 1, || None);
+        }
+        &mut self.nodes[index]
+    }
+}