Browse Source

Merge pull request #600 from DioxusLabs/jk/templates-v3

Template architecture, async components, inline iterators, error boundaries, multiple renderers
Jon Kelley 2 years ago
parent
commit
2b4d19247c
100 changed files with 7083 additions and 7506 deletions
  1. 9 6
      Cargo.toml
  2. 2 2
      docs/guide/examples/event_handler_prop.rs
  3. 2 2
      docs/guide/examples/meme_editor.rs
  4. 2 2
      docs/guide/examples/meme_editor_dark_mode.rs
  5. 401 399
      examples/all_css.rs
  6. 1 1
      examples/all_events.rs
  7. 2 6
      examples/borrowed.rs
  8. 10 22
      examples/calculator.rs
  9. 22 0
      examples/callback.rs
  10. 9 7
      examples/custom_element.rs
  11. 26 34
      examples/dog_app.rs
  12. 12 0
      examples/error_handle.rs
  13. 1 1
      examples/eval.rs
  14. 1 1
      examples/fermi.rs
  15. 2 2
      examples/file_explorer.rs
  16. 1 1
      examples/framework_benchmark.rs
  17. 13 0
      examples/generic_component.rs
  18. 1 1
      examples/hydration.rs
  19. 3 5
      examples/inputs.rs
  20. 2 4
      examples/login_form.rs
  21. 1 1
      examples/nested_listeners.rs
  22. 1 0
      examples/pattern_model.rs
  23. 2 2
      examples/router.rs
  24. 4 3
      examples/rsx_compile_fail.rs
  25. 3 3
      examples/rsx_usage.rs
  26. 14 11
      examples/simple_list.rs
  27. 3 2
      examples/ssr.rs
  28. 14 12
      examples/svg.rs
  29. 8 6
      examples/todomvc.rs
  30. 6 6
      examples/window_event.rs
  31. 5 1
      packages/core-macro/src/inlineprops.rs
  32. 4 26
      packages/core-macro/src/lib.rs
  33. 1 1
      packages/core-macro/src/props/mod.rs
  34. 4 17
      packages/core/Cargo.toml
  35. 64 46
      packages/core/README.md
  36. 5 0
      packages/core/architecture.md
  37. 73 0
      packages/core/src/any_props.rs
  38. 0 642
      packages/core/src/arbitrary_value.rs
  39. 163 0
      packages/core/src/arena.rs
  40. 29 0
      packages/core/src/bump_frame.rs
  41. 401 0
      packages/core/src/create.rs
  42. 538 1115
      packages/core/src/diff.rs
  43. 19 0
      packages/core/src/dirty_scope.rs
  44. 0 190
      packages/core/src/dynamic_template_context.rs
  45. 19 0
      packages/core/src/error_boundary.rs
  46. 126 144
      packages/core/src/events.rs
  47. 103 0
      packages/core/src/fragment.rs
  48. 19 91
      packages/core/src/lazynodes.rs
  49. 34 83
      packages/core/src/lib.rs
  50. 186 333
      packages/core/src/mutations.rs
  51. 437 810
      packages/core/src/nodes.rs
  52. 1 96
      packages/core/src/properties.rs
  53. 48 0
      packages/core/src/scheduler/mod.rs
  54. 52 0
      packages/core/src/scheduler/suspense.rs
  55. 66 0
      packages/core/src/scheduler/task.rs
  56. 106 0
      packages/core/src/scheduler/wait.rs
  57. 36 0
      packages/core/src/scheduler/waker.rs
  58. 146 0
      packages/core/src/scope_arena.rs
  59. 302 881
      packages/core/src/scopes.rs
  60. 30 0
      packages/core/src/subtree.rs
  61. 0 1302
      packages/core/src/template.rs
  62. 0 127
      packages/core/src/util.rs
  63. 401 593
      packages/core/src/virtual_dom.rs
  64. 2 0
      packages/core/tests/.rustfmt.toml
  65. 2 7
      packages/core/tests/README.md
  66. 75 0
      packages/core/tests/attr_cleanup.rs
  67. 15 0
      packages/core/tests/boolattrs.rs
  68. 55 0
      packages/core/tests/borrowedstate.rs
  69. 28 0
      packages/core/tests/bubble_error.rs
  70. 51 0
      packages/core/tests/context_api.rs
  71. 197 0
      packages/core/tests/create_dom.rs
  72. 32 0
      packages/core/tests/create_element.rs
  73. 104 0
      packages/core/tests/create_fragments.rs
  74. 72 0
      packages/core/tests/create_lists.rs
  75. 106 0
      packages/core/tests/create_passthru.rs
  76. 51 0
      packages/core/tests/cycle.rs
  77. 102 0
      packages/core/tests/diff_component.rs
  78. 84 0
      packages/core/tests/diff_element.rs
  79. 356 0
      packages/core/tests/diff_keyed_list.rs
  80. 379 0
      packages/core/tests/diff_unkeyed_list.rs
  81. 1 0
      packages/core/tests/hotreloading.rs
  82. 42 0
      packages/core/tests/kitchen_sink.rs
  83. 168 0
      packages/core/tests/lifecycle.rs
  84. 49 0
      packages/core/tests/miri_full_app.rs
  85. 127 0
      packages/core/tests/miri_simple.rs
  86. 351 0
      packages/core/tests/miri_stress.rs
  87. 24 0
      packages/core/tests/safety.rs
  88. 92 0
      packages/core/tests/suspense.rs
  89. 42 0
      packages/core/tests/task.rs
  90. 6 4
      packages/desktop/Cargo.toml
  91. 50 38
      packages/desktop/src/controller.rs
  92. 100 88
      packages/desktop/src/desktop_context.rs
  93. 56 186
      packages/desktop/src/events.rs
  94. 14 9
      packages/desktop/src/hot_reload.rs
  95. 128 111
      packages/desktop/src/lib.rs
  96. 1 1
      packages/desktop/src/protocol.rs
  97. 3 6
      packages/dioxus/Cargo.toml
  98. 0 1
      packages/dioxus/benches/create.rs
  99. 21 15
      packages/dioxus/benches/jsframework.rs
  100. 101 0
      packages/dioxus/examples/stress.rs

+ 9 - 6
Cargo.toml

@@ -1,4 +1,3 @@
-
 [workspace]
 members = [
     "packages/dioxus",
@@ -13,16 +12,20 @@ members = [
     "packages/mobile",
     "packages/interpreter",
     "packages/fermi",
-    "packages/tui",
     "packages/liveview",
     "packages/autofmt",
     "packages/rsx",
+    "docs/guide",
+    # "packages/tui",
+    # "packages/native-core",
+    # "packages/native-core-macro",
+]
+exclude = [
+    "packages/tui",
     "packages/native-core",
     "packages/native-core-macro",
-    "docs/guide",
 ]
 
-
 # This is a "virtual package"
 # It is not meant to be published, but is used so "cargo run --example XYZ" works properly
 [package]
@@ -41,7 +44,7 @@ rust-version = "1.60.0"
 
 [dev-dependencies]
 dioxus = { path = "./packages/dioxus" }
-dioxus-desktop = { path = "./packages/desktop", features = ["hot-reload"] }
+dioxus-desktop = { path = "./packages/desktop" }
 dioxus-ssr = { path = "./packages/ssr" }
 dioxus-router = { path = "./packages/router" }
 fermi = { path = "./packages/fermi" }
@@ -63,4 +66,4 @@ env_logger = "0.9.0"
 [profile.release]
 opt-level = 3
 lto = true
-debug = true
+debug = true

+ 2 - 2
docs/guide/examples/event_handler_prop.rs

@@ -1,6 +1,6 @@
 #![allow(non_snake_case)]
 
-use dioxus::events::MouseEvent;
+use dioxus::events::MouseData;
 use dioxus::prelude::*;
 
 fn main() {
@@ -20,7 +20,7 @@ fn App(cx: Scope) -> Element {
 // ANCHOR: component_with_handler
 #[derive(Props)]
 pub struct FancyButtonProps<'a> {
-    on_click: EventHandler<'a, MouseEvent>,
+    on_click: EventHandler<'a, MouseData>,
 }
 
 pub fn FancyButton<'a>(cx: Scope<'a, FancyButtonProps<'a>>) -> Element<'a> {

+ 2 - 2
docs/guide/examples/meme_editor.rs

@@ -1,7 +1,7 @@
 // ANCHOR: all
 #![allow(non_snake_case)]
 
-use dioxus::events::FormEvent;
+use dioxus::events::FormData;
 use dioxus::prelude::*;
 
 fn main() {
@@ -83,7 +83,7 @@ fn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {
 fn CaptionEditor<'a>(
     cx: Scope<'a>,
     caption: &'a str,
-    on_input: EventHandler<'a, FormEvent>,
+    on_input: EventHandler<'a, FormData>,
 ) -> Element<'a> {
     let input_style = r"
         border: none;

+ 2 - 2
docs/guide/examples/meme_editor_dark_mode.rs

@@ -1,7 +1,7 @@
 // ANCHOR: all
 #![allow(non_snake_case)]
 
-use dioxus::events::FormEvent;
+use dioxus::events::FormData;
 use dioxus::prelude::*;
 
 fn main() {
@@ -150,7 +150,7 @@ fn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {
 fn CaptionEditor<'a>(
     cx: Scope<'a>,
     caption: &'a str,
-    on_input: EventHandler<'a, FormEvent>,
+    on_input: EventHandler<'a, FormData>,
 ) -> Element<'a> {
     let is_dark_mode = use_is_dark_mode(&cx);
 

+ 401 - 399
examples/all_css.rs

@@ -7,405 +7,407 @@ fn main() {
 fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div {
-          align_content: "a",
-          align_items: "a",
-          align_self: "a",
-          alignment_adjust: "a",
-          alignment_baseline: "a",
-          all: "a",
-          alt: "a",
-          animation: "a",
-          animation_delay: "a",
-          animation_direction: "a",
-          animation_duration: "a",
-          animation_fill_mode: "a",
-          animation_iteration_count: "a",
-          animation_name: "a",
-          animation_play_state: "a",
-          animation_timing_function: "a",
-          azimuth: "a",
-          backface_visibility: "a",
-          background: "a",
-          background_attachment: "a",
-          background_clip: "a",
-          background_color: "a",
-          background_image: "a",
-          background_origin: "a",
-          background_position: "a",
-          background_repeat: "a",
-          background_size: "a",
-          background_blend_mode: "a",
-          baseline_shift: "a",
-          bleed: "a",
-          bookmark_label: "a",
-          bookmark_level: "a",
-          bookmark_state: "a",
-          border: "a",
-          border_color: "a",
-          border_style: "a",
-          border_width: "a",
-          border_bottom: "a",
-          border_bottom_color: "a",
-          border_bottom_style: "a",
-          border_bottom_width: "a",
-          border_left: "a",
-          border_left_color: "a",
-          border_left_style: "a",
-          border_left_width: "a",
-          border_right: "a",
-          border_right_color: "a",
-          border_right_style: "a",
-          border_right_width: "a",
-          border_top: "a",
-          border_top_color: "a",
-          border_top_style: "a",
-          border_top_width: "a",
-          border_collapse: "a",
-          border_image: "a",
-          border_image_outset: "a",
-          border_image_repeat: "a",
-          border_image_slice: "a",
-          border_image_source: "a",
-          border_image_width: "a",
-          border_radius: "a",
-          border_bottom_left_radius: "a",
-          border_bottom_right_radius: "a",
-          border_top_left_radius: "a",
-          border_top_right_radius: "a",
-          border_spacing: "a",
-          bottom: "a",
-          box_decoration_break: "a",
-          box_shadow: "a",
-          box_sizing: "a",
-          box_snap: "a",
-          break_after: "a",
-          break_before: "a",
-          break_inside: "a",
-          buffered_rendering: "a",
-          caption_side: "a",
-          clear: "a",
-          clear_side: "a",
-          clip: "a",
-          clip_path: "a",
-          clip_rule: "a",
-          color: "a",
-          color_adjust: "a",
-          color_correction: "a",
-          color_interpolation: "a",
-          color_interpolation_filters: "a",
-          color_profile: "a",
-          color_rendering: "a",
-          column_fill: "a",
-          column_gap: "a",
-          column_rule: "a",
-          column_rule_color: "a",
-          column_rule_style: "a",
-          column_rule_width: "a",
-          column_span: "a",
-          columns: "a",
-          column_count: "a",
-          column_width: "a",
-          contain: "a",
-          content: "a",
-          counter_increment: "a",
-          counter_reset: "a",
-          counter_set: "a",
-          cue: "a",
-          cue_after: "a",
-          cue_before: "a",
-          cursor: "a",
-          direction: "a",
-          display: "a",
-          display_inside: "a",
-          display_outside: "a",
-          display_extras: "a",
-          display_box: "a",
-          dominant_baseline: "a",
-          elevation: "a",
-          empty_cells: "a",
-          enable_background: "a",
-          fill: "a",
-          fill_opacity: "a",
-          fill_rule: "a",
-          filter: "a",
-          float: "a",
-          float_defer_column: "a",
-          float_defer_page: "a",
-          float_offset: "a",
-          float_wrap: "a",
-          flow_into: "a",
-          flow_from: "a",
-          flex: "a",
-          flex_basis: "a",
-          flex_grow: "a",
-          flex_shrink: "a",
-          flex_flow: "a",
-          flex_direction: "a",
-          flex_wrap: "a",
-          flood_color: "a",
-          flood_opacity: "a",
-          font: "a",
-          font_family: "a",
-          font_size: "a",
-          font_stretch: "a",
-          font_style: "a",
-          font_weight: "a",
-          font_feature_settings: "a",
-          font_kerning: "a",
-          font_language_override: "a",
-          font_size_adjust: "a",
-          font_synthesis: "a",
-          font_variant: "a",
-          font_variant_alternates: "a",
-          font_variant_caps: "a",
-          font_variant_east_asian: "a",
-          font_variant_ligatures: "a",
-          font_variant_numeric: "a",
-          font_variant_position: "a",
-          footnote_policy: "a",
-          glyph_orientation_horizontal: "a",
-          glyph_orientation_vertical: "a",
-          grid: "a",
-          grid_auto_flow: "a",
-          grid_auto_columns: "a",
-          grid_auto_rows: "a",
-          grid_template: "a",
-          grid_template_areas: "a",
-          grid_template_columns: "a",
-          grid_template_rows: "a",
-          grid_area: "a",
-          grid_column: "a",
-          grid_column_start: "a",
-          grid_column_end: "a",
-          grid_row: "a",
-          grid_row_start: "a",
-          grid_row_end: "a",
-          hanging_punctuation: "a",
-          height: "a",
-          hyphenate_character: "a",
-          hyphenate_limit_chars: "a",
-          hyphenate_limit_last: "a",
-          hyphenate_limit_lines: "a",
-          hyphenate_limit_zone: "a",
-          hyphens: "a",
-          icon: "a",
-          image_orientation: "a",
-          image_resolution: "a",
-          image_rendering: "a",
-          ime: "a",
-          ime_align: "a",
-          ime_mode: "a",
-          ime_offset: "a",
-          ime_width: "a",
-          initial_letters: "a",
-          inline_box_align: "a",
-          isolation: "a",
-          justify_content: "a",
-          justify_items: "a",
-          justify_self: "a",
-          kerning: "a",
-          left: "a",
-          letter_spacing: "a",
-          lighting_color: "a",
-          line_box_contain: "a",
-          line_break: "a",
-          line_grid: "a",
-          line_height: "a",
-          line_slack: "a",
-          line_snap: "a",
-          list_style: "a",
-          list_style_image: "a",
-          list_style_position: "a",
-          list_style_type: "a",
-          margin: "a",
-          margin_bottom: "a",
-          margin_left: "a",
-          margin_right: "a",
-          margin_top: "a",
-          marker: "a",
-          marker_end: "a",
-          marker_mid: "a",
-          marker_pattern: "a",
-          marker_segment: "a",
-          marker_start: "a",
-          marker_knockout_left: "a",
-          marker_knockout_right: "a",
-          marker_side: "a",
-          marks: "a",
-          marquee_direction: "a",
-          marquee_play_count: "a",
-          marquee_speed: "a",
-          marquee_style: "a",
-          mask: "a",
-          mask_image: "a",
-          mask_repeat: "a",
-          mask_position: "a",
-          mask_clip: "a",
-          mask_origin: "a",
-          mask_size: "a",
-          mask_box: "a",
-          mask_box_outset: "a",
-          mask_box_repeat: "a",
-          mask_box_slice: "a",
-          mask_box_source: "a",
-          mask_box_width: "a",
-          mask_type: "a",
-          max_height: "a",
-          max_lines: "a",
-          max_width: "a",
-          min_height: "a",
-          min_width: "a",
-          mix_blend_mode: "a",
-          nav_down: "a",
-          nav_index: "a",
-          nav_left: "a",
-          nav_right: "a",
-          nav_up: "a",
-          object_fit: "a",
-          object_position: "a",
-          offset_after: "a",
-          offset_before: "a",
-          offset_end: "a",
-          offset_start: "a",
-          opacity: "a",
-          order: "a",
-          orphans: "a",
-          outline: "a",
-          outline_color: "a",
-          outline_style: "a",
-          outline_width: "a",
-          outline_offset: "a",
-          overflow: "a",
-          overflow_x: "a",
-          overflow_y: "a",
-          overflow_style: "a",
-          overflow_wrap: "a",
-          padding: "a",
-          padding_bottom: "a",
-          padding_left: "a",
-          padding_right: "a",
-          padding_top: "a",
-          page: "a",
-          page_break_after: "a",
-          page_break_before: "a",
-          page_break_inside: "a",
-          paint_order: "a",
-          pause: "a",
-          pause_after: "a",
-          pause_before: "a",
-          perspective: "a",
-          perspective_origin: "a",
-          pitch: "a",
-          pitch_range: "a",
-          play_during: "a",
-          pointer_events: "a",
-          position: "a",
-          quotes: "a",
-          region_fragment: "a",
-          resize: "a",
-          rest: "a",
-          rest_after: "a",
-          rest_before: "a",
-          richness: "a",
-          right: "a",
-          ruby_align: "a",
-          ruby_merge: "a",
-          ruby_position: "a",
-          scroll_behavior: "a",
-          scroll_snap_coordinate: "a",
-          scroll_snap_destination: "a",
-          scroll_snap_points_x: "a",
-          scroll_snap_points_y: "a",
-          scroll_snap_type: "a",
-          shape_image_threshold: "a",
-          shape_inside: "a",
-          shape_margin: "a",
-          shape_outside: "a",
-          shape_padding: "a",
-          shape_rendering: "a",
-          size: "a",
-          speak: "a",
-          speak_as: "a",
-          speak_header: "a",
-          speak_numeral: "a",
-          speak_punctuation: "a",
-          speech_rate: "a",
-          stop_color: "a",
-          stop_opacity: "a",
-          stress: "a",
-          string_set: "a",
-          stroke: "a",
-          stroke_dasharray: "a",
-          stroke_dashoffset: "a",
-          stroke_linecap: "a",
-          stroke_linejoin: "a",
-          stroke_miterlimit: "a",
-          stroke_opacity: "a",
-          stroke_width: "a",
-          tab_size: "a",
-          table_layout: "a",
-          text_align: "a",
-          text_align_all: "a",
-          text_align_last: "a",
-          text_anchor: "a",
-          text_combine_upright: "a",
-          text_decoration: "a",
-          text_decoration_color: "a",
-          text_decoration_line: "a",
-          text_decoration_style: "a",
-          text_decoration_skip: "a",
-          text_emphasis: "a",
-          text_emphasis_color: "a",
-          text_emphasis_style: "a",
-          text_emphasis_position: "a",
-          text_emphasis_skip: "a",
-          text_height: "a",
-          text_indent: "a",
-          text_justify: "a",
-          text_orientation: "a",
-          text_overflow: "a",
-          text_rendering: "a",
-          text_shadow: "a",
-          text_size_adjust: "a",
-          text_space_collapse: "a",
-          text_spacing: "a",
-          text_transform: "a",
-          text_underline_position: "a",
-          text_wrap: "a",
-          top: "a",
-          touch_action: "a",
-          transform: "a",
-          transform_box: "a",
-          transform_origin: "a",
-          transform_style: "a",
-          transition: "a",
-          transition_delay: "a",
-          transition_duration: "a",
-          transition_property: "a",
-          unicode_bidi: "a",
-          vector_effect: "a",
-          vertical_align: "a",
-          visibility: "a",
-          voice_balance: "a",
-          voice_duration: "a",
-          voice_family: "a",
-          voice_pitch: "a",
-          voice_range: "a",
-          voice_rate: "a",
-          voice_stress: "a",
-          voice_volumn: "a",
-          volume: "a",
-          white_space: "a",
-          widows: "a",
-          width: "a",
-          will_change: "a",
-          word_break: "a",
-          word_spacing: "a",
-          word_wrap: "a",
-          wrap_flow: "a",
-          wrap_through: "a",
-          writing_mode: "a",
-          z_index: "a",
+            align_content: "a",
+            align_items: "a",
+            align_self: "a",
+            alignment_adjust: "a",
+            alignment_baseline: "a",
+            all: "a",
+            alt: "a",
+            animation: "a",
+            animation_delay: "a",
+            animation_direction: "a",
+            animation_duration: "a",
+            animation_fill_mode: "a",
+            animation_iteration_count: "a",
+            animation_name: "a",
+            animation_play_state: "a",
+            animation_timing_function: "a",
+            azimuth: "a",
+            backface_visibility: "a",
+            background: "a",
+            background_attachment: "a",
+            background_clip: "a",
+            background_color: "a",
+            background_image: "a",
+            background_origin: "a",
+            background_position: "a",
+            background_repeat: "a",
+            background_size: "a",
+            background_blend_mode: "a",
+            baseline_shift: "a",
+            bleed: "a",
+            bookmark_label: "a",
+            bookmark_level: "a",
+            bookmark_state: "a",
+            border: "a",
+            border_color: "a",
+            border_style: "a",
+            border_width: "a",
+            border_bottom: "a",
+            border_bottom_color: "a",
+            border_bottom_style: "a",
+            border_bottom_width: "a",
+            border_left: "a",
+            border_left_color: "a",
+            border_left_style: "a",
+            border_left_width: "a",
+            border_right: "a",
+            border_right_color: "a",
+            border_right_style: "a",
+            border_right_width: "a",
+            border_top: "a",
+            border_top_color: "a",
+            border_top_style: "a",
+            border_top_width: "a",
+            border_collapse: "a",
+            border_image: "a",
+            border_image_outset: "a",
+            border_image_repeat: "a",
+            border_image_slice: "a",
+            border_image_source: "a",
+            border_image_width: "a",
+            border_radius: "a",
+            border_bottom_left_radius: "a",
+            border_bottom_right_radius: "a",
+            border_top_left_radius: "a",
+            border_top_right_radius: "a",
+            border_spacing: "a",
+            bottom: "a",
+            box_decoration_break: "a",
+            box_shadow: "a",
+            box_sizing: "a",
+            box_snap: "a",
+            break_after: "a",
+            break_before: "a",
+            break_inside: "a",
+            buffered_rendering: "a",
+            caption_side: "a",
+            clear: "a",
+            clear_side: "a",
+            clip: "a",
+            clip_path: "a",
+            clip_rule: "a",
+            color: "a",
+            color_adjust: "a",
+            color_correction: "a",
+            color_interpolation: "a",
+            color_interpolation_filters: "a",
+            color_profile: "a",
+            color_rendering: "a",
+            column_fill: "a",
+            column_gap: "a",
+            column_rule: "a",
+            column_rule_color: "a",
+            column_rule_style: "a",
+            column_rule_width: "a",
+            column_span: "a",
+            columns: "a",
+            column_count: "a",
+            column_width: "a",
+            contain: "a",
+            content: "a",
+            counter_increment: "a",
+            counter_reset: "a",
+            counter_set: "a",
+            cue: "a",
+            cue_after: "a",
+            cue_before: "a",
+            cursor: "a",
+            direction: "a",
+            display: "a",
+            display_inside: "a",
+            display_outside: "a",
+            display_extras: "a",
+            display_box: "a",
+            dominant_baseline: "a",
+            elevation: "a",
+            empty_cells: "a",
+            enable_background: "a",
+            fill: "a",
+            fill_opacity: "a",
+            fill_rule: "a",
+            filter: "a",
+            float: "a",
+            float_defer_column: "a",
+            float_defer_page: "a",
+            float_offset: "a",
+            float_wrap: "a",
+            flow_into: "a",
+            flow_from: "a",
+            flex: "a",
+            flex_basis: "a",
+            flex_grow: "a",
+            flex_shrink: "a",
+            flex_flow: "a",
+            flex_direction: "a",
+            flex_wrap: "a",
+            flood_color: "a",
+            flood_opacity: "a",
+            font: "a",
+            font_family: "a",
+            font_size: "a",
+            font_stretch: "a",
+            font_style: "a",
+            font_weight: "a",
+            font_feature_settings: "a",
+            font_kerning: "a",
+            font_language_override: "a",
+            font_size_adjust: "a",
+            font_synthesis: "a",
+            font_variant: "a",
+            font_variant_alternates: "a",
+            font_variant_caps: "a",
+            font_variant_east_asian: "a",
+            font_variant_ligatures: "a",
+            font_variant_numeric: "a",
+            font_variant_position: "a",
+            footnote_policy: "a",
+            glyph_orientation_horizontal: "a",
+            glyph_orientation_vertical: "a",
+            grid: "a",
+            grid_auto_flow: "a",
+            grid_auto_columns: "a",
+            grid_auto_rows: "a",
+            grid_template: "a",
+            grid_template_areas: "a",
+            grid_template_columns: "a",
+            grid_template_rows: "a",
+            grid_area: "a",
+            grid_column: "a",
+            grid_column_start: "a",
+            grid_column_end: "a",
+            grid_row: "a",
+            grid_row_start: "a",
+            grid_row_end: "a",
+            hanging_punctuation: "a",
+            height: "a",
+            hyphenate_character: "a",
+            hyphenate_limit_chars: "a",
+            hyphenate_limit_last: "a",
+            hyphenate_limit_lines: "a",
+            hyphenate_limit_zone: "a",
+            hyphens: "a",
+            icon: "a",
+            image_orientation: "a",
+            image_resolution: "a",
+            image_rendering: "a",
+            ime: "a",
+            ime_align: "a",
+            ime_mode: "a",
+            ime_offset: "a",
+            ime_width: "a",
+            initial_letters: "a",
+            inline_box_align: "a",
+            isolation: "a",
+            justify_content: "a",
+            justify_items: "a",
+            justify_self: "a",
+            kerning: "a",
+            left: "a",
+            letter_spacing: "a",
+            lighting_color: "a",
+            line_box_contain: "a",
+            line_break: "a",
+            line_grid: "a",
+            line_height: "a",
+            line_slack: "a",
+            line_snap: "a",
+            list_style: "a",
+            list_style_image: "a",
+            list_style_position: "a",
+            list_style_type: "a",
+            margin: "a",
+            margin_bottom: "a",
+            margin_left: "a",
+            margin_right: "a",
+            margin_top: "a",
+            marker: "a",
+            marker_end: "a",
+            marker_mid: "a",
+            marker_pattern: "a",
+            marker_segment: "a",
+            marker_start: "a",
+            marker_knockout_left: "a",
+            marker_knockout_right: "a",
+            marker_side: "a",
+            marks: "a",
+            marquee_direction: "a",
+            marquee_play_count: "a",
+            marquee_speed: "a",
+            marquee_style: "a",
+            mask: "a",
+            mask_image: "a",
+            mask_repeat: "a",
+            mask_position: "a",
+            mask_clip: "a",
+            mask_origin: "a",
+            mask_size: "a",
+            mask_box: "a",
+            mask_box_outset: "a",
+            mask_box_repeat: "a",
+            mask_box_slice: "a",
+            mask_box_source: "a",
+            mask_box_width: "a",
+            mask_type: "a",
+            max_height: "a",
+            max_lines: "a",
+            max_width: "a",
+            min_height: "a",
+            min_width: "a",
+            mix_blend_mode: "a",
+            nav_down: "a",
+            nav_index: "a",
+            nav_left: "a",
+            nav_right: "a",
+            nav_up: "a",
+            object_fit: "a",
+            object_position: "a",
+            offset_after: "a",
+            offset_before: "a",
+            offset_end: "a",
+            offset_start: "a",
+            opacity: "a",
+            order: "a",
+            orphans: "a",
+            outline: "a",
+            outline_color: "a",
+            outline_style: "a",
+            outline_width: "a",
+            outline_offset: "a",
+            overflow: "a",
+            overflow_x: "a",
+            overflow_y: "a",
+            overflow_style: "a",
+            overflow_wrap: "a",
+            padding: "a",
+            padding_bottom: "a",
+            padding_left: "a",
+            padding_right: "a",
+            padding_top: "a",
+            page: "a",
+            page_break_after: "a",
+            page_break_before: "a",
+            page_break_inside: "a",
+            paint_order: "a",
+            pause: "a",
+            pause_after: "a",
+            pause_before: "a",
+            perspective: "a",
+            perspective_origin: "a",
+            pitch: "a",
+            pitch_range: "a",
+            play_during: "a",
+            pointer_events: "a",
+            position: "a",
+            quotes: "a",
+            region_fragment: "a",
+            resize: "a",
+            rest: "a",
+            rest_after: "a",
+            rest_before: "a",
+            richness: "a",
+            right: "a",
+            ruby_align: "a",
+            ruby_merge: "a",
+            ruby_position: "a",
+            scroll_behavior: "a",
+            scroll_snap_coordinate: "a",
+            scroll_snap_destination: "a",
+            scroll_snap_points_x: "a",
+            scroll_snap_points_y: "a",
+            scroll_snap_type: "a",
+            shape_image_threshold: "a",
+            shape_inside: "a",
+            shape_margin: "a",
+            shape_outside: "a",
+            shape_padding: "a",
+            shape_rendering: "a",
+            size: "a",
+            speak: "a",
+            speak_as: "a",
+            speak_header: "a",
+            speak_numeral: "a",
+            speak_punctuation: "a",
+            speech_rate: "a",
+            stop_color: "a",
+            stop_opacity: "a",
+            stress: "a",
+            string_set: "a",
+            stroke: "a",
+            stroke_dasharray: "a",
+            stroke_dashoffset: "a",
+            stroke_linecap: "a",
+            stroke_linejoin: "a",
+            stroke_miterlimit: "a",
+            stroke_opacity: "a",
+            stroke_width: "a",
+            tab_size: "a",
+            table_layout: "a",
+            text_align: "a",
+            text_align_all: "a",
+            text_align_last: "a",
+            text_anchor: "a",
+            text_combine_upright: "a",
+            text_decoration: "a",
+            text_decoration_color: "a",
+            text_decoration_line: "a",
+            text_decoration_style: "a",
+            text_decoration_skip: "a",
+            text_emphasis: "a",
+            text_emphasis_color: "a",
+            text_emphasis_style: "a",
+            text_emphasis_position: "a",
+            text_emphasis_skip: "a",
+            text_height: "a",
+            text_indent: "a",
+            text_justify: "a",
+            text_orientation: "a",
+            text_overflow: "a",
+            text_rendering: "a",
+            text_shadow: "a",
+            text_size_adjust: "a",
+            text_space_collapse: "a",
+            text_spacing: "a",
+            text_transform: "a",
+            text_underline_position: "a",
+            text_wrap: "a",
+            top: "a",
+            touch_action: "a",
+            transform: "a",
+            transform_box: "a",
+            transform_origin: "a",
+            transform_style: "a",
+            transition: "a",
+            transition_delay: "a",
+            transition_duration: "a",
+            transition_property: "a",
+            unicode_bidi: "a",
+            vector_effect: "a",
+            vertical_align: "a",
+            visibility: "a",
+            voice_balance: "a",
+            voice_duration: "a",
+            voice_family: "a",
+            voice_pitch: "a",
+            voice_range: "a",
+            voice_rate: "a",
+            voice_stress: "a",
+            voice_volumn: "a",
+            volume: "a",
+            white_space: "a",
+            widows: "a",
+            width: "a",
+            will_change: "a",
+            word_break: "a",
+            word_spacing: "a",
+            word_wrap: "a",
+            wrap_flow: "a",
+            wrap_through: "a",
+            writing_mode: "a",
+            z_index: "a",
+
+            "This example isn't quite useful yet"
         }
     })
 }

+ 1 - 1
examples/all_events.rs

@@ -1,4 +1,4 @@
-use dioxus::{events::*, prelude::*};
+use dioxus::{events::*, html::MouseEvent, prelude::*};
 
 fn main() {
     dioxus_desktop::launch(app);

+ 2 - 6
examples/borrowed.rs

@@ -29,9 +29,7 @@ fn app(cx: Scope) -> Element {
 
     cx.render(rsx! {
         div {
-            Child1 {
-                text: first
-            }
+            Child1 { text: first }
         }
     })
 }
@@ -59,9 +57,7 @@ struct C2Props<'a> {
 
 fn Child2<'a>(cx: Scope<'a, C2Props<'a>>) -> Element {
     cx.render(rsx! {
-        Child3 {
-            text: cx.props.text
-        }
+        Child3 { text: cx.props.text }
     })
 }
 

+ 10 - 22
examples/calculator.rs

@@ -25,6 +25,7 @@ fn app(cx: Scope) -> Element {
         if val.get() == "0" {
             val.set(String::new());
         }
+
         val.make_mut().push_str(num.to_string().as_str());
     };
 
@@ -99,12 +100,8 @@ fn app(cx: Scope) -> Element {
                                 }
                             }
                             div { class: "digit-keys",
-                                button { class: "calculator-key key-0", onclick: move |_| input_digit(0),
-                                    "0"
-                                }
-                                button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'),
-                                    "●"
-                                }
+                                button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
+                                button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" }
                                 (1..10).map(|k| rsx!{
                                     button {
                                         class: "calculator-key {k}",
@@ -116,22 +113,13 @@ fn app(cx: Scope) -> Element {
                             }
                         }
                         div { class: "operator-keys",
-                            button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"),
-                                "÷"
-                            }
-                            button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"),
-                                "×"
-                            }
-                            button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"),
-                                "−"
-                            }
-                            button { class: "calculator-key key-add", onclick: move |_| input_operator("+"),
-                                "+"
-                            }
-                            button { class: "calculator-key key-equals",
-                                onclick: move |_| {
-                                    val.set(format!("{}", calc_val(val.as_str())));
-                                },
+                            button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), "÷" }
+                            button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), "×" }
+                            button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), "−" }
+                            button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), "+" }
+                            button {
+                                class: "calculator-key key-equals",
+                                onclick: move |_| val.set(format!("{}", calc_val(val.as_str()))),
                                 "="
                             }
                         }

+ 22 - 0
examples/callback.rs

@@ -0,0 +1,22 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let login = use_callback!(cx, move |_| async move {
+        let res = reqwest::get("https://dog.ceo/api/breeds/list/all")
+            .await
+            .unwrap()
+            .text()
+            .await
+            .unwrap();
+
+        println!("{:#?}, ", res);
+    });
+
+    cx.render(rsx! {
+        button { onclick: login, "Click me!" }
+    })
+}

+ 9 - 7
examples/custom_element.rs

@@ -10,21 +10,23 @@ fn main() {
     let mut dom = VirtualDom::new(app);
     let _ = dom.rebuild();
 
-    let output = dioxus_ssr::render_vdom(&dom);
+    let output = dioxus_ssr::render(&dom);
 
     println!("{}", output);
 }
 
 fn app(cx: Scope) -> Element {
-    let nf = NodeFactory::new(&cx);
+    // let nf = NodeFactory::new(&cx);
 
-    let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
+    // let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
 
-    attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
+    // attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
 
-    attrs.push(nf.attr("name", format_args!("bob"), None, false));
+    // attrs.push(nf.attr("name", format_args!("bob"), None, false));
 
-    attrs.push(nf.attr("age", format_args!("47"), None, false));
+    // attrs.push(nf.attr("age", format_args!("47"), None, false));
 
-    Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
+    // Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
+
+    todo!()
 }

+ 26 - 34
examples/dog_app.rs

@@ -1,12 +1,8 @@
-#![allow(non_snake_case)]
-
-//! Render a bunch of doggos!
-
 use dioxus::prelude::*;
 use std::collections::HashMap;
 
 fn main() {
-    dioxus_desktop::launch(app);
+    dioxus_desktop::launch(|cx| render!(app_root {}));
 }
 
 #[derive(Debug, Clone, PartialEq, serde::Deserialize)]
@@ -14,10 +10,10 @@ struct ListBreeds {
     message: HashMap<String, Vec<String>>,
 }
 
-fn app(cx: Scope) -> Element {
-    let breed = use_state(&cx, || None);
+async fn app_root(cx: Scope<'_>) -> Element {
+    let breed = use_state(cx, || "deerhound".to_string());
 
-    let breeds = use_future(&cx, (), |_| async move {
+    let breeds = use_future!(cx, || async move {
         reqwest::get("https://dog.ceo/api/breeds/list/all")
             .await
             .unwrap()
@@ -25,32 +21,26 @@ fn app(cx: Scope) -> Element {
             .await
     });
 
-    match breeds.value() {
-        Some(Ok(breeds)) => cx.render(rsx! {
-            div {
+    match breeds.await {
+        Ok(breeds) => cx.render(rsx! {
+            div { height: "500px",
                 h1 { "Select a dog breed!" }
                 div { display: "flex",
                     ul { flex: "50%",
-                        breeds.message.keys().map(|cur_breed| rsx!(
-                            li {
+                        for cur_breed in breeds.message.keys().take(10) {
+                            li { key: "{cur_breed}",
                                 button {
-                                    onclick: move |_| breed.set(Some(cur_breed.clone())),
+                                    onclick: move |_| breed.set(cur_breed.clone()),
                                     "{cur_breed}"
                                 }
                             }
-                        ))
-                    }
-                    div { flex: "50%",
-                        match breed.get() {
-                            Some(breed) => rsx!( Breed { breed: breed.clone() } ),
-                            None => rsx!("No Breed selected"),
                         }
                     }
+                    div { flex: "50%", breed_pic { breed: breed.to_string() } }
                 }
             }
         }),
-        Some(Err(_e)) => cx.render(rsx! { div { "Error fetching breeds" } }),
-        None => cx.render(rsx! { div { "Loading dogs..." } }),
+        Err(_e) => cx.render(rsx! { div { "Error fetching breeds" } }),
     }
 }
 
@@ -60,8 +50,8 @@ struct DogApi {
 }
 
 #[inline_props]
-fn Breed(cx: Scope, breed: String) -> Element {
-    let fut = use_future(&cx, (breed,), |(breed,)| async move {
+async fn breed_pic(cx: Scope, breed: String) -> Element {
+    let fut = use_future!(cx, |breed| async move {
         reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
             .await
             .unwrap()
@@ -69,21 +59,23 @@ fn Breed(cx: Scope, breed: String) -> Element {
             .await
     });
 
-    cx.render(match fut.value() {
-        Some(Ok(resp)) => rsx! {
-            button {
-                onclick: move |_| fut.restart(),
-                "Click to fetch another doggo"
-            }
+    match fut.await {
+        Ok(resp) => render! {
             div {
+                button {
+                    onclick: move |_| {
+                        println!("clicked");
+                        fut.restart()
+                    },
+                    "Click to fetch another doggo"
+                }
                 img {
+                    src: "{resp.message}",
                     max_width: "500px",
                     max_height: "500px",
-                    src: "{resp.message}",
                 }
             }
         },
-        Some(Err(_)) => rsx! { div { "loading dogs failed" } },
-        None => rsx! { div { "loading dogs..." } },
-    })
+        Err(_) => render! { div { "loading dogs failed" } },
+    }
 }

+ 12 - 0
examples/error_handle.rs

@@ -18,5 +18,17 @@ fn app(cx: Scope) -> Element {
             onclick: move |_| val.set("invalid"),
             "Set an invalid number"
         }
+        (0..5).map(|i| rsx! {
+            demo_c { x: i }
+        })
+    })
+}
+
+#[inline_props]
+fn demo_c(cx: Scope, x: i32) -> Element {
+    cx.render(rsx! {
+        h1 {
+            "asdasdasdasd {x}"
+        }
     })
 }

+ 1 - 1
examples/eval.rs

@@ -16,7 +16,7 @@ fn app(cx: Scope) -> Element {
                 oninput: move |e| script.set(e.value.clone()),
             }
             button {
-                onclick: move |_| eval(script),
+                onclick: move |_| eval(script.to_string()),
                 "Execute"
             }
         }

+ 1 - 1
examples/fermi.rs

@@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div { "hello {name}!" }
         Child {}
-        ChildWithRef{}
+        ChildWithRef {}
     })
 }
 

+ 2 - 2
examples/file_explorer.rs

@@ -21,7 +21,7 @@ fn main() {
 fn app(cx: Scope) -> Element {
     let files = use_ref(&cx, Files::new);
 
-    render! {
+    cx.render(rsx! {
         div {
             link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
             style { include_str!("./assets/fileexplorer.css") }
@@ -62,7 +62,7 @@ fn app(cx: Scope) -> Element {
                 })
             }
         }
-    }
+    })
 }
 
 struct Files {

+ 1 - 1
examples/framework_benchmark.rs

@@ -72,7 +72,7 @@ fn app(cx: Scope) -> Element {
                             td { class:"col-md-1" }
                             td { class:"col-md-1", "{item.key}" }
                             td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
-                                a { class: "lbl", item.labels }
+                                a { class: "lbl", "{item.labels[0]}{item.labels[1]}{item.labels[2]}" }
                             }
                             td { class: "col-md-1",
                                 a { class: "remove", onclick: move |_| { items.write().remove(id); },

+ 13 - 0
examples/generic_component.rs

@@ -0,0 +1,13 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! { generic_child::<i32>{} })
+}
+
+fn generic_child<T>(cx: Scope) -> Element {
+    cx.render(rsx! { div {} })
+}

+ 1 - 1
examples/hydration.rs

@@ -14,7 +14,7 @@ use dioxus_desktop::Config;
 
 fn main() {
     let vdom = VirtualDom::new(app);
-    let content = dioxus_ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
+    let content = dioxus_ssr::pre_render(&vdom);
 
     dioxus_desktop::launch_cfg(app, Config::new().with_prerendered(content));
 }

+ 3 - 5
examples/inputs.rs

@@ -2,7 +2,7 @@
 //!
 //! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
 
-use dioxus::{events::FormEvent, prelude::*};
+use dioxus::prelude::*;
 
 fn main() {
     dioxus_desktop::launch(app);
@@ -28,7 +28,6 @@ const FIELDS: &[(&str, &str)] = &[
     ("text", ""),
     ("time", ""),
     ("url", ""),
-    //
     // less supported things
     ("hidden", ""),
     ("month", ""), // degrades to text most of the time, but works properly as "value'"
@@ -114,7 +113,7 @@ fn app(cx: Scope) -> Element {
                 }
             }
 
-            FIELDS.iter().map(|(field, value)| rsx!(
+            FIELDS.iter().map(|(field, value)| rsx! {
                 div {
                     input {
                         id: "{field}",
@@ -131,8 +130,7 @@ fn app(cx: Scope) -> Element {
                     }
                     br {}
                 }
-            ))
-
+            })
         }
     })
 }

+ 2 - 4
examples/login_form.rs

@@ -1,7 +1,6 @@
 //! This example demonstrates the following:
 //! Futures in a callback, Router, and Forms
 
-use dioxus::events::*;
 use dioxus::prelude::*;
 
 fn main() {
@@ -37,11 +36,10 @@ fn app(cx: Scope) -> Element {
         form {
             onsubmit: onsubmit,
             prevent_default: "onsubmit", // Prevent the default behavior of <form> to post
-
-            input { "type": "text", id: "username", name: "username" }
+            input { r#type: "text", id: "username", name: "username" }
             label { "Username" }
             br {}
-            input { "type": "password", id: "password", name: "password" }
+            input { r#type: "password", id: "password", name: "password" }
             label { "Password" }
             br {}
             button { "Login" }

+ 1 - 1
examples/nested_listeners.rs

@@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element {
             button {
                 onclick: move |evt| {
                     println!("clicked! bottom no bubbling");
-                    evt.cancel_bubble();
+                    evt.stop_propogation();
                 },
                 "Dont propogate"
             }

+ 1 - 0
examples/pattern_model.rs

@@ -19,6 +19,7 @@
 
 use dioxus::events::*;
 use dioxus::html::input_data::keyboard_types::Key;
+use dioxus::html::MouseEvent;
 use dioxus::prelude::*;
 use dioxus_desktop::wry::application::dpi::LogicalSize;
 use dioxus_desktop::{Config, WindowBuilder};

+ 2 - 2
examples/router.rs

@@ -30,7 +30,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn BlogPost(cx: Scope) -> Element {
-    let post = dioxus_router::use_route(&cx).last_segment()?;
+    let post = dioxus_router::use_route(&cx).last_segment().unwrap();
 
     cx.render(rsx! {
         div {
@@ -46,7 +46,7 @@ struct Query {
 }
 
 fn User(cx: Scope) -> Element {
-    let post = dioxus_router::use_route(&cx).last_segment()?;
+    let post = dioxus_router::use_route(&cx).last_segment().unwrap();
 
     let query = dioxus_router::use_route(&cx)
         .query::<Query>()

+ 4 - 3
examples/rsx_compile_fail.rs

@@ -5,10 +5,11 @@ use dioxus::prelude::*;
 
 fn main() {
     let mut vdom = VirtualDom::new(example);
-    vdom.rebuild();
+    _ = vdom.rebuild();
 
-    let out = dioxus_ssr::render_vdom_cfg(&vdom, |c| c.newline(true).indent(true));
-    println!("{}", out);
+    let mut renderer = dioxus_ssr::Renderer::new();
+    renderer.pretty = true;
+    renderer.render(&vdom);
 }
 
 fn example(cx: Scope) -> Element {

+ 3 - 3
examples/rsx_usage.rs

@@ -165,13 +165,13 @@ fn app(cx: Scope) -> Element {
 
             // Can pass in props directly as an expression
             {
-                let props = TallerProps {a: "hello", children: Default::default()};
+                let props = TallerProps {a: "hello", children: cx.render(rsx!(()))};
                 rsx!(Taller { ..props })
             }
 
             // Spreading can also be overridden manually
             Taller {
-                ..TallerProps { a: "ballin!", children: Default::default() },
+                ..TallerProps { a: "ballin!", children: cx.render(rsx!(()) )},
                 a: "not ballin!"
             }
 
@@ -183,7 +183,7 @@ fn app(cx: Scope) -> Element {
 
             // Components can be generic too
             // This component takes i32 type to give you typed input
-            TypedInput::<TypedInputProps<i32>> {}
+            TypedInput::<i32> {}
 
             // Type inference can be used too
             TypedInput { initial: 10.0 }

+ 14 - 11
examples/simple_list.rs

@@ -6,23 +6,26 @@ fn main() {
 
 fn app(cx: Scope) -> Element {
     cx.render(rsx!(
-        // Use Map directly to lazily pull elements
-        (0..10).map(|f| rsx! { "{f}" }),
+        div {
+            // Use Map directly to lazily pull elements
+            (0..10).map(|f| rsx! { "{f}" }),
 
-        // Collect into an intermediate collection if necessary
-        ["a", "b", "c"]
-            .into_iter()
-            .map(|f| rsx! { "{f}" })
-            .collect::<Vec<_>>(),
+            // Collect into an intermediate collection if necessary, and call into_iter
+            ["a", "b", "c", "d", "e", "f"]
+                .into_iter()
+                .map(|f| rsx! { "{f}" })
+                .collect::<Vec<_>>()
+                .into_iter(),
 
-        // Use optionals
-        Some(rsx! { "Some" }),
+            // Use optionals
+            Some(rsx! { "Some" }),
 
-        div {
+            // use a for loop where the body itself is RSX
             for name in 0..10 {
-                rsx! { "{name}" }
+                div {"{name}"}
             }
 
+            // Or even use an unterminated conditional
             if true {
                 rsx!{ "hello world!" }
             }

+ 3 - 2
examples/ssr.rs

@@ -5,6 +5,7 @@
 use std::fmt::Write;
 
 use dioxus::prelude::*;
+use dioxus_ssr::config::Config;
 
 fn main() {
     // We can render VirtualDoms
@@ -25,14 +26,14 @@ fn main() {
     // We can configure the SSR rendering to add ids for rehydration
     println!(
         "{}",
-        dioxus_ssr::render_vdom_cfg(&vdom, |c| c.pre_render(true))
+        dioxus_ssr::render_vdom_cfg(&vdom, Config::default().pre_render(true))
     );
 
     // We can even render as a writer
     let mut file = String::new();
     let _ = file.write_fmt(format_args!(
         "{}",
-        dioxus_ssr::TextRenderer::from_vdom(&vdom, Default::default())
+        dioxus_ssr::SsrRender::default().render_vdom(&vdom)
     ));
     println!("{}", file);
 }

+ 14 - 12
examples/svg.rs

@@ -1,6 +1,6 @@
 // Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
 
-use dioxus::{events::MouseEvent, prelude::*};
+use dioxus::prelude::*;
 
 fn main() {
     dioxus_desktop::launch(app);
@@ -9,7 +9,7 @@ fn main() {
 fn app(cx: Scope) -> Element {
     let val = use_state(&cx, || 5);
 
-    render! {
+    cx.render(rsx! {
         div {
             user_select: "none",
             webkit_user_select: "none",
@@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element {
                 }
             }
         }
-    }
+    })
 }
 
 #[derive(Props)]
@@ -70,19 +70,21 @@ pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
         .map(|((x, y), _)| {
             let dcx = x * OFFSET;
             let dcy = y * OFFSET;
-            rsx!(circle {
-                cx: "{dcx}",
-                cy: "{dcy}",
-                r: "{DOT_RADIUS}",
-                fill: "#333"
-            })
+
+            rsx! {
+                circle {
+                    cx: "{dcx}",
+                    cy: "{dcy}",
+                    r: "{DOT_RADIUS}",
+                    fill: "#333"
+                }
+            }
         });
 
-    render! {
+    cx.render(rsx! {
       svg {
         onclick: move |e| cx.props.onclick.call(e),
         prevent_default: "onclick",
-        "dioxus-prevent-default": "onclick",
         class: "die",
         view_box: "-1000 -1000 2000 2000",
 
@@ -97,5 +99,5 @@ pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
 
         dots
       }
-    }
+    })
 }

+ 8 - 6
examples/todomvc.rs

@@ -22,10 +22,10 @@ pub struct TodoItem {
 }
 
 pub fn app(cx: Scope<()>) -> Element {
-    let todos = use_state(&cx, im_rc::HashMap::<u32, TodoItem>::default);
-    let filter = use_state(&cx, || FilterState::All);
-    let draft = use_state(&cx, || "".to_string());
-    let todo_id = use_state(&cx, || 0);
+    let todos = use_state(cx, im_rc::HashMap::<u32, TodoItem>::default);
+    let filter = use_state(cx, || FilterState::All);
+    let draft = use_state(cx, || "".to_string());
+    let todo_id = use_state(cx, || 0);
 
     // Filter the todos based on the filter state
     let mut filtered_todos = todos
@@ -57,7 +57,9 @@ pub fn app(cx: Scope<()>) -> Element {
                         placeholder: "What needs to be done?",
                         value: "{draft}",
                         autofocus: "true",
-                        oninput: move |evt| draft.set(evt.value.clone()),
+                        oninput: move |evt| {
+                            draft.set(evt.value.clone());
+                        },
                         onkeydown: move |evt| {
                             if evt.key() == Key::Enter && !draft.is_empty() {
                                 todos.make_mut().insert(
@@ -114,7 +116,7 @@ pub struct TodoEntryProps<'a> {
 }
 
 pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
-    let is_editing = use_state(&cx, || false);
+    let is_editing = use_state(cx, || false);
 
     let todos = cx.props.todos.get();
     let todo = &todos[&cx.props.id];

+ 6 - 6
examples/window_event.rs

@@ -35,13 +35,13 @@ fn app(cx: Scope) -> Element {
                 nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
-                    onmousedown: |evt| evt.cancel_bubble(),
+                    onmousedown: |evt| evt.stop_propogation(),
                     onclick: move |_| window.set_minimized(true),
                     "Minimize"
                 }
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
-                    onmousedown: |evt| evt.cancel_bubble(),
+                    onmousedown: |evt| evt.stop_propogation(),
                     onclick: move |_| {
 
                         window.set_fullscreen(!**fullscreen);
@@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element {
                 }
                 button {
                     class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
-                    onmousedown: |evt| evt.cancel_bubble(),
+                    onmousedown: |evt| evt.stop_propogation(),
                     onclick: move |_| window.close(),
                     "Close"
                 }
@@ -66,7 +66,7 @@ fn app(cx: Scope) -> Element {
                 div {
                     button {
                         class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.cancel_bubble(),
+                        onmousedown: |evt| evt.stop_propogation(),
                         onclick: move |_| {
                             window.set_always_on_top(!always_on_top);
                             always_on_top.set(!always_on_top);
@@ -77,7 +77,7 @@ fn app(cx: Scope) -> Element {
                 div {
                     button {
                         class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.cancel_bubble(),
+                        onmousedown: |evt| evt.stop_propogation(),
                         onclick: move |_| {
                             window.set_decorations(!decorations);
                             decorations.set(!decorations);
@@ -88,7 +88,7 @@ fn app(cx: Scope) -> Element {
                 div {
                     button {
                         class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
-                        onmousedown: |evt| evt.cancel_bubble(),
+                        onmousedown: |evt| evt.stop_propogation(),
                         onclick: move |_| window.set_title("Dioxus Application"),
                         "Change Title"
                     }

+ 5 - 1
packages/core-macro/src/inlineprops.rs

@@ -9,6 +9,7 @@ use syn::{
 pub struct InlinePropsBody {
     pub attrs: Vec<Attribute>,
     pub vis: syn::Visibility,
+    pub maybe_async: Option<Token![async]>,
     pub fn_token: Token![fn],
     pub ident: Ident,
     pub cx_token: Box<Pat>,
@@ -25,6 +26,7 @@ pub struct InlinePropsBody {
 impl Parse for InlinePropsBody {
     fn parse(input: ParseStream) -> Result<Self> {
         let attrs: Vec<Attribute> = input.call(Attribute::parse_outer)?;
+        let maybe_async: Option<Token![async]> = input.parse().ok();
         let vis: Visibility = input.parse()?;
 
         let fn_token = input.parse()?;
@@ -57,6 +59,7 @@ impl Parse for InlinePropsBody {
 
         Ok(Self {
             vis,
+            maybe_async,
             fn_token,
             ident,
             generics,
@@ -84,6 +87,7 @@ impl ToTokens for InlinePropsBody {
             block,
             cx_token,
             attrs,
+            maybe_async,
             ..
         } = self;
 
@@ -151,7 +155,7 @@ impl ToTokens for InlinePropsBody {
             }
 
             #(#attrs)*
-            #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
+            #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
             #where_clause
             {
                 let #struct_name { #(#field_names),* } = &cx.props;

+ 4 - 26
packages/core-macro/src/lib.rs

@@ -11,8 +11,7 @@ use dioxus_rsx as rsx;
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
     use rsx::*;
-    let item = parse_macro_input!(input as IfmtInput);
-    format_args_f_impl(item)
+    format_args_f_impl(parse_macro_input!(input as IfmtInput))
         .unwrap_or_else(|err| err.to_compile_error())
         .into()
 }
@@ -40,19 +39,6 @@ pub fn rsx(s: TokenStream) -> TokenStream {
     }
 }
 
-/// A version of the rsx! macro that does not use templates. Used for testing diffing
-#[proc_macro]
-pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsx::CallBody>(s) {
-        Err(err) => err.to_compile_error().into(),
-        Ok(body) => {
-            let mut tokens = proc_macro2::TokenStream::new();
-            body.to_tokens_without_template(&mut tokens);
-            tokens.into()
-        }
-    }
-}
-
 /// The render! macro makes it easy for developers to write jsx-style markup in their components.
 ///
 /// The render macro automatically renders rsx - making it unhygenic.
@@ -65,18 +51,10 @@ pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
 pub fn render(s: TokenStream) -> TokenStream {
     match syn::parse::<rsx::CallBody>(s) {
         Err(err) => err.to_compile_error().into(),
-        Ok(body) => {
-            let mut inner = proc_macro2::TokenStream::new();
-            body.to_tokens_without_lazynodes(&mut inner);
-            quote::quote! {
-                {
-                    let __cx = NodeFactory::new(&cx.scope);
-                    Some(#inner)
-                }
-            }
+        Ok(mut body) => {
+            body.inline_cx = true;
+            body.into_token_stream().into()
         }
-        .into_token_stream()
-        .into(),
     }
 }
 

+ 1 - 1
packages/core-macro/src/props/mod.rs

@@ -194,7 +194,7 @@ mod field_info {
                 // children field is automatically defaulted to None
                 if name == "children" {
                     builder_attr.default =
-                        Some(syn::parse(quote!(Default::default()).into()).unwrap());
+                        Some(syn::parse(quote!(::dioxus::core::VNode::empty()).into()).unwrap());
                 }
 
                 // auto detect optional

+ 4 - 17
packages/core/Cargo.toml

@@ -18,40 +18,27 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 bumpalo = { version = "3.6", features = ["collections", "boxed"] }
 
 # faster hashmaps
-rustc-hash = "1.1.0"
+fxhash = "0.2"
 
 # Used in diffing
 longest-increasing-subsequence = "0.1.0"
 
 futures-util = { version = "0.3", default-features = false }
 
-smallvec = "1.6"
-
 slab = "0.4"
 
 futures-channel = "0.3.21"
 
-# internally used
-log = "0.4"
-
-# used for noderefs
-once_cell = "1.8"
-
+indexmap = "1.7"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
-
-# todo: I want to get rid of this
-backtrace = { version = "0.3" }
-
-# allows cloing trait objects
-dyn-clone = "1.0.9"
+anyhow = "1.0.66"
 
 [dev-dependencies]
+tokio = { version = "*", features = ["full"] }
 dioxus = { path = "../dioxus" }
 
 [features]
 default = []
 serialize = ["serde"]
-debug_vdom = []
-hot-reload = []

+ 64 - 46
packages/core/README.md

@@ -1,52 +1,84 @@
-# Dioxus-core
+# dioxus-core
 
-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.
+dioxus-core is a fast and featureful VirtualDom implementation written in and for Rust.
 
-To build new apps with Dioxus or to extend the ecosystem with new hooks or components, use the higher-level `dioxus` crate with the appropriate feature flags.
+# Features
 
+- Functions as components
+- Hooks for local state
+- Task pool for spawning futures
+- Template-based architecture
+- Asynchronous components
+- Suspense boundaries
+- Error boundaries through the `anyhow` crate
+- Customizable memoization
+
+If just starting out, check out the Guides first.
+
+# General Theory
+
+The dioxus-core `VirtualDom` object is built around the concept of a `Template`. Templates describe a layout tree known at compile time with dynamic parts filled at runtime.
+
+Each component in the VirtualDom works as a dedicated render loop where re-renders are triggered by events external to the VirtualDom, or from the components themselves.
+
+When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object, and calculates the differences of the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and new layout, Dioxus will write modifications to the `Mutations` object.
+
+Dioxus expects the target renderer to save its nodes in a list. Each element is given a numerical ID which can be used to directly index into that list for O(1) lookups.
+
+# Usage
+
+All Dioxus apps start as just a function that takes the [`Scope`] object and returns an [`Element`].
+
+The `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler syntax of Rust into the logic required to build Templates.
+
+First, start with your app:
 
 ```rust, ignore
 fn app(cx: Scope) -> Element {
-    render!(div { "hello world" })
+    cx.render(rsx!( div { "hello world" } ))
 }
+```
 
-fn main() {
-    let mut renderer = SomeRenderer::new();
+Then, we'll want to create a new VirtualDom using this app as the root component.
 
-    // Creating a new virtualdom from a component
-    let mut dom = VirtualDom::new(app);
+```rust, ignore
+let mut dom = VirtualDom::new(app);
+```
 
-    // Patching the renderer with the changes to draw the screen
-    let edits = dom.rebuild();
-    renderer.apply(edits);
+To build the app into a stream of mutations, we'll use [`VirtualDom::rebuild`]:
 
-    // Injecting events
-    dom.handle_message(SchedulerMsg::Event(UserEvent {
-        scope_id: None,
-        priority: EventPriority::High,
-        element: ElementId(0),
-        name: "onclick",
-        data: Arc::new(()),
-    }));
+```rust, ignore
+let mutations = dom.rebuild();
 
-    // polling asynchronously
-    dom.wait_for_work().await;
+apply_edits_to_real_dom(mutations);
+```
 
-    // working with a deadline
-    if let Some(edits) = dom.work_with_deadline(|| false) {
-        renderer.apply(edits);
-    }
+We can then wait for any asynchronous components or pending futures using the `wait_for_work()` method. If we have a deadline, then we can use render_with_deadline instead:
 
-    // getting state of scopes
-    let scope = dom.get_scope(ScopeId(0)).unwrap();
+```rust, ignore
+// Wait for the dom to be marked dirty internally
+dom.wait_for_work().await;
+
+// Or wait for a deadline and then collect edits
+dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
+```
+
+If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
 
-    // iterating through the tree
-    match scope.root_node() {
-        VNodes::Text(vtext) => dbg!(vtext),
-        VNodes::Element(vel) => dbg!(vel),
-        _ => todo!()
+```rust, ignore
+loop {
+    select! {
+        evt = real_dom.event() => dom.handle_event("click", evt.data, evt.element, evt.bubbles),
+        _ = dom.wait_for_work() => {}
     }
+
+    // Render any work without blocking the main thread for too long
+    let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(10)));
+
+    // And then apply the edits
+    real_dom.apply(mutations);
 }
+
 ```
 
 ## Internals
@@ -82,17 +114,3 @@ The final implementation of Dioxus must:
 - Support server-side-rendering (SSR). VNodes should render to a string that can be served via a web server.
 - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
 - Be modular. Components and hooks should be work anywhere without worrying about target platform.
-
-
-## Safety
-
-Dioxus uses unsafe. The design of Dioxus *requires* unsafe (self-referential trees).
-
-All of our test suite passes MIRI without errors.
-
-Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe.
-
-If you don't want to use a crate that uses unsafe, then this crate is not for you.
-
-However, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. We'd be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous invariants required to execute certain features.
-

+ 5 - 0
packages/core/architecture.md

@@ -114,6 +114,11 @@ Some essential reading:
 - https://web.dev/rail/
 - https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
 
+# Templates
+
+If everything is a template, then we'll have the idea that the only children can b Templates
+
+
 # What's going on?
 
 Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI

+ 73 - 0
packages/core/src/any_props.rs

@@ -0,0 +1,73 @@
+use std::marker::PhantomData;
+
+use crate::{
+    innerlude::Scoped,
+    nodes::{ComponentReturn, RenderReturn},
+    scopes::{Scope, ScopeState},
+    Element,
+};
+
+/// A trait that essentially allows VComponentProps to be used generically
+///
+/// # Safety
+///
+/// This should not be implemented outside this module
+pub(crate) unsafe trait AnyProps<'a> {
+    fn props_ptr(&self) -> *const ();
+    fn render(&'a self, bump: &'a ScopeState) -> RenderReturn<'a>;
+    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
+}
+
+pub(crate) struct VProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> {
+    pub render_fn: fn(Scope<'a, P>) -> F,
+    pub memo: unsafe fn(&P, &P) -> bool,
+    pub props: P,
+    _marker: PhantomData<A>,
+}
+
+impl<'a, P, A, F> VProps<'a, P, A, F>
+where
+    F: ComponentReturn<'a, A>,
+{
+    pub(crate) fn new(
+        render_fn: fn(Scope<'a, P>) -> F,
+        memo: unsafe fn(&P, &P) -> bool,
+        props: P,
+    ) -> Self {
+        Self {
+            render_fn,
+            memo,
+            props,
+            _marker: PhantomData,
+        }
+    }
+}
+
+unsafe impl<'a, P, A, F> AnyProps<'a> for VProps<'a, P, A, F>
+where
+    F: ComponentReturn<'a, A>,
+{
+    fn props_ptr(&self) -> *const () {
+        &self.props as *const _ as *const ()
+    }
+
+    // Safety:
+    // this will downcast the other ptr as our swallowed type!
+    // you *must* make this check *before* calling this method
+    // if your functions are not the same, then you will downcast a pointer into a different type (UB)
+    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
+        let real_other: &P = &*(other.props_ptr() as *const _ as *const P);
+        let real_us: &P = &*(self.props_ptr() as *const _ as *const P);
+        (self.memo)(real_us, real_other)
+    }
+
+    fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> {
+        let scope: &mut Scoped<P> = cx.bump().alloc(Scoped {
+            props: &self.props,
+            scope: cx,
+        });
+
+        // Call the render function directly
+        (self.render_fn)(scope).into_return(cx)
+    }
+}

+ 0 - 642
packages/core/src/arbitrary_value.rs

@@ -1,642 +0,0 @@
-use std::fmt::{Arguments, Formatter};
-
-use bumpalo::Bump;
-use dyn_clone::{clone_box, DynClone};
-
-/// Possible values for an attribute
-// trying to keep values at 3 bytes
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(untagged))]
-#[derive(Clone, Debug, PartialEq)]
-#[allow(missing_docs)]
-pub enum AttributeValue<'a> {
-    Text(&'a str),
-    Float32(f32),
-    Float64(f64),
-    Int32(i32),
-    Int64(i64),
-    Uint32(u32),
-    Uint64(u64),
-    Bool(bool),
-
-    Vec3Float(f32, f32, f32),
-    Vec3Int(i32, i32, i32),
-    Vec3Uint(u32, u32, u32),
-
-    Vec4Float(f32, f32, f32, f32),
-    Vec4Int(i32, i32, i32, i32),
-    Vec4Uint(u32, u32, u32, u32),
-
-    Bytes(&'a [u8]),
-    Any(ArbitraryAttributeValue<'a>),
-}
-
-/// A value that can be converted into an attribute value
-pub trait IntoAttributeValue<'a> {
-    /// Convert into an attribute value
-    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
-}
-
-impl<'a> IntoAttributeValue<'a> for u32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Uint32(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for u64 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Uint64(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for i32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Int32(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for i64 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Int64(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for f32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Float32(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for f64 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Float64(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for bool {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Bool(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for &'a str {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Text(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
-    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
-        use bumpalo::core_alloc::fmt::Write;
-        let mut str_buf = bumpalo::collections::String::new_in(bump);
-        str_buf.write_fmt(self).unwrap();
-        AttributeValue::Text(str_buf.into_bump_str())
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for &'a [u8] {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Bytes(self)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (f32, f32, f32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec3Float(self.0, self.1, self.2)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (i32, i32, i32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec3Int(self.0, self.1, self.2)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (u32, u32, u32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec3Uint(self.0, self.1, self.2)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (f32, f32, f32, f32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec4Float(self.0, self.1, self.2, self.3)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (i32, i32, i32, i32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec4Int(self.0, self.1, self.2, self.3)
-    }
-}
-
-impl<'a> IntoAttributeValue<'a> for (u32, u32, u32, u32) {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Vec4Uint(self.0, self.1, self.2, self.3)
-    }
-}
-
-impl<'a, T> IntoAttributeValue<'a> for &'a T
-where
-    T: AnyClone + PartialEq,
-{
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Any(ArbitraryAttributeValue {
-            value: self,
-            cmp: |a, b| {
-                if let Some(a) = a.as_any().downcast_ref::<T>() {
-                    if let Some(b) = b.as_any().downcast_ref::<T>() {
-                        a == b
-                    } else {
-                        false
-                    }
-                } else {
-                    false
-                }
-            },
-        })
-    }
-}
-
-// todo
-#[allow(missing_docs)]
-impl<'a> AttributeValue<'a> {
-    pub fn is_truthy(&self) -> bool {
-        match self {
-            AttributeValue::Text(t) => *t == "true",
-            AttributeValue::Bool(t) => *t,
-            _ => false,
-        }
-    }
-
-    pub fn is_falsy(&self) -> bool {
-        match self {
-            AttributeValue::Text(t) => *t == "false",
-            AttributeValue::Bool(t) => !(*t),
-            _ => false,
-        }
-    }
-}
-
-impl<'a> std::fmt::Display for AttributeValue<'a> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        match self {
-            AttributeValue::Text(a) => write!(f, "{}", a),
-            AttributeValue::Float32(a) => write!(f, "{}", a),
-            AttributeValue::Float64(a) => write!(f, "{}", a),
-            AttributeValue::Int32(a) => write!(f, "{}", a),
-            AttributeValue::Int64(a) => write!(f, "{}", a),
-            AttributeValue::Uint32(a) => write!(f, "{}", a),
-            AttributeValue::Uint64(a) => write!(f, "{}", a),
-            AttributeValue::Bool(a) => write!(f, "{}", a),
-            AttributeValue::Vec3Float(_, _, _) => todo!(),
-            AttributeValue::Vec3Int(_, _, _) => todo!(),
-            AttributeValue::Vec3Uint(_, _, _) => todo!(),
-            AttributeValue::Vec4Float(_, _, _, _) => todo!(),
-            AttributeValue::Vec4Int(_, _, _, _) => todo!(),
-            AttributeValue::Vec4Uint(_, _, _, _) => todo!(),
-            AttributeValue::Bytes(a) => write!(f, "{:?}", a),
-            AttributeValue::Any(a) => write!(f, "{:?}", a),
-        }
-    }
-}
-
-#[derive(Clone, Copy)]
-#[allow(missing_docs)]
-pub struct ArbitraryAttributeValue<'a> {
-    pub value: &'a dyn AnyClone,
-    pub cmp: fn(&dyn AnyClone, &dyn AnyClone) -> bool,
-}
-
-impl PartialEq for ArbitraryAttributeValue<'_> {
-    fn eq(&self, other: &Self) -> bool {
-        (self.cmp)(self.value, other.value)
-    }
-}
-
-impl std::fmt::Debug for ArbitraryAttributeValue<'_> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("ArbitraryAttributeValue").finish()
-    }
-}
-
-#[cfg(feature = "serialize")]
-impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> {
-    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        panic!("ArbitraryAttributeValue should not be serialized")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("ArbitraryAttributeValue is not deserializable!")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("ArbitraryAttributeValue is not deserializable!")
-    }
-}
-
-/// A clone, sync and send version of `Any`
-// we only need the Sync + Send bound when hot reloading is enabled
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-pub trait AnyClone: std::any::Any + DynClone + Send + Sync {
-    fn as_any(&self) -> &dyn std::any::Any;
-}
-#[cfg(not(any(feature = "hot-reload", debug_assertions)))]
-pub trait AnyClone: std::any::Any + DynClone {
-    fn as_any(&self) -> &dyn std::any::Any;
-}
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-impl<T: std::any::Any + DynClone + Send + Sync> AnyClone for T {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-#[cfg(not(any(feature = "hot-reload", debug_assertions)))]
-impl<T: std::any::Any + DynClone> AnyClone for T {
-    fn as_any(&self) -> &dyn std::any::Any {
-        self
-    }
-}
-
-dyn_clone::clone_trait_object!(AnyClone);
-
-#[derive(Clone)]
-#[allow(missing_docs)]
-pub struct OwnedArbitraryAttributeValue {
-    pub value: Box<dyn AnyClone>,
-    pub cmp: fn(&dyn AnyClone, &dyn AnyClone) -> bool,
-}
-
-impl PartialEq for OwnedArbitraryAttributeValue {
-    fn eq(&self, other: &Self) -> bool {
-        (self.cmp)(&*self.value, &*other.value)
-    }
-}
-
-impl std::fmt::Debug for OwnedArbitraryAttributeValue {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("OwnedArbitraryAttributeValue").finish()
-    }
-}
-
-#[cfg(feature = "serialize")]
-impl serde::Serialize for OwnedArbitraryAttributeValue {
-    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        panic!("OwnedArbitraryAttributeValue should not be serialized")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de> serde::Deserialize<'de> for &OwnedArbitraryAttributeValue {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("OwnedArbitraryAttributeValue is not deserializable!")
-    }
-}
-#[cfg(feature = "serialize")]
-impl<'de> serde::Deserialize<'de> for OwnedArbitraryAttributeValue {
-    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        panic!("OwnedArbitraryAttributeValue is not deserializable!")
-    }
-}
-
-// todo
-#[allow(missing_docs)]
-impl<'a> AttributeValue<'a> {
-    pub fn as_text(&self) -> Option<&'a str> {
-        match self {
-            AttributeValue::Text(s) => Some(s),
-            _ => None,
-        }
-    }
-
-    pub fn as_float32(&self) -> Option<f32> {
-        match self {
-            AttributeValue::Float32(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    pub fn as_float64(&self) -> Option<f64> {
-        match self {
-            AttributeValue::Float64(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    pub fn as_int32(&self) -> Option<i32> {
-        match self {
-            AttributeValue::Int32(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_int64(&self) -> Option<i64> {
-        match self {
-            AttributeValue::Int64(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_uint32(&self) -> Option<u32> {
-        match self {
-            AttributeValue::Uint32(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_uint64(&self) -> Option<u64> {
-        match self {
-            AttributeValue::Uint64(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_bool(&self) -> Option<bool> {
-        match self {
-            AttributeValue::Bool(b) => Some(*b),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
-        match self {
-            AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
-        match self {
-            AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
-        match self {
-            AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
-        match self {
-            AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
-        match self {
-            AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
-        match self {
-            AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_bytes(&self) -> Option<&[u8]> {
-        match self {
-            AttributeValue::Bytes(b) => Some(b),
-            _ => None,
-        }
-    }
-
-    pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> {
-        match self {
-            AttributeValue::Any(a) => Some(a),
-            _ => None,
-        }
-    }
-}
-
-/// A owned attribute value.
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(
-    all(feature = "serialize"),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-#[allow(missing_docs)]
-pub enum OwnedAttributeValue {
-    Text(String),
-    Float32(f32),
-    Float64(f64),
-    Int32(i32),
-    Int64(i64),
-    Uint32(u32),
-    Uint64(u64),
-    Bool(bool),
-
-    Vec3Float(f32, f32, f32),
-    Vec3Int(i32, i32, i32),
-    Vec3Uint(u32, u32, u32),
-
-    Vec4Float(f32, f32, f32, f32),
-    Vec4Int(i32, i32, i32, i32),
-    Vec4Uint(u32, u32, u32, u32),
-
-    Bytes(Vec<u8>),
-    // TODO: support other types
-    Any(OwnedArbitraryAttributeValue),
-}
-
-impl PartialEq<AttributeValue<'_>> for OwnedAttributeValue {
-    fn eq(&self, other: &AttributeValue<'_>) -> bool {
-        match (self, other) {
-            (Self::Text(l0), AttributeValue::Text(r0)) => l0 == r0,
-            (Self::Float32(l0), AttributeValue::Float32(r0)) => l0 == r0,
-            (Self::Float64(l0), AttributeValue::Float64(r0)) => l0 == r0,
-            (Self::Int32(l0), AttributeValue::Int32(r0)) => l0 == r0,
-            (Self::Int64(l0), AttributeValue::Int64(r0)) => l0 == r0,
-            (Self::Uint32(l0), AttributeValue::Uint32(r0)) => l0 == r0,
-            (Self::Uint64(l0), AttributeValue::Uint64(r0)) => l0 == r0,
-            (Self::Bool(l0), AttributeValue::Bool(r0)) => l0 == r0,
-            (Self::Vec3Float(l0, l1, l2), AttributeValue::Vec3Float(r0, r1, r2)) => {
-                l0 == r0 && l1 == r1 && l2 == r2
-            }
-            (Self::Vec3Int(l0, l1, l2), AttributeValue::Vec3Int(r0, r1, r2)) => {
-                l0 == r0 && l1 == r1 && l2 == r2
-            }
-            (Self::Vec3Uint(l0, l1, l2), AttributeValue::Vec3Uint(r0, r1, r2)) => {
-                l0 == r0 && l1 == r1 && l2 == r2
-            }
-            (Self::Vec4Float(l0, l1, l2, l3), AttributeValue::Vec4Float(r0, r1, r2, r3)) => {
-                l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
-            }
-            (Self::Vec4Int(l0, l1, l2, l3), AttributeValue::Vec4Int(r0, r1, r2, r3)) => {
-                l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
-            }
-            (Self::Vec4Uint(l0, l1, l2, l3), AttributeValue::Vec4Uint(r0, r1, r2, r3)) => {
-                l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
-            }
-            (Self::Bytes(l0), AttributeValue::Bytes(r0)) => l0 == r0,
-            (_, _) => false,
-        }
-    }
-}
-
-impl<'a> From<AttributeValue<'a>> for OwnedAttributeValue {
-    fn from(attr: AttributeValue<'a>) -> Self {
-        match attr {
-            AttributeValue::Text(t) => OwnedAttributeValue::Text(t.to_owned()),
-            AttributeValue::Float32(f) => OwnedAttributeValue::Float32(f),
-            AttributeValue::Float64(f) => OwnedAttributeValue::Float64(f),
-            AttributeValue::Int32(i) => OwnedAttributeValue::Int32(i),
-            AttributeValue::Int64(i) => OwnedAttributeValue::Int64(i),
-            AttributeValue::Uint32(u) => OwnedAttributeValue::Uint32(u),
-            AttributeValue::Uint64(u) => OwnedAttributeValue::Uint64(u),
-            AttributeValue::Bool(b) => OwnedAttributeValue::Bool(b),
-            AttributeValue::Vec3Float(f1, f2, f3) => OwnedAttributeValue::Vec3Float(f1, f2, f3),
-            AttributeValue::Vec3Int(f1, f2, f3) => OwnedAttributeValue::Vec3Int(f1, f2, f3),
-            AttributeValue::Vec3Uint(f1, f2, f3) => OwnedAttributeValue::Vec3Uint(f1, f2, f3),
-            AttributeValue::Vec4Float(f1, f2, f3, f4) => {
-                OwnedAttributeValue::Vec4Float(f1, f2, f3, f4)
-            }
-            AttributeValue::Vec4Int(f1, f2, f3, f4) => OwnedAttributeValue::Vec4Int(f1, f2, f3, f4),
-            AttributeValue::Vec4Uint(f1, f2, f3, f4) => {
-                OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4)
-            }
-            AttributeValue::Bytes(b) => OwnedAttributeValue::Bytes(b.to_owned()),
-            AttributeValue::Any(a) => OwnedAttributeValue::Any(OwnedArbitraryAttributeValue {
-                value: clone_box(a.value),
-                cmp: a.cmp,
-            }),
-        }
-    }
-}
-
-// todo
-#[allow(missing_docs)]
-impl OwnedAttributeValue {
-    pub fn as_text(&self) -> Option<&str> {
-        match self {
-            OwnedAttributeValue::Text(s) => Some(s),
-            _ => None,
-        }
-    }
-
-    pub fn as_float32(&self) -> Option<f32> {
-        match self {
-            OwnedAttributeValue::Float32(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    pub fn as_float64(&self) -> Option<f64> {
-        match self {
-            OwnedAttributeValue::Float64(f) => Some(*f),
-            _ => None,
-        }
-    }
-
-    pub fn as_int32(&self) -> Option<i32> {
-        match self {
-            OwnedAttributeValue::Int32(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_int64(&self) -> Option<i64> {
-        match self {
-            OwnedAttributeValue::Int64(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_uint32(&self) -> Option<u32> {
-        match self {
-            OwnedAttributeValue::Uint32(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_uint64(&self) -> Option<u64> {
-        match self {
-            OwnedAttributeValue::Uint64(i) => Some(*i),
-            _ => None,
-        }
-    }
-
-    pub fn as_bool(&self) -> Option<bool> {
-        match self {
-            OwnedAttributeValue::Bool(b) => Some(*b),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
-        match self {
-            OwnedAttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
-        match self {
-            OwnedAttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
-        match self {
-            OwnedAttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
-        match self {
-            OwnedAttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
-        match self {
-            OwnedAttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
-        match self {
-            OwnedAttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
-            _ => None,
-        }
-    }
-
-    pub fn as_bytes(&self) -> Option<&[u8]> {
-        match self {
-            OwnedAttributeValue::Bytes(b) => Some(b),
-            _ => None,
-        }
-    }
-}

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

@@ -0,0 +1,163 @@
+use crate::{nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId};
+use bumpalo::boxed::Box as BumpBox;
+
+/// An Element's unique identifier.
+///
+/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
+/// unmounted, then the `ElementId` will be reused for a new component.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+pub struct ElementId(pub usize);
+
+pub(crate) struct ElementRef {
+    // the pathway of the real element inside the template
+    pub path: ElementPath,
+
+    // The actual template
+    pub template: *const VNode<'static>,
+}
+
+#[derive(Clone, Copy)]
+pub enum ElementPath {
+    Deep(&'static [u8]),
+    Root(usize),
+}
+
+impl ElementRef {
+    pub(crate) fn null() -> Self {
+        Self {
+            template: std::ptr::null_mut(),
+            path: ElementPath::Root(0),
+        }
+    }
+}
+
+impl VirtualDom {
+    pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
+        self.next(template, ElementPath::Deep(path))
+    }
+
+    pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
+        self.next(template, ElementPath::Root(path))
+    }
+
+    fn next(&mut self, template: &VNode, path: ElementPath) -> ElementId {
+        let entry = self.elements.vacant_entry();
+        let id = entry.key();
+
+        entry.insert(ElementRef {
+            template: template as *const _ as *mut _,
+            path,
+        });
+        ElementId(id)
+    }
+
+    pub(crate) fn reclaim(&mut self, el: ElementId) {
+        self.try_reclaim(el)
+            .unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
+    }
+
+    pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
+        if el.0 == 0 {
+            panic!(
+                "Cannot reclaim the root element - {:#?}",
+                std::backtrace::Backtrace::force_capture()
+            );
+        }
+
+        self.elements.try_remove(el.0)
+    }
+
+    pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) {
+        let node: *const VNode = node as *const _;
+        self.elements[el.0].template = unsafe { std::mem::transmute(node) };
+    }
+
+    // Drop a scope and all its children
+    pub(crate) fn drop_scope(&mut self, id: ScopeId) {
+        if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
+            if let RenderReturn::Sync(Ok(node)) = unsafe { root.extend_lifetime_ref() } {
+                self.drop_scope_inner(node)
+            }
+        }
+
+        self.scopes[id.0].props.take();
+
+        let scope = &mut self.scopes[id.0];
+
+        // Drop all the hooks once the children are dropped
+        // this means we'll drop hooks bottom-up
+        for hook in scope.hook_list.get_mut().drain(..) {
+            drop(unsafe { BumpBox::from_raw(hook) });
+        }
+    }
+
+    fn drop_scope_inner(&mut self, node: &VNode) {
+        node.clear_listeners();
+        node.dynamic_nodes.iter().for_each(|node| match node {
+            DynamicNode::Component(c) => self.drop_scope(c.scope.get().unwrap()),
+            DynamicNode::Fragment(nodes) => {
+                nodes.iter().for_each(|node| self.drop_scope_inner(node))
+            }
+            DynamicNode::Placeholder(t) => {
+                self.try_reclaim(t.get());
+            }
+            DynamicNode::Text(t) => {
+                self.try_reclaim(t.id.get());
+            }
+        });
+
+        for root in node.root_ids {
+            let id = root.get();
+            if id.0 != 0 {
+                self.try_reclaim(id);
+            }
+        }
+    }
+
+    /// Descend through the tree, removing any borrowed props and listeners
+    pub(crate) fn ensure_drop_safety(&self, scope: ScopeId) {
+        let node = unsafe { self.scopes[scope.0].previous_frame().try_load_node() };
+
+        // And now we want to make sure the previous frame has dropped anything that borrows self
+        if let Some(RenderReturn::Sync(Ok(node))) = node {
+            self.ensure_drop_safety_inner(node);
+        }
+    }
+
+    fn ensure_drop_safety_inner(&self, node: &VNode) {
+        node.clear_listeners();
+
+        node.dynamic_nodes.iter().for_each(|child| match child {
+            // Only descend if the props are borrowed
+            DynamicNode::Component(c) if !c.static_props => {
+                self.ensure_drop_safety(c.scope.get().unwrap());
+                c.props.set(None);
+            }
+
+            DynamicNode::Fragment(f) => f
+                .iter()
+                .for_each(|node| self.ensure_drop_safety_inner(node)),
+
+            _ => {}
+        });
+    }
+}
+
+impl ElementPath {
+    pub(crate) fn is_ascendant(&self, big: &&[u8]) -> bool {
+        match *self {
+            ElementPath::Deep(small) => small.len() <= big.len() && small == &big[..small.len()],
+            ElementPath::Root(r) => big.len() == 1 && big[0] == r as u8,
+        }
+    }
+}
+
+impl PartialEq<&[u8]> for ElementPath {
+    fn eq(&self, other: &&[u8]) -> bool {
+        match *self {
+            ElementPath::Deep(deep) => deep.eq(*other),
+            ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8,
+        }
+    }
+}

+ 29 - 0
packages/core/src/bump_frame.rs

@@ -0,0 +1,29 @@
+use crate::nodes::RenderReturn;
+use bumpalo::Bump;
+use std::cell::Cell;
+
+pub(crate) struct BumpFrame {
+    pub bump: Bump,
+    pub node: Cell<*const RenderReturn<'static>>,
+}
+
+impl BumpFrame {
+    pub(crate) fn new(capacity: usize) -> Self {
+        let bump = Bump::with_capacity(capacity);
+        Self {
+            bump,
+            node: Cell::new(std::ptr::null()),
+        }
+    }
+
+    /// Creates a new lifetime out of thin air
+    pub(crate) unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
+        let node = self.node.get();
+
+        if node.is_null() {
+            return None;
+        }
+
+        unsafe { std::mem::transmute(&*node) }
+    }
+}

+ 401 - 0
packages/core/src/create.rs

@@ -0,0 +1,401 @@
+use std::cell::Cell;
+use std::rc::Rc;
+
+use crate::innerlude::{VComponent, VText};
+use crate::mutations::Mutation;
+use crate::mutations::Mutation::*;
+use crate::nodes::VNode;
+use crate::nodes::{DynamicNode, TemplateNode};
+use crate::virtual_dom::VirtualDom;
+use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
+
+impl<'b> VirtualDom {
+    /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
+    ///
+    /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
+    pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
+        self.scope_stack.push(scope);
+        let out = self.create(template);
+        self.scope_stack.pop();
+
+        out
+    }
+
+    /// Create this template and write its mutations
+    pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize {
+        // The best renderers will have templates prehydrated and registered
+        // Just in case, let's create the template using instructions anyways
+        if !self.templates.contains_key(&template.template.name) {
+            self.register_template(template);
+        }
+
+        // Walk the roots, creating nodes and assigning IDs
+        // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
+        let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable();
+        let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
+
+        let cur_scope = self.scope_stack.last().copied().unwrap();
+
+        let mut on_stack = 0;
+        for (root_idx, root) in template.template.roots.iter().enumerate() {
+            // We might need to generate an ID for the root node
+            on_stack += match root {
+                TemplateNode::DynamicText { id } | TemplateNode::Dynamic { id } => {
+                    match &template.dynamic_nodes[*id] {
+                        // a dynamic text node doesn't replace a template node, instead we create it on the fly
+                        DynamicNode::Text(VText { id: slot, value }) => {
+                            let id = self.next_element(template, template.template.node_paths[*id]);
+                            slot.set(id);
+
+                            // Safety: we promise not to re-alias this text later on after committing it to the mutation
+                            let unbounded_text = unsafe { std::mem::transmute(*value) };
+                            self.mutations.push(CreateTextNode {
+                                value: unbounded_text,
+                                id,
+                            });
+
+                            1
+                        }
+
+                        DynamicNode::Placeholder(slot) => {
+                            let id = self.next_element(template, template.template.node_paths[*id]);
+                            slot.set(id);
+                            self.mutations.push(CreatePlaceholder { id });
+                            1
+                        }
+
+                        DynamicNode::Fragment(_) | DynamicNode::Component { .. } => {
+                            self.create_dynamic_node(template, &template.dynamic_nodes[*id], *id)
+                        }
+                    }
+                }
+
+                TemplateNode::Element { .. } | TemplateNode::Text { .. } => {
+                    let this_id = self.next_root(template, root_idx);
+
+                    template.root_ids[root_idx].set(this_id);
+                    self.mutations.push(LoadTemplate {
+                        name: template.template.name,
+                        index: root_idx,
+                        id: this_id,
+                    });
+
+                    // we're on top of a node that has a dynamic attribute for a descendant
+                    // Set that attribute now before the stack gets in a weird state
+                    while let Some((mut attr_id, path)) =
+                        dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
+                    {
+                        // if attribute is on a root node, then we've already created the element
+                        // Else, it's deep in the template and we should create a new id for it
+                        let id = match path.len() {
+                            1 => this_id,
+                            _ => {
+                                let id = self
+                                    .next_element(template, template.template.attr_paths[attr_id]);
+                                self.mutations.push(Mutation::AssignId {
+                                    path: &path[1..],
+                                    id,
+                                });
+                                id
+                            }
+                        };
+
+                        loop {
+                            let attribute = template.dynamic_attrs.get(attr_id).unwrap();
+                            attribute.mounted_element.set(id);
+
+                            // Safety: we promise not to re-alias this text later on after committing it to the mutation
+                            let unbounded_name = unsafe { std::mem::transmute(attribute.name) };
+
+                            match &attribute.value {
+                                AttributeValue::Text(value) => {
+                                    // Safety: we promise not to re-alias this text later on after committing it to the mutation
+                                    let unbounded_value = unsafe { std::mem::transmute(*value) };
+
+                                    self.mutations.push(SetAttribute {
+                                        name: unbounded_name,
+                                        value: unbounded_value,
+                                        ns: attribute.namespace,
+                                        id,
+                                    })
+                                }
+                                AttributeValue::Bool(value) => {
+                                    self.mutations.push(SetBoolAttribute {
+                                        name: unbounded_name,
+                                        value: *value,
+                                        id,
+                                    })
+                                }
+                                AttributeValue::Listener(_) => {
+                                    self.mutations.push(NewEventListener {
+                                        // all listeners start with "on"
+                                        name: &unbounded_name[2..],
+                                        scope: cur_scope,
+                                        id,
+                                    })
+                                }
+                                AttributeValue::Float(_) => todo!(),
+                                AttributeValue::Int(_) => todo!(),
+                                AttributeValue::Any(_) => todo!(),
+                                AttributeValue::None => todo!(),
+                            }
+
+                            // Only push the dynamic attributes forward if they match the current path (same element)
+                            match dynamic_attrs.next_if(|(_, p)| *p == path) {
+                                Some((next_attr_id, _)) => attr_id = next_attr_id,
+                                None => break,
+                            }
+                        }
+                    }
+
+                    // We're on top of a node that has a dynamic child for a descendant
+                    // Skip any node that's a root
+                    let mut start = None;
+                    let mut end = None;
+
+                    // Collect all the dynamic nodes below this root
+                    // We assign the start and end of the range of dynamic nodes since they area ordered in terms of tree path
+                    //
+                    // [0]
+                    // [1, 1]     <---|
+                    // [1, 1, 1]  <---| these are the range of dynamic nodes below root 1
+                    // [1, 1, 2]  <---|
+                    // [2]
+                    //
+                    // We collect each range and then create them and replace the placeholder in the template
+                    while let Some((idx, p)) =
+                        dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8)
+                    {
+                        if p.len() == 1 {
+                            continue;
+                        }
+
+                        if start.is_none() {
+                            start = Some(idx);
+                        }
+
+                        end = Some(idx);
+                    }
+
+                    //
+                    if let (Some(start), Some(end)) = (start, end) {
+                        for idx in start..=end {
+                            let node = &template.dynamic_nodes[idx];
+                            let m = self.create_dynamic_node(template, node, idx);
+                            if m > 0 {
+                                self.mutations.push(ReplacePlaceholder {
+                                    m,
+                                    path: &template.template.node_paths[idx][1..],
+                                });
+                            }
+                        }
+                    }
+
+                    // elements create only one node :-)
+                    1
+                }
+            };
+        }
+
+        on_stack
+    }
+
+    /// Insert a new template into the VirtualDom's template registry
+    fn register_template(&mut self, template: &'b VNode<'b>) {
+        // First, make sure we mark the template as seen, regardless if we process it
+        self.templates
+            .insert(template.template.name, template.template);
+
+        // If it's all dynamic nodes, then we don't need to register it
+        // Quickly run through and see if it's all just dynamic nodes
+        let dynamic_roots = template
+            .template
+            .roots
+            .iter()
+            .filter(|root| {
+                matches!(
+                    root,
+                    TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
+                )
+            })
+            .count();
+
+        if dynamic_roots == template.template.roots.len() {
+            return;
+        }
+
+        self.mutations.templates.push(template.template);
+    }
+
+    pub(crate) fn create_dynamic_node(
+        &mut self,
+        template: &'b VNode<'b>,
+        node: &'b DynamicNode<'b>,
+        idx: usize,
+    ) -> usize {
+        use DynamicNode::*;
+        match node {
+            Text(text) => self.create_dynamic_text(template, text, idx),
+            Fragment(frag) => self.create_fragment(frag),
+            Placeholder(frag) => self.create_placeholder(frag, template, idx),
+            Component(component) => self.create_component_node(template, component, idx),
+        }
+    }
+
+    fn create_dynamic_text(
+        &mut self,
+        template: &'b VNode<'b>,
+        text: &'b VText<'b>,
+        idx: usize,
+    ) -> usize {
+        // Allocate a dynamic element reference for this text node
+        let new_id = self.next_element(template, template.template.node_paths[idx]);
+
+        // Make sure the text node is assigned to the correct element
+        text.id.set(new_id);
+
+        // Safety: we promise not to re-alias this text later on after committing it to the mutation
+        let value = unsafe { std::mem::transmute(text.value) };
+
+        // Add the mutation to the list
+        self.mutations.push(HydrateText {
+            id: new_id,
+            path: &template.template.node_paths[idx][1..],
+            value,
+        });
+
+        // Since we're hydrating an existing node, we don't create any new nodes
+        0
+    }
+
+    pub(crate) fn create_placeholder(
+        &mut self,
+        slot: &Cell<ElementId>,
+        template: &'b VNode<'b>,
+        idx: usize,
+    ) -> usize {
+        // Allocate a dynamic element reference for this text node
+        let id = self.next_element(template, template.template.node_paths[idx]);
+
+        // Make sure the text node is assigned to the correct element
+        slot.set(id);
+
+        // Assign the ID to the existing node in the template
+        self.mutations.push(AssignId {
+            path: &template.template.node_paths[idx][1..],
+            id,
+        });
+
+        // Since the placeholder is already in the DOM, we don't create any new nodes
+        0
+    }
+
+    pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+        nodes.iter().fold(0, |acc, child| acc + self.create(child))
+    }
+
+    pub(super) fn create_component_node(
+        &mut self,
+        template: &'b VNode<'b>,
+        component: &'b VComponent<'b>,
+        idx: usize,
+    ) -> usize {
+        let props = component
+            .props
+            .replace(None)
+            .expect("Props to always exist when a component is being created");
+
+        let unbounded_props = unsafe { std::mem::transmute(props) };
+
+        let scope = self.new_scope(unbounded_props, component.name);
+        let scope = scope.id;
+        component.scope.set(Some(scope));
+
+        let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
+
+        use RenderReturn::*;
+
+        match return_nodes {
+            Sync(Ok(t)) => self.mount_component(scope, template, t, idx),
+            Sync(Err(_e)) => todo!("Propogate error upwards"),
+            Async(_) => self.mount_component_placeholder(template, idx, scope),
+        }
+    }
+
+    fn mount_component(
+        &mut self,
+        scope: ScopeId,
+        parent: &'b VNode<'b>,
+        new: &'b VNode<'b>,
+        idx: usize,
+    ) -> usize {
+        // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary
+        // is encountered
+        let mutations_to_this_point = self.mutations.edits.len();
+
+        // Create the component's root element
+        let created = self.create_scope(scope, new);
+
+        // If there are no suspense leaves below us, then just don't bother checking anything suspense related
+        if self.collected_leaves.is_empty() {
+            return created;
+        }
+
+        // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense
+        let boundary = match self.scopes[scope.0].has_context::<Rc<SuspenseContext>>() {
+            Some(boundary) => boundary,
+            _ => return created,
+        };
+
+        // Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
+        let new_id = self.next_element(new, parent.template.node_paths[idx]);
+
+        // Now connect everything to the boundary
+        self.scopes[scope.0].placeholder.set(Some(new_id));
+
+        // This involves breaking off the mutations to this point, and then creating a new placeholder for the boundary
+        // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately
+        let split_off = unsafe {
+            std::mem::transmute::<Vec<Mutation>, Vec<Mutation>>(
+                self.mutations.edits.split_off(mutations_to_this_point),
+            )
+        };
+        boundary.mutations.borrow_mut().edits.extend(split_off);
+        boundary.created_on_stack.set(created);
+        boundary
+            .waiting_on
+            .borrow_mut()
+            .extend(self.collected_leaves.drain(..));
+
+        // Now assign the placeholder in the DOM
+        self.mutations.push(AssignId {
+            id: new_id,
+            path: &parent.template.node_paths[idx][1..],
+        });
+
+        0
+    }
+
+    /// Take the rendered nodes from a component and handle them if they were async
+    ///
+    /// IE simply assign an ID to the placeholder
+    fn mount_component_placeholder(
+        &mut self,
+        template: &VNode,
+        idx: usize,
+        scope: ScopeId,
+    ) -> usize {
+        let new_id = self.next_element(template, template.template.node_paths[idx]);
+
+        // Set the placeholder of the scope
+        self.scopes[scope.0].placeholder.set(Some(new_id));
+
+        // Since the placeholder is already in the DOM, we don't create any new nodes
+        self.mutations.push(AssignId {
+            id: new_id,
+            path: &template.template.node_paths[idx][1..],
+        });
+
+        0
+    }
+}

+ 538 - 1115
packages/core/src/diff.rs

@@ -1,859 +1,390 @@
-#![warn(clippy::pedantic)]
-#![allow(clippy::cast_possible_truncation)]
-
-//! This module contains the stateful [`DiffState`] and all methods to diff [`VNode`]s, their properties, and their children.
-//!
-//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
-//! of mutations for the renderer to apply.
-//!
-//! ## Notice:
-//!
-//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
-//! Components, Fragments, Suspense, `SubTree` memoization, incremental diffing, cancellation, pausing, priority
-//! scheduling, and additional batching operations.
-//!
-//! ## Implementation Details:
-//!
-//! ### IDs for elements
-//! --------------------
-//! All nodes are addressed by their IDs.
-//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
-//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
-//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive
-//! garbage collection as the [`crate::VirtualDom`] replaces old nodes.
-//!
-//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
-//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly
-//! and brick the user's page.
-//!
-//! ### Fragment Support
-//! --------------------
-//! 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 anchor node lets us "reserve" a spot for the empty
-//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`crate::innerlude::NodeFactory`] - it is
-//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
-//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
-//! the platform.
-//!
-//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
-//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
-//!
-//! ### Suspense
-//! ------------
-//! Dioxus implements Suspense slightly differently than React. In React, each fiber is manually progressed until it runs
-//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once
-//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before
-//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on.
-//! Due to the frequent calls to [`crate::virtual_dom::VirtualDom::work_with_deadline`] we can get the pure "fetch-as-you-render" behavior of React Fiber.
-//!
-//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to
-//! DOM, but as a placeholder.
-//!
-//! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes
-//! and contents, without the need for a `use_suspense` hook. In the interim, this is the quickest way to get Suspense working.
-//!
-//! ## Subtree Memoization
-//! -----------------------
-//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can
-//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
-//! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
-//! ```rust, ignore
-//! rsx!( div { class: "hello world", "this node is entirely static" } )
-//! ```
-//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to
-//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
-//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
-//! a way of short-circuiting the most expensive checks.
-//!
-//! ## Bloom Filter and Heuristics
-//! ------------------------------
-//! 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. The information currently tracked includes the size of a
-//! bump arena after first render, the number of hooks, and the number of nodes in the tree.
-//!
-//! ## Garbage Collection
-//! ---------------------
-//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage
-//! 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.
-//!
-//! ## Further Reading and Thoughts
-//! ----------------------------
-//! There are more ways of increasing diff performance here that are currently not implemented.
-//! - Strong memoization of subtrees.
-//! - Guided diffing.
-//! - Certain web-dom-specific optimizations.
-//!
-//! More info on how to improve this diffing algorithm:
-//!  - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
+use std::cell::Cell;
 
 use crate::{
-    dynamic_template_context::TemplateContext,
-    innerlude::{
-        AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment,
-        VNode, VPlaceholder, VText,
-    },
-    template::{
-        Template, TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId,
-        TemplateNodeType, TemplateValue, TextTemplateSegment, VTemplateRef,
-    },
-    Attribute, TemplateAttributeValue,
+    arena::ElementId,
+    innerlude::{DirtyScope, VComponent, VText},
+    mutations::Mutation,
+    nodes::RenderReturn,
+    nodes::{DynamicNode, VNode},
+    scopes::ScopeId,
+    virtual_dom::VirtualDom,
+    AttributeValue, TemplateNode,
 };
-use bumpalo::Bump;
-use rustc_hash::{FxHashMap, FxHashSet};
-use smallvec::{smallvec, SmallVec};
-
-pub(crate) struct DiffState<'bump> {
-    pub(crate) scopes: &'bump ScopeArena,
-    pub(crate) mutations: Mutations<'bump>,
-    pub(crate) force_diff: bool,
-    pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
-}
 
-impl<'b> DiffState<'b> {
-    pub fn new(scopes: &'b ScopeArena) -> Self {
-        Self {
-            scopes,
-            mutations: Mutations::new(),
-            force_diff: false,
-            scope_stack: smallvec![],
-        }
-    }
+use fxhash::{FxHashMap, FxHashSet};
+use DynamicNode::*;
 
-    pub fn diff_scope(&mut self, parent: ElementId, scopeid: ScopeId) {
-        let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
+impl<'b> VirtualDom {
+    pub(super) fn diff_scope(&mut self, scope: ScopeId) {
+        let scope_state = &mut self.scopes[scope.0];
 
-        self.scope_stack.push(scopeid);
-        {
-            self.diff_node(parent, old, new);
+        self.scope_stack.push(scope);
+        unsafe {
+            // Load the old and new bump arenas
+            let old = scope_state
+                .previous_frame()
+                .try_load_node()
+                .expect("Call rebuild before diffing");
+
+            let new = scope_state
+                .current_frame()
+                .try_load_node()
+                .expect("Call rebuild before diffing");
+
+            use RenderReturn::{Async, Sync};
+
+            match (old, new) {
+                (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
+
+                // Err cases
+                (Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e),
+                (Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r),
+                (Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ }
+
+                // Async
+                (Sync(Ok(_l)), Async(_)) => todo!(),
+                (Sync(Err(_e)), Async(_)) => todo!(),
+                (Async(_), Sync(Ok(_r))) => todo!(),
+                (Async(_), Sync(Err(_e))) => { /* nothing */ }
+                (Async(_), Async(_)) => { /* nothing */ }
+            };
         }
         self.scope_stack.pop();
-
-        self.mutations.mark_dirty_scope(scopeid);
     }
 
-    pub fn diff_node(
-        &mut self,
-        parent: ElementId,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        use VNode::{Component, Element, Fragment, Placeholder, TemplateRef, Text};
-        match (old_node, new_node) {
-            (Text(old), Text(new)) => {
-                self.diff_text_nodes(old, new, old_node, new_node);
-            }
+    fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {}
+    fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
 
-            (Placeholder(old), Placeholder(new)) => {
-                self.diff_placeholder_nodes(old, new, old_node, new_node);
-            }
-
-            (Element(old), Element(new)) => {
-                self.diff_element_nodes(old, new, old_node, new_node);
-            }
-
-            (Component(old), Component(new)) => {
-                self.diff_component_nodes(parent, old, new, old_node, new_node);
-            }
-
-            (Fragment(old), Fragment(new)) => {
-                self.diff_fragment_nodes(parent, old, new);
-            }
+    fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
+        if left_template.template.name != right_template.template.name {
+            return self.light_diff_templates(left_template, right_template);
+        }
 
-            (TemplateRef(old), TemplateRef(new)) => {
-                self.diff_template_ref_nodes(parent, old, new, old_node, new_node);
+        for (left_attr, right_attr) in left_template
+            .dynamic_attrs
+            .iter()
+            .zip(right_template.dynamic_attrs.iter())
+        {
+            // Move over the ID from the old to the new
+            right_attr
+                .mounted_element
+                .set(left_attr.mounted_element.get());
+
+            // We want to make sure anything listener that gets pulled is valid
+            if let AttributeValue::Listener(_) = right_attr.value {
+                self.update_template(left_attr.mounted_element.get(), right_template);
+            }
+
+            if left_attr.value != right_attr.value || left_attr.volatile {
+                // todo: add more types of attribute values
+                match right_attr.value {
+                    AttributeValue::Text(text) => {
+                        let name = unsafe { std::mem::transmute(left_attr.name) };
+                        let value = unsafe { std::mem::transmute(text) };
+                        self.mutations.push(Mutation::SetAttribute {
+                            id: left_attr.mounted_element.get(),
+                            ns: right_attr.namespace,
+                            name,
+                            value,
+                        });
+                    }
+                    // todo: more types of attribute values
+                    _ => todo!("other attribute types"),
+                }
             }
-
-            (
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
-                Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
-            ) => self.replace_node(parent, old_node, new_node),
         }
-    }
 
-    pub fn create_node(&mut self, parent: ElementId, node: &'b VNode<'b>, nodes: &mut Vec<u64>) {
-        match node {
-            VNode::Text(vtext) => self.create_text_node(vtext, node, nodes),
-            VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node, nodes),
-            VNode::Element(element) => self.create_element_node(parent, element, node, nodes),
-            VNode::Fragment(frag) => self.create_fragment_node(parent, frag, nodes),
-            VNode::Component(component) => self.create_component_node(parent, component, nodes),
-            VNode::TemplateRef(temp) => self.create_template_ref_node(parent, temp, node, nodes),
+        for (idx, (left_node, right_node)) in left_template
+            .dynamic_nodes
+            .iter()
+            .zip(right_template.dynamic_nodes.iter())
+            .enumerate()
+        {
+            match (left_node, right_node) {
+                (Text(left), Text(right)) => self.diff_vtext(left, right),
+                (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
+                (Placeholder(left), Placeholder(right)) => {
+                    right.set(left.get());
+                }
+                (Component(left), Component(right)) => {
+                    self.diff_vcomponent(left, right, right_template, idx)
+                }
+                (Placeholder(left), Fragment(right)) => {
+                    self.replace_placeholder_with_nodes(left, right)
+                }
+                (Fragment(left), Placeholder(right)) => {
+                    self.replace_nodes_with_placeholder(left, right)
+                }
+                _ => todo!(),
+            };
         }
-    }
-
-    fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>, nodes: &mut Vec<u64>) {
-        let real_id = self.scopes.reserve_node(node);
-        text.id.set(Some(real_id));
-        self.mutations
-            .create_text_node(text.text, Some(real_id.as_u64()));
-        nodes.push(real_id.0 as u64);
-    }
-
-    fn create_anchor_node(
-        &mut self,
-        anchor: &'b VPlaceholder,
-        node: &'b VNode<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let real_id = self.scopes.reserve_node(node);
-        anchor.id.set(Some(real_id));
-        self.mutations.create_placeholder(Some(real_id.as_u64()));
-        nodes.push(real_id.0 as u64);
-    }
-
-    fn create_element_node(
-        &mut self,
-        parent: ElementId,
-        element: &'b VElement<'b>,
-        node: &'b VNode<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let VElement {
-            tag: tag_name,
-            listeners,
-            attributes,
-            children,
-            namespace,
-            id: dom_id,
-            parent: parent_id,
-            ..
-        } = &element;
-
-        parent_id.set(Some(parent));
-
-        let real_id = self.scopes.reserve_node(node);
-
-        dom_id.set(Some(real_id));
 
+        // Make sure the roots get transferred over
+        for (left, right) in left_template
+            .root_ids
+            .iter()
+            .zip(right_template.root_ids.iter())
         {
-            self.mutations
-                .create_element(tag_name, *namespace, Some(real_id.as_u64()), 0);
-
-            let cur_scope_id = self.current_scope();
-
-            for listener in listeners.iter() {
-                listener.mounted_node.set(Some(real_id));
-                self.mutations.new_event_listener(listener, cur_scope_id);
-            }
-
-            for attr in attributes.iter() {
-                self.mutations.set_attribute(attr, Some(real_id.as_u64()));
-            }
-
-            if !children.is_empty() {
-                self.create_and_append_children(real_id, children);
-            }
+            right.set(left.get());
         }
-
-        nodes.push(real_id.0 as u64);
     }
 
-    fn create_fragment_node(
-        &mut self,
-        parent: ElementId,
-        frag: &'b VFragment<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        self.create_children(parent, frag.children, nodes);
+    fn replace_placeholder_with_nodes(&mut self, l: &'b Cell<ElementId>, r: &'b [VNode<'b>]) {
+        let m = self.create_children(r);
+        let id = l.get();
+        self.mutations.push(Mutation::ReplaceWith { id, m });
+        self.reclaim(id);
     }
 
-    fn create_component_node(
-        &mut self,
-        parent: ElementId,
-        vcomponent: &'b VComponent<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let parent_idx = self.current_scope();
-
-        // the component might already exist - if it does, we need to reuse it
-        // this makes figure out when to drop the component more complicated
-        let new_idx = if let Some(idx) = vcomponent.scope.get() {
-            assert!(self.scopes.get_scope(idx).is_some());
-            idx
-        } else {
-            // Insert a new scope into our component list
-            let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap();
-            let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
-            self.scopes
-                .new_with_key(vcomponent.user_fc, props, Some(parent_idx), parent)
-        };
+    fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell<ElementId>) {
+        // Remove the old nodes, except for one
+        self.remove_nodes(&l[1..]);
 
-        // Actually initialize the caller's slot with the right address
-        vcomponent.scope.set(Some(new_idx));
+        // Now create the new one
+        let first = self.replace_inner(&l[0]);
 
-        log::trace!(
-            "created component \"{}\", id: {:?} parent {:?}",
-            vcomponent.fn_name,
-            new_idx,
-            parent_idx,
-        );
-
-        // if vcomponent.can_memoize {
-        //     // todo: implement promotion logic. save us from boxing props that we don't need
-        // } else {
-        //     // track this component internally so we know the right drop order
-        // }
-
-        self.enter_scope(new_idx);
-
-        // Run the scope for one iteration to initialize it
-        self.scopes.run_scope(new_idx);
-        self.mutations.mark_dirty_scope(new_idx);
-
-        // Take the node that was just generated from running the component
-        let nextnode = self.scopes.fin_head(new_idx);
-        self.create_node(parent, nextnode, nodes);
-
-        self.leave_scope();
-    }
-
-    pub(crate) fn create_template_ref_node(
-        &mut self,
-        parent: ElementId,
-        new: &'b VTemplateRef<'b>,
-        _node: &'b VNode<'b>,
-        nodes: &mut Vec<u64>,
-    ) {
-        let (id, just_created) = {
-            let mut resolver = self.scopes.template_resolver.borrow_mut();
-            resolver.get_or_create_client_id(&new.template_id, self.scopes)
-        };
-
-        let template = {
-            let templates = self.scopes.templates.borrow();
-            templates.get(&new.template_id).unwrap().clone()
-        };
-        let template = template.borrow();
-
-        if just_created {
-            self.register_template(&template, id);
-        }
-
-        new.template_ref_id
-            .set(Some(self.scopes.reserve_template_ref(new)));
-
-        let template_ref_id = new.template_ref_id.get().unwrap();
-
-        let root_nodes = template.root_nodes();
-        nodes.extend(root_nodes.iter().map(|node_id| {
-            let real_id = self.scopes.reserve_template_node(template_ref_id, *node_id);
-            new.set_node_id(*node_id, real_id);
-            real_id.as_u64()
-        }));
+        // Create the placeholder first, ensuring we get a dedicated ID for the placeholder
+        let placeholder = self.next_element(&l[0], &[]);
+        r.set(placeholder);
+        self.mutations
+            .push(Mutation::CreatePlaceholder { id: placeholder });
 
-        self.mutations.clone_node_children(
-            Some(id.as_u64()),
-            nodes[(nodes.len() - root_nodes.len())..].to_vec(),
-        );
+        self.mutations
+            .push(Mutation::ReplaceWith { id: first, m: 1 });
 
-        new.hydrate(parent, &template, self);
+        self.try_reclaim(first);
     }
 
-    pub(crate) fn diff_text_nodes(
+    fn diff_vcomponent(
         &mut self,
-        old: &'b VText<'b>,
-        new: &'b VText<'b>,
-        _old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
+        left: &'b VComponent<'b>,
+        right: &'b VComponent<'b>,
+        right_template: &'b VNode<'b>,
+        idx: usize,
     ) {
-        if std::ptr::eq(old, new) {
+        // Replace components that have different render fns
+        if left.render_fn != right.render_fn {
+            let created = self.create_component_node(right_template, right, idx);
+            let head = unsafe {
+                self.scopes[left.scope.get().unwrap().0]
+                    .root_node()
+                    .extend_lifetime_ref()
+            };
+            let id = match head {
+                RenderReturn::Sync(Ok(node)) => self.replace_inner(node),
+                _ => todo!(),
+            };
+            self.mutations
+                .push(Mutation::ReplaceWith { id, m: created });
+            self.drop_scope(left.scope.get().unwrap());
             return;
         }
 
-        // if the node is comming back not assigned, that means it was borrowed but removed
-        let root = match old.id.get() {
-            Some(id) => id,
-            None => self.scopes.reserve_node(new_node),
-        };
+        // Make sure the new vcomponent has the right scopeid associated to it
+        let scope_id = left.scope.get().unwrap();
+        right.scope.set(Some(scope_id));
 
-        if old.text != new.text {
-            self.mutations.set_text(new.text, Some(root.as_u64()));
-        }
+        // copy out the box for both
+        let old = self.scopes[scope_id.0].props.as_ref();
+        let new = right.props.replace(None).unwrap();
 
-        self.scopes.update_node(new_node, root);
-
-        new.id.set(Some(root));
-    }
-
-    pub(crate) fn diff_placeholder_nodes(
-        &mut self,
-        old: &'b VPlaceholder,
-        new: &'b VPlaceholder,
-        _old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        if std::ptr::eq(old, new) {
+        // If the props are static, then we try to memoize by setting the new with the old
+        // The target scopestate still has the reference to the old props, so there's no need to update anything
+        // This also implicitly drops the new props since they're not used
+        if left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } {
             return;
         }
 
-        // if the node is comming back not assigned, that means it was borrowed but removed
-        let root = match old.id.get() {
-            Some(id) => id,
-            None => self.scopes.reserve_node(new_node),
-        };
+        // First, move over the props from the old to the new, dropping old props in the process
+        self.scopes[scope_id.0].props = unsafe { std::mem::transmute(new) };
 
-        self.scopes.update_node(new_node, root);
-        new.id.set(Some(root));
+        // Now run the component and diff it
+        self.run_scope(scope_id);
+        self.diff_scope(scope_id);
     }
 
-    fn diff_element_nodes(
-        &mut self,
-        old: &'b VElement<'b>,
-        new: &'b VElement<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
-        // if the node is comming back not assigned, that means it was borrowed but removed
-        let root = match old.id.get() {
-            Some(id) => id,
-            None => self.scopes.reserve_node(new_node),
-        };
-
-        // If the element type is completely different, the element needs to be re-rendered completely
-        // This is an optimization React makes due to how users structure their code
-        //
-        // This case is rather rare (typically only in non-keyed lists)
-        if new.tag != old.tag || new.namespace != old.namespace {
-            self.replace_node(root, old_node, new_node);
-            return;
+    /// Lightly diff the two templates, checking only their roots.
+    ///
+    /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
+    /// behavior where the component state is preserved when the component is re-rendered.
+    ///
+    /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
+    ///
+    /// We then pass the new template through "create" which should be smart enough to skip roots.
+    ///
+    /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
+    /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
+    ///
+    /// This is mostly implemented to help solve the issue where the same component is rendered under two different
+    /// conditions:
+    ///
+    /// ```rust, ignore
+    /// if enabled {
+    ///     rsx!{ Component { enabled_sign: "abc" } }
+    /// } else {
+    ///     rsx!{ Component { enabled_sign: "xyz" } }
+    /// }
+    /// ```
+    ///
+    /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
+    /// then you should be passing in separate props instead.
+    ///
+    /// ```rust, ignore
+    /// let props = if enabled {
+    ///     ComponentProps { enabled_sign: "abc" }
+    /// } else {
+    ///     ComponentProps { enabled_sign: "xyz" }
+    /// };
+    ///
+    /// rsx! {
+    ///     Component { ..props }
+    /// }
+    /// ```
+    fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
+        match matching_components(left, right) {
+            None => self.replace(left, right),
+            Some(components) => components
+                .into_iter()
+                .enumerate()
+                .for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)),
         }
+    }
 
-        self.scopes.update_node(new_node, root);
-
-        new.id.set(Some(root));
-        new.parent.set(old.parent.get());
-
-        // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
-        // element to modify its attributes.
-        // it would result in fewer instructions if we just set the id directly.
-        // it would also clean up this code some, but that's not very important anyways
+    /// Diff the two text nodes
+    ///
+    /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
+    /// different.
+    fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
+        let id = left.id.get();
 
-        // 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.is_static && old_attr.value != new_attr.value
-                    || new_attr.attribute.volatile
-                {
-                    self.mutations.set_attribute(new_attr, Some(root.as_u64()));
-                }
-            }
-        } else {
-            for attribute in old.attributes {
-                self.mutations
-                    .remove_attribute(attribute, Some(root.as_u64()));
-            }
-            for attribute in new.attributes {
-                self.mutations.set_attribute(attribute, Some(root.as_u64()));
-            }
+        right.id.set(id);
+        if left.value != right.value {
+            let value = unsafe { std::mem::transmute(right.value) };
+            self.mutations.push(Mutation::SetText { id, value });
         }
+    }
 
-        // 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_id = self.current_scope();
-
-        if old.listeners.len() == new.listeners.len() {
-            for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
-                new_l.mounted_node.set(old_l.mounted_node.get());
-                if old_l.event != new_l.event {
-                    self.mutations
-                        .remove_event_listener(old_l.event, Some(root.as_u64()));
-                    self.mutations.new_event_listener(new_l, cur_scope_id);
+    /// Remove all the top-level nodes, returning the firstmost root ElementId
+    ///
+    /// All IDs will be garbage collected
+    fn replace_inner(&mut self, node: &'b VNode<'b>) -> ElementId {
+        let id = match node.dynamic_root(0) {
+            None => node.root_ids[0].get(),
+            Some(Text(t)) => t.id.get(),
+            Some(Placeholder(e)) => e.get(),
+            Some(Fragment(nodes)) => {
+                let id = self.replace_inner(&nodes[0]);
+                self.remove_nodes(&nodes[1..]);
+                id
+            }
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.replace_inner(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
                 }
             }
-        } else {
-            for listener in old.listeners {
-                self.mutations
-                    .remove_event_listener(listener.event, Some(root.as_u64()));
-            }
-            for listener in new.listeners {
-                listener.mounted_node.set(Some(root));
-                self.mutations.new_event_listener(listener, cur_scope_id);
-            }
-        }
-
-        match (old.children.len(), new.children.len()) {
-            (0, 0) => {}
-            (0, _) => {
-                let mut created = Vec::new();
-                self.create_children(root, new.children, &mut created);
-                self.mutations.append_children(Some(root.as_u64()), created);
-            }
-            (_, _) => self.diff_children(root, old.children, new.children),
         };
-    }
 
-    fn diff_component_nodes(
-        &mut self,
-        parent: ElementId,
-        old: &'b VComponent<'b>,
-        new: &'b VComponent<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        let scope_addr = old
-            .scope
-            .get()
-            .expect("existing component nodes should have a scope");
-
-        if std::ptr::eq(old, new) {
-            return;
+        // Just remove the rest from the dom
+        for (idx, _) in node.template.roots.iter().enumerate().skip(1) {
+            self.remove_root_node(node, idx);
         }
 
-        // Make sure we're dealing with the same component (by function pointer)
-        if old.user_fc == new.user_fc {
-            self.enter_scope(scope_addr);
-            {
-                // Make sure the new component vnode is referencing the right scope id
-                new.scope.set(Some(scope_addr));
-
-                // make sure the component's caller function is up to date
-                let scope = self
-                    .scopes
-                    .get_scope(scope_addr)
-                    .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
-
-                // take the new props out regardless
-                // when memoizing, push to the existing scope if memoization happens
-                let new_props = new
-                    .props
-                    .borrow_mut()
-                    .take()
-                    .expect("new component props should exist");
-
-                let should_diff = {
-                    if old.can_memoize {
-                        // safety: we trust the implementation of "memoize"
-                        let props_are_the_same = unsafe {
-                            let new_ref = new_props.as_ref();
-                            scope.props.borrow().as_ref().unwrap().memoize(new_ref)
-                        };
-                        !props_are_the_same || self.force_diff
-                    } else {
-                        true
-                    }
-                };
-
-                if should_diff {
-                    let _old_props = scope
-                        .props
-                        .replace(unsafe { std::mem::transmute(Some(new_props)) });
+        // Garabge collect all of the nodes since this gets used in replace
+        self.clean_up_node(node);
 
-                    // this should auto drop the previous props
-                    self.scopes.run_scope(scope_addr);
-                    self.mutations.mark_dirty_scope(scope_addr);
-
-                    self.diff_node(
-                        parent,
-                        self.scopes.wip_head(scope_addr),
-                        self.scopes.fin_head(scope_addr),
-                    );
-                } else {
-                    // memoization has taken place
-                    drop(new_props);
-                };
-            }
-            self.leave_scope();
-        } else {
-            self.replace_node(parent, old_node, new_node);
-        }
-    }
-
-    fn diff_fragment_nodes(
-        &mut self,
-        parent: ElementId,
-        old: &'b VFragment<'b>,
-        new: &'b VFragment<'b>,
-    ) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
-        // This is the case where options or direct vnodes might be used.
-        // In this case, it's faster to just skip ahead to their diff
-        if old.children.len() == 1 && new.children.len() == 1 {
-            if !std::ptr::eq(old, new) {
-                self.diff_node(parent, &old.children[0], &new.children[0]);
-            }
-            return;
-        }
-
-        debug_assert!(!old.children.is_empty());
-        debug_assert!(!new.children.is_empty());
-
-        self.diff_children(parent, old.children, new.children);
+        id
     }
 
-    #[allow(clippy::too_many_lines)]
-    fn diff_template_ref_nodes(
-        &mut self,
-        parent: ElementId,
-        old: &'b VTemplateRef<'b>,
-        new: &'b VTemplateRef<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        fn diff_attributes<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
-            nodes: &Nodes,
-            ctx: (
-                &mut DiffState<'b>,
-                &'b Bump,
-                &'b VTemplateRef<'b>,
-                &Template,
-                usize,
-            ),
-        ) where
-            Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            let (diff_state, scope_bump, new, template, idx) = ctx;
-            for (node_id, attr_idx) in template.get_dynamic_nodes_for_attribute_index(idx) {
-                if let TemplateNodeType::Element(el) = &nodes.as_ref()[node_id.0].node_type {
-                    let TemplateElement { attributes, .. } = el;
-                    let attr = &attributes.as_ref()[*attr_idx];
-                    let attribute = Attribute {
-                        attribute: attr.attribute,
-                        value: new.dynamic_context.resolve_attribute(idx).clone(),
-                        is_static: false,
+    /// Clean up the node, not generating mutations
+    ///
+    /// Simply walks through the dynamic nodes
+    fn clean_up_node(&mut self, node: &'b VNode<'b>) {
+        for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
+            // Roots are cleaned up automatically?
+            if node.template.node_paths[idx].len() == 1 {
+                continue;
+            }
+
+            match dyn_node {
+                Component(comp) => {
+                    let scope = comp.scope.get().unwrap();
+                    match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                        RenderReturn::Sync(Ok(t)) => self.clean_up_node(t),
+                        _ => todo!("cannot handle nonstandard nodes"),
                     };
-                    let real_id = new.get_node_id(*node_id);
-                    diff_state
-                        .mutations
-                        .set_attribute(scope_bump.alloc(attribute), Some(real_id.as_u64()));
-                } else {
-                    panic!("expected element node");
                 }
-            }
-        }
-
-        fn set_attribute<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
-            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
-            ctx: (&mut DiffState<'b>, &'b Bump, &'b VTemplateRef<'b>, usize),
-        ) where
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            let (diff_state, scope_bump, new, template_attr_idx) = ctx;
-            if let TemplateNodeType::Element(el) = &node.node_type {
-                let TemplateElement { attributes, .. } = el;
-                let attr = &attributes.as_ref()[template_attr_idx];
-                let value = match &attr.value {
-                    TemplateAttributeValue::Dynamic(idx) => {
-                        new.dynamic_context.resolve_attribute(*idx).clone()
-                    }
-                    TemplateAttributeValue::Static(value) => value.allocate(scope_bump),
-                };
-                let attribute = Attribute {
-                    attribute: attr.attribute,
-                    value,
-                    is_static: false,
-                };
-                let real_id = new.get_node_id(node.id);
-                diff_state
-                    .mutations
-                    .set_attribute(scope_bump.alloc(attribute), Some(real_id.as_u64()));
-            } else {
-                panic!("expected element node");
-            }
+                Text(t) => self.reclaim(t.id.get()),
+                Placeholder(t) => self.reclaim(t.get()),
+                Fragment(nodes) => nodes.iter().for_each(|node| self.clean_up_node(node)),
+            };
         }
 
-        fn diff_text<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
-            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
-            ctx: (
-                &mut DiffState<'b>,
-                &'b VTemplateRef<'b>,
-                &TemplateContext<'b>,
-            ),
-        ) where
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            let (diff, new, dynamic_context) = ctx;
-            if let TemplateNodeType::Text(text) = &node.node_type {
-                let text = dynamic_context.resolve_text(text);
-                let real_id = new.get_node_id(node.id);
-                diff.mutations.set_text(
-                    diff.current_scope_bump().alloc(text),
-                    Some(real_id.as_u64()),
-                );
-            } else {
-                panic!("expected text node");
+        // we clean up nodes with dynamic attributes, provided the node is unique and not a root node
+        let mut id = None;
+        for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
+            // We'll clean up the root nodes either way, so don't worry
+            if node.template.attr_paths[idx].len() == 1 {
+                continue;
             }
-        }
-
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
-        // if the templates are different, just rebuild it
-        if old.template_id != new.template_id
-            || self
-                .scopes
-                .template_resolver
-                .borrow()
-                .is_dirty(&new.template_id)
-        {
-            self.replace_node(parent, old_node, new_node);
-            return;
-        }
-
-        if let Some(template_ref_id) = old.template_ref_id.get() {
-            self.scopes.update_template_ref(template_ref_id, new);
-            new.template_ref_id.set(Some(template_ref_id));
-        } else {
-            new.template_ref_id
-                .set(Some(self.scopes.reserve_template_ref(new)));
-        }
-        new.parent.set(Some(parent));
-        new.node_ids.replace(old.node_ids.take());
 
-        let scope_bump = &self.current_scope_bump();
+            let next_id = attr.mounted_element.get();
 
-        let template = {
-            let templates = self.scopes.templates.borrow();
-            templates.get(&new.template_id).unwrap().clone()
-        };
-        let template = template.borrow();
-
-        // diff dynamic attributes
-        for (idx, (old_attr, new_attr)) in old
-            .dynamic_context
-            .attributes
-            .iter()
-            .zip(new.dynamic_context.attributes.iter())
-            .enumerate()
-        {
-            if old_attr != new_attr {
-                template.with_nodes(
-                    diff_attributes,
-                    diff_attributes,
-                    (self, scope_bump, new, &template, idx),
-                );
+            if id == Some(next_id) {
+                continue;
             }
-        }
 
-        // set all volatile attributes
-        for (id, idx) in template.volatile_attributes() {
-            template.with_node(
-                id,
-                set_attribute,
-                set_attribute,
-                (self, scope_bump, new, idx),
-            );
-        }
+            id = Some(next_id);
 
-        // diff dynmaic nodes
-        for (old_node, new_node) in old
-            .dynamic_context
-            .nodes
-            .iter()
-            .zip(new.dynamic_context.nodes.iter())
-        {
-            self.diff_node(parent, old_node, new_node);
+            self.reclaim(next_id);
         }
+    }
 
-        // diff dynamic text
-        // text nodes could rely on multiple dynamic text parts, so we keep a record of which ones to rerender and send the diffs at the end
-        let mut dirty_text_nodes = FxHashSet::default();
-        for (idx, (old_text, new_text)) in old
-            .dynamic_context
-            .text_segments
-            .iter()
-            .zip(new.dynamic_context.text_segments.iter())
-            .enumerate()
-        {
-            if old_text != new_text {
-                for node_id in template.get_dynamic_nodes_for_text_index(idx) {
-                    dirty_text_nodes.insert(*node_id);
-                }
+    fn remove_root_node(&mut self, node: &'b VNode<'b>, idx: usize) {
+        match node.dynamic_root(idx) {
+            Some(Text(i)) => {
+                let id = i.id.get();
+                self.mutations.push(Mutation::Remove { id });
+                self.reclaim(id);
             }
-        }
-        for node_id in dirty_text_nodes {
-            template.with_node(
-                node_id,
-                diff_text,
-                diff_text,
-                (self, new, &new.dynamic_context),
-            );
-        }
+            Some(Placeholder(e)) => {
+                let id = e.get();
+                self.mutations.push(Mutation::Remove { id });
+                self.reclaim(id);
+            }
+            Some(Fragment(nodes)) => self.remove_nodes(nodes),
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.remove_node(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
+                };
+            }
+            None => {
+                let id = node.root_ids[idx].get();
+                self.mutations.push(Mutation::Remove { id });
+                self.reclaim(id);
+            }
+        };
     }
 
-    // Diff the given set of old and new children.
-    //
-    // The parent must be on top of the change list stack when this function is
-    // entered:
-    //
-    //     [... 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.
-    //
-    // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
-    // to an element, and appending makes sense.
-    fn diff_children(&mut self, parent: ElementId, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
-        // Remember, fragments can never be empty (they always have a single child)
-        match (old, new) {
-            ([], []) => {}
-            ([], _) => self.create_and_append_children(parent, new),
-            (_, []) => self.remove_nodes(old, true),
-            _ => {
-                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"
-                );
+    fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        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(parent, old, new);
-                } else {
-                    self.diff_non_keyed_children(parent, old, new);
-                }
-            }
+        if new_is_keyed && old_is_keyed {
+            self.diff_keyed_children(old, new);
+        } else {
+            self.diff_non_keyed_children(old, new);
         }
     }
 
@@ -865,12 +396,7 @@ impl<'b> DiffState<'b> {
     //     [... parent]
     //
     // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(
-        &mut self,
-        parent: ElementId,
-        old: &'b [VNode<'b>],
-        new: &'b [VNode<'b>],
-    ) {
+    fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         use std::cmp::Ordering;
 
         // Handled these cases in `diff_children` before calling this function.
@@ -878,15 +404,13 @@ impl<'b> DiffState<'b> {
         debug_assert!(!old.is_empty());
 
         match old.len().cmp(&new.len()) {
-            Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
-            Ordering::Less => {
-                self.create_and_insert_after(parent, &new[old.len()..], old.last().unwrap());
-            }
+            Ordering::Greater => self.remove_nodes(&old[new.len()..]),
+            Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
             Ordering::Equal => {}
         }
 
         for (new, old) in new.iter().zip(old.iter()) {
-            self.diff_node(parent, old, new);
+            self.diff_node(old, new);
         }
     }
 
@@ -906,18 +430,13 @@ impl<'b> DiffState<'b> {
     // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
     //
     // The stack is empty upon entry.
-    fn diff_keyed_children(
-        &mut self,
-        parent: ElementId,
-        old: &'b [VNode<'b>],
-        new: &'b [VNode<'b>],
-    ) {
+    fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         if cfg!(debug_assertions) {
-            let mut keys = rustc_hash::FxHashSet::default();
+            let mut keys = fxhash::FxHashSet::default();
             let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
                 keys.clear();
                 for child in children {
-                    let key = child.key();
+                    let key = child.key;
                     debug_assert!(
                         key.is_some(),
                         "if any sibling is keyed, all siblings must be keyed"
@@ -939,7 +458,7 @@ impl<'b> DiffState<'b> {
         //
         // `shared_prefix_count` is the count of how many nodes at the start of
         // `new` and `old` share the same keys.
-        let (left_offset, right_offset) = match self.diff_keyed_ends(parent, old, new) {
+        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
             Some(count) => count,
             None => return,
         };
@@ -958,25 +477,25 @@ impl<'b> DiffState<'b> {
 
         if new_middle.is_empty() {
             // remove the old elements
-            self.remove_nodes(old_middle, true);
+            self.remove_nodes(old_middle);
         } else if old_middle.is_empty() {
             // there were no old elements, so just create the new elements
             // we need to find the right "foothold" though - we shouldn't use the "append" at all
             if left_offset == 0 {
                 // insert at the beginning of the old list
                 let foothold = &old[old.len() - right_offset];
-                self.create_and_insert_before(parent, new_middle, foothold);
+                self.create_and_insert_before(new_middle, foothold);
             } else if right_offset == 0 {
                 // insert at the end  the old list
                 let foothold = old.last().unwrap();
-                self.create_and_insert_after(parent, new_middle, foothold);
+                self.create_and_insert_after(new_middle, foothold);
             } else {
                 // inserting in the middle
                 let foothold = &old[left_offset - 1];
-                self.create_and_insert_after(parent, new_middle, foothold);
+                self.create_and_insert_after(new_middle, foothold);
             }
         } else {
-            self.diff_keyed_middle(parent, old_middle, new_middle);
+            self.diff_keyed_middle(old_middle, new_middle);
         }
     }
 
@@ -987,7 +506,6 @@ impl<'b> DiffState<'b> {
     /// If there is no offset, then this function returns None and the diffing is complete.
     fn diff_keyed_ends(
         &mut self,
-        parent: ElementId,
         old: &'b [VNode<'b>],
         new: &'b [VNode<'b>],
     ) -> Option<(usize, usize)> {
@@ -995,24 +513,24 @@ impl<'b> DiffState<'b> {
 
         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() {
+            if old.key != new.key {
                 break;
             }
-            self.diff_node(parent, old, new);
+            self.diff_node(old, new);
             left_offset += 1;
         }
 
         // If that was all of the old children, then create and append the remaining
         // new children and we're finished.
         if left_offset == old.len() {
-            self.create_and_insert_after(parent, &new[left_offset..], old.last().unwrap());
+            self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
             return None;
         }
 
         // And if that was all of the new children, then remove all of the remaining
         // old children and we're finished.
         if left_offset == new.len() {
-            self.remove_nodes(&old[left_offset..], true);
+            self.remove_nodes(&old[left_offset..]);
             return None;
         }
 
@@ -1020,10 +538,10 @@ impl<'b> DiffState<'b> {
         let mut right_offset = 0;
         for (old, new) in old.iter().rev().zip(new.iter().rev()) {
             // abort early if we finally run into nodes with different keys
-            if old.key() != new.key() {
+            if old.key != new.key {
                 break;
             }
-            self.diff_node(parent, old, new);
+            self.diff_node(old, new);
             right_offset += 1;
         }
 
@@ -1044,7 +562,7 @@ impl<'b> DiffState<'b> {
     //
     // Upon exit from this function, it will be restored to that same self.
     #[allow(clippy::too_many_lines)]
-    fn diff_keyed_middle(&mut self, parent: ElementId, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+    fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         /*
         1. Map the old keys into a numerical ordering based on indices.
         2. Create a map of old key to its index
@@ -1066,11 +584,10 @@ impl<'b> DiffState<'b> {
         8. Finally, generate instructions to remove any old children.
         9. Generate instructions to finally diff children that are the same between both
         */
-
         // 0. Debug sanity checks
         // Should have already diffed the shared-key prefixes and suffixes.
-        debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
-        debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
+        debug_assert_ne!(new.first().map(|i| i.key), old.first().map(|i| i.key));
+        debug_assert_ne!(new.last().map(|i| i.key), old.last().map(|i| i.key));
 
         // 1. Map the old keys into a numerical ordering based on indices.
         // 2. Create a map of old key to its index
@@ -1078,7 +595,7 @@ impl<'b> DiffState<'b> {
         let old_key_to_old_index = old
             .iter()
             .enumerate()
-            .map(|(i, o)| (o.key().unwrap(), i))
+            .map(|(i, o)| (o.key.unwrap(), i))
             .collect::<FxHashMap<_, _>>();
 
         let mut shared_keys = FxHashSet::default();
@@ -1087,7 +604,7 @@ impl<'b> DiffState<'b> {
         let new_index_to_old_index = new
             .iter()
             .map(|node| {
-                let key = node.key().unwrap();
+                let key = node.key.unwrap();
                 if let Some(&index) = old_key_to_old_index.get(&key) {
                     shared_keys.insert(key);
                     index
@@ -1100,15 +617,15 @@ impl<'b> DiffState<'b> {
         // If none of the old keys are reused by the new children, then we remove all the remaining old children and
         // create the new children afresh.
         if shared_keys.is_empty() {
-            if let Some(first_old) = old.get(0) {
-                self.remove_nodes(&old[1..], true);
-                let mut nodes_created = Vec::new();
-                self.create_children(parent, new, &mut nodes_created);
-                self.replace_inner(first_old, nodes_created);
+            if old.get(0).is_some() {
+                self.remove_nodes(&old[1..]);
+                self.replace_many(&old[0], new);
             } else {
                 // I think this is wrong - why are we appending?
                 // only valid of the if there are no trailing elements
-                self.create_and_append_children(parent, new);
+                // self.create_and_append_children(new);
+
+                todo!("we should never be appending - just creating N");
             }
             return;
         }
@@ -1116,9 +633,9 @@ impl<'b> DiffState<'b> {
         // remove any old children that are not shared
         // todo: make this an iterator
         for child in old {
-            let key = child.key().unwrap();
+            let key = child.key.unwrap();
             if !shared_keys.contains(&key) {
-                self.remove_nodes([child], true);
+                self.remove_node(child);
             }
         }
 
@@ -1146,10 +663,10 @@ impl<'b> DiffState<'b> {
         }
 
         for idx in &lis_sequence {
-            self.diff_node(parent, &old[new_index_to_old_index[*idx]], &new[*idx]);
+            self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
         }
 
-        let mut nodes_created = Vec::new();
+        let mut nodes_created = 0;
 
         // add mount instruction for the first items not covered by the lis
         let last = *lis_sequence.last().unwrap();
@@ -1158,16 +675,19 @@ impl<'b> DiffState<'b> {
                 let new_idx = idx + last + 1;
                 let old_index = new_index_to_old_index[new_idx];
                 if old_index == u32::MAX as usize {
-                    self.create_node(parent, new_node, &mut nodes_created);
+                    nodes_created += self.create(new_node);
                 } else {
-                    self.diff_node(parent, &old[old_index], new_node);
-                    self.get_all_real_nodes(new_node, &mut nodes_created);
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
                 }
             }
 
-            let last = Some(self.find_last_element(&new[last]).unwrap().as_u64());
-            self.mutations.insert_after(last, nodes_created);
-            nodes_created = Vec::new();
+            let id = self.find_last_element(&new[last]);
+            self.mutations.push(Mutation::InsertAfter {
+                id,
+                m: nodes_created,
+            });
+            nodes_created = 0;
         }
 
         // for each spacing, generate a mount instruction
@@ -1179,17 +699,20 @@ impl<'b> DiffState<'b> {
                     let new_idx = idx + next + 1;
                     let old_index = new_index_to_old_index[new_idx];
                     if old_index == u32::MAX as usize {
-                        self.create_node(parent, new_node, &mut nodes_created);
+                        nodes_created += self.create(new_node);
                     } else {
-                        self.diff_node(parent, &old[old_index], new_node);
-                        self.get_all_real_nodes(new_node, &mut nodes_created);
+                        self.diff_node(&old[old_index], new_node);
+                        nodes_created += self.push_all_real_nodes(new_node);
                     }
                 }
 
-                let first = Some(self.find_first_element(&new[last]).unwrap().as_u64());
-                self.mutations.insert_before(first, nodes_created);
+                let id = self.find_first_element(&new[last]);
+                self.mutations.push(Mutation::InsertBefore {
+                    id,
+                    m: nodes_created,
+                });
 
-                nodes_created = Vec::new();
+                nodes_created = 0;
             }
             last = *next;
         }
@@ -1200,327 +723,227 @@ impl<'b> DiffState<'b> {
             for (idx, new_node) in new[..first_lis].iter().enumerate() {
                 let old_index = new_index_to_old_index[idx];
                 if old_index == u32::MAX as usize {
-                    self.create_node(parent, new_node, &mut nodes_created);
+                    nodes_created += self.create(new_node);
                 } else {
-                    self.diff_node(parent, &old[old_index], new_node);
-                    self.get_all_real_nodes(new_node, &mut nodes_created);
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
                 }
             }
 
-            let first = Some(self.find_first_element(&new[first_lis]).unwrap().as_u64());
-            self.mutations.insert_before(first, nodes_created);
+            let id = self.find_first_element(&new[first_lis]);
+            self.mutations.push(Mutation::InsertBefore {
+                id,
+                m: nodes_created,
+            });
         }
     }
 
-    pub fn replace_node(&mut self, parent: ElementId, old: &'b VNode<'b>, new: &'b VNode<'b>) {
-        let mut nodes_vec = Vec::new();
-        self.create_node(parent, new, &mut nodes_vec);
-        self.replace_inner(old, nodes_vec);
+    /// Remove these nodes from the dom
+    /// Wont generate mutations for the inner nodes
+    fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) {
+        // note that we iterate in reverse to unlink lists of nodes in their rough index order
+        nodes.iter().rev().for_each(|node| self.remove_node(node));
     }
 
-    fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: Vec<u64>) {
-        match old {
-            VNode::Element(el) => {
-                let id = old
-                    .try_mounted_id()
-                    .unwrap_or_else(|| panic!("broke on {:?}", old));
-
-                self.mutations
-                    .replace_with(Some(id.as_u64()), nodes_created);
-                self.remove_nodes(el.children, false);
-                self.scopes.collect_garbage(id);
-            }
-
-            VNode::Text(_) | VNode::Placeholder(_) => {
-                let id = old
-                    .try_mounted_id()
-                    .unwrap_or_else(|| panic!("broke on {:?}", old));
-
-                self.mutations
-                    .replace_with(Some(id.as_u64()), nodes_created);
-                self.scopes.collect_garbage(id);
-            }
-
-            VNode::Fragment(f) => {
-                self.replace_inner(&f.children[0], nodes_created);
-                self.remove_nodes(f.children.iter().skip(1), true);
-            }
-
-            VNode::Component(c) => {
-                log::trace!("Replacing component {:?}", old);
-                let scope_id = c.scope.get().unwrap();
-                let node = self.scopes.fin_head(scope_id);
-
-                self.enter_scope(scope_id);
-                {
-                    self.replace_inner(node, nodes_created);
-
-                    log::trace!("Replacing component x2 {:?}", old);
-
-                    let scope = self.scopes.get_scope(scope_id).unwrap();
-                    c.scope.set(None);
-                    let props = scope.props.take().unwrap();
-                    c.props.borrow_mut().replace(props);
-                    self.scopes.try_remove(scope_id);
-                }
-                self.leave_scope();
-            }
-
-            VNode::TemplateRef(template_ref) => {
-                let templates = self.scopes.templates.borrow();
-                let template = templates.get(&template_ref.template_id).unwrap().borrow();
-                let mut root_iter = template.root_nodes().iter();
-                let first_real_id = template_ref.get_node_id(*root_iter.next().unwrap());
-                self.mutations
-                    .replace_with(Some(first_real_id.as_u64()), nodes_created);
-                for id in root_iter {
-                    let real_id = template_ref.get_node_id(*id);
-                    self.mutations.remove(Some(real_id.as_u64()));
-                }
+    fn remove_node(&mut self, node: &'b VNode<'b>) {
+        for (idx, _) in node.template.roots.iter().enumerate() {
+            let id = match node.dynamic_root(idx) {
+                Some(Text(t)) => t.id.get(),
+                Some(Placeholder(t)) => t.get(),
+                Some(Fragment(t)) => return self.remove_nodes(t),
+                Some(Component(comp)) => return self.remove_component(comp.scope.get().unwrap()),
+                None => node.root_ids[idx].get(),
+            };
 
-                self.remove_nodes(template_ref.dynamic_context.nodes, true);
+            self.mutations.push(Mutation::Remove { id })
+        }
 
-                if let Some(id) = template_ref.template_ref_id.get() {
-                    self.scopes.template_refs.borrow_mut().remove(id.0);
-                }
+        self.clean_up_node(node);
 
-                for id in template_ref.node_ids.borrow().iter() {
-                    if let Some(id) = id.get() {
-                        self.scopes.collect_garbage(*id);
-                    }
-                }
+        for root in node.root_ids {
+            let id = root.get();
+            if id.0 != 0 {
+                self.reclaim(id);
             }
         }
     }
 
-    pub fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>, gen_muts: bool) {
-        for node in nodes {
-            match node {
-                VNode::Text(t) => {
-                    // this check exists because our null node will be removed but does not have an ID
-                    if let Some(id) = t.id.get() {
-                        self.scopes.collect_garbage(id);
-                        t.id.set(None);
-
-                        if gen_muts {
-                            self.mutations.remove(Some(id.as_u64()));
-                        }
-                    }
-                }
-                VNode::Placeholder(a) => {
-                    let id = a.id.get().unwrap();
-                    self.scopes.collect_garbage(id);
-                    a.id.set(None);
-
-                    if gen_muts {
-                        self.mutations.remove(Some(id.as_u64()));
-                    }
-                }
-                VNode::Element(e) => {
-                    let id = e.id.get().unwrap();
+    fn remove_component(&mut self, scope_id: ScopeId) {
+        let height = self.scopes[scope_id.0].height;
+        self.dirty_scopes.remove(&DirtyScope {
+            height,
+            id: scope_id,
+        });
 
-                    if gen_muts {
-                        self.mutations.remove(Some(id.as_u64()));
-                    }
+        // I promise, since we're descending down the tree, this is safe
+        match unsafe { self.scopes[scope_id.0].root_node().extend_lifetime_ref() } {
+            RenderReturn::Sync(Ok(t)) => self.remove_node(t),
+            _ => todo!("cannot handle nonstandard nodes"),
+        }
+    }
 
-                    self.scopes.collect_garbage(id);
-                    e.id.set(None);
+    /// Push all the real nodes on the stack
+    fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+        let mut onstack = 0;
 
-                    self.remove_nodes(e.children, false);
+        for (idx, _) in node.template.roots.iter().enumerate() {
+            match node.dynamic_root(idx) {
+                Some(Text(t)) => {
+                    self.mutations.push(Mutation::PushRoot { id: t.id.get() });
+                    onstack += 1;
                 }
-
-                VNode::Fragment(f) => {
-                    self.remove_nodes(f.children, gen_muts);
+                Some(Placeholder(t)) => {
+                    self.mutations.push(Mutation::PushRoot { id: t.get() });
+                    onstack += 1;
                 }
-
-                VNode::Component(c) => {
-                    self.enter_scope(c.scope.get().unwrap());
-                    {
-                        let scope_id = c.scope.get().unwrap();
-                        let root = self.scopes.root_node(scope_id);
-                        self.remove_nodes([root], gen_muts);
-
-                        let scope = self.scopes.get_scope(scope_id).unwrap();
-                        c.scope.set(None);
-
-                        let props = scope.props.take().unwrap();
-                        c.props.borrow_mut().replace(props);
-                        self.scopes.try_remove(scope_id);
+                Some(Fragment(nodes)) => {
+                    for node in *nodes {
+                        onstack += self.push_all_real_nodes(node);
                     }
-                    self.leave_scope();
                 }
-
-                VNode::TemplateRef(template_ref) => {
-                    let templates = self.scopes.templates.borrow();
-                    let template = templates.get(&template_ref.template_id).unwrap().borrow();
-                    if gen_muts {
-                        for id in template.root_nodes() {
-                            let real_id = template_ref.get_node_id(*id);
-                            self.mutations.remove(Some(real_id.as_u64()));
-                        }
-                    }
-
-                    self.remove_nodes(template_ref.dynamic_context.nodes, gen_muts);
-
-                    if let Some(id) = template_ref.template_ref_id.get() {
-                        self.scopes.template_refs.borrow_mut().remove(id.0);
-                    }
-
-                    for id in template_ref.node_ids.borrow().iter() {
-                        if let Some(id) = id.get() {
-                            self.scopes.collect_garbage(*id);
+                Some(Component(comp)) => {
+                    let scope = comp.scope.get().unwrap();
+                    onstack +=
+                        match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                            RenderReturn::Sync(Ok(node)) => self.push_all_real_nodes(node),
+                            _ => todo!(),
                         }
-                    }
                 }
-            }
+                None => {
+                    self.mutations.push(Mutation::PushRoot {
+                        id: node.root_ids[idx].get(),
+                    });
+                    onstack += 1;
+                }
+            };
         }
-    }
 
-    fn create_children(
-        &mut self,
-        parent: ElementId,
-        nodes: &'b [VNode<'b>],
-        nodes_vec: &mut Vec<u64>,
-    ) {
-        nodes_vec.reserve(nodes.len());
-        for node in nodes {
-            self.create_node(parent, node, nodes_vec);
-        }
+        onstack
     }
 
-    fn create_and_append_children(&mut self, parent: ElementId, nodes: &'b [VNode<'b>]) {
-        let mut nodes_vec = Vec::with_capacity(nodes.len());
-        self.create_children(parent, nodes, &mut nodes_vec);
-        self.mutations
-            .append_children(Some(parent.as_u64()), nodes_vec);
+    fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+        nodes.iter().fold(0, |acc, child| acc + self.create(child))
     }
 
-    fn create_and_insert_after(
-        &mut self,
-        parent: ElementId,
-        nodes: &'b [VNode<'b>],
-        after: &'b VNode<'b>,
-    ) {
-        let mut nodes_vec = Vec::with_capacity(nodes.len());
-        self.create_children(parent, nodes, &mut nodes_vec);
-        let last = self.find_last_element(after).unwrap();
-        self.mutations.insert_after(Some(last.as_u64()), nodes_vec);
+    fn create_and_insert_before(&mut self, new: &'b [VNode<'b>], before: &'b VNode<'b>) {
+        let m = self.create_children(new);
+        let id = self.find_first_element(before);
+        self.mutations.push(Mutation::InsertBefore { id, m })
     }
 
-    fn create_and_insert_before(
-        &mut self,
-        parent: ElementId,
-        nodes: &'b [VNode<'b>],
-        before: &'b VNode<'b>,
-    ) {
-        let mut nodes_vec = Vec::with_capacity(nodes.len());
-        self.create_children(parent, nodes, &mut nodes_vec);
-        let first = self.find_first_element(before).unwrap();
-        self.mutations
-            .insert_before(Some(first.as_u64()), nodes_vec);
-    }
-
-    pub fn current_scope(&self) -> ScopeId {
-        self.scope_stack.last().copied().expect("no current scope")
-    }
-
-    fn enter_scope(&mut self, scope: ScopeId) {
-        self.scope_stack.push(scope);
-    }
-
-    fn leave_scope(&mut self) {
-        self.scope_stack.pop();
+    fn create_and_insert_after(&mut self, new: &'b [VNode<'b>], after: &'b VNode<'b>) {
+        let m = self.create_children(new);
+        let id = self.find_last_element(after);
+        self.mutations.push(Mutation::InsertAfter { id, m })
     }
 
-    fn find_last_element(&mut self, vnode: &'b VNode<'b>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-        loop {
-            match &search_node.take().unwrap() {
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Placeholder(t) => break t.id.get(),
-                VNode::Fragment(frag) => search_node = frag.children.last(),
-                VNode::Component(el) => {
-                    let scope_id = el.scope.get().unwrap();
-                    search_node = Some(self.scopes.root_node(scope_id));
-                }
-                VNode::TemplateRef(t) => {
-                    let templates = self.scopes.templates.borrow();
-                    let template = templates.get(&t.template_id).unwrap();
-                    let template = template.borrow();
-                    break template.root_nodes().last().map(|id| t.get_node_id(*id));
+    fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
+        match node.dynamic_root(0) {
+            None => node.root_ids[0].get(),
+            Some(Text(t)) => t.id.get(),
+            Some(Fragment(t)) => self.find_first_element(&t[0]),
+            Some(Placeholder(t)) => t.get(),
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.find_first_element(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
                 }
             }
         }
     }
 
-    fn find_first_element(&mut self, vnode: &'b VNode<'b>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-        loop {
-            match &search_node.take().expect("search node to have an ID") {
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Placeholder(t) => break t.id.get(),
-                VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
-                VNode::Component(el) => {
-                    let scope = el.scope.get().expect("element to have a scope assigned");
-                    search_node = Some(self.scopes.root_node(scope));
-                }
-                VNode::TemplateRef(t) => {
-                    let templates = self.scopes.templates.borrow();
-                    let template = templates.get(&t.template_id).unwrap();
-                    let template = template.borrow();
-                    break template.root_nodes().first().map(|id| t.get_node_id(*id));
+    fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
+        match node.dynamic_root(node.template.roots.len() - 1) {
+            None => node.root_ids.last().unwrap().get(),
+            Some(Text(t)) => t.id.get(),
+            Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
+            Some(Placeholder(t)) => t.get(),
+            Some(Component(comp)) => {
+                let scope = comp.scope.get().unwrap();
+                match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+                    RenderReturn::Sync(Ok(t)) => self.find_last_element(t),
+                    _ => todo!("cannot handle nonstandard nodes"),
                 }
             }
         }
     }
 
-    // recursively push all the nodes of a tree onto the stack and return how many are there
-    fn get_all_real_nodes(&mut self, node: &'b VNode<'b>, nodes: &mut Vec<u64>) {
-        match node {
-            VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => {
-                nodes.push(node.mounted_id().0 as u64);
-            }
+    fn replace(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
+        let first = self.find_first_element(left);
+        let id = self.replace_inner(left);
+        let created = self.create(right);
+        self.mutations.push(Mutation::ReplaceWith {
+            id: first,
+            m: created,
+        });
+        self.try_reclaim(id);
+    }
 
-            VNode::TemplateRef(template_ref) => {
-                let templates = self.scopes.templates.borrow();
-                let template = templates.get(&template_ref.template_id).unwrap();
-                let template = template.borrow();
-                nodes.extend(
-                    template
-                        .root_nodes()
-                        .iter()
-                        .map(|id| template_ref.get_node_id(*id).as_u64()),
-                );
-            }
+    fn replace_many(&mut self, left: &'b VNode<'b>, right: &'b [VNode<'b>]) {
+        let first = self.find_first_element(left);
+        let id = self.replace_inner(left);
+        let created = self.create_children(right);
+        self.mutations.push(Mutation::ReplaceWith {
+            id: first,
+            m: created,
+        });
+        self.try_reclaim(id);
+    }
+}
 
-            VNode::Fragment(frag) => {
-                nodes.reserve(frag.children.len());
-                for child in frag.children {
-                    self.get_all_real_nodes(child, nodes);
-                }
-            }
+fn matching_components<'a>(
+    left: &'a VNode<'a>,
+    right: &'a VNode<'a>,
+) -> Option<Vec<(&'a VComponent<'a>, &'a VComponent<'a>)>> {
+    if left.template.roots.len() != right.template.roots.len() {
+        return None;
+    }
+
+    // run through the components, ensuring they're the same
+    left.template
+        .roots
+        .iter()
+        .zip(right.template.roots.iter())
+        .map(|(l, r)| {
+            let (l, r) = match (l, r) {
+                (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
+                _ => return None,
+            };
 
-            VNode::Component(c) => {
-                let scope_id = c.scope.get().unwrap();
-                let root = self.scopes.root_node(scope_id);
-                self.get_all_real_nodes(root, nodes);
-            }
-        }
-    }
+            let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
+                (Component(l), Component(r)) => (l, r),
+                _ => return None,
+            };
 
-    pub(crate) fn current_scope_bump(&self) -> &'b Bump {
-        &self
-            .scopes
-            .get_scope(self.current_scope())
-            .unwrap()
-            .fin_frame()
-            .bump
+            Some((l, r))
+        })
+        .collect()
+}
+
+/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
+///
+/// IE
+///  - for text - we can use SetTextContent
+///  - for clearning children we can use RemoveChildren
+///  - for appending children we can use AppendChildren
+#[allow(dead_code)]
+fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
+    let path = node.template.node_paths[idx];
+
+    // use a loop to index every static node's children until the path has run out
+    // only break if the last path index is a dynamic node
+    let mut static_node = &node.template.roots[path[0] as usize];
+
+    for i in 1..path.len() - 1 {
+        match static_node {
+            TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize],
+            _ => return false,
+        }
     }
 
-    pub fn register_template(&mut self, template: &Template, id: ElementId) {
-        let bump = &self.scopes.template_bump;
-        template.create(&mut self.mutations, bump, id);
+    match static_node {
+        TemplateNode::Element { children, .. } => children.len() == 1,
+        _ => false,
     }
 }

+ 19 - 0
packages/core/src/dirty_scope.rs

@@ -0,0 +1,19 @@
+use crate::ScopeId;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DirtyScope {
+    pub height: u32,
+    pub id: ScopeId,
+}
+
+impl PartialOrd for DirtyScope {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.height.cmp(&other.height))
+    }
+}
+
+impl Ord for DirtyScope {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.height.cmp(&other.height)
+    }
+}

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

@@ -1,190 +0,0 @@
-use std::{fmt::Write, marker::PhantomData, ops::Deref};
-
-use once_cell::sync::Lazy;
-
-use crate::{
-    template::{TemplateNodeId, TextTemplateSegment},
-    AttributeValue, Listener, TextTemplate, VNode,
-};
-
-/// A lazily initailized vector
-#[derive(Debug, Clone, Copy)]
-pub struct LazyStaticVec<T: 'static>(pub &'static Lazy<Vec<T>>);
-
-impl<T: 'static> AsRef<[T]> for LazyStaticVec<T> {
-    fn as_ref(&self) -> &[T] {
-        let v: &Vec<_> = self.0.deref();
-        v.as_ref()
-    }
-}
-
-impl<T> PartialEq for LazyStaticVec<T> {
-    fn eq(&self, other: &Self) -> bool {
-        std::ptr::eq(self.0, other.0)
-    }
-}
-
-/// Stores what nodes depend on specific dynamic parts of the template to allow the diffing algorithm to jump to that part of the template instead of travering it
-/// This makes adding constant template nodes add no additional cost to diffing.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-pub struct DynamicNodeMapping<
-    Nodes,
-    TextOuter,
-    TextInner,
-    AttributesOuter,
-    AttributesInner,
-    Volatile,
-    Listeners,
-> where
-    Nodes: AsRef<[Option<TemplateNodeId>]>,
-    TextOuter: AsRef<[TextInner]>,
-    TextInner: AsRef<[TemplateNodeId]>,
-    AttributesOuter: AsRef<[AttributesInner]>,
-    AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
-    Volatile: AsRef<[(TemplateNodeId, usize)]>,
-    Listeners: AsRef<[TemplateNodeId]>,
-{
-    /// The node that depend on each node in the dynamic template
-    pub nodes: Nodes,
-    text_inner: PhantomData<TextInner>,
-    /// The text nodes that depend on each text segment of the dynamic template
-    pub text: TextOuter,
-    /// The attributes along with the attribute index in the template that depend on each attribute of the dynamic template
-    pub attributes: AttributesOuter,
-    attributes_inner: PhantomData<AttributesInner>,
-    /// The attributes that are marked as volatile in the template
-    pub volatile_attributes: Volatile,
-    /// The listeners that depend on each listener of the dynamic template
-    pub nodes_with_listeners: Listeners,
-}
-
-impl<Nodes, TextOuter, TextInner, AttributesOuter, AttributesInner, Volatile, Listeners>
-    DynamicNodeMapping<
-        Nodes,
-        TextOuter,
-        TextInner,
-        AttributesOuter,
-        AttributesInner,
-        Volatile,
-        Listeners,
-    >
-where
-    Nodes: AsRef<[Option<TemplateNodeId>]>,
-    TextOuter: AsRef<[TextInner]>,
-    TextInner: AsRef<[TemplateNodeId]>,
-    AttributesOuter: AsRef<[AttributesInner]>,
-    AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
-    Volatile: AsRef<[(TemplateNodeId, usize)]>,
-    Listeners: AsRef<[TemplateNodeId]>,
-{
-    /// Creates a new dynamic node mapping
-    pub const fn new(
-        nodes: Nodes,
-        text: TextOuter,
-        attributes: AttributesOuter,
-        volatile_attributes: Volatile,
-        listeners: Listeners,
-    ) -> Self {
-        DynamicNodeMapping {
-            nodes,
-            text_inner: PhantomData,
-            text,
-            attributes,
-            attributes_inner: PhantomData,
-            volatile_attributes,
-            nodes_with_listeners: listeners,
-        }
-    }
-}
-
-/// A dynamic node mapping that is stack allocated
-pub type StaticDynamicNodeMapping = DynamicNodeMapping<
-    &'static [Option<TemplateNodeId>],
-    &'static [&'static [TemplateNodeId]],
-    &'static [TemplateNodeId],
-    &'static [&'static [(TemplateNodeId, usize)]],
-    &'static [(TemplateNodeId, usize)],
-    // volatile attribute information is available at compile time, but there is no way for the macro to generate it, so we initialize it lazily instead
-    LazyStaticVec<(TemplateNodeId, usize)>,
-    &'static [TemplateNodeId],
->;
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-/// A dynamic node mapping that is heap allocated
-pub type OwnedDynamicNodeMapping = DynamicNodeMapping<
-    Vec<Option<TemplateNodeId>>,
-    Vec<Vec<TemplateNodeId>>,
-    Vec<TemplateNodeId>,
-    Vec<Vec<(TemplateNodeId, usize)>>,
-    Vec<(TemplateNodeId, usize)>,
-    Vec<(TemplateNodeId, usize)>,
-    Vec<TemplateNodeId>,
->;
-
-/// The dynamic parts used to saturate a template durring runtime
-pub struct TemplateContext<'b> {
-    /// The dynamic nodes
-    pub nodes: &'b [VNode<'b>],
-    /// The dynamic text
-    pub text_segments: &'b [&'b str],
-    /// The dynamic attributes
-    pub attributes: &'b [AttributeValue<'b>],
-    /// The dynamic attributes
-    // The listeners must not change during the lifetime of the context, use a dynamic node if the listeners change
-    pub listeners: &'b [Listener<'b>],
-    /// A optional key for diffing
-    pub key: Option<&'b str>,
-}
-
-impl<'b> TemplateContext<'b> {
-    /// Resolve text segments to a string
-    pub fn resolve_text<TextSegments, Text>(
-        &self,
-        text: &TextTemplate<TextSegments, Text>,
-    ) -> String
-    where
-        TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-        Text: AsRef<str>,
-    {
-        let mut result = String::with_capacity(text.min_size);
-        self.resolve_text_into(text, &mut result);
-        result
-    }
-
-    /// Resolve text and writes the result
-    pub fn resolve_text_into<TextSegments, Text>(
-        &self,
-        text: &TextTemplate<TextSegments, Text>,
-        result: &mut impl Write,
-    ) where
-        TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-        Text: AsRef<str>,
-    {
-        for seg in text.segments.as_ref() {
-            match seg {
-                TextTemplateSegment::Static(s) => {
-                    let _ = result.write_str(s.as_ref());
-                }
-                TextTemplateSegment::Dynamic(idx) => {
-                    let _ = result.write_str(self.text_segments[*idx]);
-                }
-            }
-        }
-    }
-
-    /// Resolve an attribute value
-    pub fn resolve_attribute(&self, idx: usize) -> &'b AttributeValue<'b> {
-        &self.attributes[idx]
-    }
-
-    /// Resolve a listener
-    pub fn resolve_listener(&self, idx: usize) -> &'b Listener<'b> {
-        &self.listeners[idx]
-    }
-
-    /// Resolve a node
-    pub fn resolve_node(&self, idx: usize) -> &'b VNode<'b> {
-        &self.nodes[idx]
-    }
-}

+ 19 - 0
packages/core/src/error_boundary.rs

@@ -0,0 +1,19 @@
+use std::cell::RefCell;
+
+use crate::ScopeId;
+
+/// A boundary that will capture any errors from child components
+#[allow(dead_code)]
+pub struct ErrorBoundary {
+    error: RefCell<Option<(anyhow::Error, ScopeId)>>,
+    id: ScopeId,
+}
+
+impl ErrorBoundary {
+    pub fn new(id: ScopeId) -> Self {
+        Self {
+            error: RefCell::new(None),
+            id,
+        }
+    }
+}

+ 126 - 144
packages/core/src/events.rs

@@ -1,182 +1,164 @@
-//! Internal and external event system
-//!
-//!
-//! This is all kinda WIP, but the bones are there.
+use std::{
+    cell::{Cell, RefCell},
+    rc::Rc,
+};
 
-use crate::{ElementId, ScopeId};
-use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc};
-
-pub(crate) struct BubbleState {
-    pub canceled: Cell<bool>,
-}
-
-impl BubbleState {
-    pub fn new() -> Self {
-        Self {
-            canceled: Cell::new(false),
-        }
-    }
-}
-
-/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
-///
-/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
-/// where each listener is checked and fired if the event name matches.
+/// A wrapper around some generic data that handles the event's state
 ///
-/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
-/// attempting to downcast the event data.
 ///
-/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
-/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
+/// Prevent this event from continuing to bubble up the tree to parent elements.
 ///
 /// # Example
+///
 /// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     render!(div {
-///         onclick: move |_| println!("Clicked!")
-///     })
-/// }
+/// rsx! {
+///     button {
+///         onclick: move |evt: Event<MouseData>| {
+///             evt.cancel_bubble();
 ///
-/// let mut dom = VirtualDom::new(App);
-/// let mut scheduler = dom.get_scheduler_channel();
-/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
-///     UserEvent {
-///         scope_id: None,
-///         priority: EventPriority::Medium,
-///         name: "click",
-///         element: Some(ElementId(0)),
-///         data: Arc::new(ClickEvent { .. })
+///         }
 ///     }
-/// )).unwrap();
+/// }
 /// ```
-#[derive(Debug, Clone)]
-pub struct UserEvent {
-    /// The originator of the event trigger if available
-    pub scope_id: Option<ScopeId>,
-
-    /// The priority of the event to be scheduled around ongoing work
-    pub priority: EventPriority,
-
-    /// The optional real node associated with the trigger
-    pub element: Option<ElementId>,
-
-    /// The event type IE "onclick" or "onmouseover"
-    pub name: &'static str,
-
-    /// If the event is bubbles up through the vdom
-    pub bubbles: bool,
-
-    /// The event data to be passed onto the event handler
-    pub data: Arc<dyn Any + Send + Sync>,
+pub struct Event<T: 'static + ?Sized> {
+    pub(crate) data: Rc<T>,
+    pub(crate) propogates: Rc<Cell<bool>>,
 }
 
-/// Priority of Event Triggers.
-///
-/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
-/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers
-/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
-///
-/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
-///
-/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
-/// we keep it simple, and just use a 3-tier priority system.
-///
-/// - `NoPriority` = 0
-/// - `LowPriority` = 1
-/// - `NormalPriority` = 2
-/// - `UserBlocking` = 3
-/// - `HighPriority` = 4
-/// - `ImmediatePriority` = 5
-///
-/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
-/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
-/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
-pub enum EventPriority {
-    /// Work that must be completed during the EventHandler phase.
+impl<T> Event<T> {
+    /// Prevent this event from continuing to bubble up the tree to parent elements.
     ///
-    /// Currently this is reserved for controlled inputs.
-    Immediate = 3,
-
-    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
-    ///
-    /// This is typically reserved for things like user interaction.
+    /// # Example
     ///
-    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
-    High = 2,
+    /// ```rust, ignore
+    /// rsx! {
+    ///     button {
+    ///         onclick: move |evt: Event<MouseData>| {
+    ///             evt.cancel_bubble();
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    #[deprecated = "use stop_propogation instead"]
+    pub fn cancel_bubble(&self) {
+        self.propogates.set(false);
+    }
 
-    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
-    /// than "High Priority" events and will take precedence over low priority events.
+    /// Prevent this event from continuing to bubble up the tree to parent elements.
     ///
-    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
+    /// # Example
     ///
-    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
-    Medium = 1,
+    /// ```rust, ignore
+    /// rsx! {
+    ///     button {
+    ///         onclick: move |evt: Event<MouseData>| {
+    ///             evt.cancel_bubble();
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    pub fn stop_propogation(&self) {
+        self.propogates.set(false);
+    }
 
-    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
-    /// advanced to the front of the work queue until completed.
-    ///
-    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
+    /// Get a reference to the inner data from this event
     ///
-    /// This is considered "idle" work or "background" work.
-    Low = 0,
+    /// ```rust, ignore
+    /// rsx! {
+    ///     button {
+    ///         onclick: move |evt: Event<MouseData>| {
+    ///             let data = evt.inner.clone();
+    ///             cx.spawn(async move {
+    ///                 println!("{:?}", data);
+    ///             });
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    pub fn inner(&self) -> &Rc<T> {
+        &self.data
+    }
 }
 
-/// The internal Dioxus type that carries any event data to the relevant handler.
-
-pub struct AnyEvent {
-    pub(crate) bubble_state: Rc<BubbleState>,
-    pub(crate) data: Arc<dyn Any + Send + Sync>,
+impl<T: ?Sized> Clone for Event<T> {
+    fn clone(&self) -> Self {
+        Self {
+            propogates: self.propogates.clone(),
+            data: self.data.clone(),
+        }
+    }
 }
 
-impl AnyEvent {
-    /// Convert this [`AnyEvent`] into a specific [`UiEvent`] with [`EventData`].
-    ///
-    /// ```rust, ignore
-    /// let evt: FormEvent = evvt.downcast().unwrap();
-    /// ```
-    #[must_use]
-    pub fn downcast<T: Send + Sync + 'static>(self) -> Option<UiEvent<T>> {
-        let AnyEvent { data, bubble_state } = self;
+impl<T> std::ops::Deref for Event<T> {
+    type Target = Rc<T>;
+    fn deref(&self) -> &Self::Target {
+        &self.data
+    }
+}
 
-        data.downcast::<T>()
-            .ok()
-            .map(|data| UiEvent { data, bubble_state })
+impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("UiEvent")
+            .field("bubble_state", &self.propogates)
+            .field("data", &self.data)
+            .finish()
     }
 }
 
-/// A [`UiEvent`] is a type that wraps various [`EventData`].
+/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
 ///
-/// You should prefer to use the name of the event directly, rather than
-/// the [`UiEvent`]<T> generic type.
+/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
 ///
-/// For the HTML crate, this would include `MouseEvent`, `FormEvent` etc.
-pub struct UiEvent<T> {
-    /// The internal data of the event
-    /// This is wrapped in an Arc so that it can be sent across threads
-    pub data: Arc<T>,
-
-    #[allow(unused)]
-    bubble_state: Rc<BubbleState>,
+///
+/// # Example
+///
+/// ```rust, ignore
+/// rsx!{
+///     MyComponent { onclick: move |evt| log::info!("clicked") }
+/// }
+///
+/// #[derive(Props)]
+/// struct MyProps<'a> {
+///     onclick: EventHandler<'a, MouseEvent>,
+/// }
+///
+/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
+///     cx.render(rsx!{
+///         button {
+///             onclick: move |evt| cx.props.onclick.call(evt),
+///         }
+///     })
+/// }
+///
+/// ```
+pub struct EventHandler<'bump, T = ()> {
+    pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
 }
 
-impl<T: Debug> std::fmt::Debug for UiEvent<T> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("UiEvent").field("data", &self.data).finish()
+impl<T> Default for EventHandler<'_, T> {
+    fn default() -> Self {
+        Self {
+            callback: Default::default(),
+        }
     }
 }
 
-impl<T> std::ops::Deref for UiEvent<T> {
-    type Target = T;
+type ExternalListenerCallback<'bump, T> = bumpalo::boxed::Box<'bump, dyn FnMut(T) + 'bump>;
 
-    fn deref(&self) -> &Self::Target {
-        self.data.as_ref()
+impl<T> EventHandler<'_, T> {
+    /// Call this event handler with the appropriate event type
+    ///
+    /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
+    pub fn call(&self, event: T) {
+        if let Some(callback) = self.callback.borrow_mut().as_mut() {
+            callback(event);
+        }
     }
-}
 
-impl<T> UiEvent<T> {
-    /// Prevent this event from bubbling up the tree.
-    pub fn cancel_bubble(&self) {
-        self.bubble_state.canceled.set(true);
+    /// Forcibly drop the internal handler callback, releasing memory
+    ///
+    /// This will force any future calls to "call" to not doing anything
+    pub fn release(&self) {
+        self.callback.replace(None);
     }
 }

+ 103 - 0
packages/core/src/fragment.rs

@@ -0,0 +1,103 @@
+use crate::innerlude::*;
+
+/// Create inline fragments using Component syntax.
+///
+/// ## Details
+///
+/// Fragments capture a series of children without rendering extra nodes.
+///
+/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
+/// a key is needed to identify each item.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// rsx!{
+///     Fragment { key: "abc" }
+/// }
+/// ```
+///
+/// ## Usage
+///
+/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
+///
+/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+///
+/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+#[allow(non_upper_case_globals, non_snake_case)]
+pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
+    let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?;
+    Ok(VNode {
+        key: children.key,
+        parent: children.parent,
+        template: children.template,
+        root_ids: children.root_ids,
+        dynamic_nodes: children.dynamic_nodes,
+        dynamic_attrs: children.dynamic_attrs,
+    })
+}
+
+pub struct FragmentProps<'a>(Element<'a>);
+pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
+impl<'a> FragmentBuilder<'a, false> {
+    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
+        FragmentBuilder(children)
+    }
+}
+impl<'a, const A: bool> FragmentBuilder<'a, A> {
+    pub fn build(self) -> FragmentProps<'a> {
+        FragmentProps(self.0)
+    }
+}
+
+/// Access the children elements passed into the component
+///
+/// This enables patterns where a component is passed children from its parent.
+///
+/// ## Details
+///
+/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
+/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
+/// on the props that takes Context.
+///
+/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
+/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
+/// props are valid for the static lifetime.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+///     cx.render(rsx!{
+///         CustomCard {
+///             h1 {}
+///             p {}
+///         }
+///     })
+/// }
+///
+/// #[derive(PartialEq, Props)]
+/// struct CardProps {
+///     children: Element
+/// }
+///
+/// fn CustomCard(cx: Scope<CardProps>) -> Element {
+///     cx.render(rsx!{
+///         div {
+///             h1 {"Title card"}
+///             {cx.props.children}
+///         }
+///     })
+/// }
+/// ```
+impl<'a> Properties for FragmentProps<'a> {
+    type Builder = FragmentBuilder<'a, false>;
+    const IS_STATIC: bool = false;
+    fn builder() -> Self::Builder {
+        FragmentBuilder(VNode::empty())
+    }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        false
+    }
+}

+ 19 - 91
packages/core/src/lazynodes.rs

@@ -3,7 +3,7 @@
 //! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
 //! to `rsx!` more efficient.
 //!
-//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures.
+//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`ScopeState`] closures.
 //!
 //! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
 //! we build a tiny alloactor in the stack and allocate the closure into that.
@@ -11,13 +11,13 @@
 //! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
 //! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
 
-use crate::innerlude::{NodeFactory, VNode};
+use crate::{innerlude::VNode, ScopeState};
 use std::mem;
 
 /// 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
-/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
+/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
 ///
 ///
 /// ```rust, ignore
@@ -31,7 +31,7 @@ type StackHeapSize = [usize; 16];
 
 enum StackNodeStorage<'a, 'b> {
     Stack(LazyStack),
-    Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
+    Heap(Box<dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b>),
 }
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
@@ -40,11 +40,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// If the closure cannot fit into the stack allocation (16 bytes), then it
     /// is placed on the heap. Most closures will fit into the stack, and is
     /// the most optimal way to use the creation function.
-    pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
+    pub fn new(val: impl FnOnce(&'a ScopeState) -> VNode<'a> + 'b) -> Self {
         // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
         let mut slot = Some(val);
 
-        let val = move |fac: Option<NodeFactory<'a>>| {
+        let val = move |fac: Option<&'a ScopeState>| {
             fac.map(
                 slot.take()
                     .expect("LazyNodes closure to be called only once"),
@@ -65,13 +65,13 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// Create a new [`LazyNodes`] closure, but force it onto the heap.
     pub fn new_boxed<F>(inner: F) -> Self
     where
-        F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
+        F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b,
     {
         // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
         let mut slot = Some(inner);
 
         Self {
-            inner: StackNodeStorage::Heap(Box::new(move |fac: Option<NodeFactory<'a>>| {
+            inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| {
                 fac.map(
                     slot.take()
                         .expect("LazyNodes closure to be called only once"),
@@ -82,9 +82,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
 
     unsafe fn new_inner<F>(val: F) -> Self
     where
-        F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
+        F: FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b,
     {
-        let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
+        let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>;
 
         assert_eq!(
             ptr as *const u8, &val as *const _ as *const u8,
@@ -160,12 +160,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// ```rust, ignore
     /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
     ///
-    /// let fac = NodeFactory::new(&cx);
-    ///
     /// let node = f.call(cac);
     /// ```
     #[must_use]
-    pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
+    pub fn call(self, f: &'a ScopeState) -> VNode<'a> {
         match self.inner {
             StackNodeStorage::Heap(mut lazy) => {
                 lazy(Some(f)).expect("Closure should not be called twice")
@@ -182,18 +180,18 @@ struct LazyStack {
 }
 
 impl LazyStack {
-    fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
+    fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> {
         let LazyStack { buf, .. } = self;
         let data = buf.as_ref();
 
         let info_size =
-            mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
+            mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>>()
                 / mem::size_of::<usize>()
                 - 1;
 
         let info_ofs = data.len() - info_size;
 
-        let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
+        let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> =
             unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
 
         self.dropped = true;
@@ -208,14 +206,14 @@ impl Drop for LazyStack {
             let LazyStack { buf, .. } = self;
             let data = buf.as_ref();
 
-            let info_size = mem::size_of::<
-                *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
-            >() / mem::size_of::<usize>()
-                - 1;
+            let info_size =
+                mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>>>()
+                    / mem::size_of::<usize>()
+                    - 1;
 
             let info_ofs = data.len() - info_size;
 
-            let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
+            let g: *mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>> =
                 unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
 
             self.dropped = true;
@@ -250,73 +248,3 @@ unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut
 fn round_to_words(len: usize) -> usize {
     (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::innerlude::{Element, Scope, VirtualDom};
-
-    #[test]
-    fn it_works() {
-        fn app(cx: Scope<()>) -> Element {
-            cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
-        }
-
-        let mut dom = VirtualDom::new(app);
-        dom.rebuild();
-
-        let g = dom.base_scope().root_node();
-        dbg!(g);
-    }
-
-    #[test]
-    fn it_drops() {
-        use std::rc::Rc;
-
-        struct AppProps {
-            inner: Rc<i32>,
-        }
-
-        fn app(cx: Scope<AppProps>) -> Element {
-            struct DropInner {
-                id: i32,
-            }
-            impl Drop for DropInner {
-                fn drop(&mut self) {
-                    eprintln!("dropping inner");
-                }
-            }
-
-            let caller = {
-                let it = (0..10).map(|i| {
-                    let val = cx.props.inner.clone();
-                    LazyNodes::new(move |f| {
-                        eprintln!("hell closure");
-                        let inner = DropInner { id: i };
-                        f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
-                    })
-                });
-
-                LazyNodes::new(|f| {
-                    eprintln!("main closure");
-                    f.fragment_from_iter(it)
-                })
-            };
-
-            cx.render(caller)
-        }
-
-        let inner = Rc::new(0);
-        let mut dom = VirtualDom::new_with_props(
-            app,
-            AppProps {
-                inner: inner.clone(),
-            },
-        );
-        dom.rebuild();
-
-        drop(dom);
-
-        assert_eq!(Rc::strong_count(&inner), 1);
-    }
-}

+ 34 - 83
packages/core/src/lib.rs

@@ -1,37 +1,43 @@
-#![allow(non_snake_case)]
 #![doc = include_str!("../README.md")]
-#![deny(missing_docs)]
+#![warn(missing_docs)]
 
-pub(crate) mod arbitrary_value;
-pub(crate) mod diff;
-pub(crate) mod dynamic_template_context;
-pub(crate) mod events;
-pub(crate) mod lazynodes;
-pub(crate) mod mutations;
-pub(crate) mod nodes;
-pub(crate) mod properties;
-pub(crate) mod scopes;
-pub(crate) mod template;
-pub(crate) mod util;
-pub(crate) mod virtual_dom;
+mod any_props;
+mod arena;
+mod bump_frame;
+mod create;
+mod diff;
+mod dirty_scope;
+mod error_boundary;
+mod events;
+mod fragment;
+mod lazynodes;
+mod mutations;
+mod nodes;
+mod properties;
+mod scheduler;
+mod scope_arena;
+mod scopes;
+mod virtual_dom;
 
 pub(crate) mod innerlude {
-    pub use crate::arbitrary_value::*;
-    pub use crate::dynamic_template_context::*;
+    pub use crate::arena::*;
+    pub use crate::dirty_scope::*;
+    pub use crate::error_boundary::*;
     pub use crate::events::*;
+    pub use crate::fragment::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
+    pub use crate::nodes::RenderReturn;
     pub use crate::nodes::*;
     pub use crate::properties::*;
+    pub use crate::scheduler::*;
     pub use crate::scopes::*;
-    pub use crate::template::*;
-    pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
-    /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
+    /// An [`Element`] is a possibly-errored [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
     ///
-    /// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
-    pub type Element<'a> = Option<VNode<'a>>;
+    /// An Errored [`Element`] will propagate the error to the nearest error boundary.
+    pub type Element<'a> = Result<VNode<'a>, anyhow::Error>;
 
     /// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
     ///
@@ -61,44 +67,23 @@ pub(crate) mod innerlude {
     /// )
     /// ```
     pub type Component<P = ()> = fn(Scope<P>) -> Element;
-
-    /// A list of attributes
-    pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
 }
 
 pub use crate::innerlude::{
-    AnyEvent, ArbitraryAttributeValue, Attribute, AttributeDiscription, AttributeValue,
-    CodeLocation, Component, DioxusElement, DomEdit, DynamicNodeMapping, Element, ElementId,
-    ElementIdIterator, EventHandler, EventPriority, IntoAttributeValue, IntoVNode, LazyNodes,
-    Listener, Mutations, NodeFactory, OwnedAttributeValue, PathSeg, Properties, RendererTemplateId,
-    SchedulerMsg, Scope, ScopeId, ScopeState, StaticCodeLocation, StaticDynamicNodeMapping,
-    StaticPathSeg, StaticTemplateNode, StaticTemplateNodes, StaticTraverse, TaskId, Template,
-    TemplateAttribute, TemplateAttributeValue, TemplateContext, TemplateElement, TemplateId,
-    TemplateNode, TemplateNodeId, TemplateNodeType, TemplateValue, TextTemplate,
-    TextTemplateSegment, UiEvent, UpdateOp, UserEvent, VComponent, VElement, VFragment, VNode,
-    VPlaceholder, VText, VirtualDom,
-};
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-pub use crate::innerlude::{
-    OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedPathSeg, OwnedTemplateNode,
-    OwnedTemplateNodes, OwnedTraverse, SetTemplateMsg,
+    fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
+    Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope,
+    ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute,
+    TemplateNode, VComponent, VNode, VText, VirtualDom,
 };
 
 /// The purpose of this module is to alleviate imports of many common types
 ///
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
-    pub use crate::get_line_num;
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    pub use crate::innerlude::OwnedTemplate;
     pub use crate::innerlude::{
-        fc_to_builder, AttributeDiscription, AttributeValue, Attributes, CodeLocation, Component,
-        DioxusElement, Element, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
-        LazyStaticVec, NodeFactory, Properties, Scope, ScopeId, ScopeState, StaticAttributeValue,
-        StaticCodeLocation, StaticDynamicNodeMapping, StaticPathSeg, StaticTemplate,
-        StaticTemplateNodes, StaticTraverse, Template, TemplateAttribute, TemplateAttributeValue,
-        TemplateContext, TemplateElement, TemplateId, TemplateNode, TemplateNodeId,
-        TemplateNodeType, TextTemplate, TextTemplateSegment, UpdateOp, VNode, VirtualDom,
+        fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope,
+        ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode,
+        VirtualDom,
     };
 }
 
@@ -106,38 +91,4 @@ pub mod exports {
     //! Important dependencies that are used by the rest of the library
     //! Feel free to just add the dependencies in your own Crates.toml
     pub use bumpalo;
-    pub use futures_channel;
-    pub use once_cell;
-}
-
-/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
-pub(crate) mod unsafe_utils {
-    use crate::VNode;
-
-    pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
-        std::mem::transmute(node)
-    }
-}
-
-#[macro_export]
-/// A helper macro for using hooks in async environements.
-///
-/// # Usage
-///
-///
-/// ```ignore
-/// let (data) = use_ref(&cx, || {});
-///
-/// let handle_thing = move |_| {
-///     to_owned![data]
-///     cx.spawn(async move {
-///         // do stuff
-///     });
-/// }
-/// ```
-macro_rules! to_owned {
-    ($($es:ident),+$(,)?) => {$(
-        #[allow(unused_mut)]
-        let mut $es = $es.to_owned();
-    )*}
 }

+ 186 - 333
packages/core/src/mutations.rs

@@ -1,407 +1,260 @@
-//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
-//!
-//! This module contains an internal API to generate these instructions.
-//!
-//! Beware that changing code in this module will break compatibility with
-//! interpreters for these types of DomEdits.
+use fxhash::FxHashSet;
 
-use crate::innerlude::*;
-use std::{any::Any, fmt::Debug};
+use crate::{arena::ElementId, ScopeId, Template};
 
-/// ## Mutations
+/// A container for all the relevant steps to modify the Real DOM
 ///
-/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
-/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
-/// applied the edits.
+/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the
+/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes
+/// in the form of a [`Mutation`].
+///
+/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.
+///
+/// Templates, however, apply to all subtrees, not just target subtree.
 ///
 /// Mutations are the only link between the RealDOM and the VirtualDOM.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[derive(Debug, Default)]
+#[must_use = "not handling edits can lead to visual inconsistencies in UI"]
 pub struct Mutations<'a> {
-    /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
-    pub edits: Vec<DomEdit<'a>>,
+    /// The ID of the subtree that these edits are targetting
+    pub subtree: usize,
 
     /// The list of Scopes that were diffed, created, and removed during the Diff process.
     pub dirty_scopes: FxHashSet<ScopeId>,
 
-    /// The list of nodes to connect to the RealDOM.
-    pub refs: Vec<NodeRefMutation<'a>>,
+    /// Any templates encountered while diffing the DOM.
+    ///
+    /// These must be loaded into a cache before applying the edits
+    pub templates: Vec<Template<'a>>,
+
+    /// Any mutations required to patch the renderer to match the layout of the VirtualDom
+    pub edits: Vec<Mutation<'a>>,
 }
 
-impl Debug for Mutations<'_> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Mutations")
-            .field("edits", &self.edits)
-            .field("noderefs", &self.refs)
-            .finish()
+impl<'a> Mutations<'a> {
+    /// Rewrites IDs to just be "template", so you can compare the mutations
+    ///
+    /// Used really only for testing
+    pub fn santize(mut self) -> Self {
+        for edit in self.edits.iter_mut() {
+            if let Mutation::LoadTemplate { name, .. } = edit {
+                *name = "template"
+            }
+        }
+
+        self
+    }
+
+    /// Push a new mutation into the dom_edits list
+    pub(crate) fn push(&mut self, mutation: Mutation<'static>) {
+        self.edits.push(mutation)
     }
 }
 
-/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
-/// network or through FFI boundaries.
-#[derive(Debug, PartialEq)]
+/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
+/// of the Dioxus VirtualDom.
+///
+/// These edits can be serialized and sent over the network or through any interface
 #[cfg_attr(
     feature = "serialize",
     derive(serde::Serialize, serde::Deserialize),
     serde(tag = "type")
 )]
-pub enum DomEdit<'bump> {
-    /// Pop the topmost node from our stack and append them to the node
-    /// at the top of the stack.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Mutation<'a> {
+    /// Add these m children to the target element
     AppendChildren {
-        /// The parent to append nodes to.
-        root: Option<u64>,
-
-        /// The ids of the children to append.
-        children: Vec<u64>,
-    },
-
-    /// Replace a given (single) node with a handful of nodes currently on the stack.
-    ReplaceWith {
-        /// The ID of the node to be replaced.
-        root: Option<u64>,
-
-        /// The ids of the nodes to replace the root with.
-        nodes: Vec<u64>,
-    },
-
-    /// Insert a number of nodes after a given node.
-    InsertAfter {
-        /// The ID of the node to insert after.
-        root: Option<u64>,
+        /// The ID of the element being mounted to
+        id: ElementId,
 
-        /// The ids of the nodes to insert after the target node.
-        nodes: Vec<u64>,
+        /// The number of nodes on the stack
+        m: usize,
     },
 
-    /// Insert a number of nodes before a given node.
-    InsertBefore {
-        /// The ID of the node to insert before.
-        root: Option<u64>,
-
-        /// The ids of the nodes to insert before the target node.
-        nodes: Vec<u64>,
+    /// Assign the element at the given path the target ElementId.
+    ///
+    /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
+    /// element, hence the use of a single byte.
+    ///
+    ///
+    AssignId {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+        path: &'static [u8],
+
+        /// The ID we're assigning to this element/placeholder.
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Remove a particular node from the DOM
-    Remove {
-        /// The ID of the node to remove.
-        root: Option<u64>,
+    /// Create an placeholder int he DOM that we will use later.
+    ///
+    /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
+    CreatePlaceholder {
+        /// The ID we're assigning to this element/placeholder.
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Create a new purely-text node
+    /// Create a node specifically for text with the given value
     CreateTextNode {
-        /// The ID the new node should have.
-        root: Option<u64>,
+        /// The text content of this text node
+        value: &'a str,
 
-        /// The textcontent of the node
-        text: &'bump str,
+        /// The ID we're assigning to this specific text nodes
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Create a new purely-element node
-    CreateElement {
-        /// The ID the new node should have.
-        root: Option<u64>,
-
-        /// The tagname of the node
-        tag: &'bump str,
-
-        /// The number of children nodes that will follow this message.
-        children: u32,
+    /// Hydrate an existing text node at the given path with the given text.
+    ///
+    /// Assign this text node the given ID since we will likely need to modify this text at a later point
+    HydrateText {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+        path: &'static [u8],
+
+        /// The value of the textnode that we want to set the placeholder with
+        value: &'a str,
+
+        /// The ID we're assigning to this specific text nodes
+        ///
+        /// This will be used later to modify the element or replace it with another element.
+        id: ElementId,
     },
 
-    /// Create a new purely-comment node with a given namespace
-    CreateElementNs {
-        /// The ID the new node should have.
-        root: Option<u64>,
-
-        /// The namespace of the node
-        tag: &'bump str,
+    /// Load and clone an existing node from a template saved under that specific name
+    ///
+    /// Dioxus guarantees that the renderer will have already been provided the template.
+    /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
+    LoadTemplate {
+        /// The "name" of the template. When paired with `rsx!`, this is autogenerated
+        name: &'static str,
 
-        /// The namespace of the node (like `SVG`)
-        ns: &'static str,
+        /// Which root are we loading from the template?
+        ///
+        /// The template is stored as a list of nodes. This index represents the position of that root
+        index: usize,
 
-        /// The number of children nodes that will follow this message.
-        children: u32,
+        /// The ID we're assigning to this element being loaded from the template
+        ///
+        /// This will be used later to move the element around in lists
+        id: ElementId,
     },
 
-    /// Create a new placeholder node.
-    /// In most implementations, this will either be a hidden div or a comment node.
-    CreatePlaceholder {
-        /// The ID the new node should have.
-        root: Option<u64>,
-    },
+    /// Replace the target element (given by its ID) with the topmost m nodes on the stack
+    ReplaceWith {
+        /// The ID of the node we're going to replace with
+        id: ElementId,
 
-    /// Create a new Event Listener.
-    NewEventListener {
-        /// The name of the event to listen for.
-        event_name: &'static str,
+        /// The number of nodes on the stack to use to replace
+        m: usize,
+    },
 
-        /// The ID of the node to attach the listener to.
-        scope: ScopeId,
+    /// Replace an existing element in the template at the given path with the m nodes on the stack
+    ReplacePlaceholder {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+        path: &'static [u8],
 
-        /// The ID of the node to attach the listener to.
-        root: Option<u64>,
+        /// The number of nodes on the stack to use to replace
+        m: usize,
     },
 
-    /// Remove an existing Event Listener.
-    RemoveEventListener {
-        /// The ID of the node to remove.
-        root: Option<u64>,
+    /// Insert a number of nodes after a given node.
+    InsertAfter {
+        /// The ID of the node to insert after.
+        id: ElementId,
 
-        /// The name of the event to remove.
-        event: &'static str,
+        /// The ids of the nodes to insert after the target node.
+        m: usize,
     },
 
-    /// Set the textcontent of a node.
-    SetText {
-        /// The ID of the node to set the textcontent of.
-        root: Option<u64>,
+    /// Insert a number of nodes before a given node.
+    InsertBefore {
+        /// The ID of the node to insert before.
+        id: ElementId,
 
-        /// The textcontent of the node
-        text: &'bump str,
+        /// The ids of the nodes to insert before the target node.
+        m: usize,
     },
 
     /// Set the value of a node's attribute.
     SetAttribute {
-        /// The ID of the node to set the attribute of.
-        root: Option<u64>,
-
         /// The name of the attribute to set.
-        field: &'static str,
-
+        name: &'a str,
         /// The value of the attribute.
-        value: AttributeValue<'bump>,
+        value: &'a str,
+
+        /// The ID of the node to set the attribute of.
+        id: ElementId,
 
-        // value: &'bump str,
         /// The (optional) namespace of the attribute.
         /// For instance, "style" is in the "style" namespace.
-        ns: Option<&'bump str>,
+        ns: Option<&'a str>,
     },
 
-    /// Remove an attribute from a node.
-    RemoveAttribute {
-        /// The ID of the node to remove.
-        root: Option<u64>,
+    /// Set the value of a node's attribute.
+    SetBoolAttribute {
+        /// The name of the attribute to set.
+        name: &'a str,
 
-        /// The name of the attribute to remove.
-        name: &'static str,
+        /// The value of the attribute.
+        value: bool,
 
-        /// The namespace of the attribute.
-        ns: Option<&'bump str>,
+        /// The ID of the node to set the attribute of.
+        id: ElementId,
     },
 
-    /// Clones a node.
-    CloneNode {
-        /// The ID of the node to clone.
-        id: Option<u64>,
+    /// Set the textcontent of a node.
+    SetText {
+        /// The textcontent of the node
+        value: &'a str,
 
-        /// The ID of the new node.
-        new_id: u64,
+        /// The ID of the node to set the textcontent of.
+        id: ElementId,
     },
 
-    /// Clones the children of a node. (allows cloning fragments)
-    CloneNodeChildren {
-        /// The ID of the node to clone.
-        id: Option<u64>,
-
-        /// The ID of the new node.
-        new_ids: Vec<u64>,
-    },
+    /// Create a new Event Listener.
+    NewEventListener {
+        /// The name of the event to listen for.
+        name: &'a str,
 
-    /// Navigates to the last node to the first child of the current node.
-    FirstChild {},
+        /// The ID of the node to attach the listener to.
+        scope: ScopeId,
 
-    /// Navigates to the last node to the last child of the current node.
-    NextSibling {},
+        /// The ID of the node to attach the listener to.
+        id: ElementId,
+    },
 
-    /// Navigates to the last node to the parent of the current node.
-    ParentNode {},
+    /// Remove an existing Event Listener.
+    RemoveEventListener {
+        /// The name of the event to remove.
+        name: &'a str,
 
-    /// Stores the last node with a new id.
-    StoreWithId {
-        /// The ID of the node to store.
-        id: u64,
+        /// The ID of the node to remove.
+        id: ElementId,
     },
 
-    /// Manually set the last node.
-    SetLastNode {
-        /// The ID to set the last node to.
-        id: u64,
+    /// Remove a particular node from the DOM
+    Remove {
+        /// The ID of the node to remove.
+        id: ElementId,
     },
-}
-
-use rustc_hash::FxHashSet;
-use DomEdit::*;
-
-#[allow(unused)]
-impl<'a> Mutations<'a> {
-    pub(crate) fn new() -> Self {
-        Self {
-            edits: Vec::new(),
-            refs: Vec::new(),
-            dirty_scopes: Default::default(),
-        }
-    }
-
-    pub(crate) fn replace_with(&mut self, root: Option<u64>, nodes: Vec<u64>) {
-        self.edits.push(ReplaceWith { nodes, root });
-    }
 
-    pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
-        self.edits.push(InsertAfter { nodes, root });
-    }
-
-    pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
-        self.edits.push(InsertBefore { nodes, root });
-    }
-
-    pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
-        self.edits.push(AppendChildren { root, children });
-    }
-
-    // Remove Nodes from the dom
-    pub(crate) fn remove(&mut self, id: Option<u64>) {
-        self.edits.push(Remove { root: id });
-    }
-
-    // Create
-    pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
-        self.edits.push(CreateTextNode { text, root: id });
-    }
-
-    pub(crate) fn create_element(
-        &mut self,
-        tag: &'static str,
-        ns: Option<&'static str>,
-        id: Option<u64>,
-        children: u32,
-    ) {
-        match ns {
-            Some(ns) => self.edits.push(CreateElementNs {
-                root: id,
-                ns,
-                tag,
-                children,
-            }),
-            None => self.edits.push(CreateElement {
-                root: id,
-                tag,
-                children,
-            }),
-        }
-    }
-
-    // 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: Option<u64>) {
-        self.edits.push(CreatePlaceholder { root: id });
-    }
-
-    // events
-    pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
-        let Listener {
-            event,
-            mounted_node,
-            ..
-        } = listener;
-
-        let element_id = Some(mounted_node.get().unwrap().into());
-
-        self.edits.push(NewEventListener {
-            scope,
-            event_name: event,
-            root: element_id,
-        });
-    }
-
-    pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option<u64>) {
-        self.edits.push(RemoveEventListener { event, root });
-    }
-
-    // modify
-    pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
-        self.edits.push(SetText { text, root });
-    }
-
-    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
-        let Attribute {
-            value, attribute, ..
-        } = attribute;
-
-        self.edits.push(SetAttribute {
-            field: attribute.name,
-            value: value.clone(),
-            ns: attribute.namespace,
-            root,
-        });
-    }
-
-    pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option<u64>) {
-        let Attribute { attribute, .. } = attribute;
-
-        self.edits.push(RemoveAttribute {
-            name: attribute.name,
-            ns: attribute.namespace,
-            root,
-        });
-    }
-
-    pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
-        self.dirty_scopes.insert(scope);
-    }
-
-    pub(crate) fn clone_node(&mut self, id: Option<u64>, new_id: u64) {
-        self.edits.push(CloneNode { id, new_id });
-    }
-
-    pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
-        self.edits.push(CloneNodeChildren { id, new_ids });
-    }
-
-    pub(crate) fn first_child(&mut self) {
-        self.edits.push(FirstChild {});
-    }
-
-    pub(crate) fn next_sibling(&mut self) {
-        self.edits.push(NextSibling {});
-    }
-
-    pub(crate) fn parent_node(&mut self) {
-        self.edits.push(ParentNode {});
-    }
-
-    pub(crate) fn store_with_id(&mut self, id: u64) {
-        self.edits.push(StoreWithId { id });
-    }
-
-    pub(crate) fn set_last_node(&mut self, id: u64) {
-        self.edits.push(SetLastNode { id });
-    }
-}
-
-// refs are only assigned once
-pub struct NodeRefMutation<'a> {
-    pub element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
-    pub element_id: ElementId,
-}
-
-impl<'a> std::fmt::Debug for NodeRefMutation<'a> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("NodeRefMutation")
-            .field("element_id", &self.element_id)
-            .finish()
-    }
-}
-
-impl<'a> NodeRefMutation<'a> {
-    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
-        self.element
-            .as_ref()
-            .and_then(|f| f.get())
-            .and_then(|f| f.downcast_ref::<T>())
-    }
-    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
-        self.element
-            .as_mut()
-            .and_then(|f| f.get_mut())
-            .and_then(|f| f.downcast_mut::<T>())
-    }
+    /// Push the given root node onto our stack.
+    PushRoot {
+        /// The ID of the root node to push.
+        id: ElementId,
+    },
 }

+ 437 - 810
packages/core/src/nodes.rs

@@ -1,949 +1,576 @@
-//! Virtual Node Support
-//!
-//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
-//! cheap and *very* fast to construct - building a full tree should be quick.
 use crate::{
-    dynamic_template_context::TemplateContext,
-    innerlude::{
-        AttributeValue, ComponentPtr, Element, IntoAttributeValue, Properties, Scope, ScopeId,
-        ScopeState, Template, TemplateId,
-    },
-    lazynodes::LazyNodes,
-    template::VTemplateRef,
-    AnyEvent, Component,
+    any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
 };
-use bumpalo::{boxed::Box as BumpBox, Bump};
+use bumpalo::boxed::Box as BumpBox;
+use bumpalo::Bump;
 use std::{
+    any::{Any, TypeId},
     cell::{Cell, RefCell},
-    fmt::{Arguments, Debug, Formatter},
-    rc::Rc,
+    fmt::Arguments,
+    future::Future,
 };
 
-/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
+pub type TemplateId = &'static str;
+
+/// The actual state of the component's most recent computation
 ///
-/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
+/// Because Dioxus accepts components in the form of `async fn(Scope) -> Result<VNode>`, we need to support both
+/// sync and async versions.
 ///
-/// - the `rsx!` macro
-/// - the [`NodeFactory`] API
-pub enum VNode<'src> {
-    /// Text VNodes are simply bump-allocated (or static) string slices
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut vdom = VirtualDom::new();
-    /// let node = vdom.render_vnode(rsx!( "hello" ));
-    ///
-    /// if let VNode::Text(vtext) = node {
-    ///     assert_eq!(vtext.text, "hello");
-    ///     assert_eq!(vtext.dom_id.get(), None);
-    ///     assert_eq!(vtext.is_static, true);
-    /// }
-    /// ```
-    Text(&'src VText<'src>),
-
-    /// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut vdom = VirtualDom::new();
-    ///
-    /// let node = vdom.render_vnode(rsx!{
-    ///     div {
-    ///         key: "a",
-    ///         onclick: |e| log::info!("clicked"),
-    ///         hidden: "true",
-    ///         style: { background_color: "red" },
-    ///         "hello"
-    ///     }
-    /// });
-    ///
-    /// if let VNode::Element(velement) = node {
-    ///     assert_eq!(velement.tag_name, "div");
-    ///     assert_eq!(velement.namespace, None);
-    ///     assert_eq!(velement.key, Some("a"));
-    /// }
-    /// ```
-    Element(&'src VElement<'src>),
-
-    /// Fragment nodes may contain many VNodes without a single root.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// rsx!{
-    ///     a {}
-    ///     link {}
-    ///     style {}
-    ///     "asd"
-    ///     Example {}
-    /// }
-    /// ```
-    Fragment(&'src VFragment<'src>),
-
-    /// Component nodes represent a mounted component with props, children, and a key.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// fn Example(cx: Scope) -> Element {
-    ///     ...
-    /// }
-    ///
-    /// let mut vdom = VirtualDom::new();
-    ///
-    /// let node = vdom.render_vnode(rsx!( Example {} ));
-    ///
-    /// if let VNode::Component(vcomp) = node {
-    ///     assert_eq!(vcomp.user_fc, Example as *const ());
-    /// }
-    /// ```
-    Component(&'src VComponent<'src>),
+/// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor
+/// you might need to handle the case where there's no node immediately ready.
+pub enum RenderReturn<'a> {
+    /// A currently-available element
+    Sync(Element<'a>),
 
-    /// Placeholders are a type of placeholder VNode used when fragments don't contain any children.
-    ///
-    /// Placeholders cannot be directly constructed via public APIs.
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut vdom = VirtualDom::new();
-    ///
-    /// let node = vdom.render_vnode(rsx!( Fragment {} ));
-    ///
-    /// if let VNode::Fragment(frag) = node {
-    ///     let root = &frag.children[0];
-    ///     assert_eq!(root, VNode::Anchor);
-    /// }
-    /// ```
-    Placeholder(&'src VPlaceholder),
-
-    /// Templetes ase generated by the rsx macro to eleminate diffing static nodes.
-    TemplateRef(&'src VTemplateRef<'src>),
-}
-
-impl<'src> VNode<'src> {
-    /// Get the VNode's "key" used in the keyed diffing algorithm.
-    pub fn key(&self) -> Option<&'src str> {
-        match &self {
-            VNode::Element(el) => el.key,
-            VNode::Component(c) => c.key,
-            VNode::Fragment(f) => f.key,
-            VNode::Text(_t) => None,
-            VNode::Placeholder(_f) => None,
-            VNode::TemplateRef(t) => t.dynamic_context.key,
-        }
-    }
+    /// An ongoing future that will resolve to a [`Element`]
+    Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
+}
 
-    /// Get the ElementID of the mounted VNode.
+/// A reference to a template along with any context needed to hydrate it
+///
+/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
+/// static parts of the template.
+#[derive(Debug, Clone)]
+pub struct VNode<'a> {
+    /// The key given to the root of this template.
     ///
-    /// Panics if the mounted ID is None or if the VNode is not represented by a single Element.
-    pub fn mounted_id(&self) -> ElementId {
-        self.try_mounted_id().unwrap()
+    /// In fragments, this is the key of the first child. In other cases, it is the key of the root.
+    pub key: Option<&'a str>,
+
+    /// When rendered, this template will be linked to its parent manually
+    pub parent: Option<ElementId>,
+
+    /// The static nodes and static descriptor of the template
+    pub template: Template<'static>,
+
+    /// The IDs for the roots of this template - to be used when moving the template around and removing it from
+    /// the actual Dom
+    pub root_ids: &'a [Cell<ElementId>],
+
+    /// The dynamic parts of the template
+    pub dynamic_nodes: &'a [DynamicNode<'a>],
+
+    /// The dynamic parts of the template
+    pub dynamic_attrs: &'a [Attribute<'a>],
+}
+
+impl<'a> VNode<'a> {
+    /// Create a template with no nodes that will be skipped over during diffing
+    pub fn empty() -> Element<'a> {
+        Ok(VNode {
+            key: None,
+            parent: None,
+            root_ids: &[],
+            dynamic_nodes: &[],
+            dynamic_attrs: &[],
+            template: Template {
+                name: "dioxus-empty",
+                roots: &[],
+                node_paths: &[],
+                attr_paths: &[],
+            },
+        })
     }
 
-    /// Try to get the ElementID of the mounted VNode.
+    /// Load a dynamic root at the given index
     ///
-    /// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component)
-    pub fn try_mounted_id(&self) -> Option<ElementId> {
-        match &self {
-            VNode::Text(el) => el.id.get(),
-            VNode::Element(el) => el.id.get(),
-            VNode::Placeholder(el) => el.id.get(),
-            VNode::Fragment(_) => None,
-            VNode::Component(_) => None,
-            VNode::TemplateRef(_) => None,
+    /// Returns [`None`] if the root is actually a static node (Element/Text)
+    pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
+        match &self.template.roots[idx] {
+            TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
+            TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
+                Some(&self.dynamic_nodes[*id])
+            }
         }
     }
 
-    // Create an "owned" version of the vnode.
-    pub(crate) fn decouple(&self) -> VNode<'src> {
-        match *self {
-            VNode::Text(t) => VNode::Text(t),
-            VNode::Element(e) => VNode::Element(e),
-            VNode::Component(c) => VNode::Component(c),
-            VNode::Placeholder(a) => VNode::Placeholder(a),
-            VNode::Fragment(f) => VNode::Fragment(f),
-            VNode::TemplateRef(t) => VNode::TemplateRef(t),
-        }
-    }
-}
-
-impl Debug for VNode<'_> {
-    fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
-        match &self {
-            VNode::Element(el) => s
-                .debug_struct("VNode::Element")
-                .field("name", &el.tag)
-                .field("key", &el.key)
-                .field("attrs", &el.attributes)
-                .field("children", &el.children)
-                .field("id", &el.id)
-                .finish(),
-            VNode::Text(t) => s
-                .debug_struct("VNode::Text")
-                .field("text", &t.text)
-                .field("id", &t.id)
-                .finish(),
-            VNode::Placeholder(t) => s
-                .debug_struct("VNode::Placholder")
-                .field("id", &t.id)
-                .finish(),
-            VNode::Fragment(frag) => s
-                .debug_struct("VNode::Fragment")
-                .field("children", &frag.children)
-                .finish(),
-            VNode::Component(comp) => s
-                .debug_struct("VNode::Component")
-                .field("name", &comp.fn_name)
-                .field("fnptr", &comp.user_fc)
-                .field("key", &comp.key)
-                .field("scope", &comp.scope)
-                .finish(),
-            VNode::TemplateRef(temp) => s
-                .debug_struct("VNode::TemplateRef")
-                .field("template_id", &temp.template_id)
-                .finish(),
+    pub(crate) fn clear_listeners(&self) {
+        for attr in self.dynamic_attrs {
+            if let AttributeValue::Listener(l) = &attr.value {
+                l.borrow_mut().take();
+            }
         }
     }
 }
 
-/// An Element's unique identifier.
+/// A static layout of a UI tree that describes a set of dynamic and static nodes.
 ///
-/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
-/// unmounted, then the `ElementId` will be reused for a new component.
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(transparent))]
-pub struct ElementId(pub usize);
-impl std::fmt::Display for ElementId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
-impl ElementId {
-    /// Convert the ElementId to a `u64`.
-    pub fn as_u64(&self) -> u64 {
-        (*self).into()
-    }
-}
+/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
+/// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use
+/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
+///
+/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
+/// ways, with the suggested approach being the unique code location (file, line, col, etc).
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
+pub struct Template<'a> {
+    /// The name of the template. This must be unique across your entire program for template diffing to work properly
+    ///
+    /// If two templates have the same name, it's likely that Dioxus will panic when diffing.
+    pub name: &'a str,
 
-impl From<ElementId> for u64 {
-    fn from(el: ElementId) -> u64 {
-        el.0 as u64
-    }
-}
+    /// The list of template nodes that make up the template
+    ///
+    /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
+    pub roots: &'a [TemplateNode<'a>],
 
-fn empty_cell<T>() -> Cell<Option<T>> {
-    Cell::new(None)
-}
+    /// The paths of each node relative to the root of the template.
+    ///
+    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+    /// topmost element, not the `roots` field.
+    pub node_paths: &'a [&'a [u8]],
 
-/// A placeholder node only generated when Fragments don't have any children.
-pub struct VPlaceholder {
-    /// The [`ElementId`] of the placeholder.
-    pub id: Cell<Option<ElementId>>,
+    /// The paths of each dynamic attribute relative to the root of the template
+    ///
+    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+    /// topmost element, not the `roots` field.
+    pub attr_paths: &'a [&'a [u8]],
 }
 
-/// A bump-allocated string slice and metadata.
-pub struct VText<'src> {
-    /// The [`ElementId`] of the VText.
-    pub id: Cell<Option<ElementId>>,
-
-    /// The text of the VText.
-    pub text: &'src str,
+/// A statically known node in a layout.
+///
+/// This can be created at compile time, saving the VirtualDom time when diffing the tree
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
+pub enum TemplateNode<'a> {
+    /// An statically known element in the dom.
+    ///
+    /// In HTML this would be something like `<div id="123"> </div>`
+    Element {
+        /// The name of the element
+        ///
+        /// IE for a div, it would be the string "div"
+        tag: &'a str,
+
+        /// The namespace of the element
+        ///
+        /// In HTML, this would be a valid URI that defines a namespace for all elements below it
+        /// SVG is an example of this namespace
+        namespace: Option<&'a str>,
+
+        /// A list of possibly dynamic attribues for this element
+        ///
+        /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
+        attrs: &'a [TemplateAttribute<'a>],
+
+        /// A list of template nodes that define another set of template nodes
+        children: &'a [TemplateNode<'a>],
+    },
 
-    /// An indiciation if this VText can be ignored during diffing
-    /// Is usually only when there are no strings to be formatted (so the text is &'static str)
-    pub is_static: bool,
-}
+    /// This template node is just a piece of static text
+    Text {
+        /// The actual text
+        text: &'a str,
+    },
 
-/// A list of VNodes with no single root.
-pub struct VFragment<'src> {
-    /// The key of the fragment to be used during keyed diffing.
-    pub key: Option<&'src str>,
+    /// This template node is unknown, and needs to be created at runtime.
+    Dynamic {
+        /// The index of the dynamic node in the VNode's dynamic_nodes list
+        id: usize,
+    },
 
-    /// Fragments can never have zero children. Enforced by NodeFactory.
+    /// This template node is known to be some text, but needs to be created at runtime
     ///
-    /// You *can* make a fragment with no children, but it's not a valid fragment and your VDom will panic.
-    pub children: &'src [VNode<'src>],
+    /// This is separate from the pure Dynamic variant for various optimizations
+    DynamicText {
+        /// The index of the dynamic node in the VNode's dynamic_nodes list
+        id: usize,
+    },
 }
 
-/// An element like a "div" with children, listeners, and attributes.
-pub struct VElement<'a> {
-    /// The [`ElementId`] of the VText.
-    pub id: Cell<Option<ElementId>>,
+/// A node created at runtime
+///
+/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
+#[derive(Debug)]
+pub enum DynamicNode<'a> {
+    /// A component node
+    ///
+    /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
+    /// assigned scope are dynamic.
+    ///
+    /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
+    /// the render function at runtime
+    Component(VComponent<'a>),
 
-    /// The key of the element to be used during keyed diffing.
-    pub key: Option<&'a str>,
+    /// A text node
+    Text(VText<'a>),
 
-    /// The tag name of the element.
+    /// A placeholder
     ///
-    /// IE "div"
-    pub tag: &'static str,
-
-    /// The namespace of the VElement
+    /// Used by suspense when a node isn't ready and by fragments that don't render anything
     ///
-    /// IE "svg"
-    pub namespace: Option<&'static str>,
+    /// In code, this is just an ElementId whose initial value is set to 0 upon creation
+    Placeholder(Cell<ElementId>),
 
-    /// The parent of the Element (if any).
+    /// A list of VNodes.
     ///
-    /// Used when bubbling events
-    pub parent: Cell<Option<ElementId>>,
-
-    /// The Listeners of the VElement.
-    pub listeners: &'a [Listener<'a>],
-
-    /// The attributes of the VElement.
-    pub attributes: &'a [Attribute<'a>],
-
-    /// The children of the VElement.
-    pub children: &'a [VNode<'a>],
-}
-
-impl Debug for VElement<'_> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("VElement")
-            .field("tag_name", &self.tag)
-            .field("namespace", &self.namespace)
-            .field("key", &self.key)
-            .field("id", &self.id)
-            .field("parent", &self.parent)
-            .field("listeners", &self.listeners.len())
-            .field("attributes", &self.attributes)
-            .field("children", &self.children)
-            .finish()
-    }
+    /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
+    /// or iterators.
+    Fragment(&'a [VNode<'a>]),
 }
 
-/// A trait for any generic Dioxus Element.
-///
-/// This trait provides the ability to use custom elements in the `rsx!` macro.
-///
-/// ```rust, ignore
-/// struct my_element;
-///
-/// impl DioxusElement for my_element {
-///     const TAG_NAME: "my_element";
-///     const NAME_SPACE: None;
-/// }
-///
-/// let _ = rsx!{
-///     my_element {}
-/// };
-/// ```
-pub trait DioxusElement {
-    /// The tag name of the element.
-    const TAG_NAME: &'static str;
-
-    /// The namespace of the element.
-    const NAME_SPACE: Option<&'static str>;
-
-    /// The tag name of the element.
-    #[inline]
-    fn tag_name(&self) -> &'static str {
-        Self::TAG_NAME
-    }
-
-    /// The namespace of the element.
-    #[inline]
-    fn namespace(&self) -> Option<&'static str> {
-        Self::NAME_SPACE
+impl Default for DynamicNode<'_> {
+    fn default() -> Self {
+        Self::Placeholder(Default::default())
     }
 }
 
-type StaticStr = &'static str;
+/// An instance of a child component
+pub struct VComponent<'a> {
+    /// The name of this component
+    pub name: &'static str,
 
-/// A discription of the attribute
-#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Deserialize)
-)]
-pub struct AttributeDiscription {
-    /// The name of the attribute.
-    #[cfg_attr(
-        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-        serde(deserialize_with = "crate::util::deserialize_static_leaky")
-    )]
-    pub name: StaticStr,
-
-    /// The namespace of the attribute.
+    /// Are the props valid for the 'static lifetime?
     ///
-    /// Doesn't exist in the html spec.
-    /// Used in Dioxus to denote "style" tags and other attribute groups.
-    #[cfg_attr(
-        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-        serde(deserialize_with = "crate::util::deserialize_static_leaky_ns")
-    )]
-    pub namespace: Option<StaticStr>,
-
-    /// An indication of we should always try and set the attribute.
-    /// Used in controlled components to ensure changes are propagated.
-    pub volatile: bool,
-}
-
-/// An attribute on a DOM node, such as `id="my-thing"` or
-/// `href="https://example.com"`.
-#[derive(Clone, Debug)]
-pub struct Attribute<'a> {
-    /// The discription of the attribute.
-    pub attribute: AttributeDiscription,
-
-    /// An indication if this attribute can be ignored during diffing
+    /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it.
     ///
-    /// Usually only when there are no strings to be formatted (so the value is &'static str)
-    pub is_static: bool,
-
-    /// The value of the attribute.
-    pub value: AttributeValue<'a>,
-}
+    /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement
+    pub static_props: bool,
 
-/// An event listener.
-/// IE onclick, onkeydown, etc
-pub struct Listener<'bump> {
-    /// The ID of the node that this listener is mounted to
-    /// Used to generate the event listener's ID on the DOM
-    pub mounted_node: Cell<Option<ElementId>>,
+    /// The assigned Scope for this component
+    pub scope: Cell<Option<ScopeId>>,
 
-    /// The type of event to listen for.
+    /// The function pointer of the component, known at compile time
     ///
-    /// IE "click" - whatever the renderer needs to attach the listener by name.
-    pub event: &'static str,
+    /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
+    pub render_fn: *const (),
 
-    /// The actual callback that the user specified
-    pub(crate) callback: InternalHandler<'bump>,
+    pub(crate) props: Cell<Option<Box<dyn AnyProps<'a> + 'a>>>,
 }
 
-pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
-type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>;
-type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
-
-/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
-///
-/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
-///
-///
-/// # Example
-///
-/// ```rust, ignore
-///
-/// rsx!{
-///     MyComponent { onclick: move |evt| log::info!("clicked"), }
-/// }
-///
-/// #[derive(Props)]
-/// struct MyProps<'a> {
-///     onclick: EventHandler<'a, MouseEvent>,
-/// }
-///
-/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
-///     cx.render(rsx!{
-///         button {
-///             onclick: move |evt| cx.props.onclick.call(evt),
-///         }
-///     })
-/// }
-///
-/// ```
-pub struct EventHandler<'bump, T = ()> {
-    /// The (optional) callback that the user specified
-    /// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
-    pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
-}
-
-impl<'a, T> Default for EventHandler<'a, T> {
-    fn default() -> Self {
-        Self {
-            callback: RefCell::new(None),
-        }
+impl<'a> std::fmt::Debug for VComponent<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("VComponent")
+            .field("name", &self.name)
+            .field("static_props", &self.static_props)
+            .field("scope", &self.scope)
+            .finish()
     }
 }
 
-impl<T> EventHandler<'_, T> {
-    /// Call this event handler with the appropriate event type
-    pub fn call(&self, event: T) {
-        log::trace!("calling event handler");
-        if let Some(callback) = self.callback.borrow_mut().as_mut() {
-            callback(event);
-        }
-        log::trace!("done");
-    }
+/// An instance of some text, mounted to the DOM
+#[derive(Debug)]
+pub struct VText<'a> {
+    /// The actual text itself
+    pub value: &'a str,
 
-    /// Forcibly drop the internal handler callback, releasing memory
-    pub fn release(&self) {
-        self.callback.replace(None);
-    }
+    /// The ID of this node in the real DOM
+    pub id: Cell<ElementId>,
 }
 
-/// Virtual Components for custom user-defined components
-/// Only supports the functional syntax
-pub struct VComponent<'src> {
-    /// The key of the component to be used during keyed diffing.
-    pub key: Option<&'src str>,
-
-    /// The ID of the component.
-    /// Will not be assigned until after the component has been initialized.
-    pub scope: Cell<Option<ScopeId>>,
-
-    /// An indication if the component is static (can be memozied)
-    pub can_memoize: bool,
-
-    /// The function pointer to the component's render function.
-    pub user_fc: ComponentPtr,
-
-    /// The actual name of the component.
-    pub fn_name: &'static str,
+/// An attribute of the TemplateNode, created at compile time
+#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
+#[cfg_attr(
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
+)]
+pub enum TemplateAttribute<'a> {
+    /// This attribute is entirely known at compile time, enabling
+    Static {
+        /// The name of this attribute.
+        ///
+        /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
+        name: &'a str,
+
+        /// The value of this attribute, known at compile time
+        ///
+        /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
+        value: &'a str,
+
+        /// The namespace of this attribute. Does not exist in the HTML spec
+        namespace: Option<&'a str>,
+    },
 
-    /// The props of the component.
-    pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
+    /// The attribute in this position is actually determined dynamically at runtime
+    ///
+    /// This is the index into the dynamic_attributes field on the container VNode
+    Dynamic {
+        /// The index
+        id: usize,
+    },
 }
 
-pub(crate) struct VComponentProps<P> {
-    pub render_fn: Component<P>,
-    pub memo: unsafe fn(&P, &P) -> bool,
-    pub props: P,
-}
+/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
+#[derive(Debug)]
+pub struct Attribute<'a> {
+    /// The name of the attribute.
+    pub name: &'a str,
 
-pub trait AnyProps {
-    fn as_ptr(&self) -> *const ();
-    fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
-    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
-}
+    /// The value of the attribute
+    pub value: AttributeValue<'a>,
 
-impl<P> AnyProps for VComponentProps<P> {
-    fn as_ptr(&self) -> *const () {
-        &self.props as *const _ as *const ()
-    }
+    /// The namespace of the attribute.
+    ///
+    /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
+    pub namespace: Option<&'static str>,
 
-    // Safety:
-    // this will downcast the other ptr as our swallowed type!
-    // you *must* make this check *before* calling this method
-    // if your functions are not the same, then you will downcast a pointer into a different type (UB)
-    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
-        let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
-        let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
-        (self.memo)(real_us, real_other)
-    }
+    /// The element in the DOM that this attribute belongs to
+    pub mounted_element: Cell<ElementId>,
 
-    fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
-        let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) };
-        (self.render_fn)(Scope { scope, props })
-    }
+    /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
+    pub volatile: bool,
 }
 
-/// This struct provides an ergonomic API to quickly build VNodes.
+/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
 ///
-/// 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(crate) scope: &'a ScopeState,
-    pub(crate) bump: &'a Bump,
-}
-
-impl<'a> NodeFactory<'a> {
-    /// Create a new [`NodeFactory`] from a [`Scope`] or [`ScopeState`]
-    pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
-        NodeFactory {
-            scope,
-            bump: &scope.wip_frame().bump,
-        }
-    }
+/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
+/// variant.
+pub enum AttributeValue<'a> {
+    /// Text attribute
+    Text(&'a str),
 
-    /// Get the custom allocator for this component
-    #[inline]
-    pub fn bump(&self) -> &'a bumpalo::Bump {
-        self.bump
-    }
+    /// A float
+    Float(f64),
 
-    /// Directly pass in text blocks without the need to use the format_args macro.
-    pub fn static_text(&self, text: &'static str) -> VNode<'a> {
-        VNode::Text(self.bump.alloc(VText {
-            id: empty_cell(),
-            text,
-            is_static: true,
-        }))
-    }
+    /// Signed integer
+    Int(i64),
 
-    /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
-    ///
-    /// Text that's static may be pointer compared, making it cheaper to diff
-    pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
-        match args.as_str() {
-            Some(static_str) => (static_str, true),
-            None => {
-                use bumpalo::core_alloc::fmt::Write;
-                let mut str_buf = bumpalo::collections::String::new_in(self.bump);
-                str_buf.write_fmt(args).unwrap();
-                (str_buf.into_bump_str(), false)
-            }
-        }
-    }
+    /// Boolean
+    Bool(bool),
 
-    /// Create some text that's allocated along with the other vnodes
-    ///
-    pub fn text(&self, args: Arguments) -> VNode<'a> {
-        let (text, is_static) = self.raw_text(args);
-
-        VNode::Text(self.bump.alloc(VText {
-            text,
-            is_static,
-            id: empty_cell(),
-        }))
-    }
+    /// A listener, like "onclick"
+    Listener(RefCell<Option<ListenerCb<'a>>>),
 
-    /// Create a new [`VNode::Element`]
-    pub fn element(
-        &self,
-        el: impl DioxusElement,
-        listeners: &'a [Listener<'a>],
-        attributes: &'a [Attribute<'a>],
-        children: &'a [VNode<'a>],
-        key: Option<Arguments>,
-    ) -> VNode<'a> {
-        self.raw_element(
-            el.tag_name(),
-            el.namespace(),
-            listeners,
-            attributes,
-            children,
-            key,
-        )
-    }
+    /// An arbitrary value that implements PartialEq and is static
+    Any(BumpBox<'a, dyn AnyValue>),
 
-    /// Create a new [`VNode::Element`] without the trait bound
-    ///
-    /// IE pass in "div" instead of `div`
-    pub fn raw_element(
-        &self,
-        tag_name: &'static str,
-        namespace: Option<&'static str>,
-        listeners: &'a [Listener<'a>],
-        attributes: &'a [Attribute<'a>],
-        children: &'a [VNode<'a>],
-        key: Option<Arguments>,
-    ) -> VNode<'a> {
-        let key = key.map(|f| self.raw_text(f).0);
-
-        let mut items = self.scope.items.borrow_mut();
-        for listener in listeners {
-            let long_listener = unsafe { std::mem::transmute(listener) };
-            items.listeners.push(long_listener);
-        }
+    /// A "none" value, resulting in the removal of an attribute from the dom
+    None,
+}
 
-        VNode::Element(self.bump.alloc(VElement {
-            tag: tag_name,
-            key,
-            namespace,
-            listeners,
-            attributes,
-            children,
-            id: empty_cell(),
-            parent: empty_cell(),
-        }))
-    }
+type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
 
-    /// Create a new [`Attribute`] from a attribute discrimination and a value
-    pub fn attr_disciption(
-        &self,
-        discription: AttributeDiscription,
-        val: impl IntoAttributeValue<'a>,
-    ) -> Attribute<'a> {
-        Attribute {
-            attribute: discription,
-            is_static: false,
-            value: val.into_value(self.bump),
+impl<'a> std::fmt::Debug for AttributeValue<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
+            Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
+            Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
+            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
+            Self::Listener(_) => f.debug_tuple("Listener").finish(),
+            Self::Any(_) => f.debug_tuple("Any").finish(),
+            Self::None => write!(f, "None"),
         }
     }
+}
 
-    /// Create a new [`Attribute`]
-    pub fn attr(
-        &self,
-        name: &'static str,
-        val: impl IntoAttributeValue<'a>,
-        namespace: Option<&'static str>,
-        is_volatile: bool,
-    ) -> Attribute<'a> {
-        Attribute {
-            attribute: AttributeDiscription {
-                name,
-                namespace,
-                volatile: is_volatile,
-            },
-            is_static: false,
-            value: val.into_value(self.bump),
+impl<'a> PartialEq for AttributeValue<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self::Text(l0), Self::Text(r0)) => l0 == r0,
+            (Self::Float(l0), Self::Float(r0)) => l0 == r0,
+            (Self::Int(l0), Self::Int(r0)) => l0 == r0,
+            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
+            (Self::Listener(_), Self::Listener(_)) => true,
+            (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
+            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
         }
     }
+}
 
-    /// Create a new [`Attribute`] using non-arguments
-    pub fn custom_attr(
-        &self,
-        name: &'static str,
-        value: AttributeValue<'a>,
-        namespace: Option<&'static str>,
-        is_volatile: bool,
-        is_static: bool,
-    ) -> Attribute<'a> {
-        Attribute {
-            attribute: AttributeDiscription {
-                name,
-                namespace,
-                volatile: is_volatile,
-            },
-            is_static,
-            value,
-        }
-    }
+#[doc(hidden)]
+pub trait AnyValue {
+    fn any_cmp(&self, other: &dyn AnyValue) -> bool;
+    fn our_typeid(&self) -> TypeId;
+}
 
-    /// Create a new [`VNode::Component`]
-    pub fn component<P>(
-        &self,
-        component: fn(Scope<'a, P>) -> Element,
-        props: P,
-        key: Option<Arguments>,
-        fn_name: &'static str,
-    ) -> VNode<'a>
-    where
-        P: Properties + 'a,
-    {
-        let vcomp = self.bump.alloc(VComponent {
-            key: key.map(|f| self.raw_text(f).0),
-            scope: Default::default(),
-            can_memoize: P::IS_STATIC,
-            user_fc: component as ComponentPtr,
-            fn_name,
-            props: RefCell::new(Some(Box::new(VComponentProps {
-                props,
-                memo: P::memoize, // smuggle the memoization function across borders
-
-                // i'm sorry but I just need to bludgeon the lifetimes into place here
-                // this is safe because we're managing all lifetimes to originate from previous calls
-                // the intricacies of Rust's lifetime system make it difficult to properly express
-                // the transformation from this specific lifetime to the for<'a> lifetime
-                render_fn: unsafe { std::mem::transmute(component) },
-            }))),
-        });
-
-        if !P::IS_STATIC {
-            let vcomp = &*vcomp;
-            let vcomp = unsafe { std::mem::transmute(vcomp) };
-            self.scope.items.borrow_mut().borrowed_props.push(vcomp);
+impl<T: PartialEq + Any> AnyValue for T {
+    fn any_cmp(&self, other: &dyn AnyValue) -> bool {
+        if self.type_id() != other.our_typeid() {
+            return false;
         }
 
-        VNode::Component(vcomp)
+        self == unsafe { &*(other as *const _ as *const T) }
     }
 
-    /// Create a new [`Listener`]
-    pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
-        Listener {
-            event,
-            mounted_node: Cell::new(None),
-            callback,
-        }
+    fn our_typeid(&self) -> TypeId {
+        self.type_id()
     }
+}
 
-    /// Create a new [`VNode::Fragment`] from a root of the rsx! call
-    pub fn fragment_root<'b, 'c>(
-        self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
-    ) -> VNode<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
-
-        for node in node_iter {
-            nodes.push(node.into_vnode(self));
-        }
+#[doc(hidden)]
+pub trait ComponentReturn<'a, A = ()> {
+    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
+}
 
-        if nodes.is_empty() {
-            VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
-        } else {
-            VNode::Fragment(self.bump.alloc(VFragment {
-                children: nodes.into_bump_slice(),
-                key: None,
-            }))
-        }
+impl<'a> ComponentReturn<'a> for Element<'a> {
+    fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
+        RenderReturn::Sync(self)
     }
+}
 
-    /// Create a new [`VNode::Fragment`] from any iterator
-    pub fn fragment_from_iter<'c, I, J>(
-        self,
-        node_iter: impl IntoVNode<'a, I, J> + 'c,
-    ) -> VNode<'a> {
-        node_iter.into_vnode(self)
+#[doc(hidden)]
+pub struct AsyncMarker;
+impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
+where
+    F: Future<Output = Element<'a>> + 'a,
+{
+    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
+        let f: &mut dyn Future<Output = Element<'a>> = cx.bump().alloc(self);
+        RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
     }
+}
 
-    /// Create a new [`VNode`] from any iterator of children
-    pub fn create_children(
-        self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
-    ) -> Element<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
+impl<'a> RenderReturn<'a> {
+    pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
+        unsafe { std::mem::transmute(self) }
+    }
+    pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
+        unsafe { std::mem::transmute(self) }
+    }
+}
 
-        for node in node_iter {
-            nodes.push(node.into_vnode(self));
-        }
+/// A trait that allows various items to be converted into a dynamic node for the rsx macro
+pub trait IntoDynNode<'a, A = ()> {
+    /// Consume this item along with a scopestate and produce a DynamicNode
+    ///
+    /// You can use the bump alloactor of the scopestate to creat the dynamic node
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
+}
 
-        if nodes.is_empty() {
-            Some(VNode::Placeholder(
-                self.bump.alloc(VPlaceholder { id: empty_cell() }),
-            ))
-        } else {
-            let children = nodes.into_bump_slice();
-
-            Some(VNode::Fragment(self.bump.alloc(VFragment {
-                children,
-                key: None,
-            })))
-        }
+impl<'a> IntoDynNode<'a> for () {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::default()
     }
-
-    /// Create a new [`EventHandler`] from an [`FnMut`]
-    pub fn event_handler<T>(self, f: impl FnMut(T) + 'a) -> EventHandler<'a, T> {
-        let handler: &mut dyn FnMut(T) = self.bump.alloc(f);
-        let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
-        let callback = RefCell::new(Some(caller));
-        EventHandler { callback }
+}
+impl<'a> IntoDynNode<'a> for VNode<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(_cx.bump().alloc([self]))
     }
+}
 
-    /// Create a refrence to a template
-    pub fn template_ref(
-        &self,
-        id: TemplateId,
-        template: Template,
-        dynamic_context: TemplateContext<'a>,
-    ) -> VNode<'a> {
-        let borrow_ref = self.scope.templates.borrow();
-        // We only create the template if it doesn't already exist to allow for hot reloading
-        if !borrow_ref.contains_key(&id) {
-            drop(borrow_ref);
-            let mut borrow_mut = self.scope.templates.borrow_mut();
-            borrow_mut.insert(id.clone(), Rc::new(RefCell::new(template)));
+// An element that's an error is currently lost into the ether
+impl<'a> IntoDynNode<'a> for Element<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self {
+            Ok(val) => val.into_vnode(_cx),
+            _ => DynamicNode::default(),
         }
-        VNode::TemplateRef(self.bump.alloc(VTemplateRef {
-            dynamic_context,
-            template_id: id,
-            node_ids: RefCell::new(Vec::new()),
-            parent: Cell::new(None),
-            template_ref_id: Cell::new(None),
-        }))
     }
 }
 
-impl Debug for NodeFactory<'_> {
-    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        Ok(())
+impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self {
+            Some(val) => val.into_vnode(_cx),
+            None => DynamicNode::default(),
+        }
     }
 }
 
-/// Trait implementations for use in the rsx! and html! macros.
-///
-/// ## Details
-///
-/// This section provides convenience methods and trait implementations for converting common structs into a format accepted
-/// by the macros.
-///
-/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
-/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
-/// ```rust, ignore
-/// impl IntoIterator<Item = impl IntoVNode<'a>>
-/// ```
-///
-/// As such, all node creation must go through the factory, which is only available in the component context.
-/// These strict requirements make it possible to manage lifetimes and state.
-pub trait IntoVNode<'a, I = (), J = ()> {
-    /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
+impl<'a> IntoDynNode<'a> for &Element<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self.as_ref() {
+            Ok(val) => val.clone().into_vnode(_cx),
+            _ => DynamicNode::default(),
+        }
+    }
 }
 
-// TODO: do we even need this? It almost seems better not to
-// // For the case where a rendered VNode is passed into the rsx! macro through curly braces
-impl<'a> IntoVNode<'a> for VNode<'a> {
-    fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
-        self
+impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
     }
 }
 
-// Conveniently, we also support "null" (nothing) passed in
-impl IntoVNode<'_> for () {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
+impl<'a> IntoDynNode<'_> for &'a str {
+    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+        cx.text_node(format_args!("{}", self))
     }
 }
 
-impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        self.call(cx)
+impl IntoDynNode<'_> for String {
+    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+        cx.text_node(format_args!("{}", self))
     }
 }
 
-impl<'b> IntoVNode<'_> for &'b str {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.text(format_args!("{}", self))
+impl<'b> IntoDynNode<'b> for Arguments<'_> {
+    fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
+        cx.text_node(self)
     }
 }
 
-impl IntoVNode<'_> for String {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.text(format_args!("{}", self))
+impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(_cx.bump().alloc([VNode {
+            parent: self.parent,
+            template: self.template,
+            root_ids: self.root_ids,
+            key: self.key,
+            dynamic_nodes: self.dynamic_nodes,
+            dynamic_attrs: self.dynamic_attrs,
+        }]))
     }
 }
 
-impl IntoVNode<'_> for Arguments<'_> {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.text(self)
+pub trait IntoTemplate<'a> {
+    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
+}
+impl<'a> IntoTemplate<'a> for VNode<'a> {
+    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+        self
     }
 }
-
-impl<'a> IntoVNode<'a> for &VNode<'a> {
-    fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
-        // borrowed nodes are strange
-        self.decouple()
+impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
+    fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
+        self.call(cx)
     }
 }
 
 // Note that we're using the E as a generic but this is never crafted anyways.
+#[doc(hidden)]
 pub struct FromNodeIterator;
-impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
+impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
 where
-    T: IntoIterator<Item = I>,
-    I: IntoVNode<'a, E>,
+    T: Iterator<Item = I>,
+    I: IntoTemplate<'a>,
 {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump);
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
+
+        nodes.extend(self.into_iter().map(|node| node.into_template(cx)));
 
-        for node in self {
-            nodes.push(node.into_vnode(cx));
+        match nodes.into_bump_slice() {
+            children if children.is_empty() => DynamicNode::default(),
+            children => DynamicNode::Fragment(children),
         }
+    }
+}
 
-        if nodes.is_empty() {
-            VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
-        } else {
-            let children = nodes.into_bump_slice();
-
-            if cfg!(debug_assertions)
-                && children.len() > 1
-                && children.last().unwrap().key().is_none()
-            {
-                // todo: make the backtrace prettier or remove it altogether
-                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.
-                -------------
-                {:?}
-                "#,
-                    backtrace::Backtrace::new()
-                );
-            }
+/// A value that can be converted into an attribute value
+pub trait IntoAttributeValue<'a> {
+    /// Convert into an attribute value
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
+}
 
-            VNode::Fragment(cx.bump.alloc(VFragment {
-                children,
-                key: None,
-            }))
-        }
+impl<'a> IntoAttributeValue<'a> for &'a str {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Text(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for f64 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Float(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for i64 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Int(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for bool {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Bool(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
+        use bumpalo::core_alloc::fmt::Write;
+        let mut str_buf = bumpalo::collections::String::new_in(bump);
+        str_buf.write_fmt(self).unwrap();
+        AttributeValue::Text(str_buf.into_bump_str())
     }
 }

+ 1 - 96
packages/core/src/properties.rs

@@ -1,100 +1,5 @@
 use crate::innerlude::*;
 
-pub struct FragmentProps<'a>(Element<'a>);
-pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
-impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
-        FragmentBuilder(children)
-    }
-}
-impl<'a, const A: bool> FragmentBuilder<'a, A> {
-    pub fn build(self) -> FragmentProps<'a> {
-        FragmentProps(self.0)
-    }
-}
-
-/// Access the children elements passed into the component
-///
-/// This enables patterns where a component is passed children from its parent.
-///
-/// ## Details
-///
-/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
-/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
-/// on the props that takes Context.
-///
-/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
-/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
-/// props are valid for the static lifetime.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     cx.render(rsx!{
-///         CustomCard {
-///             h1 {}
-///             p {}
-///         }
-///     })
-/// }
-///
-/// #[derive(PartialEq, Props)]
-/// struct CardProps {
-///     children: Element
-/// }
-///
-/// fn CustomCard(cx: Scope<CardProps>) -> Element {
-///     cx.render(rsx!{
-///         div {
-///             h1 {"Title card"}
-///             {cx.props.children}
-///         }
-///     })
-/// }
-/// ```
-impl<'a> Properties for FragmentProps<'a> {
-    type Builder = FragmentBuilder<'a, false>;
-    const IS_STATIC: bool = false;
-    fn builder() -> Self::Builder {
-        FragmentBuilder(None)
-    }
-    unsafe fn memoize(&self, _other: &Self) -> bool {
-        false
-    }
-}
-
-/// Create inline fragments using Component syntax.
-///
-/// ## Details
-///
-/// Fragments capture a series of children without rendering extra nodes.
-///
-/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
-/// a key is needed to identify each item.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// rsx!{
-///     Fragment { key: "abc" }
-/// }
-/// ```
-///
-/// ## Usage
-///
-/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
-/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
-///
-/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
-///
-/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
-#[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
-    let i = cx.props.0.as_ref().map(|f| f.decouple());
-    cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
-}
-
 /// 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.
@@ -165,6 +70,6 @@ impl EmptyBuilder {
 
 /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
 /// to initialize a component's props.
-pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
+pub fn fc_to_builder<'a, A, T: Properties + 'a>(_: fn(Scope<'a, T>) -> A) -> T::Builder {
     T::builder()
 }

+ 48 - 0
packages/core/src/scheduler/mod.rs

@@ -0,0 +1,48 @@
+use crate::ScopeId;
+use slab::Slab;
+
+mod suspense;
+mod task;
+mod wait;
+mod waker;
+
+pub use suspense::*;
+pub use task::*;
+pub use waker::RcWake;
+
+/// The type of message that can be sent to the scheduler.
+///
+/// These messages control how the scheduler will process updates to the UI.
+#[derive(Debug)]
+pub(crate) enum SchedulerMsg {
+    /// Immediate updates from Components that mark them as dirty
+    Immediate(ScopeId),
+
+    /// A task has woken and needs to be progressed
+    TaskNotified(TaskId),
+
+    /// A task has woken and needs to be progressed
+    SuspenseNotified(SuspenseId),
+}
+
+use std::{cell::RefCell, rc::Rc};
+
+pub(crate) struct Scheduler {
+    pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+
+    /// Tasks created with cx.spawn
+    pub tasks: RefCell<Slab<Rc<LocalTask>>>,
+
+    /// Async components
+    pub leaves: RefCell<Slab<Rc<SuspenseLeaf>>>,
+}
+
+impl Scheduler {
+    pub fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
+        Rc::new(Scheduler {
+            sender,
+            tasks: RefCell::new(Slab::new()),
+            leaves: RefCell::new(Slab::new()),
+        })
+    }
+}

+ 52 - 0
packages/core/src/scheduler/suspense.rs

@@ -0,0 +1,52 @@
+use super::{waker::RcWake, SchedulerMsg};
+use crate::ElementId;
+use crate::{innerlude::Mutations, Element, ScopeId};
+use std::future::Future;
+use std::{
+    cell::{Cell, RefCell},
+    collections::HashSet,
+    rc::Rc,
+};
+
+/// An ID representing an ongoing suspended component
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub(crate) struct SuspenseId(pub usize);
+
+/// A boundary in the VirtualDom that captures all suspended components below it
+pub struct SuspenseContext {
+    pub(crate) id: ScopeId,
+    pub(crate) waiting_on: RefCell<HashSet<SuspenseId>>,
+    pub(crate) mutations: RefCell<Mutations<'static>>,
+    pub(crate) placeholder: Cell<Option<ElementId>>,
+    pub(crate) created_on_stack: Cell<usize>,
+}
+
+impl SuspenseContext {
+    /// Create a new boundary for suspense
+    pub fn new(id: ScopeId) -> Self {
+        Self {
+            id,
+            waiting_on: Default::default(),
+            mutations: RefCell::new(Mutations::default()),
+            placeholder: Cell::new(None),
+            created_on_stack: Cell::new(0),
+        }
+    }
+}
+
+pub(crate) struct SuspenseLeaf {
+    pub(crate) id: SuspenseId,
+    pub(crate) scope_id: ScopeId,
+    pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+    pub(crate) notified: Cell<bool>,
+    pub(crate) task: *mut dyn Future<Output = Element<'static>>,
+}
+
+impl RcWake for SuspenseLeaf {
+    fn wake_by_ref(arc_self: &Rc<Self>) {
+        arc_self.notified.set(true);
+        _ = arc_self
+            .tx
+            .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id));
+    }
+}

+ 66 - 0
packages/core/src/scheduler/task.rs

@@ -0,0 +1,66 @@
+use super::{waker::RcWake, Scheduler, SchedulerMsg};
+use crate::ScopeId;
+use std::cell::RefCell;
+use std::future::Future;
+use std::{pin::Pin, rc::Rc};
+
+/// A task's unique identifier.
+///
+/// `TaskId` is a `usize` that is unique across the entire VirtualDOM and across time. TaskIDs will never be reused
+/// once a Task has been completed.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct TaskId(pub usize);
+
+/// the task itself is the waker
+pub(crate) struct LocalTask {
+    pub scope: ScopeId,
+    pub(super) task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
+    id: TaskId,
+    tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+}
+
+impl Scheduler {
+    /// Start a new future on the same thread as the rest of the VirtualDom.
+    ///
+    /// This future will not contribute to suspense resolving, so you should primarily use this for reacting to changes
+    /// and long running tasks.
+    ///
+    /// Whenever the component that owns this future is dropped, the future will be dropped as well.
+    ///
+    /// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which
+    /// will only occur when the VirtuaalDom itself has been dropped.
+    pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
+        let mut tasks = self.tasks.borrow_mut();
+        let entry = tasks.vacant_entry();
+        let task_id = TaskId(entry.key());
+
+        entry.insert(Rc::new(LocalTask {
+            id: task_id,
+            tx: self.sender.clone(),
+            task: RefCell::new(Box::pin(task)),
+            scope,
+        }));
+
+        self.sender
+            .unbounded_send(SchedulerMsg::TaskNotified(task_id))
+            .expect("Scheduler should exist");
+
+        task_id
+    }
+
+    /// Drop the future with the given TaskId
+    ///
+    /// This does nto abort the task, so you'll want to wrap it in an aborthandle if that's important to you
+    pub fn remove(&self, id: TaskId) {
+        self.tasks.borrow_mut().remove(id.0);
+    }
+}
+
+impl RcWake for LocalTask {
+    fn wake_by_ref(arc_self: &Rc<Self>) {
+        _ = arc_self
+            .tx
+            .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
+    }
+}

+ 106 - 0
packages/core/src/scheduler/wait.rs

@@ -0,0 +1,106 @@
+use futures_util::FutureExt;
+use std::{
+    rc::Rc,
+    task::{Context, Poll},
+};
+
+use crate::{
+    innerlude::{Mutation, Mutations, SuspenseContext},
+    nodes::RenderReturn,
+    ScopeId, TaskId, VNode, VirtualDom,
+};
+
+use super::{waker::RcWake, SuspenseId};
+
+impl VirtualDom {
+    /// Handle notifications by tasks inside the scheduler
+    ///
+    /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
+    /// queue
+    pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
+        let mut tasks = self.scheduler.tasks.borrow_mut();
+        let task = &tasks[id.0];
+
+        let waker = task.waker();
+        let mut cx = Context::from_waker(&waker);
+
+        // If the task completes...
+        if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
+            // Remove it from the scope so we dont try to double drop it when the scope dropes
+            self.scopes[task.scope.0].spawned_tasks.remove(&id);
+
+            // Remove it from the scheduler
+            tasks.remove(id.0);
+        }
+    }
+
+    pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc<SuspenseContext> {
+        self.scopes[id.0]
+            .consume_context::<Rc<SuspenseContext>>()
+            .unwrap()
+    }
+
+    pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
+        let leaf = self
+            .scheduler
+            .leaves
+            .borrow_mut()
+            .get(id.0)
+            .unwrap()
+            .clone();
+
+        let scope_id = leaf.scope_id;
+
+        // todo: cache the waker
+        let waker = leaf.waker();
+        let mut cx = Context::from_waker(&waker);
+
+        // Safety: the future is always pinned to the bump arena
+        let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
+        let as_pinned_mut = &mut pinned;
+
+        // the component finished rendering and gave us nodes
+        // we should attach them to that component and then render its children
+        // continue rendering the tree until we hit yet another suspended component
+        if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
+            // safety: we're not going to modify the suspense context but we don't want to make a clone of it
+            let fiber = self.acquire_suspense_boundary(leaf.scope_id);
+
+            let scope = &mut self.scopes[scope_id.0];
+            let arena = scope.current_frame();
+
+            let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
+            arena.node.set(ret);
+
+            fiber.waiting_on.borrow_mut().remove(&id);
+
+            if let RenderReturn::Sync(Ok(template)) = ret {
+                let mutations_ref = &mut fiber.mutations.borrow_mut();
+                let mutations = &mut **mutations_ref;
+                let template: &VNode = unsafe { std::mem::transmute(template) };
+                let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) };
+
+                std::mem::swap(&mut self.mutations, mutations);
+
+                let place_holder_id = scope.placeholder.get().unwrap();
+                self.scope_stack.push(scope_id);
+                let created = self.create(template);
+                self.scope_stack.pop();
+                mutations.push(Mutation::ReplaceWith {
+                    id: place_holder_id,
+                    m: created,
+                });
+
+                for leaf in self.collected_leaves.drain(..) {
+                    fiber.waiting_on.borrow_mut().insert(leaf);
+                }
+
+                std::mem::swap(&mut self.mutations, mutations);
+
+                if fiber.waiting_on.borrow().is_empty() {
+                    self.finished_fibers.push(fiber.id);
+                }
+            }
+        }
+    }
+}

+ 36 - 0
packages/core/src/scheduler/waker.rs

@@ -0,0 +1,36 @@
+use std::task::{RawWaker, RawWakerVTable, Waker};
+use std::{mem, rc::Rc};
+
+pub trait RcWake: Sized {
+    /// Create a waker from this self-wakening object
+    fn waker(self: &Rc<Self>) -> Waker {
+        unsafe fn rc_vtable<T: RcWake>() -> &'static RawWakerVTable {
+            &RawWakerVTable::new(
+                |data| {
+                    let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
+                    let _rc_clone: mem::ManuallyDrop<_> = arc.clone();
+                    RawWaker::new(data, rc_vtable::<T>())
+                },
+                |data| Rc::from_raw(data.cast::<T>()).wake(),
+                |data| {
+                    let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
+                    RcWake::wake_by_ref(&arc);
+                },
+                |data| drop(Rc::<T>::from_raw(data.cast::<T>())),
+            )
+        }
+
+        unsafe {
+            Waker::from_raw(RawWaker::new(
+                Rc::into_raw(self.clone()).cast(),
+                rc_vtable::<Self>(),
+            ))
+        }
+    }
+
+    fn wake_by_ref(arc_self: &Rc<Self>);
+
+    fn wake(self: Rc<Self>) {
+        Self::wake_by_ref(&self)
+    }
+}

+ 146 - 0
packages/core/src/scope_arena.rs

@@ -0,0 +1,146 @@
+use crate::{
+    any_props::AnyProps,
+    bump_frame::BumpFrame,
+    innerlude::DirtyScope,
+    innerlude::{SuspenseId, SuspenseLeaf},
+    nodes::RenderReturn,
+    scheduler::RcWake,
+    scopes::{ScopeId, ScopeState},
+    virtual_dom::VirtualDom,
+};
+use futures_util::FutureExt;
+use std::{
+    mem,
+    pin::Pin,
+    rc::Rc,
+    task::{Context, Poll},
+};
+
+impl VirtualDom {
+    pub(super) fn new_scope(
+        &mut self,
+        props: Box<dyn AnyProps<'static>>,
+        name: &'static str,
+    ) -> &ScopeState {
+        let parent = self.acquire_current_scope_raw();
+        let entry = self.scopes.vacant_entry();
+        let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
+        let id = ScopeId(entry.key());
+
+        entry.insert(Box::new(ScopeState {
+            parent,
+            id,
+            height,
+            props: Some(props),
+            name,
+            placeholder: Default::default(),
+            node_arena_1: BumpFrame::new(50),
+            node_arena_2: BumpFrame::new(50),
+            spawned_tasks: Default::default(),
+            render_cnt: Default::default(),
+            hook_arena: Default::default(),
+            hook_list: Default::default(),
+            hook_idx: Default::default(),
+            shared_contexts: Default::default(),
+            tasks: self.scheduler.clone(),
+        }))
+    }
+
+    fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
+        self.scope_stack
+            .last()
+            .copied()
+            .and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
+    }
+
+    pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
+        // Cycle to the next frame and then reset it
+        // This breaks any latent references, invalidating every pointer referencing into it.
+        // Remove all the outdated listeners
+        self.ensure_drop_safety(scope_id);
+
+        let mut new_nodes = unsafe {
+            let scope = self.scopes[scope_id.0].as_mut();
+
+            scope.previous_frame_mut().bump.reset();
+
+            // Make sure to reset the hook counter so we give out hooks in the right order
+            scope.hook_idx.set(0);
+
+            // safety: due to how we traverse the tree, we know that the scope is not currently aliased
+            let props = scope.props.as_ref().unwrap().as_ref();
+            let props: &dyn AnyProps = mem::transmute(props);
+            props.render(scope).extend_lifetime()
+        };
+
+        // immediately resolve futures that can be resolved
+        if let RenderReturn::Async(task) = &mut new_nodes {
+            let mut leaves = self.scheduler.leaves.borrow_mut();
+
+            let entry = leaves.vacant_entry();
+            let suspense_id = SuspenseId(entry.key());
+
+            let leaf = Rc::new(SuspenseLeaf {
+                scope_id,
+                task: task.as_mut(),
+                id: suspense_id,
+                tx: self.scheduler.sender.clone(),
+                notified: Default::default(),
+            });
+
+            let waker = leaf.waker();
+            let mut cx = Context::from_waker(&waker);
+
+            // safety: the task is already pinned in the bump arena
+            let mut pinned = unsafe { Pin::new_unchecked(task.as_mut()) };
+
+            // Keep polling until either we get a value or the future is not ready
+            loop {
+                match pinned.poll_unpin(&mut cx) {
+                    // If nodes are produced, then set it and we can break
+                    Poll::Ready(nodes) => {
+                        new_nodes = RenderReturn::Sync(nodes);
+                        break;
+                    }
+
+                    // If no nodes are produced but the future woke up immediately, then try polling it again
+                    // This circumvents things like yield_now, but is important is important when rendering
+                    // components that are just a stream of immediately ready futures
+                    _ if leaf.notified.get() => {
+                        leaf.notified.set(false);
+                        continue;
+                    }
+
+                    // If no nodes are produced, then we need to wait for the future to be woken up
+                    // Insert the future into fiber leaves and break
+                    _ => {
+                        entry.insert(leaf);
+                        self.collected_leaves.push(suspense_id);
+                        break;
+                    }
+                };
+            }
+        };
+
+        let scope = &self.scopes[scope_id.0];
+
+        // We write on top of the previous frame and then make it the current by pushing the generation forward
+        let frame = scope.previous_frame();
+
+        // set the new head of the bump frame
+        let alloced = &*frame.bump.alloc(new_nodes);
+        frame.node.set(alloced);
+
+        // And move the render generation forward by one
+        scope.render_cnt.set(scope.render_cnt.get() + 1);
+
+        // remove this scope from dirty scopes
+        self.dirty_scopes.remove(&DirtyScope {
+            height: scope.height,
+            id: scope.id,
+        });
+
+        // rebind the lifetime now that its stored internally
+        unsafe { mem::transmute(alloced) }
+    }
+}

+ 302 - 881
packages/core/src/scopes.rs

@@ -1,645 +1,171 @@
-use crate::{innerlude::*, template::TemplateNodeId, unsafe_utils::extend_vnode};
-use bumpalo::Bump;
-use futures_channel::mpsc::UnboundedSender;
-use rustc_hash::FxHashMap;
-use slab::Slab;
+use crate::{
+    any_props::AnyProps,
+    any_props::VProps,
+    arena::ElementId,
+    bump_frame::BumpFrame,
+    innerlude::{DynamicNode, EventHandler, VComponent, VText},
+    innerlude::{Scheduler, SchedulerMsg},
+    lazynodes::LazyNodes,
+    nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
+    Attribute, AttributeValue, Element, Event, Properties, TaskId,
+};
+use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
     collections::{HashMap, HashSet},
+    fmt::Arguments,
     future::Future,
-    pin::Pin,
     rc::Rc,
     sync::Arc,
 };
 
-pub(crate) enum NodePtr {
-    VNode(*const VNode<'static>),
-    TemplateNode {
-        template_ref: TemplateRefId,
-        node_id: TemplateNodeId,
-    },
-    Phantom,
-}
-
-pub(crate) type NodeSlab = Slab<NodePtr>;
-
-/// for traceability, we use the raw fn pointer to identify the function
-/// we also get the component name, but that's not necessarily unique in the app
-pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
-
-pub(crate) struct Heuristic {
-    hook_arena_size: usize,
-    node_arena_size: usize,
-}
+/// A wrapper around the [`Scoped`] object that contains a reference to the [`ScopeState`] and properties for a given
+/// component.
+///
+/// The [`Scope`] is your handle to the [`crate::VirtualDom`] and the component state. Every component is given its own
+/// [`ScopeState`] and merged with its properties to create a [`Scoped`].
+///
+/// The [`Scope`] handle specifically exists to provide a stable reference to these items for the lifetime of the
+/// component render.
+pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
 
-// a slab-like arena with stable references even when new scopes are allocated
-// uses a bump arena as a backing
+// This ScopedType exists because we want to limit the amount of monomorphization that occurs when making inner
+// state type generic over props. When the state is generic, it causes every method to be monomorphized for every
+// instance of Scope<T> in the codebase.
 //
-// has an internal heuristics engine to pre-allocate arenas to the right size
-pub(crate) struct ScopeArena {
-    pub scope_gen: Cell<usize>,
-    pub bump: Bump,
-    pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
-    pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
-    pub free_scopes: RefCell<Vec<*mut ScopeState>>,
-    // All nodes are stored here. This mimics the allocations needed on the renderer side.
-    // Some allocations are needed on the renderer to render nodes in templates that are
-    // not needed in dioxus, for these allocations None is used so that the id is preseved and passive memory managment is preserved.
-    pub nodes: RefCell<NodeSlab>,
-    pub tasks: Rc<TaskQueue>,
-    pub template_resolver: RefCell<TemplateResolver>,
-    pub templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>,
-    // this is used to store intermidiate artifacts of creating templates, so that the lifetime aligns with Mutations<'bump>.
-    pub template_bump: Bump,
-    pub template_refs: RefCell<Slab<*const VTemplateRef<'static>>>,
-}
-
-impl ScopeArena {
-    pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
-        let bump = Bump::new();
-
-        // allocate a container for the root element
-        // this will *never* show up in the diffing process
-        // todo: figure out why this is necessary. i forgot. whoops.
-        let el = bump.alloc(VElement {
-            tag: "root",
-            namespace: None,
-            key: None,
-            id: Cell::new(Some(ElementId(0))),
-            parent: Default::default(),
-            listeners: &[],
-            attributes: &[],
-            children: &[],
-        });
-
-        let node = bump.alloc(VNode::Element(el));
-        let mut nodes = Slab::new();
-        let root_id = nodes.insert(NodePtr::VNode(unsafe {
-            std::mem::transmute(node as *const _)
-        }));
-
-        debug_assert_eq!(root_id, 0);
-
-        Self {
-            scope_gen: Cell::new(0),
-            bump,
-            scopes: RefCell::new(FxHashMap::default()),
-            heuristics: RefCell::new(FxHashMap::default()),
-            free_scopes: RefCell::new(Vec::new()),
-            nodes: RefCell::new(nodes),
-            tasks: Rc::new(TaskQueue {
-                tasks: RefCell::new(FxHashMap::default()),
-                task_map: RefCell::new(FxHashMap::default()),
-                gen: Cell::new(0),
-                sender,
-            }),
-            template_resolver: RefCell::new(TemplateResolver::default()),
-            templates: Rc::new(RefCell::new(FxHashMap::default())),
-            template_bump: Bump::new(),
-            template_refs: RefCell::new(Slab::new()),
-        }
-    }
+//
+/// A wrapper around a component's [`ScopeState`] and properties. The [`ScopeState`] provides the majority of methods
+/// for the VirtualDom and component state.
+pub struct Scoped<'a, T = ()> {
+    /// The component's state and handle to the scheduler.
+    ///
+    /// Stores things like the custom bump arena, spawn functions, hooks, and the scheduler.
+    pub scope: &'a ScopeState,
 
-    /// Safety:
-    /// - Obtaining a mutable reference to any Scope is unsafe
-    /// - Scopes use interior mutability when sharing data into components
-    pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
-        unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
-    }
+    /// The component's properties.
+    pub props: &'a T,
+}
 
-    pub(crate) fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
-        self.scopes.borrow().get(&id).copied()
+impl<'a, T> std::ops::Deref for Scoped<'a, T> {
+    type Target = &'a ScopeState;
+    fn deref(&self) -> &Self::Target {
+        &self.scope
     }
+}
 
-    pub(crate) fn new_with_key(
-        &self,
-        fc_ptr: ComponentPtr,
-        vcomp: Box<dyn AnyProps>,
-        parent_scope: Option<ScopeId>,
-        container: ElementId,
-    ) -> ScopeId {
-        // Increment the ScopeId system. ScopeIDs are never reused
-        let new_scope_id = ScopeId(self.scope_gen.get());
-        self.scope_gen.set(self.scope_gen.get() + 1);
-
-        // Get the height of the scope
-        let height = parent_scope
-            .and_then(|id| self.get_scope(id).map(|scope| scope.height + 1))
-            .unwrap_or_default();
-
-        let parent_scope = parent_scope.and_then(|f| self.get_scope_raw(f));
-
-        /*
-        This scopearena aggressively reuses old scopes when possible.
-        We try to minimize the new allocations for props/arenas.
+/// A component's unique identifier.
+///
+/// `ScopeId` is a `usize` that acts a key for the internal slab of Scopes. This means that the key is not unqiue across
+/// time. We do try and guarantee that between calls to `wait_for_work`, no ScopeIds will be recycled in order to give
+/// time for any logic that relies on these IDs to properly update.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
+pub struct ScopeId(pub usize);
 
-        However, this will probably lead to some sort of fragmentation.
-        I'm not exactly sure how to improve this today.
-        */
-        if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
-            // reuse the old scope
-            let scope = unsafe { &mut *old_scope };
+/// A component's state separate from its props.
+///
+/// This struct exists to provide a common interface for all scopes without relying on generics.
+pub struct ScopeState {
+    pub(crate) render_cnt: Cell<usize>,
+    pub(crate) name: &'static str,
 
-            scope.container = container;
-            scope.our_arena_idx = new_scope_id;
-            scope.parent_scope = parent_scope;
-            scope.height = height;
-            scope.fnptr = fc_ptr;
-            scope.props.get_mut().replace(vcomp);
-            scope.frames[0].reset();
-            scope.frames[1].reset();
-            scope.shared_contexts.get_mut().clear();
-            scope.items.get_mut().listeners.clear();
-            scope.items.get_mut().borrowed_props.clear();
-            scope.hook_idx.set(0);
-            scope.hook_vals.get_mut().clear();
+    pub(crate) node_arena_1: BumpFrame,
+    pub(crate) node_arena_2: BumpFrame,
 
-            let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
-            debug_assert!(any_item.is_none());
-        } else {
-            // else create a new scope
-            let (node_capacity, hook_capacity) = self
-                .heuristics
-                .borrow()
-                .get(&fc_ptr)
-                .map(|h| (h.node_arena_size, h.hook_arena_size))
-                .unwrap_or_default();
+    pub(crate) parent: Option<*mut ScopeState>,
+    pub(crate) id: ScopeId,
 
-            self.scopes.borrow_mut().insert(
-                new_scope_id,
-                self.bump.alloc(ScopeState {
-                    container,
-                    our_arena_idx: new_scope_id,
-                    parent_scope,
-                    height,
-                    fnptr: fc_ptr,
-                    props: RefCell::new(Some(vcomp)),
-                    frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
+    pub(crate) height: u32,
 
-                    generation: 0.into(),
+    pub(crate) hook_arena: Bump,
+    pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
+    pub(crate) hook_idx: Cell<usize>,
 
-                    tasks: self.tasks.clone(),
-                    shared_contexts: RefCell::default(),
+    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
 
-                    items: RefCell::new(SelfReferentialItems {
-                        listeners: Vec::default(),
-                        borrowed_props: Vec::default(),
-                    }),
+    pub(crate) tasks: Rc<Scheduler>,
+    pub(crate) spawned_tasks: HashSet<TaskId>,
 
-                    hook_arena: Bump::new(),
-                    hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
-                    hook_idx: Cell::default(),
+    pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
+    pub(crate) placeholder: Cell<Option<ElementId>>,
+}
 
-                    templates: self.templates.clone(),
-                }),
-            );
+impl<'src> ScopeState {
+    pub(crate) fn current_frame(&self) -> &BumpFrame {
+        match self.render_cnt.get() % 2 {
+            0 => &self.node_arena_1,
+            1 => &self.node_arena_2,
+            _ => unreachable!(),
         }
-
-        new_scope_id
     }
 
-    // Removes a scope and its descendents from the arena
-    pub fn try_remove(&self, id: ScopeId) {
-        self.ensure_drop_safety(id);
-
-        // Dispose of any ongoing tasks
-        let mut tasks = self.tasks.tasks.borrow_mut();
-        let mut task_map = self.tasks.task_map.borrow_mut();
-        if let Some(cur_tasks) = task_map.remove(&id) {
-            for task in cur_tasks {
-                tasks.remove(&task);
-            }
+    pub(crate) fn previous_frame(&self) -> &BumpFrame {
+        match self.render_cnt.get() % 2 {
+            1 => &self.node_arena_1,
+            0 => &self.node_arena_2,
+            _ => unreachable!(),
         }
-
-        // Safety:
-        // - ensure_drop_safety ensures that no references to this scope are in use
-        // - this raw pointer is removed from the map
-        let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
-        scope.reset();
-
-        self.free_scopes.borrow_mut().push(scope);
     }
 
-    pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
-        let mut els = self.nodes.borrow_mut();
-        let entry = els.vacant_entry();
-        let key = entry.key();
-        let id = ElementId(key);
-        let node = unsafe { extend_vnode(node) };
-        entry.insert(NodePtr::VNode(node as *const _));
-        id
-    }
-
-    pub fn reserve_template_ref<'a>(&self, template_ref: &'a VTemplateRef<'a>) -> TemplateRefId {
-        let mut refs = self.template_refs.borrow_mut();
-        let entry = refs.vacant_entry();
-        let key = entry.key();
-        let id = TemplateRefId(key);
-        let static_ref: &VTemplateRef<'static> = unsafe { std::mem::transmute(template_ref) };
-        entry.insert(static_ref as *const _);
-        id
-    }
-
-    pub fn reserve_template_node(
-        &self,
-        template_ref_id: TemplateRefId,
-        node_id: TemplateNodeId,
-    ) -> ElementId {
-        let mut els = self.nodes.borrow_mut();
-        let entry = els.vacant_entry();
-        let key = entry.key();
-        let id = ElementId(key);
-        entry.insert(NodePtr::TemplateNode {
-            template_ref: template_ref_id,
-            node_id,
-        });
-        id
-    }
-
-    pub fn reserve_phantom_node(&self) -> ElementId {
-        let mut els = self.nodes.borrow_mut();
-        let entry = els.vacant_entry();
-        let key = entry.key();
-        let id = ElementId(key);
-        entry.insert(NodePtr::Phantom);
-        id
+    pub(crate) fn previous_frame_mut(&mut self) -> &mut BumpFrame {
+        match self.render_cnt.get() % 2 {
+            1 => &mut self.node_arena_1,
+            0 => &mut self.node_arena_2,
+            _ => unreachable!(),
+        }
     }
 
-    pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
-        let node = unsafe { extend_vnode(node) };
-        *self.nodes.borrow_mut().get_mut(id.0).unwrap() = NodePtr::VNode(node);
+    /// Get the name of this component
+    pub fn name(&self) -> &str {
+        self.name
     }
 
-    pub fn update_template_ref<'a>(
-        &self,
-        template_ref_id: TemplateRefId,
-        template_ref: &'a VTemplateRef<'a>,
-    ) {
-        let template_ref = unsafe { std::mem::transmute(template_ref) };
-        *self
-            .template_refs
-            .borrow_mut()
-            .get_mut(template_ref_id.0)
-            .unwrap() = template_ref;
-    }
-
-    pub fn collect_garbage(&self, id: ElementId) {
-        self.nodes.borrow_mut().remove(id.0);
+    /// Get the current render since the inception of this component
+    ///
+    /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
+    pub fn generation(&self) -> usize {
+        self.render_cnt.get()
     }
 
-    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
-    /// causing UB in our tree.
+    /// Get a handle to the currently active bump arena for this Scope
     ///
-    /// 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.
+    /// This is a bump memory allocator. Be careful using this directly since the contents will be wiped on the next render.
+    /// It's easy to leak memory here since the drop implementation will not be called for any objects allocated in this arena.
     ///
-    /// References 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.
-    ///
-    /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
-    /// be dropped.
-    pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
-        if let Some(scope) = self.get_scope(scope_id) {
-            let mut items = scope.items.borrow_mut();
-
-            // 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 Reference)
-            // recursively call ensure_drop_safety on all children
-            items.borrowed_props.drain(..).for_each(|comp| {
-                if let Some(scope_id) = comp.scope.get() {
-                    self.ensure_drop_safety(scope_id);
-                }
-                drop(comp.props.take());
-            });
-
-            // Now that all the references are gone, we can safely drop our own references in our listeners.
-            items
-                .listeners
-                .drain(..)
-                .for_each(|listener| drop(listener.callback.borrow_mut().take()));
-        }
+    /// If you need to allocate items that need to be dropped, use bumpalo's box.
+    pub fn bump(&self) -> &Bump {
+        // note that this is actually the previous frame since we use that as scratch space while the component is rendering
+        &self.previous_frame().bump
     }
 
-    pub(crate) fn run_scope(&self, id: ScopeId) {
-        // Cycle to the next frame and then reset it
-        // This breaks any latent references, invalidating every pointer referencing into it.
-        // Remove all the outdated listeners
-        self.ensure_drop_safety(id);
-
-        // todo: we *know* that this is aliased by the contents of the scope itself
-        let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
-
-        log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);
-
-        // 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
-        scope.hook_idx.set(0);
-
-        {
-            // Safety:
-            // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
-            unsafe { scope.reset_wip_frame() };
-
-            let items = scope.items.borrow();
-
-            // guarantee that we haven't screwed up - there should be no latent references anywhere
-            debug_assert!(items.listeners.is_empty());
-            debug_assert!(items.borrowed_props.is_empty());
-        }
-
-        /*
-        If the component returns None, then we fill in a placeholder node. This will wipe what was there.
-        An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
-
-        Instead, we just treat the `None` as a shortcut to placeholder.
-        If the developer wants to prevent a scope from updating, they should control its memoization instead.
-
-        Also, the way we implement hooks allows us to cut rendering short before the next hook is recalled.
-        I'm not sure if React lets you abort the component early, but we let you do that.
-        */
-
-        let props = scope.props.borrow();
-        let render = props.as_ref().unwrap();
-        if let Some(node) = render.render(scope) {
-            let frame = scope.wip_frame();
-            let node = frame.bump.alloc(node);
-            frame.node.set(unsafe { extend_vnode(node) });
-        } else {
-            let frame = scope.wip_frame();
-            let node = frame
-                .bump
-                .alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
-                    id: Cell::default(),
-                })));
-            frame.node.set(unsafe { extend_vnode(node) });
-        }
-
-        // make the "wip frame" contents the "finished frame"
-        // any future dipping into completed nodes after "render" will go through "fin head"
-        scope.cycle_frame();
+    /// Get a handle to the currently active head node arena for this Scope
+    ///
+    /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
+    ///
+    /// Panics if the tree has not been built yet.
+    pub fn root_node(&self) -> &RenderReturn {
+        self.try_root_node()
+            .expect("The tree has not been built yet. Make sure to call rebuild on the tree before accessing its nodes.")
     }
 
-    pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) {
-        let nodes = self.nodes.borrow();
-        let mut cur_el = Some(element);
-
-        let state = Rc::new(BubbleState::new());
-
-        while let Some(id) = cur_el.take() {
-            if state.canceled.get() {
-                // stop bubbling if canceled
-                return;
-            }
-            if let Some(ptr) = nodes.get(id.0) {
-                match ptr {
-                    NodePtr::VNode(ptr) => {
-                        let real_el = unsafe { &**ptr };
-                        log::trace!("looking for listener on {:?}", real_el);
-
-                        if let VNode::Element(real_el) = real_el {
-                            for listener in real_el.listeners.iter() {
-                                if listener.event == event.name {
-                                    log::trace!("calling listener {:?}", listener.event);
-
-                                    let mut cb = listener.callback.borrow_mut();
-                                    if let Some(cb) = cb.as_mut() {
-                                        // todo: arcs are pretty heavy to clone
-                                        // we really want to convert arc to rc
-                                        // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
-                                        // we could convert arc to rc internally or something
-                                        (cb)(AnyEvent {
-                                            bubble_state: state.clone(),
-                                            data: event.data.clone(),
-                                        });
-                                    }
-                                    break;
-                                }
-                            }
-
-                            cur_el = real_el.parent.get();
-                        }
-                    }
-                    NodePtr::TemplateNode {
-                        node_id,
-                        template_ref,
-                    } => {
-                        let template_refs = self.template_refs.borrow();
-                        let template_ptr = template_refs.get(template_ref.0).unwrap();
-                        let template_ref = unsafe { &**template_ptr };
-                        log::trace!("looking for listener in node {:?}", node_id);
-                        let templates = self.templates.borrow();
-                        let template = templates.get(&template_ref.template_id).unwrap();
-                        cur_el = template.borrow().with_nodes(
-                            bubble_template,
-                            bubble_template,
-                            (*node_id, template_ref, event, &state),
-                        );
-                    }
-                    _ => panic!("Expected Real Node"),
-                }
-            }
-
-            if !event.bubbles {
-                return;
-            }
-        }
-
-        fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text, Nodes>(
-            nodes: &Nodes,
-            ctx: (
-                TemplateNodeId,
-                &VTemplateRef<'b>,
-                &UserEvent,
-                &Rc<BubbleState>,
-            ),
-        ) -> Option<ElementId>
-        where
-            Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            let (start, template_ref, event, state) = ctx;
-            let dynamic_context = &template_ref.dynamic_context;
-            let mut current = &nodes.as_ref()[start.0];
-            loop {
-                if let TemplateNodeType::Element(el) = &current.node_type {
-                    let TemplateElement { listeners, .. } = el;
-                    for listener_idx in listeners.as_ref() {
-                        let listener = dynamic_context.resolve_listener(*listener_idx);
-                        if listener.event == event.name {
-                            log::trace!("calling listener {:?}", listener.event);
-
-                            let mut cb = listener.callback.borrow_mut();
-                            if let Some(cb) = cb.as_mut() {
-                                // todo: arcs are pretty heavy to clone
-                                // we really want to convert arc to rc
-                                // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
-                                // we could convert arc to rc internally or something
-                                (cb)(AnyEvent {
-                                    bubble_state: state.clone(),
-                                    data: event.data.clone(),
-                                });
-                            }
-                            break;
-                        }
-                    }
+    /// Try to get a handle to the currently active head node arena for this Scope
+    ///
+    /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
+    ///
+    /// Returns [`None`] if the tree has not been built yet.
+    pub fn try_root_node(&self) -> Option<&RenderReturn> {
+        let ptr = self.current_frame().node.get();
 
-                    if let Some(id) = current.parent {
-                        current = &nodes.as_ref()[id.0];
-                    } else {
-                        return template_ref.parent.get();
-                    }
-                } else {
-                    return None;
-                }
-            }
+        if ptr.is_null() {
+            return None;
         }
-    }
-
-    // The head of the bumpframe is the first linked NodeLink
-    pub fn wip_head(&self, id: ScopeId) -> &VNode {
-        let scope = self.get_scope(id).unwrap();
-        let frame = scope.wip_frame();
-        let node = unsafe { &*frame.node.get() };
-        unsafe { extend_vnode(node) }
-    }
-
-    // The head of the bumpframe is the first linked NodeLink
-    pub fn fin_head(&self, id: ScopeId) -> &VNode {
-        let scope = self.get_scope(id).unwrap();
-        let frame = scope.fin_frame();
-        let node = unsafe { &*frame.node.get() };
-        unsafe { extend_vnode(node) }
-    }
-
-    pub fn root_node(&self, id: ScopeId) -> &VNode {
-        self.fin_head(id)
-    }
-
-    // this is totally okay since all our nodes are always in a valid state
-    pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
-        self.nodes.borrow().get(id.0).and_then(|ptr| match ptr {
-            NodePtr::VNode(ptr) => Some(unsafe { extend_vnode(&**ptr) }),
-            _ => None,
-        })
-    }
-}
-
-/// Components in Dioxus use the "Context" object to interact with their lifecycle.
-///
-/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
-///
-/// For the most part, the only method you should be using regularly is `render`.
-///
-/// ## Example
-///
-/// ```ignore
-/// #[derive(Props)]
-/// struct ExampleProps {
-///     name: String
-/// }
-///
-/// fn Example(cx: Scope<ExampleProps>) -> Element {
-///     cx.render(rsx!{ div {"Hello, {cx.props.name}"} })
-/// }
-/// ```
-pub struct Scope<'a, P = ()> {
-    /// The internal ScopeState for this component
-    pub scope: &'a ScopeState,
 
-    /// The props for this component
-    pub props: &'a P,
-}
+        let r: &RenderReturn = unsafe { &*ptr };
 
-impl<P> Copy for Scope<'_, P> {}
-impl<P> Clone for Scope<'_, P> {
-    fn clone(&self) -> Self {
-        Self {
-            scope: self.scope,
-            props: self.props,
-        }
+        unsafe { std::mem::transmute(r) }
     }
-}
 
-impl<'a, P> std::ops::Deref for Scope<'a, P> {
-    // rust will auto deref again to the original 'a lifetime at the call site
-    type Target = &'a ScopeState;
-    fn deref(&self) -> &Self::Target {
-        &self.scope
-    }
-}
-
-/// A component's unique identifier.
-///
-/// `ScopeId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`ScopeID`]s will never be reused
-/// once a component has been unmounted.
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
-pub struct ScopeId(pub usize);
-
-/// A task's unique identifier.
-///
-/// `TaskId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`TaskID`]s will never be reused
-/// once a Task has been completed.
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct TaskId {
-    /// The global ID of the task
-    pub id: usize,
-
-    /// The original scope that this task was scheduled in
-    pub scope: ScopeId,
-}
-
-/// Every component in Dioxus is represented by a `ScopeState`.
-///
-/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
-///
-/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
-/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
-///
-/// We expose the `Scope` type so downstream users can traverse the Dioxus [`VirtualDom`] for whatever
-/// use case they might have.
-pub struct ScopeState {
-    pub(crate) parent_scope: Option<*mut ScopeState>,
-    pub(crate) container: ElementId,
-    pub(crate) our_arena_idx: ScopeId,
-    pub(crate) height: u32,
-    pub(crate) fnptr: ComponentPtr,
-    pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
-
-    // nodes, items
-    pub(crate) frames: [BumpFrame; 2],
-    pub(crate) generation: Cell<u32>,
-    pub(crate) items: RefCell<SelfReferentialItems<'static>>,
-
-    // hooks
-    pub(crate) hook_arena: Bump,
-    pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
-    pub(crate) hook_idx: Cell<usize>,
-
-    // shared state -> todo: move this out of scopestate
-    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
-    pub(crate) tasks: Rc<TaskQueue>,
-
-    // templates
-    pub(crate) templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>,
-}
-
-pub struct SelfReferentialItems<'a> {
-    pub(crate) listeners: Vec<&'a Listener<'a>>,
-    pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
-}
-
-// Public methods exposed to libraries and components
-impl ScopeState {
     /// Get the height of this Scope - IE the number of scopes above it.
     ///
     /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
@@ -658,9 +184,9 @@ impl ScopeState {
         self.height
     }
 
-    /// Get the Parent of this [`Scope`] within this Dioxus [`VirtualDom`].
+    /// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
     ///
-    /// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
+    /// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
     ///
     /// The base component will not have a parent, and will return `None`.
     ///
@@ -676,12 +202,12 @@ impl ScopeState {
     /// ```
     pub fn parent(&self) -> Option<ScopeId> {
         // safety: the pointer to our parent is *always* valid thanks to the bump arena
-        self.parent_scope.map(|p| unsafe { &*p }.our_arena_idx)
+        self.parent.map(|p| unsafe { &*p }.id)
     }
 
-    /// Get the ID of this Scope within this Dioxus [`VirtualDom`].
+    /// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
     ///
-    /// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
+    /// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
     ///
     /// # Example
     ///
@@ -693,17 +219,12 @@ impl ScopeState {
     /// assert_eq!(base.scope_id(), 0);
     /// ```
     pub fn scope_id(&self) -> ScopeId {
-        self.our_arena_idx
-    }
-
-    /// Get a handle to the raw update scheduler channel
-    pub fn scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
-        self.tasks.sender.clone()
+        self.id
     }
 
     /// Create a subscription that schedules a future render for the reference component
     ///
-    /// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
+    /// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
     pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
         let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
         Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
@@ -719,16 +240,14 @@ impl ScopeState {
         Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
     }
 
-    /// Get the [`ScopeId`] of a mounted component.
-    ///
-    /// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
+    /// Mark this scope as dirty, and schedule a render for it.
     pub fn needs_update(&self) {
         self.needs_update_any(self.scope_id());
     }
 
     /// Get the [`ScopeId`] of a mounted component.
     ///
-    /// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
+    /// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
     pub fn needs_update_any(&self, id: ScopeId) {
         self.tasks
             .sender
@@ -736,20 +255,45 @@ impl ScopeState {
             .expect("Scheduler to exist if scope exists");
     }
 
-    /// Get the Root Node of this scope
-    pub fn root_node(&self) -> &VNode {
-        let node = unsafe { &*self.fin_frame().node.get() };
-        unsafe { std::mem::transmute(node) }
+    /// Return any context of type T if it exists on this scope
+    pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
+        self.shared_contexts
+            .borrow()
+            .get(&TypeId::of::<T>())?
+            .downcast_ref::<T>()
+            .cloned()
     }
 
-    /// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
+    /// Try to retrieve a shared state with type `T` from any parent scope.
+    ///
+    /// Clones the state if it exists.
+    pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
+        if let Some(this_ctx) = self.has_context() {
+            return Some(this_ctx);
+        }
+
+        let mut search_parent = self.parent;
+        while let Some(parent_ptr) = search_parent {
+            // safety: all parent pointers are valid thanks to the bump arena
+            let parent = unsafe { &*parent_ptr };
+            if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
+                return shared.downcast_ref::<T>().cloned();
+            }
+            search_parent = parent.parent;
+        }
+        None
+    }
+
+    /// Expose state to children further down the [`crate::VirtualDom`] Tree. Does not require `clone` on the context,
+    /// though we do recommend it.
     ///
     /// This is a "fundamental" operation and should only be called during initialization of a hook.
     ///
-    /// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead.
+    /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.
     ///
-    /// When the component is dropped, so is the context. Be aware of this behavior when consuming
-    /// the context via Rc/Weak.
+    /// If a state is provided that already exists, the new value will not be inserted. Instead, this method will
+    /// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular
+    /// behavior might change in the future.
     ///
     /// # Example
     ///
@@ -767,102 +311,18 @@ impl ScopeState {
     /// }
     /// ```
     pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
+        let value2 = value.clone();
+
         self.shared_contexts
             .borrow_mut()
-            .insert(TypeId::of::<T>(), Box::new(value.clone()))
-            .and_then(|f| f.downcast::<T>().ok());
-        value
-    }
-
-    /// Provide a context for the root component from anywhere in your app.
-    ///
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// struct SharedState(&'static str);
-    ///
-    /// static App: Component = |cx| {
-    ///     cx.use_hook(|| cx.provide_root_context(SharedState("world")));
-    ///     render!(Child {})
-    /// }
-    ///
-    /// static Child: Component = |cx| {
-    ///     let state = cx.consume_state::<SharedState>();
-    ///     render!(div { "hello {state.0}" })
-    /// }
-    /// ```
-    pub fn provide_root_context<T: 'static + Clone>(&self, value: T) -> T {
-        // if we *are* the root component, then we can just provide the context directly
-        if self.scope_id() == ScopeId(0) {
-            self.shared_contexts
-                .borrow_mut()
-                .insert(TypeId::of::<T>(), Box::new(value.clone()))
-                .and_then(|f| f.downcast::<T>().ok());
-            return value;
-        }
+            .insert(TypeId::of::<T>(), Box::new(value));
 
-        let mut search_parent = self.parent_scope;
-
-        while let Some(parent) = search_parent.take() {
-            let parent = unsafe { &*parent };
-
-            if parent.scope_id() == ScopeId(0) {
-                let exists = parent
-                    .shared_contexts
-                    .borrow_mut()
-                    .insert(TypeId::of::<T>(), Box::new(value.clone()));
-
-                if exists.is_some() {
-                    log::warn!("Context already provided to parent scope - replacing it");
-                }
-                return value;
-            }
-
-            search_parent = parent.parent_scope;
-        }
-
-        unreachable!("all apps have a root scope")
-    }
-
-    /// Try to retrieve a shared state with type T from the any parent Scope.
-    pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
-        if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
-            Some(
-                (*shared
-                    .downcast_ref::<T>()
-                    .expect("Context of type T should exist"))
-                .clone(),
-            )
-        } else {
-            let mut search_parent = self.parent_scope;
-
-            while let Some(parent_ptr) = search_parent {
-                // safety: all parent pointers are valid thanks to the bump arena
-                let parent = unsafe { &*parent_ptr };
-                if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
-                    return Some(
-                        shared
-                            .downcast_ref::<T>()
-                            .expect("Context of type T should exist")
-                            .clone(),
-                    );
-                }
-                search_parent = parent.parent_scope;
-            }
-            None
-        }
+        value2
     }
 
     /// Pushes the future onto the poll queue to be polled after the component renders.
     pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
-        // wake up the scheduler if it is sleeping
-        self.tasks
-            .sender
-            .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
-            .expect("Scheduler should exist");
-
-        self.tasks.spawn(self.our_arena_idx, fut)
+        self.tasks.spawn(self.id, fut)
     }
 
     /// Spawns the future but does not return the [`TaskId`]
@@ -870,27 +330,30 @@ impl ScopeState {
         self.push_future(fut);
     }
 
-    /// Spawn a future that Dioxus will never clean up
+    /// Spawn a future that Dioxus won't clean up when this component is unmounted
     ///
     /// This is good for tasks that need to be run after the component has been dropped.
     pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
+        // The root scope will never be unmounted so we can just add the task at the top of the app
+        let id = self.tasks.spawn(ScopeId(0), fut);
+
         // wake up the scheduler if it is sleeping
         self.tasks
             .sender
-            .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
+            .unbounded_send(SchedulerMsg::TaskNotified(id))
             .expect("Scheduler should exist");
 
-        // The root scope will never be unmounted so we can just add the task at the top of the app
-        self.tasks.spawn(ScopeId(0), fut)
+        id
     }
 
-    /// Informs the scheduler that this task is no longer needed and should be removed
-    /// on next poll.
+    /// Informs the scheduler that this task is no longer needed and should be removed.
+    ///
+    /// This drops the task immediately.
     pub fn remove_future(&self, id: TaskId) {
         self.tasks.remove(id);
     }
 
-    /// Take a lazy [`VNode`] structure and actually build it with the context of the Vdoms efficient [`VNode`] allocator.
+    /// Take a lazy [`crate::VNode`] structure and actually build it with the context of the efficient [`bumpalo::Bump`] allocator.
     ///
     /// ## Example
     ///
@@ -903,11 +366,124 @@ impl ScopeState {
     ///     cx.render(lazy_tree)
     /// }
     ///```
-    pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
-        Some(rsx.call(NodeFactory {
-            scope: self,
-            bump: &self.wip_frame().bump,
-        }))
+    pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
+        Ok(rsx.call(self))
+    }
+
+    /// Create a dynamic text node using [`Arguments`] and the [`ScopeState`]'s internal [`Bump`] allocator
+    pub fn text_node(&'src self, args: Arguments) -> DynamicNode<'src> {
+        DynamicNode::Text(VText {
+            value: self.raw_text(args),
+            id: Default::default(),
+        })
+    }
+
+    /// Allocate some text inside the [`ScopeState`] from [`Arguments`]
+    ///
+    /// Uses the currently active [`Bump`] allocator
+    pub fn raw_text(&'src self, args: Arguments) -> &'src str {
+        args.as_str().unwrap_or_else(|| {
+            use bumpalo::core_alloc::fmt::Write;
+            let mut str_buf = bumpalo::collections::String::new_in(self.bump());
+            str_buf.write_fmt(args).unwrap();
+            str_buf.into_bump_str()
+        })
+    }
+
+    /// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`] using the internal [`Bump`] allocator
+    pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode<'src, I> + 'c) -> DynamicNode {
+        into.into_vnode(self)
+    }
+
+    /// Create a new [`Attribute`] from a name, value, namespace, and volatile bool
+    ///
+    /// "Volatile" referes to whether or not Dioxus should always override the value. This helps prevent the UI in
+    /// some renderers stay in sync with the VirtualDom's understanding of the world
+    pub fn attr(
+        &'src self,
+        name: &'static str,
+        value: impl IntoAttributeValue<'src>,
+        namespace: Option<&'static str>,
+        volatile: bool,
+    ) -> Attribute<'src> {
+        Attribute {
+            name,
+            namespace,
+            volatile,
+            mounted_element: Default::default(),
+            value: value.into_value(self.bump()),
+        }
+    }
+
+    /// Create a new [`DynamicNode::Component`] variant
+    ///
+    ///
+    /// The given component can be any of four signatures. Remember that an [`Element`] is really a [`Result<VNode>`].
+    ///
+    /// ```rust, ignore
+    /// // Without explicit props
+    /// fn(Scope) -> Element;
+    /// async fn(Scope<'_>) -> Element;
+    ///
+    /// // With explicit props
+    /// fn(Scope<Props>) -> Element;
+    /// async fn(Scope<Props<'_>>) -> Element;
+    /// ```
+    pub fn component<P, A, F: ComponentReturn<'src, A>>(
+        &'src self,
+        component: fn(Scope<'src, P>) -> F,
+        props: P,
+        fn_name: &'static str,
+    ) -> DynamicNode<'src>
+    where
+        P: Properties + 'src,
+    {
+        let vcomp = VProps::new(component, P::memoize, props);
+
+        // cast off the lifetime of the render return
+        let as_dyn: Box<dyn AnyProps<'src> + '_> = Box::new(vcomp);
+        let extended: Box<dyn AnyProps<'src> + 'src> = unsafe { std::mem::transmute(as_dyn) };
+
+        DynamicNode::Component(VComponent {
+            name: fn_name,
+            render_fn: component as *const (),
+            static_props: P::IS_STATIC,
+            props: Cell::new(Some(extended)),
+            scope: Cell::new(None),
+        })
+    }
+
+    /// Create a new [`EventHandler`] from an [`FnMut`]
+    pub fn event_handler<T>(&'src self, f: impl FnMut(T) + 'src) -> EventHandler<'src, T> {
+        let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
+        let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
+        let callback = RefCell::new(Some(caller));
+        EventHandler { callback }
+    }
+
+    /// Create a new [`AttributeValue`] with the listener variant from a callback
+    ///
+    /// The callback must be confined to the lifetime of the ScopeState
+    pub fn listener<T: 'static>(
+        &'src self,
+        mut callback: impl FnMut(Event<T>) + 'src,
+    ) -> AttributeValue<'src> {
+        // safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
+        // This is the suggested way to build a bumpbox
+        //
+        // In theory, we could just use regular boxes
+        let boxed: BumpBox<'src, dyn FnMut(_) + 'src> = unsafe {
+            BumpBox::from_raw(self.bump().alloc(move |event: Event<dyn Any>| {
+                if let Ok(data) = event.data.downcast::<T>() {
+                    callback(Event {
+                        propogates: event.propogates,
+                        data,
+                    })
+                }
+            }))
+        };
+
+        AttributeValue::Listener(RefCell::new(Some(boxed)))
     }
 
     /// Store a value between renders. The foundational hook for all other hooks.
@@ -928,19 +504,17 @@ impl ScopeState {
     /// ```
     #[allow(clippy::mut_from_ref)]
     pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
-        let mut vals = self.hook_vals.borrow_mut();
+        let cur_hook = self.hook_idx.get();
+        let mut hook_list = self.hook_list.borrow_mut();
 
-        let hook_len = vals.len();
-        let cur_idx = self.hook_idx.get();
-
-        if cur_idx >= hook_len {
-            vals.push(self.hook_arena.alloc(initializer()));
+        if cur_hook >= hook_list.len() {
+            hook_list.push(self.hook_arena.alloc(initializer()));
         }
 
-        vals
-            .get(cur_idx)
+        hook_list
+            .get(cur_hook)
             .and_then(|inn| {
-                self.hook_idx.set(cur_idx + 1);
+                self.hook_idx.set(cur_hook + 1);
                 let raw_box = unsafe { &mut **inn };
                 raw_box.downcast_mut::<State>()
             })
@@ -954,157 +528,4 @@ impl ScopeState {
                 "###,
             )
     }
-
-    /// The "work in progress frame" represents the frame that is currently being worked on.
-    pub(crate) fn wip_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 {
-            0 => &self.frames[0],
-            _ => &self.frames[1],
-        }
-    }
-
-    /// Mutable access to the "work in progress frame" - used to clear it
-    pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
-        match self.generation.get() & 1 {
-            0 => &mut self.frames[0],
-            _ => &mut self.frames[1],
-        }
-    }
-
-    /// Access to the frame where finalized nodes existed
-    pub(crate) fn fin_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 {
-            1 => &self.frames[0],
-            _ => &self.frames[1],
-        }
-    }
-
-    /// Reset this component's frame
-    ///
-    /// # Safety:
-    ///
-    /// This method breaks every reference of every [`VNode`] in the current frame.
-    ///
-    /// Calling reset itself is not usually a big deal, but we consider it important
-    /// due to the complex safety guarantees we need to uphold.
-    pub(crate) unsafe fn reset_wip_frame(&mut self) {
-        self.wip_frame_mut().bump.reset();
-    }
-
-    /// Cycle to the next generation
-    pub(crate) fn cycle_frame(&self) {
-        self.generation.set(self.generation.get() + 1);
-    }
-
-    // todo: disable bookkeeping on drop (unncessary)
-    pub(crate) fn reset(&mut self) {
-        // first: book keaping
-        self.hook_idx.set(0);
-        self.parent_scope = None;
-        self.generation.set(0);
-
-        // next: shared context data
-        self.shared_contexts.get_mut().clear();
-
-        // next: reset the node data
-        let SelfReferentialItems {
-            borrowed_props,
-            listeners,
-        } = self.items.get_mut();
-        borrowed_props.clear();
-        listeners.clear();
-        self.frames[0].reset();
-        self.frames[1].reset();
-
-        // Free up the hook values
-        self.hook_vals.get_mut().drain(..).for_each(|state| {
-            let as_mut = unsafe { &mut *state };
-            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
-            drop(boxed);
-        });
-
-        // Finally, clear the hook arena
-        self.hook_arena.reset();
-    }
-}
-
-pub(crate) struct BumpFrame {
-    pub bump: Bump,
-    pub node: Cell<*const VNode<'static>>,
-}
-impl BumpFrame {
-    pub(crate) fn new(capacity: usize) -> Self {
-        let bump = Bump::with_capacity(capacity);
-        let node = bump.alloc(VText {
-            text: "placeholdertext",
-            id: Cell::default(),
-            is_static: false,
-        });
-        let node = bump.alloc(VNode::Text(unsafe {
-            &*(node as *mut VText as *const VText)
-        }));
-        let nodes = Cell::new(node as *const _);
-        Self { bump, node: nodes }
-    }
-
-    pub(crate) fn reset(&mut self) {
-        self.bump.reset();
-        let node = self.bump.alloc(VText {
-            text: "placeholdertext",
-            id: Cell::default(),
-            is_static: false,
-        });
-        let node = self.bump.alloc(VNode::Text(unsafe {
-            &*(node as *mut VText as *const VText)
-        }));
-        self.node.set(node as *const _);
-    }
-}
-
-pub(crate) struct TaskQueue {
-    pub(crate) tasks: RefCell<FxHashMap<TaskId, InnerTask>>,
-    pub(crate) task_map: RefCell<FxHashMap<ScopeId, HashSet<TaskId>>>,
-    gen: Cell<usize>,
-    sender: UnboundedSender<SchedulerMsg>,
-}
-
-pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
-impl TaskQueue {
-    fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
-        let pinned = Box::pin(task);
-        let id = self.gen.get();
-        self.gen.set(id + 1);
-        let tid = TaskId { id, scope };
-
-        self.tasks.borrow_mut().insert(tid, pinned);
-
-        // also add to the task map
-        // when the component is unmounted we know to remove it from the map
-        self.task_map
-            .borrow_mut()
-            .entry(scope)
-            .or_default()
-            .insert(tid);
-
-        tid
-    }
-
-    fn remove(&self, id: TaskId) {
-        if let Ok(mut tasks) = self.tasks.try_borrow_mut() {
-            tasks.remove(&id);
-            if let Some(task_map) = self.task_map.borrow_mut().get_mut(&id.scope) {
-                task_map.remove(&id);
-            }
-        }
-        // the task map is still around, but it'll be removed when the scope is unmounted
-    }
-
-    pub(crate) fn has_tasks(&self) -> bool {
-        !self.tasks.borrow().is_empty()
-    }
-}
-
-#[test]
-fn sizeof() {
-    dbg!(std::mem::size_of::<ScopeState>());
 }

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

@@ -0,0 +1,30 @@
+/*
+This is a WIP module
+
+Subtrees allow the virtualdom to split up the mutation stream into smaller chunks which can be directed to different parts of the dom.
+It's core to implementing multiwindow desktop support, portals, and alternative inline renderers like react-three-fiber.
+
+The primary idea is to give each renderer a linear element tree managed by Dioxus to maximize performance and minimize memory usage.
+This can't be done if two renderers need to share the same native tree.
+With subtrees, we have an entirely different slab of elements
+
+*/
+
+use std::borrow::Cow;
+
+use slab::Slab;
+
+use crate::{ElementPath, ScopeId};
+
+/// A collection of elements confined to a single scope under a chunk of the tree
+///
+/// All elements in this collection are guaranteed to be in the same scope and share the same numbering
+///
+/// This unit can be multithreaded
+/// Whenever multiple subtrees are present, we can perform **parallel diffing**
+pub struct Subtree {
+    id: usize,
+    namespace: Cow<'static, str>,
+    root: ScopeId,
+    elements: Slab<ElementPath>,
+}

+ 0 - 1302
packages/core/src/template.rs

@@ -1,1302 +0,0 @@
-//! Templates are used to skip diffing on any static parts of the rsx.
-//! TemplateNodes are different from VNodes in that they can contain partial dynamic and static content in the same node.
-//! For example:
-//! ```
-//! # use dioxus::prelude::*;
-//! # let color = "red";
-//! # let dynamic_text_1 = "a";
-//! # let dynamic_text_2 = "b";
-//! # let dynamic_iterator = std::iter::once("");
-//! rsx! {
-//!     div {
-//!         color: "{color}",
-//!         "Hello, world",
-//!         "{dynamic_text_1}",
-//!         "{dynamic_text_2}",
-//!         dynamic_iterator
-//!     }
-//! };
-//! ```
-//! The above will turn into a template that contains information on how to build div { "Hello, world" } and then every refrence to the template will hydrate with the value of dynamic_text_1, dynamic_text_2, dynamic_iterator, and the color property.
-//! The rsx macro will both generate the template and the `DynamicNodeMapping` struct that contains the information on what parts of the template depend on each value of the dynamic context.
-//! In templates with many dynamic parts, this allows the diffing algorithm to skip traversing the template to find what part to hydrate.
-//! Each dynamic part will contain a index into the dynamic context to determine what value to use. The indexes are origionally ordered by traversing the tree depth first from the root.
-//! The indexes for the above would be as follows:
-//! ```
-//! # use dioxus::prelude::*;
-//! # let color = "red";
-//! # let dynamic_text_1 = "a";
-//! # let dynamic_text_2 = "b";
-//! # let dynamic_iterator = std::iter::once("");
-//! rsx! {
-//!     div {
-//!         color: "{color}", // attribute index 0
-//!         "Hello, world",
-//!         "{dynamic_text_1}", // text index 0
-//!         "{dynamic_text_2}", // text index 1
-//!         dynamic_iterator // node index 0
-//!     }
-//! };
-//! ```
-//! Including these indexes allows hot reloading to move the dynamic parts of the template around.
-//! The templates generated by rsx are stored as 'static refrences, but you can change the template at runtime to allow hot reloading.
-//! The template could be replaced with a new one at runtime:
-//! ```
-//! # use dioxus::prelude::*;
-//! # let color = "red";
-//! # let dynamic_text_1 = "a";
-//! # let dynamic_text_2 = "b";
-//! # let dynamic_iterator = std::iter::once("");
-//! rsx! {
-//!     div {
-//!         "Hello, world",
-//!         dynamic_iterator // node index 0
-//!         h1 {
-//!             background_color: "{color}", // attribute index 0
-//!             "{dynamic_text_2}", // text index 1
-//!         }
-//!         h1 {
-//!            color: "{color}", // attribute index 0
-//!            "{dynamic_text_1}", // text index 0
-//!         }
-//!     }
-//! };
-//! ```
-//! Notice how the indecies are no longer in depth first traversal order, and indecies are no longer unique. Attributes and dynamic parts of the text can be duplicated, but dynamic vnodes and componets cannot.
-//! To minimize the cost of allowing hot reloading on applications that do not use it there are &'static and owned versions of template nodes, and dynamic node mapping.
-//!
-//! Notes:
-//! 1) The template allow diffing to scale with reactivity.
-//! With a virtual dom the diffing cost scales with the number of nodes in the dom. With templates the cost scales with the number of dynamic parts of the dom. The dynamic template context links any parts of the template that can change which allows the diffing algorithm to skip traversing the template and find what part to hydrate in constant time.
-
-use once_cell::unsync::OnceCell;
-use std::{
-    cell::{Cell, RefCell},
-    hash::Hash,
-    marker::PhantomData,
-    ptr,
-};
-
-use rustc_hash::FxHashMap;
-
-use bumpalo::Bump;
-
-use crate::{
-    diff::DiffState, dynamic_template_context::TemplateContext, nodes::AttributeDiscription,
-    scopes::ScopeArena, Attribute, AttributeValue, ElementId, Mutations, OwnedAttributeValue,
-    OwnedDynamicNodeMapping, StaticDynamicNodeMapping,
-};
-
-#[derive(Debug, Clone, Copy)]
-pub(crate) struct TemplateRefId(pub usize);
-
-/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize)
-)]
-pub struct StaticCodeLocation {
-    /// the path to the crate that contains the location
-    pub crate_path: &'static str,
-    /// the path within the crate to the file that contains the location
-    pub file_path: &'static str,
-    /// the line number of the location
-    pub line: u32,
-    /// the column number of the location
-    pub column: u32,
-}
-
-/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub struct OwnedCodeLocation {
-    /// the path to the crate that contains the location
-    pub crate_path: String,
-    /// the path within the crate to the file that contains the location
-    pub file_path: String,
-    /// the line number of the location
-    pub line: u32,
-    /// the column number of the location
-    pub column: u32,
-}
-
-/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
-#[derive(Clone, Eq, Debug)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize)
-)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    serde(untagged)
-)]
-pub enum CodeLocation {
-    /// A loctation that is created at compile time.
-    Static(&'static StaticCodeLocation),
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    /// A loctation that is created at runtime.
-    Dynamic(Box<OwnedCodeLocation>),
-}
-
-#[cfg(all(feature = "serialize", any(feature = "hot-reload", debug_assertions)))]
-impl<'de> serde::Deserialize<'de> for CodeLocation {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        Ok(Self::Dynamic(Box::new(OwnedCodeLocation::deserialize(
-            deserializer,
-        )?)))
-    }
-}
-
-impl Hash for CodeLocation {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        match self {
-            CodeLocation::Static(loc) => {
-                let loc: &'static _ = *loc;
-                state.write_usize((loc as *const _) as usize);
-            }
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            CodeLocation::Dynamic(loc) => {
-                let (crate_path, file_path): (&str, &str) = (&loc.crate_path, &loc.file_path);
-                crate_path.hash(state);
-                file_path.hash(state);
-                state.write_u32(loc.line);
-                state.write_u32(loc.column);
-            }
-        }
-    }
-}
-
-impl PartialEq for CodeLocation {
-    fn eq(&self, other: &Self) -> bool {
-        match (self, other) {
-            (Self::Static(l), Self::Static(r)) => ptr::eq(*l, *r),
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            (Self::Dynamic(l), Self::Dynamic(r)) => l == r,
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            (Self::Static(l), Self::Dynamic(r)) => **r == **l,
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            (Self::Dynamic(l), Self::Static(r)) => **l == **r,
-        }
-    }
-}
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-impl PartialEq<StaticCodeLocation> for OwnedCodeLocation {
-    fn eq(&self, other: &StaticCodeLocation) -> bool {
-        self.crate_path == other.crate_path
-            && self.file_path == other.file_path
-            && self.line == other.line
-            && self.column == other.column
-    }
-}
-
-impl CodeLocation {
-    /// Get the line number of the location.
-    pub fn line(&self) -> u32 {
-        match self {
-            CodeLocation::Static(loc) => loc.line,
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            CodeLocation::Dynamic(loc) => loc.line,
-        }
-    }
-
-    /// Get the column number of the location.
-    pub fn column(&self) -> u32 {
-        match self {
-            CodeLocation::Static(loc) => loc.column,
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            CodeLocation::Dynamic(loc) => loc.column,
-        }
-    }
-
-    /// Get the path within the crate to the location.
-    pub fn file_path(&self) -> &str {
-        match self {
-            CodeLocation::Static(loc) => loc.file_path,
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            CodeLocation::Dynamic(loc) => loc.file_path.as_str(),
-        }
-    }
-
-    /// Get the path of the crate to the location.
-    pub fn crate_path(&self) -> &str {
-        match self {
-            CodeLocation::Static(loc) => loc.crate_path,
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            CodeLocation::Dynamic(loc) => loc.crate_path.as_str(),
-        }
-    }
-
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    /// Create an owned code location from a code location.
-    pub fn to_owned(&self) -> OwnedCodeLocation {
-        match self {
-            CodeLocation::Static(loc) => OwnedCodeLocation {
-                crate_path: loc.crate_path.to_owned(),
-                file_path: loc.file_path.to_owned(),
-                line: loc.line,
-                column: loc.column,
-            },
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            CodeLocation::Dynamic(loc) => *loc.clone(),
-        }
-    }
-}
-
-/// get the code location of the code that called this function
-#[macro_export]
-macro_rules! get_line_num {
-    () => {{
-        const LOC: CodeLocation = CodeLocation::Static(&StaticCodeLocation {
-            crate_path: env!("CARGO_MANIFEST_DIR"),
-            file_path: file!(),
-            line: line!(),
-            column: column!(),
-        });
-        LOC
-    }};
-}
-
-/// An Template's unique identifier within the vdom.
-///
-/// `TemplateId` is a refrence to the location in the code the template was created.
-#[derive(Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub struct TemplateId(pub CodeLocation);
-
-/// An Template's unique identifier within the renderer.
-///
-/// `RendererTemplateId` is a unique id of the template sent to the renderer. It is unique across time.
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct RendererTemplateId(pub usize);
-
-impl From<RendererTemplateId> for u64 {
-    fn from(id: RendererTemplateId) -> u64 {
-        id.0 as u64
-    }
-}
-
-/// A TemplateNode's unique identifier.
-///
-/// `TemplateNodeId` is a `usize` that is only unique across the template that contains it, it is not unique across multaple instances of that template.
-#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serialize", serde(transparent))]
-pub struct TemplateNodeId(pub usize);
-
-/// A refrence to a template along with any context needed to hydrate it
-pub struct VTemplateRef<'a> {
-    pub(crate) template_ref_id: Cell<Option<TemplateRefId>>,
-    pub template_id: TemplateId,
-    pub dynamic_context: TemplateContext<'a>,
-    /// The parent of the template
-    pub(crate) parent: Cell<Option<ElementId>>,
-    // any nodes that already have ids assigned to them in the renderer
-    pub node_ids: RefCell<Vec<OnceCell<ElementId>>>,
-}
-
-impl<'a> VTemplateRef<'a> {
-    // update the template with content from the dynamic context
-    pub(crate) fn hydrate<'b: 'a>(
-        &'b self,
-        parent: ElementId,
-        template: &Template,
-        diff_state: &mut DiffState<'a>,
-    ) {
-        fn traverse_seg<'b, T, O, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
-            seg: &PathSeg<T, O>,
-            nodes: &Nodes,
-            diff_state: &mut DiffState<'b>,
-            template_ref: &'b VTemplateRef<'b>,
-            parent: ElementId,
-        ) where
-            Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-            T: Traversable<O>,
-            O: AsRef<[UpdateOp]>,
-        {
-            let mut current_node_id = None;
-            let mut temp_id = false;
-            for op in seg.ops.as_ref() {
-                match op {
-                    UpdateOp::StoreNode(id) => {
-                        if let Some(real_id) = template_ref.try_get_node_id(*id) {
-                            current_node_id = Some(real_id);
-                            nodes.as_ref()[id.0].hydrate(real_id, diff_state, template_ref);
-                        } else {
-                            let real_id = diff_state.scopes.reserve_template_node(
-                                template_ref.template_ref_id.get().unwrap(),
-                                *id,
-                            );
-                            current_node_id = Some(real_id);
-                            template_ref.set_node_id(*id, real_id);
-                            diff_state.mutations.store_with_id(real_id.as_u64());
-                            nodes.as_ref()[id.0].hydrate(real_id, diff_state, template_ref);
-                        }
-                    }
-                    UpdateOp::AppendChild(id) => {
-                        let node = &nodes.as_ref()[id.0];
-                        match &node.node_type {
-                            TemplateNodeType::DynamicNode(idx) => {
-                                if current_node_id.is_none() {
-                                    // create a temporary node to come back to later
-                                    let id = diff_state.scopes.reserve_phantom_node();
-                                    diff_state.mutations.store_with_id(id.as_u64());
-                                    temp_id = true;
-                                    current_node_id = Some(id);
-                                }
-                                let id = current_node_id.unwrap();
-                                let mut created = Vec::new();
-                                let node = template_ref.dynamic_context.resolve_node(*idx);
-                                diff_state.create_node(id, node, &mut created);
-                                diff_state.mutations.set_last_node(id.as_u64());
-                                diff_state.mutations.append_children(None, created);
-                            }
-                            _ => panic!("can only insert dynamic nodes"),
-                        }
-                    }
-                    UpdateOp::InsertBefore(id) | UpdateOp::InsertAfter(id) => {
-                        let node = &nodes.as_ref()[id.0];
-                        match &node.node_type {
-                            TemplateNodeType::DynamicNode(idx) => {
-                                if current_node_id.is_none() {
-                                    // create a temporary node to come back to later
-                                    let id = diff_state.scopes.reserve_phantom_node();
-                                    diff_state.mutations.store_with_id(id.as_u64());
-                                    temp_id = true;
-                                    current_node_id = Some(id);
-                                }
-                                let id = current_node_id.unwrap();
-                                let mut created = Vec::new();
-                                let node = template_ref.dynamic_context.resolve_node(*idx);
-                                diff_state.create_node(parent, node, &mut created);
-                                diff_state.mutations.set_last_node(id.as_u64());
-                                match op {
-                                    UpdateOp::InsertBefore(_) => {
-                                        diff_state.mutations.insert_before(None, created);
-                                    }
-                                    UpdateOp::InsertAfter(_) => {
-                                        diff_state.mutations.insert_after(None, created);
-                                    }
-                                    _ => unreachable!(),
-                                }
-                            }
-                            _ => panic!("can only insert dynamic nodes"),
-                        }
-                    }
-                }
-            }
-            match (seg.traverse.first_child(), seg.traverse.next_sibling()) {
-                (Some(child), Some(sibling)) => {
-                    if current_node_id.is_none() {
-                        // create a temporary node to come back to later
-                        let id = diff_state.scopes.reserve_phantom_node();
-                        diff_state.mutations.store_with_id(id.as_u64());
-                        temp_id = true;
-                        current_node_id = Some(id);
-                    }
-                    let id = current_node_id.unwrap();
-                    diff_state.mutations.first_child();
-                    traverse_seg(child, nodes, diff_state, template_ref, id);
-                    diff_state.mutations.set_last_node(id.as_u64());
-                    diff_state.mutations.next_sibling();
-                    traverse_seg(sibling, nodes, diff_state, template_ref, parent);
-                }
-                (Some(seg), None) => {
-                    if current_node_id.is_none() {
-                        let id = diff_state.scopes.reserve_phantom_node();
-                        diff_state.mutations.store_with_id(id.as_u64());
-                        temp_id = true;
-                        current_node_id = Some(id);
-                    }
-                    let id = current_node_id.unwrap();
-                    diff_state.mutations.first_child();
-                    traverse_seg(seg, nodes, diff_state, template_ref, id);
-                }
-                (None, Some(seg)) => {
-                    diff_state.mutations.next_sibling();
-                    traverse_seg(seg, nodes, diff_state, template_ref, parent);
-                }
-                (None, None) => {}
-            }
-            if temp_id {
-                if let Some(id) = current_node_id {
-                    // remove the temporary node
-                    diff_state.scopes.collect_garbage(id);
-                }
-            }
-        }
-        fn hydrate_inner<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
-            nodes: &Nodes,
-            ctx: (
-                &mut DiffState<'b>,
-                &'b VTemplateRef<'b>,
-                &Template,
-                ElementId,
-            ),
-        ) where
-            Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            let (diff_state, template_ref, template, parent) = ctx;
-
-            match template {
-                Template::Static(s) => {
-                    if let Some(seg) = &s.dynamic_path {
-                        diff_state.mutations.set_last_node(
-                            template_ref.get_node_id(template.root_nodes()[0]).as_u64(),
-                        );
-                        traverse_seg(seg, nodes, diff_state, template_ref, parent);
-                    }
-                }
-                Template::Owned(o) => {
-                    if let Some(seg) = &o.dynamic_path {
-                        diff_state.mutations.set_last_node(
-                            template_ref.get_node_id(template.root_nodes()[0]).as_u64(),
-                        );
-                        traverse_seg(seg, nodes, diff_state, template_ref, parent);
-                    }
-                }
-            }
-        }
-
-        template.with_nodes(
-            hydrate_inner,
-            hydrate_inner,
-            (diff_state, self, template, parent),
-        );
-    }
-
-    pub(crate) fn get_node_id(&self, id: TemplateNodeId) -> ElementId {
-        self.try_get_node_id(id).unwrap()
-    }
-
-    pub(crate) fn try_get_node_id(&self, id: TemplateNodeId) -> Option<ElementId> {
-        let node_ids = self.node_ids.borrow();
-        node_ids.get(id.0).and_then(|cell| cell.get().copied())
-    }
-
-    pub(crate) fn set_node_id(&self, id: TemplateNodeId, real_id: ElementId) {
-        let mut ids = self.node_ids.borrow_mut();
-        if ids.len() <= id.0 {
-            ids.resize(id.0 + 1, OnceCell::new());
-        }
-        ids[id.0].set(real_id).unwrap();
-    }
-}
-
-/// A template that is created at compile time
-#[derive(Debug, PartialEq)]
-pub struct StaticTemplate {
-    /// The nodes in the template
-    pub nodes: StaticTemplateNodes,
-    /// The ids of the root nodes in the template
-    pub root_nodes: StaticRootNodes,
-    /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced.
-    pub dynamic_mapping: StaticDynamicNodeMapping,
-    /// The path to take to update the template with dynamic content (starts from the first root node)
-    pub dynamic_path: Option<StaticPathSeg>,
-}
-
-/// A template that is created at runtime
-#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-pub struct OwnedTemplate {
-    /// The nodes in the template
-    pub nodes: OwnedTemplateNodes,
-    /// The ids of the root nodes in the template
-    pub root_nodes: OwnedRootNodes,
-    /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced.
-    pub dynamic_mapping: OwnedDynamicNodeMapping,
-    /// The path to take to update the template with dynamic content
-    pub dynamic_path: Option<OwnedPathSeg>,
-}
-
-/// A template used to skip diffing on some static parts of the rsx
-#[derive(Debug, Clone, PartialEq)]
-pub enum Template {
-    /// A template that is createded at compile time
-    Static(&'static StaticTemplate),
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    /// A template that is created at runtime
-    Owned(OwnedTemplate),
-}
-
-impl Template {
-    pub(crate) fn create<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: ElementId) {
-        let children = match self {
-            Template::Static(s) => self.count_real_nodes(s.root_nodes),
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            Template::Owned(o) => self.count_real_nodes(&o.root_nodes),
-        };
-        mutations.create_element("template", None, Some(id.into()), children as u32);
-        let empty = match self {
-            Template::Static(s) => s.nodes.is_empty(),
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            Template::Owned(o) => o.nodes.is_empty(),
-        };
-        if !empty {
-            let roots = match self {
-                Template::Static(s) => s.root_nodes,
-                #[cfg(any(feature = "hot-reload", debug_assertions))]
-                Template::Owned(o) => &o.root_nodes,
-            };
-            for root in roots {
-                self.create_node(mutations, bump, *root);
-            }
-        }
-    }
-
-    fn create_node<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: TemplateNodeId) {
-        fn crate_node_inner<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
-            node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
-            ctx: (&mut Mutations<'b>, &'b Bump, &Template),
-        ) where
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            let (mutations, bump, template) = ctx;
-            match &node.node_type {
-                TemplateNodeType::Element(el) => {
-                    let TemplateElement {
-                        tag,
-                        namespace,
-                        attributes,
-                        children,
-                        ..
-                    } = el;
-                    mutations.create_element(
-                        tag,
-                        *namespace,
-                        None,
-                        template.count_real_nodes(children.as_ref()) as u32,
-                    );
-                    for attr in attributes.as_ref() {
-                        if let TemplateAttributeValue::Static(val) = &attr.value {
-                            let val: AttributeValue<'b> = val.allocate(bump);
-                            let attribute = Attribute {
-                                attribute: attr.attribute,
-                                is_static: true,
-                                value: val,
-                            };
-                            mutations.set_attribute(bump.alloc(attribute), None);
-                        }
-                    }
-                    for child in children.as_ref() {
-                        template.create_node(mutations, bump, *child);
-                    }
-                }
-                TemplateNodeType::Text(text) => {
-                    let mut text_iter = text.segments.as_ref().iter();
-                    if let (Some(TextTemplateSegment::Static(txt)), None) =
-                        (text_iter.next(), text_iter.next())
-                    {
-                        mutations.create_text_node(bump.alloc_str(txt.as_ref()), None);
-                    } else {
-                        mutations.create_text_node("", None);
-                    }
-                }
-                TemplateNodeType::DynamicNode(_) => {}
-            }
-        }
-        self.with_node(
-            id,
-            crate_node_inner,
-            crate_node_inner,
-            (mutations, bump, self),
-        );
-    }
-
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    pub(crate) fn with_node<F1, F2, Ctx, R>(
-        &self,
-        id: TemplateNodeId,
-        mut f1: F1,
-        mut f2: F2,
-        ctx: Ctx,
-    ) -> R
-    where
-        F1: FnMut(&StaticTemplateNode, Ctx) -> R,
-        F2: FnMut(&OwnedTemplateNode, Ctx) -> R,
-    {
-        match self {
-            Template::Static(s) => f1(&s.nodes[id.0], ctx),
-            Template::Owned(o) => f2(&o.nodes[id.0], ctx),
-        }
-    }
-
-    #[cfg(not(any(feature = "hot-reload", debug_assertions)))]
-    pub(crate) fn with_node<F1, F2, Ctx, R>(
-        &self,
-        id: TemplateNodeId,
-        mut f1: F1,
-        _f2: F2,
-        ctx: Ctx,
-    ) -> R
-    where
-        F1: FnMut(&StaticTemplateNode, Ctx) -> R,
-        F2: FnMut(&StaticTemplateNode, Ctx) -> R,
-    {
-        match self {
-            Template::Static(s) => f1(&s.nodes[id.0], ctx),
-        }
-    }
-
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    pub(crate) fn with_nodes<'a, F1, F2, Ctx, R>(&'a self, mut f1: F1, mut f2: F2, ctx: Ctx) -> R
-    where
-        F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
-        F2: FnMut(&'a Vec<OwnedTemplateNode>, Ctx) -> R,
-    {
-        match self {
-            Template::Static(s) => f1(&s.nodes, ctx),
-            Template::Owned(o) => f2(&o.nodes, ctx),
-        }
-    }
-
-    #[cfg(not(any(feature = "hot-reload", debug_assertions)))]
-    pub(crate) fn with_nodes<'a, F1, F2, Ctx, R>(&'a self, mut f1: F1, _f2: F2, ctx: Ctx) -> R
-    where
-        F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
-        F2: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
-    {
-        match self {
-            Template::Static(s) => f1(&s.nodes, ctx),
-        }
-    }
-
-    fn count_real_nodes(&self, ids: &[TemplateNodeId]) -> usize {
-        fn count_real_nodes_inner<Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
-            nodes: &Nodes,
-            id: TemplateNodeId,
-        ) -> usize
-        where
-            Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
-            Attributes: AsRef<[TemplateAttribute<V>]>,
-            V: TemplateValue,
-            Children: AsRef<[TemplateNodeId]>,
-            Listeners: AsRef<[usize]>,
-            TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-            Text: AsRef<str>,
-        {
-            match &nodes.as_ref()[id.0].node_type {
-                TemplateNodeType::DynamicNode(_) => 0,
-                TemplateNodeType::Element(_) => 1,
-                TemplateNodeType::Text(_) => 1,
-            }
-        }
-        ids.iter()
-            .map(|id| self.with_nodes(count_real_nodes_inner, count_real_nodes_inner, *id))
-            .sum()
-    }
-
-    pub(crate) fn volatile_attributes<'a>(
-        &'a self,
-    ) -> Box<dyn Iterator<Item = (TemplateNodeId, usize)> + 'a> {
-        match self {
-            Template::Static(s) => Box::new(
-                s.dynamic_mapping
-                    .volatile_attributes
-                    .as_ref()
-                    .iter()
-                    .copied(),
-            ),
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            Template::Owned(o) => Box::new(o.dynamic_mapping.volatile_attributes.iter().copied()),
-        }
-    }
-
-    pub(crate) fn get_dynamic_nodes_for_text_index(&self, idx: usize) -> &[TemplateNodeId] {
-        match self {
-            Template::Static(s) => s.dynamic_mapping.text[idx],
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            Template::Owned(o) => o.dynamic_mapping.text[idx].as_ref(),
-        }
-    }
-
-    pub(crate) fn get_dynamic_nodes_for_attribute_index(
-        &self,
-        idx: usize,
-    ) -> &[(TemplateNodeId, usize)] {
-        match self {
-            Template::Static(s) => s.dynamic_mapping.attributes[idx],
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            Template::Owned(o) => o.dynamic_mapping.attributes[idx].as_ref(),
-        }
-    }
-
-    pub(crate) fn root_nodes(&self) -> &[TemplateNodeId] {
-        match self {
-            Template::Static(s) => s.root_nodes,
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            Template::Owned(o) => &o.root_nodes,
-        }
-    }
-}
-
-/// A array of stack allocated Template nodes
-pub type StaticTemplateNodes = &'static [StaticTemplateNode];
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-/// A vec of heep allocated Template nodes
-pub type OwnedTemplateNodes = Vec<OwnedTemplateNode>;
-
-/// A stack allocated Template node
-pub type StaticTemplateNode = TemplateNode<
-    &'static [TemplateAttribute<StaticAttributeValue>],
-    StaticAttributeValue,
-    &'static [TemplateNodeId],
-    &'static [usize],
-    &'static [TextTemplateSegment<&'static str>],
-    &'static str,
->;
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-/// A heap allocated Template node
-pub type OwnedTemplateNode = TemplateNode<
-    Vec<TemplateAttribute<OwnedAttributeValue>>,
-    OwnedAttributeValue,
-    Vec<TemplateNodeId>,
-    Vec<usize>,
-    Vec<TextTemplateSegment<String>>,
-    String,
->;
-
-/// A stack allocated list of root Template nodes
-pub type StaticRootNodes = &'static [TemplateNodeId];
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-/// A heap allocated list of root Template nodes
-pub type OwnedRootNodes = Vec<TemplateNodeId>;
-
-/// Templates can only contain a limited subset of VNodes and keys are not needed, as diffing will be skipped.
-/// Dynamic parts of the Template are inserted into the VNode using the `TemplateContext` by traversing the tree in order and filling in dynamic parts
-/// This template node is generic over the storage of the nodes to allow for owned and &'static versions.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub struct TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>
-where
-    Attributes: AsRef<[TemplateAttribute<V>]>,
-    V: TemplateValue,
-    Children: AsRef<[TemplateNodeId]>,
-    Listeners: AsRef<[usize]>,
-    TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-    Text: AsRef<str>,
-{
-    /// The ID of the [`TemplateNode`]. Note that this is not an elenemt id, and should be allocated seperately from VNodes on the frontend.
-    pub id: TemplateNodeId,
-    /// The depth of the node in the template node tree
-    /// Root nodes have a depth of 0
-    pub depth: usize,
-    /// The type of the [`TemplateNode`].
-    pub node_type: TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>,
-    /// The parent of this node.
-    pub parent: Option<TemplateNodeId>,
-}
-
-impl<Attributes, V, Children, Listeners, TextSegments, Text>
-    TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>
-where
-    Attributes: AsRef<[TemplateAttribute<V>]>,
-    V: TemplateValue,
-    Children: AsRef<[TemplateNodeId]>,
-    Listeners: AsRef<[usize]>,
-    TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-    Text: AsRef<str>,
-{
-    fn hydrate<'b>(
-        &self,
-        real_node_id: ElementId,
-        diff_state: &mut DiffState<'b>,
-        template_ref: &'b VTemplateRef<'b>,
-    ) {
-        match &self.node_type {
-            TemplateNodeType::Element(el) => {
-                let TemplateElement {
-                    attributes,
-                    listeners,
-                    ..
-                } = el;
-                for attr in attributes.as_ref() {
-                    if let TemplateAttributeValue::Dynamic(idx) = attr.value {
-                        let attribute = Attribute {
-                            attribute: attr.attribute,
-                            value: template_ref
-                                .dynamic_context
-                                .resolve_attribute(idx)
-                                .to_owned(),
-                            is_static: false,
-                        };
-                        let scope_bump = diff_state.current_scope_bump();
-                        diff_state.mutations.set_attribute(
-                            scope_bump.alloc(attribute),
-                            Some(real_node_id.as_u64()),
-                        );
-                    }
-                }
-                for listener_idx in listeners.as_ref() {
-                    let listener = template_ref.dynamic_context.resolve_listener(*listener_idx);
-                    listener.mounted_node.set(Some(real_node_id));
-                    diff_state
-                        .mutations
-                        .new_event_listener(listener, diff_state.current_scope());
-                }
-            }
-            TemplateNodeType::Text(text) => {
-                let scope_bump = diff_state.current_scope_bump();
-                let mut bump_str =
-                    bumpalo::collections::String::with_capacity_in(text.min_size, scope_bump);
-                template_ref
-                    .dynamic_context
-                    .resolve_text_into(text, &mut bump_str);
-
-                diff_state
-                    .mutations
-                    .set_text(bump_str.into_bump_str(), Some(real_node_id.as_u64()));
-            }
-            TemplateNodeType::DynamicNode(_) => {}
-        }
-    }
-}
-
-/// A template for an attribute
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub struct TemplateAttribute<V: TemplateValue> {
-    /// The discription of the attribute
-    pub attribute: AttributeDiscription,
-    /// The value of the attribute
-    pub value: TemplateAttributeValue<V>,
-}
-
-/// A template attribute value that is either dynamic or static
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub enum TemplateAttributeValue<V: TemplateValue> {
-    /// A static attribute
-    Static(V),
-    /// A dynamic attribute
-    Dynamic(usize),
-}
-
-/// The value for an attribute in a template
-pub trait TemplateValue {
-    /// Allocates the attribute in a bump allocator
-    fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b>;
-}
-
-impl TemplateValue for StaticAttributeValue {
-    fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b> {
-        match self.clone() {
-            StaticAttributeValue::Text(txt) => AttributeValue::Text(bump.alloc_str(txt)),
-            StaticAttributeValue::Bytes(bytes) => {
-                AttributeValue::Bytes(bump.alloc_slice_copy(bytes))
-            }
-            StaticAttributeValue::Float32(f) => AttributeValue::Float32(f),
-            StaticAttributeValue::Float64(f) => AttributeValue::Float64(f),
-            StaticAttributeValue::Int32(i) => AttributeValue::Int32(i),
-            StaticAttributeValue::Int64(i) => AttributeValue::Int64(i),
-            StaticAttributeValue::Uint32(u) => AttributeValue::Uint32(u),
-            StaticAttributeValue::Uint64(u) => AttributeValue::Uint64(u),
-            StaticAttributeValue::Bool(b) => AttributeValue::Bool(b),
-            StaticAttributeValue::Vec3Float(f1, f2, f3) => AttributeValue::Vec3Float(f1, f2, f3),
-            StaticAttributeValue::Vec3Int(i1, i2, i3) => AttributeValue::Vec3Int(i1, i2, i3),
-            StaticAttributeValue::Vec3Uint(u1, u2, u3) => AttributeValue::Vec3Uint(u1, u2, u3),
-            StaticAttributeValue::Vec4Float(f1, f2, f3, f4) => {
-                AttributeValue::Vec4Float(f1, f2, f3, f4)
-            }
-            StaticAttributeValue::Vec4Int(i1, i2, i3, i4) => {
-                AttributeValue::Vec4Int(i1, i2, i3, i4)
-            }
-            StaticAttributeValue::Vec4Uint(u1, u2, u3, u4) => {
-                AttributeValue::Vec4Uint(u1, u2, u3, u4)
-            }
-        }
-    }
-}
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-impl TemplateValue for OwnedAttributeValue {
-    fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b> {
-        match self.clone() {
-            OwnedAttributeValue::Text(txt) => AttributeValue::Text(bump.alloc(txt)),
-            OwnedAttributeValue::Bytes(bytes) => AttributeValue::Bytes(bump.alloc(bytes)),
-            OwnedAttributeValue::Float32(f) => AttributeValue::Float32(f),
-            OwnedAttributeValue::Float64(f) => AttributeValue::Float64(f),
-            OwnedAttributeValue::Int32(i) => AttributeValue::Int32(i),
-            OwnedAttributeValue::Int64(i) => AttributeValue::Int64(i),
-            OwnedAttributeValue::Uint32(u) => AttributeValue::Uint32(u),
-            OwnedAttributeValue::Uint64(u) => AttributeValue::Uint64(u),
-            OwnedAttributeValue::Bool(b) => AttributeValue::Bool(b),
-            OwnedAttributeValue::Vec3Float(f1, f2, f3) => AttributeValue::Vec3Float(f1, f2, f3),
-            OwnedAttributeValue::Vec3Int(i1, i2, i3) => AttributeValue::Vec3Int(i1, i2, i3),
-            OwnedAttributeValue::Vec3Uint(u1, u2, u3) => AttributeValue::Vec3Uint(u1, u2, u3),
-            OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => {
-                AttributeValue::Vec4Float(f1, f2, f3, f4)
-            }
-            OwnedAttributeValue::Vec4Int(i1, i2, i3, i4) => AttributeValue::Vec4Int(i1, i2, i3, i4),
-            OwnedAttributeValue::Vec4Uint(u1, u2, u3, u4) => {
-                AttributeValue::Vec4Uint(u1, u2, u3, u4)
-            }
-            OwnedAttributeValue::Any(owned) => {
-                AttributeValue::Any(crate::ArbitraryAttributeValue {
-                    value: bump.alloc(owned.value),
-                    cmp: owned.cmp,
-                })
-            }
-        }
-    }
-}
-
-/// The kind of node the template is.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub enum TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>
-where
-    Attributes: AsRef<[TemplateAttribute<V>]>,
-    Children: AsRef<[TemplateNodeId]>,
-    Listeners: AsRef<[usize]>,
-    V: TemplateValue,
-    TextSegments: AsRef<[TextTemplateSegment<Text>]>,
-    Text: AsRef<str>,
-{
-    /// A element node (e.g. div{}).
-    Element(TemplateElement<Attributes, V, Children, Listeners>),
-    /// A text node (e.g. "Hello World").
-    Text(TextTemplate<TextSegments, Text>),
-    /// A dynamic node (e.g. (0..10).map(|i| cx.render(rsx!{div{}})))
-    /// The index in the dynamic node array this node should be replaced with
-    DynamicNode(usize),
-}
-
-type StaticStr = &'static str;
-
-/// A element template
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub struct TemplateElement<Attributes, V, Children, Listeners>
-where
-    Attributes: AsRef<[TemplateAttribute<V>]>,
-    Children: AsRef<[TemplateNodeId]>,
-    Listeners: AsRef<[usize]>,
-    V: TemplateValue,
-{
-    /// The tag name of the element
-    #[cfg_attr(
-        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-        serde(deserialize_with = "crate::util::deserialize_static_leaky")
-    )]
-    pub tag: StaticStr,
-    /// The namespace of the element
-    #[cfg_attr(
-        all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-        serde(deserialize_with = "crate::util::deserialize_static_leaky_ns")
-    )]
-    pub namespace: Option<StaticStr>,
-    /// The attributes that modify the element
-    pub attributes: Attributes,
-    /// The ids of the children of the element
-    pub children: Children,
-    /// The ids of the listeners of the element
-    pub listeners: Listeners,
-    value: PhantomData<V>,
-}
-
-impl<Attributes, V, Children, Listeners> TemplateElement<Attributes, V, Children, Listeners>
-where
-    Attributes: AsRef<[TemplateAttribute<V>]>,
-    Children: AsRef<[TemplateNodeId]>,
-    Listeners: AsRef<[usize]>,
-    V: TemplateValue,
-{
-    /// create a new element template
-    pub const fn new(
-        tag: &'static str,
-        namespace: Option<&'static str>,
-        attributes: Attributes,
-        children: Children,
-        listeners: Listeners,
-    ) -> Self {
-        TemplateElement {
-            tag,
-            namespace,
-            attributes,
-            children,
-            listeners,
-            value: PhantomData,
-        }
-    }
-}
-
-/// A template for some text that may contain dynamic segments for example "Hello {name}" contains the static segment "Hello " and the dynamic segment "{name}".
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub struct TextTemplate<Segments, Text>
-where
-    Segments: AsRef<[TextTemplateSegment<Text>]>,
-    Text: AsRef<str>,
-{
-    /// The segments of the template.
-    pub segments: Segments,
-    /// The minimum size of the output text.
-    pub min_size: usize,
-    text: PhantomData<Text>,
-}
-
-impl<Segments, Text> TextTemplate<Segments, Text>
-where
-    Segments: AsRef<[TextTemplateSegment<Text>]>,
-    Text: AsRef<str>,
-{
-    /// create a new template from the segments it is composed of.
-    pub const fn new(segments: Segments, min_size: usize) -> Self {
-        TextTemplate {
-            segments,
-            min_size,
-            text: PhantomData,
-        }
-    }
-}
-
-/// A segment of a text template that may be dynamic or static.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-pub enum TextTemplateSegment<Text>
-where
-    Text: AsRef<str>,
-{
-    /// A constant text segment
-    Static(Text),
-    /// A dynamic text segment
-    Dynamic(usize),
-}
-
-/// A template value that is created at compile time that is sync.
-#[derive(Debug, Clone, PartialEq)]
-#[allow(missing_docs)]
-pub enum StaticAttributeValue {
-    Text(&'static str),
-    Float32(f32),
-    Float64(f64),
-    Int32(i32),
-    Int64(i64),
-    Uint32(u32),
-    Uint64(u64),
-    Bool(bool),
-
-    Vec3Float(f32, f32, f32),
-    Vec3Int(i32, i32, i32),
-    Vec3Uint(u32, u32, u32),
-
-    Vec4Float(f32, f32, f32, f32),
-    Vec4Int(i32, i32, i32, i32),
-    Vec4Uint(u32, u32, u32, u32),
-
-    Bytes(&'static [u8]),
-}
-
-#[derive(Default)]
-pub(crate) struct TemplateResolver {
-    // maps a id to the rendererid and if that template needs to be re-created
-    pub template_id_mapping: FxHashMap<TemplateId, (ElementId, bool)>,
-}
-
-impl TemplateResolver {
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    pub fn mark_dirty(&mut self, id: &TemplateId) {
-        if let Some((_, dirty)) = self.template_id_mapping.get_mut(id) {
-            println!("marking dirty {:?}", id);
-            *dirty = true;
-        } else {
-            println!("failed {:?}", id);
-        }
-    }
-
-    pub fn is_dirty(&self, id: &TemplateId) -> bool {
-        matches!(self.template_id_mapping.get(id), Some((_, true)))
-    }
-
-    // returns (id, if the id was created)
-    pub fn get_or_create_client_id(
-        &mut self,
-        template_id: &TemplateId,
-        scopes: &ScopeArena,
-    ) -> (ElementId, bool) {
-        if let Some(id) = self.template_id_mapping.get(template_id) {
-            *id
-        } else {
-            let renderer_id = scopes.reserve_phantom_node();
-            self.template_id_mapping
-                .insert(template_id.clone(), (renderer_id, false));
-            (renderer_id, true)
-        }
-    }
-}
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-/// A message telling the virtual dom to set a template
-#[derive(Debug, Clone)]
-#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-pub struct SetTemplateMsg(pub TemplateId, pub OwnedTemplate);
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-/// A path segment that lives on the heap.
-pub type OwnedPathSeg = PathSeg<OwnedTraverse, Vec<UpdateOp>>;
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-#[derive(Debug, Clone, PartialEq)]
-/// A traverse message that lives on the heap.
-pub enum OwnedTraverse {
-    /// Halt traversal
-    Halt,
-    /// Traverse to the first child of the current node.
-    FirstChild(Box<OwnedPathSeg>),
-    /// Traverse to the next sibling of the current node.
-    NextSibling(Box<OwnedPathSeg>),
-    /// Traverse to the both the first child and next sibling of the current node.
-    Both(Box<(OwnedPathSeg, OwnedPathSeg)>),
-}
-
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-impl Traversable<Vec<UpdateOp>> for OwnedTraverse {
-    fn first_child(&self) -> Option<&OwnedPathSeg> {
-        match self {
-            OwnedTraverse::FirstChild(p) => Some(p),
-            OwnedTraverse::Both(ps) => Some(&ps.0),
-            _ => None,
-        }
-    }
-
-    fn next_sibling(&self) -> Option<&OwnedPathSeg> {
-        match self {
-            OwnedTraverse::NextSibling(p) => Some(p),
-            OwnedTraverse::Both(ps) => Some(&ps.1),
-            _ => None,
-        }
-    }
-}
-
-/// A path segment that lives on the stack.
-pub type StaticPathSeg = PathSeg<StaticTraverse, &'static [UpdateOp]>;
-
-#[derive(Debug, Clone, PartialEq)]
-/// A traverse message that lives on the stack.
-pub enum StaticTraverse {
-    /// Halt traversal
-    Halt,
-    /// Traverse to the first child of the current node.
-    FirstChild(&'static StaticPathSeg),
-    /// Traverse to the next sibling of the current node.
-    NextSibling(&'static StaticPathSeg),
-    /// Traverse to the both the first child and next sibling of the current node.
-    Both(&'static (StaticPathSeg, StaticPathSeg)),
-}
-
-impl Traversable<&'static [UpdateOp]> for StaticTraverse {
-    fn first_child(&self) -> Option<&StaticPathSeg> {
-        match self {
-            StaticTraverse::FirstChild(p) => Some(p),
-            StaticTraverse::Both((p, _)) => Some(p),
-            _ => None,
-        }
-    }
-
-    fn next_sibling(&self) -> Option<&StaticPathSeg> {
-        match self {
-            StaticTraverse::NextSibling(p) => Some(p),
-            StaticTraverse::Both((_, p)) => Some(p),
-            _ => None,
-        }
-    }
-}
-
-pub trait Traversable<O: AsRef<[UpdateOp]>>
-where
-    Self: Sized,
-{
-    fn first_child(&self) -> Option<&PathSeg<Self, O>>;
-    fn next_sibling(&self) -> Option<&PathSeg<Self, O>>;
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-/// A path segment that defines a way to traverse a template node and resolve dynamic sections.
-pub struct PathSeg<T: Traversable<O>, O: AsRef<[UpdateOp]>> {
-    /// The operation to perform on the current node.
-    pub ops: O,
-    /// The next traversal step.
-    pub traverse: T,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[cfg_attr(
-    all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
-    derive(serde::Serialize, serde::Deserialize)
-)]
-/// A operation that can be applied to a template node when intially updating it.
-pub enum UpdateOp {
-    /// Store a dynamic node on the renderer
-    StoreNode(TemplateNodeId),
-    /// Insert a dynamic node before the current node
-    InsertBefore(TemplateNodeId),
-    /// Insert a dynamic node after the current node
-    InsertAfter(TemplateNodeId),
-    /// Append a dynamic node to the current node
-    AppendChild(TemplateNodeId),
-}

+ 0 - 127
packages/core/src/util.rs

@@ -1,127 +0,0 @@
-use crate::innerlude::{VNode, VirtualDom};
-
-/// An iterator that only yields "real" [`Element`]s. IE only Elements that are
-/// not [`VNode::Component`] or [`VNode::Fragment`], .
-pub struct ElementIdIterator<'a> {
-    vdom: &'a VirtualDom,
-
-    // Heuristically 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> ElementIdIterator<'a> {
-    /// Create a new iterator from the given [`VirtualDom`] and [`VNode`]
-    ///
-    /// This will allow you to iterate through all the real childrne of the [`VNode`].
-    pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
-        Self {
-            vdom,
-            stack: smallvec::smallvec![(0, node)],
-        }
-    }
-}
-
-impl<'a> Iterator for ElementIdIterator<'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
-                    VNode::Element(_)
-                    | VNode::Text(_)
-                    | VNode::Placeholder(_)
-                    | VNode::TemplateRef(_) => {
-                        // We've recursed INTO an element/text
-                        // We need to recurse *out* of it and move forward to the next
-                        // println!("Found element! Returning it!");
-                        should_pop = true;
-                        returned_node = Some(&**node);
-                    }
-
-                    // If we get a fragment we push the next child
-                    VNode::Fragment(frag) => {
-                        let count = *count as usize;
-                        if count >= frag.children.len() {
-                            should_pop = true;
-                        } else {
-                            should_push = Some(&frag.children[count]);
-                        }
-                    }
-
-                    // For components, we load their root and push them onto the stack
-                    VNode::Component(sc) => {
-                        let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
-                        // Simply swap the current node on the stack with the root of the component
-                        *node = scope.root_node();
-                    }
-                }
-            } 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
-    }
-}
-
-/// This intentionally leaks once per element name to allow more flexability when hot reloding templetes
-#[cfg(all(any(feature = "hot-reload", debug_assertions), feature = "serde"))]
-mod leaky {
-    use std::sync::Mutex;
-
-    use once_cell::sync::Lazy;
-    use rustc_hash::FxHashSet;
-    static STATIC_CACHE: Lazy<Mutex<FxHashSet<&'static str>>> =
-        Lazy::new(|| Mutex::new(FxHashSet::default()));
-
-    pub fn deserialize_static_leaky<'de, D>(d: D) -> Result<&'static str, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        use serde::Deserialize;
-        let s = <&str>::deserialize(d)?;
-        Ok(if let Some(stat) = STATIC_CACHE.lock().unwrap().get(s) {
-            *stat
-        } else {
-            Box::leak(s.into())
-        })
-    }
-
-    pub fn deserialize_static_leaky_ns<'de, D>(d: D) -> Result<Option<&'static str>, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        use serde::Deserialize;
-        Ok(<Option<&str>>::deserialize(d)?.map(|s| {
-            if let Some(stat) = STATIC_CACHE.lock().unwrap().get(s) {
-                *stat
-            } else {
-                Box::leak(s.into())
-            }
-        }))
-    }
-}
-
-#[cfg(all(any(feature = "hot-reload", debug_assertions), feature = "serde"))]
-pub use leaky::*;

+ 401 - 593
packages/core/src/virtual_dom.rs

@@ -2,17 +2,30 @@
 //!
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
 
-use std::{collections::VecDeque, iter::FromIterator, task::Poll};
-
-use crate::diff::DiffState;
-use crate::innerlude::*;
-use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
-use futures_util::{future::poll_fn, StreamExt};
-use rustc_hash::FxHashSet;
+use crate::{
+    any_props::VProps,
+    arena::{ElementId, ElementRef},
+    innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
+    mutations::Mutation,
+    nodes::RenderReturn,
+    nodes::{Template, TemplateId},
+    scheduler::SuspenseId,
+    scopes::{ScopeId, ScopeState},
+    AttributeValue, Element, Event, Scope, SuspenseContext,
+};
+use futures_util::{pin_mut, StreamExt};
+use slab::Slab;
+use std::{
+    any::Any,
+    borrow::BorrowMut,
+    cell::Cell,
+    collections::{BTreeSet, HashMap},
+    future::Future,
+    rc::Rc,
+};
 
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
-///
 /// ## Guide
 ///
 /// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
@@ -50,95 +63,112 @@ use rustc_hash::FxHashSet;
 /// let edits = vdom.rebuild();
 /// ```
 ///
-/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
+/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
 ///
 /// ```rust, ignore
-/// let channel = vdom.get_scheduler_channel();
-/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
-///     // ...
-/// }))
+/// vdom.handle_event(event);
 /// ```
 ///
-/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
+/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
 ///
 /// ```rust, ignore
 /// vdom.wait_for_work().await;
 /// ```
 ///
-/// Once work is ready, call [`VirtualDom::work_with_deadline`] to compute the differences between the previous and
+/// Once work is ready, call [`VirtualDom::render_with_deadline`] to compute the differences between the previous and
 /// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
 /// handled by the renderer.
 ///
 /// ```rust, ignore
-/// let mutations = vdom.work_with_deadline(|| false);
-/// for edit in mutations {
-///     apply(edit);
+/// let mutations = vdom.work_with_deadline(tokio::time::sleep(Duration::from_millis(100)));
+///
+/// for edit in mutations.edits {
+///     real_dom.apply(edit);
 /// }
 /// ```
 ///
+/// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`] or pass an immediately
+/// ready future to [`VirtualDom::render_with_deadline`].
+///
+///
 /// ## Building an event loop around Dioxus:
 ///
 /// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
-///
 /// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     cx.render(rsx!{
+/// fn app(cx: Scope) -> Element {
+///     cx.render(rsx! {
 ///         div { "Hello World" }
 ///     })
 /// }
 ///
-/// async fn main() {
-///     let mut dom = VirtualDom::new(App);
+/// let dom = VirtualDom::new(app);
 ///
-///     let mut inital_edits = dom.rebuild();
-///     apply_edits(inital_edits);
+/// real_dom.apply(dom.rebuild());
 ///
-///     loop {
-///         dom.wait_for_work().await;
-///         let frame_timeout = TimeoutFuture::new(Duration::from_millis(16));
-///         let deadline = || (&mut frame_timeout).now_or_never();
-///         let edits = dom.run_with_deadline(deadline).await;
-///         apply_edits(edits);
+/// loop {
+///     select! {
+///         _ = dom.wait_for_work() => {}
+///         evt = real_dom.wait_for_event() => dom.handle_event(evt),
 ///     }
+///
+///     real_dom.apply(dom.render_immediate());
+/// }
+/// ```
+///
+/// ## Waiting for suspense
+///
+/// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other usecases
+/// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
+/// [`VirtualDom::render_with_deadline`] method:
+///
+/// ```rust, ignore
+/// let dom = VirtualDom::new(app);
+///
+/// let deadline = tokio::time::sleep(Duration::from_millis(100));
+/// let edits = dom.render_with_deadline(deadline).await;
+/// ```
+///
+/// ## Use with streaming
+///
+/// If not all rendering is done by the deadline, it might be worthwhile to stream the rest later. To do this, we
+/// suggest rendering with a deadline, and then looping between [`VirtualDom::wait_for_work`] and render_immediate until
+/// no suspended work is left.
+///
+/// ```rust, ignore
+/// let dom = VirtualDom::new(app);
+///
+/// let deadline = tokio::time::sleep(Duration::from_millis(20));
+/// let edits = dom.render_with_deadline(deadline).await;
+///
+/// real_dom.apply(edits);
+///
+/// while dom.has_suspended_work() {
+///    dom.wait_for_work().await;
+///    real_dom.apply(dom.render_immediate());
 /// }
 /// ```
 pub struct VirtualDom {
-    root: ElementId,
-    scopes: ScopeArena,
-
-    pending_messages: VecDeque<SchedulerMsg>,
-    dirty_scopes: Vec<ScopeId>,
-    removed_scopes: FxHashSet<ScopeId>,
-
-    channel: (
-        UnboundedSender<SchedulerMsg>,
-        UnboundedReceiver<SchedulerMsg>,
-    ),
-}
+    pub(crate) templates: HashMap<TemplateId, Template<'static>>,
+    pub(crate) scopes: Slab<Box<ScopeState>>,
+    pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
+    pub(crate) scheduler: Rc<Scheduler>,
 
-/// The type of message that can be sent to the scheduler.
-///
-/// These messages control how the scheduler will process updates to the UI.
-#[derive(Debug)]
-pub enum SchedulerMsg {
-    /// Events from the Renderer
-    Event(UserEvent),
+    // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
+    pub(crate) elements: Slab<ElementRef>,
 
-    /// Immediate updates from Components that mark them as dirty
-    Immediate(ScopeId),
+    // While diffing we need some sort of way of breaking off a stream of suspended mutations.
+    pub(crate) scope_stack: Vec<ScopeId>,
+    pub(crate) collected_leaves: Vec<SuspenseId>,
 
-    /// Mark all components as dirty and update them
-    DirtyAll,
+    // Whenever a suspense tree is finished, we push its boundary onto this stack.
+    // When "render_with_deadline" is called, we pop the stack and return the mutations
+    pub(crate) finished_fibers: Vec<ScopeId>,
 
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    /// Mark a template as dirty, used for hot reloading
-    SetTemplate(Box<SetTemplateMsg>),
+    pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
 
-    /// New tasks from components that should be polled when the next poll is ready
-    NewTask(ScopeId),
+    pub(crate) mutations: Mutations<'static>,
 }
 
-// Methods to create the VirtualDom
 impl VirtualDom {
     /// Create a new VirtualDom with a component that does not have special props.
     ///
@@ -160,8 +190,8 @@ impl VirtualDom {
     /// ```
     ///
     /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
-    pub fn new(root: Component) -> Self {
-        Self::new_with_props(root, ())
+    pub fn new(app: fn(Scope) -> Element) -> Self {
+        Self::new_with_props(app, ())
     }
 
     /// Create a new VirtualDom with the given properties for the root component.
@@ -194,626 +224,404 @@ impl VirtualDom {
     /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
     /// let mutations = dom.rebuild();
     /// ```
-    pub fn new_with_props<P>(root: Component<P>, root_props: P) -> Self
-    where
-        P: 'static,
-    {
-        Self::new_with_props_and_scheduler(
-            root,
-            root_props,
-            futures_channel::mpsc::unbounded::<SchedulerMsg>(),
-        )
-    }
-
-    /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
-    ///
-    /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
-    /// VirtualDom to be created just to retrieve its channel receiver.
-    ///
-    /// ```rust, ignore
-    /// let channel = futures_channel::mpsc::unbounded();
-    /// let dom = VirtualDom::new_with_scheduler(Example, (), channel);
-    /// ```
-    pub fn new_with_props_and_scheduler<P: 'static>(
-        root: Component<P>,
-        root_props: P,
-        channel: (
-            UnboundedSender<SchedulerMsg>,
-            UnboundedReceiver<SchedulerMsg>,
-        ),
-    ) -> Self {
-        let scopes = ScopeArena::new(channel.0.clone());
-
-        scopes.new_with_key(
-            root as ComponentPtr,
-            Box::new(VComponentProps {
-                props: root_props,
-                memo: |_a, _b| unreachable!("memo on root will neve be run"),
-                render_fn: root,
-            }),
-            None,
-            ElementId(0),
+    pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
+        let (tx, rx) = futures_channel::mpsc::unbounded();
+        let mut dom = Self {
+            rx,
+            scheduler: Scheduler::new(tx),
+            templates: Default::default(),
+            scopes: Slab::default(),
+            elements: Default::default(),
+            scope_stack: Vec::new(),
+            dirty_scopes: BTreeSet::new(),
+            collected_leaves: Vec::new(),
+            finished_fibers: Vec::new(),
+            mutations: Mutations::default(),
+        };
+
+        let root = dom.new_scope(
+            Box::new(VProps::new(root, |_, _| unreachable!(), root_props)),
+            "app",
         );
 
-        Self {
-            root: ElementId(0),
-            scopes,
-            channel,
-            dirty_scopes: Vec::from_iter([ScopeId(0)]),
-            pending_messages: VecDeque::new(),
-            removed_scopes: FxHashSet::default(),
-        }
-    }
+        // The root component is always a suspense boundary for any async children
+        // This could be unexpected, so we might rethink this behavior later
+        //
+        // We *could* just panic if the suspense boundary is not found
+        root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0))));
 
-    /// Get the [`Scope`] for the root component.
-    ///
-    /// This is useful for traversing the tree from the root for heuristics or alternative renderers that use Dioxus
-    /// directly.
-    ///
-    /// This method is equivalent to calling `get_scope(ScopeId(0))`
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut dom = VirtualDom::new(example);
-    /// dom.rebuild();
-    ///
-    ///
-    /// ```
-    pub fn base_scope(&self) -> &ScopeState {
-        self.get_scope(ScopeId(0)).unwrap()
+        // Unlike react, we provide a default error boundary that just renders the error as a string
+        root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
+
+        // the root element is always given element ID 0 since it's the container for the entire tree
+        dom.elements.insert(ElementRef::null());
+
+        dom
     }
 
-    /// Get the [`ScopeState`] for a component given its [`ScopeId`]
-    ///
-    /// # Example
-    ///
-    ///
+    /// Get the state for any scope given its ID
     ///
+    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
     pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
-        self.scopes.get_scope(id)
+        self.scopes.get(id.0).map(|f| f.as_ref())
     }
 
-    /// Get an [`UnboundedSender`] handle to the channel used by the scheduler.
-    ///
-    /// # Example
+    /// Get the single scope at the top of the VirtualDom tree that will always be around
     ///
-    /// ```rust, ignore
-    /// let dom = VirtualDom::new(App);
-    /// let sender = dom.get_scheduler_channel();
-    /// ```
-    pub fn get_scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
-        self.channel.0.clone()
-    }
-
-    /// Try to get an element from its ElementId
-    pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
-        self.scopes.get_element(id)
+    /// This scope has a ScopeId of 0 and is the root of the tree
+    pub fn base_scope(&self) -> &ScopeState {
+        self.scopes.get(0).unwrap()
     }
 
-    /// Add a new message to the scheduler queue directly.
-    ///
-    ///
-    /// This method makes it possible to send messages to the scheduler from outside the VirtualDom without having to
-    /// call `get_schedule_channel` and then `send`.
+    /// Build the virtualdom with a global context inserted into the base scope
     ///
-    /// # Example
-    /// ```rust, ignore
-    /// let dom = VirtualDom::new(App);
-    /// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
-    /// ```
-    pub fn handle_message(&mut self, msg: SchedulerMsg) {
-        if self.channel.0.unbounded_send(msg).is_ok() {
-            self.process_all_messages();
-        }
+    /// This is useful for what is essentially dependency injection when building the app
+    pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
+        self.base_scope().provide_context(context);
+        self
     }
 
-    /// Check if the [`VirtualDom`] has any pending updates or work to be done.
-    ///
-    /// # Example
+    /// Manually mark a scope as requiring a re-render
     ///
-    /// ```rust, ignore
-    /// let dom = VirtualDom::new(App);
-    ///
-    /// // the dom is "dirty" when it is started and must be rebuilt to get the first render
-    /// assert!(dom.has_any_work());
-    /// ```
-    pub fn has_work(&self) -> bool {
-        !(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
+    /// Whenever the VirtualDom "works", it will re-render this scope
+    pub fn mark_dirty(&mut self, id: ScopeId) {
+        let height = self.scopes[id.0].height;
+        self.dirty_scopes.insert(DirtyScope { height, id });
     }
 
-    /// Wait for the scheduler to have any work.
-    ///
-    /// This method polls the internal future queue *and* the scheduler channel.
-    /// To add work to the VirtualDom, insert a message via the scheduler channel.
-    ///
-    /// This lets us poll async tasks during idle periods without blocking the main thread.
+    /// Determine whether or not a scope is currently in a suspended state
     ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let dom = VirtualDom::new(App);
-    /// let sender = dom.get_scheduler_channel();
-    /// ```
-    pub async fn wait_for_work(&mut self) {
-        loop {
-            if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
-                break;
-            }
-
-            if self.pending_messages.is_empty() {
-                if self.scopes.tasks.has_tasks() {
-                    use futures_util::future::{select, Either};
+    /// This does not mean the scope is waiting on its own futures, just that the tree that the scope exists in is
+    /// currently suspended.
+    pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
+        !self.scopes[id.0]
+            .consume_context::<Rc<SuspenseContext>>()
+            .unwrap()
+            .waiting_on
+            .borrow()
+            .is_empty()
+    }
 
-                    let scopes = &mut self.scopes;
-                    let task_poll = poll_fn(|cx| {
-                        let mut tasks = scopes.tasks.tasks.borrow_mut();
-                        tasks.retain(|_, task| task.as_mut().poll(cx).is_pending());
+    /// Determine if the tree is at all suspended. Used by SSR and other outside mechanisms to determine if the tree is
+    /// ready to be rendered.
+    pub fn has_suspended_work(&self) -> bool {
+        !self.scheduler.leaves.borrow().is_empty()
+    }
 
-                        match tasks.is_empty() {
-                            true => Poll::Ready(()),
-                            false => Poll::Pending,
-                        }
-                    });
+    /// Call a listener inside the VirtualDom with data from outside the VirtualDom.
+    ///
+    /// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
+    /// this method does not give any indication as to the success of the listener call. If the listener is not found,
+    /// nothing will happen.
+    ///
+    /// It is up to the listeners themselves to mark nodes as dirty.
+    ///
+    /// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
+    pub fn handle_event(
+        &mut self,
+        name: &str,
+        data: Rc<dyn Any>,
+        element: ElementId,
+        bubbles: bool,
+    ) {
+        /*
+        ------------------------
+        The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
+        we find the target path.
+
+        With the target path, we try and move up to the parent until there is no parent.
+        Due to how bubbling works, we call the listeners before walking to the parent.
+
+        If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
+        ----------------------
+
+        For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the
+        right.
+
+        |           <-- yes (is ascendant)
+        | | |       <-- no  (is not direct ascendant)
+        | |         <-- yes (is ascendant)
+        | | | | |   <--- target element, break early, don't check other listeners
+        | | |       <-- no, broke early
+        |           <-- no, broke early
+        */
+        let mut parent_path = self.elements.get(element.0);
+        let mut listeners = vec![];
+
+        // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
+        let uievent = Event {
+            propogates: Rc::new(Cell::new(bubbles)),
+            data,
+        };
+
+        // Loop through each dynamic attribute in this template before moving up to the template's parent.
+        while let Some(el_ref) = parent_path {
+            // safety: we maintain references of all vnodes in the element slab
+            let template = unsafe { &*el_ref.template };
+            let target_path = el_ref.path;
+
+            for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                let this_path = template.template.attr_paths[idx];
+
+                // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
+                // we should fix this so that we look for "onclick" instead of "click"
+                if &attr.name[2..] == name && target_path.is_ascendant(&this_path) {
+                    listeners.push(&attr.value);
+
+                    // Break if the event doesn't bubble anyways
+                    if !bubbles {
+                        break;
+                    }
 
-                    match select(task_poll, self.channel.1.next()).await {
-                        Either::Left((_, _)) => {}
-                        Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
+                    // Break if this is the exact target element.
+                    // This means we won't call two listeners with the same name on the same element. This should be
+                    // documented, or be rejected from the rsx! macro outright
+                    if target_path == this_path {
+                        break;
                     }
-                } else {
-                    self.pending_messages
-                        .push_front(self.channel.1.next().await.unwrap());
                 }
             }
 
-            // Move all the messages into the queue
-            self.process_all_messages();
-        }
-    }
-
-    /// Manually kick the VirtualDom to process any
-    pub fn process_all_messages(&mut self) {
-        // clear out the scheduler queue
-        while let Ok(Some(msg)) = self.channel.1.try_next() {
-            self.pending_messages.push_front(msg);
-        }
-
-        // process all the messages pulled from the queue
-        while let Some(msg) = self.pending_messages.pop_back() {
-            self.process_message(msg);
-        }
-    }
+            // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
+            // We check the bubble state between each call to see if the event has been stopped from bubbling
+            for listener in listeners.drain(..).rev() {
+                if let AttributeValue::Listener(listener) = listener {
+                    if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                        cb(uievent.clone());
+                    }
 
-    /// Handle an individual message for the scheduler.
-    ///
-    /// This will either call an event listener or mark a component as dirty.
-    pub fn process_message(&mut self, msg: SchedulerMsg) {
-        match msg {
-            SchedulerMsg::NewTask(_id) => {
-                // uh, not sure? I think end up re-polling it anyways
-            }
-            SchedulerMsg::Event(event) => {
-                if let Some(element) = event.element {
-                    self.scopes.call_listener_with_bubbling(&event, element);
-                }
-            }
-            SchedulerMsg::Immediate(s) => {
-                self.mark_dirty_scope(s);
-            }
-            SchedulerMsg::DirtyAll => {
-                let dirty = self
-                    .scopes
-                    .scopes
-                    .borrow()
-                    .keys()
-                    .copied()
-                    .collect::<Vec<_>>();
-                for id in dirty {
-                    self.mark_dirty_scope(id);
+                    if !uievent.propogates.get() {
+                        return;
+                    }
                 }
             }
-            #[cfg(any(feature = "hot-reload", debug_assertions))]
-            SchedulerMsg::SetTemplate(msg) => {
-                let SetTemplateMsg(id, tmpl) = *msg;
-                if self
-                    .scopes
-                    .templates
-                    .borrow_mut()
-                    .insert(
-                        id.clone(),
-                        std::rc::Rc::new(std::cell::RefCell::new(Template::Owned(tmpl))),
-                    )
-                    .is_some()
-                {
-                    self.scopes.template_resolver.borrow_mut().mark_dirty(&id)
-                }
 
-                // mark any scopes that used the template as dirty
-                self.process_message(SchedulerMsg::DirtyAll);
-            }
+            parent_path = template.parent.and_then(|id| self.elements.get(id.0));
         }
     }
 
-    /// Run the virtualdom with a deadline.
-    ///
-    /// This method will perform any outstanding diffing work and try to return as many mutations as possible before the
-    /// deadline is reached. This method accepts a closure that returns `true` if the deadline has been reached. To wrap
-    /// your future into a deadline, consider the `now_or_never` method from `future_utils`.
-    ///
-    /// ```rust, ignore
-    /// let mut vdom = VirtualDom::new(App);
-    ///
-    /// let timeout = TimeoutFuture::from_ms(16);
-    /// let deadline = || (&mut timeout).now_or_never();
-    ///
-    /// let mutations = vdom.work_with_deadline(deadline);
-    /// ```
-    ///
-    /// 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.
+    /// Wait for the scheduler to have any work.
     ///
-    /// If the work is not finished by the deadline, Dioxus will store it for later and return when work_with_deadline
-    /// is called again. This means you can ensure some level of free time on the VirtualDom's thread during the work phase.
+    /// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
+    /// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method
+    /// will exit.
     ///
-    /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
-    /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
-    /// entirely jank-free applications that perform a ton of work.
+    /// This method is cancel-safe, so you're fine to discard the future in a select block.
     ///
-    /// In general use, Dioxus is plenty fast enough to not need to worry about this.
+    /// This lets us poll async tasks and suspended trees during idle periods without blocking the main thread.
     ///
     /// # Example
     ///
     /// ```rust, ignore
-    /// fn App(cx: Scope) -> Element {
-    ///     cx.render(rsx!( div {"hello"} ))
-    /// }
-    ///
-    /// let mut dom = VirtualDom::new(App);
-    ///
-    /// loop {
-    ///     let mut timeout = TimeoutFuture::from_ms(16);
-    ///     let deadline = move || (&mut timeout).now_or_never();
-    ///
-    ///     let mutations = dom.run_with_deadline(deadline).await;
-    ///
-    ///     apply_mutations(mutations);
-    /// }
+    /// let dom = VirtualDom::new(App);
+    /// let sender = dom.get_scheduler_channel();
     /// ```
-    #[allow(unused)]
-    pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
-        let mut committed_mutations = vec![];
-        self.scopes.template_bump.reset();
-        self.removed_scopes.clear();
-
-        while !self.dirty_scopes.is_empty() {
-            let scopes = &self.scopes;
-            let mut diff_state = DiffState::new(scopes);
-
-            let mut ran_scopes = FxHashSet::default();
-
-            // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
-            self.dirty_scopes
-                .retain(|id| scopes.get_scope(*id).is_some());
-
-            self.dirty_scopes.sort_by(|a, b| {
-                let h1 = scopes.get_scope(*a).unwrap().height;
-                let h2 = scopes.get_scope(*b).unwrap().height;
-                h1.cmp(&h2).reverse()
-            });
-
-            if let Some(scopeid) = self.dirty_scopes.pop() {
-                if scopes.get_scope(scopeid).is_some()
-                    && !self.removed_scopes.contains(&scopeid)
-                    && !ran_scopes.contains(&scopeid)
-                {
-                    ran_scopes.insert(scopeid);
-
-                    self.scopes.run_scope(scopeid);
-
-                    diff_state.diff_scope(self.root, scopeid);
-
-                    let DiffState { mutations, .. } = diff_state;
-
-                    self.removed_scopes
-                        .extend(mutations.dirty_scopes.iter().copied());
+    pub async fn wait_for_work(&mut self) {
+        let mut some_msg = None;
 
-                    if !mutations.edits.is_empty() {
-                        committed_mutations.push(mutations);
+        loop {
+            match some_msg.take() {
+                // If a bunch of messages are ready in a sequence, try to pop them off synchronously
+                Some(msg) => match msg {
+                    SchedulerMsg::Immediate(id) => self.mark_dirty(id),
+                    SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
+                    SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
+                },
+
+                // If they're not ready, then we should wait for them to be ready
+                None => {
+                    match self.rx.try_next() {
+                        Ok(Some(val)) => some_msg = Some(val),
+                        Ok(None) => return,
+                        Err(_) => {
+                            // If we have any dirty scopes, or finished fiber trees then we should exit
+                            if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() {
+                                return;
+                            }
+
+                            some_msg = self.rx.next().await
+                        }
                     }
-
-                    // todo: pause the diff machine
-                    // if diff_state.work(&mut deadline) {
-                    //     let DiffState { mutations, .. } = diff_state;
-                    //     for scope in &mutations.dirty_scopes {
-                    //         self.dirty_scopes.remove(scope);
-                    //     }
-                    //     committed_mutations.push(mutations);
-                    // } else {
-                    //     // leave the work in an incomplete state
-                    //     //
-                    //     // todo: we should store the edits and re-apply them later
-                    //     // for now, we just dump the work completely (threadsafe)
-                    //     return committed_mutations;
-                    // }
                 }
             }
         }
+    }
 
-        committed_mutations
+    /// Process all events in the queue until there are no more left
+    pub fn process_events(&mut self) {
+        while let Ok(Some(msg)) = self.rx.try_next() {
+            match msg {
+                SchedulerMsg::Immediate(id) => self.mark_dirty(id),
+                SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
+                SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
+            }
+        }
     }
 
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
     ///
-    /// The diff machine expects the RealDom's stack to be the root of the application.
+    /// The mutations item expects the RealDom's stack to be the root of the application.
     ///
     /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
     /// root component will be ran once and then diffed. All updates will flow out as mutations.
     ///
     /// All state stored in components will be completely wiped away.
     ///
+    /// Any templates previously registered will remain.
+    ///
     /// # Example
     /// ```rust, ignore
     /// static App: Component = |cx|  cx.render(rsx!{ "hello world" });
+    ///
     /// let mut dom = VirtualDom::new();
     /// let edits = dom.rebuild();
     ///
     /// apply_edits(edits);
     /// ```
     pub fn rebuild(&mut self) -> Mutations {
-        let scope_id = ScopeId(0);
-
-        let mut diff_state = DiffState::new(&self.scopes);
-        self.scopes.run_scope(scope_id);
-
-        diff_state.scope_stack.push(scope_id);
-
-        let node = self.scopes.fin_head(scope_id);
-        let mut created = Vec::new();
-        diff_state.create_node(self.root, node, &mut created);
-
-        diff_state
-            .mutations
-            .append_children(Some(self.root.as_u64()), created);
+        match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
+            // Rebuilding implies we append the created elements to the root
+            RenderReturn::Sync(Ok(node)) => {
+                let m = self.create_scope(ScopeId(0), node);
+                self.mutations.edits.push(Mutation::AppendChildren {
+                    id: ElementId(0),
+                    m,
+                });
+            }
+            // If an error occurs, we should try to render the default error component and context where the error occured
+            RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),
+            RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
+        }
 
-        self.dirty_scopes.clear();
-        assert!(self.dirty_scopes.is_empty());
+        self.finalize()
+    }
 
-        diff_state.mutations
+    /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
+    /// suspended subtrees.
+    pub fn render_immediate(&mut self) -> Mutations {
+        // Build a waker that won't wake up since our deadline is already expired when it's polled
+        let waker = futures_util::task::noop_waker();
+        let mut cx = std::task::Context::from_waker(&waker);
+
+        // Now run render with deadline but dont even try to poll any async tasks
+        let fut = self.render_with_deadline(std::future::ready(()));
+        pin_mut!(fut);
+
+        // The root component is not allowed to be async
+        match fut.poll(&mut cx) {
+            std::task::Poll::Ready(mutations) => mutations,
+            std::task::Poll::Pending => panic!("render_immediate should never return pending"),
+        }
     }
 
-    /// Compute a manual diff of the VirtualDom between states.
-    ///
-    /// This can be useful when state inside the DOM is remotely changed from the outside, but not propagated as an event.
+    /// Render what you can given the timeline and then move on
     ///
-    /// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
-    /// to force an update of the DOM when the state of the app is changed outside of the app.
+    /// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
     ///
-    /// To force a reflow of the entire VirtualDom, use `ScopeId(0)` as the scope_id.
-    ///
-    /// # Example
-    /// ```rust, ignore
-    /// #[derive(PartialEq, Props)]
-    /// struct AppProps {
-    ///     value: Shared<&'static str>,
-    /// }
-    ///
-    /// static App: Component<AppProps> = |cx| {
-    ///     let val = cx.value.borrow();
-    ///     cx.render(rsx! { div { "{val}" } })
-    /// };
-    ///
-    /// let value = Rc::new(RefCell::new("Hello"));
-    /// let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone(), });
-    ///
-    /// let _ = dom.rebuild();
-    ///
-    /// *value.borrow_mut() = "goodbye";
-    ///
-    /// let edits = dom.hard_diff(ScopeId(0));
-    /// ```
-    pub fn hard_diff(&mut self, scope_id: ScopeId) -> Mutations {
-        let mut diff_machine = DiffState::new(&self.scopes);
-        self.scopes.run_scope(scope_id);
+    /// If no suspense trees are present
+    pub async fn render_with_deadline(&mut self, deadline: impl Future<Output = ()>) -> Mutations {
+        pin_mut!(deadline);
 
-        let (old, new) = (
-            diff_machine.scopes.wip_head(scope_id),
-            diff_machine.scopes.fin_head(scope_id),
-        );
+        loop {
+            // first, unload any complete suspense trees
+            for finished_fiber in self.finished_fibers.drain(..) {
+                let scope = &mut self.scopes[finished_fiber.0];
+                let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
 
-        diff_machine.force_diff = true;
-        diff_machine.scope_stack.push(scope_id);
-        let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
+                self.mutations
+                    .templates
+                    .append(&mut context.mutations.borrow_mut().templates);
 
-        diff_machine.diff_node(scope.container, old, new);
+                self.mutations
+                    .edits
+                    .append(&mut context.mutations.borrow_mut().edits);
 
-        diff_machine.mutations
-    }
+                // TODO: count how many nodes are on the stack?
+                self.mutations.push(Mutation::ReplaceWith {
+                    id: context.placeholder.get().unwrap(),
+                    m: 1,
+                })
+            }
 
-    /// Renders an `rsx` call into the Base Scope's allocator.
-    ///
-    /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
-    ///
-    /// ```rust, ignore
-    /// fn Base(cx: Scope) -> Element {
-    ///     render!(div {})
-    /// }
-    ///
-    /// let dom = VirtualDom::new(Base);
-    /// let nodes = dom.render_nodes(rsx!("div"));
-    /// ```
-    pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> {
-        let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
-        let frame = scope.wip_frame();
-        let factory = NodeFactory::new(scope);
-        let node = lazy_nodes.call(factory);
-        frame.bump.alloc(node)
-    }
+            // Next, diff any dirty scopes
+            // We choose not to poll the deadline since we complete pretty quickly anyways
+            if let Some(dirty) = self.dirty_scopes.iter().next().cloned() {
+                self.dirty_scopes.remove(&dirty);
 
-    /// Renders an `rsx` call into the Base Scope's allocator.
-    ///
-    /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
-    ///
-    /// ```rust, ignore
-    /// fn Base(cx: Scope) -> Element {
-    ///     render!(div {})
-    /// }
-    ///
-    /// let dom = VirtualDom::new(Base);
-    /// let nodes = dom.render_nodes(rsx!("div"));
-    /// ```
-    pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
-        let mut machine = DiffState::new(&self.scopes);
-        machine.scope_stack.push(ScopeId(0));
-        machine.diff_node(self.root, old, new);
+                // if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update
+                if self.is_scope_suspended(dirty.id) {
+                    continue;
+                }
 
-        machine.mutations
-    }
+                // Save the current mutations length so we can split them into boundary
+                let mutations_to_this_point = self.mutations.edits.len();
+
+                // Run the scope and get the mutations
+                self.run_scope(dirty.id);
+                self.diff_scope(dirty.id);
+
+                // If suspended leaves are present, then we should find the boundary for this scope and attach things
+                // No placeholder necessary since this is a diff
+                if !self.collected_leaves.is_empty() {
+                    let mut boundary = self.scopes[dirty.id.0]
+                        .consume_context::<Rc<SuspenseContext>>()
+                        .unwrap();
+
+                    let boundary_mut = boundary.borrow_mut();
+
+                    // Attach mutations
+                    boundary_mut
+                        .mutations
+                        .borrow_mut()
+                        .edits
+                        .extend(self.mutations.edits.split_off(mutations_to_this_point));
+
+                    // Attach suspended leaves
+                    boundary
+                        .waiting_on
+                        .borrow_mut()
+                        .extend(self.collected_leaves.drain(..));
+                }
+            }
 
-    /// Renders an `rsx` call into the Base Scope's allocator.
-    ///
-    /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
-    ///
-    ///
-    /// ```rust, ignore
-    /// fn Base(cx: Scope) -> Element {
-    ///     render!(div {})
-    /// }
-    ///
-    /// let dom = VirtualDom::new(Base);
-    /// let nodes = dom.render_nodes(rsx!("div"));
-    /// ```
-    pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
-        let mut machine = DiffState::new(&self.scopes);
-        machine.scope_stack.push(ScopeId(0));
-        let node = self.render_vnodes(nodes);
-        let mut created = Vec::new();
-        machine.create_node(self.root, node, &mut created);
-        machine
-            .mutations
-            .append_children(Some(self.root.as_u64()), created);
-        machine.mutations
-    }
+            // If there's more work, then just continue, plenty of work to do
+            if !self.dirty_scopes.is_empty() {
+                continue;
+            }
 
-    /// Renders an `rsx` call into the Base Scopes's arena.
-    ///
-    /// Useful when needing to diff two rsx! calls from outside the VirtualDom, such as in a test.
-    ///
-    ///
-    /// ```rust, ignore
-    /// fn Base(cx: Scope) -> Element {
-    ///     render!(div {})
-    /// }
-    ///
-    /// let dom = VirtualDom::new(Base);
-    /// let nodes = dom.render_nodes(rsx!("div"));
-    /// ```
-    pub fn diff_lazynodes<'a>(
-        &'a self,
-        left: LazyNodes<'a, '_>,
-        right: LazyNodes<'a, '_>,
-    ) -> (Mutations<'a>, Mutations<'a>) {
-        let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
-
-        let mut create = DiffState::new(&self.scopes);
-        create.scope_stack.push(ScopeId(0));
-        let mut created = Vec::new();
-        create.create_node(self.root, old, &mut created);
-        create
-            .mutations
-            .append_children(Some(self.root.as_u64()), created);
-
-        let mut edit = DiffState::new(&self.scopes);
-        edit.scope_stack.push(ScopeId(0));
-        edit.diff_node(self.root, old, new);
-
-        (create.mutations, edit.mutations)
-    }
+            // If there's no pending suspense, then we have no reason to wait for anything
+            if self.scheduler.leaves.borrow().is_empty() {
+                return self.finalize();
+            }
 
-    /// Runs a function with the template associated with a given id.
-    pub fn with_template<R>(&self, id: &TemplateId, mut f: impl FnMut(&Template) -> R) -> R {
-        self.scopes
-            .templates
-            .borrow()
-            .get(id)
-            .map(|inner| {
-                let borrow = inner;
-                f(&borrow.borrow())
-            })
-            .unwrap()
-    }
+            // Poll the suspense leaves in the meantime
+            let mut work = self.wait_for_work();
 
-    fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
-        let scopes = &self.scopes;
-        if let Some(scope) = scopes.get_scope(scope_id) {
-            let height = scope.height;
-            let id = scope_id.0;
-            if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
-                let scope = scopes.get_scope(*new).unwrap();
-                let new_height = scope.height;
-                let new_id = &scope.scope_id();
-                height.cmp(&new_height).then(new_id.0.cmp(&id))
-            }) {
-                self.dirty_scopes.insert(index, scope_id);
-                log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
+            // safety: this is okay since we don't touch the original future
+            let pinned = unsafe { std::pin::Pin::new_unchecked(&mut work) };
+
+            // If the deadline is exceded (left) then we should return the mutations we have
+            use futures_util::future::{select, Either};
+            if let Either::Left((_, _)) = select(&mut deadline, pinned).await {
+                // release the borrowed
+                drop(work);
+                return self.finalize();
             }
         }
     }
-}
-
-/*
-Scopes and ScopeArenas are never dropped internally.
-An app will always occupy as much memory as its biggest form.
-
-This means we need to handle all specifics of drop *here*. It's easier
-to reason about centralizing all the drop
-logic in one spot rather than scattered in each module.
-
-Broadly speaking, we want to use the remove_nodes method to clean up *everything*
-This will drop listeners, borrowed props, and hooks for all components.
-We need to do this in the correct order - nodes at the very bottom must be dropped first to release
-the borrow chain.
 
-Once the contents of the tree have been cleaned up, we can finally clean up the
-memory used by ScopeState itself.
-
-questions:
-should we build a vcomponent for the root?
-- probably - yes?
-- store the vcomponent in the root dom
+    /// Swap the current mutations with a new
+    fn finalize(&mut self) -> Mutations {
+        // todo: make this a routine
+        let mut out = Mutations::default();
+        std::mem::swap(&mut self.mutations, &mut out);
+        out
+    }
+}
 
-- 1: Use remove_nodes to use the ensure_drop_safety pathway to safely drop the tree
-- 2: Drop the ScopeState itself
-*/
 impl Drop for VirtualDom {
     fn drop(&mut self) {
-        // the best way to drop the dom is to replace the root scope with a dud
-        // the diff infrastructure will then finish the rest
-        let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
-
-        // todo: move the remove nodes method onto scopearena
-        // this will clear *all* scopes *except* the root scope
-        let mut machine = DiffState::new(&self.scopes);
-        machine.remove_nodes([scope.root_node()], false);
-
-        // Now, clean up the root scope
-        // safety: there are no more references to the root scope
-        let scope = unsafe { &mut *self.scopes.get_scope_raw(ScopeId(0)).unwrap() };
-        scope.reset();
-
-        // make sure there are no "live" components
-        for (_, scopeptr) in self.scopes.scopes.get_mut().drain() {
-            // safety: all scopes were made in the bump's allocator
-            // They are never dropped until now. The only way to drop is through Box.
-            let scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
-            drop(scope);
-        }
-
-        for scopeptr in self.scopes.free_scopes.get_mut().drain(..) {
-            // safety: all scopes were made in the bump's allocator
-            // They are never dropped until now. The only way to drop is through Box.
-            let mut scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
-            scope.reset();
-            drop(scope);
-        }
+        // Simply drop this scope which drops all of its children
+        self.drop_scope(ScopeId(0));
     }
 }

+ 2 - 0
packages/core/tests/.rustfmt.toml

@@ -0,0 +1,2 @@
+struct_variant_width = 100
+struct_lit_width = 80

+ 2 - 7
packages/dioxus/tests/README.md → packages/core/tests/README.md

@@ -1,9 +1,6 @@
 # Testing of Dioxus core
 
 
-NodeFactory
-- [] rsx, html, NodeFactory generate the same structures
-
 Diffing
 - [x] create elements
 - [x] create text
@@ -19,15 +16,13 @@ Diffing
 - [x] keyed diffing
 - [x] keyed diffing out of order
 - [x] keyed diffing with prefix/suffix
-- [x] suspended nodes work 
+- [x] suspended nodes work
 
 Lifecycle
 - [] Components mount properly
 - [] Components create new child components
 - [] Replaced components unmount old components and mount new
 - [] Post-render effects are called
-- [] 
-
 
 Shared Context
 - [] Shared context propagates downwards
@@ -37,7 +32,7 @@ Suspense
 - [] use_suspense generates suspended nodes
 
 
-Hooks 
+Hooks
 - [] Drop order is maintained
 - [] Shared hook state is okay
 - [] use_hook works

+ 75 - 0
packages/core/tests/attr_cleanup.rs

@@ -0,0 +1,75 @@
+//! dynamic attributes in dioxus necessitate an allocated node ID.
+//!
+//! This tests to ensure we clean it up
+
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+#[test]
+fn attrs_cycle() {
+    let mut dom = VirtualDom::new(|cx| {
+        let id = cx.generation();
+        match cx.generation() % 2 {
+            0 => cx.render(rsx! {
+                div {}
+            }),
+            1 => cx.render(rsx! {
+                div {
+                    h1 { class: "{id}", id: "{id}" }
+                }
+            }),
+            _ => unreachable!(),
+        }
+    });
+
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            AppendChildren { m: 1, id: ElementId(0) },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            AssignId { path: &[0,], id: ElementId(3,) },
+            SetAttribute { name: "class", value: "1", id: ElementId(3,), ns: None },
+            SetAttribute { name: "id", value: "1", id: ElementId(3,), ns: None },
+            ReplaceWith { id: ElementId(1,), m: 1 },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            ReplaceWith { id: ElementId(2), m: 1 }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            AssignId { path: &[0], id: ElementId(1) },
+            SetAttribute { name: "class", value: "3", id: ElementId(1), ns: None },
+            SetAttribute { name: "id", value: "3", id: ElementId(1), ns: None },
+            ReplaceWith { id: ElementId(3), m: 1 }
+        ]
+    );
+
+    // we take the node taken by attributes since we reused it
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            ReplaceWith { id: ElementId(2), m: 1 }
+        ]
+    );
+}

+ 15 - 0
packages/core/tests/boolattrs.rs

@@ -0,0 +1,15 @@
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+#[test]
+fn bool_test() {
+    let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false })));
+    assert_eq!(
+        app.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },
+            AppendChildren { m: 1, id: ElementId(0) },
+        ]
+    )
+}

+ 55 - 0
packages/core/tests/borrowedstate.rs

@@ -0,0 +1,55 @@
+#![allow(non_snake_case)]
+
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+#[test]
+fn test_borrowed_state() {
+    let mut dom = VirtualDom::new(Parent);
+
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+            HydrateText { path: &[0,], value: "Hello w1!", id: ElementId(4,) },
+            ReplacePlaceholder { path: &[1,], m: 1 },
+            ReplacePlaceholder { path: &[0,], m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
+        ]
+    )
+}
+
+fn Parent(cx: Scope) -> Element {
+    let w1 = cx.use_hook(|| String::from("w1"));
+
+    cx.render(rsx! {
+        div {
+            Child { name: w1 }
+        }
+    })
+}
+
+#[derive(Props)]
+struct ChildProps<'a> {
+    name: &'a str,
+}
+
+fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
+    cx.render(rsx! {
+        div {
+            h1 { "it's nested" }
+            Child2 { name: cx.props.name }
+        }
+    })
+}
+
+#[derive(Props)]
+struct Grandchild<'a> {
+    name: &'a str,
+}
+
+fn Child2<'a>(cx: Scope<'a, Grandchild<'a>>) -> Element {
+    cx.render(rsx!(div { "Hello {cx.props.name}!" }))
+}

+ 28 - 0
packages/core/tests/bubble_error.rs

@@ -0,0 +1,28 @@
+//! we should properly bubble up errors from components
+
+use dioxus::prelude::*;
+
+fn app(cx: Scope) -> Element {
+    let raw = match cx.generation() % 2 {
+        0 => "123.123",
+        1 => "123.123.123",
+        _ => unreachable!(),
+    };
+
+    let value = raw.parse::<f32>()?;
+
+    cx.render(rsx! {
+        div { "hello {value}" }
+    })
+}
+
+#[test]
+fn bubbles_error() {
+    let mut dom = VirtualDom::new(app);
+
+    let _edits = dom.rebuild().santize();
+
+    dom.mark_dirty(ScopeId(0));
+
+    _ = dom.render_immediate();
+}

+ 51 - 0
packages/core/tests/context_api.rs

@@ -0,0 +1,51 @@
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+#[test]
+fn state_shares() {
+    fn app(cx: Scope) -> Element {
+        cx.provide_context(cx.generation() as i32);
+
+        cx.render(rsx!(child_1 {}))
+    }
+
+    fn child_1(cx: Scope) -> Element {
+        cx.render(rsx!(child_2 {}))
+    }
+
+    fn child_2(cx: Scope) -> Element {
+        let value = cx.consume_context::<i32>().unwrap();
+        cx.render(rsx!("Value is {value}"))
+    }
+
+    let mut dom = VirtualDom::new(app);
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            CreateTextNode { value: "Value is 0", id: ElementId(1,) },
+            AppendChildren { m: 1, id: ElementId(0) },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    _ = dom.render_immediate();
+    assert_eq!(dom.base_scope().consume_context::<i32>().unwrap(), 1);
+
+    dom.mark_dirty(ScopeId(0));
+    _ = dom.render_immediate();
+    assert_eq!(dom.base_scope().consume_context::<i32>().unwrap(), 2);
+
+    dom.mark_dirty(ScopeId(2));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [SetText { value: "Value is 2", id: ElementId(1,) },]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    dom.mark_dirty(ScopeId(2));
+    let edits = dom.render_immediate();
+    assert_eq!(
+        edits.santize().edits,
+        [SetText { value: "Value is 3", id: ElementId(1,) },]
+    );
+}

+ 197 - 0
packages/core/tests/create_dom.rs

@@ -0,0 +1,197 @@
+#![allow(unused, non_upper_case_globals, non_snake_case)]
+
+//! Prove that the dom works normally through virtualdom methods.
+//!
+//! This methods all use "rebuild" which completely bypasses the scheduler.
+//! Hard rebuilds don't consume any events from the event queue.
+
+use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+
+#[test]
+fn test_original_diff() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            div {
+                div {
+                    "Hello, world!"
+                }
+            }
+        })
+    });
+
+    let edits = dom.rebuild().santize();
+
+    assert_eq!(
+        edits.edits,
+        [
+            // add to root
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            AppendChildren { m: 1, id: ElementId(0) }
+        ]
+    )
+}
+
+#[test]
+fn create() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            div {
+                div {
+                    "Hello, world!"
+                    div {
+                        div {
+                            Fragment {
+                                "hello"
+                                "world"
+                            }
+                        }
+                    }
+                }
+            }
+        })
+    });
+
+    let _edits = dom.rebuild().santize();
+
+    // todo: we don't test template mutations anymore since the templates are passed along
+
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create template
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello, world!" },
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateStaticPlaceholder {},
+    //         AppendChildren { m: 1 },
+    //         AppendChildren { m: 1 },
+    //         AppendChildren { m: 2 },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 1 },
+    //         // The fragment child template
+    //         CreateStaticText { value: "hello" },
+    //         CreateStaticText { value: "world" },
+    //         SaveTemplate { name: "template", m: 2 },
+    //     ]
+    // );
+}
+
+#[test]
+fn create_list() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            (0..3).map(|f| rsx!( div { "hello" } ))
+        })
+    });
+
+    let _edits = dom.rebuild().santize();
+
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create template
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "hello" },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 1 }
+    //     ]
+    // );
+}
+
+#[test]
+fn create_simple() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            div {}
+            div {}
+            div {}
+            div {}
+        })
+    });
+
+    let edits = dom.rebuild().santize();
+
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create template
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         // add to root
+    //         SaveTemplate { name: "template", m: 4 }
+    //     ]
+    // );
+}
+#[test]
+fn create_components() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            Child { "abc1" }
+            Child { "abc2" }
+            Child { "abc3" }
+        })
+    });
+
+    #[derive(Props)]
+    struct ChildProps<'a> {
+        children: Element<'a>,
+    }
+
+    fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
+        cx.render(rsx! {
+            h1 {}
+            div { &cx.props.children }
+            p {}
+        })
+    }
+
+    let _edits = dom.rebuild().santize();
+
+    // todo: test this
+}
+
+#[test]
+fn anchors() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            if true {
+                rsx!( div { "hello" } )
+            }
+            if false {
+                rsx!( div { "goodbye" } )
+            }
+        })
+    });
+
+    // note that the template under "false" doesn't show up since it's not loaded
+    let edits = dom.rebuild().santize();
+
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create each template
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "hello" },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { m: 1, name: "template" },
+    //     ]
+    // );
+
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            CreatePlaceholder { id: ElementId(2) },
+            AppendChildren { m: 2, id: ElementId(0) }
+        ]
+    )
+}

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

@@ -0,0 +1,32 @@
+// use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+
+#[test]
+fn multiroot() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            div { "Hello a" }
+            div { "Hello b" }
+            div { "Hello c" }
+        })
+    });
+
+    // note: we dont test template edits anymore
+    let _templates = dom.rebuild().santize().templates;
+
+    // assert_eq!(
+    //     dom.rebuild().santize().templates,
+    //     [
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello a" },
+    //         AppendChildren { m: 1 },
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello b" },
+    //         AppendChildren { m: 1 },
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello c" },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 3 }
+    //     ]
+    // )
+}

+ 104 - 0
packages/core/tests/create_fragments.rs

@@ -0,0 +1,104 @@
+//! Do we create fragments properly across complex boundaries?
+
+use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+
+#[test]
+fn empty_fragment_creates_nothing() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx!(()))
+    }
+
+    let mut vdom = VirtualDom::new(app);
+    let edits = vdom.rebuild();
+
+    assert_eq!(
+        edits.edits,
+        [
+            CreatePlaceholder { id: ElementId(1) },
+            AppendChildren { id: ElementId(0), m: 1 }
+        ]
+    );
+}
+
+#[test]
+fn root_fragments_work() {
+    let mut vdom = VirtualDom::new(|cx| {
+        cx.render(rsx!(
+            div { "hello" }
+            div { "goodbye" }
+        ))
+    });
+
+    assert_eq!(
+        vdom.rebuild().edits.last().unwrap(),
+        &AppendChildren { id: ElementId(0), m: 2 }
+    );
+}
+
+#[test]
+fn fragments_nested() {
+    let mut vdom = VirtualDom::new(|cx| {
+        cx.render(rsx!(
+            div { "hello" }
+            div { "goodbye" }
+            rsx! {
+                div { "hello" }
+                div { "goodbye" }
+                rsx! {
+                    div { "hello" }
+                    div { "goodbye" }
+                    rsx! {
+                        div { "hello" }
+                        div { "goodbye" }
+                    }
+                }
+            }
+        ))
+    });
+
+    assert_eq!(
+        vdom.rebuild().edits.last().unwrap(),
+        &AppendChildren { id: ElementId(0), m: 8 }
+    );
+}
+
+#[test]
+fn fragments_across_components() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            demo_child {}
+            demo_child {}
+            demo_child {}
+            demo_child {}
+        })
+    }
+
+    fn demo_child(cx: Scope) -> Element {
+        let world = "world";
+        cx.render(rsx! {
+            "hellO!"
+            world
+        })
+    }
+
+    assert_eq!(
+        VirtualDom::new(app).rebuild().edits.last().unwrap(),
+        &AppendChildren { id: ElementId(0), m: 8 }
+    );
+}
+
+#[test]
+fn list_fragments() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx!(
+            h1 {"hello"}
+            (0..6).map(|f| rsx!( span { "{f}" }))
+        ))
+    }
+    assert_eq!(
+        VirtualDom::new(app).rebuild().edits.last().unwrap(),
+        &AppendChildren { id: ElementId(0), m: 7 }
+    );
+}

+ 72 - 0
packages/core/tests/create_lists.rs

@@ -0,0 +1,72 @@
+use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+
+// A real-world usecase of templates at peak performance
+// In react, this would be a lot of node creation.
+//
+// In Dioxus, we memoize the rsx! body and simplify it down to a few template loads
+//
+// Also note that the IDs increase linearly. This lets us drive a vec on the renderer for O(1) re-indexing
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div {
+            (0..3).map(|i| rsx! {
+                div {
+                    h1 { "hello world! "}
+                    p { "{i}" }
+                }
+            })
+        }
+    })
+}
+
+#[test]
+fn list_renders() {
+    let mut dom = VirtualDom::new(app);
+
+    let edits = dom.rebuild().santize();
+
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // Create the outer div
+    //         CreateElement { name: "div" },
+    //         // todo: since this is the only child, we should just use
+    //         // append when modify the values (IE no need for a placeholder)
+    //         CreateStaticPlaceholder,
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 1 },
+    //         // Create the inner template div
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "h1" },
+    //         CreateStaticText { value: "hello world! " },
+    //         AppendChildren { m: 1 },
+    //         CreateElement { name: "p" },
+    //         CreateTextPlaceholder,
+    //         AppendChildren { m: 1 },
+    //         AppendChildren { m: 2 },
+    //         SaveTemplate { name: "template", m: 1 }
+    //     ],
+    // );
+
+    assert_eq!(
+        edits.edits,
+        [
+            // Load the outer div
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            // Load each template one-by-one, rehydrating it
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            HydrateText { path: &[1, 0], value: "0", id: ElementId(3) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+            HydrateText { path: &[1, 0], value: "1", id: ElementId(5) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            HydrateText { path: &[1, 0], value: "2", id: ElementId(7) },
+            // Replace the 0th childn on the div with the 3 templates on the stack
+            ReplacePlaceholder { m: 3, path: &[0] },
+            // Append the container div to the dom
+            AppendChildren { m: 1, id: ElementId(0) }
+        ],
+    )
+}

+ 106 - 0
packages/core/tests/create_passthru.rs

@@ -0,0 +1,106 @@
+use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+
+/// Should push the text node onto the stack and modify it
+#[test]
+fn nested_passthru_creates() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            pass_thru {
+                pass_thru {
+                    pass_thru {
+                        div { "hi" }
+                    }
+                }
+            }
+        })
+    }
+
+    #[inline_props]
+    fn pass_thru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
+        cx.render(rsx!(children))
+    }
+
+    let mut dom = VirtualDom::new(app);
+    let edits = dom.rebuild().santize();
+
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            AppendChildren { m: 1, id: ElementId(0) },
+        ]
+    )
+}
+
+/// Should load all the templates and append them
+///
+/// Take note on how we don't spit out the template for child_comp since it's entirely dynamic
+#[test]
+fn nested_passthru_creates_add() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            child_comp {
+                "1"
+                child_comp {
+                    "2"
+                    child_comp {
+                        "3"
+                        div {
+                            "hi"
+                        }
+                    }
+                }
+            }
+        })
+    }
+
+    #[inline_props]
+    fn child_comp<'a>(cx: Scope, children: Element<'a>) -> Element {
+        cx.render(rsx! { children })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            // load 1
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            // load 2
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            // load 3
+            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            // load div that contains 4
+            LoadTemplate { name: "template", index: 1, id: ElementId(4) },
+            AppendChildren { id: ElementId(0), m: 4 },
+        ]
+    );
+}
+
+/// note that the template is all dynamic roots - so it doesn't actually get cached as a template
+#[test]
+fn dynamic_node_as_root() {
+    fn app(cx: Scope) -> Element {
+        let a = 123;
+        let b = 456;
+        cx.render(rsx! { "{a}" "{b}" })
+    }
+
+    let mut dom = VirtualDom::new(app);
+    let edits = dom.rebuild().santize();
+
+    // Since the roots were all dynamic, they should not cause any template muations
+    assert!(edits.templates.is_empty());
+
+    // The root node is text, so we just create it on the spot
+    assert_eq!(
+        edits.edits,
+        [
+            CreateTextNode { value: "123", id: ElementId(1) },
+            CreateTextNode { value: "456", id: ElementId(2) },
+            AppendChildren { id: ElementId(0), m: 2 }
+        ]
+    )
+}

+ 51 - 0
packages/core/tests/cycle.rs

@@ -0,0 +1,51 @@
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+/// As we clean up old templates, the ID for the node should cycle
+#[test]
+fn cycling_elements() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(match cx.generation() % 2 {
+            0 => rsx! { div { "wasd" } },
+            1 => rsx! { div { "abcd" } },
+            _ => unreachable!(),
+        })
+    });
+
+    let edits = dom.rebuild().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            AppendChildren { m: 1, id: ElementId(0) },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            ReplaceWith { id: ElementId(1,), m: 1 },
+        ]
+    );
+
+    // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            ReplaceWith { id: ElementId(2,), m: 1 },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            ReplaceWith { id: ElementId(1,), m: 1 },
+        ]
+    );
+}

+ 102 - 0
packages/core/tests/diff_component.rs

@@ -0,0 +1,102 @@
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+/// When returning sets of components, we do a light diff of the contents to preserve some react-like functionality
+///
+/// This means that nav_bar should never get re-created and that we should only be swapping out
+/// different pointers
+#[test]
+fn component_swap() {
+    fn app(cx: Scope) -> Element {
+        let render_phase = cx.use_hook(|| 0);
+
+        *render_phase += 1;
+
+        cx.render(match *render_phase {
+            0 => rsx! {
+                nav_bar {}
+                dash_board {}
+            },
+            1 => rsx! {
+                nav_bar {}
+                dash_results {}
+            },
+            2 => rsx! {
+                nav_bar {}
+                dash_board {}
+            },
+            3 => rsx! {
+                nav_bar {}
+                dash_results {}
+            },
+            4 => rsx! {
+                nav_bar {}
+                dash_board {}
+            },
+            _ => rsx!("blah"),
+        })
+    }
+
+    fn nav_bar(cx: Scope) -> Element {
+        cx.render(rsx! {
+            h1 {
+                "NavBar"
+                (0..3).map(|_| rsx!(nav_link {}))
+            }
+        })
+    }
+
+    fn nav_link(cx: Scope) -> Element {
+        cx.render(rsx!( h1 { "nav_link" } ))
+    }
+
+    fn dash_board(cx: Scope) -> Element {
+        cx.render(rsx!( div { "dashboard" } ))
+    }
+
+    fn dash_results(cx: Scope) -> Element {
+        cx.render(rsx!( div { "results" } ))
+    }
+
+    let mut dom = VirtualDom::new(app);
+    let edits = dom.rebuild().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+            ReplacePlaceholder { path: &[1], m: 3 },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            AppendChildren { m: 2, id: ElementId(0) }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            ReplaceWith { id: ElementId(5), m: 1 }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            ReplaceWith { id: ElementId(6), m: 1 }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            ReplaceWith { id: ElementId(5), m: 1 }
+        ]
+    );
+}

+ 84 - 0
packages/core/tests/diff_element.rs

@@ -0,0 +1,84 @@
+use dioxus::core::Mutation::*;
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+
+#[test]
+fn text_diff() {
+    fn app(cx: Scope) -> Element {
+        let gen = cx.generation();
+        cx.render(rsx!( h1 { "hello {gen}" } ))
+    }
+
+    let mut vdom = VirtualDom::new(app);
+    _ = vdom.rebuild();
+
+    vdom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        vdom.render_immediate().edits,
+        [SetText { value: "hello 1", id: ElementId(2) }]
+    );
+
+    vdom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        vdom.render_immediate().edits,
+        [SetText { value: "hello 2", id: ElementId(2) }]
+    );
+
+    vdom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        vdom.render_immediate().edits,
+        [SetText { value: "hello 3", id: ElementId(2) }]
+    );
+}
+
+#[test]
+fn element_swap() {
+    fn app(cx: Scope) -> Element {
+        let gen = cx.generation();
+
+        match gen % 2 {
+            0 => cx.render(rsx!( h1 { "hello 1" } )),
+            1 => cx.render(rsx!( h2 { "hello 2" } )),
+            _ => unreachable!(),
+        }
+    }
+
+    let mut vdom = VirtualDom::new(app);
+    _ = vdom.rebuild();
+
+    vdom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            ReplaceWith { id: ElementId(1,), m: 1 },
+        ]
+    );
+
+    vdom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            ReplaceWith { id: ElementId(2,), m: 1 },
+        ]
+    );
+
+    vdom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            ReplaceWith { id: ElementId(1,), m: 1 },
+        ]
+    );
+
+    vdom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            ReplaceWith { id: ElementId(2,), m: 1 },
+        ]
+    );
+}

+ 356 - 0
packages/core/tests/diff_keyed_list.rs

@@ -0,0 +1,356 @@
+//! Diffing Tests
+//!
+//! These tests only verify that the diffing algorithm works properly for single components.
+//!
+//! It does not validated that component lifecycles work properly. This is done in another test file.
+
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+/// Should result in moves, but not removals or additions
+#[test]
+fn keyed_diffing_out_of_order() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9],
+            1 => &[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
+            AppendChildren { m: 10, id: ElementId(0) },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(7,) },
+            InsertBefore { id: ElementId(5,), m: 1 },
+        ]
+    )
+}
+
+/// Should result in moves only
+#[test]
+fn keyed_diffing_out_of_order_adds() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 8, 7, 4, 5, 6 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(5,) },
+            PushRoot { id: ElementId(4,) },
+            InsertBefore { id: ElementId(1,), m: 2 },
+        ]
+    )
+}
+
+/// Should result in moves only
+#[test]
+fn keyed_diffing_out_of_order_adds_3() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 8, 7, 5, 6 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(5,) },
+            PushRoot { id: ElementId(4,) },
+            InsertBefore { id: ElementId(2,), m: 2 },
+        ]
+    );
+}
+
+/// Should result in moves onl
+#[test]
+fn keyed_diffing_out_of_order_adds_4() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 5, 8, 7, 6 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(5,) },
+            PushRoot { id: ElementId(4,) },
+            InsertBefore { id: ElementId(3,), m: 2 },
+        ]
+    );
+}
+
+/// Should result in moves onl
+#[test]
+fn keyed_diffing_out_of_order_adds_5() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 5, 6, 8, 7 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().edits,
+        [
+            PushRoot { id: ElementId(5,) },
+            InsertBefore { id: ElementId(4,), m: 1 },
+        ]
+    );
+}
+
+/// Should result in moves onl
+#[test]
+fn keyed_diffing_additions() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7, 8 /**/],
+            1 => &[/**/ 4, 5, 6, 7, 8, 9, 10 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7) },
+            InsertAfter { id: ElementId(5), m: 2 }
+        ]
+    );
+}
+
+#[test]
+fn keyed_diffing_additions_and_moves_on_ends() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[/**/ 4, 5, 6, 7 /**/],
+            1 => &[/**/ 7, 4, 5, 6, 11, 12 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            // create 11, 12
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            InsertAfter { id: ElementId(3), m: 2 },
+            // move 7 to the front
+            PushRoot { id: ElementId(4) },
+            InsertBefore { id: ElementId(1), m: 1 }
+        ]
+    );
+}
+
+#[test]
+fn keyed_diffing_additions_and_moves_in_middle() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[/**/ 1, 2, 3, 4 /**/],
+            1 => &[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    // LIS: 4, 5, 6
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            // create 5, 6
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            InsertBefore { id: ElementId(3), m: 2 },
+            // create 7, 8
+            LoadTemplate { name: "template", index: 0, id: ElementId(7) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(8) },
+            InsertBefore { id: ElementId(2), m: 2 },
+            // move 7
+            PushRoot { id: ElementId(4) },
+            InsertBefore { id: ElementId(1), m: 1 }
+        ]
+    );
+}
+
+#[test]
+fn controlled_keyed_diffing_out_of_order() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[4, 5, 6, 7],
+            1 => &[0, 5, 9, 6, 4],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    // LIS: 5, 6
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            // remove 7
+            Remove { id: ElementId(4,) },
+            // move 4 to after 6
+            PushRoot { id: ElementId(1) },
+            InsertAfter { id: ElementId(3,), m: 1 },
+            // create 9 and insert before 6
+            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+            InsertBefore { id: ElementId(3,), m: 1 },
+            // create 0 and insert before 5
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            InsertBefore { id: ElementId(2,), m: 1 },
+        ]
+    );
+}
+
+#[test]
+fn controlled_keyed_diffing_out_of_order_max_test() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[0, 1, 2, 3, 4],
+            1 => &[3, 0, 1, 10, 2],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            Remove { id: ElementId(5,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            InsertBefore { id: ElementId(3,), m: 1 },
+            PushRoot { id: ElementId(4) },
+            InsertBefore { id: ElementId(1,), m: 1 },
+        ]
+    );
+}
+
+// noticed some weird behavior in the desktop interpreter
+// just making sure it doesnt happen in the core implementation
+#[test]
+fn remove_list() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[9, 8, 7, 6, 5],
+            1 => &[9, 8],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            Remove { id: ElementId(5) },
+            Remove { id: ElementId(4) },
+            Remove { id: ElementId(3) },
+        ]
+    );
+}
+
+#[test]
+fn no_common_keys() {
+    let mut dom = VirtualDom::new(|cx| {
+        let order: &[_] = match cx.generation() % 2 {
+            0 => &[1, 2, 3],
+            1 => &[4, 5, 6],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
+    });
+
+    _ = dom.rebuild();
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            Remove { id: ElementId(3) },
+            Remove { id: ElementId(2) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+            ReplaceWith { id: ElementId(1), m: 3 }
+        ]
+    );
+}

+ 379 - 0
packages/core/tests/diff_unkeyed_list.rs

@@ -0,0 +1,379 @@
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+
+#[test]
+fn list_creates_one_by_one() {
+    let mut dom = VirtualDom::new(|cx| {
+        let gen = cx.generation();
+
+        cx.render(rsx! {
+            div {
+                (0..gen).map(|i| rsx! {
+                    div { "{i}" }
+                })
+            }
+        })
+    });
+
+    // load the div and then assign the empty fragment as a placeholder
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            AssignId { path: &[0], id: ElementId(2,) },
+            AppendChildren { id: ElementId(0), m: 1 },
+        ]
+    );
+
+    // Rendering the first item should replace the placeholder with an element
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+            HydrateText { path: &[0], value: "0", id: ElementId(4,) },
+            ReplaceWith { id: ElementId(2,), m: 1 },
+        ]
+    );
+
+    // Rendering the next item should insert after the previous
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            HydrateText { path: &[0], value: "1", id: ElementId(5,) },
+            InsertAfter { id: ElementId(3,), m: 1 },
+        ]
+    );
+
+    // ... and again!
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
+            HydrateText { path: &[0], value: "2", id: ElementId(7,) },
+            InsertAfter { id: ElementId(2,), m: 1 },
+        ]
+    );
+
+    // once more
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
+            HydrateText { path: &[0], value: "3", id: ElementId(9,) },
+            InsertAfter { id: ElementId(6,), m: 1 },
+        ]
+    );
+}
+
+#[test]
+fn removes_one_by_one() {
+    let mut dom = VirtualDom::new(|cx| {
+        let gen = 3 - cx.generation() % 4;
+
+        cx.render(rsx! {
+            div {
+                (0..gen).map(|i| rsx! {
+                    div { "{i}" }
+                })
+            }
+        })
+    });
+
+    // load the div and then assign the empty fragment as a placeholder
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            // The container
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            // each list item
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            HydrateText { path: &[0], value: "0", id: ElementId(3) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+            HydrateText { path: &[0], value: "1", id: ElementId(5) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            HydrateText { path: &[0], value: "2", id: ElementId(7) },
+            // replace the placeholder in the template with the 3 templates on the stack
+            ReplacePlaceholder { m: 3, path: &[0] },
+            // Mount the div
+            AppendChildren { id: ElementId(0), m: 1 }
+        ]
+    );
+
+    // Remove div(3)
+    // Rendering the first item should replace the placeholder with an element
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [Remove { id: ElementId(6) }]
+    );
+
+    // Remove div(2)
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [Remove { id: ElementId(4) }]
+    );
+
+    // Remove div(1) and replace with a placeholder
+    // todo: this should just be a remove with no placeholder
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            CreatePlaceholder { id: ElementId(3) },
+            ReplaceWith { id: ElementId(2), m: 1 }
+        ]
+    );
+
+    // load the 3 and replace the placeholder
+    // todo: this should actually be append to, but replace placeholder is fine for now
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            HydrateText { path: &[0], value: "0", id: ElementId(4) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+            HydrateText { path: &[0], value: "1", id: ElementId(6) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7) },
+            HydrateText { path: &[0], value: "2", id: ElementId(8) },
+            ReplaceWith { id: ElementId(3), m: 3 }
+        ]
+    );
+}
+
+#[test]
+fn list_shrink_multiroot() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            div {
+                (0..cx.generation()).map(|i| rsx! {
+                    div { "{i}" }
+                    div { "{i}" }
+                })
+            }
+        })
+    });
+
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            AssignId { path: &[0,], id: ElementId(2,) },
+            AppendChildren { id: ElementId(0), m: 1 }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            HydrateText { path: &[0], value: "0", id: ElementId(4) },
+            LoadTemplate { name: "template", index: 1, id: ElementId(5) },
+            HydrateText { path: &[0], value: "0", id: ElementId(6) },
+            ReplaceWith { id: ElementId(2), m: 2 }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            HydrateText { path: &[0], value: "1", id: ElementId(7) },
+            LoadTemplate { name: "template", index: 1, id: ElementId(8) },
+            HydrateText { path: &[0], value: "1", id: ElementId(9) },
+            InsertAfter { id: ElementId(5), m: 2 }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(10) },
+            HydrateText { path: &[0], value: "2", id: ElementId(11) },
+            LoadTemplate { name: "template", index: 1, id: ElementId(12) },
+            HydrateText { path: &[0], value: "2", id: ElementId(13) },
+            InsertAfter { id: ElementId(8), m: 2 }
+        ]
+    );
+}
+
+#[test]
+fn removes_one_by_one_multiroot() {
+    let mut dom = VirtualDom::new(|cx| {
+        let gen = 3 - cx.generation() % 4;
+
+        cx.render(rsx! {
+            div {
+                (0..gen).map(|i| rsx! {
+                    div { "{i}" }
+                    div { "{i}" }
+                })
+            }
+        })
+    });
+
+    // load the div and then assign the empty fragment as a placeholder
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            //
+            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+            HydrateText { path: &[0], value: "0", id: ElementId(3) },
+            LoadTemplate { name: "template", index: 1, id: ElementId(4) },
+            HydrateText { path: &[0], value: "0", id: ElementId(5) },
+            //
+            LoadTemplate { name: "template", index: 0, id: ElementId(6) },
+            HydrateText { path: &[0], value: "1", id: ElementId(7) },
+            LoadTemplate { name: "template", index: 1, id: ElementId(8) },
+            HydrateText { path: &[0], value: "1", id: ElementId(9) },
+            //
+            LoadTemplate { name: "template", index: 0, id: ElementId(10) },
+            HydrateText { path: &[0], value: "2", id: ElementId(11) },
+            LoadTemplate { name: "template", index: 1, id: ElementId(12) },
+            HydrateText { path: &[0], value: "2", id: ElementId(13) },
+            //
+            ReplacePlaceholder { path: &[0], m: 6 },
+            //
+            AppendChildren { id: ElementId(0), m: 1 }
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    assert_eq!(
+        dom.render_immediate().santize().edits,
+        [
+            Remove { id: ElementId(4) },
+            CreatePlaceholder { id: ElementId(5) },
+            ReplaceWith { id: ElementId(2), m: 1 }
+        ]
+    );
+}
+
+#[test]
+fn two_equal_fragments_are_equal_static() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            (0..5).map(|_| rsx! {
+                div { "hello" }
+            })
+        })
+    });
+
+    _ = dom.rebuild();
+    assert!(dom.render_immediate().edits.is_empty());
+}
+
+#[test]
+fn two_equal_fragments_are_equal() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            (0..5).map(|i| rsx! {
+                div { "hello {i}" }
+            })
+        })
+    });
+
+    _ = dom.rebuild();
+    assert!(dom.render_immediate().edits.is_empty());
+}
+
+#[test]
+fn remove_many() {
+    let mut dom = VirtualDom::new(|cx| {
+        let num = match cx.generation() % 3 {
+            0 => 0,
+            1 => 1,
+            2 => 5,
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx! {
+            (0..num).map(|i| rsx! { div { "hello {i}" } })
+        })
+    });
+
+    let edits = dom.rebuild().santize();
+    assert!(edits.templates.is_empty());
+    assert_eq!(
+        edits.edits,
+        [
+            CreatePlaceholder { id: ElementId(1,) },
+            AppendChildren { id: ElementId(0), m: 1 },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
+            ReplaceWith { id: ElementId(1,), m: 1 },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+            HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+            HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+            HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
+            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+            HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
+            InsertAfter { id: ElementId(2,), m: 4 },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            Remove { id: ElementId(9,) },
+            Remove { id: ElementId(7,) },
+            Remove { id: ElementId(5,) },
+            Remove { id: ElementId(1,) },
+            CreatePlaceholder { id: ElementId(3,) },
+            ReplaceWith { id: ElementId(2,), m: 1 },
+        ]
+    );
+
+    dom.mark_dirty(ScopeId(0));
+    let edits = dom.render_immediate().santize();
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+            HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) },
+            ReplaceWith { id: ElementId(3,), m: 1 },
+        ]
+    )
+}

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

@@ -0,0 +1 @@
+//! It should be possible to swap out templates at runtime, enabling hotreloading

+ 42 - 0
packages/core/tests/kitchen_sink.rs

@@ -0,0 +1,42 @@
+use dioxus::core::{ElementId, Mutation};
+use dioxus::prelude::*;
+
+fn basic_syntax_is_a_template(cx: Scope) -> Element {
+    let asd = 123;
+    let var = 123;
+
+    cx.render(rsx! {
+        div { key: "12345",
+            class: "asd",
+            class: "{asd}",
+            onclick: move |_| {},
+            div { "{var}" }
+            div {
+                h1 { "var" }
+                p { "you're great!" }
+                div { background_color: "red",
+                    h1 { "var" }
+                    div { b { "asd" } "not great" }
+                }
+                p { "you're great!" }
+            }
+        }
+    })
+}
+#[test]
+fn dual_stream() {
+    let mut dom = VirtualDom::new(basic_syntax_is_a_template);
+    let edits = dom.rebuild().santize();
+
+    use Mutation::*;
+    assert_eq!(
+        edits.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
+            NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) },
+            HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
+            AppendChildren { id: ElementId(0), m: 1 }
+        ],
+    );
+}

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

@@ -0,0 +1,168 @@
+#![allow(unused, non_upper_case_globals)]
+#![allow(non_snake_case)]
+
+//! Tests for the lifecycle of components.
+use dioxus::core::{ElementId, Mutation::*};
+use dioxus::prelude::*;
+use std::rc::Rc;
+use std::sync::{Arc, Mutex};
+
+type Shared<T> = Arc<Mutex<T>>;
+
+#[test]
+fn manual_diffing() {
+    struct AppProps {
+        value: Shared<&'static str>,
+    }
+
+    fn app(cx: Scope<AppProps>) -> Element {
+        let val = cx.props.value.lock().unwrap();
+        cx.render(rsx! { div { "{val}" } })
+    };
+
+    let value = Arc::new(Mutex::new("Hello"));
+    let mut dom = VirtualDom::new_with_props(app, AppProps { value: value.clone() });
+
+    let _ = dom.rebuild();
+
+    *value.lock().unwrap() = "goodbye";
+
+    assert_eq!(
+        dom.rebuild().santize().edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+            HydrateText { path: &[0], value: "goodbye", id: ElementId(4) },
+            AppendChildren { m: 1, id: ElementId(0) }
+        ]
+    );
+}
+
+#[test]
+fn events_generate() {
+    fn app(cx: Scope) -> Element {
+        let count = cx.use_hook(|| 0);
+
+        match *count {
+            0 => cx.render(rsx! {
+                div { onclick: move |_| *count += 1,
+                    div { "nested" }
+                    "Click me!"
+                }
+            }),
+            _ => cx.render(rsx!(())),
+        }
+    };
+
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+
+    dom.handle_event("click", Rc::new(MouseData::default()), ElementId(1), true);
+
+    dom.mark_dirty(ScopeId(0));
+    let edits = dom.render_immediate();
+
+    assert_eq!(
+        edits.edits,
+        [
+            CreatePlaceholder { id: ElementId(2) },
+            ReplaceWith { id: ElementId(1), m: 1 }
+        ]
+    )
+}
+
+// #[test]
+// fn components_generate() {
+//     fn app(cx: Scope) -> Element {
+//         let render_phase = cx.use_hook(|| 0);
+//         *render_phase += 1;
+
+//         cx.render(match *render_phase {
+//             1 => rsx_without_templates!("Text0"),
+//             2 => rsx_without_templates!(div {}),
+//             3 => rsx_without_templates!("Text2"),
+//             4 => rsx_without_templates!(Child {}),
+//             5 => rsx_without_templates!({ None as Option<()> }),
+//             6 => rsx_without_templates!("text 3"),
+//             7 => rsx_without_templates!({ (0..2).map(|f| rsx_without_templates!("text {f}")) }),
+//             8 => rsx_without_templates!(Child {}),
+//             _ => todo!(),
+//         })
+//     };
+
+//     fn Child(cx: Scope) -> Element {
+//         println!("Running child");
+//         cx.render(rsx_without_templates! {
+//             h1 {}
+//         })
+//     }
+
+//     let mut dom = VirtualDom::new(app);
+//     let edits = dom.rebuild();
+//     assert_eq!(
+//         edits.edits,
+//         [
+//             CreateTextNode { root: Some(1), text: "Text0" },
+//             AppendChildren { root: Some(0), children: vec![1] }
+//         ]
+//     );
+
+//     assert_eq!(
+//         dom.hard_diff(ScopeId(0)).edits,
+//         [
+//             CreateElement { root: Some(2), tag: "div", children: 0 },
+//             ReplaceWith { root: Some(1), nodes: vec![2] }
+//         ]
+//     );
+
+//     assert_eq!(
+//         dom.hard_diff(ScopeId(0)).edits,
+//         [
+//             CreateTextNode { root: Some(1), text: "Text2" },
+//             ReplaceWith { root: Some(2), nodes: vec![1] }
+//         ]
+//     );
+
+//     // child {}
+//     assert_eq!(
+//         dom.hard_diff(ScopeId(0)).edits,
+//         [
+//             CreateElement { root: Some(2), tag: "h1", children: 0 },
+//             ReplaceWith { root: Some(1), nodes: vec![2] }
+//         ]
+//     );
+
+//     // placeholder
+//     assert_eq!(
+//         dom.hard_diff(ScopeId(0)).edits,
+//         [
+//             CreatePlaceholder { root: Some(1) },
+//             ReplaceWith { root: Some(2), nodes: vec![1] }
+//         ]
+//     );
+
+//     assert_eq!(
+//         dom.hard_diff(ScopeId(0)).edits,
+//         [
+//             CreateTextNode { root: Some(2), text: "text 3" },
+//             ReplaceWith { root: Some(1), nodes: vec![2] }
+//         ]
+//     );
+
+//     assert_eq!(
+//         dom.hard_diff(ScopeId(0)).edits,
+//         [
+//             CreateTextNode { text: "text 0", root: Some(1) },
+//             CreateTextNode { text: "text 1", root: Some(3) },
+//             ReplaceWith { root: Some(2), nodes: vec![1, 3] },
+//         ]
+//     );
+
+//     assert_eq!(
+//         dom.hard_diff(ScopeId(0)).edits,
+//         [
+//             CreateElement { tag: "h1", root: Some(2), children: 0 },
+//             ReplaceWith { root: Some(1), nodes: vec![2] },
+//             Remove { root: Some(3) },
+//         ]
+//     );
+// }

+ 49 - 0
packages/core/tests/miri_full_app.rs

@@ -0,0 +1,49 @@
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+use std::rc::Rc;
+
+#[test]
+fn miri_rollover() {
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+
+    for _ in 0..3 {
+        dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true);
+        dom.process_events();
+        _ = dom.render_immediate();
+    }
+}
+
+fn app(cx: Scope) -> Element {
+    let mut idx = use_state(cx, || 0);
+    let onhover = |_| println!("go!");
+
+    cx.render(rsx! {
+        div {
+            button {
+                onclick: move |_| {
+                    idx += 1;
+                    println!("Clicked");
+                },
+                "+"
+            }
+            button { onclick: move |_| idx -= 1, "-" }
+            ul {
+                (0..**idx).map(|i| rsx! {
+                    child_example { i: i, onhover: onhover }
+                })
+            }
+        }
+    })
+}
+
+#[inline_props]
+fn child_example<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
+    cx.render(rsx! {
+        li {
+            onmouseover: move |e| onhover.call(e),
+            "{i}"
+        }
+    })
+}

+ 127 - 0
packages/core/tests/miri_simple.rs

@@ -0,0 +1,127 @@
+use dioxus::prelude::*;
+
+#[test]
+fn app_drops() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            div {}
+        })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+    dom.mark_dirty(ScopeId(0));
+    _ = dom.render_immediate();
+}
+
+#[test]
+fn hooks_drop() {
+    fn app(cx: Scope) -> Element {
+        cx.use_hook(|| String::from("asd"));
+        cx.use_hook(|| String::from("asd"));
+        cx.use_hook(|| String::from("asd"));
+        cx.use_hook(|| String::from("asd"));
+
+        cx.render(rsx! {
+            div {}
+        })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+    dom.mark_dirty(ScopeId(0));
+    _ = dom.render_immediate();
+}
+
+#[test]
+fn contexts_drop() {
+    fn app(cx: Scope) -> Element {
+        cx.provide_context(String::from("asd"));
+
+        cx.render(rsx! {
+            div {
+                child_comp {}
+            }
+        })
+    }
+
+    fn child_comp(cx: Scope) -> Element {
+        let el = cx.consume_context::<String>().unwrap();
+
+        cx.render(rsx! {
+            div { "hello {el}" }
+        })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+    dom.mark_dirty(ScopeId(0));
+    _ = dom.render_immediate();
+}
+
+#[test]
+fn tasks_drop() {
+    fn app(cx: Scope) -> Element {
+        cx.spawn(async {
+            tokio::time::sleep(std::time::Duration::from_millis(100000)).await;
+        });
+
+        cx.render(rsx! {
+            div { }
+        })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+    dom.mark_dirty(ScopeId(0));
+    _ = dom.render_immediate();
+}
+
+#[test]
+fn root_props_drop() {
+    struct RootProps(String);
+
+    let mut dom = VirtualDom::new_with_props(
+        |cx| cx.render(rsx!( div { "{cx.props.0}"  } )),
+        RootProps("asdasd".to_string()),
+    );
+
+    _ = dom.rebuild();
+    dom.mark_dirty(ScopeId(0));
+    _ = dom.render_immediate();
+}
+
+#[test]
+fn diffing_drops_old() {
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx! {
+            div {
+                match cx.generation() % 2 {
+                    0 => rsx!( child_comp1 { name: "asdasd".to_string() }),
+                    1 => rsx!( child_comp2 { name: "asdasd".to_string() }),
+                    _ => todo!()
+                }
+            }
+        })
+    }
+
+    #[inline_props]
+    fn child_comp1(cx: Scope, name: String) -> Element {
+        cx.render(rsx! { "Hello {name}" })
+    }
+
+    #[inline_props]
+    fn child_comp2(cx: Scope, name: String) -> Element {
+        cx.render(rsx! { "Goodbye {name}"  })
+    }
+
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+    dom.mark_dirty(ScopeId(0));
+
+    _ = dom.render_immediate();
+}

+ 351 - 0
packages/core/tests/miri_stress.rs

@@ -0,0 +1,351 @@
+#![allow(non_snake_case)]
+
+use std::rc::Rc;
+
+use dioxus::prelude::*;
+
+/// This test checks that we should release all memory used by the virtualdom when it exits.
+///
+/// When miri runs, it'll let us know if we leaked or aliased.
+#[test]
+fn test_memory_leak() {
+    fn app(cx: Scope) -> Element {
+        let val = cx.generation();
+
+        cx.spawn(async {
+            tokio::time::sleep(std::time::Duration::from_millis(100000)).await;
+        });
+
+        if val == 2 || val == 4 {
+            return cx.render(rsx!(()));
+        }
+
+        let name = cx.use_hook(|| String::from("numbers: "));
+
+        name.push_str("123 ");
+
+        cx.render(rsx!(
+            div { "Hello, world!" }
+            Child {}
+            Child {}
+            Child {}
+            Child {}
+            Child {}
+            Child {}
+            BorrowedChild { name: name }
+            BorrowedChild { name: name }
+            BorrowedChild { name: name }
+            BorrowedChild { name: name }
+            BorrowedChild { name: name }
+        ))
+    }
+
+    #[derive(Props)]
+    struct BorrowedProps<'a> {
+        name: &'a str,
+    }
+
+    fn BorrowedChild<'a>(cx: Scope<'a, BorrowedProps<'a>>) -> Element {
+        cx.render(rsx! {
+            div {
+                "goodbye {cx.props.name}"
+                Child {}
+                Child {}
+            }
+        })
+    }
+
+    fn Child(cx: Scope) -> Element {
+        render!(div { "goodbye world" })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+
+    for _ in 0..5 {
+        dom.mark_dirty(ScopeId(0));
+        _ = dom.render_immediate();
+    }
+}
+
+#[test]
+fn memo_works_properly() {
+    fn app(cx: Scope) -> Element {
+        let val = cx.generation();
+
+        if val == 2 || val == 4 {
+            return cx.render(rsx!(()));
+        }
+
+        let name = cx.use_hook(|| String::from("asd"));
+
+        cx.render(rsx!(
+            div { "Hello, world! {name}" }
+            Child { na: "asdfg".to_string() }
+        ))
+    }
+
+    #[derive(PartialEq, Props)]
+    struct ChildProps {
+        na: String,
+    }
+
+    fn Child(cx: Scope<ChildProps>) -> Element {
+        render!(div { "goodbye world" })
+    }
+
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+    // todo!()
+    // dom.hard_diff(ScopeId(0));
+    // dom.hard_diff(ScopeId(0));
+    // dom.hard_diff(ScopeId(0));
+    // dom.hard_diff(ScopeId(0));
+    // dom.hard_diff(ScopeId(0));
+    // dom.hard_diff(ScopeId(0));
+    // dom.hard_diff(ScopeId(0));
+}
+
+#[test]
+fn free_works_on_root_hooks() {
+    /*
+    On Drop, scopearena drops all the hook contents. and props
+    */
+    #[derive(PartialEq, Clone, Props)]
+    struct AppProps {
+        inner: Rc<String>,
+    }
+
+    fn app(cx: Scope<AppProps>) -> Element {
+        let name: &AppProps = cx.use_hook(|| cx.props.clone());
+        render!(child_component { inner: name.inner.clone() })
+    }
+
+    fn child_component(cx: Scope<AppProps>) -> Element {
+        render!(div { "{cx.props.inner}" })
+    }
+
+    let ptr = Rc::new("asdasd".to_string());
+    let mut dom = VirtualDom::new_with_props(app, AppProps { inner: ptr.clone() });
+    let _ = dom.rebuild();
+
+    // ptr gets cloned into props and then into the hook
+    assert_eq!(Rc::strong_count(&ptr), 4);
+
+    drop(dom);
+
+    assert_eq!(Rc::strong_count(&ptr), 1);
+}
+
+// #[test]
+// fn old_props_arent_stale() {
+//     fn app(cx: Scope) -> Element {
+//         dbg!("rendering parent");
+//         let cnt = cx.use_hook(|| 0);
+//         *cnt += 1;
+
+//         if *cnt == 1 {
+//             render!(div { Child { a: "abcdef".to_string() } })
+//         } else {
+//             render!(div { Child { a: "abcdef".to_string() } })
+//         }
+//     }
+
+//     #[derive(Props, PartialEq)]
+//     struct ChildProps {
+//         a: String,
+//     }
+//     fn Child(cx: Scope<ChildProps>) -> Element {
+//         dbg!("rendering child", &cx.props.a);
+//         render!(div { "child {cx.props.a}" })
+//     }
+
+//     let mut dom = new_dom(app, ());
+//     let _ = dom.rebuild();
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//     dom.work_with_deadline(|| false);
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//     dom.work_with_deadline(|| false);
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//     dom.work_with_deadline(|| false);
+
+//     dbg!("forcing update to child");
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+//     dom.work_with_deadline(|| false);
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+//     dom.work_with_deadline(|| false);
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+//     dom.work_with_deadline(|| false);
+// }
+
+// #[test]
+// fn basic() {
+//     fn app(cx: Scope) -> Element {
+//         render!(div {
+//             Child { a: "abcdef".to_string() }
+//         })
+//     }
+
+//     #[derive(Props, PartialEq)]
+//     struct ChildProps {
+//         a: String,
+//     }
+
+//     fn Child(cx: Scope<ChildProps>) -> Element {
+//         dbg!("rendering child", &cx.props.a);
+//         render!(div { "child {cx.props.a}" })
+//     }
+
+//     let mut dom = new_dom(app, ());
+//     let _ = dom.rebuild();
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//     dom.work_with_deadline(|| false);
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//     dom.work_with_deadline(|| false);
+// }
+
+// #[test]
+// fn leak_thru_children() {
+//     fn app(cx: Scope) -> Element {
+//         cx.render(rsx! {
+//             Child {
+//                 name: "asd".to_string(),
+//             }
+//         });
+//         cx.render(rsx! {
+//             div {}
+//         })
+//     }
+
+//     #[inline_props]
+//     fn Child(cx: Scope, name: String) -> Element {
+//         render!(div { "child {name}" })
+//     }
+
+//     let mut dom = new_dom(app, ());
+//     let _ = dom.rebuild();
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//     dom.work_with_deadline(|| false);
+
+//     dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//     dom.work_with_deadline(|| false);
+// }
+
+// #[test]
+// fn test_pass_thru() {
+//     #[inline_props]
+//     fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element {
+//         cx.render(rsx! {
+//             header {
+//                 nav { children }
+//             }
+//         })
+//     }
+
+//     fn NavMenu(cx: Scope) -> Element {
+//         render!(            NavBrand {}
+//             div {
+//                 NavStart {}
+//                 NavEnd {}
+//             }
+//         )
+//     }
+
+//     fn NavBrand(cx: Scope) -> Element {
+//         render!(div {})
+//     }
+
+//     fn NavStart(cx: Scope) -> Element {
+//         render!(div {})
+//     }
+
+//     fn NavEnd(cx: Scope) -> Element {
+//         render!(div {})
+//     }
+
+//     #[inline_props]
+//     fn MainContainer<'a>(
+//         cx: Scope,
+//         nav: Element<'a>,
+//         body: Element<'a>,
+//         footer: Element<'a>,
+//     ) -> Element {
+//         cx.render(rsx! {
+//             div {
+//                 class: "columns is-mobile",
+//                 div {
+//                     class: "column is-full",
+//                     nav,
+//                     body,
+//                     footer,
+//                 }
+//             }
+//         })
+//     }
+
+//     fn app(cx: Scope) -> Element {
+//         let nav = cx.render(rsx! {
+//             NavContainer {
+//                 NavMenu {}
+//             }
+//         });
+//         let body = cx.render(rsx! {
+//             div {}
+//         });
+//         let footer = cx.render(rsx! {
+//             div {}
+//         });
+
+//         cx.render(rsx! {
+//             MainContainer {
+//                 nav: nav,
+//                 body: body,
+//                 footer: footer,
+//             }
+//         })
+//     }
+
+//     let mut dom = new_dom(app, ());
+//     let _ = dom.rebuild();
+
+//     for _ in 0..40 {
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
+//         dom.work_with_deadline(|| false);
+
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
+//         dom.work_with_deadline(|| false);
+
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
+//         dom.work_with_deadline(|| false);
+
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
+//         dom.work_with_deadline(|| false);
+//         dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
+//         dom.work_with_deadline(|| false);
+//     }
+// }

+ 24 - 0
packages/core/tests/safety.rs

@@ -0,0 +1,24 @@
+//! Tests related to safety of the library.
+
+use std::rc::Rc;
+
+use dioxus::prelude::*;
+use dioxus_core::SuspenseContext;
+
+/// Ensure no issues with not calling rebuild
+#[test]
+fn root_node_isnt_null() {
+    let dom = VirtualDom::new(|cx| render!("Hello world!"));
+
+    let scope = dom.base_scope();
+
+    // We haven't built the tree, so trying to get out the root node should fail
+    assert!(scope.try_root_node().is_none());
+
+    // The height should be 0
+    assert_eq!(scope.height(), 0);
+
+    // There should be a default suspense context
+    // todo: there should also be a default error boundary
+    assert!(scope.has_context::<Rc<SuspenseContext>>().is_some());
+}

+ 92 - 0
packages/core/tests/suspense.rs

@@ -0,0 +1,92 @@
+use dioxus::core::ElementId;
+use dioxus::core::{Mutation::*, SuspenseContext};
+use dioxus::prelude::*;
+use std::future::IntoFuture;
+use std::rc::Rc;
+use std::time::Duration;
+
+#[tokio::test]
+async fn it_works() {
+    let mut dom = VirtualDom::new(app);
+
+    let mutations = dom.rebuild().santize();
+
+    // We should at least get the top-level template in before pausing for the children
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     mutations.templates,
+    //     [
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Waiting for child..." },
+    //         CreateStaticPlaceholder,
+    //         AppendChildren { m: 2 },
+    //         SaveTemplate { name: "template", m: 1 }
+    //     ]
+    // );
+
+    // And we should load it in and assign the placeholder properly
+    assert_eq!(
+        mutations.edits,
+        [
+            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+            // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
+            // can we even?
+            AssignId { path: &[1], id: ElementId(3) },
+            AppendChildren { m: 1, id: ElementId(0) },
+        ]
+    );
+
+    // wait just a moment, not enough time for the boundary to resolve
+
+    dom.wait_for_work().await;
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx!(
+        div {
+            "Waiting for child..."
+            suspense_boundary {}
+        }
+    ))
+}
+
+fn suspense_boundary(cx: Scope) -> Element {
+    cx.use_hook(|| {
+        cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id())));
+    });
+
+    // Ensure the right types are found
+    cx.has_context::<Rc<SuspenseContext>>().unwrap();
+
+    cx.render(rsx!(async_child {}))
+}
+
+async fn async_child(cx: Scope<'_>) -> Element {
+    use_future!(cx, || tokio::time::sleep(Duration::from_millis(10))).await;
+    cx.render(rsx!(async_text {}))
+}
+
+async fn async_text(cx: Scope<'_>) -> Element {
+    let username = use_future!(cx, || async {
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+        "async child 1"
+    });
+
+    let age = use_future!(cx, || async {
+        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
+        1234
+    });
+
+    let (_user, _age) = use_future!(cx, || async {
+        tokio::join!(
+            tokio::time::sleep(std::time::Duration::from_secs(1)),
+            tokio::time::sleep(std::time::Duration::from_secs(2))
+        );
+        ("async child 1", 1234)
+    })
+    .await;
+
+    let (username, age) = tokio::join!(username.into_future(), age.into_future());
+
+    cx.render(rsx!( div { "Hello! {username}, you are {age}, {_user} {_age}" } ))
+}

+ 42 - 0
packages/core/tests/task.rs

@@ -0,0 +1,42 @@
+//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
+
+use dioxus::prelude::*;
+use std::time::Duration;
+
+static mut POLL_COUNT: usize = 0;
+
+#[tokio::test]
+async fn it_works() {
+    let mut dom = VirtualDom::new(app);
+
+    let _ = dom.rebuild();
+
+    tokio::select! {
+        _ = dom.wait_for_work() => {}
+        _ = tokio::time::sleep(Duration::from_millis(500)) => {}
+    };
+
+    // By the time the tasks are finished, we should've accumulated ticks from two tasks
+    // Be warned that by setting the delay to too short, tokio might not schedule in the tasks
+    assert_eq!(unsafe { POLL_COUNT }, 135);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.use_hook(|| {
+        cx.spawn(async {
+            for x in 0..10 {
+                tokio::time::sleep(Duration::from_micros(50)).await;
+                unsafe { POLL_COUNT += x }
+            }
+        });
+
+        cx.spawn(async {
+            for x in 0..10 {
+                tokio::time::sleep(Duration::from_micros(25)).await;
+                unsafe { POLL_COUNT += x * 2 }
+            }
+        });
+    });
+
+    cx.render(rsx!(()))
+}

+ 6 - 4
packages/desktop/Cargo.toml

@@ -27,12 +27,14 @@ tokio = { version = "1.16.1", features = [
     "rt-multi-thread",
     "rt",
     "time",
+    "macros",
 ], optional = true, default-features = false }
 webbrowser = "0.8.0"
-infer = "0.9.0"
+infer = "0.11.0"
 dunce = "1.0.2"
 
-interprocess = { version = "1.1.1" }
+interprocess = { version = "1.1.1", optional = true}
+futures-util = "0.3.25"
 
 [target.'cfg(target_os = "ios")'.dependencies]
 objc = "0.2.7"
@@ -42,12 +44,12 @@ objc_id = "0.1.1"
 core-foundation = "0.9.3"
 
 [features]
-default = ["tokio_runtime"]
+default = ["tokio_runtime", "hot-reload"]
 tokio_runtime = ["tokio"]
 fullscreen = ["wry/fullscreen"]
 transparent = ["wry/transparent"]
 tray = ["wry/tray"]
-hot-reload = ["dioxus-core/hot-reload"]
+hot-reload = ["interprocess"]
 
 [dev-dependencies]
 dioxus-core-macro = { path = "../core-macro" }

+ 50 - 38
packages/desktop/src/controller.rs

@@ -1,12 +1,15 @@
 use crate::desktop_context::{DesktopContext, UserWindowEvent};
-
+use crate::events::{decode_event, EventMessage};
 use dioxus_core::*;
+use futures_channel::mpsc::{unbounded, UnboundedSender};
+use futures_util::StreamExt;
 #[cfg(target_os = "ios")]
 use objc::runtime::Object;
 use std::{
     collections::HashMap,
     sync::Arc,
     sync::{atomic::AtomicBool, Mutex},
+    time::Duration,
 };
 use wry::{
     self,
@@ -16,10 +19,12 @@ use wry::{
 
 pub(super) struct DesktopController {
     pub(super) webviews: HashMap<WindowId, WebView>,
-    pub(super) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
     pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
     pub(super) quit_app_on_close: bool,
     pub(super) is_ready: Arc<AtomicBool>,
+    pub(super) proxy: EventLoopProxy<UserWindowEvent>,
+    pub(super) event_tx: UnboundedSender<serde_json::Value>,
+
     #[cfg(target_os = "ios")]
     pub(super) views: Vec<*mut Object>,
 }
@@ -33,64 +38,61 @@ impl DesktopController {
         proxy: EventLoopProxy<UserWindowEvent>,
     ) -> Self {
         let edit_queue = Arc::new(Mutex::new(Vec::new()));
-        let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
+        let (event_tx, mut event_rx) = unbounded();
+        let proxy2 = proxy.clone();
 
         let pending_edits = edit_queue.clone();
-        let return_sender = sender.clone();
         let desktop_context_proxy = proxy.clone();
 
         std::thread::spawn(move || {
-            // We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
+            // We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
+            // I'd personally not require tokio to be built-in to Dioxus-Desktop, but the DX is worse without it
             let runtime = tokio::runtime::Builder::new_multi_thread()
                 .enable_all()
                 .build()
                 .unwrap();
 
             runtime.block_on(async move {
-                let mut dom =
-                    VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
-
-                let window_context = DesktopContext::new(desktop_context_proxy);
-
-                dom.base_scope().provide_context(window_context);
-
-                // allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel
-                #[cfg(any(feature = "hot-reload", debug_assertions))]
-                crate::hot_reload::init(&dom);
-
-                let edits = dom.rebuild();
-
-                edit_queue
-                    .lock()
-                    .unwrap()
-                    .push(serde_json::to_string(&edits.edits).unwrap());
-
-                // Make sure the window is ready for any new updates
-                proxy.send_event(UserWindowEvent::Update).unwrap();
+                let mut dom = VirtualDom::new_with_props(root, props)
+                    .with_root_context(DesktopContext::new(desktop_context_proxy));
+                {
+                    let edits = dom.rebuild();
+                    let mut queue = edit_queue.lock().unwrap();
+                    queue.push(serde_json::to_string(&edits).unwrap());
+                    proxy.send_event(UserWindowEvent::EditsReady).unwrap();
+                }
 
                 loop {
-                    dom.wait_for_work().await;
-
-                    let muts = dom.work_with_deadline(|| false);
-
-                    for edit in muts {
-                        edit_queue
-                            .lock()
-                            .unwrap()
-                            .push(serde_json::to_string(&edit.edits).unwrap());
+                    tokio::select! {
+                        _ = dom.wait_for_work() => {}
+                        Some(json_value) = event_rx.next() => {
+                            if let Ok(value) = serde_json::from_value::<EventMessage>(json_value) {
+                                let name = value.event.clone();
+                                let el_id = ElementId(value.mounted_dom_id);
+                                if let Some(evt) = decode_event(value) {
+                                    dom.handle_event(&name,  evt, el_id,  dioxus_html::events::event_bubbles(&name));
+                                }
+                            }
+                        }
                     }
 
-                    let _ = proxy.send_event(UserWindowEvent::Update);
+                    let muts = dom
+                        .render_with_deadline(tokio::time::sleep(Duration::from_millis(16)))
+                        .await;
+
+                    edit_queue.lock().unwrap().push(serde_json::to_string(&muts).unwrap());
+                    let _ = proxy.send_event(UserWindowEvent::EditsReady);
                 }
             })
         });
 
         Self {
             pending_edits,
-            sender: return_sender,
             webviews: HashMap::new(),
             is_ready: Arc::new(AtomicBool::new(false)),
             quit_app_on_close: true,
+            proxy: proxy2,
+            event_tx,
             #[cfg(target_os = "ios")]
             views: vec![],
         }
@@ -106,13 +108,23 @@ impl DesktopController {
 
     pub(super) fn try_load_ready_webviews(&mut self) {
         if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
-            let mut queue = self.pending_edits.lock().unwrap();
+            let mut new_queue = Vec::new();
+
+            {
+                let mut queue = self.pending_edits.lock().unwrap();
+                std::mem::swap(&mut new_queue, &mut *queue);
+            }
+
             let (_id, view) = self.webviews.iter_mut().next().unwrap();
 
-            for edit in queue.drain(..) {
+            for edit in new_queue.drain(..) {
                 view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
                     .unwrap();
             }
         }
     }
+
+    pub(crate) fn set_template(&self, _serialized_template: String) {
+        todo!("hot reloading currently WIP")
+    }
 }

+ 100 - 88
packages/desktop/src/desktop_context.rs

@@ -1,3 +1,5 @@
+use std::rc::Rc;
+
 use crate::controller::DesktopController;
 use dioxus_core::ScopeState;
 use wry::application::event_loop::ControlFlow;
@@ -17,6 +19,13 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
         .unwrap()
 }
 
+/// Get a closure that executes any JavaScript in the WebView context.
+pub fn use_eval(cx: &ScopeState) -> &Rc<dyn Fn(String)> {
+    let desktop = use_window(cx).clone();
+
+    &*cx.use_hook(|| Rc::new(move |script| desktop.eval(script)) as Rc<dyn Fn(String)>)
+}
+
 /// An imperative interface to the current window.
 ///
 /// To get a handle to the current window, use the [`use_window`] hook.
@@ -152,12 +161,18 @@ impl DesktopContext {
 
 #[derive(Debug)]
 pub enum UserWindowEvent {
-    Update,
+    EditsReady,
+    Initialize,
 
     CloseWindow,
     DragWindow,
     FocusWindow,
 
+    /// Set a new Dioxus template for hot-reloading
+    ///
+    /// Is a no-op in release builds. Must fit the right format for templates
+    SetTemplate(String),
+
     Visible(bool),
     Minimize(bool),
     Maximize(bool),
@@ -185,107 +200,104 @@ pub enum UserWindowEvent {
     PopView,
 }
 
-pub(super) fn handler(
-    user_event: UserWindowEvent,
-    desktop: &mut DesktopController,
-    control_flow: &mut ControlFlow,
-) {
-    // currently dioxus-desktop supports a single window only,
-    // so we can grab the only webview from the map;
-    // on wayland it is possible that a user event is emitted
-    // before the webview is initialized. ignore the event.
-    let webview = if let Some(webview) = desktop.webviews.values().next() {
-        webview
-    } else {
-        return;
-    };
-    let window = webview.window();
-
-    match user_event {
-        Update => desktop.try_load_ready_webviews(),
-        CloseWindow => *control_flow = ControlFlow::Exit,
-        DragWindow => {
-            // if the drag_window has any errors, we don't do anything
-            window.fullscreen().is_none().then(|| window.drag_window());
-        }
-        Visible(state) => window.set_visible(state),
-        Minimize(state) => window.set_minimized(state),
-        Maximize(state) => window.set_maximized(state),
-        MaximizeToggle => window.set_maximized(!window.is_maximized()),
-        Fullscreen(state) => {
-            if let Some(handle) = window.current_monitor() {
-                window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
+impl DesktopController {
+    pub(super) fn handle_event(
+        &mut self,
+        user_event: UserWindowEvent,
+        control_flow: &mut ControlFlow,
+    ) {
+        // currently dioxus-desktop supports a single window only,
+        // so we can grab the only webview from the map;
+        // on wayland it is possible that a user event is emitted
+        // before the webview is initialized. ignore the event.
+        let webview = if let Some(webview) = self.webviews.values().next() {
+            webview
+        } else {
+            return;
+        };
+
+        let window = webview.window();
+
+        match user_event {
+            Initialize | EditsReady => self.try_load_ready_webviews(),
+            SetTemplate(template) => self.set_template(template),
+            CloseWindow => *control_flow = ControlFlow::Exit,
+            DragWindow => {
+                // if the drag_window has any errors, we don't do anything
+                window.fullscreen().is_none().then(|| window.drag_window());
             }
-        }
-        FocusWindow => window.set_focus(),
-        Resizable(state) => window.set_resizable(state),
-        AlwaysOnTop(state) => window.set_always_on_top(state),
+            Visible(state) => window.set_visible(state),
+            Minimize(state) => window.set_minimized(state),
+            Maximize(state) => window.set_maximized(state),
+            MaximizeToggle => window.set_maximized(!window.is_maximized()),
+            Fullscreen(state) => {
+                if let Some(handle) = window.current_monitor() {
+                    window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
+                }
+            }
+            FocusWindow => window.set_focus(),
+            Resizable(state) => window.set_resizable(state),
+            AlwaysOnTop(state) => window.set_always_on_top(state),
 
-        CursorVisible(state) => window.set_cursor_visible(state),
-        CursorGrab(state) => {
-            let _ = window.set_cursor_grab(state);
-        }
+            CursorVisible(state) => window.set_cursor_visible(state),
+            CursorGrab(state) => {
+                let _ = window.set_cursor_grab(state);
+            }
 
-        SetTitle(content) => window.set_title(&content),
-        SetDecorations(state) => window.set_decorations(state),
+            SetTitle(content) => window.set_title(&content),
+            SetDecorations(state) => window.set_decorations(state),
 
-        SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
+            SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
 
-        Print => {
-            if let Err(e) = webview.print() {
-                // we can't panic this error.
-                log::warn!("Open print modal failed: {e}");
+            Print => {
+                if let Err(e) = webview.print() {
+                    // we can't panic this error.
+                    log::warn!("Open print modal failed: {e}");
+                }
+            }
+            DevTool => {
+                #[cfg(debug_assertions)]
+                webview.open_devtools();
+                #[cfg(not(debug_assertions))]
+                log::warn!("Devtools are disabled in release builds");
             }
-        }
-        DevTool => {
-            #[cfg(debug_assertions)]
-            webview.open_devtools();
-            #[cfg(not(debug_assertions))]
-            log::warn!("Devtools are disabled in release builds");
-        }
 
-        Eval(code) => {
-            if let Err(e) = webview.evaluate_script(code.as_str()) {
-                // we can't panic this error.
-                log::warn!("Eval script error: {e}");
+            Eval(code) => {
+                if let Err(e) = webview.evaluate_script(code.as_str()) {
+                    // we can't panic this error.
+                    log::warn!("Eval script error: {e}");
+                }
             }
-        }
 
-        #[cfg(target_os = "ios")]
-        PushView(view) => unsafe {
-            use objc::runtime::Object;
-            use objc::*;
-            assert!(is_main_thread());
-            let ui_view = window.ui_view() as *mut Object;
-            let ui_view_frame: *mut Object = msg_send![ui_view, frame];
-            let _: () = msg_send![view, setFrame: ui_view_frame];
-            let _: () = msg_send![view, setAutoresizingMask: 31];
-
-            let ui_view_controller = window.ui_view_controller() as *mut Object;
-            let _: () = msg_send![ui_view_controller, setView: view];
-            desktop.views.push(ui_view);
-        },
-
-        #[cfg(target_os = "ios")]
-        PopView => unsafe {
-            use objc::runtime::Object;
-            use objc::*;
-            assert!(is_main_thread());
-            if let Some(view) = desktop.views.pop() {
+            #[cfg(target_os = "ios")]
+            PushView(view) => unsafe {
+                use objc::runtime::Object;
+                use objc::*;
+                assert!(is_main_thread());
+                let ui_view = window.ui_view() as *mut Object;
+                let ui_view_frame: *mut Object = msg_send![ui_view, frame];
+                let _: () = msg_send![view, setFrame: ui_view_frame];
+                let _: () = msg_send![view, setAutoresizingMask: 31];
+
                 let ui_view_controller = window.ui_view_controller() as *mut Object;
                 let _: () = msg_send![ui_view_controller, setView: view];
-            }
-        },
+                desktop.views.push(ui_view);
+            },
+
+            #[cfg(target_os = "ios")]
+            PopView => unsafe {
+                use objc::runtime::Object;
+                use objc::*;
+                assert!(is_main_thread());
+                if let Some(view) = desktop.views.pop() {
+                    let ui_view_controller = window.ui_view_controller() as *mut Object;
+                    let _: () = msg_send![ui_view_controller, setView: view];
+                }
+            },
+        }
     }
 }
 
-/// Get a closure that executes any JavaScript in the WebView context.
-pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) {
-    let desktop = use_window(cx).clone();
-
-    cx.use_hook(|| move |script| desktop.eval(script))
-}
-
 #[cfg(target_os = "ios")]
 fn is_main_thread() -> bool {
     use objc::runtime::{Class, BOOL, NO};

+ 56 - 186
packages/desktop/src/events.rs

@@ -1,13 +1,10 @@
 //! Convert a serialized event to an event trigger
 
-use std::any::Any;
-use std::sync::Arc;
-
-use dioxus_core::ElementId;
-use dioxus_core::{EventPriority, UserEvent};
-use dioxus_html::event_bubbles;
-use dioxus_html::on::*;
+use dioxus_html::events::*;
 use serde::{Deserialize, Serialize};
+use serde_json::from_value;
+use std::any::Any;
+use std::rc::Rc;
 
 #[derive(Deserialize, Serialize)]
 pub(crate) struct IpcMessage {
@@ -35,187 +32,60 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
     }
 }
 
-#[derive(Deserialize, Serialize)]
-struct ImEvent {
-    event: String,
-    mounted_dom_id: ElementId,
-    contents: serde_json::Value,
-}
-
-pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
-    let ImEvent {
-        event,
-        mounted_dom_id,
-        contents,
-    } = serde_json::from_value(val).unwrap();
-
-    let mounted_dom_id = Some(mounted_dom_id);
-    let name = event_name_from_type(&event);
-
-    let event = make_synthetic_event(&event, contents);
-
-    UserEvent {
-        name,
-        priority: EventPriority::Low,
-        scope_id: None,
-        element: mounted_dom_id,
-        bubbles: event_bubbles(name),
-        data: event,
-    }
-}
-
-fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc<dyn Any + Send + Sync> {
-    match name {
-        "copy" | "cut" | "paste" => {
-            //
-            Arc::new(ClipboardData {})
-        }
-        "compositionend" | "compositionstart" | "compositionupdate" => {
-            Arc::new(serde_json::from_value::<CompositionData>(val).unwrap())
-        }
-        "keydown" | "keypress" | "keyup" => {
-            let evt = serde_json::from_value::<KeyboardData>(val).unwrap();
-            Arc::new(evt)
+macro_rules! match_data {
+    (
+        $m:ident;
+        $name:ident;
+        $(
+            $tip:ty => $($mname:literal)|* ;
+        )*
+    ) => {
+        match $name {
+            $( $($mname)|* => {
+                let val: $tip = from_value::<$tip>($m).ok()?;
+                Rc::new(val) as Rc<dyn Any>
+            })*
+            _ => return None,
         }
-        "focus" | "blur" | "focusout" | "focusin" => {
-            //
-            Arc::new(FocusData {})
-        }
-
-        // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
-        // don't have a good solution with the serialized event problem
-        "change" | "input" | "invalid" | "reset" | "submit" => {
-            Arc::new(serde_json::from_value::<FormData>(val).unwrap())
-        }
-
-        "click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter"
-        | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown"
-        | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
-            Arc::new(serde_json::from_value::<MouseData>(val).unwrap())
-        }
-        "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
-        | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
-            Arc::new(serde_json::from_value::<PointerData>(val).unwrap())
-        }
-        "select" => {
-            //
-            Arc::new(serde_json::from_value::<SelectionData>(val).unwrap())
-        }
-
-        "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
-            Arc::new(serde_json::from_value::<TouchData>(val).unwrap())
-        }
-
-        "scroll" => Arc::new(ScrollData {}),
-
-        "wheel" => Arc::new(serde_json::from_value::<WheelData>(val).unwrap()),
-
-        "animationstart" | "animationend" | "animationiteration" => {
-            Arc::new(serde_json::from_value::<AnimationData>(val).unwrap())
-        }
-
-        "transitionend" => Arc::new(serde_json::from_value::<TransitionData>(val).unwrap()),
-
-        "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
-        | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
-        | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
-        | "timeupdate" | "volumechange" | "waiting" => {
-            //
-            Arc::new(MediaData {})
-        }
-
-        "toggle" => Arc::new(ToggleData {}),
+    };
+}
 
-        _ => Arc::new(()),
-    }
+#[derive(Deserialize)]
+pub struct EventMessage {
+    pub contents: serde_json::Value,
+    pub event: String,
+    pub mounted_dom_id: usize,
 }
 
-fn event_name_from_type(typ: &str) -> &'static str {
-    match typ {
-        "copy" => "copy",
-        "cut" => "cut",
-        "paste" => "paste",
-        "compositionend" => "compositionend",
-        "compositionstart" => "compositionstart",
-        "compositionupdate" => "compositionupdate",
-        "keydown" => "keydown",
-        "keypress" => "keypress",
-        "keyup" => "keyup",
-        "focus" => "focus",
-        "focusout" => "focusout",
-        "focusin" => "focusin",
-        "blur" => "blur",
-        "change" => "change",
-        "input" => "input",
-        "invalid" => "invalid",
-        "reset" => "reset",
-        "submit" => "submit",
-        "click" => "click",
-        "contextmenu" => "contextmenu",
-        "doubleclick" => "doubleclick",
-        "dblclick" => "dblclick",
-        "drag" => "drag",
-        "dragend" => "dragend",
-        "dragenter" => "dragenter",
-        "dragexit" => "dragexit",
-        "dragleave" => "dragleave",
-        "dragover" => "dragover",
-        "dragstart" => "dragstart",
-        "drop" => "drop",
-        "mousedown" => "mousedown",
-        "mouseenter" => "mouseenter",
-        "mouseleave" => "mouseleave",
-        "mousemove" => "mousemove",
-        "mouseout" => "mouseout",
-        "mouseover" => "mouseover",
-        "mouseup" => "mouseup",
-        "pointerdown" => "pointerdown",
-        "pointermove" => "pointermove",
-        "pointerup" => "pointerup",
-        "pointercancel" => "pointercancel",
-        "gotpointercapture" => "gotpointercapture",
-        "lostpointercapture" => "lostpointercapture",
-        "pointerenter" => "pointerenter",
-        "pointerleave" => "pointerleave",
-        "pointerover" => "pointerover",
-        "pointerout" => "pointerout",
-        "select" => "select",
-        "touchcancel" => "touchcancel",
-        "touchend" => "touchend",
-        "touchmove" => "touchmove",
-        "touchstart" => "touchstart",
-        "scroll" => "scroll",
-        "wheel" => "wheel",
-        "animationstart" => "animationstart",
-        "animationend" => "animationend",
-        "animationiteration" => "animationiteration",
-        "transitionend" => "transitionend",
-        "abort" => "abort",
-        "canplay" => "canplay",
-        "canplaythrough" => "canplaythrough",
-        "durationchange" => "durationchange",
-        "emptied" => "emptied",
-        "encrypted" => "encrypted",
-        "ended" => "ended",
-        "error" => "error",
-        "loadeddata" => "loadeddata",
-        "loadedmetadata" => "loadedmetadata",
-        "loadstart" => "loadstart",
-        "pause" => "pause",
-        "play" => "play",
-        "playing" => "playing",
-        "progress" => "progress",
-        "ratechange" => "ratechange",
-        "seeked" => "seeked",
-        "seeking" => "seeking",
-        "stalled" => "stalled",
-        "suspend" => "suspend",
-        "timeupdate" => "timeupdate",
-        "volumechange" => "volumechange",
-        "waiting" => "waiting",
-        "toggle" => "toggle",
-        a => {
-            panic!("unsupported event type {:?}", a);
-        }
-    }
+pub fn decode_event(value: EventMessage) -> Option<Rc<dyn Any>> {
+    let val = value.contents;
+    let name = value.event.as_str();
+    type DragData = MouseData;
+
+    let evt = match_data! { val; name;
+        MouseData => "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup";
+        ClipboardData => "copy" | "cut" | "paste";
+        CompositionData => "compositionend" | "compositionstart" | "compositionupdate";
+        KeyboardData => "keydown" | "keypress" | "keyup";
+        FocusData => "blur" | "focus" | "focusin" | "focusout";
+        FormData => "change" | "input" | "invalid" | "reset" | "submit";
+        DragData => "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop";
+        PointerData => "pointerlockchange" | "pointerlockerror" | "pointerdown" | "pointermove" | "pointerup" | "pointerover" | "pointerout" | "pointerenter" | "pointerleave" | "gotpointercapture" | "lostpointercapture";
+        SelectionData => "selectstart" | "selectionchange" | "select";
+        TouchData => "touchcancel" | "touchend" | "touchmove" | "touchstart";
+        ScrollData => "scroll";
+        WheelData => "wheel";
+        MediaData => "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
+            | "encrypted" | "ended" | "interruptbegin" | "interruptend" | "loadeddata"
+            | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress"
+            | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate"
+            | "volumechange" | "waiting" | "error" | "load" | "loadend" | "timeout";
+        AnimationData => "animationstart" | "animationend" | "animationiteration";
+        TransitionData => "transitionend";
+        ToggleData => "toggle";
+        // ImageData => "load" | "error";
+        // OtherData => "abort" | "afterprint" | "beforeprint" | "beforeunload" | "hashchange" | "languagechange" | "message" | "offline" | "online" | "pagehide" | "pageshow" | "popstate" | "rejectionhandled" | "storage" | "unhandledrejection" | "unload" | "userproximity" | "vrdisplayactivate" | "vrdisplayblur" | "vrdisplayconnect" | "vrdisplaydeactivate" | "vrdisplaydisconnect" | "vrdisplayfocus" | "vrdisplaypointerrestricted" | "vrdisplaypointerunrestricted" | "vrdisplaypresentchange";
+    };
+
+    Some(evt)
 }

+ 14 - 9
packages/desktop/src/hot_reload.rs

@@ -1,4 +1,7 @@
-use dioxus_core::{SchedulerMsg, SetTemplateMsg, VirtualDom};
+#![allow(dead_code)]
+
+use dioxus_core::VirtualDom;
+
 use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
 use std::io::{BufRead, BufReader};
 use std::time::Duration;
@@ -10,32 +13,34 @@ fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalS
         .ok()
 }
 
-pub(crate) fn init(dom: &VirtualDom) {
+pub(crate) fn init(_dom: &VirtualDom) {
     let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
         Arc::new(Mutex::new(None));
+
     let latest_in_connection_handle = latest_in_connection.clone();
 
     // connect to processes for incoming data
     std::thread::spawn(move || {
-        if let Ok(listener) = LocalSocketListener::bind("@dioxusin") {
+        let temp_file = std::env::temp_dir().join("@dioxusin");
+
+        if let Ok(listener) = LocalSocketListener::bind(temp_file) {
             for conn in listener.incoming().filter_map(handle_error) {
                 *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn));
             }
         }
     });
 
-    let mut channel = dom.get_scheduler_channel();
-
     std::thread::spawn(move || {
         loop {
             if let Some(conn) = &mut *latest_in_connection.lock().unwrap() {
                 let mut buf = String::new();
                 match conn.read_line(&mut buf) {
                     Ok(_) => {
-                        let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
-                        channel
-                            .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
-                            .unwrap();
+                        todo!()
+                        // let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
+                        // channel
+                        //     .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
+                        //     .unwrap();
                     }
                     Err(err) => {
                         if err.kind() != std::io::ErrorKind::WouldBlock {

+ 128 - 111
packages/desktop/src/lib.rs

@@ -8,16 +8,20 @@ mod controller;
 mod desktop_context;
 mod escape;
 mod events;
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-mod hot_reload;
 mod protocol;
 
+#[cfg(all(feature = "hot-reload", debug_assertions))]
+mod hot_reload;
+
+use std::sync::atomic::AtomicBool;
+use std::sync::Arc;
+
 use desktop_context::UserWindowEvent;
 pub use desktop_context::{use_eval, use_window, DesktopContext};
+use futures_channel::mpsc::UnboundedSender;
 pub use wry;
 pub use wry::application as tao;
 
-use crate::events::trigger_from_serialized;
 pub use cfg::Config;
 use controller::DesktopController;
 use dioxus_core::*;
@@ -100,117 +104,13 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
 /// ```
 pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
     let event_loop = EventLoop::with_user_event();
-
     let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
-    let proxy = event_loop.create_proxy();
-
-    // We assume that if the icon is None, then the user just didnt set it
-    if cfg.window.window.window_icon.is_none() {
-        cfg.window.window.window_icon = Some(
-            tao::window::Icon::from_rgba(
-                include_bytes!("./assets/default_icon.bin").to_vec(),
-                460,
-                460,
-            )
-            .expect("image parse failed"),
-        );
-    }
 
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
         match window_event {
-            Event::NewEvents(StartCause::Init) => {
-                let builder = cfg.window.clone();
-
-                let window = builder.build(event_loop).unwrap();
-                let window_id = window.id();
-
-                let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone());
-
-                let proxy = proxy.clone();
-
-                let file_handler = cfg.file_drop_handler.take();
-                let custom_head = cfg.custom_head.clone();
-                let resource_dir = cfg.resource_dir.clone();
-                let index_file = cfg.custom_index.clone();
-
-                let mut webview = WebViewBuilder::new(window)
-                    .unwrap()
-                    .with_transparent(cfg.window.window.transparent)
-                    .with_url("dioxus://index.html/")
-                    .unwrap()
-                    .with_ipc_handler(move |_window: &Window, payload: String| {
-                        parse_ipc_message(&payload)
-                            .map(|message| match message.method() {
-                                "user_event" => {
-                                    let event = trigger_from_serialized(message.params());
-                                    log::trace!("User event: {:?}", event);
-                                    sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
-                                }
-                                "initialize" => {
-                                    is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
-                                    let _ = proxy.send_event(UserWindowEvent::Update);
-                                }
-                                "browser_open" => {
-                                    let data = message.params();
-                                    log::trace!("Open browser: {:?}", data);
-                                    if let Some(temp) = data.as_object() {
-                                        if temp.contains_key("href") {
-                                            let url = temp.get("href").unwrap().as_str().unwrap();
-                                            if let Err(e) = webbrowser::open(url) {
-                                                log::error!("Open Browser error: {:?}", e);
-                                            }
-                                        }
-                                    }
-                                }
-                                _ => (),
-                            })
-                            .unwrap_or_else(|| {
-                                log::warn!("invalid IPC message received");
-                            });
-                    })
-                    .with_custom_protocol(String::from("dioxus"), move |r| {
-                        protocol::desktop_handler(
-                            r,
-                            resource_dir.clone(),
-                            custom_head.clone(),
-                            index_file.clone(),
-                        )
-                    })
-                    .with_file_drop_handler(move |window, evet| {
-                        file_handler
-                            .as_ref()
-                            .map(|handler| handler(window, evet))
-                            .unwrap_or_default()
-                    });
-
-                for (name, handler) in cfg.protocols.drain(..) {
-                    webview = webview.with_custom_protocol(name, handler)
-                }
-
-                if cfg.disable_context_menu {
-                    // in release mode, we don't want to show the dev tool or reload menus
-                    webview = webview.with_initialization_script(
-                        r#"
-                        if (document.addEventListener) {
-                        document.addEventListener('contextmenu', function(e) {
-                            e.preventDefault();
-                        }, false);
-                        } else {
-                        document.attachEvent('oncontextmenu', function() {
-                            window.event.returnValue = false;
-                        });
-                        }
-                    "#,
-                    )
-                } else {
-                    // in debug, we are okay with the reload menu showing and dev tool
-                    webview = webview.with_devtools(true);
-                }
-
-                desktop.webviews.insert(window_id, webview.build().unwrap());
-            }
+            Event::NewEvents(StartCause::Init) => desktop.start(&mut cfg, event_loop),
 
             Event::WindowEvent {
                 event, window_id, ..
@@ -220,9 +120,7 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
                 _ => {}
             },
 
-            Event::UserEvent(user_event) => {
-                desktop_context::handler(user_event, &mut desktop, control_flow)
-            }
+            Event::UserEvent(user_event) => desktop.handle_event(user_event, control_flow),
             Event::MainEventsCleared => {}
             Event::Resumed => {}
             Event::Suspended => {}
@@ -232,3 +130,122 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
         }
     })
 }
+
+impl DesktopController {
+    fn start(
+        &mut self,
+        cfg: &mut Config,
+        event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
+    ) {
+        let webview = build_webview(
+            cfg,
+            event_loop,
+            self.is_ready.clone(),
+            self.proxy.clone(),
+            self.event_tx.clone(),
+        );
+
+        self.webviews.insert(webview.window().id(), webview);
+    }
+}
+
+fn build_webview(
+    cfg: &mut Config,
+    event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
+    is_ready: Arc<AtomicBool>,
+    proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
+    event_tx: UnboundedSender<serde_json::Value>,
+) -> wry::webview::WebView {
+    let builder = cfg.window.clone();
+    let window = builder.build(event_loop).unwrap();
+    let file_handler = cfg.file_drop_handler.take();
+    let custom_head = cfg.custom_head.clone();
+    let resource_dir = cfg.resource_dir.clone();
+    let index_file = cfg.custom_index.clone();
+
+    // We assume that if the icon is None in cfg, then the user just didnt set it
+    if cfg.window.window.window_icon.is_none() {
+        window.set_window_icon(Some(
+            tao::window::Icon::from_rgba(
+                include_bytes!("./assets/default_icon.bin").to_vec(),
+                460,
+                460,
+            )
+            .expect("image parse failed"),
+        ));
+    }
+
+    let mut webview = WebViewBuilder::new(window)
+        .unwrap()
+        .with_transparent(cfg.window.window.transparent)
+        .with_url("dioxus://index.html/")
+        .unwrap()
+        .with_ipc_handler(move |_window: &Window, payload: String| {
+            parse_ipc_message(&payload)
+                .map(|message| match message.method() {
+                    "user_event" => {
+                        _ = event_tx.unbounded_send(message.params());
+                    }
+                    "initialize" => {
+                        is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+                        let _ = proxy.send_event(UserWindowEvent::EditsReady);
+                    }
+                    "browser_open" => {
+                        let data = message.params();
+                        log::trace!("Open browser: {:?}", data);
+                        if let Some(temp) = data.as_object() {
+                            if temp.contains_key("href") {
+                                let url = temp.get("href").unwrap().as_str().unwrap();
+                                if let Err(e) = webbrowser::open(url) {
+                                    log::error!("Open Browser error: {:?}", e);
+                                }
+                            }
+                        }
+                    }
+                    _ => (),
+                })
+                .unwrap_or_else(|| {
+                    log::warn!("invalid IPC message received");
+                });
+        })
+        .with_custom_protocol(String::from("dioxus"), move |r| {
+            protocol::desktop_handler(
+                r,
+                resource_dir.clone(),
+                custom_head.clone(),
+                index_file.clone(),
+            )
+        })
+        .with_file_drop_handler(move |window, evet| {
+            file_handler
+                .as_ref()
+                .map(|handler| handler(window, evet))
+                .unwrap_or_default()
+        });
+
+    for (name, handler) in cfg.protocols.drain(..) {
+        webview = webview.with_custom_protocol(name, handler)
+    }
+
+    if cfg.disable_context_menu {
+        // in release mode, we don't want to show the dev tool or reload menus
+        webview = webview.with_initialization_script(
+            r#"
+                        if (document.addEventListener) {
+                        document.addEventListener('contextmenu', function(e) {
+                            e.preventDefault();
+                        }, false);
+                        } else {
+                        document.attachEvent('oncontextmenu', function() {
+                            window.event.returnValue = false;
+                        });
+                        }
+                    "#,
+        )
+    } else {
+        // in debug, we are okay with the reload menu showing and dev tool
+        webview = webview.with_devtools(true);
+    }
+
+    webview.build().unwrap()
+}

+ 1 - 1
packages/desktop/src/protocol.rs

@@ -7,7 +7,7 @@ use wry::{
 const MODULE_LOADER: &str = r#"
 <script>
     import("./index.js").then(function (module) {
-    module.main();
+        module.main();
     });
 </script>
 "#;

+ 3 - 6
packages/dioxus/Cargo.toml

@@ -23,9 +23,7 @@ default = ["macro", "hooks", "html"]
 macro = ["dioxus-core-macro", "dioxus-rsx"]
 html = ["dioxus-html"]
 hooks = ["dioxus-hooks"]
-hot-reload = [
-    "dioxus-core/hot-reload",
-]
+
 
 [dev-dependencies]
 futures-util = "0.3.21"
@@ -34,10 +32,9 @@ rand = { version = "0.8.4", features = ["small_rng"] }
 criterion = "0.3.5"
 thiserror = "1.0.30"
 env_logger = "0.9.0"
+tokio = { version = "1.21.2", features = ["full"] }
+# dioxus-edit-stream = { path = "../edit-stream" }
 
-[[bench]]
-name = "create"
-harness = false
 
 [[bench]]
 name = "jsframework"

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

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

+ 21 - 15
packages/dioxus/benches/jsframework.rs

@@ -5,9 +5,14 @@
 //! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
 //! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
 //!
-//! On my MBP 2019:
-//! - Dioxus takes 3ms to create 1_000 rows
-//! - Dioxus takes 30ms to create 10_000 rows
+//!
+//! Pre-templates (Mac M1):
+//! - 3ms to create 1_000 rows
+//! - 30ms to create 10_000 rows
+//!
+//! Post-templates
+//! - 580us to create 1_000 rows
+//! - 6.2ms to create 10_000 rows
 //!
 //! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
 //! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
@@ -20,25 +25,26 @@ criterion_group!(mbenches, create_rows);
 criterion_main!(mbenches);
 
 fn create_rows(c: &mut Criterion) {
-    static App: Component = |cx| {
+    fn app(cx: Scope) -> Element {
         let mut rng = SmallRng::from_entropy();
 
-        render!(table {
-            tbody {
-                (0..10_000_usize).map(|f| {
-                    let label = Label::new(&mut rng);
-                    rsx!(Row {
-                        row_id: f,
-                        label: label
+        render!(
+            table {
+                tbody {
+                    (0..10_000_usize).map(|f| {
+                        let label = Label::new(&mut rng);
+                        rsx!( Row { row_id: f, label: label } )
                     })
-                })
+                }
             }
-        })
-    };
+        )
+    }
 
     c.bench_function("create rows", |b| {
+        let mut dom = VirtualDom::new(app);
+        dom.rebuild();
+
         b.iter(|| {
-            let mut dom = VirtualDom::new(App);
             let g = dom.rebuild();
             assert!(g.edits.len() > 1);
         })

+ 101 - 0
packages/dioxus/examples/stress.rs

@@ -0,0 +1,101 @@
+use dioxus::prelude::*;
+use rand::prelude::*;
+
+fn main() {
+    let mut dom = VirtualDom::new(app);
+
+    dom.rebuild();
+
+    for _ in 0..1000 {
+        dom.rebuild();
+    }
+}
+
+fn app(cx: Scope) -> Element {
+    let mut rng = SmallRng::from_entropy();
+
+    render! (
+        table {
+            tbody {
+                (0..10_000_usize).map(|f| {
+                    let label = Label::new(&mut rng);
+                    rsx!( Row { row_id: f, label: label } )
+                })
+            }
+        }
+    )
+}
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Label,
+}
+fn Row(cx: Scope<RowProps>) -> Element {
+    let [adj, col, noun] = cx.props.label.0;
+    render! {
+        tr {
+            td { class:"col-md-1", "{cx.props.row_id}" }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
+                a { class: "lbl", "{adj}" "{col}" "{noun}" }
+            }
+            td { class: "col-md-1",
+                a { class: "remove", onclick: move |_| {/* remove */},
+                    span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
+                }
+            }
+            td { class: "col-md-6" }
+        }
+    }
+}
+
+#[derive(PartialEq)]
+struct Label([&'static str; 3]);
+
+impl Label {
+    fn new(rng: &mut SmallRng) -> Self {
+        Label([
+            ADJECTIVES.choose(rng).unwrap(),
+            COLOURS.choose(rng).unwrap(),
+            NOUNS.choose(rng).unwrap(),
+        ])
+    }
+}
+
+static ADJECTIVES: &[&str] = &[
+    "pretty",
+    "large",
+    "big",
+    "small",
+    "tall",
+    "short",
+    "long",
+    "handsome",
+    "plain",
+    "quaint",
+    "clean",
+    "elegant",
+    "easy",
+    "angry",
+    "crazy",
+    "helpful",
+    "mushy",
+    "odd",
+    "unsightly",
+    "adorable",
+    "important",
+    "inexpensive",
+    "cheap",
+    "expensive",
+    "fancy",
+];
+
+static COLOURS: &[&str] = &[
+    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
+    "orange",
+];
+
+static NOUNS: &[&str] = &[
+    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
+    "pizza", "mouse", "keyboard",
+];

Some files were not shown because too many files changed in this diff