Prechádzať zdrojové kódy

Merge branch 'master' into full-tailwind-css-example

ealmloff 2 rokov pred
rodič
commit
4b5bea171a
100 zmenil súbory, kde vykonal 5799 pridanie a 4281 odobranie
  1. 3 3
      .github/workflows/main.yml
  2. 3 1
      Cargo.toml
  3. 4 1
      docs/guide/Cargo.toml
  4. 311 0
      docs/guide/examples/custom_renderer.rs
  5. 26 203
      docs/guide/src/en/custom_renderer/index.md
  6. 8 7
      docs/guide/src/en/getting_started/desktop.md
  7. 17 0
      examples/PWA-example/Cargo.toml
  8. 42 0
      examples/PWA-example/Dioxus.toml
  9. 21 0
      examples/PWA-example/LICENSE
  10. 44 0
      examples/PWA-example/README.md
  11. 30 0
      examples/PWA-example/index.html
  12. BIN
      examples/PWA-example/public/favicon.ico
  13. BIN
      examples/PWA-example/public/logo_192.png
  14. BIN
      examples/PWA-example/public/logo_512.png
  15. 34 0
      examples/PWA-example/public/manifest.json
  16. 198 0
      examples/PWA-example/public/sw.js
  17. 20 0
      examples/PWA-example/src/main.rs
  18. BIN
      examples/assets/logo.png
  19. 36 0
      examples/counter.rs
  20. 3 2
      examples/custom_assets.rs
  21. 16 3
      examples/generic_component.rs
  22. 1 1
      examples/svg.rs
  23. 86 39
      examples/todomvc.rs
  24. 1 0
      packages/core/src/create.rs
  25. 25 12
      packages/core/src/diff.rs
  26. 5 5
      packages/core/src/mutations.rs
  27. 1 1
      packages/desktop/Cargo.toml
  28. BIN
      packages/desktop/src/assets/default_icon.bin
  29. BIN
      packages/desktop/src/assets/default_icon.png
  30. 13 2
      packages/desktop/src/cfg.rs
  31. 6 2
      packages/desktop/src/lib.rs
  32. 8 5
      packages/desktop/src/protocol.rs
  33. 18 4
      packages/desktop/src/webview.rs
  34. 0 0
      packages/dioxus-tui/.gitignore
  35. 0 0
      packages/dioxus-tui/.vscode/spellright.dict
  36. 2 7
      packages/dioxus-tui/Cargo.toml
  37. 9 9
      packages/dioxus-tui/README.md
  38. 165 0
      packages/dioxus-tui/benches/update.rs
  39. 0 0
      packages/dioxus-tui/examples/all_events.rs
  40. 0 0
      packages/dioxus-tui/examples/border.rs
  41. 0 0
      packages/dioxus-tui/examples/buttons.rs
  42. 0 0
      packages/dioxus-tui/examples/color_test.rs
  43. 4 2
      packages/dioxus-tui/examples/colorpicker.rs
  44. 0 0
      packages/dioxus-tui/examples/components.rs
  45. 0 0
      packages/dioxus-tui/examples/example.png
  46. 0 0
      packages/dioxus-tui/examples/flex.rs
  47. 0 0
      packages/dioxus-tui/examples/hover.rs
  48. 0 0
      packages/dioxus-tui/examples/list.rs
  49. 0 0
      packages/dioxus-tui/examples/margin.rs
  50. 0 0
      packages/dioxus-tui/examples/quadrants.rs
  51. 0 0
      packages/dioxus-tui/examples/readme.rs
  52. 5 19
      packages/dioxus-tui/examples/stress.rs
  53. 0 0
      packages/dioxus-tui/examples/task.rs
  54. 0 0
      packages/dioxus-tui/examples/text.rs
  55. 0 1
      packages/dioxus-tui/examples/widgets.rs
  56. 143 0
      packages/dioxus-tui/src/lib.rs
  57. 1 0
      packages/dioxus-tui/src/prelude/mod.rs
  58. 2 2
      packages/dioxus-tui/src/widgets/button.rs
  59. 2 2
      packages/dioxus-tui/src/widgets/checkbox.rs
  60. 0 0
      packages/dioxus-tui/src/widgets/input.rs
  61. 22 0
      packages/dioxus-tui/src/widgets/mod.rs
  62. 4 4
      packages/dioxus-tui/src/widgets/number.rs
  63. 6 4
      packages/dioxus-tui/src/widgets/password.rs
  64. 3 3
      packages/dioxus-tui/src/widgets/slider.rs
  65. 4 4
      packages/dioxus-tui/src/widgets/textbox.rs
  66. 0 0
      packages/dioxus-tui/test.html
  67. 0 0
      packages/dioxus-tui/tests/events.rs
  68. 20 0
      packages/fermi/README.md
  69. 33 6
      packages/html/src/events/keyboard.rs
  70. 2 3
      packages/interpreter/Cargo.toml
  71. 1 0
      packages/interpreter/src/sledgehammer_bindings.rs
  72. 348 525
      packages/native-core-macro/src/lib.rs
  73. 0 29
      packages/native-core-macro/src/sorted_slice.rs
  74. 0 244
      packages/native-core-macro/tests/called_minimally_on_build.rs
  75. 0 421
      packages/native-core-macro/tests/update_state.rs
  76. 15 6
      packages/native-core/Cargo.toml
  77. 1 1
      packages/native-core/README.md
  78. 231 0
      packages/native-core/examples/custom_attr.rs
  79. 175 0
      packages/native-core/examples/font_size.rs
  80. 222 0
      packages/native-core/examples/simple.rs
  81. 249 0
      packages/native-core/examples/simple_dioxus.rs
  82. 296 0
      packages/native-core/src/dioxus.rs
  83. 4 0
      packages/native-core/src/layout_attributes.rs
  84. 33 35
      packages/native-core/src/lib.rs
  85. 160 54
      packages/native-core/src/node.rs
  86. 128 128
      packages/native-core/src/node_ref.rs
  87. 17 0
      packages/native-core/src/node_watcher.rs
  88. 267 818
      packages/native-core/src/passes.rs
  89. 982 401
      packages/native-core/src/real_dom.rs
  90. 0 305
      packages/native-core/src/state.rs
  91. 215 561
      packages/native-core/src/tree.rs
  92. 43 71
      packages/native-core/src/utils/cursor.rs
  93. 4 0
      packages/native-core/src/utils/mod.rs
  94. 219 263
      packages/native-core/src/utils/persistant_iterator.rs
  95. 237 0
      packages/native-core/tests/called_minimally_on_build.rs
  96. 51 31
      packages/native-core/tests/fuzzing.rs
  97. 50 31
      packages/native-core/tests/miri_native.rs
  98. 440 0
      packages/native-core/tests/passes.rs
  99. 2 0
      packages/rink/.gitignore
  100. 2 0
      packages/rink/.vscode/spellright.dict

+ 3 - 3
.github/workflows/main.yml

@@ -39,7 +39,7 @@ jobs:
           override: true
       - uses: Swatinem/rust-cache@v2
       - run: sudo apt-get update
-      - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
+      - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
       - uses: actions/checkout@v3
       - uses: actions-rs/cargo@v1
         with:
@@ -58,7 +58,7 @@ jobs:
           override: true
       - uses: Swatinem/rust-cache@v2
       - run: sudo apt-get update
-      - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
+      - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
       - uses: davidB/rust-cargo-make@v1
       - uses: browser-actions/setup-firefox@latest
       - uses: jetli/wasm-pack-action@v0.4.0
@@ -98,7 +98,7 @@ jobs:
           override: true
       - uses: Swatinem/rust-cache@v2
       - run: sudo apt-get update
-      - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
+      - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
       - run: rustup component add clippy
       - uses: actions/checkout@v3
       - uses: actions-rs/cargo@v1

+ 3 - 1
Cargo.toml

@@ -15,7 +15,8 @@ members = [
     "packages/liveview",
     "packages/autofmt",
     "packages/rsx",
-    "packages/tui",
+    "packages/dioxus-tui",
+    "packages/rink",
     "packages/native-core",
     "packages/native-core-macro",
     "packages/rsx-rosetta",
@@ -24,6 +25,7 @@ members = [
     "docs/guide",
     # Full project examples
     "examples/tailwind",
+    "examples/PWA-example",
 ]
 
 # This is a "virtual package"

+ 4 - 1
docs/guide/Cargo.toml

@@ -11,10 +11,13 @@ dioxus = { path = "../../packages/dioxus" }
 dioxus-desktop = { path = "../../packages/desktop" }
 dioxus-web = { path = "../../packages/web" }
 dioxus-ssr = { path = "../../packages/ssr" }
+dioxus-native-core = { path = "../../packages/native-core" }
+dioxus-native-core-macro = { path = "../../packages/native-core-macro" }
 dioxus-router = { path = "../../packages/router" }
 dioxus-liveview = { path = "../../packages/liveview", features = ["axum"] }
-dioxus-tui = { path = "../../packages/tui" }
+dioxus-tui = { path = "../../packages/dioxus-tui" }
 fermi = { path = "../../packages/fermi" }
+shipyard = "0.6.2"
 
 
 # dioxus = { path = "../../packages/dioxus", features = ["desktop", "web", "ssr", "router", "fermi", "tui"] }

+ 311 - 0
docs/guide/examples/custom_renderer.rs

@@ -0,0 +1,311 @@
+use dioxus::html::input_data::keyboard_types::{Code, Key, Modifiers};
+use dioxus::prelude::*;
+use dioxus_native_core::exports::shipyard::Component;
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core::utils::cursor::{Cursor, Pos};
+use dioxus_native_core_macro::partial_derive_state;
+
+// ANCHOR: state_impl
+struct FontSize(f64);
+
+// All states need to derive Component
+#[derive(Default, Debug, Copy, Clone, Component)]
+struct Size(f64, f64);
+
+/// Derive some of the boilerplate for the State implementation
+#[partial_derive_state]
+impl State for Size {
+    type ParentDependencies = ();
+
+    // The size of the current node depends on the size of its children
+    type ChildDependencies = (Self,);
+
+    type NodeDependencies = ();
+
+    // Size only cares about the width, height, and text parts of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        // Get access to the width and height attributes
+        .with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
+        // Get access to the text of the node
+        .with_text();
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> bool {
+        let font_size = context.get::<FontSize>().unwrap().0;
+        let mut width;
+        let mut height;
+        if let Some(text) = node_view.text() {
+            // if the node has text, use the text to size our object
+            width = text.len() as f64 * font_size;
+            height = font_size;
+        } else {
+            // otherwise, the size is the maximum size of the children
+            width = children
+                .iter()
+                .map(|(item,)| item.0)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+
+            height = children
+                .iter()
+                .map(|(item,)| item.1)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+        }
+        // if the node contains a width or height attribute it overrides the other size
+        for a in node_view.attributes().into_iter().flatten() {
+            match &*a.attribute.name {
+                "width" => width = a.value.as_float().unwrap(),
+                "height" => height = a.value.as_float().unwrap(),
+                // because Size only depends on the width and height, no other attributes will be passed to the member
+                _ => panic!(),
+            }
+        }
+        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
+        let changed = (width != self.0) || (height != self.1);
+        *self = Self(width, height);
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct TextColor {
+    r: u8,
+    g: u8,
+    b: u8,
+}
+
+#[partial_derive_state]
+impl State for TextColor {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // TextColor only cares about the color attribute of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the color attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
+        let new = match node_view
+            .attributes()
+            .and_then(|mut attrs| attrs.next())
+            .and_then(|attr| attr.value.as_text())
+        {
+            // if there is a color tag, translate it
+            Some("red") => TextColor { r: 255, g: 0, b: 0 },
+            Some("green") => TextColor { r: 0, g: 255, b: 0 },
+            Some("blue") => TextColor { r: 0, g: 0, b: 255 },
+            Some(color) => panic!("unknown color {color}"),
+            // otherwise check if the node has a parent and inherit that color
+            None => match parent {
+                Some((parent,)) => *parent,
+                None => Self::default(),
+            },
+        };
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct Border(bool);
+
+#[partial_derive_state]
+impl State for Border {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // Border does not depended on any other member in the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the border attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // check if the node contians a border attribute
+        let new = Self(
+            node_view
+                .attributes()
+                .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
+                .is_some(),
+        );
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+// ANCHOR_END: state_impl
+
+// ANCHOR: rendering
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    fn app(cx: Scope) -> Element {
+        let count = use_state(cx, || 0);
+
+        use_future(cx, (count,), |(count,)| async move {
+            loop {
+                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+                count.set(*count + 1);
+            }
+        });
+
+        cx.render(rsx! {
+            div{
+                color: "red",
+                "{count}"
+            }
+        })
+    }
+
+    // create the vdom, the real_dom, and the binding layer between them
+    let mut vdom = VirtualDom::new(app);
+    let mut rdom: RealDom = RealDom::new([
+        Border::to_type_erased(),
+        TextColor::to_type_erased(),
+        Size::to_type_erased(),
+    ]);
+    let mut dioxus_intigration_state = DioxusState::create(&mut rdom);
+
+    let mutations = vdom.rebuild();
+    // update the structure of the real_dom tree
+    dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
+    let mut ctx = SendAnyMap::new();
+    // set the font size to 3.3
+    ctx.insert(FontSize(3.3));
+    // update the State for nodes in the real_dom tree
+    let _to_rerender = rdom.update_state(ctx);
+
+    // we need to run the vdom in a async runtime
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?
+        .block_on(async {
+            loop {
+                // wait for the vdom to update
+                vdom.wait_for_work().await;
+
+                // get the mutations from the vdom
+                let mutations = vdom.render_immediate();
+
+                // update the structure of the real_dom tree
+                dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
+
+                // update the state of the real_dom tree
+                let mut ctx = SendAnyMap::new();
+                // set the font size to 3.3
+                ctx.insert(FontSize(3.3));
+                let _to_rerender = rdom.update_state(ctx);
+
+                // render...
+                rdom.traverse_depth_first(|node| {
+                    let indent = " ".repeat(node.height() as usize);
+                    let color = *node.get::<TextColor>().unwrap();
+                    let size = *node.get::<Size>().unwrap();
+                    let border = *node.get::<Border>().unwrap();
+                    let id = node.id();
+                    let node = node.node_type();
+                    let node_type = &*node;
+                    println!("{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}");
+                });
+            }
+        })
+}
+// ANCHOR_END: rendering
+
+// ANCHOR: derive_state
+// All states must derive Component (https://docs.rs/shipyard/latest/shipyard/derive.Component.html)
+// They also must implement Default or provide a custom implementation of create in the State trait
+#[derive(Default, Component)]
+struct MyState;
+
+/// Derive some of the boilerplate for the State implementation
+#[partial_derive_state]
+impl State for MyState {
+    // The states of the parent nodes this state depends on
+    type ParentDependencies = ();
+
+    // The states of the child nodes this state depends on
+    type ChildDependencies = (Self,);
+
+    // The states of the current node this state depends on
+    type NodeDependencies = ();
+
+    // The parts of the current text, element, or placeholder node in the tree that this state depends on
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+    // How to update the state of the current node based on the state of the parent nodes, child nodes, and the current node
+    // Returns true if the node was updated and false if the node was not updated
+    fn update<'a>(
+        &mut self,
+        // The view of the current node limited to the parts this state depends on
+        _node_view: NodeView<()>,
+        // The state of the current node that this state depends on
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        // The state of the parent nodes that this state depends on
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        // The state of the child nodes that this state depends on
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        // The context of the current node used to pass global state into the tree
+        _context: &SendAnyMap,
+    ) -> bool {
+        todo!()
+    }
+
+    // partial_derive_state will generate a default implementation of all the other methods
+}
+// ANCHOR_END: derive_state
+
+#[allow(unused)]
+// ANCHOR: cursor
+fn text_editing() {
+    let mut cursor = Cursor::default();
+    let mut text = String::new();
+
+    // handle keyboard input with a max text length of 10
+    cursor.handle_input(
+        &Code::ArrowRight,
+        &Key::ArrowRight,
+        &Modifiers::empty(),
+        &mut text,
+        10,
+    );
+
+    // mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse)
+    cursor.start = Pos::new(0, 0);
+    cursor.end = Some(Pos::new(5, 0));
+
+    // delete the selected text and move the cursor to the start of the selection
+    cursor.delete_selection(&mut text);
+}
+// ANCHOR_END: cursor

+ 26 - 203
docs/guide/src/en/custom_renderer/index.md

@@ -99,13 +99,14 @@ Template {
     attr_paths: &'a [&'a [u8]],
 }
 ```
+
 > For more detailed docs about the struture of templates see the [Template api docs](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html)
 
 This template will be sent to the renderer in the [list of templates](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates) supplied with the mutations the first time it is used. Any time the renderer encounters a [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate) mutation after this, it should clone the template and store it in the given id.
 
-For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be navigated to later.
+For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later.
 
-In HTML renderers, this template could look like:
+In HTML renderers, this template could look like this:
 
 ```html
 <h1>""</h1>
@@ -206,7 +207,9 @@ nodes: [
     "count: 0",
 ]
 ```
+
 Over time, our stack looked like this:
+
 ```rust
 [Root]
 [Root, <h1>""</h1>]
@@ -218,15 +221,15 @@ Conveniently, this approach completely separates the Virtual DOM and the Real DO
 
 Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.
 
-This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs.
+This little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs.
 
 ## Event loop
 
-Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too.
+Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too.
 
 The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:
 
-```rust
+```rust, ignore
 pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
     // Push the body element onto the WebsysDom's stack machine
     let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());
@@ -254,9 +257,9 @@ pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
 }
 ```
 
-It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.
+It's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.
 
-```rust
+```rust, ignore
 fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
     match event.type_().as_str() {
         "keydown" => {
@@ -294,16 +297,17 @@ For more examples and information on how to create custom namespaces, see the [`
 
 # Native Core
 
-If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout, and text editing for you.
+If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you.
 
 ## The RealDom
 
-The `RealDom` is a higher-level abstraction over updating the Dom. It updates with `Mutations` and provides a way to incrementally update the state of nodes based on attributes or other states that change.
+The `RealDom` is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply `Mutations` to the RealDom.
 
 ### Example
 
 Let's build a toy renderer with borders, size, and text color.
 Before we start let's take a look at an example element we can render:
+
 ```rust
 cx.render(rsx!{
     div{
@@ -372,188 +376,24 @@ In the following diagram arrows represent dataflow:
 [//]: # "        end"
 [//]: # "    end"
 
-To help in building a Dom, native-core provides four traits: State, ChildDepState, ParentDepState, NodeDepState, and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with ```#[derive(State)]```. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.
+To help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.
 
-```rust
+Native Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the `#[partial_derive_state]` macro handle the rest:
 
-use dioxus_native_core::node_ref::*;
-use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
-use dioxus_native_core_macro::{sorted_str_slice, State};
-
-#[derive(Default, Copy, Clone)]
-struct Size(f64, f64);
-// Size only depends on the current node and its children, so it implements ChildDepState
-impl ChildDepState for Size {
-    // Size accepts a font size context
-    type Ctx = f64;
-    // Size depends on the Size part of each child
-    type DepState = (Self,);
-    // Size only cares about the width, height, and text parts of the current node
-    const NODE_MASK: NodeMask =
-        NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([
-            "width", "height"
-        ])))
-        .with_text();
-    fn reduce<'a>(
-        &mut self,
-        node: NodeView,
-        children: impl Iterator<Item = (&'a Self,)>,
-        ctx: &Self::Ctx,
-    ) -> bool
-    where
-        Self::DepState: 'a,
-    {
-        let mut width;
-        let mut height;
-        if let Some(text) = node.text() {
-            // if the node has text, use the text to size our object
-            width = text.len() as f64 * ctx;
-            height = *ctx;
-        } else {
-            // otherwise, the size is the maximum size of the children
-            width = children
-                .by_ref()
-                .map(|(item,)| item.0)
-                .reduce(|accum, item| if accum >= item { accum } else { item })
-                .unwrap_or(0.0);
-
-            height = children
-                .map(|(item,)| item.1)
-                .reduce(|accum, item| if accum >= item { accum } else { item })
-                .unwrap_or(0.0);
-        }
-        // if the node contains a width or height attribute it overrides the other size
-        for a in node.attributes().into_iter().flatten() {
-            match &*a.attribute.name {
-                "width" => width = a.value.as_float().unwrap(),
-                "height" => height = a.value.as_float().unwrap(),
-                // because Size only depends on the width and height, no other attributes will be passed to the member
-                _ => panic!(),
-            }
-        }
-        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
-        let changed = (width != self.0) || (height != self.1);
-        *self = Self(width, height);
-        changed
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Default)]
-struct TextColor {
-    r: u8,
-    g: u8,
-    b: u8,
-}
-// TextColor only depends on the current node and its parent, so it implements ParentDepState
-impl ParentDepState for TextColor {
-    type Ctx = ();
-    // TextColor depends on the TextColor part of the parent
-    type DepState = (Self,);
-    // TextColor only cares about the color attribute of the current node
-    const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"]));
-    fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
-        // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
-        let new = match node
-            .attributes()
-            .and_then(|attrs| attrs.next())
-            .map(|attr| attr.attribute.name.as_str())
-        {
-            // if there is a color tag, translate it
-            Some("red") => TextColor { r: 255, g: 0, b: 0 },
-            Some("green") => TextColor { r: 0, g: 255, b: 0 },
-            Some("blue") => TextColor { r: 0, g: 0, b: 255 },
-            Some(_) => panic!("unknown color"),
-            // otherwise check if the node has a parent and inherit that color
-            None => match parent {
-                Some((parent,)) => *parent,
-                None => Self::default(),
-            },
-        };
-        // check if the member has changed
-        let changed = new != *self;
-        *self = new;
-        changed
-    }
-}
+```rust, ignore
+{{#include ../../../examples/custom_renderer.rs:derive_state}}
+```
 
-#[derive(Debug, Clone, PartialEq, Default)]
-struct Border(bool);
-// TextColor only depends on the current node, so it implements NodeDepState
-impl NodeDepState for Border {
-    type Ctx = ();
-    type DepState = ();
-
-    // Border does not depended on any other member in the current node
-    const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["border"]));
-    fn reduce(&mut self, node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
-        // check if the node contians a border attribute
-        let new = Self(
-            node.attributes()
-                .and_then(|attrs| attrs.next().map(|a| a.attribute.name == "border"))
-                .is_some(),
-        );
-        // check if the member has changed
-        let changed = new != *self;
-        *self = new;
-        changed
-    }
-}
+Lets take a look at how to implement the State trait for a simple renderer.
 
-// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)]
-#[derive(State, Default, Clone)]
-struct ToyState {
-    // the color member of it's parent and no context
-    #[parent_dep_state(color)]
-    color: TextColor,
-    // depends on the node, and no context
-    #[node_dep_state()]
-    border: Border,
-    // depends on the layout_width member of children and f32 context (for text size)
-    #[child_dep_state(size, f32)]
-    size: Size,
-}
+```rust
+{{#include ../../../examples/custom_renderer.rs:state_impl}}
 ```
 
-Now that we have our state, we can put it to use in our dom. We can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.
+Now that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed.
+
 ```rust
-fn main(){
-    fn app(cx: Scope) -> Element {
-        cx.render(rsx!{
-            div{
-                color: "red",
-                "hello world"
-            }
-        })
-    }
-    let vdom = VirtualDom::new(app);
-    let rdom: RealDom<ToyState> = RealDom::new();
-
-    let mutations = dom.rebuild();
-    // update the structure of the real_dom tree
-    let to_update = rdom.apply_mutations(vec![mutations]);
-    let mut ctx = AnyMap::new();
-    // set the font size to 3.3
-    ctx.insert(3.3f64);
-    // update the ToyState for nodes in the real_dom tree
-    let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();
-
-    // we need to run the vdom in a async runtime
-    tokio::runtime::Builder::new_current_thread()
-        .enable_all()
-        .build()?
-        .block_on(async {
-            loop{
-                let wait = vdom.wait_for_work();
-                let mutations = vdom.work_with_deadline(|| false);
-                let to_update = rdom.apply_mutations(mutations);
-                let mut ctx = AnyMap::new();
-                ctx.insert(3.3f64);
-                let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();
-
-                // render...
-            }
-        })
-}
+{{#include ../../../examples/custom_renderer.rs:rendering}}
 ```
 
 ## Layout
@@ -565,26 +405,9 @@ For most platforms, the layout of the Elements will stay the same. The [layout_a
 To make it easier to implement text editing in rust renderers, `native-core` also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated.
 
 ```rust
-let mut cursor = Cursor::default();
-let mut text = String::new();
-
-let keyboard_data = dioxus_html::KeyboardData::new(
-    dioxus_html::input_data::keyboard_types::Key::ArrowRight,
-    dioxus_html::input_data::keyboard_types::Code::ArrowRight,
-    dioxus_html::input_data::keyboard_types::Location::Standard,
-    false,
-    Modifiers::empty(),
-);
-// handle keyboard input with a max text length of 10
-cursor.handle_input(&keyboard_data, &mut text, 10);
-
-// mannually select text between characters 0-5 on the first line (this could be from dragging with a mouse)
-cursor.start = Pos::new(0, 0);
-cursor.end = Some(Pos::new(5, 0));
-
-// delete the selected text and move the cursor to the start of the selection
-cursor.delete_selection(&mut text);
+{{#include ../../../examples/custom_renderer.rs:cursor}}
 ```
 
 ## Conclusion
+
 That should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM).

+ 8 - 7
docs/guide/src/en/getting_started/desktop.md

@@ -5,6 +5,7 @@ Build a standalone native desktop app that looks and feels the same across opera
 Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.
 
 Examples:
+
 - [File Explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer)
 - [WiFi Scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
 
@@ -12,21 +13,22 @@ Examples:
 
 ## Support
 
-The desktop is a powerful target for Dioxus but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations.
+The desktop is a powerful target for Dioxus but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are _not_ available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs _are_ accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations.
 
 Dioxus Desktop is built off [Tauri](https://tauri.app/). Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri – mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly.
 
 # Getting started
 
 ## Platform-Specific Dependencies
+
 Dioxus desktop renders through a web view. Depending on your platform, you might need to install some dependancies.
 
 ### Windows
 
-Windows Desktop apps depend on WebView2 – a library that should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you *don't* have Webview2, [then you can install it through Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). MS provides 3 options:
+Windows Desktop apps depend on WebView2 – a library that should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you _don't_ have Webview2, [then you can install it through Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). MS provides 3 options:
 
-1. A tiny "evergreen" *bootstrapper* that fetches an installer from Microsoft's CDN
-2. A tiny *installer* that fetches Webview2 from Microsoft's CDN
+1. A tiny "evergreen" _bootstrapper_ that fetches an installer from Microsoft's CDN
+2. A tiny _installer_ that fetches Webview2 from Microsoft's CDN
 3. A statically linked version of Webview2 in your final binary for offline users
 
 For development purposes, use Option 1.
@@ -36,19 +38,18 @@ For development purposes, use Option 1.
 Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, likely, your users will already have WebkitGtk.
 
 ```bash
-sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
+sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
 ```
 
 When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.
 
 ```bash
 # on Debian/bullseye use:
-sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
+sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
 ```
 
 If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/v1/guides/getting-started/prerequisites#setting-up-linux).
 
-
 ### MacOS
 
 Currently – everything for macOS is built right in! However, you might run into an issue if you're using nightly Rust due to some permissions issues in our Tao dependency (which have been resolved but not published).

+ 17 - 0
examples/PWA-example/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "dioxus-pwa-example"
+version = "0.1.0"
+authors = ["Antonio Curavalea <one.kyonblack@gmail.com>"]
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dioxus = { path = "../../packages/dioxus", version = "^0.3.0"}
+dioxus-web = { path = "../../packages/web", version = "^0.3.0"}
+
+log = "0.4.6"
+
+# WebAssembly Debug
+wasm-logger = "0.2.0"
+console_error_panic_hook = "0.1.7"

+ 42 - 0
examples/PWA-example/Dioxus.toml

@@ -0,0 +1,42 @@
+[application]
+
+# App (Project) Name
+name = "dioxus-pwa-example"
+
+# Dioxus App Default Platform
+# desktop, web, mobile, ssr
+default_platform = "web"
+
+# `build` & `serve` dist path
+out_dir = "dist"
+
+# resource (public) file folder
+asset_dir = "public"
+
+[web.app]
+
+# HTML title tag content
+title = "dioxus | ⛺"
+
+[web.watcher]
+
+# when watcher trigger, regenerate the `index.html`
+reload_html = true
+
+# which files or dirs will be watcher monitoring
+watch_path = ["src", "public"]
+
+# include `assets` in web platform
+[web.resource]
+
+# CSS style file
+style = []
+
+# Javascript code file
+script = []
+
+[web.resource.dev]
+
+# Javascript code file
+# serve: [dev-server] only
+script = []

+ 21 - 0
examples/PWA-example/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Dioxus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 44 - 0
examples/PWA-example/README.md

@@ -0,0 +1,44 @@
+# Dioxus PWA example
+
+This is a basic example of a progressive web app (PWA) using Dioxus and Dioxus CLI.
+Currently PWA functionality requires the use of a service worker and manifest file, so this isn't 100% Rust yet.
+
+It is also very much usable as a template for your projects, if you're aiming to create a PWA.
+
+## Try the example
+
+Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`).
+
+You can run `dioxus serve` in this directory to start the web server locally, or run
+`dioxus build --release` to build the project so you can deploy it on a separate web-server.
+
+## Project Structure
+```
+├── Cargo.toml
+├── Dioxus.toml
+├── index.html // Custom HTML is needed for this, to load the SW and manifest.
+├── LICENSE
+├── public
+│   ├── favicon.ico
+│   ├── logo_192.png
+│   ├── logo_512.png
+│   ├── manifest.json // The manifest file - edit this as you need to.
+│   └── sw.js // The service worker - you must edit this for actual projects.
+├── README.md
+└── src
+    └── main.rs
+```
+
+## Resources
+
+If you're just getting started with PWAs, here are some useful resources:
+
+* [PWABuilder docs](https://docs.pwabuilder.com/#/)
+* [MDN article on PWAs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
+
+For service worker scripting (in JavaScript):
+
+* [Service worker guide from PWABuilder](https://docs.pwabuilder.com/#/home/sw-intro)
+* [Service worker examples, also from PWABuilder](https://github.com/pwa-builder/pwabuilder-serviceworkers)
+
+If you want to stay as close to 100% Rust as possible, you can try using [wasi-worker](https://github.com/dunnock/wasi-worker) to replace the JS service worker file. The JSON manifest will still be required though.

+ 30 - 0
examples/PWA-example/index.html

@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>{app_title}</title>
+  <script>
+    if ('serviceWorker' in navigator) {
+      navigator.serviceWorker.register(
+        '/sw.js'
+      );
+    }
+  </script>
+  <link rel="manifest" href="manifest.json">
+  <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <meta charset="UTF-8" />
+  {style_include}
+</head>
+<body>
+  <div id="main"></div>
+  <script type="module">
+    import init from "/{base_path}/assets/dioxus/{app_name}.js";
+    init("/{base_path}/assets/dioxus/{app_name}_bg.wasm").then(wasm => {
+      if (wasm.__wbindgen_start == undefined) {
+        wasm.main();
+      }
+    });
+  </script>
+  {script_include}
+</body>
+</html>

BIN
examples/PWA-example/public/favicon.ico


BIN
examples/PWA-example/public/logo_192.png


BIN
examples/PWA-example/public/logo_512.png


+ 34 - 0
examples/PWA-example/public/manifest.json

@@ -0,0 +1,34 @@
+{
+  "name": "Dioxus",
+  "icons": [
+    {
+      "src": "logo_192.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "logo_512.png",
+      "type": "image/png",
+      "sizes": "512x512",
+      "purpose": "any"
+    },
+    {
+      "src": "logo_512.png",
+      "type": "image/png",
+      "sizes": "any",
+      "purpose": "any"
+    }
+  ],
+  "start_url": "/",
+  "id": "/",
+  "display": "standalone",
+  "display_override": ["window-control-overlay", "standalone"],
+  "scope": "/",
+  "theme_color": "#000000",
+  "background_color": "#ffffff",
+  "short_name": "Dioxus",
+  "description": "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust.",
+  "dir": "ltr",
+  "lang": "en",
+  "orientation": "portrait"
+}

+ 198 - 0
examples/PWA-example/public/sw.js

@@ -0,0 +1,198 @@
+"use strict";
+
+//console.log('WORKER: executing.');
+
+/* A version number is useful when updating the worker logic,
+   allowing you to remove outdated cache entries during the update.
+*/
+var version = 'v1.0.0::';
+
+/* These resources will be downloaded and cached by the service worker
+   during the installation process. If any resource fails to be downloaded,
+   then the service worker won't be installed either.
+*/
+var offlineFundamentals = [
+  // add here the files you want to cache
+  'favicon.ico'
+];
+
+/* The install event fires when the service worker is first installed.
+   You can use this event to prepare the service worker to be able to serve
+   files while visitors are offline.
+*/
+self.addEventListener("install", function (event) {
+  //console.log('WORKER: install event in progress.');
+  /* Using event.waitUntil(p) blocks the installation process on the provided
+     promise. If the promise is rejected, the service worker won't be installed.
+  */
+  event.waitUntil(
+    /* The caches built-in is a promise-based API that helps you cache responses,
+       as well as finding and deleting them.
+    */
+    caches
+      /* You can open a cache by name, and this method returns a promise. We use
+         a versioned cache name here so that we can remove old cache entries in
+         one fell swoop later, when phasing out an older service worker.
+      */
+      .open(version + 'fundamentals')
+      .then(function (cache) {
+        /* After the cache is opened, we can fill it with the offline fundamentals.
+           The method below will add all resources in `offlineFundamentals` to the
+           cache, after making requests for them.
+        */
+        return cache.addAll(offlineFundamentals);
+      })
+      .then(function () {
+        //console.log('WORKER: install completed');
+      })
+  );
+});
+
+/* The fetch event fires whenever a page controlled by this service worker requests
+   a resource. This isn't limited to `fetch` or even XMLHttpRequest. Instead, it
+   comprehends even the request for the HTML page on first load, as well as JS and
+   CSS resources, fonts, any images, etc.
+*/
+self.addEventListener("fetch", function (event) {
+  //console.log('WORKER: fetch event in progress.');
+
+  /* We should only cache GET requests, and deal with the rest of method in the
+     client-side, by handling failed POST,PUT,PATCH,etc. requests.
+  */
+  if (event.request.method !== 'GET') {
+    /* If we don't block the event as shown below, then the request will go to
+       the network as usual.
+    */
+    //console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
+    return;
+  }
+  /* Similar to event.waitUntil in that it blocks the fetch event on a promise.
+     Fulfillment result will be used as the response, and rejection will end in a
+     HTTP response indicating failure.
+  */
+  event.respondWith(
+    caches
+      /* This method returns a promise that resolves to a cache entry matching
+         the request. Once the promise is settled, we can then provide a response
+         to the fetch request.
+      */
+      .match(event.request)
+      .then(function (cached) {
+        /* Even if the response is in our cache, we go to the network as well.
+           This pattern is known for producing "eventually fresh" responses,
+           where we return cached responses immediately, and meanwhile pull
+           a network response and store that in the cache.
+
+           Read more:
+           https://ponyfoo.com/articles/progressive-networking-serviceworker
+        */
+        var networked = fetch(event.request)
+          // We handle the network request with success and failure scenarios.
+          .then(fetchedFromNetwork, unableToResolve)
+          // We should catch errors on the fetchedFromNetwork handler as well.
+          .catch(unableToResolve);
+
+        /* We return the cached response immediately if there is one, and fall
+           back to waiting on the network as usual.
+        */
+        //console.log('WORKER: fetch event', cached ? '(cached)' : '(network)', event.request.url);
+        return cached || networked;
+
+        function fetchedFromNetwork(response) {
+          /* We copy the response before replying to the network request.
+             This is the response that will be stored on the ServiceWorker cache.
+          */
+          var cacheCopy = response.clone();
+
+          //console.log('WORKER: fetch response from network.', event.request.url);
+
+          caches
+            // We open a cache to store the response for this request.
+            .open(version + 'pages')
+            .then(function add(cache) {
+              /* We store the response for this request. It'll later become
+                 available to caches.match(event.request) calls, when looking
+                 for cached responses.
+              */
+              cache.put(event.request, cacheCopy);
+            })
+            .then(function () {
+              //console.log('WORKER: fetch response stored in cache.', event.request.url);
+            });
+
+          // Return the response so that the promise is settled in fulfillment.
+          return response;
+        }
+
+        /* When this method is called, it means we were unable to produce a response
+           from either the cache or the network. This is our opportunity to produce
+           a meaningful response even when all else fails. It's the last chance, so
+           you probably want to display a "Service Unavailable" view or a generic
+           error response.
+        */
+        function unableToResolve() {
+          /* There's a couple of things we can do here.
+             - Test the Accept header and then return one of the `offlineFundamentals`
+               e.g: `return caches.match('/some/cached/image.png')`
+             - You should also consider the origin. It's easier to decide what
+               "unavailable" means for requests against your origins than for requests
+               against a third party, such as an ad provider.
+             - Generate a Response programmaticaly, as shown below, and return that.
+          */
+
+          //console.log('WORKER: fetch request failed in both cache and network.');
+
+          /* Here we're creating a response programmatically. The first parameter is the
+             response body, and the second one defines the options for the response.
+          */
+          return new Response('<h1>Service Unavailable</h1>', {
+            status: 503,
+            statusText: 'Service Unavailable',
+            headers: new Headers({
+              'Content-Type': 'text/html'
+            })
+          });
+        }
+      })
+  );
+});
+
+/* The activate event fires after a service worker has been successfully installed.
+   It is most useful when phasing out an older version of a service worker, as at
+   this point you know that the new worker was installed correctly. In this example,
+   we delete old caches that don't match the version in the worker we just finished
+   installing.
+*/
+self.addEventListener("activate", function (event) {
+  /* Just like with the install event, event.waitUntil blocks activate on a promise.
+     Activation will fail unless the promise is fulfilled.
+  */
+  //console.log('WORKER: activate event in progress.');
+
+  event.waitUntil(
+    caches
+      /* This method returns a promise which will resolve to an array of available
+         cache keys.
+      */
+      .keys()
+      .then(function (keys) {
+        // We return a promise that settles when all outdated caches are deleted.
+        return Promise.all(
+          keys
+            .filter(function (key) {
+              // Filter by keys that don't start with the latest version prefix.
+              return !key.startsWith(version);
+            })
+            .map(function (key) {
+              /* Return a promise that's fulfilled
+                 when each outdated cache is deleted.
+              */
+              return caches.delete(key);
+            })
+        );
+      })
+      .then(function () {
+        //console.log('WORKER: activate completed.');
+      })
+  );
+});

+ 20 - 0
examples/PWA-example/src/main.rs

@@ -0,0 +1,20 @@
+use dioxus::prelude::*;
+
+fn main() {
+    // init debug tool for WebAssembly
+    wasm_logger::init(wasm_logger::Config::default());
+    console_error_panic_hook::set_once();
+
+    dioxus_web::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    cx.render(rsx! (
+        div {
+            style: "text-align: center;",
+            h1 { "🌗 Dioxus 🚀" }
+            h3 { "Frontend that scales." }
+            p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." }
+        }
+    ))
+}

BIN
examples/assets/logo.png


+ 36 - 0
examples/counter.rs

@@ -0,0 +1,36 @@
+//! Comparison example with leptos' counter example
+//! https://github.com/leptos-rs/leptos/blob/main/examples/counters/src/lib.rs
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let counters = use_state(cx, || vec![0, 0, 0]);
+    let sum: usize = counters.iter().copied().sum();
+
+    render! {
+        div {
+            button { onclick: move |_| counters.make_mut().push(0), "Add counter" }
+            button { onclick: move |_| { counters.make_mut().pop(); }, "Remove counter" }
+            p { "Total: {sum}" }
+            for (i, counter) in counters.iter().enumerate() {
+                li {
+                    button { onclick: move |_| counters.make_mut()[i] -= 1, "-1" }
+                    input {
+                        value: "{counter}",
+                        oninput: move |e| {
+                            if let Ok(value) = e.value.parse::<usize>() {
+                                counters.make_mut()[i] = value;
+                            }
+                        }
+                    }
+                    button { onclick: move |_| counters.make_mut()[i] += 1, "+1" }
+                    button { onclick: move |_| { counters.make_mut().remove(i); }, "x" }
+                }
+            }
+        }
+    }
+}

+ 3 - 2
examples/custom_assets.rs

@@ -7,9 +7,10 @@ fn main() {
 fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div {
-            "This should show an image:"
+            p {
+                "This should show an image:"
+            }
             img { src: "examples/assets/logo.png" }
-            img { src: "/Users/jonkelley/Desktop/blitz.png" }
         }
     })
 }

+ 16 - 3
examples/generic_component.rs

@@ -1,3 +1,5 @@
+use std::fmt::Display;
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -5,9 +7,20 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    cx.render(rsx! { generic_child::<i32>{} })
+    cx.render(rsx! { generic_child {
+        data: 0i32
+    } })
+}
+
+#[derive(PartialEq, Props)]
+struct GenericChildProps<T: Display + PartialEq> {
+    data: T,
 }
 
-fn generic_child<T>(cx: Scope) -> Element {
-    cx.render(rsx! { div {} })
+fn generic_child<T: Display + PartialEq>(cx: Scope<GenericChildProps<T>>) -> Element {
+    let data = &cx.props.data;
+
+    cx.render(rsx! { div {
+        "{data}"
+    } })
 }

+ 1 - 1
examples/svg.rs

@@ -26,7 +26,7 @@ fn app(cx: Scope) -> Element {
                     onclick: move |_| {
                         use rand::Rng;
                         let mut rng = rand::thread_rng();
-                        val.set(rng.gen_range(1..6));
+                        val.set(rng.gen_range(1..=6));
                     }
                 }
             }

+ 86 - 39
examples/todomvc.rs

@@ -7,7 +7,7 @@ fn main() {
     dioxus_desktop::launch(app);
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Copy)]
 pub enum FilterState {
     All,
     Active,
@@ -39,56 +39,99 @@ pub fn app(cx: Scope<()>) -> Element {
         .collect::<Vec<_>>();
     filtered_todos.sort_unstable();
 
-    let show_clear_completed = todos.values().any(|todo| todo.checked);
-    let items_left = filtered_todos.len();
-    let item_text = match items_left {
+    let active_todo_count = todos.values().filter(|item| !item.checked).count();
+    let active_todo_text = match active_todo_count {
         1 => "item",
         _ => "items",
     };
 
-    cx.render(rsx!{
+    let show_clear_completed = todos.values().any(|todo| todo.checked);
+
+    let selected = |state| {
+        if *filter == state {
+            "selected"
+        } else {
+            "false"
+        }
+    };
+
+    cx.render(rsx! {
         section { class: "todoapp",
             style { include_str!("./assets/todomvc.css") }
-            div {
-                header { class: "header",
-                    h1 {"todos"}
-                    input {
-                        class: "new-todo",
-                        placeholder: "What needs to be done?",
-                        value: "{draft}",
-                        autofocus: "true",
-                        oninput: move |evt| {
-                            draft.set(evt.value.clone());
-                        },
-                        onkeydown: move |evt| {
-                            if evt.key() == Key::Enter && !draft.is_empty() {
-                                todos.make_mut().insert(
-                                    **todo_id,
-                                    TodoItem {
-                                        id: **todo_id,
-                                        checked: false,
-                                        contents: draft.to_string(),
-                                    },
-                                );
-                                *todo_id.make_mut() += 1;
-                                draft.set("".to_string());
-                            }
+            header { class: "header",
+                h1 {"todos"}
+                input {
+                    class: "new-todo",
+                    placeholder: "What needs to be done?",
+                    value: "{draft}",
+                    autofocus: "true",
+                    oninput: move |evt| {
+                        draft.set(evt.value.clone());
+                    },
+                    onkeydown: move |evt| {
+                        if evt.key() == Key::Enter && !draft.is_empty() {
+                            todos.make_mut().insert(
+                                **todo_id,
+                                TodoItem {
+                                    id: **todo_id,
+                                    checked: false,
+                                    contents: draft.to_string(),
+                                },
+                            );
+                            *todo_id.make_mut() += 1;
+                            draft.set("".to_string());
+                        }
+                    }
+                }
+            }
+            section {
+                class: "main",
+                if !todos.is_empty() {
+                    rsx! {
+                        input {
+                            id: "toggle-all",
+                            class: "toggle-all",
+                            r#type: "checkbox",
+                            onchange: move |_| {
+                                let check = active_todo_count != 0;
+                                for (_, item) in todos.make_mut().iter_mut() {
+                                    item.checked = check;
+                                }
+                            },
+                            checked: if active_todo_count == 0 { "true" } else { "false" },
                         }
+                        label { r#for: "toggle-all" }
                     }
                 }
                 ul { class: "todo-list",
-                    filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id, todos: todos }))
+                    filtered_todos.iter().map(|id| rsx!(TodoEntry {
+                        key: "{id}",
+                        id: *id,
+                        todos: todos,
+                    }))
                 }
                 (!todos.is_empty()).then(|| rsx!(
                     footer { class: "footer",
                         span { class: "todo-count",
-                            strong {"{items_left} "}
-                            span {"{item_text} left"}
+                            strong {"{active_todo_count} "}
+                            span {"{active_todo_text} left"}
                         }
                         ul { class: "filters",
-                            li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
-                            li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
-                            li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
+                            for (state, state_text, url) in [
+                                (FilterState::All, "All", "#/"),
+                                (FilterState::Active, "Active", "#/active"),
+                                (FilterState::Completed, "Completed", "#/completed"),
+                            ] {
+                                li {
+                                    a {
+                                        href: url,
+                                        class: selected(state),
+                                        onclick: move |_| filter.set(state),
+                                        prevent_default: "onclick",
+                                        state_text
+                                    }
+                                }
+                            }
                         }
                         show_clear_completed.then(|| rsx!(
                             button {
@@ -102,8 +145,8 @@ pub fn app(cx: Scope<()>) -> Element {
             }
         }
         footer { class: "info",
-            p {"Double-click to edit a todo"}
-            p { "Created by ", a {  href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
+            p { "Double-click to edit a todo" }
+            p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
             p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
         }
     })
@@ -136,13 +179,17 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
                         cx.props.todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap();
                     }
                 }
-
                 label {
                     r#for: "cbg-{todo.id}",
-                    onclick: move |_| is_editing.set(true),
+                    ondblclick: move |_| is_editing.set(true),
                     prevent_default: "onclick",
                     "{todo.contents}"
                 }
+                button {
+                    class: "destroy",
+                    onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); },
+                    prevent_default: "onclick",
+                }
             }
             is_editing.then(|| rsx!{
                 input {

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

@@ -35,6 +35,7 @@ fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
 }
 
 #[test]
+#[cfg(debug_assertions)]
 fn sorting() {
     let r: [(usize, &[u8]); 5] = [
         (0, &[0, 1]),

+ 25 - 12
packages/core/src/diff.rs

@@ -148,6 +148,13 @@ impl<'b> VirtualDom {
 
         // Make sure the roots get transferred over while we're here
         right_template.root_ids.transfer(&left_template.root_ids);
+
+        // Update the node refs
+        for i in 0..right_template.root_ids.len() {
+            if let Some(root_id) = right_template.root_ids.get(i) {
+                self.update_template(root_id, right_template);
+            }
+        }
     }
 
     fn diff_dynamic_node(
@@ -628,10 +635,12 @@ impl<'b> VirtualDom {
             }
 
             let id = self.find_last_element(&new[last]);
-            self.mutations.push(Mutation::InsertAfter {
-                id,
-                m: nodes_created,
-            });
+            if nodes_created > 0 {
+                self.mutations.push(Mutation::InsertAfter {
+                    id,
+                    m: nodes_created,
+                })
+            }
             nodes_created = 0;
         }
 
@@ -652,10 +661,12 @@ impl<'b> VirtualDom {
                 }
 
                 let id = self.find_first_element(&new[last]);
-                self.mutations.push(Mutation::InsertBefore {
-                    id,
-                    m: nodes_created,
-                });
+                if nodes_created > 0 {
+                    self.mutations.push(Mutation::InsertBefore {
+                        id,
+                        m: nodes_created,
+                    });
+                }
 
                 nodes_created = 0;
             }
@@ -676,10 +687,12 @@ impl<'b> VirtualDom {
             }
 
             let id = self.find_first_element(&new[first_lis]);
-            self.mutations.push(Mutation::InsertBefore {
-                id,
-                m: nodes_created,
-            });
+            if nodes_created > 0 {
+                self.mutations.push(Mutation::InsertBefore {
+                    id,
+                    m: nodes_created,
+                });
+            }
         }
     }
 

+ 5 - 5
packages/core/src/mutations.rs

@@ -68,7 +68,7 @@ pub enum Mutation<'a> {
         /// The ID of the element being mounted to
         id: ElementId,
 
-        /// The number of nodes on the stack
+        /// The number of nodes on the stack to append to the target element
         m: usize,
     },
 
@@ -155,7 +155,7 @@ pub enum Mutation<'a> {
         /// The ID of the node we're going to replace with
         id: ElementId,
 
-        /// The number of nodes on the stack to use to replace
+        /// The number of nodes on the stack to replace the target element with
         m: usize,
     },
 
@@ -167,7 +167,7 @@ pub enum Mutation<'a> {
         /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
         path: &'static [u8],
 
-        /// The number of nodes on the stack to use to replace
+        /// The number of nodes on the stack to replace the target element with
         m: usize,
     },
 
@@ -176,7 +176,7 @@ pub enum Mutation<'a> {
         /// The ID of the node to insert after.
         id: ElementId,
 
-        /// The ids of the nodes to insert after the target node.
+        /// The number of nodes on the stack to insert after the target node.
         m: usize,
     },
 
@@ -185,7 +185,7 @@ pub enum Mutation<'a> {
         /// The ID of the node to insert before.
         id: ElementId,
 
-        /// The ids of the nodes to insert before the target node.
+        /// The number of nodes on the stack to insert before the target node.
         m: usize,
     },
 

+ 1 - 1
packages/desktop/Cargo.toml

@@ -21,7 +21,7 @@ serde = "1.0.136"
 serde_json = "1.0.79"
 thiserror = "1.0.30"
 log = "0.4.14"
-wry = { version = "0.23.4" }
+wry = { version = "0.27.2" }
 futures-channel = "0.3.21"
 tokio = { version = "1.16.1", features = [
     "sync",

BIN
packages/desktop/src/assets/default_icon.bin


BIN
packages/desktop/src/assets/default_icon.png


+ 13 - 2
packages/desktop/src/cfg.rs

@@ -1,3 +1,4 @@
+use std::borrow::Cow;
 use std::path::PathBuf;
 
 use wry::application::window::Icon;
@@ -18,6 +19,7 @@ pub struct Config {
     pub(crate) pre_rendered: Option<String>,
     pub(crate) disable_context_menu: bool,
     pub(crate) resource_dir: Option<PathBuf>,
+    pub(crate) data_dir: Option<PathBuf>,
     pub(crate) custom_head: Option<String>,
     pub(crate) custom_index: Option<String>,
     pub(crate) root_name: String,
@@ -27,7 +29,7 @@ type DropHandler = Box<dyn Fn(&Window, FileDropEvent) -> bool>;
 
 pub(crate) type WryProtocol = (
     String,
-    Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static>,
+    Box<dyn Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static>,
 );
 
 impl Config {
@@ -44,6 +46,7 @@ impl Config {
             pre_rendered: None,
             disable_context_menu: !cfg!(debug_assertions),
             resource_dir: None,
+            data_dir: None,
             custom_head: None,
             custom_index: None,
             root_name: "main".to_string(),
@@ -56,6 +59,14 @@ impl Config {
         self
     }
 
+    /// set the directory where data will be stored in release mode.
+    ///
+    /// > Note: This **must** be set when bundling on Windows.
+    pub fn with_data_directory(mut self, path: impl Into<PathBuf>) -> Self {
+        self.data_dir = Some(path.into());
+        self
+    }
+
     /// Set whether or not the right-click context menu should be disabled.
     pub fn with_disable_context_menu(mut self, disable: bool) -> Self {
         self.disable_context_menu = disable;
@@ -88,7 +99,7 @@ impl Config {
     /// Set a custom protocol
     pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
     where
-        F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Vec<u8>>> + 'static,
+        F: Fn(&HttpRequest<Vec<u8>>) -> WryResult<HttpResponse<Cow<'static, [u8]>>> + 'static,
     {
         self.protocols.push((name, Box::new(handler)));
         self

+ 6 - 2
packages/desktop/src/lib.rs

@@ -36,8 +36,8 @@ use tao::{
 };
 pub use wry;
 pub use wry::application as tao;
-use wry::application::window::WindowId;
 use wry::webview::WebView;
+use wry::{application::window::WindowId, webview::WebContext};
 
 /// Launch the WebView and run the event loop.
 ///
@@ -281,7 +281,7 @@ fn create_new_window(
     event_handlers: &WindowEventHandlers,
     shortcut_manager: ShortcutRegistry,
 ) -> WebviewHandler {
-    let webview = webview::build(&mut cfg, event_loop, proxy.clone());
+    let (webview, web_context) = webview::build(&mut cfg, event_loop, proxy.clone());
 
     dom.base_scope().provide_context(DesktopContext::new(
         webview.clone(),
@@ -299,6 +299,7 @@ fn create_new_window(
         webview,
         dom,
         waker: waker::tao_waker(proxy, id),
+        web_context,
     }
 }
 
@@ -306,6 +307,9 @@ struct WebviewHandler {
     dom: VirtualDom,
     webview: Rc<wry::webview::WebView>,
     waker: Waker,
+    // This is nessisary because of a bug in wry. Wry assumes the webcontext is alive for the lifetime of the webview. We need to keep the webcontext alive, otherwise the webview will crash
+    #[allow(dead_code)]
+    web_context: WebContext,
 }
 
 /// Poll the virtualdom until it's pending

+ 8 - 5
packages/desktop/src/protocol.rs

@@ -1,5 +1,8 @@
 use dioxus_interpreter_js::INTERPRETER_JS;
-use std::path::{Path, PathBuf};
+use std::{
+    borrow::Cow,
+    path::{Path, PathBuf},
+};
 use wry::{
     http::{status::StatusCode, Request, Response},
     Result,
@@ -27,7 +30,7 @@ pub(super) fn desktop_handler(
     custom_head: Option<String>,
     custom_index: Option<String>,
     root_name: &str,
-) -> Result<Response<Vec<u8>>> {
+) -> Result<Response<Cow<'static, [u8]>>> {
     // If the request is for the root, we'll serve the index.html file.
     if request.uri().path() == "/" {
         // If a custom index is provided, just defer to that, expecting the user to know what they're doing.
@@ -53,7 +56,7 @@ pub(super) fn desktop_handler(
 
         return Response::builder()
             .header("Content-Type", "text/html")
-            .body(body)
+            .body(Cow::from(body))
             .map_err(From::from);
     }
 
@@ -72,13 +75,13 @@ pub(super) fn desktop_handler(
     if asset.exists() {
         return Response::builder()
             .header("Content-Type", get_mime_from_path(&asset)?)
-            .body(std::fs::read(asset)?)
+            .body(Cow::from(std::fs::read(asset)?))
             .map_err(From::from);
     }
 
     Response::builder()
         .status(StatusCode::NOT_FOUND)
-        .body(String::from("Not Found").into_bytes())
+        .body(Cow::from(String::from("Not Found").into_bytes()))
         .map_err(From::from)
 }
 

+ 18 - 4
packages/desktop/src/webview.rs

@@ -7,13 +7,13 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
 pub use wry;
 pub use wry::application as tao;
 use wry::application::window::Window;
-use wry::webview::{WebView, WebViewBuilder};
+use wry::webview::{WebContext, WebView, WebViewBuilder};
 
 pub fn build(
     cfg: &mut Config,
     event_loop: &EventLoopWindowTarget<UserWindowEvent>,
     proxy: EventLoopProxy<UserWindowEvent>,
-) -> Rc<WebView> {
+) -> (Rc<WebView>, WebContext) {
     let builder = cfg.window.clone();
     let window = builder.build(event_loop).unwrap();
     let file_handler = cfg.file_drop_handler.take();
@@ -33,6 +33,8 @@ pub fn build(
         ));
     }
 
+    let mut web_context = WebContext::new(cfg.data_dir.clone());
+
     let mut webview = WebViewBuilder::new(window)
         .unwrap()
         .with_transparent(cfg.window.window.transparent)
@@ -52,7 +54,19 @@ pub fn build(
                 .as_ref()
                 .map(|handler| handler(window, evet))
                 .unwrap_or_default()
-        });
+        })
+        .with_web_context(&mut web_context);
+
+    #[cfg(windows)]
+    {
+        // Windows has a platform specific settings to disable the browser shortcut keys
+        use wry::webview::WebViewBuilderExtWindows;
+        webview = webview.with_browser_accelerator_keys(false);
+    }
+
+    // These are commented out because wry is currently broken in wry
+    // let mut web_context = WebContext::new(cfg.data_dir.clone());
+    // .with_web_context(&mut web_context);
 
     for (name, handler) in cfg.protocols.drain(..) {
         webview = webview.with_custom_protocol(name, handler)
@@ -78,5 +92,5 @@ pub fn build(
         webview = webview.with_devtools(true);
     }
 
-    Rc::new(webview.build().unwrap())
+    (Rc::new(webview.build().unwrap()), web_context)
 }

+ 0 - 0
packages/tui/.gitignore → packages/dioxus-tui/.gitignore


+ 0 - 0
packages/tui/.vscode/spellright.dict → packages/dioxus-tui/.vscode/spellright.dict


+ 2 - 7
packages/tui/Cargo.toml → packages/dioxus-tui/Cargo.toml

@@ -16,20 +16,15 @@ license = "MIT/Apache-2.0"
 dioxus = { path = "../dioxus", version = "^0.3.0" }
 dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
 dioxus-html = { path = "../html", version = "^0.3.0" }
-dioxus-native-core = { path = "../native-core", version = "^0.3.0" }
+dioxus-native-core = { path = "../native-core", version = "^0.2.0", features = ["dioxus"] }
 dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.3.0" }
 dioxus-hot-reload = { path = "../hot-reload", optional = true }
+rink = { path = "../rink" }
 
-tui = "0.17.0"
 crossterm = "0.23.0"
-anyhow = "1.0.42"
 tokio = { version = "1.15.0", features = ["full"] }
 futures = "0.3.19"
 taffy = "0.2.1"
-smallvec = "1.6"
-rustc-hash = "1.1.0"
-anymap = "1.0.0-beta.2"
-futures-channel = "0.3.25"
 
 [dev-dependencies]
 dioxus = { path = "../dioxus" }

+ 9 - 9
packages/tui/README.md → packages/dioxus-tui/README.md

@@ -1,5 +1,5 @@
 <div align="center">
-  <h1>Rink</h1>
+  <h1>Dioxus TUI</h1>
   <p>
     <strong>Beautiful terminal user interfaces in Rust with <a href="https://dioxuslabs.com/">Dioxus </a>.</strong>
   </p>
@@ -37,7 +37,6 @@
   </a>
 </div>
 
-
 <br/>
 
 Leverage React-like patterns, CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces with Dioxus.
@@ -63,33 +62,34 @@ fn app(cx: Scope) -> Element {
 
 You can use Html-like semantics with inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.
 
-Rink is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
+Dioxus TUI is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Dioxus TUI doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
 
 ## Limitations
 
 - **Subset of Html**
-Terminals can only render a subset of HTML. We support as much as we can.
+  Terminals can only render a subset of HTML. We support as much as we can.
 - **Particular frontend design**
-Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers.
-
+  Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers.
 
 ## Status
 
-**WARNING: Rink is currently under construction!**
+**WARNING: Dioxus TUI is currently under construction!**
 
 Rendering a VirtualDom works fine, but the ecosystem of hooks is not yet ready. Additionally, some bugs in the flexbox implementation might be quirky at times.
 
 ## Features
 
-Rink features:
+Dioxus TUI features:
+
 - [x] Flexbox-based layout system
 - [ ] CSS selectors
 - [x] inline CSS support
 - [x] Built-in focusing system
+
 * [x] Widgets<sup>1</sup>
 * [ ] Support for events, hooks, and callbacks<sup>2</sup>
 * [ ] Html tags<sup>3</sup>
 
 <sup>1</sup> Currently only a subset of the input element is implemented as a component (not an element). The `Input` component supports sliders, text, numbers, passwords, buttons, and checkboxes.
 <sup>2</sup> Basic keyboard, mouse, and focus events are implemented.
-<sup>3</sup> Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` *element* won't mean anything nor does it have any additional functionality.
+<sup>3</sup> Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` _element_ won't mean anything nor does it have any additional functionality.

+ 165 - 0
packages/dioxus-tui/benches/update.rs

@@ -0,0 +1,165 @@
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
+use dioxus::prelude::*;
+use dioxus_tui::{Config, TuiContext};
+
+criterion_group!(mbenches, tui_update);
+criterion_main!(mbenches);
+
+/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
+fn tui_update(c: &mut Criterion) {
+    {
+        let mut group = c.benchmark_group("Update boxes");
+
+        for size in 1..=20usize {
+            let parameter_string = format!("{}", (size).pow(2));
+            group.bench_with_input(
+                BenchmarkId::new("size", parameter_string),
+                &size,
+                |b, size| {
+                    b.iter(|| {
+                        dioxus_tui::launch_cfg_with_props(
+                            app,
+                            GridProps {
+                                size: *size,
+                                update_count: 1,
+                            },
+                            Config::default().with_headless(),
+                        )
+                    })
+                },
+            );
+        }
+    }
+
+    {
+        let mut group = c.benchmark_group("Update many boxes");
+
+        for update_count in 1..=20usize {
+            let update_count = update_count * 20;
+            let parameter_string = update_count.to_string();
+            group.bench_with_input(
+                BenchmarkId::new("update count", parameter_string),
+                &update_count,
+                |b, update_count| {
+                    b.iter(|| {
+                        dioxus_tui::launch_cfg_with_props(
+                            app,
+                            GridProps {
+                                size: 20,
+                                update_count: *update_count,
+                            },
+                            Config::default().with_headless(),
+                        )
+                    })
+                },
+            );
+        }
+    }
+}
+
+#[derive(Props, PartialEq)]
+struct BoxProps {
+    x: usize,
+    y: usize,
+    hue: f32,
+    alpha: f32,
+}
+#[allow(non_snake_case)]
+fn Box(cx: Scope<BoxProps>) -> Element {
+    let count = use_state(cx, || 0);
+
+    let x = cx.props.x * 2;
+    let y = cx.props.y * 2;
+    let hue = cx.props.hue;
+    let display_hue = cx.props.hue as u32 / 10;
+    let count = count.get();
+    let alpha = cx.props.alpha + (count % 100) as f32;
+
+    cx.render(rsx! {
+        div {
+            left: "{x}%",
+            top: "{y}%",
+            width: "100%",
+            height: "100%",
+            background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
+            align_items: "center",
+            p{"{display_hue:03}"}
+        }
+    })
+}
+
+#[derive(Props, PartialEq)]
+struct GridProps {
+    size: usize,
+    update_count: usize,
+}
+#[allow(non_snake_case)]
+fn Grid(cx: Scope<GridProps>) -> Element {
+    let size = cx.props.size;
+    let count = use_state(cx, || 0);
+    let counts = use_ref(cx, || vec![0; size * size]);
+
+    let ctx: TuiContext = cx.consume_context().unwrap();
+    if *count.get() + cx.props.update_count >= (size * size) {
+        ctx.quit();
+    } else {
+        for _ in 0..cx.props.update_count {
+            counts.with_mut(|c| {
+                let i = *count.current();
+                c[i] += 1;
+                c[i] %= 360;
+            });
+            count.with_mut(|i| {
+                *i += 1;
+                *i %= size * size;
+            });
+        }
+    }
+
+    render! {
+        div{
+            width: "100%",
+            height: "100%",
+            flex_direction: "column",
+            (0..size).map(|x|
+                    {
+                    rsx! {
+                        div{
+                            width: "100%",
+                            height: "100%",
+                            flex_direction: "row",
+                            (0..size).map(|y|
+                                {
+                                    let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
+                                    let key = format!("{}-{}", x, y);
+                                    rsx! {
+                                        Box{
+                                            x: x,
+                                            y: y,
+                                            alpha: 100.0,
+                                            hue: alpha,
+                                            key: "{key}",
+                                        }
+                                    }
+                                }
+                            )
+                        }
+                    }
+                }
+            )
+        }
+    }
+}
+
+fn app(cx: Scope<GridProps>) -> Element {
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            Grid{
+                size: cx.props.size,
+                update_count: cx.props.update_count,
+            }
+        }
+    })
+}

+ 0 - 0
packages/tui/examples/all_events.rs → packages/dioxus-tui/examples/all_events.rs


+ 0 - 0
packages/tui/examples/border.rs → packages/dioxus-tui/examples/border.rs


+ 0 - 0
packages/tui/examples/buttons.rs → packages/dioxus-tui/examples/buttons.rs


+ 0 - 0
packages/tui/examples/color_test.rs → packages/dioxus-tui/examples/color_test.rs


+ 4 - 2
packages/tui/examples/colorpicker.rs → packages/dioxus-tui/examples/colorpicker.rs

@@ -1,6 +1,7 @@
 use dioxus::core::RenderReturn;
 use dioxus::prelude::*;
-use dioxus_tui::query::Query;
+use dioxus_tui::DioxusElementToNodeId;
+use dioxus_tui::Query;
 use dioxus_tui::Size;
 
 fn main() {
@@ -11,6 +12,7 @@ fn app(cx: Scope) -> Element {
     let hue = use_state(cx, || 0.0);
     let brightness = use_state(cx, || 0.0);
     let tui_query: Query = cx.consume_context().unwrap();
+    let mapping: DioxusElementToNodeId = cx.consume_context().unwrap();
     // disable templates so that every node has an id and can be queried
     cx.render(rsx! {
         div{
@@ -19,7 +21,7 @@ fn app(cx: Scope) -> Element {
             onmousemove: move |evt| {
                 if let RenderReturn::Ready(node) = cx.root_node() {
                     if let Some(id) = node.root_ids.get(0){
-                        let node = tui_query.get(id);
+                        let node = tui_query.get(mapping.get_node_id(id).unwrap());
                         let Size{width, height} = node.size().unwrap();
                         let pos = evt.inner().element_coordinates();
                         hue.set((pos.x as f32/width as f32)*255.0);

+ 0 - 0
packages/tui/examples/components.rs → packages/dioxus-tui/examples/components.rs


+ 0 - 0
packages/tui/examples/example.png → packages/dioxus-tui/examples/example.png


+ 0 - 0
packages/tui/examples/flex.rs → packages/dioxus-tui/examples/flex.rs


+ 0 - 0
packages/tui/examples/hover.rs → packages/dioxus-tui/examples/hover.rs


+ 0 - 0
packages/tui/examples/list.rs → packages/dioxus-tui/examples/list.rs


+ 0 - 0
packages/tui/examples/margin.rs → packages/dioxus-tui/examples/margin.rs


+ 0 - 0
packages/tui/examples/quadrants.rs → packages/dioxus-tui/examples/quadrants.rs


+ 0 - 0
packages/tui/examples/readme.rs → packages/dioxus-tui/examples/readme.rs


+ 5 - 19
packages/tui/benches/update.rs → packages/dioxus-tui/examples/stress.rs

@@ -1,26 +1,12 @@
-use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
 use dioxus::prelude::*;
 use dioxus_tui::{Config, TuiContext};
 
-criterion_group!(mbenches, tui_update);
-criterion_main!(mbenches);
-
 /// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
-fn tui_update(c: &mut Criterion) {
-    let mut group = c.benchmark_group("Update boxes");
-
-    // We can also use loops to define multiple benchmarks, even over multiple dimensions.
-    for size in 1..=8usize {
-        let parameter_string = format!("{}", (3 * size).pow(2));
-        group.bench_with_input(
-            BenchmarkId::new("size", parameter_string),
-            &size,
-            |b, size| {
-                b.iter(|| {
-                    dioxus_tui::launch_cfg_with_props(app, *size, Config::default().with_headless())
-                })
-            },
-        );
+fn main() {
+    for size in 1..=20usize {
+        for _ in 0..10 {
+            dioxus_tui::launch_cfg_with_props(app, size, Config::default().with_headless())
+        }
     }
 }
 

+ 0 - 0
packages/tui/examples/task.rs → packages/dioxus-tui/examples/task.rs


+ 0 - 0
packages/tui/examples/text.rs → packages/dioxus-tui/examples/text.rs


+ 0 - 1
packages/tui/examples/widgets.rs → packages/dioxus-tui/examples/widgets.rs

@@ -1,7 +1,6 @@
 use dioxus::prelude::*;
 use dioxus_html::FormData;
 use dioxus_tui::prelude::*;
-use dioxus_tui::Config;
 
 fn main() {
     dioxus_tui::launch_cfg(app, Config::new());

+ 143 - 0
packages/dioxus-tui/src/lib.rs

@@ -0,0 +1,143 @@
+pub mod prelude;
+pub mod widgets;
+
+use std::{
+    ops::Deref,
+    rc::Rc,
+    sync::{Arc, RwLock},
+};
+
+use dioxus_core::{Component, ElementId, VirtualDom};
+use dioxus_html::EventData;
+use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt};
+use dioxus_native_core::prelude::*;
+
+pub use rink::{query::Query, Config, RenderingMode, Size, TuiContext};
+use rink::{render, Driver};
+
+pub fn launch(app: Component<()>) {
+    launch_cfg(app, Config::default())
+}
+
+pub fn launch_cfg(app: Component<()>, cfg: Config) {
+    launch_cfg_with_props(app, (), cfg);
+}
+
+pub fn launch_cfg_with_props<Props: 'static>(app: Component<Props>, props: Props, cfg: Config) {
+    render(cfg, |rdom, taffy, event_tx| {
+        let dioxus_state = {
+            let mut rdom = rdom.write().unwrap();
+            DioxusState::create(&mut rdom)
+        };
+        let dioxus_state = Rc::new(RwLock::new(dioxus_state));
+        let mut vdom = VirtualDom::new_with_props(app, props)
+            .with_root_context(TuiContext::new(event_tx))
+            .with_root_context(Query::new(rdom.clone(), taffy.clone()))
+            .with_root_context(DioxusElementToNodeId {
+                mapping: dioxus_state.clone(),
+            });
+        let muts = vdom.rebuild();
+        let mut rdom = rdom.write().unwrap();
+        dioxus_state
+            .write()
+            .unwrap()
+            .apply_mutations(&mut rdom, muts);
+        DioxusRenderer {
+            vdom,
+            dioxus_state,
+            #[cfg(all(feature = "hot-reload", debug_assertions))]
+            hot_reload_rx: {
+                let (hot_reload_tx, hot_reload_rx) =
+                    tokio::sync::mpsc::unbounded_channel::<dioxus_hot_reload::HotReloadMsg>();
+                dioxus_hot_reload::connect(move |msg| {
+                    let _ = hot_reload_tx.send(msg);
+                });
+                hot_reload_rx
+            },
+        }
+    })
+    .unwrap();
+}
+
+struct DioxusRenderer {
+    vdom: VirtualDom,
+    dioxus_state: Rc<RwLock<DioxusState>>,
+    #[cfg(all(feature = "hot-reload", debug_assertions))]
+    hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver<dioxus_hot_reload::HotReloadMsg>,
+}
+
+impl Driver for DioxusRenderer {
+    fn update(&mut self, rdom: &Arc<RwLock<RealDom>>) {
+        let muts = self.vdom.render_immediate();
+        {
+            let mut rdom = rdom.write().unwrap();
+            self.dioxus_state
+                .write()
+                .unwrap()
+                .apply_mutations(&mut rdom, muts);
+        }
+    }
+
+    fn handle_event(
+        &mut self,
+        rdom: &Arc<RwLock<RealDom>>,
+        id: NodeId,
+        event: &str,
+        value: Rc<EventData>,
+        bubbles: bool,
+    ) {
+        let id = { rdom.read().unwrap().get(id).unwrap().mounted_id() };
+        if let Some(id) = id {
+            self.vdom
+                .handle_event(event, value.deref().clone().into_any(), id, bubbles);
+        }
+    }
+
+    fn poll_async(&mut self) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + '_>> {
+        #[cfg(all(feature = "hot-reload", debug_assertions))]
+        return Box::pin(async {
+            let hot_reload_wait = self.hot_reload_rx.recv();
+            let mut hot_reload_msg = None;
+            let wait_for_work = self.vdom.wait_for_work();
+            tokio::select! {
+                Some(msg) = hot_reload_wait => {
+                    #[cfg(all(feature = "hot-reload", debug_assertions))]
+                    {
+                        hot_reload_msg = Some(msg);
+                    }
+                    #[cfg(not(all(feature = "hot-reload", debug_assertions)))]
+                    let () = msg;
+                }
+                _ = wait_for_work => {}
+            }
+            // if we have a new template, replace the old one
+            if let Some(msg) = hot_reload_msg {
+                match msg {
+                    dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
+                        self.vdom.replace_template(template);
+                    }
+                    dioxus_hot_reload::HotReloadMsg::Shutdown => {
+                        std::process::exit(0);
+                    }
+                }
+            }
+        });
+
+        #[cfg(not(all(feature = "hot-reload", debug_assertions)))]
+        Box::pin(self.vdom.wait_for_work())
+    }
+}
+
+#[derive(Clone)]
+pub struct DioxusElementToNodeId {
+    mapping: Rc<RwLock<DioxusState>>,
+}
+
+impl DioxusElementToNodeId {
+    pub fn get_node_id(&self, element_id: ElementId) -> Option<NodeId> {
+        self.mapping
+            .read()
+            .unwrap()
+            .try_element_to_node_id(element_id)
+    }
+}

+ 1 - 0
packages/tui/src/prelude/mod.rs → packages/dioxus-tui/src/prelude/mod.rs

@@ -1 +1,2 @@
 pub use crate::widgets::*;
+pub use rink::Config;

+ 2 - 2
packages/tui/src/widgets/button.rs → packages/dioxus-tui/src/widgets/button.rs

@@ -37,7 +37,7 @@ pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
         }
         state.set(new_state);
     };
-    cx.render(rsx! {
+    render! {
         div{
             width: "{width}",
             height: "{height}",
@@ -55,5 +55,5 @@ pub(crate) fn Button<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
             },
             "{text}"
         }
-    })
+    }
 }

+ 2 - 2
packages/tui/src/widgets/checkbox.rs → packages/dioxus-tui/src/widgets/checkbox.rs

@@ -61,7 +61,7 @@ pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> {
         }
         state.set(new_state);
     };
-    cx.render(rsx! {
+    render! {
         div {
             width: "{width}",
             height: "{height}",
@@ -78,5 +78,5 @@ pub(crate) fn CheckBox<'a>(cx: Scope<'a, CheckBoxProps>) -> Element<'a> {
             },
             "{text}"
         }
-    })
+    }
 }

+ 0 - 0
packages/tui/src/widgets/input.rs → packages/dioxus-tui/src/widgets/input.rs


+ 22 - 0
packages/dioxus-tui/src/widgets/mod.rs

@@ -0,0 +1,22 @@
+mod button;
+mod checkbox;
+mod input;
+mod number;
+mod password;
+mod slider;
+mod textbox;
+
+use dioxus_core::{RenderReturn, Scope};
+use dioxus_native_core::NodeId;
+pub use input::*;
+
+use crate::DioxusElementToNodeId;
+
+pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<NodeId> {
+    if let RenderReturn::Ready(sync) = cx.root_node() {
+        let mapping: DioxusElementToNodeId = cx.consume_context()?;
+        mapping.get_node_id(sync.root_ids.get(0)?)
+    } else {
+        None
+    }
+}

+ 4 - 4
packages/tui/src/widgets/number.rs → packages/dioxus-tui/src/widgets/number.rs

@@ -1,11 +1,11 @@
 use crate::widgets::get_root_id;
-use crate::Query;
 use crossterm::{cursor::MoveTo, execute};
 use dioxus::prelude::*;
 use dioxus_elements::input_data::keyboard_types::Key;
 use dioxus_html as dioxus_elements;
 use dioxus_html::FormData;
 use dioxus_native_core::utils::cursor::{Cursor, Pos};
+use rink::Query;
 use std::{collections::HashMap, io::stdout};
 use taffy::geometry::Point;
 
@@ -99,7 +99,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
         update(text.clone());
     };
 
-    cx.render(rsx! {
+    render! {
         div{
             width: "{width}",
             height: "{height}",
@@ -113,7 +113,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
                 };
                 if is_text{
                     let mut text = text_ref.write();
-                    cursor.write().handle_input(&k, &mut *text, max_len);
+                    cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
                     update(text.clone());
 
                     let node = tui_query.get(get_root_id(cx).unwrap());
@@ -205,5 +205,5 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
 
             "{text_after_second_cursor}"
         }
-    })
+    }
 }

+ 6 - 4
packages/tui/src/widgets/password.rs → packages/dioxus-tui/src/widgets/password.rs

@@ -1,11 +1,11 @@
 use crate::widgets::get_root_id;
-use crate::Query;
 use crossterm::{cursor::*, execute};
 use dioxus::prelude::*;
 use dioxus_elements::input_data::keyboard_types::Key;
 use dioxus_html as dioxus_elements;
 use dioxus_html::FormData;
 use dioxus_native_core::utils::cursor::{Cursor, Pos};
+use rink::Query;
 use std::{collections::HashMap, io::stdout};
 use taffy::geometry::Point;
 
@@ -88,7 +88,9 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
             return;
         }
         let mut text = text_ref.write();
-        cursor.write().handle_input(&k, &mut *text, max_len);
+        cursor
+            .write()
+            .handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
         if let Some(input_handler) = &cx.props.raw_oninput {
             input_handler.call(FormData {
                 value: text.clone(),
@@ -114,7 +116,7 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
         }
     };
 
-    cx.render(rsx! {
+    render! {
         div {
             width: "{width}",
             height: "{height}",
@@ -187,5 +189,5 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
 
             "{text_after_second_cursor}"
         }
-    })
+    }
 }

+ 3 - 3
packages/tui/src/widgets/slider.rs → packages/dioxus-tui/src/widgets/slider.rs

@@ -1,11 +1,11 @@
 use std::collections::HashMap;
 
 use crate::widgets::get_root_id;
-use crate::Query;
 use dioxus::prelude::*;
 use dioxus_elements::input_data::keyboard_types::Key;
 use dioxus_html as dioxus_elements;
 use dioxus_html::FormData;
+use rink::Query;
 
 #[derive(Props)]
 pub(crate) struct SliderProps<'a> {
@@ -62,7 +62,7 @@ pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> {
         }
     };
 
-    cx.render(rsx! {
+    render! {
         div{
             width: "{width}",
             height: "{height}",
@@ -103,5 +103,5 @@ pub(crate) fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> {
                 background_color: "rgba(10,10,10,0.5)",
             }
         }
-    })
+    }
 }

+ 4 - 4
packages/tui/src/widgets/textbox.rs → packages/dioxus-tui/src/widgets/textbox.rs

@@ -1,11 +1,11 @@
 use crate::widgets::get_root_id;
-use crate::Query;
 use crossterm::{cursor::*, execute};
 use dioxus::prelude::*;
 use dioxus_elements::input_data::keyboard_types::Key;
 use dioxus_html as dioxus_elements;
 use dioxus_html::FormData;
 use dioxus_native_core::utils::cursor::{Cursor, Pos};
+use rink::Query;
 use std::{collections::HashMap, io::stdout};
 use taffy::geometry::Point;
 
@@ -79,7 +79,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
         "solid"
     };
 
-    cx.render(rsx! {
+    render! {
         div{
             width: "{width}",
             height: "{height}",
@@ -90,7 +90,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
                     return;
                 }
                 let mut text = text_ref.write();
-                cursor.write().handle_input(&k, &mut *text, max_len);
+                cursor.write().handle_input(&k.code(), &k.key(), &k.modifiers(), &mut *text, max_len);
                 if let Some(input_handler) = &cx.props.raw_oninput{
                     input_handler.call(FormData{
                         value: text.clone(),
@@ -178,5 +178,5 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
 
             "{text_after_second_cursor}"
         }
-    })
+    }
 }

+ 0 - 0
packages/tui/test.html → packages/dioxus-tui/test.html


+ 0 - 0
packages/tui/tests/events.rs → packages/dioxus-tui/tests/events.rs


+ 20 - 0
packages/fermi/README.md

@@ -63,6 +63,26 @@ fn NameCard(cx: Scope) -> Element {
 }
 ```
 
+If needed, we can update the atom's value, based on itself:
+
+```rust, ignore
+static COUNT: Atom<i32> = |_| 0;
+
+fn Counter(cx: Scope) -> Element {
+    let mut count = use_atom_state(cx, COUNT);
+
+    cx.render(rsx!{
+        p {
+          "{count}"
+        }
+        button {
+            onclick: move |_| count += 1,
+            "Increment counter"
+        }
+    })
+}
+```
+
 It's that simple!
 
 ## Installation

+ 33 - 6
packages/html/src/events/keyboard.rs

@@ -5,6 +5,16 @@ use std::convert::TryInto;
 use std::fmt::{Debug, Formatter};
 use std::str::FromStr;
 
+#[cfg(feature = "serialize")]
+fn resilient_deserialize_code<'de, D>(deserializer: D) -> Result<Code, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    use serde::Deserialize;
+    // If we fail to deserialize the code for any reason, just return Unidentified instead of failing.
+    Ok(Code::deserialize(deserializer).unwrap_or(Code::Unidentified))
+}
+
 pub type KeyboardEvent = Event<KeyboardData>;
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Clone, PartialEq, Eq)]
@@ -27,6 +37,10 @@ pub struct KeyboardData {
     pub key_code: KeyCode,
 
     /// the physical key on the keyboard
+    #[cfg_attr(
+        feature = "serialize",
+        serde(deserialize_with = "resilient_deserialize_code")
+    )]
     code: Code,
 
     /// Indicate if the `alt` modifier key was pressed during this keyboard event
@@ -102,7 +116,7 @@ impl KeyboardData {
     /// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout.
     pub fn key(&self) -> Key {
         #[allow(deprecated)]
-        FromStr::from_str(&self.key).expect("could not parse")
+        FromStr::from_str(&self.key).unwrap_or(Key::Unidentified)
     }
 
     /// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys.
@@ -158,10 +172,24 @@ impl Debug for KeyboardData {
     }
 }
 
-#[cfg_attr(
-    feature = "serialize",
-    derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
-)]
+#[cfg(feature = "serialize")]
+impl<'de> serde::Deserialize<'de> for KeyCode {
+    fn deserialize<D>(deserializer: D) -> Result<KeyCode, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        // We could be deserializing a unicode character, so we need to use u64 even if the output only takes u8
+        let value = u64::deserialize(deserializer)?;
+
+        if let Ok(smaller_uint) = value.try_into() {
+            Ok(KeyCode::from_raw_code(smaller_uint))
+        } else {
+            Ok(KeyCode::Unknown)
+        }
+    }
+}
+
+#[cfg_attr(feature = "serialize", derive(serde_repr::Serialize_repr))]
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 #[repr(u8)]
 pub enum KeyCode {
@@ -525,7 +553,6 @@ pub enum KeyCode {
     // kanji, = 244
     // unlock trackpad (Chrome/Edge), = 251
     // toggle touchpad, = 255
-    #[cfg_attr(feature = "serialize", serde(other))]
     Unknown,
 }
 

+ 2 - 3
packages/interpreter/Cargo.toml

@@ -10,15 +10,14 @@ homepage = "https://dioxuslabs.com"
 documentation = "https://docs.rs/dioxus"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
 
-
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
 wasm-bindgen = { version = "0.2.79", optional = true }
 js-sys = { version = "0.3.56", optional = true }
 web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
-sledgehammer_bindgen = { version = "0.1.3", optional = true }
-sledgehammer_utils = { version = "0.1.0", optional = true }
+sledgehammer_bindgen = { version = "0.2.1", optional = true }
+sledgehammer_utils = { version = "0.1.1", optional = true }
 
 [features]
 default = []

+ 1 - 0
packages/interpreter/src/sledgehammer_bindings.rs

@@ -108,6 +108,7 @@ mod js {
     const listeners = new ListenerMap();
     let nodes = [];
     let stack = [];
+    let root;
     const templates = {};
     let node, els, end, ptr_end, k;
     export function save_template(nodes, tmpl_id) {

+ 348 - 525
packages/native-core-macro/src/lib.rs

@@ -1,563 +1,386 @@
 extern crate proc_macro;
 
-mod sorted_slice;
+use std::collections::HashSet;
 
 use proc_macro::TokenStream;
-use quote::{quote, ToTokens, __private::Span};
-use sorted_slice::StrSlice;
-use syn::parenthesized;
-use syn::parse::ParseBuffer;
-use syn::punctuated::Punctuated;
-use syn::{
-    self,
-    parse::{Parse, ParseStream, Result},
-    parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
-};
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, ItemImpl, Type, TypePath, TypeTuple};
 
-/// Sorts a slice of string literals at compile time.
-#[proc_macro]
-pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
-    let slice: StrSlice = parse_macro_input!(input as StrSlice);
-    let strings = slice.map.values();
-    quote!([#(#strings, )*]).into()
-}
-
-#[derive(PartialEq, Debug, Clone)]
-enum DependencyKind {
-    Node,
-    Child,
-    Parent,
-}
+/// A helper attribute for deriving `State` for a struct.
+#[proc_macro_attribute]
+pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream {
+    let impl_block: syn::ItemImpl = parse_macro_input!(input as syn::ItemImpl);
 
-/// Derive's the state from any elements that have a node_dep_state, child_dep_state, parent_dep_state, or state attribute.
-///
-/// # Declaring elements
-/// Each of the attributes require specifying the members of the struct it depends on to allow the macro to find the optimal resultion order.
-/// These dependencies should match the types declared in the trait the member implements.
-///
-/// # The node_dep_state attribute
-/// The node_dep_state attribute declares a member that implements the NodeDepState trait.
-/// ```rust, ignore
-/// #[derive(State)]
-/// struct MyStruct {
-///     // MyDependency implements ChildDepState<()>
-///     #[node_dep_state()]
-///     my_dependency_1: MyDependency,
-///     // MyDependency2 implements ChildDepState<(MyDependency,)>
-///     #[node_dep_state(my_dependency_1)]
-///     my_dependency_2: MyDependency2,
-/// }
-/// // or
-/// #[derive(State)]
-/// struct MyStruct {
-///     // MyDependnancy implements NodeDepState<()>
-///     #[node_dep_state()]
-///     my_dependency_1: MyDependency,
-///     // MyDependency2 implements NodeDepState<()>
-///     #[node_dep_state()]
-///     my_dependency_2: MyDependency2,
-///     // MyDependency3 implements NodeDepState<(MyDependency, MyDependency2)> with Ctx = f32
-///     #[node_dep_state((my_dependency_1, my_dependency_2), f32)]
-///     my_dependency_3: MyDependency2,
-/// }
-/// ```
-/// # The child_dep_state attribute
-/// The child_dep_state attribute declares a member that implements the ChildDepState trait.
-/// ```rust, ignore
-/// #[derive(State)]
-/// struct MyStruct {
-///     // MyDependnacy implements ChildDepState with DepState = Self
-///     #[child_dep_state(my_dependency_1)]
-///     my_dependency_1: MyDependency,
-/// }
-/// // or
-/// #[derive(State)]
-/// struct MyStruct {
-///     // MyDependnacy implements ChildDepState with DepState = Self
-///     #[child_dep_state(my_dependency_1)]
-///     my_dependency_1: MyDependency,
-///     // MyDependnacy2 implements ChildDepState with DepState = MyDependency and Ctx = f32
-///     #[child_dep_state(my_dependency_1, f32)]
-///     my_dependency_2: MyDependency2,
-/// }
-/// ```
-/// # The parent_dep_state attribute
-/// The parent_dep_state attribute declares a member that implements the ParentDepState trait.
-/// The parent_dep_state attribute can be called in the forms:
-/// ```rust, ignore
-/// #[derive(State)]
-/// struct MyStruct {
-///     // MyDependnacy implements ParentDepState with DepState = Self
-///     #[parent_dep_state(my_dependency_1)]
-///     my_dependency_1: MyDependency,
-/// }
-/// // or
-/// #[derive(State)]
-/// struct MyStruct {
-///     // MyDependnacy implements ParentDepState with DepState = Self
-///     #[parent_dep_state(my_dependency_1)]
-///     my_dependency_1: MyDependency,
-///     // MyDependnacy2 implements ParentDepState with DepState = MyDependency and Ctx = f32
-///     #[parent_dep_state(my_dependency_1, f32)]
-///     my_dependency_2: MyDependency2,
-/// }
-/// ```
-///
-/// # Combining dependancies
-/// The node_dep_state, parent_dep_state, and child_dep_state attributes can be combined to allow for more complex dependancies.
-/// For example if we wanted to combine the font that is passed from the parent to the child and the layout of the size children to find the size of the current node we could do:
-/// ```rust, ignore
-/// #[derive(State)]
-/// struct MyStruct {
-///     // ChildrenSize implements ChildDepState with DepState = Size
-///     #[child_dep_state(size)]
-///     children_size: ChildrenSize,
-///     // FontSize implements ParentDepState with DepState = Self
-///     #[parent_dep_state(font_size)]
-///     font_size: FontSize,
-///     // Size implements NodeDepState<(ChildrenSize, FontSize)>
-///     #[parent_dep_state((children_size, font_size))]
-///     size: Size,
-/// }
-/// ```
-///
-/// # The state attribute
-/// The state macro declares a member that implements the State trait. This allows you to organize your state into multiple isolated components.
-/// Unlike the other attributes, the state attribute does not accept any arguments, because a nested state cannot depend on any other part of the state.
-///
-/// # Custom values
-///
-/// If your state has a custom value type you can specify it with the state attribute.
-///
-/// ```rust, ignore
-/// #[derive(State)]
-/// #[state(custom_value = MyCustomType)]
-/// struct MyStruct {
-///     // ...
-/// }
-#[proc_macro_derive(
-    State,
-    attributes(node_dep_state, child_dep_state, parent_dep_state, state)
-)]
-pub fn state_macro_derive(input: TokenStream) -> TokenStream {
-    let ast = syn::parse(input).unwrap();
-    impl_derive_macro(&ast)
-}
+    let has_create_fn = impl_block
+        .items
+        .iter()
+        .any(|item| matches!(item, syn::ImplItem::Method(method) if method.sig.ident == "create"));
 
-fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
-    let custom_type = ast
-        .attrs
+    let parent_dependencies = impl_block
+        .items
         .iter()
-        .find(|a| a.path.is_ident("state"))
-        .and_then(|attr| {
-            // parse custom_type = "MyType"
-            let assignment = attr.parse_args::<syn::Expr>().unwrap();
-            if let syn::Expr::Assign(assign) = assignment {
-                let (left, right) = (&*assign.left, &*assign.right);
-                if let syn::Expr::Path(e) = left {
-                    let path = &e.path;
-                    if let Some(ident) = path.get_ident() {
-                        if ident == "custom_value" {
-                            return match right {
-                                syn::Expr::Path(e) => {
-                                    let path = &e.path;
-                                    Some(quote! {#path})
-                                }
-                                _ => None,
-                            };
-                        }
-                    }
-                }
+        .find_map(|item| {
+            if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item {
+                (ident == "ParentDependencies").then_some(ty)
+            } else {
+                None
             }
-            None
         })
-        .unwrap_or(quote! {()});
-    let type_name = &ast.ident;
-    let fields: Vec<_> = match &ast.data {
-        syn::Data::Struct(data) => match &data.fields {
-            syn::Fields::Named(e) => &e.named,
-            syn::Fields::Unnamed(_) => todo!("unnamed fields"),
-            syn::Fields::Unit => todo!("unit structs"),
-        }
+        .expect("ParentDependencies must be defined");
+    let child_dependencies = impl_block
+        .items
+        .iter()
+        .find_map(|item| {
+            if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item {
+                (ident == "ChildDependencies").then_some(ty)
+            } else {
+                None
+            }
+        })
+        .expect("ChildDependencies must be defined");
+    let node_dependencies = impl_block
+        .items
         .iter()
-        .collect(),
-        _ => unimplemented!(),
+        .find_map(|item| {
+            if let syn::ImplItem::Type(syn::ImplItemType { ident, ty, .. }) = item {
+                (ident == "NodeDependencies").then_some(ty)
+            } else {
+                None
+            }
+        })
+        .expect("NodeDependencies must be defined");
+
+    let this_type = &impl_block.self_ty;
+    let this_type = extract_type_path(this_type)
+        .unwrap_or_else(|| panic!("Self must be a type path, found {}", quote!(#this_type)));
+
+    let mut combined_dependencies = HashSet::new();
+
+    let self_path: TypePath = syn::parse_quote!(Self);
+
+    let parent_dependencies = match extract_tuple(parent_dependencies) {
+        Some(tuple) => {
+            let mut parent_dependencies = Vec::new();
+            for type_ in &tuple.elems {
+                let mut type_ = extract_type_path(type_).unwrap_or_else(|| {
+                    panic!(
+                        "ParentDependencies must be a tuple of type paths, found {}",
+                        quote!(#type_)
+                    )
+                });
+                if type_ == self_path {
+                    type_ = this_type.clone();
+                }
+                combined_dependencies.insert(type_.clone());
+                parent_dependencies.push(type_);
+            }
+            parent_dependencies
+        }
+        _ => panic!(
+            "ParentDependencies must be a tuple, found {}",
+            quote!(#parent_dependencies)
+        ),
     };
-    let strct = Struct::new(type_name.clone(), &fields);
-    match StateStruct::parse(&fields, &strct) {
-        Ok(state_strct) => {
-            let passes = state_strct.state_members.iter().map(|m| {
-                let unit = &m.mem.unit_type;
-                match m.dep_kind {
-                    DependencyKind::Node => quote! {dioxus_native_core::AnyPass::Node(&#unit)},
-                    DependencyKind::Child => quote! {dioxus_native_core::AnyPass::Upward(&#unit)},
-                    DependencyKind::Parent => {
-                        quote! {dioxus_native_core::AnyPass::Downward(&#unit)}
-                    }
+    let child_dependencies = match extract_tuple(child_dependencies) {
+        Some(tuple) => {
+            let mut child_dependencies = Vec::new();
+            for type_ in &tuple.elems {
+                let mut type_ = extract_type_path(type_).unwrap_or_else(|| {
+                    panic!(
+                        "ChildDependencies must be a tuple of type paths, found {}",
+                        quote!(#type_)
+                    )
+                });
+                if type_ == self_path {
+                    type_ = this_type.clone();
                 }
-            });
-            let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty);
-            let impl_members = state_strct
-                .state_members
-                .iter()
-                .map(|m| m.impl_pass(state_strct.ty, &custom_type));
-
-            let gen = quote! {
-                #(#impl_members)*
-                impl State<#custom_type> for #type_name {
-                    const PASSES: &'static [dioxus_native_core::AnyPass<dioxus_native_core::node::Node<Self, #custom_type>>] = &[
-                        #(#passes),*
-                    ];
-                    const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*];
+                combined_dependencies.insert(type_.clone());
+                child_dependencies.push(type_);
+            }
+            child_dependencies
+        }
+        _ => panic!(
+            "ChildDependencies must be a tuple, found {}",
+            quote!(#child_dependencies)
+        ),
+    };
+    let node_dependencies = match extract_tuple(node_dependencies) {
+        Some(tuple) => {
+            let mut node_dependencies = Vec::new();
+            for type_ in &tuple.elems {
+                let mut type_ = extract_type_path(type_).unwrap_or_else(|| {
+                    panic!(
+                        "NodeDependencies must be a tuple of type paths, found {}",
+                        quote!(#type_)
+                    )
+                });
+                if type_ == self_path {
+                    type_ = this_type.clone();
                 }
-            };
-            gen.into()
+                combined_dependencies.insert(type_.clone());
+                node_dependencies.push(type_);
+            }
+            node_dependencies
         }
-        Err(e) => e.into_compile_error().into(),
-    }
-}
+        _ => panic!(
+            "NodeDependencies must be a tuple, found {}",
+            quote!(#node_dependencies)
+        ),
+    };
+    combined_dependencies.insert(this_type.clone());
 
-struct Struct {
-    name: Ident,
-    members: Vec<Member>,
-}
+    let combined_dependencies: Vec<_> = combined_dependencies.into_iter().collect();
+    let parent_dependancies_idxes: Vec<_> = parent_dependencies
+        .iter()
+        .filter_map(|ident| combined_dependencies.iter().position(|i| i == ident))
+        .collect();
+    let child_dependencies_idxes: Vec<_> = child_dependencies
+        .iter()
+        .filter_map(|ident| combined_dependencies.iter().position(|i| i == ident))
+        .collect();
+    let node_dependencies_idxes: Vec<_> = node_dependencies
+        .iter()
+        .filter_map(|ident| combined_dependencies.iter().position(|i| i == ident))
+        .collect();
+    let this_type_idx = combined_dependencies
+        .iter()
+        .enumerate()
+        .find_map(|(i, ident)| (this_type == *ident).then_some(i))
+        .unwrap();
+    let this_view = format_ident!("__data{}", this_type_idx);
 
-impl Struct {
-    fn new(name: Ident, fields: &[&Field]) -> Self {
-        let members = fields
-            .iter()
-            .enumerate()
-            .filter_map(|(i, f)| Member::parse(&name, f, i as u64))
-            .collect();
-        Self { name, members }
-    }
-}
+    let combined_dependencies_quote = combined_dependencies.iter().map(|ident| {
+        if ident == &this_type {
+            quote! {shipyard::ViewMut<#ident>}
+        } else {
+            quote! {shipyard::View<#ident>}
+        }
+    });
+    let combined_dependencies_quote = quote!((#(#combined_dependencies_quote,)*));
 
-struct StateStruct<'a> {
-    state_members: Vec<StateMember<'a>>,
-    #[allow(unused)]
-    child_states: Vec<&'a Member>,
-    ty: &'a Ident,
-}
+    let ItemImpl {
+        attrs,
+        defaultness,
+        unsafety,
+        impl_token,
+        generics,
+        trait_,
+        self_ty,
+        items,
+        ..
+    } = impl_block;
+    let for_ = trait_.as_ref().map(|t| t.2);
+    let trait_ = trait_.map(|t| t.1);
 
-impl<'a> StateStruct<'a> {
-    /// Parse the state structure, and find a resolution order that will allow us to update the state for each node in after the state(s) it depends on have been resolved.
-    fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
-        let mut parse_err = Ok(());
-        let mut state_members: Vec<_> = strct
-            .members
-            .iter()
-            .zip(fields.iter())
-            .filter_map(|(m, f)| match StateMember::parse(f, m, strct) {
-                Ok(m) => m,
-                Err(err) => {
-                    parse_err = Err(err);
-                    None
-                }
-            })
-            .collect();
-        parse_err?;
-        for i in 0..state_members.len() {
-            let deps: Vec<_> = state_members[i].dep_mems.iter().map(|m| m.id).collect();
-            for dep in deps {
-                state_members[dep as usize].dependant_mems.push(i as u64);
+    let split_views: Vec<_> = (0..combined_dependencies.len())
+        .map(|i| {
+            let ident = format_ident!("__data{}", i);
+            if i == this_type_idx {
+                quote! {mut #ident}
+            } else {
+                quote! {#ident}
+            }
+        })
+        .collect();
+
+    let node_view = node_dependencies_idxes
+        .iter()
+        .map(|i| format_ident!("__data{}", i))
+        .collect::<Vec<_>>();
+    let get_node_view = {
+        if node_dependencies.is_empty() {
+            quote! {
+                let raw_node = ();
+            }
+        } else {
+            let temps = (0..node_dependencies.len())
+                .map(|i| format_ident!("__temp{}", i))
+                .collect::<Vec<_>>();
+            quote! {
+                let raw_node: (#(*const #node_dependencies,)*) = {
+                    let (#(#temps,)*) = (#(&#node_view,)*).get(id).unwrap_or_else(|err| panic!("Failed to get node view {:?}", err));
+                    (#(#temps as *const _,)*)
+                };
+            }
+        }
+    };
+    let deref_node_view = {
+        if node_dependencies.is_empty() {
+            quote! {
+                let node = raw_node;
+            }
+        } else {
+            let indexes = (0..node_dependencies.len()).map(syn::Index::from);
+            quote! {
+                let node = unsafe { (#(dioxus_native_core::prelude::DependancyView::new(&*raw_node.#indexes),)*) };
             }
         }
+    };
 
-        let child_states = strct
-            .members
-            .iter()
-            .zip(fields.iter())
-            .filter(|(_, f)| {
-                f.attrs.iter().any(|a| {
-                    a.path
-                        .get_ident()
-                        .filter(|i| i.to_string().as_str() == "state")
-                        .is_some()
-                })
-            })
-            .map(|(m, _)| m);
+    let parent_view = parent_dependancies_idxes
+        .iter()
+        .map(|i| format_ident!("__data{}", i))
+        .collect::<Vec<_>>();
+    let get_parent_view = {
+        if parent_dependencies.is_empty() {
+            quote! {
+                let raw_parent = tree.parent_id(id).map(|_| ());
+            }
+        } else {
+            let temps = (0..parent_dependencies.len())
+                .map(|i| format_ident!("__temp{}", i))
+                .collect::<Vec<_>>();
+            quote! {
+                let raw_parent = tree.parent_id(id).and_then(|parent_id| {
+                    let raw_parent: Option<(#(*const #parent_dependencies,)*)> = (#(&#parent_view,)*).get(parent_id).ok().map(|c| {
+                        let (#(#temps,)*) = c;
+                        (#(#temps as *const _,)*)
+                    });
+                    raw_parent
+                });
+            }
+        }
+    };
+    let deref_parent_view = {
+        if parent_dependencies.is_empty() {
+            quote! {
+                let parent = raw_parent;
+            }
+        } else {
+            let indexes = (0..parent_dependencies.len()).map(syn::Index::from);
+            quote! {
+                let parent = unsafe { raw_parent.map(|raw_parent| (#(dioxus_native_core::prelude::DependancyView::new(&*raw_parent.#indexes),)*)) };
+            }
+        }
+    };
 
-        // members need to be sorted so that members are updated after the members they depend on
-        Ok(Self {
-            ty: &strct.name,
-            state_members,
-            child_states: child_states.collect(),
-        })
-    }
-}
+    let child_view = child_dependencies_idxes
+        .iter()
+        .map(|i| format_ident!("__data{}", i))
+        .collect::<Vec<_>>();
+    let get_child_view = {
+        if child_dependencies.is_empty() {
+            quote! {
+                let raw_children: Vec<_> = tree.children_ids(id).into_iter().map(|_| ()).collect();
+            }
+        } else {
+            let temps = (0..child_dependencies.len())
+                .map(|i| format_ident!("__temp{}", i))
+                .collect::<Vec<_>>();
+            quote! {
+                let raw_children: Vec<_> = tree.children_ids(id).into_iter().filter_map(|id| {
+                    let raw_children: Option<(#(*const #child_dependencies,)*)> = (#(&#child_view,)*).get(id).ok().map(|c| {
+                        let (#(#temps,)*) = c;
+                        (#(#temps as *const _,)*)
+                    });
+                    raw_children
+                }).collect();
+            }
+        }
+    };
+    let deref_child_view = {
+        if child_dependencies.is_empty() {
+            quote! {
+                let children = raw_children;
+            }
+        } else {
+            let indexes = (0..child_dependencies.len()).map(syn::Index::from);
+            quote! {
+                let children = unsafe { raw_children.iter().map(|raw_children| (#(dioxus_native_core::prelude::DependancyView::new(&*raw_children.#indexes),)*)).collect::<Vec<_>>() };
+            }
+        }
+    };
 
-fn try_parenthesized(input: ParseStream) -> Result<ParseBuffer> {
-    let inside;
-    parenthesized!(inside in input);
-    Ok(inside)
-}
+    let trait_generics = trait_
+        .as_ref()
+        .unwrap()
+        .segments
+        .last()
+        .unwrap()
+        .arguments
+        .clone();
 
-struct Dependency {
-    ctx_ty: Option<Type>,
-    deps: Vec<Ident>,
-}
+    // if a create function is defined, we don't generate one
+    // otherwise we generate a default one that uses the update function and the default constructor
+    let create_fn = (!has_create_fn).then(|| {
+        quote! {
+            fn create<'a>(
+                node_view: dioxus_native_core::prelude::NodeView # trait_generics,
+                node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+                parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+                children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+                context: &dioxus_native_core::prelude::SendAnyMap,
+            ) -> Self {
+                let mut myself = Self::default();
+                myself.update(node_view, node, parent, children, context);
+                myself
+            }
+        }
+    });
 
-impl Parse for Dependency {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let deps: Option<Punctuated<Ident, Token![,]>> = {
-            try_parenthesized(input)
-                .ok()
-                .and_then(|inside| inside.parse_terminated(Ident::parse).ok())
-        };
-        let deps: Vec<_> = deps
-            .map(|deps| deps.into_iter().collect())
-            .or_else(|| {
-                input
-                    .parse::<Ident>()
-                    .ok()
-                    .filter(|i: &Ident| i != "NONE")
-                    .map(|i| vec![i])
-            })
-            .unwrap_or_default();
-        let comma: Option<Token![,]> = input.parse().ok();
-        let ctx_ty = input.parse().ok();
-        Ok(Self {
-            ctx_ty: comma.and(ctx_ty),
-            deps,
-        })
-    }
-}
+    quote!(
+        #(#attrs)*
+        #defaultness #unsafety #impl_token #generics #trait_ #for_ #self_ty {
+            #create_fn
 
-/// The type of the member and the ident of the member
-#[derive(PartialEq, Debug)]
-struct Member {
-    id: u64,
-    ty: Type,
-    unit_type: Ident,
-    ident: Ident,
-}
+            #(#items)*
 
-impl Member {
-    fn parse(parent: &Ident, field: &Field, id: u64) -> Option<Self> {
-        Some(Self {
-            id,
-            ty: field.ty.clone(),
-            unit_type: Ident::new(
-                ("_Unit".to_string()
-                    + parent.to_token_stream().to_string().as_str()
-                    + field.ty.to_token_stream().to_string().as_str())
-                .as_str(),
-                Span::call_site(),
-            ),
-            ident: field.ident.as_ref()?.clone(),
-        })
-    }
-}
+            fn workload_system(type_id: std::any::TypeId, dependants: std::sync::Arc<dioxus_native_core::prelude::Dependants>, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem {
+                use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent};
+                use dioxus_native_core::tree::TreeRef;
+                use dioxus_native_core::prelude::{NodeType, NodeView};
 
-#[derive(Debug, Clone)]
-struct StateMember<'a> {
-    mem: &'a Member,
-    // the kind of dependncies this state has
-    dep_kind: DependencyKind,
-    // the depenancy and if it is satified
-    dep_mems: Vec<&'a Member>,
-    // any members that depend on this member
-    dependant_mems: Vec<u64>,
-    // the context this state requires
-    ctx_ty: Option<Type>,
-}
+                let node_mask = Self::NODE_MASK.build();
 
-impl<'a> StateMember<'a> {
-    fn parse(
-        field: &Field,
-        mem: &'a Member,
-        parent: &'a Struct,
-    ) -> Result<Option<StateMember<'a>>> {
-        let mut err = Ok(());
-        let member = field.attrs.iter().find_map(|a| {
-            let dep_kind = a
-                .path
-                .get_ident()
-                .and_then(|i| match i.to_string().as_str() {
-                    "node_dep_state" => Some(DependencyKind::Node),
-                    "child_dep_state" => Some(DependencyKind::Child),
-                    "parent_dep_state" => Some(DependencyKind::Parent),
-                    _ => None,
-                })?;
-            match a.parse_args::<Dependency>() {
-                Ok(dependency) => {
-                    let dep_mems = dependency
-                        .deps
-                        .iter()
-                        .filter_map(|name| {
-                            if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
-                                Some(found)
-                            } else {
-                                err = Err(Error::new(
-                                    name.span(),
-                                    format!("{} not found in {}", name, parent.name),
-                                ));
-                                None
-                            }
-                        })
-                        .collect();
-                    Some(Self {
-                        mem,
-                        dep_kind,
-                        dep_mems,
-                        dependant_mems: Vec::new(),
-                        ctx_ty: dependency.ctx_ty,
-                    })
-                }
-                Err(e) => {
-                    err = Err(e);
-                    None
-                }
-            }
-        });
-        err?;
-        Ok(member)
-    }
+                (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::prelude::RunPassView #trait_generics| {
+                    let (#(#split_views,)*) = data;
+                    let tree = run_view.tree.clone();
+                    let node_types = run_view.node_type.clone();
+                    dioxus_native_core::prelude::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| {
+                        let node_data: &NodeType<_> = node_types.get(id).unwrap_or_else(|err| panic!("Failed to get node type {:?}", err));
+                        // get all of the states from the tree view
+                        // Safety: No node has itself as a parent or child.
+                        let raw_myself: Option<*mut Self> = (&mut #this_view).get(id).ok().map(|c| c as *mut _);
+                        #get_node_view
+                        #get_parent_view
+                        #get_child_view
 
-    /// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty.
-    fn impl_pass(
-        &self,
-        parent_type: &Ident,
-        custom_type: impl ToTokens,
-    ) -> quote::__private::TokenStream {
-        let ident = &self.mem.ident;
-        let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
-            if ctx_ty == &parse_quote!(()) {
-                quote! {&()}
-            } else {
-                let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
-                quote! {ctx.get().expect(#msg)}
-            }
-        } else {
-            quote! {&()}
-        };
+                        let myself: Option<&mut Self> = unsafe { raw_myself.map(|val| &mut *val) };
+                        #deref_node_view
+                        #deref_parent_view
+                        #deref_child_view
 
-        let ty = &self.mem.ty;
-        let unit_type = &self.mem.unit_type;
-        let node_view =
-            quote!(dioxus_native_core::node_ref::NodeView::new(&node.node_data, #ty::NODE_MASK));
-        let dep_idents = self.dep_mems.iter().map(|m| &m.ident);
-        let impl_specific = match self.dep_kind {
-            DependencyKind::Node => {
-                quote! {
-                    impl dioxus_native_core::NodePass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type {
-                        fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool {
-                            node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx)
-                        }
-                    }
-                }
-            }
-            DependencyKind::Child => {
-                let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
-                    quote! {
-                        if update {
-                            dioxus_native_core::PassReturn{
-                                progress: true,
-                                mark_dirty: true,
-                            }
-                        } else {
-                            dioxus_native_core::PassReturn{
-                                progress: false,
-                                mark_dirty: false,
-                            }
+                        let view = NodeView::new(id, node_data, &node_mask);
+                        if let Some(myself) = myself {
+                            myself
+                                .update(view, node, parent, children, context)
                         }
-                    }
-                } else {
-                    quote! {
-                        if update {
-                            dioxus_native_core::PassReturn{
-                                progress: false,
-                                mark_dirty: true,
-                            }
-                        } else {
-                            dioxus_native_core::PassReturn{
-                                progress: false,
-                                mark_dirty: false,
-                            }
-                        }
-                    }
-                };
-                quote!(
-                    impl dioxus_native_core::UpwardPass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type{
-                        fn pass<'a>(
-                            &self,
-                            node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>,
-                            children: &mut dyn Iterator<Item = &'a mut dioxus_native_core::node::Node<#parent_type, #custom_type>>,
-                            ctx: &dioxus_native_core::SendAnyMap,
-                        ) -> dioxus_native_core::PassReturn {
-                            let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx);
-                            #update
-                        }
-                    }
-                )
-            }
-            DependencyKind::Parent => {
-                let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
-                    quote! {
-                        if update {
-                            dioxus_native_core::PassReturn{
-                                progress: true,
-                                mark_dirty: true,
-                            }
-                        } else {
-                            dioxus_native_core::PassReturn{
-                                progress: false,
-                                mark_dirty: false,
-                            }
+                        else {
+                            (&mut #this_view).add_component_unchecked(
+                                id,
+                                Self::create(view, node, parent, children, context));
+                            true
                         }
-                    }
-                } else {
-                    quote! {
-                        if update {
-                            dioxus_native_core::PassReturn{
-                                progress: false,
-                                mark_dirty: true,
-                            }
-                        } else {
-                            dioxus_native_core::PassReturn{
-                                progress: false,
-                                mark_dirty: false,
-                            }
-                        }
-                    }
-                };
-                quote!(
-                    impl dioxus_native_core::DownwardPass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type {
-                        fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type, #custom_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{
-                            let update = node.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx);
-                            #update
-                        }
-                    }
-                )
-            }
-        };
-        let pass_id = self.mem.id;
-        let depenancies = self.dep_mems.iter().map(|m| m.id);
-        let dependants = &self.dependant_mems;
-        let mask = self
-            .dep_mems
-            .iter()
-            .map(|m| 1u64 << m.id)
-            .fold(1 << self.mem.id, |a, b| a | b);
-        quote! {
-            #[derive(Clone, Copy)]
-            struct #unit_type;
-            #impl_specific
-            impl dioxus_native_core::Pass for #unit_type {
-                fn pass_id(&self) -> dioxus_native_core::PassId {
-                    dioxus_native_core::PassId(#pass_id)
-                }
-                fn dependancies(&self) -> &'static [dioxus_native_core::PassId] {
-                    &[#(dioxus_native_core::PassId(#depenancies)),*]
-                }
-                fn dependants(&self) -> &'static [dioxus_native_core::PassId] {
-                    &[#(dioxus_native_core::PassId(#dependants)),*]
-                }
-                fn mask(&self) -> dioxus_native_core::MemberMask {
-                    dioxus_native_core::MemberMask(#mask)
-                }
+                    })
+                }).into_workload_system().unwrap()
             }
         }
+    )
+    .into()
+}
+
+fn extract_tuple(ty: &Type) -> Option<TypeTuple> {
+    match ty {
+        Type::Tuple(tuple) => Some(tuple.clone()),
+        Type::Group(group) => extract_tuple(&group.elem),
+        _ => None,
+    }
+}
+
+fn extract_type_path(ty: &Type) -> Option<TypePath> {
+    match ty {
+        Type::Path(path) => Some(path.clone()),
+        Type::Group(group) => extract_type_path(&group.elem),
+        _ => None,
     }
 }

+ 0 - 29
packages/native-core-macro/src/sorted_slice.rs

@@ -1,29 +0,0 @@
-extern crate proc_macro;
-
-use std::collections::BTreeMap;
-
-use syn::{
-    self, bracketed,
-    parse::{Parse, ParseStream, Result},
-    LitStr, Token,
-};
-
-pub struct StrSlice {
-    pub map: BTreeMap<String, LitStr>,
-}
-
-impl Parse for StrSlice {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let content;
-        bracketed!(content in input);
-        let mut map = BTreeMap::new();
-        while let Ok(s) = content.parse::<LitStr>() {
-            map.insert(s.value(), s);
-            #[allow(unused_must_use)]
-            {
-                content.parse::<Token![,]>();
-            }
-        }
-        Ok(StrSlice { map })
-    }
-}

+ 0 - 244
packages/native-core-macro/tests/called_minimally_on_build.rs

@@ -1,244 +0,0 @@
-use dioxus::prelude::*;
-use dioxus_native_core::node_ref::*;
-use dioxus_native_core::real_dom::*;
-use dioxus_native_core::state::{
-    ChildDepState, ElementBorrowable, NodeDepState, ParentDepState, State,
-};
-use dioxus_native_core::tree::*;
-use dioxus_native_core::SendAnyMap;
-use dioxus_native_core_macro::State;
-
-macro_rules! dep {
-    ( child( $name:ty, $dep:ty ) ) => {
-        impl ChildDepState for $name {
-            type Ctx = ();
-            type DepState = $dep;
-            const NODE_MASK: NodeMask = NodeMask::ALL;
-            fn reduce<'a>(
-                &mut self,
-                _: NodeView,
-                _: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
-                _: &Self::Ctx,
-            ) -> bool
-            where
-                Self::DepState: 'a,
-            {
-                self.0 += 1;
-                true
-            }
-        }
-    };
-
-    ( parent( $name:ty, $dep:ty ) ) => {
-        impl ParentDepState for $name {
-            type Ctx = ();
-            type DepState = $dep;
-            const NODE_MASK: NodeMask = NodeMask::ALL;
-            fn reduce(
-                &mut self,
-                _: NodeView,
-                _: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'_>>,
-                _: &Self::Ctx,
-            ) -> bool {
-                self.0 += 1;
-                true
-            }
-        }
-    };
-
-    ( node( $name:ty, $dep:ty ) ) => {
-        impl NodeDepState for $name {
-            type Ctx = ();
-            type DepState = $dep;
-            const NODE_MASK: NodeMask = NodeMask::ALL;
-            fn reduce(
-                &mut self,
-                _: NodeView,
-                _: <Self::DepState as ElementBorrowable>::ElementBorrowed<'_>,
-                _: &Self::Ctx,
-            ) -> bool {
-                self.0 += 1;
-                true
-            }
-        }
-    };
-}
-
-macro_rules! test_state{
-    ( $s:ty, child: ( $( $child:ident ),* ), node: ( $( $node:ident ),* ), parent: ( $( $parent:ident ),* ) ) => {
-        #[test]
-        fn state_reduce_initally_called_minimally() {
-            #[allow(non_snake_case)]
-            fn Base(cx: Scope) -> Element {
-                render!{
-                    div {
-                        div{
-                            div{
-                                p{}
-                            }
-                            p{
-                                "hello"
-                            }
-                            div{
-                                h1{}
-                            }
-                            p{
-                                "world"
-                            }
-                        }
-                    }
-                }
-            }
-
-            let mut vdom = VirtualDom::new(Base);
-
-            let mutations = vdom.rebuild();
-
-            let mut dom: RealDom<$s> = RealDom::new();
-
-            let (nodes_updated, _) = dom.apply_mutations(mutations);
-            let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
-
-            dom.traverse_depth_first(|n| {
-                $(
-                    assert_eq!(n.state.$child.0, 1);
-                )*
-                $(
-                    assert_eq!(n.state.$node.0, 1);
-                )*
-                $(
-                    assert_eq!(n.state.$parent.0, 1);
-                )*
-            });
-        }
-    }
-}
-
-mod node_depends_on_child_and_parent {
-    use super::*;
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Node(i32);
-    dep!(node(Node, (Child, Parent)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Child(i32);
-    dep!(child(Child, (Child,)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Parent(i32);
-    dep!(parent(Parent, (Parent,)));
-
-    #[derive(Debug, Clone, Default, State)]
-    struct StateTester {
-        #[node_dep_state((child, parent))]
-        node: Node,
-        #[child_dep_state(child)]
-        child: Child,
-        #[parent_dep_state(parent)]
-        parent: Parent,
-    }
-
-    test_state!(StateTester, child: (child), node: (node), parent: (parent));
-}
-
-mod child_depends_on_node_that_depends_on_parent {
-    use super::*;
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Node(i32);
-    dep!(node(Node, (Parent,)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Child(i32);
-    dep!(child(Child, (Node,)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Parent(i32);
-    dep!(parent(Parent, (Parent,)));
-
-    #[derive(Debug, Clone, Default, State)]
-    struct StateTester {
-        #[node_dep_state(parent)]
-        node: Node,
-        #[child_dep_state(node)]
-        child: Child,
-        #[parent_dep_state(parent)]
-        parent: Parent,
-    }
-
-    test_state!(StateTester, child: (child), node: (node), parent: (parent));
-}
-
-mod parent_depends_on_node_that_depends_on_child {
-    use super::*;
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Node(i32);
-    dep!(node(Node, (Child,)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Child(i32);
-    dep!(child(Child, (Child,)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Parent(i32);
-    dep!(parent(Parent, (Node,)));
-
-    #[derive(Debug, Clone, Default, State)]
-    struct StateTester {
-        #[node_dep_state(child)]
-        node: Node,
-        #[child_dep_state(child)]
-        child: Child,
-        #[parent_dep_state(node)]
-        parent: Parent,
-    }
-
-    test_state!(StateTester, child: (child), node: (node), parent: (parent));
-}
-
-mod node_depends_on_other_node_state {
-    use super::*;
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Node1(i32);
-    dep!(node(Node1, (Node2,)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Node2(i32);
-    dep!(node(Node2, ()));
-
-    #[derive(Debug, Clone, Default, State)]
-    struct StateTester {
-        #[node_dep_state((node2))]
-        node1: Node1,
-        #[node_dep_state()]
-        node2: Node2,
-    }
-
-    test_state!(StateTester, child: (), node: (node1, node2), parent: ());
-}
-
-mod node_child_and_parent_state_depends_on_self {
-    use super::*;
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Node(i32);
-    dep!(node(Node, ()));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Child(i32);
-    dep!(child(Child, (Child,)));
-
-    #[derive(Debug, Clone, Default, PartialEq)]
-    struct Parent(i32);
-    dep!(parent(Parent, (Parent,)));
-
-    #[derive(Debug, Clone, Default, State)]
-    struct StateTester {
-        #[node_dep_state()]
-        node: Node,
-        #[child_dep_state(child)]
-        child: Child,
-        #[parent_dep_state(parent)]
-        parent: Parent,
-    }
-
-    test_state!(StateTester, child: (child), node: (node), parent: (parent));
-}

+ 0 - 421
packages/native-core-macro/tests/update_state.rs

@@ -1,421 +0,0 @@
-use dioxus::prelude::*;
-use dioxus_native_core::real_dom::*;
-use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
-use dioxus_native_core::tree::TreeView;
-use dioxus_native_core::{node_ref::*, NodeId, SendAnyMap};
-use dioxus_native_core_macro::State;
-
-#[derive(Debug, Clone, Default, State)]
-struct CallCounterState {
-    #[child_dep_state(child_counter)]
-    child_counter: ChildDepCallCounter,
-    #[parent_dep_state(parent_counter)]
-    parent_counter: ParentDepCallCounter,
-    #[node_dep_state()]
-    node_counter: NodeDepCallCounter,
-}
-
-#[derive(Debug, Clone, Default)]
-struct ChildDepCallCounter(u32);
-impl ChildDepState for ChildDepCallCounter {
-    type Ctx = ();
-    type DepState = (Self,);
-    const NODE_MASK: NodeMask = NodeMask::ALL;
-    fn reduce<'a>(
-        &mut self,
-        _: NodeView,
-        _: impl Iterator<Item = (&'a Self,)>,
-        _: &Self::Ctx,
-    ) -> bool
-    where
-        Self::DepState: 'a,
-    {
-        self.0 += 1;
-        true
-    }
-}
-
-#[derive(Debug, Clone, Default)]
-struct ParentDepCallCounter(u32);
-impl ParentDepState for ParentDepCallCounter {
-    type Ctx = ();
-    type DepState = (Self,);
-    const NODE_MASK: NodeMask = NodeMask::ALL;
-    fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
-        self.0 += 1;
-        true
-    }
-}
-
-#[derive(Debug, Clone, Default)]
-struct NodeDepCallCounter(u32);
-impl NodeDepState for NodeDepCallCounter {
-    type Ctx = ();
-    type DepState = ();
-    const NODE_MASK: NodeMask = NodeMask::ALL;
-    fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
-        self.0 += 1;
-        true
-    }
-}
-
-#[allow(clippy::vec_box)]
-#[derive(Debug, Clone, PartialEq, Default)]
-struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
-impl ChildDepState for BubbledUpStateTester {
-    type Ctx = u32;
-    type DepState = (Self,);
-    const NODE_MASK: NodeMask = NodeMask::new().with_tag();
-    fn reduce<'a>(
-        &mut self,
-        node: NodeView,
-        children: impl Iterator<Item = (&'a Self,)>,
-        ctx: &Self::Ctx,
-    ) -> bool
-    where
-        Self::DepState: 'a,
-    {
-        assert_eq!(*ctx, 42);
-        *self = BubbledUpStateTester(
-            node.tag().map(|s| s.to_string()),
-            children
-                .into_iter()
-                .map(|(c,)| Box::new(c.clone()))
-                .collect(),
-        );
-        true
-    }
-}
-
-#[derive(Debug, Clone, PartialEq, Default)]
-struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
-impl ParentDepState for PushedDownStateTester {
-    type Ctx = u32;
-    type DepState = (Self,);
-    const NODE_MASK: NodeMask = NodeMask::new().with_tag();
-    fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, ctx: &Self::Ctx) -> bool {
-        assert_eq!(*ctx, 42);
-        *self = PushedDownStateTester(
-            node.tag().map(|s| s.to_string()),
-            parent.map(|(c,)| Box::new(c.clone())),
-        );
-        true
-    }
-}
-
-#[derive(Debug, Clone, PartialEq, Default)]
-struct NodeStateTester(Option<String>, Vec<(String, String)>);
-impl NodeDepState for NodeStateTester {
-    type Ctx = u32;
-    type DepState = ();
-    const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag();
-    fn reduce(&mut self, node: NodeView, _sibling: (), ctx: &Self::Ctx) -> bool {
-        assert_eq!(*ctx, 42);
-        *self = NodeStateTester(
-            node.tag().map(|s| s.to_string()),
-            node.attributes()
-                .map(|iter| {
-                    iter.map(|a| {
-                        (
-                            a.attribute.name.to_string(),
-                            a.value.as_text().unwrap().to_string(),
-                        )
-                    })
-                    .collect()
-                })
-                .unwrap_or_default(),
-        );
-        true
-    }
-}
-
-#[derive(State, Clone, Default, Debug)]
-struct StateTester {
-    #[child_dep_state(bubbled, u32)]
-    bubbled: BubbledUpStateTester,
-    #[parent_dep_state(pushed, u32)]
-    pushed: PushedDownStateTester,
-    #[node_dep_state(NONE, u32)]
-    node: NodeStateTester,
-}
-
-#[test]
-fn state_initial() {
-    #[allow(non_snake_case)]
-    fn Base(cx: Scope) -> Element {
-        render! {
-            div {
-                p{
-                    color: "red"
-                }
-                h1{}
-            }
-        }
-    }
-
-    let mut vdom = VirtualDom::new(Base);
-
-    let mutations = vdom.rebuild();
-
-    let mut dom: RealDom<StateTester> = RealDom::new();
-
-    let (nodes_updated, _) = dom.apply_mutations(mutations);
-    let mut ctx = SendAnyMap::new();
-    ctx.insert(42u32);
-    let _to_rerender = dom.update_state(nodes_updated, ctx);
-
-    let root_div_id = dom.children_ids(NodeId(0)).unwrap()[0];
-    let root_div = &dom.get(root_div_id).unwrap();
-    assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
-    assert_eq!(
-        root_div.state.bubbled.1,
-        vec![
-            Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())),
-            Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new()))
-        ]
-    );
-    assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
-    assert_eq!(
-        root_div.state.pushed.1,
-        Some(Box::new(PushedDownStateTester(
-            Some("Root".to_string()),
-            None
-        )))
-    );
-    assert_eq!(root_div.state.node.0, Some("div".to_string()));
-    assert_eq!(root_div.state.node.1, vec![]);
-
-    let child_p_id = dom.children_ids(root_div_id).unwrap()[0];
-    let child_p = &dom[child_p_id];
-    assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
-    assert_eq!(child_p.state.bubbled.1, Vec::new());
-    assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
-    assert_eq!(
-        child_p.state.pushed.1,
-        Some(Box::new(PushedDownStateTester(
-            Some("div".to_string()),
-            Some(Box::new(PushedDownStateTester(
-                Some("Root".to_string()),
-                None
-            )))
-        )))
-    );
-    assert_eq!(child_p.state.node.0, Some("p".to_string()));
-    assert_eq!(
-        child_p.state.node.1,
-        vec![("color".to_string(), "red".to_string())]
-    );
-
-    let child_h1_id = dom.children_ids(root_div_id).unwrap()[1];
-    let child_h1 = &dom[child_h1_id];
-    assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
-    assert_eq!(child_h1.state.bubbled.1, Vec::new());
-    assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
-    assert_eq!(
-        child_h1.state.pushed.1,
-        Some(Box::new(PushedDownStateTester(
-            Some("div".to_string()),
-            Some(Box::new(PushedDownStateTester(
-                Some("Root".to_string()),
-                None
-            )))
-        )))
-    );
-    assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
-    assert_eq!(child_h1.state.node.1, vec![]);
-}
-
-#[test]
-fn state_reduce_parent_called_minimally_on_update() {
-    #[allow(non_snake_case)]
-    fn Base(cx: Scope) -> Element {
-        let width = if cx.generation() == 0 { "100%" } else { "99%" };
-        cx.render(rsx! {
-            div {
-                width: "{width}",
-                div{
-                    div{
-                        p{}
-                    }
-                    p{
-                        "hello"
-                    }
-                    div{
-                        h1{}
-                    }
-                    p{
-                        "world"
-                    }
-                }
-            }
-        })
-    }
-
-    let mut vdom = VirtualDom::new(Base);
-
-    let mut dom: RealDom<CallCounterState> = RealDom::new();
-
-    let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild());
-    let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
-    vdom.mark_dirty(ScopeId(0));
-    let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate());
-    let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
-
-    let mut is_root = true;
-    dom.traverse_depth_first(|n| {
-        if is_root {
-            is_root = false;
-            assert_eq!(n.state.parent_counter.0, 1);
-        } else {
-            assert_eq!(n.state.parent_counter.0, 2);
-        }
-    });
-}
-
-#[test]
-fn state_reduce_child_called_minimally_on_update() {
-    #[allow(non_snake_case)]
-    fn Base(cx: Scope) -> Element {
-        let width = if cx.generation() == 0 { "100%" } else { "99%" };
-        cx.render(rsx! {
-            // updated: 2
-            div {
-                // updated: 2
-                div{
-                    // updated: 2
-                    div{
-                        // updated: 2
-                        p{
-                            width: "{width}",
-                        }
-                    }
-                    // updated: 1
-                    p{
-                        // updated: 1
-                        "hello"
-                    }
-                    // updated: 1
-                    div{
-                        // updated: 1
-                        h1{}
-                    }
-                    // updated: 1
-                    p{
-                        // updated: 1
-                        "world"
-                    }
-                }
-            }
-        })
-    }
-
-    let mut vdom = VirtualDom::new(Base);
-
-    let mut dom: RealDom<CallCounterState> = RealDom::new();
-
-    let (nodes_updated, _) = dom.apply_mutations(vdom.rebuild());
-    let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
-    vdom.mark_dirty(ScopeId(0));
-    let (nodes_updated, _) = dom.apply_mutations(vdom.render_immediate());
-    let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
-
-    let mut traverse_count = 0;
-    dom.traverse_depth_first(|n| {
-        assert_eq!(n.state.child_counter.0, {
-            if traverse_count > 4 {
-                1
-            } else {
-                2
-            }
-        });
-        traverse_count += 1;
-    });
-}
-
-#[derive(Debug, Clone, Default, State)]
-struct UnorderedDependanciesState {
-    #[node_dep_state(c)]
-    b: BDepCallCounter,
-    #[node_dep_state()]
-    c: CDepCallCounter,
-    #[node_dep_state(b)]
-    a: ADepCallCounter,
-}
-
-#[derive(Debug, Clone, Default, PartialEq)]
-struct ADepCallCounter(usize, BDepCallCounter);
-impl NodeDepState for ADepCallCounter {
-    type Ctx = ();
-    type DepState = (BDepCallCounter,);
-    const NODE_MASK: NodeMask = NodeMask::NONE;
-    fn reduce(
-        &mut self,
-        _node: NodeView,
-        (sibling,): (&BDepCallCounter,),
-        _ctx: &Self::Ctx,
-    ) -> bool {
-        self.0 += 1;
-        self.1 = sibling.clone();
-        true
-    }
-}
-
-#[derive(Debug, Clone, Default, PartialEq)]
-struct BDepCallCounter(usize, CDepCallCounter);
-impl NodeDepState for BDepCallCounter {
-    type Ctx = ();
-    type DepState = (CDepCallCounter,);
-    const NODE_MASK: NodeMask = NodeMask::NONE;
-    fn reduce(
-        &mut self,
-        _node: NodeView,
-        (sibling,): (&CDepCallCounter,),
-        _ctx: &Self::Ctx,
-    ) -> bool {
-        self.0 += 1;
-        self.1 = sibling.clone();
-        true
-    }
-}
-
-#[derive(Debug, Clone, Default, PartialEq)]
-struct CDepCallCounter(usize);
-impl NodeDepState for CDepCallCounter {
-    type Ctx = ();
-    type DepState = ();
-    const NODE_MASK: NodeMask = NodeMask::ALL;
-    fn reduce(&mut self, _node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {
-        self.0 += 1;
-        true
-    }
-}
-
-#[test]
-fn dependancies_order_independant() {
-    #[allow(non_snake_case)]
-    fn Base(cx: Scope) -> Element {
-        render!(div {
-            width: "100%",
-            p{
-                "hello"
-            }
-        })
-    }
-
-    let mut vdom = VirtualDom::new(Base);
-
-    let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
-
-    let mutations = vdom.rebuild();
-    let (nodes_updated, _) = dom.apply_mutations(mutations);
-    let _to_rerender = dom.update_state(nodes_updated, SendAnyMap::new());
-
-    let c = CDepCallCounter(1);
-    let b = BDepCallCounter(1, c.clone());
-    let a = ADepCallCounter(1, b.clone());
-    dom.traverse_depth_first(|n| {
-        assert_eq!(&n.state.a, &a);
-        assert_eq!(&n.state.b, &b);
-        assert_eq!(&n.state.c, &c);
-    });
-}

+ 15 - 6
packages/native-core/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-native-core"
-version = "0.3.0"
+version = "0.2.0"
 edition = "2021"
 license = "MIT/Apache-2.0"
 repository = "https://github.com/DioxusLabs/dioxus/"
@@ -11,24 +11,33 @@ keywords = ["dom", "ui", "gui", "react"]
 
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.3.0" }
-dioxus-html = { path = "../html", version = "^0.3.0" }
-dioxus-core-macro = { path = "../core-macro", version = "^0.3.0" }
+dioxus-core = { path = "../core", version = "^0.3.0", optional = true }
 
+keyboard-types = "0.6.2"
 taffy = "0.2.1"
 smallvec = "1.6"
 rustc-hash = "1.1.0"
 anymap = "1.0.0-beta.2"
 slab = "0.4"
-parking_lot = "0.12.1"
+parking_lot = { version = "0.12.1", features = ["send_guard"] }
 crossbeam-deque = "0.8.2"
 dashmap = "5.4.0"
+hashbrown = { version = "0.13.2", features = ["raw"] }
 
 # for parsing attributes
 lightningcss = "1.0.0-alpha.39"
 
+rayon = "1.6.1"
+shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false }
+
 [dev-dependencies]
 rand = "0.8.5"
 dioxus = { path = "../dioxus", version = "^0.3.0" }
+tokio = { version = "*", features = ["full"] }
+dioxus-native-core = { path = ".", features = ["dioxus"] }
 dioxus-native-core-macro = { path = "../native-core-macro" }
-tokio = { version = "1.0", features = ["full"] }
+
+[features]
+default = []
+dioxus = ["dioxus-core"]
+parallel = ["shipyard/parallel"]

+ 1 - 1
packages/native-core/README.md

@@ -18,7 +18,7 @@
 [discord-url]: https://discord.gg/XgGxMSkvUM
 
 [Website](https://dioxuslabs.com) |
-[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) |
+[Guides](https://dioxuslabs.com/guide/) |
 [API Docs](https://docs.rs/dioxus-native-core/latest/dioxus_native_core) |
 [Chat](https://discord.gg/XgGxMSkvUM)
 

+ 231 - 0
packages/native-core/examples/custom_attr.rs

@@ -0,0 +1,231 @@
+use dioxus_native_core::exports::shipyard::Component;
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core::real_dom::NodeTypeMut;
+use dioxus_native_core_macro::partial_derive_state;
+
+struct FontSize(f64);
+
+// All states need to derive Component
+#[derive(Default, Debug, Copy, Clone, Component)]
+struct Size(f64, f64);
+
+#[derive(Default, Debug, Copy, Clone, Component)]
+struct X;
+
+impl FromAnyValue for X {
+    fn from_any_value(_: &dyn std::any::Any) -> Self {
+        X
+    }
+}
+
+/// Derive some of the boilerplate for the State implementation
+#[partial_derive_state]
+impl State<X> for Size {
+    type ParentDependencies = ();
+
+    // The size of the current node depends on the size of its children
+    type ChildDependencies = (Self,);
+
+    type NodeDependencies = ();
+
+    // Size only cares about the width, height, and text parts of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        // Get access to the width and height attributes
+        .with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
+        // Get access to the text of the node
+        .with_text();
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<X>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> bool {
+        let font_size = context.get::<FontSize>().unwrap().0;
+        let mut width;
+        let mut height;
+        if let Some(text) = node_view.text() {
+            // if the node has text, use the text to size our object
+            width = text.len() as f64 * font_size;
+            height = font_size;
+        } else {
+            // otherwise, the size is the maximum size of the children
+            width = children
+                .iter()
+                .map(|(item,)| item.0)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+
+            height = children
+                .iter()
+                .map(|(item,)| item.1)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+        }
+        // if the node contains a width or height attribute it overrides the other size
+        for a in node_view.attributes().into_iter().flatten() {
+            match &*a.attribute.name {
+                "width" => width = a.value.as_float().unwrap(),
+                "height" => height = a.value.as_float().unwrap(),
+                // because Size only depends on the width and height, no other attributes will be passed to the member
+                _ => panic!(),
+            }
+        }
+        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
+        let changed = (width != self.0) || (height != self.1);
+        *self = Self(width, height);
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct TextColor {
+    r: u8,
+    g: u8,
+    b: u8,
+}
+
+#[partial_derive_state]
+impl State<X> for TextColor {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // TextColor only cares about the color attribute of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the color attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<X>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
+        let new = match node_view
+            .attributes()
+            .and_then(|mut attrs| attrs.next())
+            .and_then(|attr| attr.value.as_text())
+        {
+            // if there is a color tag, translate it
+            Some("red") => TextColor { r: 255, g: 0, b: 0 },
+            Some("green") => TextColor { r: 0, g: 255, b: 0 },
+            Some("blue") => TextColor { r: 0, g: 0, b: 255 },
+            Some(color) => panic!("unknown color {color}"),
+            // otherwise check if the node has a parent and inherit that color
+            None => match parent {
+                Some((parent,)) => *parent,
+                None => Self::default(),
+            },
+        };
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct Border(bool);
+
+#[partial_derive_state]
+impl State<X> for Border {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // Border does not depended on any other member in the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the border attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<X>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // check if the node contians a border attribute
+        let new = Self(
+            node_view
+                .attributes()
+                .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
+                .is_some(),
+        );
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let mut rdom: RealDom<X> = RealDom::new([
+        Border::to_type_erased(),
+        TextColor::to_type_erased(),
+        Size::to_type_erased(),
+    ]);
+
+    let mut count = 0;
+
+    // intial render
+    let text_id = rdom.create_node(format!("Count: {count}")).id();
+    let mut root = rdom.get_mut(rdom.root_id()).unwrap();
+    // set the color to red
+    if let NodeTypeMut::Element(mut element) = root.node_type_mut() {
+        element.set_attribute(("color", "style"), "red".to_string());
+    }
+    root.add_child(text_id);
+
+    let mut ctx = SendAnyMap::new();
+    // set the font size to 3.3
+    ctx.insert(FontSize(3.3));
+    // update the State for nodes in the real_dom tree
+    let _to_rerender = rdom.update_state(ctx);
+
+    // we need to run the vdom in a async runtime
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?
+        .block_on(async {
+            loop {
+                // update the count
+                count += 1;
+                let mut text = rdom.get_mut(text_id).unwrap();
+                if let NodeTypeMut::Text(mut text) = text.node_type_mut() {
+                    *text = format!("Count: {count}");
+                }
+
+                let mut ctx = SendAnyMap::new();
+                ctx.insert(FontSize(3.3));
+                let _to_rerender = rdom.update_state(ctx);
+
+                // render...
+                rdom.traverse_depth_first(|node| {
+                    let indent = " ".repeat(node.height() as usize);
+                    let color = *node.get::<TextColor>().unwrap();
+                    let size = *node.get::<Size>().unwrap();
+                    let border = *node.get::<Border>().unwrap();
+                    let id = node.id();
+                    println!("{indent}{id:?} {color:?} {size:?} {border:?}");
+                });
+
+                // wait 1 second
+                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+            }
+        })
+}

+ 175 - 0
packages/native-core/examples/font_size.rs

@@ -0,0 +1,175 @@
+use dioxus_native_core::exports::shipyard::Component;
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core::real_dom::NodeTypeMut;
+use dioxus_native_core_macro::partial_derive_state;
+
+// All states need to derive Component
+#[derive(Default, Debug, Copy, Clone, Component)]
+struct Size(f64, f64);
+
+/// Derive some of the boilerplate for the State implementation
+#[partial_derive_state]
+impl State for Size {
+    type ParentDependencies = ();
+
+    // The size of the current node depends on the size of its children
+    type ChildDependencies = (Self,);
+
+    type NodeDependencies = (FontSize,);
+
+    // Size only cares about the width, height, and text parts of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        // Get access to the width and height attributes
+        .with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
+        // Get access to the text of the node
+        .with_text();
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        (font_size,): <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _: &SendAnyMap,
+    ) -> bool {
+        let font_size = font_size.size;
+        let mut width;
+        let mut height;
+        if let Some(text) = node_view.text() {
+            // if the node has text, use the text to size our object
+            width = text.len() as f64 * font_size;
+            height = font_size;
+        } else {
+            // otherwise, the size is the maximum size of the children
+            width = children
+                .iter()
+                .map(|(item,)| item.0)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+
+            height = children
+                .iter()
+                .map(|(item,)| item.1)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+        }
+        // if the node contains a width or height attribute it overrides the other size
+        for a in node_view.attributes().into_iter().flatten() {
+            match &*a.attribute.name {
+                "width" => width = a.value.as_float().unwrap(),
+                "height" => height = a.value.as_float().unwrap(),
+                // because Size only depends on the width and height, no other attributes will be passed to the member
+                _ => panic!(),
+            }
+        }
+        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
+        let changed = (width != self.0) || (height != self.1);
+        *self = Self(width, height);
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Component)]
+struct FontSize {
+    size: f64,
+}
+
+impl Default for FontSize {
+    fn default() -> Self {
+        Self { size: 16.0 }
+    }
+}
+
+#[partial_derive_state]
+impl State for FontSize {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // TextColor only cares about the color attribute of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the color attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["font-size"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        let mut new = None;
+        for attr in node_view.attributes().into_iter().flatten() {
+            if attr.attribute.name == "font-size" {
+                new = Some(FontSize {
+                    size: attr.value.as_float().unwrap(),
+                });
+            }
+        }
+        let new = new.unwrap_or(parent.map(|(p,)| *p).unwrap_or_default());
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let mut rdom: RealDom = RealDom::new([FontSize::to_type_erased(), Size::to_type_erased()]);
+
+    let mut count = 0;
+
+    // intial render
+    let text_id = rdom.create_node(format!("Count: {count}")).id();
+    let mut root = rdom.get_mut(rdom.root_id()).unwrap();
+    // set the color to red
+    if let NodeTypeMut::Element(mut element) = root.node_type_mut() {
+        element.set_attribute(("color", "style"), "red".to_string());
+        element.set_attribute(("font-size", "style"), 1.);
+    }
+    root.add_child(text_id);
+
+    let ctx = SendAnyMap::new();
+    // update the State for nodes in the real_dom tree
+    let _to_rerender = rdom.update_state(ctx);
+
+    // we need to run the vdom in a async runtime
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?
+        .block_on(async {
+            loop {
+                // update the count and font size
+                count += 1;
+                let mut text = rdom.get_mut(text_id).unwrap();
+                if let NodeTypeMut::Text(mut text) = text.node_type_mut() {
+                    *text = format!("Count: {count}");
+                }
+                if let NodeTypeMut::Element(mut element) =
+                    rdom.get_mut(rdom.root_id()).unwrap().node_type_mut()
+                {
+                    element.set_attribute(("font-size", "style"), count as f64);
+                }
+
+                let ctx = SendAnyMap::new();
+                let _to_rerender = rdom.update_state(ctx);
+
+                // render...
+                rdom.traverse_depth_first(|node| {
+                    let indent = " ".repeat(node.height() as usize);
+                    let font_size = *node.get::<FontSize>().unwrap();
+                    let size = *node.get::<Size>().unwrap();
+                    let id = node.id();
+                    println!("{indent}{id:?} {font_size:?} {size:?}");
+                });
+
+                // wait 1 second
+                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+            }
+        })
+}

+ 222 - 0
packages/native-core/examples/simple.rs

@@ -0,0 +1,222 @@
+use dioxus_native_core::exports::shipyard::Component;
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core::real_dom::NodeTypeMut;
+use dioxus_native_core_macro::partial_derive_state;
+
+struct FontSize(f64);
+
+// All states need to derive Component
+#[derive(Default, Debug, Copy, Clone, Component)]
+struct Size(f64, f64);
+
+/// Derive some of the boilerplate for the State implementation
+#[partial_derive_state]
+impl State for Size {
+    type ParentDependencies = ();
+
+    // The size of the current node depends on the size of its children
+    type ChildDependencies = (Self,);
+
+    type NodeDependencies = ();
+
+    // Size only cares about the width, height, and text parts of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        // Get access to the width and height attributes
+        .with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
+        // Get access to the text of the node
+        .with_text();
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> bool {
+        let font_size = context.get::<FontSize>().unwrap().0;
+        let mut width;
+        let mut height;
+        if let Some(text) = node_view.text() {
+            // if the node has text, use the text to size our object
+            width = text.len() as f64 * font_size;
+            height = font_size;
+        } else {
+            // otherwise, the size is the maximum size of the children
+            width = children
+                .iter()
+                .map(|(item,)| item.0)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+
+            height = children
+                .iter()
+                .map(|(item,)| item.1)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+        }
+        // if the node contains a width or height attribute it overrides the other size
+        for a in node_view.attributes().into_iter().flatten() {
+            match &*a.attribute.name {
+                "width" => width = a.value.as_float().unwrap(),
+                "height" => height = a.value.as_float().unwrap(),
+                // because Size only depends on the width and height, no other attributes will be passed to the member
+                _ => panic!(),
+            }
+        }
+        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
+        let changed = (width != self.0) || (height != self.1);
+        *self = Self(width, height);
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct TextColor {
+    r: u8,
+    g: u8,
+    b: u8,
+}
+
+#[partial_derive_state]
+impl State for TextColor {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // TextColor only cares about the color attribute of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the color attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
+        let new = match node_view
+            .attributes()
+            .and_then(|mut attrs| attrs.next())
+            .and_then(|attr| attr.value.as_text())
+        {
+            // if there is a color tag, translate it
+            Some("red") => TextColor { r: 255, g: 0, b: 0 },
+            Some("green") => TextColor { r: 0, g: 255, b: 0 },
+            Some("blue") => TextColor { r: 0, g: 0, b: 255 },
+            Some(color) => panic!("unknown color {color}"),
+            // otherwise check if the node has a parent and inherit that color
+            None => match parent {
+                Some((parent,)) => *parent,
+                None => Self::default(),
+            },
+        };
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct Border(bool);
+
+#[partial_derive_state]
+impl State for Border {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // Border does not depended on any other member in the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the border attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // check if the node contians a border attribute
+        let new = Self(
+            node_view
+                .attributes()
+                .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
+                .is_some(),
+        );
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let mut rdom: RealDom = RealDom::new([
+        Border::to_type_erased(),
+        TextColor::to_type_erased(),
+        Size::to_type_erased(),
+    ]);
+
+    let mut count = 0;
+
+    // intial render
+    let text_id = rdom.create_node(format!("Count: {count}")).id();
+    let mut root = rdom.get_mut(rdom.root_id()).unwrap();
+    // set the color to red
+    if let NodeTypeMut::Element(mut element) = root.node_type_mut() {
+        element.set_attribute(("color", "style"), "red".to_string());
+    }
+    root.add_child(text_id);
+
+    let mut ctx = SendAnyMap::new();
+    // set the font size to 3.3
+    ctx.insert(FontSize(3.3));
+    // update the State for nodes in the real_dom tree
+    let _to_rerender = rdom.update_state(ctx);
+
+    // we need to run the vdom in a async runtime
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?
+        .block_on(async {
+            loop {
+                // update the count
+                count += 1;
+                let mut text = rdom.get_mut(text_id).unwrap();
+                if let NodeTypeMut::Text(mut text) = text.node_type_mut() {
+                    *text = format!("Count: {count}");
+                }
+
+                let mut ctx = SendAnyMap::new();
+                ctx.insert(FontSize(3.3));
+                let _to_rerender = rdom.update_state(ctx);
+
+                // render...
+                rdom.traverse_depth_first(|node| {
+                    let indent = " ".repeat(node.height() as usize);
+                    let color = *node.get::<TextColor>().unwrap();
+                    let size = *node.get::<Size>().unwrap();
+                    let border = *node.get::<Border>().unwrap();
+                    let id = node.id();
+                    println!("{indent}{id:?} {color:?} {size:?} {border:?}");
+                });
+
+                // wait 1 second
+                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+            }
+        })
+}

+ 249 - 0
packages/native-core/examples/simple_dioxus.rs

@@ -0,0 +1,249 @@
+#![allow(non_snake_case)]
+use dioxus::prelude::*;
+use dioxus_native_core::exports::shipyard::Component;
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core_macro::partial_derive_state;
+
+struct FontSize(f64);
+
+// All states need to derive Component
+#[derive(Default, Debug, Copy, Clone, Component)]
+struct Size(f64, f64);
+
+/// Derive some of the boilerplate for the State implementation
+#[partial_derive_state]
+impl State for Size {
+    type ParentDependencies = ();
+
+    // The size of the current node depends on the size of its children
+    type ChildDependencies = (Self,);
+
+    type NodeDependencies = ();
+
+    // Size only cares about the width, height, and text parts of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        // Get access to the width and height attributes
+        .with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
+        // Get access to the text of the node
+        .with_text();
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> bool {
+        let font_size = context.get::<FontSize>().unwrap().0;
+        let mut width;
+        let mut height;
+        if let Some(text) = node_view.text() {
+            // if the node has text, use the text to size our object
+            width = text.len() as f64 * font_size;
+            height = font_size;
+        } else {
+            // otherwise, the size is the maximum size of the children
+            width = children
+                .iter()
+                .map(|(item,)| item.0)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+
+            height = children
+                .iter()
+                .map(|(item,)| item.1)
+                .reduce(|accum, item| if accum >= item { accum } else { item })
+                .unwrap_or(0.0);
+        }
+        // if the node contains a width or height attribute it overrides the other size
+        for a in node_view.attributes().into_iter().flatten() {
+            match &*a.attribute.name {
+                "width" => width = a.value.as_float().unwrap(),
+                "height" => height = a.value.as_float().unwrap(),
+                // because Size only depends on the width and height, no other attributes will be passed to the member
+                _ => panic!(),
+            }
+        }
+        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
+        let changed = (width != self.0) || (height != self.1);
+        *self = Self(width, height);
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct TextColor {
+    r: u8,
+    g: u8,
+    b: u8,
+}
+
+#[partial_derive_state]
+impl State for TextColor {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // TextColor only cares about the color attribute of the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the color attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
+        let new = match node_view
+            .attributes()
+            .and_then(|mut attrs| attrs.next())
+            .and_then(|attr| attr.value.as_text())
+        {
+            // if there is a color tag, translate it
+            Some("red") => TextColor { r: 255, g: 0, b: 0 },
+            Some("green") => TextColor { r: 0, g: 255, b: 0 },
+            Some("blue") => TextColor { r: 0, g: 0, b: 255 },
+            Some(color) => panic!("unknown color {color}"),
+            // otherwise check if the node has a parent and inherit that color
+            None => match parent {
+                Some((parent,)) => *parent,
+                None => Self::default(),
+            },
+        };
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
+struct Border(bool);
+
+#[partial_derive_state]
+impl State for Border {
+    // TextColor depends on the TextColor part of the parent
+    type ParentDependencies = (Self,);
+
+    type ChildDependencies = ();
+
+    type NodeDependencies = ();
+
+    // Border does not depended on any other member in the current node
+    const NODE_MASK: NodeMaskBuilder<'static> =
+        // Get access to the border attribute
+        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
+
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<()>,
+        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _context: &SendAnyMap,
+    ) -> bool {
+        // check if the node contians a border attribute
+        let new = Self(
+            node_view
+                .attributes()
+                .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
+                .is_some(),
+        );
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    fn app(cx: Scope) -> Element {
+        let count = use_state(cx, || 0);
+
+        use_future(cx, (count,), |(count,)| async move {
+            loop {
+                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+                count.set(*count + 1);
+            }
+        });
+
+        cx.render(rsx! {
+            div{
+                color: "red",
+                "{count}",
+                Comp {}
+            }
+        })
+    }
+
+    fn Comp(cx: Scope) -> Element {
+        cx.render(rsx! {
+            div{
+                border: "",
+                "hello world"
+            }
+        })
+    }
+
+    // create the vdom, the real_dom, and the binding layer between them
+    let mut vdom = VirtualDom::new(app);
+    let mut rdom: RealDom = RealDom::new([
+        Border::to_type_erased(),
+        TextColor::to_type_erased(),
+        Size::to_type_erased(),
+    ]);
+    let mut dioxus_intigration_state = DioxusState::create(&mut rdom);
+
+    let mutations = vdom.rebuild();
+    // update the structure of the real_dom tree
+    dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
+    let mut ctx = SendAnyMap::new();
+    // set the font size to 3.3
+    ctx.insert(FontSize(3.3));
+    // update the State for nodes in the real_dom tree
+    let _to_rerender = rdom.update_state(ctx);
+
+    // we need to run the vdom in a async runtime
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?
+        .block_on(async {
+            loop {
+                // wait for the vdom to update
+                vdom.wait_for_work().await;
+
+                // get the mutations from the vdom
+                let mutations = vdom.render_immediate();
+
+                // update the structure of the real_dom tree
+                dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
+
+                // update the state of the real_dom tree
+                let mut ctx = SendAnyMap::new();
+                // set the font size to 3.3
+                ctx.insert(FontSize(3.3));
+                let _to_rerender = rdom.update_state(ctx);
+
+                // render...
+                rdom.traverse_depth_first(|node| {
+                    let indent = " ".repeat(node.height() as usize);
+                    let color = *node.get::<TextColor>().unwrap();
+                    let size = *node.get::<Size>().unwrap();
+                    let border = *node.get::<Border>().unwrap();
+                    let id = node.id();
+                    let node = node.node_type();
+                    let node_type = &*node;
+                    println!("{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}");
+                });
+            }
+        })
+}

+ 296 - 0
packages/native-core/src/dioxus.rs

@@ -0,0 +1,296 @@
+//! Integration between Dioxus and the RealDom
+
+use crate::tree::TreeMut;
+use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode};
+use rustc_hash::{FxHashMap, FxHashSet};
+use shipyard::Component;
+
+use crate::{
+    node::{
+        ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue,
+        TextNode,
+    },
+    prelude::*,
+    real_dom::NodeTypeMut,
+    NodeId,
+};
+
+#[derive(Component)]
+struct ElementIdComponent(ElementId);
+
+/// The state of the Dioxus integration with the RealDom
+pub struct DioxusState {
+    templates: FxHashMap<String, Vec<NodeId>>,
+    stack: Vec<NodeId>,
+    node_id_mapping: Vec<Option<NodeId>>,
+}
+
+impl DioxusState {
+    /// Initialize the DioxusState in the RealDom
+    pub fn create<V: FromAnyValue + Send + Sync>(rdom: &mut RealDom<V>) -> Self {
+        let root_id = rdom.root_id();
+        let mut root = rdom.get_mut(root_id).unwrap();
+        root.insert(ElementIdComponent(ElementId(0)));
+        Self {
+            templates: FxHashMap::default(),
+            stack: vec![root_id],
+            node_id_mapping: vec![Some(root_id)],
+        }
+    }
+
+    /// Convert an ElementId to a NodeId
+    pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId {
+        self.try_element_to_node_id(element_id).unwrap()
+    }
+
+    /// Attempt to convert an ElementId to a NodeId. This will return None if the ElementId is not in the RealDom.
+    pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option<NodeId> {
+        self.node_id_mapping.get(element_id.0).copied().flatten()
+    }
+
+    fn set_element_id<V: FromAnyValue + Send + Sync>(
+        &mut self,
+        mut node: NodeMut<V>,
+        element_id: ElementId,
+    ) {
+        let node_id = node.id();
+        node.insert(ElementIdComponent(element_id));
+        if self.node_id_mapping.len() <= element_id.0 {
+            self.node_id_mapping.resize(element_id.0 + 1, None);
+        }
+        self.node_id_mapping[element_id.0] = Some(node_id);
+    }
+
+    fn load_child<V: FromAnyValue + Send + Sync>(&self, rdom: &RealDom<V>, path: &[u8]) -> NodeId {
+        let mut current = rdom.get(*self.stack.last().unwrap()).unwrap();
+        for i in path {
+            let new_id = current.child_ids()[*i as usize];
+            current = rdom.get(new_id).unwrap();
+        }
+        current.id()
+    }
+
+    /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state.
+    pub fn apply_mutations<V: FromAnyValue + Send + Sync>(
+        &mut self,
+        rdom: &mut RealDom<V>,
+        mutations: Mutations,
+    ) {
+        for template in mutations.templates {
+            let mut template_root_ids = Vec::new();
+            for root in template.roots {
+                let id = create_template_node(rdom, root);
+                template_root_ids.push(id);
+            }
+            self.templates
+                .insert(template.name.to_string(), template_root_ids);
+        }
+
+        for e in mutations.edits {
+            use dioxus_core::Mutation::*;
+            match e {
+                AppendChildren { id, m } => {
+                    let children = self.stack.split_off(self.stack.len() - m);
+                    let parent = self.element_to_node_id(id);
+                    for child in children {
+                        rdom.get_mut(parent).unwrap().add_child(child);
+                    }
+                }
+                AssignId { path, id } => {
+                    let node_id = self.load_child(rdom, path);
+                    self.set_element_id(rdom.get_mut(node_id).unwrap(), id);
+                }
+                CreatePlaceholder { id } => {
+                    let node = NodeType::Placeholder;
+                    let node = rdom.create_node(node);
+                    let node_id = node.id();
+                    self.set_element_id(node, id);
+                    self.stack.push(node_id);
+                }
+                CreateTextNode { value, id } => {
+                    let node_data = NodeType::Text(TextNode {
+                        listeners: FxHashSet::default(),
+                        text: value.to_string(),
+                    });
+                    let node = rdom.create_node(node_data);
+                    let node_id = node.id();
+                    self.set_element_id(node, id);
+                    self.stack.push(node_id);
+                }
+                HydrateText { path, value, id } => {
+                    let node_id = self.load_child(rdom, path);
+                    let node = rdom.get_mut(node_id).unwrap();
+                    self.set_element_id(node, id);
+                    let mut node = rdom.get_mut(node_id).unwrap();
+                    let node_type_mut = node.node_type_mut();
+                    if let NodeTypeMut::Text(mut text) = node_type_mut {
+                        *text.text_mut() = value.to_string();
+                    } else {
+                        drop(node_type_mut);
+                        node.set_type(NodeType::Text(TextNode {
+                            text: value.to_string(),
+                            listeners: FxHashSet::default(),
+                        }));
+                    }
+                }
+                LoadTemplate { name, index, id } => {
+                    let template_id = self.templates[name][index];
+                    let clone_id = rdom.get_mut(template_id).unwrap().clone_node();
+                    let clone = rdom.get_mut(clone_id).unwrap();
+                    self.set_element_id(clone, id);
+                    self.stack.push(clone_id);
+                }
+                ReplaceWith { id, m } => {
+                    let new_nodes = self.stack.split_off(self.stack.len() - m);
+                    let old_node_id = self.element_to_node_id(id);
+                    for new in new_nodes {
+                        let mut node = rdom.get_mut(new).unwrap();
+                        node.insert_before(old_node_id);
+                    }
+                    rdom.get_mut(old_node_id).unwrap().remove();
+                }
+                ReplacePlaceholder { path, m } => {
+                    let new_nodes = self.stack.split_off(self.stack.len() - m);
+                    let old_node_id = self.load_child(rdom, path);
+                    for new in new_nodes {
+                        let mut node = rdom.get_mut(new).unwrap();
+                        node.insert_before(old_node_id);
+                    }
+                    rdom.get_mut(old_node_id).unwrap().remove();
+                }
+                InsertAfter { id, m } => {
+                    let new_nodes = self.stack.split_off(self.stack.len() - m);
+                    let old_node_id = self.element_to_node_id(id);
+                    for new in new_nodes.into_iter().rev() {
+                        let mut node = rdom.get_mut(new).unwrap();
+                        node.insert_after(old_node_id);
+                    }
+                }
+                InsertBefore { id, m } => {
+                    let new_nodes = self.stack.split_off(self.stack.len() - m);
+                    let old_node_id = self.element_to_node_id(id);
+                    for new in new_nodes {
+                        rdom.tree_mut().insert_before(old_node_id, new);
+                    }
+                }
+                SetAttribute {
+                    name,
+                    value,
+                    id,
+                    ns,
+                } => {
+                    let node_id = self.element_to_node_id(id);
+                    let mut node = rdom.get_mut(node_id).unwrap();
+                    let mut node_type_mut = node.node_type_mut();
+                    if let NodeTypeMut::Element(element) = &mut node_type_mut {
+                        if let BorrowedAttributeValue::None = &value {
+                            element.remove_attribute(&OwnedAttributeDiscription {
+                                name: name.to_string(),
+                                namespace: ns.map(|s| s.to_string()),
+                            });
+                        } else {
+                            element.set_attribute(
+                                OwnedAttributeDiscription {
+                                    name: name.to_string(),
+                                    namespace: ns.map(|s| s.to_string()),
+                                },
+                                OwnedAttributeValue::from(value),
+                            );
+                        }
+                    }
+                }
+                SetText { value, id } => {
+                    let node_id = self.element_to_node_id(id);
+                    let mut node = rdom.get_mut(node_id).unwrap();
+                    let node_type_mut = node.node_type_mut();
+                    if let NodeTypeMut::Text(mut text) = node_type_mut {
+                        *text.text_mut() = value.to_string();
+                    }
+                }
+                NewEventListener { name, id } => {
+                    let node_id = self.element_to_node_id(id);
+                    let mut node = rdom.get_mut(node_id).unwrap();
+                    node.add_event_listener(name);
+                }
+                RemoveEventListener { id, name } => {
+                    let node_id = self.element_to_node_id(id);
+                    let mut node = rdom.get_mut(node_id).unwrap();
+                    node.remove_event_listener(name);
+                }
+                Remove { id } => {
+                    let node_id = self.element_to_node_id(id);
+                    rdom.get_mut(node_id).unwrap().remove();
+                }
+                PushRoot { id } => {
+                    let node_id = self.element_to_node_id(id);
+                    self.stack.push(node_id);
+                }
+            }
+        }
+    }
+}
+
+fn create_template_node<V: FromAnyValue + Send + Sync>(
+    rdom: &mut RealDom<V>,
+    node: &TemplateNode,
+) -> NodeId {
+    match node {
+        TemplateNode::Element {
+            tag,
+            namespace,
+            attrs,
+            children,
+        } => {
+            let node = NodeType::Element(ElementNode {
+                tag: tag.to_string(),
+                namespace: namespace.map(|s| s.to_string()),
+                attributes: attrs
+                    .iter()
+                    .filter_map(|attr| match attr {
+                        dioxus_core::TemplateAttribute::Static {
+                            name,
+                            value,
+                            namespace,
+                        } => Some((
+                            OwnedAttributeDiscription {
+                                namespace: namespace.map(|s| s.to_string()),
+                                name: name.to_string(),
+                            },
+                            OwnedAttributeValue::Text(value.to_string()),
+                        )),
+                        dioxus_core::TemplateAttribute::Dynamic { .. } => None,
+                    })
+                    .collect(),
+                listeners: FxHashSet::default(),
+            });
+            let node_id = rdom.create_node(node).id();
+            for child in *children {
+                let child_id = create_template_node(rdom, child);
+                rdom.get_mut(node_id).unwrap().add_child(child_id);
+            }
+            node_id
+        }
+        TemplateNode::Text { text } => rdom
+            .create_node(NodeType::Text(TextNode {
+                text: text.to_string(),
+                ..Default::default()
+            }))
+            .id(),
+        TemplateNode::Dynamic { .. } => rdom.create_node(NodeType::Placeholder).id(),
+        TemplateNode::DynamicText { .. } => {
+            rdom.create_node(NodeType::Text(TextNode::default())).id()
+        }
+    }
+}
+
+/// A trait that extends the `NodeImmutable` trait with methods that are useful for dioxus.
+pub trait NodeImmutableDioxusExt<V: FromAnyValue + Send + Sync>: NodeImmutable<V> {
+    /// Returns the id of the element that this node is mounted to.
+    /// Not all nodes are mounted to an element, only nodes with dynamic content that have been renderered will have an id.
+    fn mounted_id(&self) -> Option<ElementId> {
+        let id = self.get::<ElementIdComponent>();
+        id.map(|id| id.0)
+    }
+}
+
+impl<T: NodeImmutable<V>, V: FromAnyValue + Send + Sync> NodeImmutableDioxusExt<V> for T {}

+ 4 - 0
packages/native-core/src/layout_attributes.rs

@@ -1,3 +1,5 @@
+//! Utility functions for applying layout attributes to taffy layout
+
 /*
 - [ ] pub display: Display, ----> taffy doesnt support all display types
 - [x] pub position_type: PositionType,  --> taffy doesnt support everything
@@ -46,12 +48,14 @@ use taffy::{
     style::{FlexDirection, PositionType},
 };
 
+/// Default values for layout attributes
 #[derive(Default)]
 pub struct LayoutConfigeration {
     /// the default border widths to use
     pub border_widths: BorderWidths,
 }
 
+/// Default border widths
 pub struct BorderWidths {
     /// the default border width to use for thin borders
     pub thin: f32,

+ 33 - 35
packages/native-core/src/lib.rs

@@ -1,51 +1,49 @@
+#![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
+
 use std::any::Any;
 use std::hash::BuildHasherDefault;
 
-pub use node_ref::NodeMask;
-pub use passes::{
-    AnyPass, DownwardPass, MemberMask, NodePass, Pass, PassId, PassReturn, UpwardPass,
-};
+use node_ref::NodeMask;
 use rustc_hash::FxHasher;
-pub use tree::NodeId;
 
+#[cfg(feature = "dioxus")]
+pub mod dioxus;
 pub mod layout_attributes;
 pub mod node;
 pub mod node_ref;
-pub mod passes;
+pub mod node_watcher;
+mod passes;
 pub mod real_dom;
-pub mod state;
 pub mod tree;
 pub mod utils;
+pub use shipyard::EntityId as NodeId;
 
-/// A id for a node that lives in the real dom.
-pub type RealNodeId = NodeId;
-pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
-pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
-pub type SendAnyMap = anymap::Map<dyn Any + Send + Sync + 'static>;
-
-/// Used in derived state macros
-#[derive(Eq, PartialEq)]
-#[doc(hidden)]
-pub struct HeightOrdering {
-    pub height: u16,
-    pub id: RealNodeId,
+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
+    // exported for the macro
+    #[doc(hidden)]
+    pub use rustc_hash::FxHashSet;
+    pub use shipyard;
 }
 
-impl HeightOrdering {
-    pub fn new(height: u16, id: RealNodeId) -> Self {
-        HeightOrdering { height, id }
-    }
+/// A prelude of commonly used items
+pub mod prelude {
+    #[cfg(feature = "dioxus")]
+    pub use crate::dioxus::*;
+    pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode};
+    pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView};
+    pub use crate::passes::{run_pass, PassDirection, RunPassView, TypeErasedState};
+    pub use crate::passes::{Dependancy, DependancyView, Dependants, State};
+    pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom};
+    pub use crate::NodeId;
+    pub use crate::SendAnyMap;
 }
 
-// not the ordering after height is just for deduplication it can be any ordering as long as it is consistent
-impl Ord for HeightOrdering {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.height.cmp(&other.height).then(self.id.cmp(&other.id))
-    }
-}
-
-impl PartialOrd for HeightOrdering {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
+/// A map that can be sent between threads
+pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
+/// A set that can be sent between threads
+pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
+/// A map of types that can be sent between threads
+pub type SendAnyMap = anymap::Map<dyn Any + Send + Sync + 'static>;

+ 160 - 54
packages/native-core/src/node.rs

@@ -1,65 +1,113 @@
-use crate::{state::State, tree::NodeId};
-use dioxus_core::{AnyValue, BorrowedAttributeValue, ElementId};
+//! Items related to Nodes in the RealDom
+
 use rustc_hash::{FxHashMap, FxHashSet};
-use std::fmt::Debug;
+use shipyard::Component;
+use std::{
+    any::Any,
+    fmt::{Debug, Display},
+};
+
+/// A element node in the RealDom
+#[derive(Debug, Clone, Default)]
+pub struct ElementNode<V: FromAnyValue = ()> {
+    /// The [tag](https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName) of the element
+    pub tag: String,
+    /// The [namespace](https://developer.mozilla.org/en-US/docs/Web/API/Element/namespaceURI) of the element
+    pub namespace: Option<String>,
+    /// The attributes of the element
+    pub attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
+    /// The events the element is listening for
+    pub listeners: FxHashSet<String>,
+}
 
-/// The node is stored client side and stores only basic data about the node.
-#[derive(Debug, Clone)]
-pub struct Node<S: State<V>, V: FromAnyValue + 'static = ()> {
-    /// The transformed state of the node.
-    pub state: S,
-    /// The raw data for the node
-    pub node_data: NodeData<V>,
+impl ElementNode {
+    /// Create a new element node
+    pub fn new(tag: impl Into<String>, namespace: impl Into<Option<String>>) -> Self {
+        Self {
+            tag: tag.into(),
+            namespace: namespace.into(),
+            attributes: Default::default(),
+            listeners: Default::default(),
+        }
+    }
 }
 
-#[derive(Debug, Clone)]
-pub struct NodeData<V: FromAnyValue = ()> {
-    /// The id of the node
-    pub node_id: NodeId,
-    /// The id of the node in the vdom.
-    pub element_id: Option<ElementId>,
-    /// Additional inforation specific to the node type
-    pub node_type: NodeType<V>,
+/// A text node in the RealDom
+#[derive(Debug, Clone, Default)]
+pub struct TextNode {
+    /// The text of the node
+    pub text: String,
+    /// The events the node is listening for
+    pub listeners: FxHashSet<String>,
 }
 
-/// A type of node with data specific to the node type. The types are a subset of the [VNode] types.
-#[derive(Debug, Clone)]
+impl TextNode {
+    /// Create a new text node
+    pub fn new(text: String) -> Self {
+        Self {
+            text,
+            listeners: Default::default(),
+        }
+    }
+}
+
+/// A type of node with data specific to the node type.
+#[derive(Debug, Clone, Component)]
 pub enum NodeType<V: FromAnyValue = ()> {
-    Text {
-        text: String,
-    },
-    Element {
-        tag: String,
-        namespace: Option<String>,
-        attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
-        listeners: FxHashSet<String>,
-    },
+    /// A text node
+    Text(TextNode),
+    /// An element node
+    Element(ElementNode<V>),
+    /// A placeholder node. This can be used as a cheaper placeholder for a node that will be created later
     Placeholder,
 }
 
-impl<S: State<V>, V: FromAnyValue> Node<S, V> {
-    pub(crate) fn new(node_type: NodeType<V>) -> Self {
-        Node {
-            state: S::default(),
-            node_data: NodeData {
-                element_id: None,
-                node_type,
-                node_id: NodeId(0),
-            },
-        }
+impl<V: FromAnyValue, S: Into<String>> From<S> for NodeType<V> {
+    fn from(text: S) -> Self {
+        Self::Text(TextNode::new(text.into()))
     }
+}
+
+impl<V: FromAnyValue> From<TextNode> for NodeType<V> {
+    fn from(text: TextNode) -> Self {
+        Self::Text(text)
+    }
+}
 
-    /// get the mounted id of the node
-    pub fn mounted_id(&self) -> Option<ElementId> {
-        self.node_data.element_id
+impl<V: FromAnyValue> From<ElementNode<V>> for NodeType<V> {
+    fn from(element: ElementNode<V>) -> Self {
+        Self::Element(element)
     }
 }
 
+/// A discription of an attribute on a DOM node, such as `id` or `href`.
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct OwnedAttributeDiscription {
+    /// The name of the attribute.
     pub name: String,
+    /// The namespace of the attribute used to identify what kind of attribute it is.
+    ///
+    /// For renderers that use HTML, this can be used to identify if the attribute is a style attribute.
+    /// Instead of parsing the style attribute every time a style is changed, you can set an attribute with the `style` namespace.
     pub namespace: Option<String>,
-    pub volatile: bool,
+}
+
+impl From<String> for OwnedAttributeDiscription {
+    fn from(name: String) -> Self {
+        Self {
+            name,
+            namespace: None,
+        }
+    }
+}
+
+impl<S: Into<String>, N: Into<String>> From<(S, N)> for OwnedAttributeDiscription {
+    fn from(name: (S, N)) -> Self {
+        Self {
+            name: name.0.into(),
+            namespace: Some(name.1.into()),
+        }
+    }
 }
 
 /// An attribute on a DOM node, such as `id="my-thing"` or
@@ -73,21 +121,59 @@ pub struct OwnedAttributeView<'a, V: FromAnyValue = ()> {
     pub value: &'a OwnedAttributeValue<V>,
 }
 
+/// The value of an attribute on a DOM node. This contains non-text values to allow users to skip parsing attribute values in some cases.
 #[derive(Clone)]
 pub enum OwnedAttributeValue<V: FromAnyValue = ()> {
+    /// A string value. This is the most common type of attribute.
     Text(String),
+    /// A floating point value.
     Float(f64),
+    /// An integer value.
     Int(i64),
+    /// A boolean value.
     Bool(bool),
+    /// A custom value specific to the renderer
     Custom(V),
 }
 
-pub trait FromAnyValue: Clone {
-    fn from_any_value(value: &dyn AnyValue) -> Self;
+impl<V: FromAnyValue> From<String> for OwnedAttributeValue<V> {
+    fn from(value: String) -> Self {
+        Self::Text(value)
+    }
+}
+
+impl<V: FromAnyValue> From<f64> for OwnedAttributeValue<V> {
+    fn from(value: f64) -> Self {
+        Self::Float(value)
+    }
+}
+
+impl<V: FromAnyValue> From<i64> for OwnedAttributeValue<V> {
+    fn from(value: i64) -> Self {
+        Self::Int(value)
+    }
+}
+
+impl<V: FromAnyValue> From<bool> for OwnedAttributeValue<V> {
+    fn from(value: bool) -> Self {
+        Self::Bool(value)
+    }
+}
+
+impl<V: FromAnyValue> From<V> for OwnedAttributeValue<V> {
+    fn from(value: V) -> Self {
+        Self::Custom(value)
+    }
+}
+
+/// Something that can be converted from a borrowed [Any] value.
+pub trait FromAnyValue: Clone + 'static {
+    /// Convert from an [Any] value.
+    fn from_any_value(value: &dyn Any) -> Self;
 }
 
 impl FromAnyValue for () {
-    fn from_any_value(_: &dyn AnyValue) -> Self {}
+    fn from_any_value(_: &dyn Any) -> Self {}
 }
 
 impl<V: FromAnyValue> Debug for OwnedAttributeValue<V> {
@@ -102,20 +188,34 @@ impl<V: FromAnyValue> Debug for OwnedAttributeValue<V> {
     }
 }
 
-impl<V: FromAnyValue> From<BorrowedAttributeValue<'_>> for OwnedAttributeValue<V> {
-    fn from(value: BorrowedAttributeValue<'_>) -> Self {
+impl<V: FromAnyValue> Display for OwnedAttributeValue<V> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Text(arg0) => f.write_str(arg0),
+            Self::Float(arg0) => f.write_str(&arg0.to_string()),
+            Self::Int(arg0) => f.write_str(&arg0.to_string()),
+            Self::Bool(arg0) => f.write_str(&arg0.to_string()),
+            Self::Custom(_) => f.write_str("custom"),
+        }
+    }
+}
+
+#[cfg(feature = "dioxus")]
+impl<V: FromAnyValue> From<dioxus_core::BorrowedAttributeValue<'_>> for OwnedAttributeValue<V> {
+    fn from(value: dioxus_core::BorrowedAttributeValue<'_>) -> Self {
         match value {
-            BorrowedAttributeValue::Text(text) => Self::Text(text.to_string()),
-            BorrowedAttributeValue::Float(float) => Self::Float(float),
-            BorrowedAttributeValue::Int(int) => Self::Int(int),
-            BorrowedAttributeValue::Bool(bool) => Self::Bool(bool),
-            BorrowedAttributeValue::Any(any) => Self::Custom(V::from_any_value(&*any)),
-            BorrowedAttributeValue::None => panic!("None attribute values result in removing the attribute, not converting it to a None value.")
+            dioxus_core::BorrowedAttributeValue::Text(text) => Self::Text(text.to_string()),
+            dioxus_core::BorrowedAttributeValue::Float(float) => Self::Float(float),
+            dioxus_core::BorrowedAttributeValue::Int(int) => Self::Int(int),
+            dioxus_core::BorrowedAttributeValue::Bool(bool) => Self::Bool(bool),
+            dioxus_core::BorrowedAttributeValue::Any(any) => Self::Custom(V::from_any_value(any.as_any())),
+            dioxus_core::BorrowedAttributeValue::None => panic!("None attribute values result in removing the attribute, not converting it to a None value.")
         }
     }
 }
 
 impl<V: FromAnyValue> OwnedAttributeValue<V> {
+    /// Attempt to convert the attribute value to a string.
     pub fn as_text(&self) -> Option<&str> {
         match self {
             OwnedAttributeValue::Text(text) => Some(text),
@@ -123,20 +223,25 @@ impl<V: FromAnyValue> OwnedAttributeValue<V> {
         }
     }
 
+    /// Attempt to convert the attribute value to a float.
     pub fn as_float(&self) -> Option<f64> {
         match self {
             OwnedAttributeValue::Float(float) => Some(*float),
+            OwnedAttributeValue::Int(int) => Some(*int as f64),
             _ => None,
         }
     }
 
+    /// Attempt to convert the attribute value to an integer.
     pub fn as_int(&self) -> Option<i64> {
         match self {
+            OwnedAttributeValue::Float(float) => Some(*float as i64),
             OwnedAttributeValue::Int(int) => Some(*int),
             _ => None,
         }
     }
 
+    /// Attempt to convert the attribute value to a boolean.
     pub fn as_bool(&self) -> Option<bool> {
         match self {
             OwnedAttributeValue::Bool(bool) => Some(*bool),
@@ -144,6 +249,7 @@ impl<V: FromAnyValue> OwnedAttributeValue<V> {
         }
     }
 
+    /// Attempt to convert the attribute value to a custom value.
     pub fn as_custom(&self) -> Option<&V> {
         match self {
             OwnedAttributeValue::Custom(custom) => Some(custom),

+ 128 - 128
packages/native-core/src/node_ref.rs

@@ -1,43 +1,41 @@
-use dioxus_core::ElementId;
+//! Utilities that provide limited access to nodes
+
+use rustc_hash::FxHashSet;
 
 use crate::{
-    node::{FromAnyValue, NodeData, NodeType, OwnedAttributeView},
-    state::union_ordered_iter,
-    RealNodeId,
+    node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView},
+    NodeId,
 };
 
-/// A view into a [VNode] with limited access.
+/// A view into a [NodeType] with a mask that determines what is visible.
 #[derive(Debug)]
 pub struct NodeView<'a, V: FromAnyValue = ()> {
-    inner: &'a NodeData<V>,
-    mask: NodeMask,
+    id: NodeId,
+    inner: &'a NodeType<V>,
+    mask: &'a NodeMask,
 }
 
 impl<'a, V: FromAnyValue> NodeView<'a, V> {
     /// Create a new NodeView from a VNode, and mask.
-    pub fn new(node: &'a NodeData<V>, view: NodeMask) -> Self {
+    pub fn new(id: NodeId, node: &'a NodeType<V>, view: &'a NodeMask) -> Self {
         Self {
             inner: node,
             mask: view,
+            id,
         }
     }
 
-    /// Get the id of the node
-    pub fn id(&self) -> Option<ElementId> {
-        self.inner.element_id
-    }
-
     /// Get the node id of the node
-    pub fn node_id(&self) -> RealNodeId {
-        self.inner.node_id
+    pub fn node_id(&self) -> NodeId {
+        self.id
     }
 
     /// Get the tag of the node if the tag is enabled in the mask
     pub fn tag(&self) -> Option<&'a str> {
         self.mask
             .tag
-            .then_some(match &self.inner.node_type {
-                NodeType::Element { tag, .. } => Some(&**tag),
+            .then_some(match &self.inner {
+                NodeType::Element(ElementNode { tag, .. }) => Some(&**tag),
                 _ => None,
             })
             .flatten()
@@ -47,8 +45,8 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
     pub fn namespace(&self) -> Option<&'a str> {
         self.mask
             .namespace
-            .then_some(match &self.inner.node_type {
-                NodeType::Element { namespace, .. } => namespace.as_deref(),
+            .then_some(match &self.inner {
+                NodeType::Element(ElementNode { namespace, .. }) => namespace.as_deref(),
                 _ => None,
             })
             .flatten()
@@ -58,8 +56,8 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
     pub fn attributes<'b>(
         &'b self,
     ) -> Option<impl Iterator<Item = OwnedAttributeView<'a, V>> + 'b> {
-        match &self.inner.node_type {
-            NodeType::Element { attributes, .. } => Some(
+        match &self.inner {
+            NodeType::Element(ElementNode { attributes, .. }) => Some(
                 attributes
                     .iter()
                     .filter(move |(attr, _)| self.mask.attritutes.contains_attribute(&attr.name))
@@ -76,18 +74,21 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
     pub fn text(&self) -> Option<&str> {
         self.mask
             .text
-            .then_some(match &self.inner.node_type {
-                NodeType::Text { text } => Some(&**text),
+            .then_some(match &self.inner {
+                NodeType::Text(text) => Some(&text.text),
                 _ => None,
             })
             .flatten()
+            .map(|x| &**x)
     }
 
     /// Get the listeners if it is enabled in the mask
     pub fn listeners(&self) -> Option<impl Iterator<Item = &'a str> + '_> {
         if self.mask.listeners {
-            match &self.inner.node_type {
-                NodeType::Element { listeners, .. } => Some(listeners.iter().map(|l| &**l)),
+            match &self.inner {
+                NodeType::Element(ElementNode { listeners, .. }) => {
+                    Some(listeners.iter().map(|l| &**l))
+                }
                 _ => None,
             }
         } else {
@@ -99,125 +100,54 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> {
 /// A mask that contains a list of attributes that are visible.
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub enum AttributeMask {
+    /// All attributes are visible
     All,
-    /// A list of attribute names that are visible, this list must be sorted
-    Dynamic(Vec<String>),
-    /// A list of attribute names that are visible, this list must be sorted
-    Static(&'static [&'static str]),
+    /// Only the given attributes are visible
+    Some(FxHashSet<Box<str>>),
 }
 
 impl AttributeMask {
-    /// A empty attribute mask
-    pub const NONE: Self = Self::Static(&[]);
-
+    /// Check if the given attribute is visible
     fn contains_attribute(&self, attr: &str) -> bool {
         match self {
             AttributeMask::All => true,
-            AttributeMask::Dynamic(l) => l.binary_search_by_key(&attr, |s| s.as_str()).is_ok(),
-            AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
+            AttributeMask::Some(attrs) => attrs.contains(attr),
         }
     }
 
     /// Create a new dynamic attribute mask with a single attribute
     pub fn single(new: &str) -> Self {
-        Self::Dynamic(vec![new.to_string()])
-    }
-
-    /// Ensure the attribute list is sorted.
-    pub fn verify(&self) {
-        match &self {
-            AttributeMask::Static(attrs) => debug_assert!(
-                attrs.windows(2).all(|w| w[0] < w[1]),
-                "attritutes must be increasing"
-            ),
-            AttributeMask::Dynamic(attrs) => debug_assert!(
-                attrs.windows(2).all(|w| w[0] < w[1]),
-                "attritutes must be increasing"
-            ),
-            _ => (),
-        }
+        let mut set = FxHashSet::default();
+        set.insert(new.into());
+        Self::Some(set)
     }
 
     /// Combine two attribute masks
     pub fn union(&self, other: &Self) -> Self {
-        let new = match (self, other) {
-            (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => {
-                AttributeMask::Dynamic(union_ordered_iter(
-                    s.iter().map(|s| s.as_str()),
-                    o.iter().map(|s| s.as_str()),
-                    s.len() + o.len(),
-                ))
-            }
-            (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => {
-                AttributeMask::Dynamic(union_ordered_iter(
-                    s.iter().copied(),
-                    o.iter().map(|s| s.as_str()),
-                    s.len() + o.len(),
-                ))
-            }
-            (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => {
-                AttributeMask::Dynamic(union_ordered_iter(
-                    s.iter().map(|s| s.as_str()),
-                    o.iter().copied(),
-                    s.len() + o.len(),
-                ))
+        match (self, other) {
+            (AttributeMask::Some(s), AttributeMask::Some(o)) => {
+                AttributeMask::Some(s.union(o).cloned().collect())
             }
-            (AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
-                union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
-            ),
             _ => AttributeMask::All,
-        };
-        new.verify();
-        new
+        }
     }
 
     /// Check if two attribute masks overlap
     fn overlaps(&self, other: &Self) -> bool {
-        fn overlaps_iter<'a>(
-            self_iter: impl Iterator<Item = &'a str>,
-            mut other_iter: impl Iterator<Item = &'a str>,
-        ) -> bool {
-            if let Some(mut other_attr) = other_iter.next() {
-                for self_attr in self_iter {
-                    while other_attr < self_attr {
-                        if let Some(attr) = other_iter.next() {
-                            other_attr = attr;
-                        } else {
-                            return false;
-                        }
-                    }
-                    if other_attr == self_attr {
-                        return true;
-                    }
-                }
-            }
-            false
-        }
         match (self, other) {
-            (AttributeMask::All, AttributeMask::All) => true,
-            (AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
-            (AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
-            (AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
-            (AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
-            (AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
-                overlaps_iter(v1.iter().map(|s| s.as_str()), v2.iter().map(|s| s.as_str()))
-            }
-            (AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
-                overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied())
-            }
-            (AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
-                overlaps_iter(v.iter().map(|s| s.as_str()), s.iter().copied())
-            }
-            (AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
-                overlaps_iter(s1.iter().copied(), s2.iter().copied())
+            (AttributeMask::All, AttributeMask::Some(attrs)) => !attrs.is_empty(),
+            (AttributeMask::Some(attrs), AttributeMask::All) => !attrs.is_empty(),
+            (AttributeMask::Some(attrs1), AttributeMask::Some(attrs2)) => {
+                !attrs1.is_disjoint(attrs2)
             }
+            _ => true,
         }
     }
 }
 
 impl Default for AttributeMask {
     fn default() -> Self {
-        AttributeMask::Static(&[])
+        AttributeMask::Some(FxHashSet::default())
     }
 }
 
@@ -232,14 +162,6 @@ pub struct NodeMask {
 }
 
 impl NodeMask {
-    /// A node mask with no parts visible.
-    pub const NONE: Self = Self::new();
-    /// A node mask with every part visible.
-    pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
-        .with_text()
-        .with_element()
-        .with_listeners();
-
     /// Check if two masks overlap
     pub fn overlaps(&self, other: &Self) -> bool {
         (self.tag && other.tag)
@@ -260,10 +182,71 @@ impl NodeMask {
         }
     }
 
-    /// Create a new node mask with the given attributes
-    pub const fn new_with_attrs(attritutes: AttributeMask) -> Self {
+    /// Allow the mask to view the given attributes
+    pub fn add_attributes(&mut self, attributes: AttributeMask) {
+        self.attritutes = self.attritutes.union(&attributes);
+    }
+
+    /// Set the mask to view the tag
+    pub fn set_tag(&mut self) {
+        self.tag = true;
+    }
+
+    /// Set the mask to view the namespace
+    pub fn set_namespace(&mut self) {
+        self.namespace = true;
+    }
+
+    /// Set the mask to view the text
+    pub fn set_text(&mut self) {
+        self.text = true;
+    }
+
+    /// Set the mask to view the listeners
+    pub fn set_listeners(&mut self) {
+        self.listeners = true;
+    }
+}
+
+/// A builder for a mask that controls what attributes are visible.
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum AttributeMaskBuilder<'a> {
+    /// All attributes are visible
+    All,
+    /// Only the given attributes are visible
+    Some(&'a [&'a str]),
+}
+
+impl Default for AttributeMaskBuilder<'_> {
+    fn default() -> Self {
+        AttributeMaskBuilder::Some(&[])
+    }
+}
+
+/// A mask that limits what parts of a node a dependency can see.
+#[derive(Default, PartialEq, Eq, Clone, Debug)]
+pub struct NodeMaskBuilder<'a> {
+    attritutes: AttributeMaskBuilder<'a>,
+    tag: bool,
+    namespace: bool,
+    text: bool,
+    listeners: bool,
+}
+
+impl<'a> NodeMaskBuilder<'a> {
+    /// A node mask with no parts visible.
+    pub const NONE: Self = Self::new();
+    /// A node mask with every part visible.
+    pub const ALL: Self = Self::new()
+        .with_attrs(AttributeMaskBuilder::All)
+        .with_text()
+        .with_element()
+        .with_listeners();
+
+    /// Create a empty node mask
+    pub const fn new() -> Self {
         Self {
-            attritutes,
+            attritutes: AttributeMaskBuilder::Some(&[]),
             tag: false,
             namespace: false,
             text: false,
@@ -271,9 +254,10 @@ impl NodeMask {
         }
     }
 
-    /// Create a empty node mask
-    pub const fn new() -> Self {
-        Self::new_with_attrs(AttributeMask::NONE)
+    /// Allow the mask to view the given attributes
+    pub const fn with_attrs(mut self, attritutes: AttributeMaskBuilder<'a>) -> Self {
+        self.attritutes = attritutes;
+        self
     }
 
     /// Allow the mask to view the tag
@@ -304,4 +288,20 @@ impl NodeMask {
         self.listeners = true;
         self
     }
+
+    /// Build the mask
+    pub fn build(self) -> NodeMask {
+        NodeMask {
+            attritutes: match self.attritutes {
+                AttributeMaskBuilder::All => AttributeMask::All,
+                AttributeMaskBuilder::Some(attrs) => {
+                    AttributeMask::Some(attrs.iter().map(|s| (*s).into()).collect())
+                }
+            },
+            tag: self.tag,
+            namespace: self.namespace,
+            text: self.text,
+            listeners: self.listeners,
+        }
+    }
 }

+ 17 - 0
packages/native-core/src/node_watcher.rs

@@ -0,0 +1,17 @@
+//! Helpers for watching for changes in the DOM tree.
+
+use crate::{node::FromAnyValue, prelude::*};
+
+/// A trait for watching for changes in the DOM tree.
+pub trait NodeWatcher<V: FromAnyValue + Send + Sync> {
+    /// Called after a node is added to the tree.
+    fn on_node_added(&self, _node: NodeMut<V>) {}
+    /// Called before a node is removed from the tree.
+    fn on_node_removed(&self, _node: NodeMut<V>) {}
+    /// Called after a node is moved to a new parent.
+    fn on_node_moved(&self, _node: NodeMut<V>) {}
+    // /// Called after the text content of a node is changed.
+    // fn on_text_changed(&self, _node: NodeMut<V>) {}
+    // /// Called after an attribute of an element is changed.
+    // fn on_attribute_changed(&self, _node: NodeMut<V>, attribute: &str) {}
+}

+ 267 - 818
packages/native-core/src/passes.rs

@@ -1,911 +1,360 @@
-use crate::tree::{NodeId, TreeView};
-use crate::{FxDashSet, SendAnyMap};
+use parking_lot::RwLock;
 use rustc_hash::{FxHashMap, FxHashSet};
+use shipyard::{Borrow, BorrowInfo, Component, Unique, UniqueView, View, WorkloadSystem};
+use std::any::{Any, TypeId};
 use std::collections::BTreeMap;
-use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign};
+use std::marker::PhantomData;
+use std::ops::Deref;
 use std::sync::Arc;
 
+use crate::node::{FromAnyValue, NodeType};
+use crate::node_ref::{NodeMaskBuilder, NodeView};
+use crate::real_dom::{DirtyNodesResult, SendAnyMapWrapper};
+use crate::tree::{TreeRef, TreeRefView};
+use crate::SendAnyMap;
+use crate::{NodeId, NodeMask};
+
 #[derive(Default)]
 struct DirtyNodes {
-    passes_dirty: Vec<u64>,
+    nodes_dirty: FxHashSet<NodeId>,
 }
 
 impl DirtyNodes {
-    fn add_node(&mut self, node_id: NodeId) {
-        let node_id = node_id.0;
-        let index = node_id / 64;
-        let bit = node_id % 64;
-        let encoded = 1 << bit;
-        if let Some(passes) = self.passes_dirty.get_mut(index) {
-            *passes |= encoded;
-        } else {
-            self.passes_dirty.resize(index + 1, 0);
-            self.passes_dirty[index] |= encoded;
-        }
+    pub fn add_node(&mut self, node_id: NodeId) {
+        self.nodes_dirty.insert(node_id);
     }
 
-    fn is_empty(&self) -> bool {
-        self.passes_dirty.iter().all(|dirty| *dirty == 0)
+    pub fn is_empty(&self) -> bool {
+        self.nodes_dirty.is_empty()
     }
 
-    fn pop(&mut self) -> Option<NodeId> {
-        let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?;
-        let passes = self.passes_dirty[index];
-        let node_id = passes.trailing_zeros();
-        let encoded = 1 << node_id;
-        self.passes_dirty[index] &= !encoded;
-        Some(NodeId((index * 64) + node_id as usize))
+    pub fn pop(&mut self) -> Option<NodeId> {
+        self.nodes_dirty.iter().next().copied().map(|id| {
+            self.nodes_dirty.remove(&id);
+            id
+        })
     }
 }
 
-#[derive(Default)]
+/// Tracks the dirty nodes sorted by height for each pass. We resolve passes based on the height of the node in order to avoid resolving any node twice in a pass.
+#[derive(Clone, Unique)]
 pub struct DirtyNodeStates {
-    dirty: BTreeMap<u16, FxHashMap<PassId, DirtyNodes>>,
+    dirty: Arc<FxHashMap<TypeId, RwLock<BTreeMap<u16, DirtyNodes>>>>,
 }
 
 impl DirtyNodeStates {
-    pub fn insert(&mut self, pass_id: PassId, node_id: NodeId, height: u16) {
-        if let Some(dirty) = self.dirty.get_mut(&height) {
-            if let Some(entry) = dirty.get_mut(&pass_id) {
+    pub fn with_passes(passes: impl Iterator<Item = TypeId>) -> Self {
+        Self {
+            dirty: Arc::new(
+                passes
+                    .map(|pass| (pass, RwLock::new(BTreeMap::new())))
+                    .collect(),
+            ),
+        }
+    }
+
+    pub fn insert(&self, pass_id: TypeId, node_id: NodeId, height: u16) {
+        if let Some(btree) = self.dirty.get(&pass_id) {
+            let mut write = btree.write();
+            if let Some(entry) = write.get_mut(&height) {
                 entry.add_node(node_id);
             } else {
                 let mut entry = DirtyNodes::default();
                 entry.add_node(node_id);
-                dirty.insert(pass_id, entry);
+                write.insert(height, entry);
             }
-        } else {
-            let mut entry = DirtyNodes::default();
-            entry.add_node(node_id);
-            let mut hm = FxHashMap::default();
-            hm.insert(pass_id, entry);
-            self.dirty.insert(height, hm);
         }
     }
 
-    fn pop_front(&mut self, pass_id: PassId) -> Option<(u16, NodeId)> {
-        let (&height, values) = self
-            .dirty
-            .iter_mut()
-            .find(|(_, values)| values.contains_key(&pass_id))?;
-        let dirty = values.get_mut(&pass_id)?;
-        let node_id = dirty.pop()?;
-        if dirty.is_empty() {
-            values.remove(&pass_id);
-        }
-        if values.is_empty() {
-            self.dirty.remove(&height);
+    fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
+        let mut values = self.dirty.get(&pass_id)?.write();
+        let mut value = values.first_entry()?;
+        let height = *value.key();
+        let ids = value.get_mut();
+        let id = ids.pop()?;
+        if ids.is_empty() {
+            value.remove_entry();
         }
 
-        Some((height, node_id))
+        Some((height, id))
     }
 
-    fn pop_back(&mut self, pass_id: PassId) -> Option<(u16, NodeId)> {
-        let (&height, values) = self
-            .dirty
-            .iter_mut()
-            .rev()
-            .find(|(_, values)| values.contains_key(&pass_id))?;
-        let dirty = values.get_mut(&pass_id)?;
-        let node_id = dirty.pop()?;
-        if dirty.is_empty() {
-            values.remove(&pass_id);
-        }
-        if values.is_empty() {
-            self.dirty.remove(&height);
+    fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
+        let mut values = self.dirty.get(&pass_id)?.write();
+        let mut value = values.last_entry()?;
+        let height = *value.key();
+        let ids = value.get_mut();
+        let id = ids.pop()?;
+        if ids.is_empty() {
+            value.remove_entry();
         }
 
-        Some((height, node_id))
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
-pub struct PassId(pub u64);
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]
-pub struct MemberMask(pub u64);
-
-impl MemberMask {
-    pub fn overlaps(&self, other: Self) -> bool {
-        (*self & other).0 != 0
-    }
-}
-
-impl BitAndAssign for MemberMask {
-    fn bitand_assign(&mut self, rhs: Self) {
-        self.0 &= rhs.0;
-    }
-}
-
-impl BitAnd for MemberMask {
-    type Output = Self;
-
-    fn bitand(self, rhs: Self) -> Self::Output {
-        MemberMask(self.0 & rhs.0)
+        Some((height, id))
     }
 }
 
-impl BitOrAssign for MemberMask {
-    fn bitor_assign(&mut self, rhs: Self) {
-        self.0 |= rhs.0;
+/// A state that is automatically inserted in a node with dependencies.
+pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
+    /// This is a tuple of (T: State, ..) of states read from the parent required to update this state
+    type ParentDependencies: Dependancy;
+    /// This is a tuple of (T: State, ..) of states read from the children required to update this state
+    type ChildDependencies: Dependancy;
+    /// This is a tuple of (T: State, ..) of states read from the node required to update this state
+    type NodeDependencies: Dependancy;
+    /// This is a mask of what aspects of the node are required to update this state
+    const NODE_MASK: NodeMaskBuilder<'static>;
+
+    /// Update this state in a node, returns if the state was updated
+    fn update<'a>(
+        &mut self,
+        node_view: NodeView<V>,
+        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> bool;
+
+    /// Create a new instance of this state
+    fn create<'a>(
+        node_view: NodeView<V>,
+        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> Self;
+
+    /// Create a workload system for this state
+    fn workload_system(
+        type_id: TypeId,
+        dependants: Arc<Dependants>,
+        pass_direction: PassDirection,
+    ) -> WorkloadSystem;
+
+    /// Converts to a type erased version of the trait
+    fn to_type_erased() -> TypeErasedState<V>
+    where
+        Self: Sized,
+    {
+        let node_mask = Self::NODE_MASK.build();
+        TypeErasedState {
+            this_type_id: TypeId::of::<Self>(),
+            parent_dependancies_ids: Self::ParentDependencies::type_ids()
+                .iter()
+                .copied()
+                .collect(),
+            child_dependancies_ids: Self::ChildDependencies::type_ids()
+                .iter()
+                .copied()
+                .collect(),
+            node_dependancies_ids: Self::NodeDependencies::type_ids().iter().copied().collect(),
+            dependants: Default::default(),
+            mask: node_mask,
+            pass_direction: pass_direction::<V, Self>(),
+            workload: Self::workload_system,
+            phantom: PhantomData,
+        }
     }
 }
 
-impl BitOr for MemberMask {
-    type Output = Self;
-
-    fn bitor(self, rhs: Self) -> Self::Output {
-        Self(self.0 | rhs.0)
+fn pass_direction<V: FromAnyValue + Send + Sync, S: State<V>>() -> PassDirection {
+    if S::ChildDependencies::type_ids()
+        .iter()
+        .any(|type_id| *type_id == TypeId::of::<S>())
+    {
+        PassDirection::ChildToParent
+    } else if S::ParentDependencies::type_ids()
+        .iter()
+        .any(|type_id| *type_id == TypeId::of::<S>())
+    {
+        PassDirection::ParentToChild
+    } else {
+        PassDirection::AnyOrder
     }
 }
 
-pub struct PassReturn {
-    pub progress: bool,
-    pub mark_dirty: bool,
+#[doc(hidden)]
+#[derive(Borrow, BorrowInfo)]
+pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
+    pub tree: TreeRefView<'a>,
+    pub node_type: View<'a, NodeType<V>>,
+    dirty_nodes_result: UniqueView<'a, DirtyNodesResult>,
+    node_states: UniqueView<'a, DirtyNodeStates>,
+    any_map: UniqueView<'a, SendAnyMapWrapper>,
 }
 
-pub trait Pass {
-    fn pass_id(&self) -> PassId;
-    fn dependancies(&self) -> &'static [PassId];
-    fn dependants(&self) -> &'static [PassId];
-    fn mask(&self) -> MemberMask;
-}
-
-pub trait UpwardPass<T>: Pass {
-    fn pass(
-        &self,
-        node: &mut T,
-        children: &mut dyn Iterator<Item = &mut T>,
-        ctx: &SendAnyMap,
-    ) -> PassReturn;
-}
-
-fn resolve_upward_pass<T, P: UpwardPass<T> + ?Sized>(
-    tree: &mut impl TreeView<T>,
-    pass: &P,
-    dirty_states: &mut DirtyNodeStates,
-    nodes_updated: &FxDashSet<NodeId>,
-    ctx: &SendAnyMap,
+// This is used by the macro
+/// Updates the given pass, marking any nodes that were changed
+#[doc(hidden)]
+pub fn run_pass<V: FromAnyValue + Send + Sync>(
+    type_id: TypeId,
+    dependants: Arc<Dependants>,
+    pass_direction: PassDirection,
+    view: RunPassView<V>,
+    mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool,
 ) {
-    let pass_id = pass.pass_id();
-    while let Some((height, id)) = dirty_states.pop_back(pass_id) {
-        let (node, mut children) = tree.parent_child_mut(id).unwrap();
-        let result = pass.pass(node, &mut children, ctx);
-        drop(children);
-        if result.progress || result.mark_dirty {
-            nodes_updated.insert(id);
-            if let Some(id) = tree.parent_id(id) {
-                if result.mark_dirty {
-                    for dependant in pass.dependants() {
-                        dirty_states.insert(*dependant, id, height - 1);
-                    }
-                }
-                if result.progress && height > 0 {
-                    dirty_states.insert(pass_id, id, height - 1);
+    let RunPassView {
+        tree,
+        dirty_nodes_result: nodes_updated,
+        node_states: dirty,
+        any_map: ctx,
+        ..
+    } = view;
+    let ctx = ctx.as_ref();
+    match pass_direction {
+        PassDirection::ParentToChild => {
+            while let Some((height, id)) = dirty.pop_front(type_id) {
+                if (update_node)(id, ctx) {
+                    nodes_updated.insert(id);
+                    dependants.mark_dirty(&dirty, id, &tree, height);
                 }
             }
         }
-    }
-}
-
-pub trait DownwardPass<T>: Pass {
-    fn pass(&self, node: &mut T, parent: Option<&mut T>, ctx: &SendAnyMap) -> PassReturn;
-}
-
-fn resolve_downward_pass<T, P: DownwardPass<T> + ?Sized>(
-    tree: &mut impl TreeView<T>,
-    pass: &P,
-    dirty_states: &mut DirtyNodeStates,
-    nodes_updated: &FxDashSet<NodeId>,
-    ctx: &SendAnyMap,
-) {
-    let pass_id = pass.pass_id();
-    while let Some((height, id)) = dirty_states.pop_front(pass_id) {
-        let (node, parent) = tree.node_parent_mut(id).unwrap();
-        let result = pass.pass(node, parent, ctx);
-        if result.mark_dirty {
-            nodes_updated.insert(id);
-        }
-        if result.mark_dirty || result.progress {
-            for id in tree.children_ids(id).unwrap() {
-                if result.mark_dirty {
-                    for dependant in pass.dependants() {
-                        dirty_states.insert(*dependant, *id, height + 1);
-                    }
-                }
-                if result.progress {
-                    dirty_states.insert(pass_id, *id, height + 1);
+        PassDirection::ChildToParent => {
+            while let Some((height, id)) = dirty.pop_back(type_id) {
+                if (update_node)(id, ctx) {
+                    nodes_updated.insert(id);
+                    dependants.mark_dirty(&dirty, id, &tree, height);
                 }
             }
         }
-    }
-}
-
-pub trait NodePass<T>: Pass {
-    fn pass(&self, node: &mut T, ctx: &SendAnyMap) -> bool;
-}
-
-fn resolve_node_pass<T, P: NodePass<T> + ?Sized>(
-    tree: &mut impl TreeView<T>,
-    pass: &P,
-    dirty_states: &mut DirtyNodeStates,
-    nodes_updated: &FxDashSet<NodeId>,
-    ctx: &SendAnyMap,
-) {
-    let pass_id = pass.pass_id();
-    while let Some((height, id)) = dirty_states.pop_back(pass_id) {
-        let node = tree.get_mut(id).unwrap();
-        if pass.pass(node, ctx) {
-            nodes_updated.insert(id);
-            for dependant in pass.dependants() {
-                dirty_states.insert(*dependant, id, height);
+        PassDirection::AnyOrder => {
+            while let Some((height, id)) = dirty.pop_back(type_id) {
+                if (update_node)(id, ctx) {
+                    nodes_updated.insert(id);
+                    dependants.mark_dirty(&dirty, id, &tree, height);
+                }
             }
         }
     }
 }
 
-pub enum AnyPass<T: 'static> {
-    Upward(&'static (dyn UpwardPass<T> + Send + Sync + 'static)),
-    Downward(&'static (dyn DownwardPass<T> + Send + Sync + 'static)),
-    Node(&'static (dyn NodePass<T> + Send + Sync + 'static)),
+/// The states that depend on this state
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
+pub struct Dependants {
+    /// The states in the parent direction that should be invalidated when this state is invalidated
+    pub parent: Vec<TypeId>,
+    /// The states in the child direction that should be invalidated when this state is invalidated
+    pub child: Vec<TypeId>,
+    /// The states in the node direction that should be invalidated when this state is invalidated
+    pub node: Vec<TypeId>,
 }
 
-impl<T> AnyPass<T> {
-    pub fn pass_id(&self) -> PassId {
-        match self {
-            Self::Upward(pass) => pass.pass_id(),
-            Self::Downward(pass) => pass.pass_id(),
-            Self::Node(pass) => pass.pass_id(),
-        }
-    }
-
-    pub fn dependancies(&self) -> &'static [PassId] {
-        match self {
-            Self::Upward(pass) => pass.dependancies(),
-            Self::Downward(pass) => pass.dependancies(),
-            Self::Node(pass) => pass.dependancies(),
-        }
-    }
-
-    fn resolve(
-        &self,
-        tree: &mut impl TreeView<T>,
-        dirty_states: &mut DirtyNodeStates,
-        nodes_updated: &FxDashSet<NodeId>,
-        ctx: &SendAnyMap,
-    ) {
-        match self {
-            Self::Downward(pass) => {
-                resolve_downward_pass(tree, *pass, dirty_states, nodes_updated, ctx)
+impl Dependants {
+    fn mark_dirty(&self, dirty: &DirtyNodeStates, id: NodeId, tree: &impl TreeRef, height: u16) {
+        for dependant in &self.child {
+            for id in tree.children_ids(id) {
+                dirty.insert(*dependant, id, height + 1);
             }
-            Self::Upward(pass) => {
-                resolve_upward_pass(tree, *pass, dirty_states, nodes_updated, ctx)
-            }
-            Self::Node(pass) => resolve_node_pass(tree, *pass, dirty_states, nodes_updated, ctx),
         }
-    }
-}
 
-pub fn resolve_passes<T, Tr: TreeView<T> + Sync + Send>(
-    tree: &mut Tr,
-    dirty_nodes: DirtyNodeStates,
-    passes: Vec<&AnyPass<T>>,
-    ctx: SendAnyMap,
-) -> FxDashSet<NodeId> {
-    resolve_passes_single_threaded(tree, dirty_nodes, passes, ctx)
-    // TODO: multithreadeding has some safety issues currently that need to be resolved before it can be used
-    // let dirty_states = Arc::new(dirty_nodes);
-    // let mut resolved_passes: FxHashSet<PassId> = FxHashSet::default();
-    // let mut resolving = Vec::new();
-    // let nodes_updated = Arc::new(FxDashSet::default());
-    // let ctx = Arc::new(ctx);
-    // while !passes.is_empty() {
-    //     let mut currently_borrowed = MemberMask::default();
-    //     std::thread::scope(|s| {
-    //         let mut i = 0;
-    //         while i < passes.len() {
-    //             let pass = &passes[i];
-    //             let pass_id = pass.pass_id();
-    //             let pass_mask = pass.mask();
-    //             if pass
-    //                 .dependancies()
-    //                 .iter()
-    //                 .all(|d| resolved_passes.contains(d) || *d == pass_id)
-    //                 && !pass_mask.overlaps(currently_borrowed)
-    //             {
-    //                 let pass = passes.remove(i);
-    //                 resolving.push(pass_id);
-    //                 currently_borrowed |= pass_mask;
-    //                 let dirty_states = dirty_states.clone();
-    //                 let nodes_updated = nodes_updated.clone();
-    //                 let ctx = ctx.clone();
-    //                 let mut dirty = DirtyNodes::default();
-    //                 // dirty_states.all_dirty(pass_id, &mut dirty, tree);
-    //                 // this is safe because the member_mask acts as a per-member mutex and we have verified that the pass does not overlap with any other pass
-    //                 let tree_mut_unbounded = unsafe { &mut *(tree as *mut Tr) };
-    //                 s.spawn(move || {
-    //                     pass.resolve(
-    //                         tree_mut_unbounded,
-    //                         dirty,
-    //                         &dirty_states,
-    //                         &nodes_updated,
-    //                         &ctx,
-    //                     );
-    //                 });
-    //             } else {
-    //                 i += 1;
-    //             }
-    //         }
-    //         // all passes are resolved at the end of the scope
-    //     });
-    //     resolved_passes.extend(resolving.iter().copied());
-    //     resolving.clear()
-    // }
-    // std::sync::Arc::try_unwrap(nodes_updated).unwrap()
-}
-
-pub fn resolve_passes_single_threaded<T, Tr: TreeView<T>>(
-    tree: &mut Tr,
-    dirty_nodes: DirtyNodeStates,
-    mut passes: Vec<&AnyPass<T>>,
-    ctx: SendAnyMap,
-) -> FxDashSet<NodeId> {
-    let mut dirty_states = dirty_nodes;
-    let mut resolved_passes: FxHashSet<PassId> = FxHashSet::default();
-    let nodes_updated = Arc::new(FxDashSet::default());
-    let ctx = Arc::new(ctx);
-    while !passes.is_empty() {
-        for (i, pass) in passes.iter().enumerate() {
-            let pass_id = pass.pass_id();
-            if pass
-                .dependancies()
-                .iter()
-                .all(|d| resolved_passes.contains(d) || *d == pass_id)
-            {
-                let pass = passes.remove(i);
-                let nodes_updated = nodes_updated.clone();
-                let ctx = ctx.clone();
-                pass.resolve(tree, &mut dirty_states, &nodes_updated, &ctx);
-                resolved_passes.insert(pass_id);
-                break;
+        for dependant in &self.parent {
+            if let Some(id) = tree.parent_id(id) {
+                dirty.insert(*dependant, id, height - 1);
             }
         }
-    }
-    std::sync::Arc::try_unwrap(nodes_updated).unwrap()
-}
-
-#[test]
-fn node_pass() {
-    use crate::tree::{Tree, TreeLike};
-    let mut tree = Tree::new(0);
-
-    struct AddPass;
-    impl Pass for AddPass {
-        fn pass_id(&self) -> PassId {
-            PassId(0)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
 
-    impl NodePass<i32> for AddPass {
-        fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool {
-            *node += 1;
-            true
+        for dependant in &self.node {
+            dirty.insert(*dependant, id, height);
         }
     }
-
-    let add_pass = AnyPass::Node(&AddPass);
-    let passes = vec![&add_pass];
-    let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
-    dirty_nodes.insert(PassId(0), tree.root(), 0);
-    resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
-
-    assert_eq!(tree.get(tree.root()).unwrap(), &1);
 }
 
-#[test]
-fn dependant_node_pass() {
-    use crate::tree::{Tree, TreeLike};
-    let mut tree = Tree::new(0);
-
-    struct AddPass;
-    impl Pass for AddPass {
-        fn pass_id(&self) -> PassId {
-            PassId(0)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[PassId(1)]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
-
-    impl NodePass<i32> for AddPass {
-        fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool {
-            *node += 1;
-            true
-        }
-    }
-
-    struct SubtractPass;
-
-    impl Pass for SubtractPass {
-        fn pass_id(&self) -> PassId {
-            PassId(1)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[PassId(0)]
-        }
-
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
-    impl NodePass<i32> for SubtractPass {
-        fn pass(&self, node: &mut i32, _: &SendAnyMap) -> bool {
-            *node -= 1;
-            true
-        }
-    }
-
-    let add_pass = AnyPass::Node(&AddPass);
-    let subtract_pass = AnyPass::Node(&SubtractPass);
-    let passes = vec![&add_pass, &subtract_pass];
-    let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
-    dirty_nodes.insert(PassId(1), tree.root(), 0);
-    resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
-
-    assert_eq!(*tree.get(tree.root()).unwrap(), 0);
+/// A type erased version of [`State`] that can be added to the [`crate::prelude::RealDom`] with [`crate::prelude::RealDom::new`]
+pub struct TypeErasedState<V: FromAnyValue + Send = ()> {
+    pub(crate) this_type_id: TypeId,
+    pub(crate) parent_dependancies_ids: FxHashSet<TypeId>,
+    pub(crate) child_dependancies_ids: FxHashSet<TypeId>,
+    pub(crate) node_dependancies_ids: FxHashSet<TypeId>,
+    pub(crate) dependants: Arc<Dependants>,
+    pub(crate) mask: NodeMask,
+    pub(crate) workload: fn(TypeId, Arc<Dependants>, PassDirection) -> WorkloadSystem,
+    pub(crate) pass_direction: PassDirection,
+    phantom: PhantomData<V>,
 }
 
-#[test]
-fn independant_node_pass() {
-    use crate::tree::{Tree, TreeLike};
-    let mut tree = Tree::new((0, 0));
-
-    struct AddPass1;
-    impl Pass for AddPass1 {
-        fn pass_id(&self) -> PassId {
-            PassId(0)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
-
-    impl NodePass<(i32, i32)> for AddPass1 {
-        fn pass(&self, node: &mut (i32, i32), _: &SendAnyMap) -> bool {
-            node.0 += 1;
-            true
-        }
-    }
-
-    struct AddPass2;
-    impl Pass for AddPass2 {
-        fn pass_id(&self) -> PassId {
-            PassId(1)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn mask(&self) -> MemberMask {
-            MemberMask(1)
-        }
+impl<V: FromAnyValue + Send> TypeErasedState<V> {
+    pub(crate) fn create_workload(&self) -> WorkloadSystem {
+        (self.workload)(
+            self.this_type_id,
+            self.dependants.clone(),
+            self.pass_direction,
+        )
     }
 
-    impl NodePass<(i32, i32)> for AddPass2 {
-        fn pass(&self, node: &mut (i32, i32), _: &SendAnyMap) -> bool {
-            node.1 += 1;
-            true
-        }
+    pub(crate) fn combined_dependancy_type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
+        self.parent_dependancies_ids
+            .iter()
+            .chain(self.child_dependancies_ids.iter())
+            .chain(self.node_dependancies_ids.iter())
+            .copied()
     }
-
-    let add_pass1 = AnyPass::Node(&AddPass1);
-    let add_pass2 = AnyPass::Node(&AddPass2);
-    let passes = vec![&add_pass1, &add_pass2];
-    let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
-    dirty_nodes.insert(PassId(0), tree.root(), 0);
-    dirty_nodes.insert(PassId(1), tree.root(), 0);
-    resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
-
-    assert_eq!(tree.get(tree.root()).unwrap(), &(1, 1));
 }
 
-#[test]
-fn down_pass() {
-    use crate::tree::{Tree, TreeLike};
-    let mut tree = Tree::new(1);
-    let parent = tree.root();
-    let child1 = tree.create_node(1);
-    tree.add_child(parent, child1);
-    let grandchild1 = tree.create_node(1);
-    tree.add_child(child1, grandchild1);
-    let child2 = tree.create_node(1);
-    tree.add_child(parent, child2);
-    let grandchild2 = tree.create_node(1);
-    tree.add_child(child2, grandchild2);
-
-    struct AddPass;
-
-    impl Pass for AddPass {
-        fn pass_id(&self) -> PassId {
-            PassId(0)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
-    impl DownwardPass<i32> for AddPass {
-        fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn {
-            if let Some(parent) = parent {
-                *node += *parent;
-            }
-            PassReturn {
-                progress: true,
-                mark_dirty: true,
-            }
-        }
-    }
-
-    let add_pass = AnyPass::Downward(&AddPass);
-    let passes = vec![&add_pass];
-    let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
-    dirty_nodes.insert(PassId(0), tree.root(), 0);
-    resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
-
-    assert_eq!(tree.get(tree.root()).unwrap(), &1);
-    assert_eq!(tree.get(child1).unwrap(), &2);
-    assert_eq!(tree.get(grandchild1).unwrap(), &3);
-    assert_eq!(tree.get(child2).unwrap(), &2);
-    assert_eq!(tree.get(grandchild2).unwrap(), &3);
+/// The direction that a pass should be run in
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
+pub enum PassDirection {
+    /// The pass should be run from the root to the leaves
+    ParentToChild,
+    /// The pass should be run from the leaves to the root
+    ChildToParent,
+    /// The pass can be run in any order
+    AnyOrder,
 }
 
-#[test]
-fn dependant_down_pass() {
-    use crate::tree::{Tree, TreeLike};
-    // 0
-    let mut tree = Tree::new(1);
-    let parent = tree.root();
-    // 1
-    let child1 = tree.create_node(1);
-    tree.add_child(parent, child1);
-    // 2
-    let grandchild1 = tree.create_node(1);
-    tree.add_child(child1, grandchild1);
-    // 3
-    let child2 = tree.create_node(1);
-    tree.add_child(parent, child2);
-    // 4
-    let grandchild2 = tree.create_node(1);
-    tree.add_child(child2, grandchild2);
-
-    struct AddPass;
-    impl Pass for AddPass {
-        fn pass_id(&self) -> PassId {
-            PassId(0)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[PassId(1)]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
+/// A trait that is implemented for all the dependancies of a [`State`]
+pub trait Dependancy {
+    /// A tuple with all the elements of the dependancy as [`DependancyView`]
+    type ElementBorrowed<'a>;
 
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
+    /// Returns a list of all the [`TypeId`]s of the elements in the dependancy
+    fn type_ids() -> Box<[TypeId]> {
+        Box::new([])
     }
-    impl DownwardPass<i32> for AddPass {
-        fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn {
-            if let Some(parent) = parent {
-                *node += *parent;
-            } else {
-            }
-            PassReturn {
-                progress: true,
-                mark_dirty: true,
-            }
-        }
-    }
-
-    struct SubtractPass;
-    impl Pass for SubtractPass {
-        fn pass_id(&self) -> PassId {
-            PassId(1)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[PassId(0)]
-        }
-
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
-    impl DownwardPass<i32> for SubtractPass {
-        fn pass(&self, node: &mut i32, parent: Option<&mut i32>, _: &SendAnyMap) -> PassReturn {
-            if let Some(parent) = parent {
-                *node -= *parent;
-            } else {
-            }
-            PassReturn {
-                progress: true,
-                mark_dirty: true,
-            }
-        }
-    }
-
-    let add_pass = AnyPass::Downward(&AddPass);
-    let subtract_pass = AnyPass::Downward(&SubtractPass);
-    let passes = vec![&add_pass, &subtract_pass];
-    let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
-    dirty_nodes.insert(PassId(1), tree.root(), 0);
-    resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
-
-    // Tree before:
-    // 1=\
-    //   1=\
-    //     1
-    //   1=\
-    //     1
-    // Tree after subtract:
-    // 1=\
-    //   0=\
-    //     1
-    //   0=\
-    //     1
-    // Tree after add:
-    // 1=\
-    //   1=\
-    //     2
-    //   1=\
-    //     2
-    assert_eq!(tree.get(tree.root()).unwrap(), &1);
-    assert_eq!(tree.get(child1).unwrap(), &1);
-    assert_eq!(tree.get(grandchild1).unwrap(), &2);
-    assert_eq!(tree.get(child2).unwrap(), &1);
-    assert_eq!(tree.get(grandchild2).unwrap(), &2);
 }
 
-#[test]
-fn up_pass() {
-    use crate::tree::{Tree, TreeLike};
-    // Tree before:
-    // 0=\
-    //   0=\
-    //     1
-    //   0=\
-    //     1
-    // Tree after:
-    // 2=\
-    //   1=\
-    //     1
-    //   1=\
-    //     1
-    let mut tree = Tree::new(0);
-    let parent = tree.root();
-    let child1 = tree.create_node(0);
-    tree.add_child(parent, child1);
-    let grandchild1 = tree.create_node(1);
-    tree.add_child(child1, grandchild1);
-    let child2 = tree.create_node(0);
-    tree.add_child(parent, child2);
-    let grandchild2 = tree.create_node(1);
-    tree.add_child(child2, grandchild2);
-
-    struct AddPass;
-    impl Pass for AddPass {
-        fn pass_id(&self) -> PassId {
-            PassId(0)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
+macro_rules! impl_dependancy {
+    ($($t:ident),*) => {
+        impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) {
+            type ElementBorrowed<'a> = ($(DependancyView<'a, $t>,)*);
 
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
-    impl UpwardPass<i32> for AddPass {
-        fn pass(
-            &self,
-            node: &mut i32,
-            children: &mut dyn Iterator<Item = &mut i32>,
-            _: &SendAnyMap,
-        ) -> PassReturn {
-            *node += children.map(|i| *i).sum::<i32>();
-            PassReturn {
-                progress: true,
-                mark_dirty: true,
+            fn type_ids() -> Box<[TypeId]> {
+                Box::new([$(TypeId::of::<$t>()),*])
             }
         }
-    }
-
-    let add_pass = AnyPass::Upward(&AddPass);
-    let passes = vec![&add_pass];
-    let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
-    dirty_nodes.insert(PassId(0), grandchild1, tree.height(grandchild1).unwrap());
-    dirty_nodes.insert(PassId(0), grandchild2, tree.height(grandchild2).unwrap());
-    resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
-
-    assert_eq!(tree.get(tree.root()).unwrap(), &2);
-    assert_eq!(tree.get(child1).unwrap(), &1);
-    assert_eq!(tree.get(grandchild1).unwrap(), &1);
-    assert_eq!(tree.get(child2).unwrap(), &1);
-    assert_eq!(tree.get(grandchild2).unwrap(), &1);
+    };
 }
 
-#[test]
-fn dependant_up_pass() {
-    use crate::tree::{Tree, TreeLike};
-    // 0
-    let mut tree = Tree::new(0);
-    let parent = tree.root();
-    // 1
-    let child1 = tree.create_node(0);
-    tree.add_child(parent, child1);
-    // 2
-    let grandchild1 = tree.create_node(1);
-    tree.add_child(child1, grandchild1);
-    // 3
-    let child2 = tree.create_node(0);
-    tree.add_child(parent, child2);
-    // 4
-    let grandchild2 = tree.create_node(1);
-    tree.add_child(child2, grandchild2);
-
-    struct AddPass;
-    impl Pass for AddPass {
-        fn pass_id(&self) -> PassId {
-            PassId(0)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[PassId(1)]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[]
-        }
+// TODO: track what components are actually read to update subscriptions
+// making this a wrapper makes it possible to implement that optimization without a breaking change
+/// A immutable view of a [`State`]
+pub struct DependancyView<'a, T> {
+    inner: &'a T,
+}
 
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
-    }
-    impl UpwardPass<i32> for AddPass {
-        fn pass(
-            &self,
-            node: &mut i32,
-            children: &mut dyn Iterator<Item = &mut i32>,
-            _: &SendAnyMap,
-        ) -> PassReturn {
-            *node += children.map(|i| *i).sum::<i32>();
-            PassReturn {
-                progress: true,
-                mark_dirty: true,
-            }
-        }
+impl<'a, T> DependancyView<'a, T> {
+    // This should only be used in the macro. This is not a public API or stable
+    #[doc(hidden)]
+    pub fn new(inner: &'a T) -> Self {
+        Self { inner }
     }
+}
 
-    struct SubtractPass;
-    impl Pass for SubtractPass {
-        fn pass_id(&self) -> PassId {
-            PassId(1)
-        }
-
-        fn dependancies(&self) -> &'static [PassId] {
-            &[]
-        }
-
-        fn dependants(&self) -> &'static [PassId] {
-            &[PassId(0)]
-        }
+impl<'a, T> Deref for DependancyView<'a, T> {
+    type Target = T;
 
-        fn mask(&self) -> MemberMask {
-            MemberMask(0)
-        }
+    fn deref(&self) -> &Self::Target {
+        self.inner
     }
-    impl UpwardPass<i32> for SubtractPass {
-        fn pass(
-            &self,
-            node: &mut i32,
-            children: &mut dyn Iterator<Item = &mut i32>,
-            _: &SendAnyMap,
-        ) -> PassReturn {
-            *node -= children.map(|i| *i).sum::<i32>();
-            PassReturn {
-                progress: true,
-                mark_dirty: true,
-            }
-        }
-    }
-
-    let add_pass = AnyPass::Upward(&AddPass);
-    let subtract_pass = AnyPass::Upward(&SubtractPass);
-    let passes = vec![&add_pass, &subtract_pass];
-    let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
-    dirty_nodes.insert(PassId(1), grandchild1, tree.height(grandchild1).unwrap());
-    dirty_nodes.insert(PassId(1), grandchild2, tree.height(grandchild2).unwrap());
-    resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
-
-    // Tree before:
-    // 0=\
-    //   0=\
-    //     1
-    //   0=\
-    //     1
-    // Tree after subtract:
-    // 2=\
-    //   -1=\
-    //      1
-    //   -1=\
-    //      1
-    // Tree after add:
-    // 2=\
-    //   0=\
-    //     1
-    //   0=\
-    //     1
-    assert_eq!(tree.get(tree.root()).unwrap(), &2);
-    assert_eq!(tree.get(child1).unwrap(), &0);
-    assert_eq!(tree.get(grandchild1).unwrap(), &1);
-    assert_eq!(tree.get(child2).unwrap(), &0);
-    assert_eq!(tree.get(grandchild2).unwrap(), &1);
 }
+
+impl_dependancy!();
+impl_dependancy!(A);
+impl_dependancy!(A, B);
+impl_dependancy!(A, B, C);
+impl_dependancy!(A, B, C, D);
+impl_dependancy!(A, B, C, D, E);
+impl_dependancy!(A, B, C, D, E, F);
+impl_dependancy!(A, B, C, D, E, F, G);
+impl_dependancy!(A, B, C, D, E, F, G, H);
+impl_dependancy!(A, B, C, D, E, F, G, H, I);
+impl_dependancy!(A, B, C, D, E, F, G, H, I, J);

+ 982 - 401
packages/native-core/src/real_dom.rs

@@ -1,482 +1,1063 @@
-use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode};
+//! A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
+
 use rustc_hash::{FxHashMap, FxHashSet};
-use std::fmt::Debug;
-use std::ops::{Deref, DerefMut, Index, IndexMut};
-
-use crate::node::{FromAnyValue, Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue};
-use crate::node_ref::{AttributeMask, NodeMask};
-use crate::passes::DirtyNodeStates;
-use crate::state::State;
-use crate::tree::{NodeId, Tree, TreeLike, TreeView};
-use crate::{FxDashSet, RealNodeId, SendAnyMap};
-
-fn mark_dirty(
-    node_id: NodeId,
-    mask: NodeMask,
-    nodes_updated: &mut FxHashMap<RealNodeId, NodeMask>,
-) {
-    if let Some(node) = nodes_updated.get_mut(&node_id) {
-        *node = node.union(&mask);
-    } else {
-        nodes_updated.insert(node_id, mask);
+use shipyard::error::GetStorage;
+use shipyard::track::Untracked;
+use shipyard::{Component, Get, IntoBorrow, ScheduledWorkload, Unique, View, ViewMut, Workload};
+use shipyard::{SystemModificator, World};
+use std::any::TypeId;
+use std::collections::VecDeque;
+use std::ops::{Deref, DerefMut};
+use std::sync::{Arc, RwLock};
+
+use crate::node::{
+    ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode,
+};
+use crate::node_ref::{NodeMask, NodeMaskBuilder};
+use crate::node_watcher::NodeWatcher;
+use crate::passes::{DirtyNodeStates, PassDirection, TypeErasedState};
+use crate::prelude::AttributeMaskBuilder;
+use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView};
+use crate::NodeId;
+use crate::{FxDashSet, SendAnyMap};
+
+/// The context passes can receive when they are executed
+#[derive(Unique)]
+pub(crate) struct SendAnyMapWrapper(SendAnyMap);
+
+impl Deref for SendAnyMapWrapper {
+    type Target = SendAnyMap;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
     }
 }
 
-/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
-/// The render state passes from parent to children and or accumulates state from children to parents.
-/// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
-///
-/// # Custom values
-/// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients.
-#[derive(Debug)]
-pub struct RealDom<S: State<V>, V: FromAnyValue + 'static = ()> {
-    pub tree: Tree<Node<S, V>>,
-    /// a map from element id to real node id
-    node_id_mapping: Vec<Option<RealNodeId>>,
-    nodes_listening: FxHashMap<String, FxHashSet<RealNodeId>>,
-    stack: Vec<RealNodeId>,
-    templates: FxHashMap<String, Vec<RealNodeId>>,
-    root_initialized: bool,
-}
+/// The nodes that were changed when updating the state of the RealDom
+#[derive(Unique, Default)]
+pub(crate) struct DirtyNodesResult(FxDashSet<NodeId>);
+
+impl Deref for DirtyNodesResult {
+    type Target = FxDashSet<NodeId>;
 
-impl<S: State<V>, V: FromAnyValue> Default for RealDom<S, V> {
-    fn default() -> Self {
-        Self::new()
+    fn deref(&self) -> &Self::Target {
+        &self.0
     }
 }
 
-impl<S: State<V>, V: FromAnyValue> RealDom<S, V> {
-    pub fn new() -> RealDom<S, V> {
-        let mut root = Node::new(NodeType::Element {
-            tag: "Root".to_string(),
-            namespace: Some("Root".to_string()),
-            attributes: FxHashMap::default(),
-            listeners: FxHashSet::default(),
-        });
-        root.node_data.element_id = Some(ElementId(0));
-        let mut tree = Tree::new(root);
-        let root_id = tree.root();
-        tree.get_mut(root_id).unwrap().node_data.node_id = root_id;
+/// The nodes that have been marked as dirty in the RealDom
+pub(crate) struct NodesDirty<V: FromAnyValue + Send + Sync> {
+    passes_updated: FxHashMap<NodeId, FxHashSet<TypeId>>,
+    nodes_updated: FxHashMap<NodeId, NodeMask>,
+    pub(crate) passes: Box<[TypeErasedState<V>]>,
+}
 
-        RealDom {
-            tree,
-            node_id_mapping: vec![Some(root_id)],
-            nodes_listening: FxHashMap::default(),
-            stack: vec![root_id],
-            templates: FxHashMap::default(),
-            root_initialized: false,
-        }
-    }
-
-    pub fn element_to_node_id(&self, element_id: ElementId) -> RealNodeId {
-        self.node_id_mapping.get(element_id.0).unwrap().unwrap()
-    }
-
-    fn set_element_id(&mut self, node_id: NodeId, element_id: ElementId) {
-        let node = self.tree.get_mut(node_id).unwrap();
-        let node_id = node.node_data.node_id;
-        node.node_data.element_id = Some(element_id);
-        if self.node_id_mapping.len() <= element_id.0 {
-            self.node_id_mapping.resize(element_id.0 + 1, None);
-        }
-        self.node_id_mapping[element_id.0] = Some(node_id);
-    }
-
-    fn load_child(&self, path: &[u8]) -> RealNodeId {
-        let mut current = *self.stack.last().unwrap();
-        for i in path {
-            current = self.tree.children_ids(current).unwrap()[*i as usize];
-        }
-        current
-    }
-
-    fn create_node(&mut self, node: Node<S, V>) -> RealNodeId {
-        let node_id = self.tree.create_node(node);
-        let node = self.tree.get_mut(node_id).unwrap();
-        node.node_data.node_id = node_id;
-        node_id
-    }
-
-    fn add_child(&mut self, node_id: RealNodeId, child_id: RealNodeId) {
-        self.tree.add_child(node_id, child_id);
-    }
-
-    fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId {
-        match node {
-            TemplateNode::Element {
-                tag,
-                namespace,
-                attrs,
-                children,
-            } => {
-                let node = Node::new(NodeType::Element {
-                    tag: tag.to_string(),
-                    namespace: namespace.map(|s| s.to_string()),
-                    attributes: attrs
-                        .iter()
-                        .filter_map(|attr| match attr {
-                            dioxus_core::TemplateAttribute::Static {
-                                name,
-                                value,
-                                namespace,
-                            } => Some((
-                                OwnedAttributeDiscription {
-                                    namespace: namespace.map(|s| s.to_string()),
-                                    name: name.to_string(),
-                                    volatile: false,
-                                },
-                                OwnedAttributeValue::Text(value.to_string()),
-                            )),
-                            dioxus_core::TemplateAttribute::Dynamic { .. } => None,
-                        })
-                        .collect(),
-                    listeners: FxHashSet::default(),
-                });
-                let node_id = self.create_node(node);
-                for child in *children {
-                    let child_id = self.create_template_node(child);
-                    self.add_child(node_id, child_id);
-                }
-                node_id
+impl<V: FromAnyValue + Send + Sync> NodesDirty<V> {
+    /// Mark a node as dirty
+    fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) {
+        self.passes_updated.entry(node_id).or_default().extend(
+            self.passes
+                .iter()
+                .filter_map(|x| x.mask.overlaps(&mask).then_some(x.this_type_id)),
+        );
+        let nodes_updated = &mut self.nodes_updated;
+        if let Some(node) = nodes_updated.get_mut(&node_id) {
+            *node = node.union(&mask);
+        } else {
+            nodes_updated.insert(node_id, mask);
+        }
+    }
+
+    /// Mark a node that has had a parent changed
+    fn mark_parent_added_or_removed(&mut self, node_id: NodeId) {
+        let hm = self.passes_updated.entry(node_id).or_default();
+        for pass in &*self.passes {
+            // If any of the states in this node depend on the parent then mark them as dirty
+            for &pass in &pass.parent_dependancies_ids {
+                hm.insert(pass);
             }
-            TemplateNode::Text { text } => self.create_node(Node::new(NodeType::Text {
-                text: text.to_string(),
-            })),
-            TemplateNode::Dynamic { .. } => self.create_node(Node::new(NodeType::Placeholder)),
-            TemplateNode::DynamicText { .. } => self.create_node(Node::new(NodeType::Text {
-                text: String::new(),
-            })),
         }
     }
 
-    /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state.
-    pub fn apply_mutations(
-        &mut self,
-        mutations: Mutations,
-    ) -> (DirtyNodeStates, FxHashMap<RealNodeId, NodeMask>) {
-        let mut nodes_updated: FxHashMap<RealNodeId, NodeMask> = FxHashMap::default();
-        for template in mutations.templates {
-            let mut template_root_ids = Vec::new();
-            for root in template.roots {
-                let id = self.create_template_node(root);
-                template_root_ids.push(id);
+    /// Mark a node as having a child added or removed
+    fn mark_child_changed(&mut self, node_id: NodeId) {
+        let hm = self.passes_updated.entry(node_id).or_default();
+        for pass in &*self.passes {
+            // If any of the states in this node depend on the children then mark them as dirty
+            for &pass in &pass.child_dependancies_ids {
+                hm.insert(pass);
             }
-            self.templates
-                .insert(template.name.to_string(), template_root_ids);
-        }
-        if !self.root_initialized {
-            self.root_initialized = true;
-            let root_id = self.tree.root();
-            nodes_updated.insert(root_id, NodeMask::ALL);
-        }
-        for e in mutations.edits {
-            use dioxus_core::Mutation::*;
-            match e {
-                AppendChildren { id, m } => {
-                    let children = self.stack.split_off(self.stack.len() - m);
-                    let parent = self.element_to_node_id(id);
-                    for child in children {
-                        self.add_child(parent, child);
-                        mark_dirty(child, NodeMask::ALL, &mut nodes_updated);
-                    }
-                }
-                AssignId { path, id } => {
-                    self.set_element_id(self.load_child(path), id);
-                }
-                CreatePlaceholder { id } => {
-                    let node = Node::new(NodeType::Placeholder);
-                    let node_id = self.create_node(node);
-                    self.set_element_id(node_id, id);
-                    self.stack.push(node_id);
-                    mark_dirty(node_id, NodeMask::ALL, &mut nodes_updated);
-                }
-                CreateTextNode { value, id } => {
-                    let node = Node::new(NodeType::Text {
-                        text: value.to_string(),
-                    });
-                    let node_id = self.create_node(node);
-                    self.set_element_id(node_id, id);
-                    self.stack.push(node_id);
-                    mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated);
-                }
-                HydrateText { path, value, id } => {
-                    let node_id = self.load_child(path);
-                    self.set_element_id(node_id, id);
-                    let node = self.tree.get_mut(node_id).unwrap();
-                    if let NodeType::Text { text } = &mut node.node_data.node_type {
-                        *text = value.to_string();
-                    } else {
-                        node.node_data.node_type = NodeType::Text {
-                            text: value.to_string(),
-                        };
-                    }
+        }
+    }
+}
 
-                    mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated);
-                }
-                LoadTemplate { name, index, id } => {
-                    let template_id = self.templates[name][index];
-                    let clone_id = self.clone_node(template_id, &mut nodes_updated);
-                    self.set_element_id(clone_id, id);
-                    self.stack.push(clone_id);
-                }
-                ReplaceWith { id, m } => {
-                    let new_nodes = self.stack.split_off(self.stack.len() - m);
-                    let old_node_id = self.element_to_node_id(id);
-                    for new in new_nodes {
-                        self.tree.insert_before(old_node_id, new);
-                        mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
-                    }
-                    self.remove(old_node_id, &mut nodes_updated);
-                }
-                ReplacePlaceholder { path, m } => {
-                    let new_nodes = self.stack.split_off(self.stack.len() - m);
-                    let old_node_id = self.load_child(path);
-                    for new in new_nodes {
-                        self.tree.insert_before(old_node_id, new);
-                        mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
-                    }
-                    self.remove(old_node_id, &mut nodes_updated);
-                }
-                InsertAfter { id, m } => {
-                    let new_nodes = self.stack.split_off(self.stack.len() - m);
-                    let old_node_id = self.element_to_node_id(id);
-                    for new in new_nodes.into_iter().rev() {
-                        self.tree.insert_after(old_node_id, new);
-                        mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
-                    }
-                }
-                InsertBefore { id, m } => {
-                    let new_nodes = self.stack.split_off(self.stack.len() - m);
-                    let old_node_id = self.element_to_node_id(id);
-                    for new in new_nodes {
-                        self.tree.insert_before(old_node_id, new);
-                        mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
-                    }
+type NodeWatchers<V> = Arc<RwLock<Vec<Box<dyn NodeWatcher<V> + Send + Sync>>>>;
+
+/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
+/// The render state passes from parent to children and or accumulates state from children to parents.
+/// To get started:
+/// 1) Implement [crate::passes::State] for each part of your state that you want to compute incrementally
+/// 2) Create a RealDom [RealDom::new], passing in each state you created
+/// 3) Update the state of the RealDom by adding and modifying nodes
+/// 4) Call [RealDom::update_state] to update the state of incrementally computed values on each node
+///
+/// # Custom attribute values
+/// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients.
+pub struct RealDom<V: FromAnyValue + Send + Sync = ()> {
+    pub(crate) world: World,
+    nodes_listening: FxHashMap<String, FxHashSet<NodeId>>,
+    pub(crate) dirty_nodes: NodesDirty<V>,
+    node_watchers: NodeWatchers<V>,
+    workload: ScheduledWorkload,
+    root_id: NodeId,
+    phantom: std::marker::PhantomData<V>,
+}
+
+impl<V: FromAnyValue + Send + Sync> RealDom<V> {
+    /// Create a new RealDom with the given states that will be inserted and updated when needed
+    pub fn new(tracked_states: impl Into<Box<[TypeErasedState<V>]>>) -> RealDom<V> {
+        let mut tracked_states = tracked_states.into();
+        // resolve dependants for each pass
+        for i in 1..=tracked_states.len() {
+            let (before, after) = tracked_states.split_at_mut(i);
+            let (current, before) = before.split_last_mut().unwrap();
+            for state in before.iter_mut().chain(after.iter_mut()) {
+                let dependants = Arc::get_mut(&mut state.dependants).unwrap();
+                // If this node depends on the other state as a parent, then the other state should update its children of the current type when it is invalidated
+                if current
+                    .parent_dependancies_ids
+                    .contains(&state.this_type_id)
+                    && !dependants.child.contains(&current.this_type_id)
+                {
+                    dependants.child.push(current.this_type_id);
                 }
-                SetAttribute {
-                    name,
-                    value,
-                    id,
-                    ns,
-                } => {
-                    let node_id = self.element_to_node_id(id);
-                    let node = self.tree.get_mut(node_id).unwrap();
-                    if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type {
-                        if let BorrowedAttributeValue::None = &value {
-                            attributes.remove(&OwnedAttributeDiscription {
-                                name: name.to_string(),
-                                namespace: ns.map(|s| s.to_string()),
-                                volatile: false,
-                            });
-                            mark_dirty(
-                                node_id,
-                                NodeMask::new_with_attrs(AttributeMask::single(name)),
-                                &mut nodes_updated,
-                            );
-                        } else {
-                            attributes.insert(
-                                OwnedAttributeDiscription {
-                                    name: name.to_string(),
-                                    namespace: ns.map(|s| s.to_string()),
-                                    volatile: false,
-                                },
-                                OwnedAttributeValue::from(value),
-                            );
-                            mark_dirty(
-                                node_id,
-                                NodeMask::new_with_attrs(AttributeMask::single(name)),
-                                &mut nodes_updated,
-                            );
-                        }
-                    }
+                // If this node depends on the other state as a child, then the other state should update its parent of the current type when it is invalidated
+                if current.child_dependancies_ids.contains(&state.this_type_id)
+                    && !dependants.parent.contains(&current.this_type_id)
+                {
+                    dependants.parent.push(current.this_type_id);
                 }
-                SetText { value, id } => {
-                    let node_id = self.element_to_node_id(id);
-                    let node = self.tree.get_mut(node_id).unwrap();
-                    if let NodeType::Text { text } = &mut node.node_data.node_type {
-                        *text = value.to_string();
-                    }
-                    mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated);
+                // If this node depends on the other state as a sibling, then the other state should update its siblings of the current type when it is invalidated
+                if current.node_dependancies_ids.contains(&state.this_type_id)
+                    && !dependants.node.contains(&current.this_type_id)
+                {
+                    dependants.node.push(current.this_type_id);
                 }
-                NewEventListener { name, id } => {
-                    let node_id = self.element_to_node_id(id);
-                    let node = self.tree.get_mut(node_id).unwrap();
-                    if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type {
-                        match self.nodes_listening.get_mut(name) {
-                            Some(hs) => {
-                                hs.insert(node_id);
-                            }
-                            None => {
-                                let mut hs = FxHashSet::default();
-                                hs.insert(node_id);
-                                self.nodes_listening.insert(name.to_string(), hs);
-                            }
-                        }
-                        listeners.insert(name.to_string());
+            }
+            // If the current state depends on itself, then it should update itself when it is invalidated
+            let dependants = Arc::get_mut(&mut current.dependants).unwrap();
+            match current.pass_direction {
+                PassDirection::ChildToParent => {
+                    if !dependants.parent.contains(&current.this_type_id) {
+                        dependants.parent.push(current.this_type_id);
                     }
                 }
-                RemoveEventListener { id, name } => {
-                    let node_id = self.element_to_node_id(id);
-                    let node = self.tree.get_mut(node_id).unwrap();
-                    if let NodeType::Element { listeners, .. } = &mut node.node_data.node_type {
-                        listeners.remove(name);
+                PassDirection::ParentToChild => {
+                    if !dependants.child.contains(&current.this_type_id) {
+                        dependants.child.push(current.this_type_id);
                     }
-                    self.nodes_listening.get_mut(name).unwrap().remove(&node_id);
-                }
-                Remove { id } => {
-                    let node_id = self.element_to_node_id(id);
-                    self.remove(node_id, &mut nodes_updated);
-                }
-                PushRoot { id } => {
-                    let node_id = self.element_to_node_id(id);
-                    self.stack.push(node_id);
                 }
+                _ => {}
             }
         }
+        let workload = construct_workload(&mut tracked_states);
+        let (workload, _) = workload.build().unwrap();
+        let mut world = World::new();
+        let root_node: NodeType<V> = NodeType::Element(ElementNode {
+            tag: "Root".to_string(),
+            namespace: Some("Root".to_string()),
+            attributes: FxHashMap::default(),
+            listeners: FxHashSet::default(),
+        });
+        let root_id = world.add_entity(root_node);
+        {
+            let mut tree: TreeMutView = world.borrow().unwrap();
+            tree.create_node(root_id);
+        }
 
-        let mut dirty_nodes = DirtyNodeStates::default();
-        for (&n, mask) in &nodes_updated {
-            // remove any nodes that were created and then removed in the same mutations from the dirty nodes list
-            if let Some(height) = self.tree.height(n) {
-                for (m, p) in S::MASKS.iter().zip(S::PASSES.iter()) {
-                    if mask.overlaps(m) {
-                        dirty_nodes.insert(p.pass_id(), n, height);
-                    }
-                }
-            }
+        let mut passes_updated = FxHashMap::default();
+        let mut nodes_updated = FxHashMap::default();
+
+        passes_updated.insert(
+            root_id,
+            tracked_states.iter().map(|x| x.this_type_id).collect(),
+        );
+        nodes_updated.insert(root_id, NodeMaskBuilder::ALL.build());
+
+        RealDom {
+            world,
+            nodes_listening: FxHashMap::default(),
+            dirty_nodes: NodesDirty {
+                passes_updated,
+                nodes_updated,
+                passes: tracked_states,
+            },
+            node_watchers: Default::default(),
+            workload,
+            root_id,
+            phantom: std::marker::PhantomData,
         }
+    }
 
-        (dirty_nodes, nodes_updated)
+    /// Get a reference to the tree.
+    pub fn tree_ref(&self) -> TreeRefView {
+        self.world.borrow().unwrap()
     }
 
-    /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts.
-    pub fn update_state_single_threaded(
-        &mut self,
-        nodes_updated: DirtyNodeStates,
-        ctx: SendAnyMap,
-    ) -> FxDashSet<RealNodeId> {
-        S::update_single_threaded(nodes_updated, &mut self.tree, ctx)
+    /// Get a mutable reference to the tree.
+    pub fn tree_mut(&self) -> TreeMutView {
+        self.world.borrow().unwrap()
+    }
+
+    /// Create a new node of the given type in the dom and return a mutable reference to it.
+    pub fn create_node(&mut self, node: impl Into<NodeType<V>>) -> NodeMut<'_, V> {
+        let id = self.world.add_entity(node.into());
+        self.tree_mut().create_node(id);
+        self.dirty_nodes
+            .passes_updated
+            .entry(id)
+            .or_default()
+            .extend(self.dirty_nodes.passes.iter().map(|x| x.this_type_id));
+        self.dirty_nodes
+            .mark_dirty(id, NodeMaskBuilder::ALL.build());
+        let watchers = self.node_watchers.clone();
+        for watcher in &*watchers.read().unwrap() {
+            watcher.on_node_added(NodeMut::new(id, self));
+        }
+        NodeMut::new(id, self)
     }
 
     /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up.
     /// This can be useful to avoid creating duplicate events.
-    pub fn get_listening_sorted(&self, event: &str) -> Vec<&Node<S, V>> {
+    pub fn get_listening_sorted(&self, event: &str) -> Vec<NodeRef<V>> {
         if let Some(nodes) = self.nodes_listening.get(event) {
-            let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
-            listening.sort_by(|n1, n2| {
-                (self.tree.height(n1.node_data.node_id))
-                    .cmp(&self.tree.height(n2.node_data.node_id))
-                    .reverse()
-            });
+            let mut listening: Vec<_> = nodes
+                .iter()
+                .map(|id| (*id, self.tree_ref().height(*id).unwrap()))
+                .collect();
+            listening.sort_by(|(_, h1), (_, h2)| h1.cmp(h2).reverse());
             listening
+                .into_iter()
+                .map(|(id, _)| NodeRef { id, dom: self })
+                .collect()
         } else {
             Vec::new()
         }
     }
 
-    /// Return the number of nodes in the dom.
-    pub fn size(&self) -> usize {
-        // The dom has a root node, ignore it.
-        self.tree.size() - 1
-    }
-
     /// Returns the id of the root node.
     pub fn root_id(&self) -> NodeId {
-        self.tree.root()
+        self.root_id
+    }
+
+    /// Check if a node exists in the dom.
+    pub fn contains(&self, id: NodeId) -> bool {
+        self.tree_ref().contains(id)
+    }
+
+    /// Get a reference to a node.
+    pub fn get(&self, id: NodeId) -> Option<NodeRef<'_, V>> {
+        self.contains(id).then_some(NodeRef { id, dom: self })
+    }
+
+    /// Get a mutable reference to a node.
+    pub fn get_mut(&mut self, id: NodeId) -> Option<NodeMut<'_, V>> {
+        let contains = self.contains(id);
+        contains.then(|| NodeMut::new(id, self))
+    }
+
+    /// Borrow a component from the world without updating the dirty nodes.
+    fn borrow_raw<'a, B: IntoBorrow>(&'a self) -> Result<B, GetStorage>
+    where
+        B::Borrow: shipyard::Borrow<'a, View = B>,
+    {
+        self.world.borrow()
+    }
+
+    /// Borrow a component from the world without updating the dirty nodes.
+    fn borrow_node_type_mut(&self) -> Result<ViewMut<NodeType<V>>, GetStorage> {
+        self.world.borrow()
     }
 
-    fn clone_node(
+    /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts.
+    pub fn update_state(
         &mut self,
-        node_id: NodeId,
-        nodes_updated: &mut FxHashMap<RealNodeId, NodeMask>,
-    ) -> RealNodeId {
-        let node = self.tree.get(node_id).unwrap();
-        let new_node = node.clone();
-        let new_id = self.create_node(new_node);
-        mark_dirty(new_id, NodeMask::ALL, nodes_updated);
-        let self_ptr = self as *mut Self;
-        for child in self.tree.children_ids(node_id).unwrap() {
-            unsafe {
-                // this is safe because no node has itself as a child
-                let self_mut = &mut *self_ptr;
-                let child_id = self_mut.clone_node(*child, nodes_updated);
-                self_mut.add_child(new_id, child_id);
+        ctx: SendAnyMap,
+    ) -> (FxDashSet<NodeId>, FxHashMap<NodeId, NodeMask>) {
+        let passes = std::mem::take(&mut self.dirty_nodes.passes_updated);
+        let nodes_updated = std::mem::take(&mut self.dirty_nodes.nodes_updated);
+        let dirty_nodes =
+            DirtyNodeStates::with_passes(self.dirty_nodes.passes.iter().map(|p| p.this_type_id));
+        let tree = self.tree_ref();
+        for (node_id, passes) in passes {
+            // remove any nodes that were created and then removed in the same mutations from the dirty nodes list
+            if let Some(height) = tree.height(node_id) {
+                for pass in passes {
+                    dirty_nodes.insert(pass, node_id, height);
+                }
+            }
+        }
+
+        let _ = self.world.remove_unique::<DirtyNodeStates>();
+        let _ = self.world.remove_unique::<SendAnyMapWrapper>();
+        self.world.add_unique(dirty_nodes);
+        self.world.add_unique(SendAnyMapWrapper(ctx));
+        self.world.add_unique(DirtyNodesResult::default());
+
+        self.workload.run_with_world(&self.world).unwrap();
+
+        let dirty = self.world.remove_unique::<DirtyNodesResult>().unwrap();
+
+        (dirty.0, nodes_updated)
+    }
+
+    /// Traverses the dom in a depth first manner, calling the provided function on each node.
+    pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef<V>)) {
+        let mut stack = vec![self.root_id()];
+        let tree = self.tree_ref();
+        while let Some(id) = stack.pop() {
+            if let Some(node) = self.get(id) {
+                f(node);
+                let children = tree.children_ids(id);
+                stack.extend(children.iter().copied().rev());
+            }
+        }
+    }
+
+    /// Traverses the dom in a breadth first manner, calling the provided function on each node.
+    pub fn traverse_breadth_first(&self, mut f: impl FnMut(NodeRef<V>)) {
+        let mut queue = VecDeque::new();
+        queue.push_back(self.root_id());
+        let tree = self.tree_ref();
+        while let Some(id) = queue.pop_front() {
+            if let Some(node) = self.get(id) {
+                f(node);
+                let children = tree.children_ids(id);
+                for id in children {
+                    queue.push_back(id);
+                }
             }
         }
-        new_id
     }
 
-    fn remove(&mut self, node_id: NodeId, nodes_updated: &mut FxHashMap<RealNodeId, NodeMask>) {
-        let node = self.tree.get(node_id).unwrap();
-        if let NodeType::Element { listeners, .. } = &node.node_data.node_type {
-            for name in listeners.iter() {
-                self.nodes_listening.get_mut(name).unwrap().remove(&node_id);
+    /// Traverses the dom in a depth first manner mutably, calling the provided function on each node.
+    pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(NodeMut<V>)) {
+        let mut stack = vec![self.root_id()];
+        while let Some(id) = stack.pop() {
+            let tree = self.tree_ref();
+            let mut children = tree.children_ids(id);
+            drop(tree);
+            children.reverse();
+            if let Some(node) = self.get_mut(id) {
+                let node = node;
+                f(node);
+                stack.extend(children.iter());
             }
         }
-        if let Some(children) = self.tree.children_ids(node_id) {
-            let children = children.to_vec();
-            for child in children {
-                self.remove(child, nodes_updated);
+    }
+
+    /// Traverses the dom in a breadth first manner mutably, calling the provided function on each node.
+    pub fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(NodeMut<V>)) {
+        let mut queue = VecDeque::new();
+        queue.push_back(self.root_id());
+        while let Some(id) = queue.pop_front() {
+            let tree = self.tree_ref();
+            let children = tree.children_ids(id);
+            drop(tree);
+            if let Some(node) = self.get_mut(id) {
+                f(node);
+                for id in children {
+                    queue.push_back(id);
+                }
             }
         }
-        self.tree.remove(node_id);
-        mark_dirty(node_id, NodeMask::ALL, nodes_updated);
+    }
+
+    /// Adds a [`NodeWatcher`] to the dom. Node watchers are called whenever a node is created or removed.
+    pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher<V> + 'static + Send + Sync) {
+        self.node_watchers.write().unwrap().push(Box::new(watcher));
+    }
+
+    /// Returns a reference to the underlying world. Any changes made to the world will not update the reactive system.
+    pub fn raw_world(&self) -> &World {
+        &self.world
+    }
+
+    /// Returns a mutable reference to the underlying world. Any changes made to the world will not update the reactive system.
+    pub fn raw_world_mut(&mut self) -> &mut World {
+        &mut self.world
     }
 }
 
-impl<S: State<V> + Sync, V: FromAnyValue> RealDom<S, V>
-where
-    Tree<Node<S, V>>: Sync + Send,
-{
-    /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts.
-    /// This will resolve the state in parallel
-    pub fn update_state(
-        &mut self,
-        nodes_updated: DirtyNodeStates,
-        ctx: SendAnyMap,
-    ) -> FxDashSet<RealNodeId> {
-        S::update(nodes_updated, &mut self.tree, ctx)
+/// A reference to a tracked component in a node.
+pub struct ViewEntry<'a, V: Component + Send + Sync> {
+    view: View<'a, V>,
+    id: NodeId,
+}
+
+impl<'a, V: Component + Send + Sync> ViewEntry<'a, V> {
+    fn new(view: View<'a, V>, id: NodeId) -> Self {
+        Self { view, id }
+    }
+}
+
+impl<'a, V: Component + Send + Sync> Deref for ViewEntry<'a, V> {
+    type Target = V;
+
+    fn deref(&self) -> &Self::Target {
+        &self.view[self.id]
     }
 }
 
-impl<S: State<V>, V: FromAnyValue> Deref for RealDom<S, V> {
-    type Target = Tree<Node<S, V>>;
+/// A mutable reference to a tracked component in a node.
+pub struct ViewEntryMut<'a, V: Component<Tracking = Untracked> + Send + Sync> {
+    view: ViewMut<'a, V, Untracked>,
+    id: NodeId,
+}
+
+impl<'a, V: Component<Tracking = Untracked> + Send + Sync> ViewEntryMut<'a, V> {
+    fn new(view: ViewMut<'a, V, Untracked>, id: NodeId) -> Self {
+        Self { view, id }
+    }
+}
+
+impl<'a, V: Component<Tracking = Untracked> + Send + Sync> Deref for ViewEntryMut<'a, V> {
+    type Target = V;
 
     fn deref(&self) -> &Self::Target {
-        &self.tree
+        self.view.get(self.id).unwrap()
     }
 }
 
-impl<S: State<V>, V: FromAnyValue> DerefMut for RealDom<S, V> {
+impl<'a, V: Component<Tracking = Untracked> + Send + Sync> DerefMut for ViewEntryMut<'a, V> {
     fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.tree
+        (&mut self.view).get(self.id).unwrap()
     }
 }
 
-impl<S: State<V>, V: FromAnyValue> Index<ElementId> for RealDom<S, V> {
-    type Output = Node<S, V>;
+/// A immutable view of a node
+pub trait NodeImmutable<V: FromAnyValue + Send + Sync = ()>: Sized {
+    /// Get the real dom this node was created in
+    fn real_dom(&self) -> &RealDom<V>;
+
+    /// Get the id of the current node
+    fn id(&self) -> NodeId;
 
-    fn index(&self, id: ElementId) -> &Self::Output {
-        self.tree.get(self.element_to_node_id(id)).unwrap()
+    /// Get the type of the current node
+    #[inline]
+    fn node_type(&self) -> ViewEntry<NodeType<V>> {
+        self.get().unwrap()
+    }
+
+    /// Get a component from the current node
+    #[inline]
+    fn get<'a, T: Component + Sync + Send>(&'a self) -> Option<ViewEntry<'a, T>> {
+        // self.real_dom().tree.get(self.id())
+        let view: View<'a, T> = self.real_dom().borrow_raw().ok()?;
+        view.contains(self.id())
+            .then(|| ViewEntry::new(view, self.id()))
+    }
+
+    /// Get the ids of the children of the current node
+    #[inline]
+    fn child_ids(&self) -> Vec<NodeId> {
+        self.real_dom().tree_ref().children_ids(self.id())
+    }
+
+    /// Get the children of the current node
+    #[inline]
+    fn children(&self) -> Vec<NodeRef<V>> {
+        self.child_ids()
+            .iter()
+            .map(|id| NodeRef {
+                id: *id,
+                dom: self.real_dom(),
+            })
+            .collect()
+    }
+
+    /// Get the id of the parent of the current node
+    #[inline]
+    fn parent_id(&self) -> Option<NodeId> {
+        self.real_dom().tree_ref().parent_id(self.id())
+    }
+
+    /// Get the parent of the current node
+    #[inline]
+    fn parent(&self) -> Option<NodeRef<V>> {
+        self.parent_id().map(|id| NodeRef {
+            id,
+            dom: self.real_dom(),
+        })
+    }
+
+    /// Get the node after the current node
+    #[inline]
+    fn next(&self) -> Option<NodeRef<V>> {
+        let parent = self.parent_id()?;
+        let children = self.real_dom().tree_ref().children_ids(parent);
+        let index = children.iter().position(|id| *id == self.id())?;
+        if index + 1 < children.len() {
+            Some(NodeRef {
+                id: children[index + 1],
+                dom: self.real_dom(),
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Get the node before the current node
+    #[inline]
+    fn prev(&self) -> Option<NodeRef<V>> {
+        let parent = self.parent_id()?;
+        let children = self.real_dom().tree_ref().children_ids(parent);
+        let index = children.iter().position(|id| *id == self.id())?;
+        if index > 0 {
+            Some(NodeRef {
+                id: children[index - 1],
+                dom: self.real_dom(),
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Get the height of the current node in the tree (the number of nodes between the current node and the root)
+    #[inline]
+    fn height(&self) -> u16 {
+        self.real_dom().tree_ref().height(self.id()).unwrap()
+    }
+}
+
+/// An immutable reference to a node in a RealDom
+pub struct NodeRef<'a, V: FromAnyValue + Send + Sync = ()> {
+    id: NodeId,
+    dom: &'a RealDom<V>,
+}
+
+impl<'a, V: FromAnyValue + Send + Sync> Clone for NodeRef<'a, V> {
+    fn clone(&self) -> Self {
+        Self {
+            id: self.id,
+            dom: self.dom,
+        }
+    }
+}
+
+impl<'a, V: FromAnyValue + Send + Sync> Copy for NodeRef<'a, V> {}
+
+impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable<V> for NodeRef<'a, V> {
+    #[inline(always)]
+    fn real_dom(&self) -> &RealDom<V> {
+        self.dom
+    }
+
+    #[inline(always)]
+    fn id(&self) -> NodeId {
+        self.id
     }
 }
 
-impl<S: State<V>, V: FromAnyValue> Index<RealNodeId> for RealDom<S, V> {
-    type Output = Node<S, V>;
+/// A mutable refrence to a node in the RealDom that tracks what States need to be updated
+pub struct NodeMut<'a, V: FromAnyValue + Send + Sync = ()> {
+    id: NodeId,
+    dom: &'a mut RealDom<V>,
+}
 
-    fn index(&self, idx: RealNodeId) -> &Self::Output {
-        self.tree.get(idx).unwrap()
+impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> {
+    /// Create a new mutable refrence to a node in a RealDom
+    pub fn new(id: NodeId, dom: &'a mut RealDom<V>) -> Self {
+        Self { id, dom }
     }
 }
 
-impl<S: State<V>, V: FromAnyValue> IndexMut<ElementId> for RealDom<S, V> {
-    fn index_mut(&mut self, id: ElementId) -> &mut Self::Output {
-        self.tree.get_mut(self.element_to_node_id(id)).unwrap()
+impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable<V> for NodeMut<'a, V> {
+    #[inline(always)]
+    fn real_dom(&self) -> &RealDom<V> {
+        self.dom
+    }
+
+    #[inline(always)]
+    fn id(&self) -> NodeId {
+        self.id
     }
 }
 
-impl<S: State<V>, V: FromAnyValue> IndexMut<RealNodeId> for RealDom<S, V> {
-    fn index_mut(&mut self, idx: RealNodeId) -> &mut Self::Output {
-        self.tree.get_mut(idx).unwrap()
+impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> {
+    /// Get the real dom this node was created in mutably
+    #[inline(always)]
+    pub fn real_dom_mut(&mut self) -> &mut RealDom<V> {
+        self.dom
+    }
+
+    /// Get the parent of this node mutably
+    #[inline]
+    pub fn parent_mut(&mut self) -> Option<NodeMut<V>> {
+        self.parent_id().map(|id| NodeMut { id, dom: self.dom })
+    }
+
+    /// Get a component from the current node mutably
+    #[inline]
+    pub fn get_mut<T: Component<Tracking = Untracked> + Sync + Send>(
+        &mut self,
+    ) -> Option<ViewEntryMut<T>> {
+        // mark the node state as dirty
+        self.dom
+            .dirty_nodes
+            .passes_updated
+            .entry(self.id)
+            .or_default()
+            .insert(TypeId::of::<T>());
+        let view_mut: ViewMut<T> = self.dom.borrow_raw().ok()?;
+        view_mut
+            .contains(self.id)
+            .then_some(ViewEntryMut::new(view_mut, self.id))
+    }
+
+    /// Insert a custom component into this node
+    ///
+    /// Note: Components that implement State and are added when the RealDom is created will automatically be created
+    #[inline]
+    pub fn insert<T: Component + Sync + Send>(&mut self, value: T) {
+        // mark the node state as dirty
+        self.dom
+            .dirty_nodes
+            .passes_updated
+            .entry(self.id)
+            .or_default()
+            .insert(TypeId::of::<T>());
+        self.dom.world.add_component(self.id, value);
+    }
+
+    /// Get the next node
+    #[inline]
+    pub fn next_mut(self) -> Option<NodeMut<'a, V>> {
+        let parent = self.parent_id()?;
+        let children = self.dom.tree_mut().children_ids(parent);
+        let index = children.iter().position(|id| *id == self.id)?;
+        if index + 1 < children.len() {
+            Some(NodeMut::new(children[index + 1], self.dom))
+        } else {
+            None
+        }
+    }
+
+    /// Get the previous node
+    #[inline]
+    pub fn prev_mut(self) -> Option<NodeMut<'a, V>> {
+        let parent = self.parent_id()?;
+        let children = self.dom.tree_ref().children_ids(parent);
+        let index = children.iter().position(|id| *id == self.id)?;
+        if index > 0 {
+            Some(NodeMut::new(children[index - 1], self.dom))
+        } else {
+            None
+        }
+    }
+
+    /// Add the given node to the end of this nodes children
+    #[inline]
+    pub fn add_child(&mut self, child: NodeId) {
+        self.dom.dirty_nodes.mark_child_changed(self.id);
+        self.dom.dirty_nodes.mark_parent_added_or_removed(child);
+        self.dom.tree_mut().add_child(self.id, child);
+        NodeMut::new(child, self.dom).mark_moved();
+    }
+
+    /// Insert this node after the given node
+    #[inline]
+    pub fn insert_after(&mut self, old: NodeId) {
+        let id = self.id();
+        let parent_id = { self.dom.tree_ref().parent_id(old) };
+        if let Some(parent_id) = parent_id {
+            self.dom.dirty_nodes.mark_child_changed(parent_id);
+            self.dom.dirty_nodes.mark_parent_added_or_removed(id);
+        }
+        self.dom.tree_mut().insert_after(old, id);
+        self.mark_moved();
+    }
+
+    /// Insert this node before the given node
+    #[inline]
+    pub fn insert_before(&mut self, old: NodeId) {
+        let id = self.id();
+        let parent_id = { self.dom.tree_ref().parent_id(old) };
+        if let Some(parent_id) = parent_id {
+            self.dom.dirty_nodes.mark_child_changed(parent_id);
+            self.dom.dirty_nodes.mark_parent_added_or_removed(id);
+        }
+        self.dom.tree_mut().insert_before(old, id);
+        self.mark_moved();
+    }
+
+    /// Remove this node from the RealDom
+    #[inline]
+    pub fn remove(&mut self) {
+        let id = self.id();
+        {
+            let RealDom {
+                world,
+                nodes_listening,
+                ..
+            } = &mut self.dom;
+            let mut view: ViewMut<NodeType<V>> = world.borrow().unwrap();
+            if let NodeType::Element(ElementNode { listeners, .. })
+            | NodeType::Text(TextNode { listeners, .. }) = (&mut view).get(id).unwrap()
+            {
+                let listeners = std::mem::take(listeners);
+                for event in listeners {
+                    nodes_listening.get_mut(&event).unwrap().remove(&id);
+                }
+            }
+        }
+        self.mark_removed();
+        let parent_id = { self.dom.tree_ref().parent_id(id) };
+        if let Some(parent_id) = parent_id {
+            self.real_dom_mut()
+                .dirty_nodes
+                .mark_child_changed(parent_id);
+        }
+        let children_ids = self.child_ids();
+        let children_ids_vec = children_ids.to_vec();
+        for child in children_ids_vec {
+            self.dom.get_mut(child).unwrap().remove();
+        }
+        self.dom.tree_mut().remove(id);
+        self.real_dom_mut().raw_world_mut().delete_entity(id);
+    }
+
+    /// Replace this node with a different node
+    #[inline]
+    pub fn replace(mut self, new: NodeId) {
+        self.mark_removed();
+        if let Some(parent_id) = self.parent_id() {
+            self.real_dom_mut()
+                .dirty_nodes
+                .mark_child_changed(parent_id);
+            self.real_dom_mut()
+                .dirty_nodes
+                .mark_parent_added_or_removed(new);
+        }
+        let id = self.id();
+        self.dom.tree_mut().replace(id, new);
+    }
+
+    /// Add an event listener
+    #[inline]
+    pub fn add_event_listener(&mut self, event: &str) {
+        let id = self.id();
+        let RealDom {
+            world,
+            dirty_nodes,
+            nodes_listening,
+            ..
+        } = &mut self.dom;
+        let mut view: ViewMut<NodeType<V>> = world.borrow().unwrap();
+        let node_type: &mut NodeType<V> = (&mut view).get(self.id).unwrap();
+        if let NodeType::Element(ElementNode { listeners, .. })
+        | NodeType::Text(TextNode { listeners, .. }) = node_type
+        {
+            dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build());
+            listeners.insert(event.to_string());
+            match nodes_listening.get_mut(event) {
+                Some(hs) => {
+                    hs.insert(id);
+                }
+                None => {
+                    let mut hs = FxHashSet::default();
+                    hs.insert(id);
+                    nodes_listening.insert(event.to_string(), hs);
+                }
+            }
+        }
+    }
+
+    /// Remove an event listener
+    #[inline]
+    pub fn remove_event_listener(&mut self, event: &str) {
+        let id = self.id();
+        let RealDom {
+            world,
+            dirty_nodes,
+            nodes_listening,
+            ..
+        } = &mut self.dom;
+        let mut view: ViewMut<NodeType<V>> = world.borrow().unwrap();
+        let node_type: &mut NodeType<V> = (&mut view).get(self.id).unwrap();
+        if let NodeType::Element(ElementNode { listeners, .. })
+        | NodeType::Text(TextNode { listeners, .. }) = node_type
+        {
+            dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build());
+            listeners.remove(event);
+
+            nodes_listening.get_mut(event).unwrap().remove(&id);
+        }
+    }
+
+    /// mark that this node was removed for the incremental system
+    fn mark_removed(&mut self) {
+        let watchers = self.dom.node_watchers.clone();
+        for watcher in &*watchers.read().unwrap() {
+            watcher.on_node_removed(NodeMut::new(self.id(), self.dom));
+        }
+    }
+
+    /// mark that this node was moved for the incremental system
+    fn mark_moved(&mut self) {
+        let watchers = self.dom.node_watchers.clone();
+        for watcher in &*watchers.read().unwrap() {
+            watcher.on_node_moved(NodeMut::new(self.id(), self.dom));
+        }
+    }
+
+    /// Get a mutable reference to the type of the current node
+    pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> {
+        let id = self.id();
+        let RealDom {
+            world, dirty_nodes, ..
+        } = &mut self.dom;
+        let view: ViewMut<NodeType<V>> = world.borrow().unwrap();
+        let node_type = ViewEntryMut::new(view, id);
+        match &*node_type {
+            NodeType::Element(_) => NodeTypeMut::Element(ElementNodeMut {
+                id,
+                element: node_type,
+                dirty_nodes,
+            }),
+            NodeType::Text(_) => NodeTypeMut::Text(TextNodeMut {
+                id,
+                text: node_type,
+                dirty_nodes,
+            }),
+            NodeType::Placeholder => NodeTypeMut::Placeholder,
+        }
+    }
+
+    /// Set the type of the current node
+    pub fn set_type(&mut self, new: NodeType<V>) {
+        {
+            let mut view: ViewMut<NodeType<V>> = self.dom.borrow_node_type_mut().unwrap();
+            *(&mut view).get(self.id).unwrap() = new;
+        }
+        self.dom
+            .dirty_nodes
+            .mark_dirty(self.id, NodeMaskBuilder::ALL.build())
+    }
+
+    /// Clone a node and it's children and returns the id of the new node.
+    /// This is more effecient than creating the node from scratch because it can pre-allocate the memory required.
+    #[inline]
+    pub fn clone_node(&mut self) -> NodeId {
+        let new_node = self.node_type().clone();
+        let rdom = self.real_dom_mut();
+        let new_id = rdom.create_node(new_node).id();
+
+        let children = self.child_ids();
+        let children = children.to_vec();
+        let rdom = self.real_dom_mut();
+        for child in children {
+            let child_id = rdom.get_mut(child).unwrap().clone_node();
+            rdom.get_mut(new_id).unwrap().add_child(child_id);
+        }
+        new_id
+    }
+}
+
+/// A mutable refrence to the type of a node in the RealDom
+pub enum NodeTypeMut<'a, V: FromAnyValue + Send + Sync = ()> {
+    /// An element node
+    Element(ElementNodeMut<'a, V>),
+    /// A text node
+    Text(TextNodeMut<'a, V>),
+    /// A placeholder node
+    Placeholder,
+}
+
+/// A mutable refrence to a text node in the RealDom
+pub struct TextNodeMut<'a, V: FromAnyValue + Send + Sync = ()> {
+    id: NodeId,
+    text: ViewEntryMut<'a, NodeType<V>>,
+    dirty_nodes: &'a mut NodesDirty<V>,
+}
+
+impl<V: FromAnyValue + Send + Sync> TextNodeMut<'_, V> {
+    /// Get the underlying test of the node
+    pub fn text(&self) -> &str {
+        match &*self.text {
+            NodeType::Text(text) => &text.text,
+            _ => unreachable!(),
+        }
+    }
+
+    /// Get the underlying text mutably
+    pub fn text_mut(&mut self) -> &mut String {
+        self.dirty_nodes
+            .mark_dirty(self.id, NodeMaskBuilder::new().with_text().build());
+        match &mut *self.text {
+            NodeType::Text(text) => &mut text.text,
+            _ => unreachable!(),
+        }
+    }
+}
+
+impl<V: FromAnyValue + Send + Sync> Deref for TextNodeMut<'_, V> {
+    type Target = String;
+
+    fn deref(&self) -> &Self::Target {
+        match &*self.text {
+            NodeType::Text(text) => &text.text,
+            _ => unreachable!(),
+        }
+    }
+}
+
+impl<V: FromAnyValue + Send + Sync> DerefMut for TextNodeMut<'_, V> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.text_mut()
+    }
+}
+
+/// A mutable refrence to a text Element node in the RealDom
+pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> {
+    id: NodeId,
+    element: ViewEntryMut<'a, NodeType<V>>,
+    dirty_nodes: &'a mut NodesDirty<V>,
+}
+
+impl<V: FromAnyValue + Send + Sync> ElementNodeMut<'_, V> {
+    /// Get the current element
+    fn element(&self) -> &ElementNode<V> {
+        match &*self.element {
+            NodeType::Element(element) => element,
+            _ => unreachable!(),
+        }
+    }
+
+    /// Get the current element mutably (does not mark anything as dirty)
+    fn element_mut(&mut self) -> &mut ElementNode<V> {
+        match &mut *self.element {
+            NodeType::Element(element) => element,
+            _ => unreachable!(),
+        }
+    }
+
+    /// Get the tag of the element
+    pub fn tag(&self) -> &str {
+        &self.element().tag
+    }
+
+    /// Get a mutable reference to the tag of the element
+    pub fn tag_mut(&mut self) -> &mut String {
+        self.dirty_nodes
+            .mark_dirty(self.id, NodeMaskBuilder::new().with_tag().build());
+        &mut self.element_mut().tag
+    }
+
+    /// Get a reference to the namespace the element is in
+    pub fn namespace(&self) -> Option<&str> {
+        self.element().namespace.as_deref()
+    }
+
+    /// Get a mutable reference to the namespace the element is in
+    pub fn namespace_mut(&mut self) -> &mut Option<String> {
+        self.dirty_nodes
+            .mark_dirty(self.id, NodeMaskBuilder::new().with_namespace().build());
+        &mut self.element_mut().namespace
+    }
+
+    /// Get a reference to all of the attributes currently set on the element
+    pub fn attributes(&self) -> &FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>> {
+        &self.element().attributes
+    }
+
+    /// Set an attribute in the element
+    pub fn set_attribute(
+        &mut self,
+        name: impl Into<OwnedAttributeDiscription>,
+        value: impl Into<OwnedAttributeValue<V>>,
+    ) -> Option<OwnedAttributeValue<V>> {
+        let name = name.into();
+        let value = value.into();
+        self.dirty_nodes.mark_dirty(
+            self.id,
+            NodeMaskBuilder::new()
+                .with_attrs(AttributeMaskBuilder::Some(&[&name.name]))
+                .build(),
+        );
+        self.element_mut().attributes.insert(name, value)
+    }
+
+    /// Remove an attribute from the element
+    pub fn remove_attribute(
+        &mut self,
+        name: &OwnedAttributeDiscription,
+    ) -> Option<OwnedAttributeValue<V>> {
+        self.dirty_nodes.mark_dirty(
+            self.id,
+            NodeMaskBuilder::new()
+                .with_attrs(AttributeMaskBuilder::Some(&[&name.name]))
+                .build(),
+        );
+        self.element_mut().attributes.remove(name)
+    }
+
+    /// Get an attribute of the element
+    pub fn get_attribute_mut(
+        &mut self,
+        name: &OwnedAttributeDiscription,
+    ) -> Option<&mut OwnedAttributeValue<V>> {
+        self.dirty_nodes.mark_dirty(
+            self.id,
+            NodeMaskBuilder::new()
+                .with_attrs(AttributeMaskBuilder::Some(&[&name.name]))
+                .build(),
+        );
+        self.element_mut().attributes.get_mut(name)
+    }
+
+    /// Get the set of all events the element is listening to
+    pub fn listeners(&self) -> &FxHashSet<String> {
+        &self.element().listeners
+    }
+}
+
+// Create a workload from all of the passes. This orders the passes so that each pass will only run at most once.
+fn construct_workload<V: FromAnyValue + Send + Sync>(
+    passes: &mut [TypeErasedState<V>],
+) -> Workload {
+    let mut workload = Workload::new("Main Workload");
+    // Assign a unique index to keep track of each pass
+    let mut unresloved_workloads = passes
+        .iter_mut()
+        .enumerate()
+        .map(|(i, pass)| {
+            let workload = Some(pass.create_workload());
+            (i, pass, workload)
+        })
+        .collect::<Vec<_>>();
+    // set all the labels
+    for (id, _, workload) in &mut unresloved_workloads {
+        *workload = Some(workload.take().unwrap().tag(id.to_string()));
+    }
+    // mark any dependancies
+    for i in 0..unresloved_workloads.len() {
+        let (_, pass, _) = &unresloved_workloads[i];
+        let all_dependancies: Vec<_> = pass.combined_dependancy_type_ids().collect();
+        for ty_id in all_dependancies {
+            let &(dependancy_id, _, _) = unresloved_workloads
+                .iter()
+                .find(|(_, pass, _)| pass.this_type_id == ty_id)
+                .unwrap();
+            let (_, _, workload) = &mut unresloved_workloads[i];
+            *workload = workload
+                .take()
+                .map(|workload| workload.after_all(dependancy_id.to_string()));
+        }
+    }
+    // Add all of the passes
+    for (_, _, mut workload_system) in unresloved_workloads {
+        workload = workload.with_system(workload_system.take().unwrap());
     }
+    workload
 }

+ 0 - 305
packages/native-core/src/state.rs

@@ -1,305 +0,0 @@
-use std::cmp::Ordering;
-
-use crate::node::{FromAnyValue, Node};
-use crate::node_ref::{NodeMask, NodeView};
-use crate::passes::{resolve_passes, resolve_passes_single_threaded, AnyPass, DirtyNodeStates};
-use crate::tree::TreeView;
-use crate::{FxDashSet, RealNodeId, SendAnyMap};
-
-/// Join two sorted iterators
-pub(crate) fn union_ordered_iter<'a>(
-    s_iter: impl Iterator<Item = &'a str>,
-    o_iter: impl Iterator<Item = &'a str>,
-    new_len_guess: usize,
-) -> Vec<String> {
-    let mut s_peekable = s_iter.peekable();
-    let mut o_peekable = o_iter.peekable();
-    let mut v = Vec::with_capacity(new_len_guess);
-    while let Some(s_i) = s_peekable.peek() {
-        while let Some(o_i) = o_peekable.peek() {
-            match o_i.cmp(s_i) {
-                Ordering::Greater => {
-                    break;
-                }
-                Ordering::Less => {
-                    v.push(o_peekable.next().unwrap().to_string());
-                }
-                Ordering::Equal => {
-                    o_peekable.next();
-                    break;
-                }
-            }
-        }
-        v.push(s_peekable.next().unwrap().to_string());
-    }
-    for o_i in o_peekable {
-        v.push(o_i.to_string());
-    }
-    for w in v.windows(2) {
-        debug_assert!(w[1] > w[0]);
-    }
-    v
-}
-
-/// This state is derived from children. For example a node's size could be derived from the size of children.
-/// Called when the current node's node properties are modified, a child's [ChildDepState] is modified or a child is removed.
-/// Called at most once per update.
-/// ```rust
-/// # use dioxus_native_core::node_ref::NodeView;
-/// # use dioxus_native_core::state::ChildDepState;
-/// #[derive(Clone, Copy, PartialEq, Default)]
-/// struct Layout {
-///     width: u32,
-///     height: u32,
-/// }
-///
-/// impl ChildDepState for Layout {
-///     type Ctx = ();
-///     // The layout depends on the layout of the children.
-///     type DepState = (Layout,);
-///     fn reduce<'a>(
-///         &mut self,
-///         _node: NodeView,
-///         children: impl Iterator<Item = (&'a Self,)>,
-///         _ctx: &Self::Ctx,
-///     ) -> bool
-///     where
-///         Self::DepState: 'a{
-///         /// Children are layed out form left to right. The width of the parent is the sum of the widths and the max of the heights.
-///         let new = children.map(|(&c,)|c).reduce(|c1, c2| Layout{
-///             width: c1.width + c2.width,
-///             height: c1.height.max(c2.height)
-///         }).unwrap_or_default();
-///         let changed = new != *self;
-///         *self = new;
-///         changed
-///     }
-/// }
-/// ```
-pub trait ChildDepState<V: FromAnyValue = ()> {
-    /// The context is passed to the [ChildDepState::reduce] when it is resolved.
-    type Ctx;
-    /// A state from each child node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
-    /// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState] or [NodeDepState].
-    type DepState: ElementBorrowable;
-    /// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
-    const NODE_MASK: NodeMask = NodeMask::NONE;
-    /// Resolve the state current node's state from the state of the children, the state of the node, and some external context.
-    fn reduce<'a>(
-        &mut self,
-        node: NodeView<'a, V>,
-        children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
-        ctx: &Self::Ctx,
-    ) -> bool
-    where
-        Self::DepState: 'a;
-}
-
-/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
-/// Called when the current node's node properties are modified or a parrent's [ParentDepState] is modified.
-/// Called at most once per update.
-/// ```rust
-/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView};
-/// use dioxus_native_core::state::*;
-///
-/// #[derive(Clone, Copy, PartialEq)]
-/// struct FontSize(usize);
-///
-/// impl ParentDepState for FontSize {
-///     type Ctx = ();
-///     // The font size depends on the font size of the parent element.
-///     type DepState = (Self,);
-///     const NODE_MASK: NodeMask =
-///         NodeMask::new_with_attrs(AttributeMask::Static(&[
-///             "font-size"
-///         ]));
-///     fn reduce<'a>(
-///         &mut self,
-///         node: NodeView,
-///         parent: Option<(&'a Self,)>,
-///         ctx: &Self::Ctx,
-///     ) -> bool{
-///         let old = *self;
-///         // If the font size was set on the parent, it is passed down to the current element
-///         if let Some((parent,)) = parent {
-///             *self = *parent;
-///         }
-///         // If the current node overrides the font size, use that size insead.
-///         for attr in node.attributes().unwrap() {
-///             match attr.attribute.name.as_str() {
-///                 "font-size" => {
-///                     self.0 = attr.value.as_text().unwrap().parse().unwrap();
-///                 }
-///                 // font-size is the only attribute we specified in the mask, so it is the only one we can see
-///                 _ => unreachable!(),
-///             }
-///         }
-///         old != *self
-///     }
-/// }
-/// ```
-pub trait ParentDepState<V: FromAnyValue = ()> {
-    /// The context is passed to the [ParentDepState::reduce] when it is resolved.
-    type Ctx;
-    /// A state from from the parent node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
-    /// Depstate must be a tuple containing any number of borrowed elements that are either [ParentDepState] or [NodeDepState].
-    type DepState: ElementBorrowable;
-    /// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
-    const NODE_MASK: NodeMask = NodeMask::NONE;
-    /// Resolve the state current node's state from the state of the parent node, the state of the node, and some external context.
-    fn reduce<'a>(
-        &mut self,
-        node: NodeView<'a, V>,
-        parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
-        ctx: &Self::Ctx,
-    ) -> bool;
-}
-
-/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
-/// Called when the current node's node properties are modified or a one of its dependanices are modified.
-/// Called at most once per update.
-/// NodeDepState is the only state that can accept multiple dependancies, but only from the current node.
-/// ```rust
-/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView};
-/// use dioxus_native_core::state::*;
-///
-/// #[derive(Clone, Copy, PartialEq)]
-/// struct TabIndex(usize);
-///
-/// impl NodeDepState for TabIndex {
-///     type Ctx = ();
-///     type DepState = ();
-///     const NODE_MASK: NodeMask =
-///         NodeMask::new_with_attrs(AttributeMask::Static(&[
-///             "tabindex"
-///         ]));
-///     fn reduce(
-///         &mut self,
-///         node: NodeView,
-///         siblings: (),
-///         ctx: &(),
-///     ) -> bool {
-///         let old = self.clone();
-///         for attr in node.attributes().unwrap() {
-///             match attr.attribute.name.as_str() {
-///                 "tabindex" => {
-///                     self.0 = attr.value.as_text().unwrap().parse().unwrap();
-///                 }
-///                 // tabindex is the only attribute we specified in the mask, so it is the only one we can see
-///                 _ => unreachable!(),
-///             }
-///         }
-///         old != *self
-///     }
-/// }
-/// ```
-pub trait NodeDepState<V: FromAnyValue = ()> {
-    /// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState], [ParentDepState] or [NodeDepState].
-    type DepState: ElementBorrowable;
-    /// The state passed to [NodeDepState::reduce] when it is resolved.
-    type Ctx;
-    /// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
-    const NODE_MASK: NodeMask = NodeMask::NONE;
-    /// Resolve the state current node's state from the state of the sibling states, the state of the node, and some external context.
-    fn reduce<'a>(
-        &mut self,
-        node: NodeView<'a, V>,
-        node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>,
-        ctx: &Self::Ctx,
-    ) -> bool;
-}
-
-/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
-pub trait State<V: FromAnyValue + 'static>: Default + Clone + 'static {
-    #[doc(hidden)]
-    const PASSES: &'static [AnyPass<Node<Self, V>>];
-    #[doc(hidden)]
-    const MASKS: &'static [NodeMask];
-
-    #[doc(hidden)]
-    fn update<T: TreeView<Node<Self, V>> + Sync + Send>(
-        dirty: DirtyNodeStates,
-        tree: &mut T,
-        ctx: SendAnyMap,
-    ) -> FxDashSet<RealNodeId> {
-        let passes = Self::PASSES.iter().collect();
-        resolve_passes(tree, dirty, passes, ctx)
-    }
-
-    #[doc(hidden)]
-    fn update_single_threaded<T: TreeView<Node<Self, V>>>(
-        dirty: DirtyNodeStates,
-        tree: &mut T,
-        ctx: SendAnyMap,
-    ) -> FxDashSet<RealNodeId> {
-        let passes = Self::PASSES.iter().collect();
-        resolve_passes_single_threaded(tree, dirty, passes, ctx)
-    }
-}
-
-impl ChildDepState for () {
-    type Ctx = ();
-    type DepState = ();
-    fn reduce<'a>(&mut self, _: NodeView<'a>, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
-    where
-        Self::DepState: 'a,
-    {
-        false
-    }
-}
-
-impl ParentDepState for () {
-    type Ctx = ();
-    type DepState = ();
-    fn reduce(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool {
-        false
-    }
-}
-
-impl NodeDepState for () {
-    type DepState = ();
-    type Ctx = ();
-    fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool {
-        false
-    }
-}
-
-pub trait ElementBorrowable {
-    type ElementBorrowed<'a>
-    where
-        Self: 'a;
-
-    fn borrow_elements(&self) -> Self::ElementBorrowed<'_>;
-}
-
-macro_rules! impl_element_borrowable {
-    ($($t:ident),*) => {
-        impl< $($t),* > ElementBorrowable for ($($t,)*) {
-            type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a;
-
-            #[allow(clippy::unused_unit, non_snake_case)]
-            fn borrow_elements<'a>(&'a self) -> Self::ElementBorrowed<'a> {
-                let ($($t,)*) = self;
-                ($(&$t,)*)
-            }
-        }
-    };
-}
-
-impl_element_borrowable!();
-impl_element_borrowable!(A);
-impl_element_borrowable!(A, B);
-impl_element_borrowable!(A, B, C);
-impl_element_borrowable!(A, B, C, D);
-impl_element_borrowable!(A, B, C, D, E);
-impl_element_borrowable!(A, B, C, D, E, F);
-impl_element_borrowable!(A, B, C, D, E, F, G);
-impl_element_borrowable!(A, B, C, D, E, F, G, H);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
-impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);

+ 215 - 561
packages/native-core/src/tree.rs

@@ -1,669 +1,323 @@
-use std::collections::VecDeque;
-use std::marker::PhantomData;
+//! A tree of nodes intigated with shipyard
 
-#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
-pub struct NodeId(pub usize);
+use crate::NodeId;
+use shipyard::{Component, EntitiesViewMut, Get, View, ViewMut};
+use std::fmt::Debug;
 
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct Node<T> {
-    value: T,
+/// A node in a tree.
+#[derive(PartialEq, Eq, Clone, Debug, Component)]
+pub struct Node {
     parent: Option<NodeId>,
     children: Vec<NodeId>,
     height: u16,
 }
 
-#[derive(Debug)]
-pub struct Tree<T> {
-    nodes: Slab<Node<T>>,
-    root: NodeId,
-}
-
-impl<T> Tree<T> {
-    fn try_remove(&mut self, id: NodeId) -> Option<Node<T>> {
-        self.nodes.try_remove(id.0).map(|node| {
-            if let Some(parent) = node.parent {
-                self.nodes
-                    .get_mut(parent.0)
-                    .unwrap()
-                    .children
-                    .retain(|child| child != &id);
-            }
-            for child in &node.children {
-                self.remove_recursive(*child);
-            }
-            node
-        })
-    }
-
-    fn remove_recursive(&mut self, node: NodeId) {
-        let node = self.nodes.remove(node.0);
-        for child in node.children {
-            self.remove_recursive(child);
-        }
-    }
-
-    fn set_height(&mut self, node: NodeId, height: u16) {
-        let self_mut = self as *mut Self;
-        let node = self.nodes.get_mut(node.0).unwrap();
-        node.height = height;
-        unsafe {
-            // Safety: No node has itself as a child
-            for child in &node.children {
-                (*self_mut).set_height(*child, height + 1);
-            }
-        }
-    }
-}
-
-pub trait TreeView<T>: Sized {
-    type Iterator<'a>: Iterator<Item = &'a T>
-    where
-        T: 'a,
-        Self: 'a;
-    type IteratorMut<'a>: Iterator<Item = &'a mut T>
-    where
-        T: 'a,
-        Self: 'a;
-
-    fn root(&self) -> NodeId;
-
-    fn contains(&self, id: NodeId) -> bool {
-        self.get(id).is_some()
-    }
-
-    fn get(&self, id: NodeId) -> Option<&T>;
-
-    fn get_unchecked(&self, id: NodeId) -> &T {
-        unsafe { self.get(id).unwrap_unchecked() }
-    }
-
-    fn get_mut(&mut self, id: NodeId) -> Option<&mut T>;
-
-    fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T {
-        unsafe { self.get_mut(id).unwrap_unchecked() }
-    }
-
-    fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>>;
-
-    fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>>;
-
-    fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)>;
-
-    fn children_ids(&self, id: NodeId) -> Option<&[NodeId]>;
-
-    fn parent(&self, id: NodeId) -> Option<&T>;
-
-    fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>;
-
-    fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>;
+/// A view of a tree.
+pub type TreeRefView<'a> = View<'a, Node>;
+/// A mutable view of a tree.
+pub type TreeMutView<'a> = (EntitiesViewMut<'a>, ViewMut<'a, Node>);
 
+/// A immutable view of a tree.
+pub trait TreeRef {
+    /// The parent id of the node.
     fn parent_id(&self, id: NodeId) -> Option<NodeId>;
-
+    /// The children ids of the node.
+    fn children_ids(&self, id: NodeId) -> Vec<NodeId>;
+    /// The height of the node.
     fn height(&self, id: NodeId) -> Option<u16>;
-
-    fn size(&self) -> usize;
-
-    fn traverse_depth_first(&self, mut f: impl FnMut(&T)) {
-        let mut stack = vec![self.root()];
-        while let Some(id) = stack.pop() {
-            if let Some(node) = self.get(id) {
-                f(node);
-                if let Some(children) = self.children_ids(id) {
-                    stack.extend(children.iter().copied().rev());
-                }
-            }
-        }
-    }
-
-    fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut T)) {
-        let mut stack = vec![self.root()];
-        while let Some(id) = stack.pop() {
-            if let Some(node) = self.get_mut(id) {
-                f(node);
-                if let Some(children) = self.children_ids(id) {
-                    stack.extend(children.iter().copied().rev());
-                }
-            }
-        }
-    }
-
-    fn traverse_breadth_first(&self, mut f: impl FnMut(&T)) {
-        let mut queue = VecDeque::new();
-        queue.push_back(self.root());
-        while let Some(id) = queue.pop_front() {
-            if let Some(node) = self.get(id) {
-                f(node);
-                if let Some(children) = self.children_ids(id) {
-                    for id in children {
-                        queue.push_back(*id);
-                    }
-                }
-            }
-        }
-    }
-
-    fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(&mut T)) {
-        let mut queue = VecDeque::new();
-        queue.push_back(self.root());
-        while let Some(id) = queue.pop_front() {
-            if let Some(node) = self.get_mut(id) {
-                f(node);
-                if let Some(children) = self.children_ids(id) {
-                    for id in children {
-                        queue.push_back(*id);
-                    }
-                }
-            }
-        }
-    }
-}
-
-pub trait TreeLike<T>: TreeView<T> {
-    fn new(root: T) -> Self;
-
-    fn create_node(&mut self, value: T) -> NodeId;
-
-    fn add_child(&mut self, parent: NodeId, child: NodeId);
-
-    fn remove(&mut self, id: NodeId) -> Option<T>;
-
-    fn remove_all_children(&mut self, id: NodeId) -> Vec<T>;
-
-    fn replace(&mut self, old: NodeId, new: NodeId);
-
-    fn insert_before(&mut self, id: NodeId, new: NodeId);
-
-    fn insert_after(&mut self, id: NodeId, new: NodeId);
-}
-
-pub struct ChildNodeIterator<'a, T> {
-    nodes: &'a Slab<Node<T>>,
-    children_ids: Vec<NodeId>,
-    index: usize,
-    node_type: PhantomData<T>,
-}
-
-impl<'a, T: 'a> Iterator for ChildNodeIterator<'a, T> {
-    type Item = &'a T;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.children_ids.get(self.index).map(|id| {
-            self.index += 1;
-            &self.nodes.get(id.0).unwrap().value
-        })
-    }
-}
-
-pub struct ChildNodeIteratorMut<'a, T> {
-    nodes: Vec<&'a mut Node<T>>,
+    /// Returns true if the node exists.
+    fn contains(&self, id: NodeId) -> bool;
 }
 
-impl<'a, T: 'a> Iterator for ChildNodeIteratorMut<'a, T> {
-    type Item = &'a mut T;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.nodes.pop().map(|node| &mut node.value)
-    }
+/// A mutable view of a tree.
+pub trait TreeMut: TreeRef {
+    /// Removes the node and all of its children.
+    fn remove(&mut self, id: NodeId);
+    /// Removes the node and all of its children.
+    fn remove_single(&mut self, id: NodeId);
+    /// Adds a new node to the tree.
+    fn create_node(&mut self, id: NodeId);
+    /// Adds a child to the node.
+    fn add_child(&mut self, parent: NodeId, new: NodeId);
+    /// Replaces the node with a new node.
+    fn replace(&mut self, old_id: NodeId, new_id: NodeId);
+    /// Inserts a node before another node.
+    fn insert_before(&mut self, old_id: NodeId, new_id: NodeId);
+    /// Inserts a node after another node.
+    fn insert_after(&mut self, old_id: NodeId, new_id: NodeId);
 }
 
-impl<T> TreeView<T> for Tree<T> {
-    type Iterator<'a> = ChildNodeIterator<'a, T> where T: 'a;
-    type IteratorMut<'a> = ChildNodeIteratorMut<'a, T> where T: 'a;
-
-    fn root(&self) -> NodeId {
-        self.root
+impl<'a> TreeRef for TreeRefView<'a> {
+    fn parent_id(&self, id: NodeId) -> Option<NodeId> {
+        self.get(id).ok()?.parent
     }
 
-    fn get(&self, id: NodeId) -> Option<&T> {
-        self.nodes.get(id.0).map(|node| &node.value)
+    fn children_ids(&self, id: NodeId) -> Vec<NodeId> {
+        self.get(id)
+            .map(|node| node.children.clone())
+            .unwrap_or_default()
     }
 
-    fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
-        self.nodes.get_mut(id.0).map(|node| &mut node.value)
+    fn height(&self, id: NodeId) -> Option<u16> {
+        Some(self.get(id).ok()?.height)
     }
 
-    fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
-        self.children_ids(id).map(|children_ids| ChildNodeIterator {
-            nodes: &self.nodes,
-            children_ids: children_ids.to_vec(),
-            index: 0,
-            node_type: PhantomData,
-        })
+    fn contains(&self, id: NodeId) -> bool {
+        self.get(id).is_ok()
     }
+}
 
-    fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
-        // Safety: No node has itself as a parent.
-        if let Some(children_ids) = self.children_ids(id) {
-            let children_ids = children_ids.to_vec();
-            Some(ChildNodeIteratorMut {
-                nodes: unsafe {
-                    self.nodes
-                        .get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id.0))
-                        .unwrap()
-                },
-            })
-        } else {
-            None
+impl<'a> TreeMut for TreeMutView<'a> {
+    fn remove(&mut self, id: NodeId) {
+        fn recurse(tree: &mut TreeMutView<'_>, id: NodeId) {
+            let children = tree.children_ids(id);
+            for child in children {
+                recurse(tree, child);
+            }
         }
-    }
-
-    fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
-        self.nodes.get(id.0).map(|node| node.children.as_slice())
-    }
-
-    fn parent(&self, id: NodeId) -> Option<&T> {
-        self.nodes
-            .get(id.0)
-            .and_then(|node| node.parent.map(|id| self.nodes.get(id.0).unwrap()))
-            .map(|node| &node.value)
-    }
-
-    fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> {
-        let self_ptr = self as *mut Self;
-        unsafe {
-            // Safety: No node has itself as a parent.
-            self.nodes
-                .get_mut(id.0)
-                .and_then(move |node| {
-                    node.parent
-                        .map(move |id| (*self_ptr).nodes.get_mut(id.0).unwrap())
-                })
-                .map(|node| &mut node.value)
+        {
+            let mut node_data_mut = &mut self.1;
+            if let Some(parent) = node_data_mut.get(id).unwrap().parent {
+                let parent = (&mut node_data_mut).get(parent).unwrap();
+                parent.children.retain(|&child| child != id);
+            }
         }
-    }
-
-    fn parent_id(&self, id: NodeId) -> Option<NodeId> {
-        self.nodes.get(id.0).and_then(|node| node.parent)
-    }
-
-    fn height(&self, id: NodeId) -> Option<u16> {
-        self.nodes.get(id.0).map(|n| n.height)
-    }
 
-    fn get_unchecked(&self, id: NodeId) -> &T {
-        unsafe { &self.nodes.get_unchecked(id.0).value }
+        recurse(self, id);
     }
 
-    fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T {
-        unsafe { &mut self.nodes.get_unchecked_mut(id.0).value }
-    }
-
-    fn size(&self) -> usize {
-        self.nodes.len()
-    }
-
-    fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> {
-        if let Some(parent_id) = self.parent_id(id) {
-            self.nodes
-                .get2_mut(id.0, parent_id.0)
-                .map(|(node, parent)| (&mut node.value, Some(&mut parent.value)))
-        } else {
-            self.nodes.get_mut(id.0).map(|node| (&mut node.value, None))
-        }
-    }
-
-    fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> {
-        // Safety: No node will appear as a child twice
-        if let Some(children_ids) = self.children_ids(id) {
-            debug_assert!(!children_ids.iter().any(|child_id| *child_id == id));
-            let mut borrowed = unsafe {
-                let as_vec = children_ids.to_vec();
-                self.nodes
-                    .get_many_mut_unchecked(
-                        as_vec
-                            .into_iter()
-                            .rev()
-                            .map(|id| id.0)
-                            .chain(std::iter::once(id.0)),
-                    )
-                    .unwrap()
-            };
-            let node = &mut borrowed.pop().unwrap().value;
-            Some((node, ChildNodeIteratorMut { nodes: borrowed }))
-        } else {
-            None
+    fn remove_single(&mut self, id: NodeId) {
+        {
+            let mut node_data_mut = &mut self.1;
+            if let Some(parent) = node_data_mut.get(id).unwrap().parent {
+                let parent = (&mut node_data_mut).get(parent).unwrap();
+                parent.children.retain(|&child| child != id);
+            }
         }
     }
-}
-
-impl<T> TreeLike<T> for Tree<T> {
-    fn new(root: T) -> Self {
-        let mut nodes = Slab::default();
-        let root = NodeId(nodes.insert(Node {
-            value: root,
-            parent: None,
-            children: Vec::new(),
-            height: 0,
-        }));
-        Self { nodes, root }
-    }
 
-    fn create_node(&mut self, value: T) -> NodeId {
-        NodeId(self.nodes.insert(Node {
-            value,
-            parent: None,
-            children: Vec::new(),
-            height: 0,
-        }))
+    fn create_node(&mut self, id: NodeId) {
+        let (entities, node_data_mut) = self;
+        entities.add_component(
+            id,
+            node_data_mut,
+            Node {
+                parent: None,
+                children: Vec::new(),
+                height: 0,
+            },
+        );
     }
 
     fn add_child(&mut self, parent: NodeId, new: NodeId) {
-        self.nodes.get_mut(new.0).unwrap().parent = Some(parent);
-        let parent = self.nodes.get_mut(parent.0).unwrap();
-        parent.children.push(new);
-        let height = parent.height + 1;
-        self.set_height(new, height);
-    }
-
-    fn remove(&mut self, id: NodeId) -> Option<T> {
-        self.try_remove(id).map(|node| node.value)
-    }
-
-    fn remove_all_children(&mut self, id: NodeId) -> Vec<T> {
-        let mut children = Vec::new();
-        let self_mut = self as *mut Self;
-        for child in self.children_ids(id).unwrap() {
-            unsafe {
-                // Safety: No node has itself as a child
-                children.push((*self_mut).remove(*child).unwrap());
-            }
+        let height;
+        {
+            let mut node_state = &mut self.1;
+            (&mut node_state).get(new).unwrap().parent = Some(parent);
+            let parent = (&mut node_state).get(parent).unwrap();
+            parent.children.push(new);
+            height = parent.height + 1;
         }
-        children
+        set_height(self, new, height);
     }
 
     fn replace(&mut self, old_id: NodeId, new_id: NodeId) {
-        // remove the old node
-        let old = self
-            .try_remove(old_id)
-            .expect("tried to replace a node that doesn't exist");
-        // update the parent's link to the child
-        if let Some(parent_id) = old.parent {
-            let parent = self.nodes.get_mut(parent_id.0).unwrap();
-            for id in &mut parent.children {
-                if *id == old_id {
-                    *id = new_id;
+        {
+            let mut node_state = &mut self.1;
+            // update the parent's link to the child
+            if let Some(parent_id) = node_state.get(old_id).unwrap().parent {
+                let parent = (&mut node_state).get(parent_id).unwrap();
+                for id in &mut parent.children {
+                    if *id == old_id {
+                        *id = new_id;
+                        break;
+                    }
                 }
+                let height = parent.height + 1;
+                set_height(self, new_id, height);
             }
-            let height = parent.height + 1;
-            self.set_height(new_id, height);
         }
+        // remove the old node
+        self.remove(old_id);
     }
 
-    fn insert_before(&mut self, id: NodeId, new: NodeId) {
-        let node = self.nodes.get(id.0).unwrap();
-        let parent_id = node.parent.expect("tried to insert before root");
-        self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id);
-        let parent = self.nodes.get_mut(parent_id.0).unwrap();
+    fn insert_before(&mut self, old_id: NodeId, new_id: NodeId) {
+        let mut node_state = &mut self.1;
+        let old_node = node_state.get(old_id).unwrap();
+        let parent_id = old_node.parent.expect("tried to insert before root");
+        (&mut node_state).get(new_id).unwrap().parent = Some(parent_id);
+        let parent = (&mut node_state).get(parent_id).unwrap();
         let index = parent
             .children
             .iter()
-            .position(|child| child == &id)
+            .position(|child| *child == old_id)
             .unwrap();
-        parent.children.insert(index, new);
+        parent.children.insert(index, new_id);
         let height = parent.height + 1;
-        self.set_height(new, height);
+        set_height(self, new_id, height);
     }
 
-    fn insert_after(&mut self, id: NodeId, new: NodeId) {
-        let node = self.nodes.get(id.0).unwrap();
-        let parent_id = node.parent.expect("tried to insert before root");
-        self.nodes.get_mut(new.0).unwrap().parent = Some(parent_id);
-        let parent = self.nodes.get_mut(parent_id.0).unwrap();
+    fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) {
+        let mut node_state = &mut self.1;
+        let old_node = node_state.get(old_id).unwrap();
+        let parent_id = old_node.parent.expect("tried to insert before root");
+        (&mut node_state).get(new_id).unwrap().parent = Some(parent_id);
+        let parent = (&mut node_state).get(parent_id).unwrap();
         let index = parent
             .children
             .iter()
-            .position(|child| child == &id)
+            .position(|child| *child == old_id)
             .unwrap();
-        parent.children.insert(index + 1, new);
+        parent.children.insert(index + 1, new_id);
         let height = parent.height + 1;
-        self.set_height(new, height);
+        set_height(self, new_id, height);
+    }
+}
+
+/// Sets the height of a node and updates the height of all its children
+fn set_height(tree: &mut TreeMutView<'_>, node: NodeId, height: u16) {
+    let children = {
+        let mut node_data_mut = &mut tree.1;
+        let mut node = (&mut node_data_mut).get(node).unwrap();
+        node.height = height;
+        node.children.clone()
+    };
+    for child in children {
+        set_height(tree, child, height + 1);
+    }
+}
+
+impl<'a> TreeRef for TreeMutView<'a> {
+    fn parent_id(&self, id: NodeId) -> Option<NodeId> {
+        let node_data = &self.1;
+        node_data.get(id).unwrap().parent
+    }
+
+    fn children_ids(&self, id: NodeId) -> Vec<NodeId> {
+        let node_data = &self.1;
+        node_data
+            .get(id)
+            .map(|node| node.children.clone())
+            .unwrap_or_default()
+    }
+
+    fn height(&self, id: NodeId) -> Option<u16> {
+        let node_data = &self.1;
+        node_data.get(id).map(|node| node.height).ok()
+    }
+
+    fn contains(&self, id: NodeId) -> bool {
+        self.1.get(id).is_ok()
     }
 }
+
 #[test]
 fn creation() {
-    let mut tree = Tree::new(1);
-    let parent = tree.root();
-    let child = tree.create_node(0);
-    tree.add_child(parent, child);
+    use shipyard::World;
+    #[derive(Component)]
+    struct Num(i32);
 
-    println!("Tree: {tree:#?}");
-    assert_eq!(tree.size(), 2);
-    assert_eq!(tree.height(parent), Some(0));
-    assert_eq!(tree.height(child), Some(1));
-    assert_eq!(*tree.get(parent).unwrap(), 1);
-    assert_eq!(*tree.get(child).unwrap(), 0);
-    assert_eq!(tree.parent_id(parent), None);
-    assert_eq!(tree.parent_id(child).unwrap(), parent);
-    assert_eq!(tree.children_ids(parent).unwrap(), &[child]);
+    let mut world = World::new();
+    let parent_id = world.add_entity(Num(1i32));
+    let child_id = world.add_entity(Num(0i32));
+
+    let mut tree = world.borrow::<TreeMutView>().unwrap();
+
+    tree.create_node(parent_id);
+    tree.create_node(child_id);
+
+    tree.add_child(parent_id, child_id);
+
+    assert_eq!(tree.height(parent_id), Some(0));
+    assert_eq!(tree.height(child_id), Some(1));
+    assert_eq!(tree.parent_id(parent_id), None);
+    assert_eq!(tree.parent_id(child_id).unwrap(), parent_id);
+    assert_eq!(tree.children_ids(parent_id), &[child_id]);
 }
 
 #[test]
 fn insertion() {
-    let mut tree = Tree::new(0);
-    let parent = tree.root();
-    let child = tree.create_node(2);
+    use shipyard::World;
+    #[derive(Component)]
+    struct Num(i32);
+
+    let mut world = World::new();
+    let parent = world.add_entity(Num(0));
+    let child = world.add_entity(Num(2));
+    let before = world.add_entity(Num(1));
+    let after = world.add_entity(Num(3));
+
+    let mut tree = world.borrow::<TreeMutView>().unwrap();
+
+    tree.create_node(parent);
+    tree.create_node(child);
+    tree.create_node(before);
+    tree.create_node(after);
+
     tree.add_child(parent, child);
-    let before = tree.create_node(1);
     tree.insert_before(child, before);
-    let after = tree.create_node(3);
     tree.insert_after(child, after);
 
-    println!("Tree: {tree:#?}");
-    assert_eq!(tree.size(), 4);
     assert_eq!(tree.height(parent), Some(0));
     assert_eq!(tree.height(child), Some(1));
     assert_eq!(tree.height(before), Some(1));
     assert_eq!(tree.height(after), Some(1));
-    assert_eq!(*tree.get(parent).unwrap(), 0);
-    assert_eq!(*tree.get(before).unwrap(), 1);
-    assert_eq!(*tree.get(child).unwrap(), 2);
-    assert_eq!(*tree.get(after).unwrap(), 3);
     assert_eq!(tree.parent_id(before).unwrap(), parent);
     assert_eq!(tree.parent_id(child).unwrap(), parent);
     assert_eq!(tree.parent_id(after).unwrap(), parent);
-    assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]);
+    assert_eq!(tree.children_ids(parent), &[before, child, after]);
 }
 
 #[test]
 fn deletion() {
-    let mut tree = Tree::new(0);
-    let parent = tree.root();
-    let child = tree.create_node(2);
+    use shipyard::World;
+    #[derive(Component)]
+    struct Num(i32);
+
+    let mut world = World::new();
+    let parent = world.add_entity(Num(0));
+    let child = world.add_entity(Num(2));
+    let before = world.add_entity(Num(1));
+    let after = world.add_entity(Num(3));
+
+    let mut tree = world.borrow::<TreeMutView>().unwrap();
+
+    tree.create_node(parent);
+    tree.create_node(child);
+    tree.create_node(before);
+    tree.create_node(after);
+
     tree.add_child(parent, child);
-    let before = tree.create_node(1);
     tree.insert_before(child, before);
-    let after = tree.create_node(3);
     tree.insert_after(child, after);
 
-    println!("Tree: {tree:#?}");
-    assert_eq!(tree.size(), 4);
     assert_eq!(tree.height(parent), Some(0));
     assert_eq!(tree.height(child), Some(1));
     assert_eq!(tree.height(before), Some(1));
     assert_eq!(tree.height(after), Some(1));
-    assert_eq!(*tree.get(parent).unwrap(), 0);
-    assert_eq!(*tree.get(before).unwrap(), 1);
-    assert_eq!(*tree.get(child).unwrap(), 2);
-    assert_eq!(*tree.get(after).unwrap(), 3);
     assert_eq!(tree.parent_id(before).unwrap(), parent);
     assert_eq!(tree.parent_id(child).unwrap(), parent);
     assert_eq!(tree.parent_id(after).unwrap(), parent);
-    assert_eq!(tree.children_ids(parent).unwrap(), &[before, child, after]);
+    assert_eq!(tree.children_ids(parent), &[before, child, after]);
 
     tree.remove(child);
 
-    println!("Tree: {tree:#?}");
-    assert_eq!(tree.size(), 3);
     assert_eq!(tree.height(parent), Some(0));
     assert_eq!(tree.height(before), Some(1));
     assert_eq!(tree.height(after), Some(1));
-    assert_eq!(*tree.get(parent).unwrap(), 0);
-    assert_eq!(*tree.get(before).unwrap(), 1);
-    assert_eq!(tree.get(child), None);
-    assert_eq!(*tree.get(after).unwrap(), 3);
     assert_eq!(tree.parent_id(before).unwrap(), parent);
     assert_eq!(tree.parent_id(after).unwrap(), parent);
-    assert_eq!(tree.children_ids(parent).unwrap(), &[before, after]);
+    assert_eq!(tree.children_ids(parent), &[before, after]);
 
     tree.remove(before);
 
-    println!("Tree: {tree:#?}");
-    assert_eq!(tree.size(), 2);
     assert_eq!(tree.height(parent), Some(0));
     assert_eq!(tree.height(after), Some(1));
-    assert_eq!(*tree.get(parent).unwrap(), 0);
-    assert_eq!(tree.get(before), None);
-    assert_eq!(*tree.get(after).unwrap(), 3);
     assert_eq!(tree.parent_id(after).unwrap(), parent);
-    assert_eq!(tree.children_ids(parent).unwrap(), &[after]);
+    assert_eq!(tree.children_ids(parent), &[after]);
 
     tree.remove(after);
 
-    println!("Tree: {tree:#?}");
-    assert_eq!(tree.size(), 1);
     assert_eq!(tree.height(parent), Some(0));
-    assert_eq!(*tree.get(parent).unwrap(), 0);
-    assert_eq!(tree.get(after), None);
-    assert_eq!(tree.children_ids(parent).unwrap(), &[]);
-}
-
-#[test]
-fn traverse_depth_first() {
-    let mut tree = Tree::new(0);
-    let parent = tree.root();
-    let child1 = tree.create_node(1);
-    tree.add_child(parent, child1);
-    let grandchild1 = tree.create_node(2);
-    tree.add_child(child1, grandchild1);
-    let child2 = tree.create_node(3);
-    tree.add_child(parent, child2);
-    let grandchild2 = tree.create_node(4);
-    tree.add_child(child2, grandchild2);
-
-    let mut node_count = 0;
-    tree.traverse_depth_first(move |node| {
-        assert_eq!(*node, node_count);
-        node_count += 1;
-    });
-}
-
-#[test]
-fn get_node_children_mut() {
-    let mut tree = Tree::new(0);
-    let parent = tree.root();
-    let child1 = tree.create_node(1);
-    tree.add_child(parent, child1);
-    let child2 = tree.create_node(2);
-    tree.add_child(parent, child2);
-    let child3 = tree.create_node(3);
-    tree.add_child(parent, child3);
-
-    let (parent, children) = tree.parent_child_mut(parent).unwrap();
-    for (i, child) in children.enumerate() {
-        assert_eq!(*child, i + 1);
-    }
-    println!("Parent: {parent:#?}");
-}
-
-#[test]
-fn get_many_mut_unchecked() {
-    let mut slab = Slab::new();
-    let parent = slab.insert(0);
-    let child = slab.insert(1);
-    let grandchild = slab.insert(2);
-
-    let all =
-        unsafe { slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) }.unwrap();
-    println!("All: {all:#?}");
-}
-
-#[derive(Debug)]
-struct Slab<T> {
-    data: Vec<Option<T>>,
-    free: VecDeque<usize>,
-}
-
-impl<T> Default for Slab<T> {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl<T> Slab<T> {
-    fn new() -> Self {
-        Self {
-            data: Vec::new(),
-            free: VecDeque::new(),
-        }
-    }
-
-    fn get(&self, id: usize) -> Option<&T> {
-        self.data.get(id).and_then(|x| x.as_ref())
-    }
-
-    unsafe fn get_unchecked(&self, id: usize) -> &T {
-        self.data.get_unchecked(id).as_ref().unwrap()
-    }
-
-    fn get_mut(&mut self, id: usize) -> Option<&mut T> {
-        self.data.get_mut(id).and_then(|x| x.as_mut())
-    }
-
-    unsafe fn get_unchecked_mut(&mut self, id: usize) -> &mut T {
-        self.data.get_unchecked_mut(id).as_mut().unwrap()
-    }
-
-    fn get2_mut(&mut self, id1: usize, id2: usize) -> Option<(&mut T, &mut T)> {
-        assert!(id1 != id2);
-        let ptr = self.data.as_mut_ptr();
-        let first = unsafe { &mut *ptr.add(id1) };
-        let second = unsafe { &mut *ptr.add(id2) };
-        if let (Some(first), Some(second)) = (first, second) {
-            Some((first, second))
-        } else {
-            None
-        }
-    }
-
-    unsafe fn get_many_mut_unchecked(
-        &mut self,
-        ids: impl Iterator<Item = usize>,
-    ) -> Option<Vec<&mut T>> {
-        let ptr = self.data.as_mut_ptr();
-        let mut result = Vec::new();
-        for id in ids {
-            let item = unsafe { &mut *ptr.add(id) };
-            if let Some(item) = item {
-                result.push(item);
-            } else {
-                return None;
-            }
-        }
-        Some(result)
-    }
-
-    fn insert(&mut self, value: T) -> usize {
-        if let Some(id) = self.free.pop_front() {
-            self.data[id] = Some(value);
-            id
-        } else {
-            self.data.push(Some(value));
-            self.data.len() - 1
-        }
-    }
-
-    fn try_remove(&mut self, id: usize) -> Option<T> {
-        self.data.get_mut(id).and_then(|x| {
-            self.free.push_back(id);
-            x.take()
-        })
-    }
-
-    fn remove(&mut self, id: usize) -> T {
-        self.try_remove(id).unwrap()
-    }
-
-    fn len(&self) -> usize {
-        self.data.len() - self.free.len()
-    }
+    assert_eq!(tree.children_ids(parent), &[]);
 }

+ 43 - 71
packages/native-core/src/utils/cursor.rs

@@ -1,6 +1,8 @@
+//! A cursor implementation that can be used to navigate and edit text.
+
 use std::{cmp::Ordering, ops::Range};
 
-use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
+use keyboard_types::{Code, Key, Modifiers};
 
 /// This contains the information about the text that is used by the cursor to handle navigation.
 pub trait Text {
@@ -221,26 +223,28 @@ impl Cursor {
     /// Handle moving the cursor with the given key.
     pub fn handle_input<T: Text + ?Sized>(
         &mut self,
-        data: &dioxus_html::KeyboardData,
+        code: &Code,
+        key: &Key,
+        modifiers: &Modifiers,
         text: &mut impl TextEditable<T>,
         max_text_length: usize,
     ) {
         use Code::*;
-        match data.code() {
+        match code {
             ArrowUp => {
                 self.move_cursor(
                     |c| c.up(text.as_ref()),
-                    data.modifiers().contains(Modifiers::SHIFT),
+                    modifiers.contains(Modifiers::SHIFT),
                 );
             }
             ArrowDown => {
                 self.move_cursor(
                     |c| c.down(text.as_ref()),
-                    data.modifiers().contains(Modifiers::SHIFT),
+                    modifiers.contains(Modifiers::SHIFT),
                 );
             }
             ArrowRight => {
-                if data.modifiers().contains(Modifiers::CONTROL) {
+                if modifiers.contains(Modifiers::CONTROL) {
                     self.move_cursor(
                         |c| {
                             let mut change = 1;
@@ -255,17 +259,17 @@ impl Cursor {
                             }
                             c.move_col(change as i32, text.as_ref());
                         },
-                        data.modifiers().contains(Modifiers::SHIFT),
+                        modifiers.contains(Modifiers::SHIFT),
                     );
                 } else {
                     self.move_cursor(
                         |c| c.right(text.as_ref()),
-                        data.modifiers().contains(Modifiers::SHIFT),
+                        modifiers.contains(Modifiers::SHIFT),
                     );
                 }
             }
             ArrowLeft => {
-                if data.modifiers().contains(Modifiers::CONTROL) {
+                if modifiers.contains(Modifiers::CONTROL) {
                     self.move_cursor(
                         |c| {
                             let mut change = -1;
@@ -279,23 +283,23 @@ impl Cursor {
                             }
                             c.move_col(change, text.as_ref());
                         },
-                        data.modifiers().contains(Modifiers::SHIFT),
+                        modifiers.contains(Modifiers::SHIFT),
                     );
                 } else {
                     self.move_cursor(
                         |c| c.left(text.as_ref()),
-                        data.modifiers().contains(Modifiers::SHIFT),
+                        modifiers.contains(Modifiers::SHIFT),
                     );
                 }
             }
             End => {
                 self.move_cursor(
                     |c| c.col = c.len_line(text.as_ref()),
-                    data.modifiers().contains(Modifiers::SHIFT),
+                    modifiers.contains(Modifiers::SHIFT),
                 );
             }
             Home => {
-                self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
+                self.move_cursor(|c| c.col = 0, modifiers.contains(Modifiers::SHIFT));
             }
             Backspace => {
                 self.start.realize_col(text.as_ref());
@@ -305,7 +309,7 @@ impl Cursor {
                 } else if start_idx > 0 {
                     self.start.left(text.as_ref());
                     text.delete_range(start_idx - 1..start_idx);
-                    if data.modifiers().contains(Modifiers::CONTROL) {
+                    if modifiers.contains(Modifiers::CONTROL) {
                         start_idx = self.start.idx(text.as_ref());
                         while start_idx > 0
                             && text
@@ -340,7 +344,7 @@ impl Cursor {
             }
             _ => {
                 self.start.realize_col(text.as_ref());
-                if let Key::Character(character) = data.key() {
+                if let Key::Character(character) = key {
                     if text.as_ref().length() + 1 - self.selection_len(text.as_ref())
                         <= max_text_length
                     {
@@ -460,13 +464,9 @@ fn cursor_input() {
 
     for _ in 0..5 {
         cursor.handle_input(
-            &dioxus_html::KeyboardData::new(
-                dioxus_html::input_data::keyboard_types::Key::ArrowRight,
-                dioxus_html::input_data::keyboard_types::Code::ArrowRight,
-                dioxus_html::input_data::keyboard_types::Location::Standard,
-                false,
-                Modifiers::empty(),
-            ),
+            &keyboard_types::Code::ArrowRight,
+            &keyboard_types::Key::ArrowRight,
+            &Modifiers::empty(),
             &mut text,
             10,
         );
@@ -474,13 +474,9 @@ fn cursor_input() {
 
     for _ in 0..5 {
         cursor.handle_input(
-            &dioxus_html::KeyboardData::new(
-                dioxus_html::input_data::keyboard_types::Key::Backspace,
-                dioxus_html::input_data::keyboard_types::Code::Backspace,
-                dioxus_html::input_data::keyboard_types::Location::Standard,
-                false,
-                Modifiers::empty(),
-            ),
+            &keyboard_types::Code::Backspace,
+            &keyboard_types::Key::Backspace,
+            &Modifiers::empty(),
             &mut text,
             10,
         );
@@ -491,61 +487,41 @@ fn cursor_input() {
     let goal_text = "hello world\nhi";
     let max_width = goal_text.len();
     cursor.handle_input(
-        &dioxus_html::KeyboardData::new(
-            dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()),
-            dioxus_html::input_data::keyboard_types::Code::KeyH,
-            dioxus_html::input_data::keyboard_types::Location::Standard,
-            false,
-            Modifiers::empty(),
-        ),
+        &keyboard_types::Code::KeyH,
+        &keyboard_types::Key::Character("h".to_string()),
+        &Modifiers::empty(),
         &mut text,
         max_width,
     );
 
     cursor.handle_input(
-        &dioxus_html::KeyboardData::new(
-            dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()),
-            dioxus_html::input_data::keyboard_types::Code::KeyE,
-            dioxus_html::input_data::keyboard_types::Location::Standard,
-            false,
-            Modifiers::empty(),
-        ),
+        &keyboard_types::Code::KeyE,
+        &keyboard_types::Key::Character("e".to_string()),
+        &Modifiers::empty(),
         &mut text,
         max_width,
     );
 
     cursor.handle_input(
-        &dioxus_html::KeyboardData::new(
-            dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
-            dioxus_html::input_data::keyboard_types::Code::KeyL,
-            dioxus_html::input_data::keyboard_types::Location::Standard,
-            false,
-            Modifiers::empty(),
-        ),
+        &keyboard_types::Code::KeyL,
+        &keyboard_types::Key::Character("l".to_string()),
+        &Modifiers::empty(),
         &mut text,
         max_width,
     );
 
     cursor.handle_input(
-        &dioxus_html::KeyboardData::new(
-            dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
-            dioxus_html::input_data::keyboard_types::Code::KeyL,
-            dioxus_html::input_data::keyboard_types::Location::Standard,
-            false,
-            Modifiers::empty(),
-        ),
+        &keyboard_types::Code::KeyL,
+        &keyboard_types::Key::Character("l".to_string()),
+        &Modifiers::empty(),
         &mut text,
         max_width,
     );
 
     cursor.handle_input(
-        &dioxus_html::KeyboardData::new(
-            dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
-            dioxus_html::input_data::keyboard_types::Code::KeyO,
-            dioxus_html::input_data::keyboard_types::Location::Standard,
-            false,
-            Modifiers::empty(),
-        ),
+        &keyboard_types::Code::KeyO,
+        &keyboard_types::Key::Character("o".to_string()),
+        &Modifiers::empty(),
         &mut text,
         max_width,
     );
@@ -553,13 +529,9 @@ fn cursor_input() {
     // these should be ignored
     for _ in 0..10 {
         cursor.handle_input(
-            &dioxus_html::KeyboardData::new(
-                dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
-                dioxus_html::input_data::keyboard_types::Code::KeyO,
-                dioxus_html::input_data::keyboard_types::Location::Standard,
-                false,
-                Modifiers::empty(),
-            ),
+            &keyboard_types::Code::KeyO,
+            &keyboard_types::Key::Character("o".to_string()),
+            &Modifiers::empty(),
             &mut text,
             max_width,
         );

+ 4 - 0
packages/native-core/src/utils/mod.rs

@@ -1,3 +1,7 @@
+//! # Utilities for renders using the RealDOM
+//!
+//! This includes an iterator that can be used to iterate over the children of a node that persists changes in the struture of the DOM, and a cursor for text editing.
+
 mod persistant_iterator;
 pub use persistant_iterator::*;
 pub mod cursor;

+ 219 - 263
packages/native-core/src/utils/persistant_iterator.rs

@@ -1,252 +1,204 @@
+use smallvec::SmallVec;
+
 use crate::{
-    node::{FromAnyValue, NodeType},
-    real_dom::RealDom,
-    state::State,
-    tree::TreeView,
-    NodeId, RealNodeId,
+    node::FromAnyValue,
+    node_watcher::NodeWatcher,
+    prelude::{NodeMut, NodeRef},
+    real_dom::{NodeImmutable, RealDom},
+    NodeId,
+};
+use std::{
+    fmt::Debug,
+    sync::{Arc, Mutex},
 };
-use dioxus_core::{Mutation, Mutations};
-use std::fmt::Debug;
 
-#[derive(Debug)]
-pub enum ElementProduced {
-    /// The iterator produced an element by progressing to the next node in a depth first order.
-    Progressed(RealNodeId),
+/// The element produced by the iterator
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct ElementProduced {
+    id: NodeId,
+    movement: IteratorMovement,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+/// The method by which the iterator produced an element
+pub enum IteratorMovement {
+    /// The iterator produced an element by progressing to the next node
+    Progressed,
     /// The iterator reached the end of the tree and looped back to the root
-    Looped(RealNodeId),
+    Looped,
 }
+
 impl ElementProduced {
-    pub fn id(&self) -> RealNodeId {
-        match self {
-            ElementProduced::Progressed(id) => *id,
-            ElementProduced::Looped(id) => *id,
+    /// Get the id of the element produced
+    pub fn id(&self) -> NodeId {
+        self.id
+    }
+
+    /// The movement the iterator made to produce the element
+    pub fn movement(&self) -> &IteratorMovement {
+        &self.movement
+    }
+
+    fn looped(id: NodeId) -> Self {
+        Self {
+            id,
+            movement: IteratorMovement::Looped,
+        }
+    }
+
+    fn progressed(id: NodeId) -> Self {
+        Self {
+            id,
+            movement: IteratorMovement::Progressed,
         }
     }
 }
 
-#[derive(Debug)]
-enum NodePosition {
-    AtNode,
-    InChild(usize),
+struct PersistantElementIterUpdater<V> {
+    stack: Arc<Mutex<smallvec::SmallVec<[NodeId; 5]>>>,
+    phantom: std::marker::PhantomData<V>,
 }
 
-impl NodePosition {
-    fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self {
-        match self {
-            Self::AtNode => Self::AtNode,
-            Self::InChild(i) => Self::InChild(f(*i)),
+impl<V: FromAnyValue + Sync + Send> NodeWatcher<V> for PersistantElementIterUpdater<V> {
+    fn on_node_moved(&self, node: NodeMut<V>) {
+        // if any element is moved, update its parents in the stack
+        let mut stack = self.stack.lock().unwrap();
+        let moved = node.id();
+        let rdom = node.real_dom();
+        if let Some(r) = stack.iter().position(|el_id| *el_id == moved) {
+            let back = &stack[r..];
+            let mut new = SmallVec::new();
+            let mut parent = node.parent_id();
+            while let Some(p) = parent.and_then(|id| rdom.get(id)) {
+                new.push(p.id());
+                parent = p.parent_id();
+            }
+            new.extend(back.iter().copied());
+            *stack = new;
         }
     }
 
-    fn get_or_insert(&mut self, child_idx: usize) -> usize {
-        match self {
-            Self::AtNode => {
-                *self = Self::InChild(child_idx);
-                child_idx
-            }
-            Self::InChild(i) => *i,
+    fn on_node_removed(&self, node: NodeMut<V>) {
+        // if any element is removed in the chain, remove it and its children from the stack
+        let mut stack = self.stack.lock().unwrap();
+        let removed = node.id();
+        if let Some(r) = stack.iter().position(|el_id| *el_id == removed) {
+            stack.truncate(r);
         }
     }
 }
 
-/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom].
+/// Focus systems need a iterator that can persist through changes in the [crate::prelude::RealDom]
 /// This iterator traverses the tree depth first.
-/// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]).
+/// You can iterate through it with [PersistantElementIter::next] and [PersistantElementIter::prev].
 /// The iterator loops around when it reaches the end or the beginning.
 pub struct PersistantElementIter {
-    // stack of elements and fragments
-    stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
-}
-
-impl Default for PersistantElementIter {
-    fn default() -> Self {
-        PersistantElementIter {
-            stack: smallvec::smallvec![(NodeId(0), NodePosition::AtNode)],
-        }
-    }
+    // stack of elements and fragments, the last element is the last element that was yielded
+    stack: Arc<Mutex<smallvec::SmallVec<[NodeId; 5]>>>,
 }
 
 impl PersistantElementIter {
-    pub fn new() -> Self {
-        Self::default()
-    }
+    /// Create a new iterator in the RealDom
+    pub fn create<V: FromAnyValue + Send + Sync>(rdom: &mut RealDom<V>) -> Self {
+        let inner = Arc::new(Mutex::new(smallvec::smallvec![rdom.root_id()]));
 
-    /// remove stale element refreneces
-    /// returns true if the focused element is removed
-    pub fn prune<S: State<V>, V: FromAnyValue>(
-        &mut self,
-        mutations: &Mutations,
-        rdom: &RealDom<S, V>,
-    ) -> bool {
-        let mut changed = false;
-        let ids_removed: Vec<_> = mutations
-            .edits
-            .iter()
-            .filter_map(|m| {
-                // nodes within templates will never be removed
-                match m {
-                    Mutation::Remove { id } => Some(rdom.element_to_node_id(*id)),
-                    Mutation::ReplaceWith { id, .. } => Some(rdom.element_to_node_id(*id)),
-                    _ => None,
-                }
-            })
-            .collect();
-        // if any element is removed in the chain, remove it and its children from the stack
-        if let Some(r) = self
-            .stack
-            .iter()
-            .position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id))
-        {
-            self.stack.truncate(r);
-            changed = true;
-        }
-        // if a child is removed or inserted before or at the current element, update the child index
-        for (el_id, child_idx) in self.stack.iter_mut() {
-            if let NodePosition::InChild(child_idx) = child_idx {
-                if let Some(children) = &rdom.tree.children_ids(*el_id) {
-                    for m in &mutations.edits {
-                        match m {
-                            Mutation::Remove { id } => {
-                                let id = rdom.element_to_node_id(*id);
-                                if children.iter().take(*child_idx + 1).any(|c| *c == id) {
-                                    *child_idx -= 1;
-                                }
-                            }
-                            Mutation::InsertBefore { id, m } => {
-                                let id = rdom.element_to_node_id(*id);
-                                if children.iter().take(*child_idx + 1).any(|c| *c == id) {
-                                    *child_idx += *m;
-                                }
-                            }
-                            Mutation::InsertAfter { id, m } => {
-                                let id = rdom.element_to_node_id(*id);
-                                if children.iter().take(*child_idx).any(|c| *c == id) {
-                                    *child_idx += *m;
-                                }
-                            }
-                            _ => (),
-                        }
-                    }
-                }
-            }
-        }
-        changed
+        rdom.add_node_watcher(PersistantElementIterUpdater {
+            stack: inner.clone(),
+            phantom: std::marker::PhantomData,
+        });
+
+        PersistantElementIter { stack: inner }
     }
 
     /// get the next element
-    pub fn next<S: State<V>, V: FromAnyValue>(&mut self, rdom: &RealDom<S, V>) -> ElementProduced {
-        if self.stack.is_empty() {
-            let id = NodeId(0);
-            let new = (id, NodePosition::AtNode);
-            self.stack.push(new);
-            ElementProduced::Looped(id)
+    pub fn next<V: FromAnyValue + Send + Sync>(&mut self, rdom: &RealDom<V>) -> ElementProduced {
+        let mut stack = self.stack.lock().unwrap();
+        if stack.is_empty() {
+            let id = rdom.root_id();
+            let new = id;
+            stack.push(new);
+            ElementProduced::looped(id)
         } else {
-            let (last, old_child_idx) = self.stack.last_mut().unwrap();
-            let node = &rdom[*last];
-            match &node.node_data.node_type {
-                NodeType::Element { .. } => {
-                    let children = rdom.tree.children_ids(*last).unwrap();
-                    *old_child_idx = old_child_idx.map(|i| i + 1);
-                    // if we have children, go to the next child
-                    let child_idx = old_child_idx.get_or_insert(0);
-                    if child_idx >= children.len() {
-                        self.pop();
-                        self.next(rdom)
-                    } else {
-                        let id = children[child_idx];
-                        if let NodeType::Element { .. } = &rdom[id].node_data.node_type {
-                            self.stack.push((id, NodePosition::AtNode));
+            let mut look_in_children = true;
+            loop {
+                if let Some(current) = stack.last().and_then(|last| rdom.get(*last)) {
+                    // if the current element has children, add the first child to the stack and return it
+                    if look_in_children {
+                        if let Some(first) = current.children().first() {
+                            let new = first.id();
+                            stack.push(new);
+                            return ElementProduced::progressed(new);
                         }
-                        ElementProduced::Progressed(id)
                     }
+                    stack.pop();
+                    if let Some(new) = current.next() {
+                        // the next element exists, add it to the stack and return it
+                        let new = new.id();
+                        stack.push(new);
+                        return ElementProduced::progressed(new);
+                    }
+                    // otherwise, continue the loop and go to the parent
+                } else {
+                    // if there is no parent, loop back to the root
+                    let new = rdom.root_id();
+                    stack.clear();
+                    stack.push(new);
+                    return ElementProduced::looped(new);
                 }
-
-                NodeType::Text { .. } | NodeType::Placeholder { .. } => {
-                    // we are at a leaf, so we are done
-                    ElementProduced::Progressed(self.pop())
-                }
+                look_in_children = false;
             }
         }
     }
 
     /// get the previous element
-    pub fn prev<S: State<V>, V: FromAnyValue>(&mut self, rdom: &RealDom<S, V>) -> ElementProduced {
+    pub fn prev<V: FromAnyValue + Send + Sync>(&mut self, rdom: &RealDom<V>) -> ElementProduced {
         // recursively add the last child element to the stack
-        fn push_back<S: State<V>, V: FromAnyValue>(
-            stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
-            new_node: RealNodeId,
-            rdom: &RealDom<S, V>,
-        ) -> RealNodeId {
-            match &rdom[new_node].node_data.node_type {
-                NodeType::Element { .. } => {
-                    let children = rdom.tree.children_ids(new_node).unwrap();
-                    if children.is_empty() {
-                        new_node
-                    } else {
-                        stack.push((new_node, NodePosition::InChild(children.len() - 1)));
-                        push_back(stack, *children.last().unwrap(), rdom)
-                    }
-                }
-                _ => new_node,
+        fn push_back<V: FromAnyValue + Send + Sync>(
+            stack: &mut smallvec::SmallVec<[NodeId; 5]>,
+            node: NodeRef<V>,
+        ) -> NodeId {
+            stack.push(node.id());
+            if let Some(last) = node.children().last() {
+                push_back(stack, *last)
+            } else {
+                node.id()
             }
         }
-        if self.stack.is_empty() {
-            let new_node = NodeId(0);
-            ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
-        } else {
-            let (last, old_child_idx) = self.stack.last_mut().unwrap();
-            let node = &rdom[*last];
-            match &node.node_data.node_type {
-                NodeType::Element { .. } => {
-                    let children = rdom.tree.children_ids(*last).unwrap();
-                    // if we have children, go to the next child
-                    if let NodePosition::InChild(0) = old_child_idx {
-                        ElementProduced::Progressed(self.pop())
-                    } else {
-                        *old_child_idx = old_child_idx.map(|i| i - 1);
-                        if let NodePosition::InChild(child_idx) = old_child_idx {
-                            if *child_idx >= children.len() || children.is_empty() {
-                                self.pop();
-                                self.prev(rdom)
-                            } else {
-                                let new_node = children[*child_idx];
-                                ElementProduced::Progressed(push_back(
-                                    &mut self.stack,
-                                    new_node,
-                                    rdom,
-                                ))
-                            }
-                        } else {
-                            self.pop();
-                            self.prev(rdom)
-                        }
-                    }
-                }
-
-                NodeType::Text { .. } | NodeType::Placeholder { .. } => {
-                    // we are at a leaf, so we are done
-                    ElementProduced::Progressed(self.pop())
-                }
+        let mut stack = self.stack.lock().unwrap();
+        if stack.is_empty() {
+            let id = rdom.root_id();
+            let last = push_back(&mut stack, rdom.get(id).unwrap());
+            ElementProduced::looped(last)
+        } else if let Some(current) = stack.pop().and_then(|last| rdom.get(last)) {
+            if let Some(new) = current.prev() {
+                // the next element exists, add it to the stack and return it
+                let new = push_back(&mut stack, new);
+                ElementProduced::progressed(new)
             }
+            // otherwise, yeild the parent
+            else if let Some(parent) = stack.last() {
+                // if there is a parent, return it
+                ElementProduced::progressed(*parent)
+            } else {
+                // if there is no parent, loop back to the root
+                let id = rdom.root_id();
+                let last = push_back(&mut stack, rdom.get(id).unwrap());
+                ElementProduced::looped(last)
+            }
+        } else {
+            // if there is no parent, loop back to the root
+            let id = rdom.root_id();
+            let last = push_back(&mut stack, rdom.get(id).unwrap());
+            ElementProduced::looped(last)
         }
     }
-
-    fn pop(&mut self) -> RealNodeId {
-        self.stack.pop().unwrap().0
-    }
-}
-
-#[derive(Default, Clone, Debug)]
-struct Empty {}
-impl State<()> for Empty {
-    const PASSES: &'static [crate::AnyPass<crate::node::Node<Self, ()>>] = &[];
-
-    const MASKS: &'static [crate::NodeMask] = &[];
 }
 
 #[test]
 #[allow(unused_variables)]
 fn traverse() {
+    use crate::dioxus::DioxusState;
+    use crate::prelude::*;
     use dioxus::prelude::*;
     #[allow(non_snake_case)]
     fn Base(cx: Scope) -> Element {
@@ -266,82 +218,85 @@ fn traverse() {
     let mut vdom = VirtualDom::new(Base);
     let mutations = vdom.rebuild();
 
-    let mut rdom: RealDom<Empty> = RealDom::new();
+    let mut rdom: RealDom = RealDom::new([]);
 
-    let _to_update = rdom.apply_mutations(mutations);
+    let mut iter = PersistantElementIter::create(&mut rdom);
+    let mut dioxus_state = DioxusState::create(&mut rdom);
+    dioxus_state.apply_mutations(&mut rdom, mutations);
 
-    let mut iter = PersistantElementIter::new();
     let div_tag = "div".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: div_tag, .. }
+        &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: div_tag, .. })
     ));
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: div_tag, .. }
+        &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: div_tag, .. })
     ));
     let text1 = "hello".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_data.node_type,
-        NodeType::Text { text: text1, .. }
+        &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
+        NodeType::Text(text1)
     ));
     let p_tag = "p".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: p_tag, .. }
+        &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: p_tag, .. })
     ));
     let text2 = "world".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_data.node_type,
-        NodeType::Text { text: text2, .. }
+        &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
+        NodeType::Text(text2)
     ));
     let text3 = "hello world".to_string();
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_data.node_type,
-        NodeType::Text { text: text3, .. }
+        &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
+        NodeType::Text(text3)
     ));
     assert!(matches!(
-        &rdom[iter.next(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: div_tag, .. }
+        &*rdom.get(iter.next(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: div_tag, .. })
     ));
 
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Text { text: text3, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Text(text3)
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Text { text: text2, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Text(text2)
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: p_tag, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: p_tag, .. })
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Text { text: text1, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Text(text1)
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: div_tag, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: div_tag, .. })
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: div_tag, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: div_tag, .. })
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Element { tag: div_tag, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: div_tag, .. })
     ));
     assert!(matches!(
-        &rdom[iter.prev(&rdom).id()].node_data.node_type,
-        NodeType::Text { text: text3, .. }
+        &*rdom.get(iter.prev(&rdom).id()).unwrap().node_type(),
+        NodeType::Text(text3)
     ));
 }
 
 #[test]
 #[allow(unused_variables)]
 fn persist_removes() {
+    use crate::dioxus::DioxusState;
+    use crate::prelude::*;
     use dioxus::prelude::*;
     #[allow(non_snake_case)]
     fn Base(cx: Scope) -> Element {
@@ -365,16 +320,17 @@ fn persist_removes() {
     }
     let mut vdom = VirtualDom::new(Base);
 
-    let mut rdom: RealDom<Empty> = RealDom::new();
+    let mut rdom: RealDom = RealDom::new([]);
 
     let build = vdom.rebuild();
     println!("{build:#?}");
-    let _to_update = rdom.apply_mutations(build);
-
     // this will end on the node that is removed
-    let mut iter1 = PersistantElementIter::new();
+    let mut iter1 = PersistantElementIter::create(&mut rdom);
     // this will end on the after node that is removed
-    let mut iter2 = PersistantElementIter::new();
+    let mut iter2 = PersistantElementIter::create(&mut rdom);
+    let mut dioxus_state = DioxusState::create(&mut rdom);
+    dioxus_state.apply_mutations(&mut rdom, build);
+
     // root
     iter1.next(&rdom).id();
     iter2.next(&rdom).id();
@@ -401,29 +357,27 @@ fn persist_removes() {
     vdom.mark_dirty(ScopeId(0));
     let update = vdom.render_immediate();
     println!("{update:#?}");
-    iter1.prune(&update, &rdom);
-    iter2.prune(&update, &rdom);
-    let _to_update = rdom.apply_mutations(update);
+    dioxus_state.apply_mutations(&mut rdom, update);
 
     let root_tag = "Root".to_string();
     let idx = iter1.next(&rdom).id();
-    dbg!(&rdom[idx].node_data.node_type);
     assert!(matches!(
-        &rdom[idx].node_data.node_type,
-        NodeType::Element { tag: root_tag, .. }
+        &*rdom.get(idx).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: root_tag, .. })
     ));
 
     let idx = iter2.next(&rdom).id();
-    dbg!(&rdom[idx].node_data.node_type);
     assert!(matches!(
-        &rdom[idx].node_data.node_type,
-        NodeType::Element { tag: root_tag, .. }
+        &*rdom.get(idx).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: root_tag, .. })
     ));
 }
 
 #[test]
 #[allow(unused_variables)]
 fn persist_instertions_before() {
+    use crate::dioxus::DioxusState;
+    use crate::prelude::*;
     use dioxus::prelude::*;
     #[allow(non_snake_case)]
     fn Base(cx: Scope) -> Element {
@@ -447,12 +401,13 @@ fn persist_instertions_before() {
     }
     let mut vdom = VirtualDom::new(Base);
 
-    let mut rdom: RealDom<Empty> = RealDom::new();
+    let mut rdom: RealDom = RealDom::new([]);
+    let mut dioxus_state = DioxusState::create(&mut rdom);
 
     let build = vdom.rebuild();
-    let _to_update = rdom.apply_mutations(build);
+    dioxus_state.apply_mutations(&mut rdom, build);
 
-    let mut iter = PersistantElementIter::new();
+    let mut iter = PersistantElementIter::create(&mut rdom);
     // div
     iter.next(&rdom).id();
     // p
@@ -466,20 +421,21 @@ fn persist_instertions_before() {
 
     vdom.mark_dirty(ScopeId(0));
     let update = vdom.render_immediate();
-    iter.prune(&update, &rdom);
-    let _to_update = rdom.apply_mutations(update);
+    dioxus_state.apply_mutations(&mut rdom, update);
 
     let p_tag = "div".to_string();
     let idx = iter.next(&rdom).id();
     assert!(matches!(
-        &rdom[idx].node_data.node_type,
-        NodeType::Element { tag: p_tag, .. }
+        &*rdom.get(idx).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: p_tag, .. })
     ));
 }
 
 #[test]
 #[allow(unused_variables)]
 fn persist_instertions_after() {
+    use crate::dioxus::DioxusState;
+    use crate::prelude::*;
     use dioxus::prelude::*;
     #[allow(non_snake_case)]
     fn Base(cx: Scope) -> Element {
@@ -503,12 +459,13 @@ fn persist_instertions_after() {
     }
     let mut vdom = VirtualDom::new(Base);
 
-    let mut rdom: RealDom<Empty> = RealDom::new();
+    let mut rdom: RealDom = RealDom::new([]);
+    let mut iter = PersistantElementIter::create(&mut rdom);
+    let mut dioxus_state = DioxusState::create(&mut rdom);
 
     let build = vdom.rebuild();
-    let _to_update = rdom.apply_mutations(build);
+    dioxus_state.apply_mutations(&mut rdom, build);
 
-    let mut iter = PersistantElementIter::new();
     // div
     iter.next(&rdom).id();
     // p
@@ -521,19 +478,18 @@ fn persist_instertions_after() {
     iter.next(&rdom).id();
 
     let update = vdom.rebuild();
-    iter.prune(&update, &rdom);
-    let _to_update = rdom.apply_mutations(update);
+    dioxus_state.apply_mutations(&mut rdom, update);
 
     let p_tag = "p".to_string();
     let idx = iter.next(&rdom).id();
     assert!(matches!(
-        &rdom[idx].node_data.node_type,
-        NodeType::Element { tag: p_tag, .. }
+        &*rdom.get(idx).unwrap().node_type(),
+        NodeType::Element(ElementNode { tag: p_tag, .. })
     ));
     let text = "hello world".to_string();
     let idx = iter.next(&rdom).id();
     assert!(matches!(
-        &rdom[idx].node_data.node_type,
-        NodeType::Text { text, .. }
+        &*rdom.get(idx).unwrap().node_type(),
+        NodeType::Text(text)
     ));
 }

+ 237 - 0
packages/native-core/tests/called_minimally_on_build.rs

@@ -0,0 +1,237 @@
+use dioxus::prelude::*;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core_macro::partial_derive_state;
+use shipyard::Component;
+
+macro_rules! dep {
+    ( child( $name:ty, $dep:ty ) ) => {
+        #[partial_derive_state]
+        impl State for $name {
+            type ParentDependencies = ();
+            type ChildDependencies = $dep;
+            type NodeDependencies = ();
+
+            const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL;
+
+            fn update<'a>(
+                &mut self,
+                _: NodeView,
+                _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+                _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+                _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+                _: &SendAnyMap,
+            ) -> bool {
+                self.0 += 1;
+                true
+            }
+
+            fn create<'a>(
+                node_view: NodeView<()>,
+                node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+                parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+                children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+                context: &SendAnyMap,
+            ) -> Self {
+                let mut myself = Self::default();
+                myself.update(node_view, node, parent, children, context);
+                myself
+            }
+        }
+    };
+
+    ( parent( $name:ty, $dep:ty ) ) => {
+        #[partial_derive_state]
+        impl State for $name {
+            type ParentDependencies = $dep;
+            type ChildDependencies = ();
+            type NodeDependencies = ();
+
+            const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL;
+
+            fn update<'a>(
+                &mut self,
+                _: NodeView,
+                _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+                _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+                _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+                _: &SendAnyMap,
+            ) -> bool {
+                self.0 += 1;
+                true
+            }
+
+            fn create<'a>(
+                node_view: NodeView<()>,
+                node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+                parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+                children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+                context: &SendAnyMap,
+            ) -> Self {
+                let mut myself = Self::default();
+                myself.update(node_view, node, parent, children, context);
+                myself
+            }
+        }
+    };
+
+    ( node( $name:ty, $dep:ty ) ) => {
+        #[partial_derive_state]
+        impl State for $name {
+            type ParentDependencies = $dep;
+            type ChildDependencies = ();
+            type NodeDependencies = ();
+
+            const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::ALL;
+
+            fn update<'a>(
+                &mut self,
+                _: NodeView,
+                _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+                _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+                _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+                _: &SendAnyMap,
+            ) -> bool {
+                self.0 += 1;
+                true
+            }
+
+            fn create<'a>(
+                node_view: NodeView<()>,
+                node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+                parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+                children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+                context: &SendAnyMap,
+            ) -> Self {
+                let mut myself = Self::default();
+                myself.update(node_view, node, parent, children, context);
+                myself
+            }
+        }
+    };
+}
+
+macro_rules! test_state{
+    ( state: ( $( $state:ty ),* ) ) => {
+        #[test]
+        fn state_reduce_initally_called_minimally() {
+            #[allow(non_snake_case)]
+            fn Base(cx: Scope) -> Element {
+                render!{
+                    div {
+                        div{
+                            div{
+                                p{}
+                            }
+                            p{
+                                "hello"
+                            }
+                            div{
+                                h1{}
+                            }
+                            p{
+                                "world"
+                            }
+                        }
+                    }
+                }
+            }
+
+            let mut vdom = VirtualDom::new(Base);
+
+            let mutations = vdom.rebuild();
+
+            let mut dom: RealDom = RealDom::new([$( <$state>::to_type_erased() ),*]);
+            let mut dioxus_state = DioxusState::create(&mut dom);
+
+            dioxus_state.apply_mutations(&mut dom, mutations);
+            dom.update_state(SendAnyMap::new());
+
+            dom.traverse_depth_first(|n| {
+                $(
+                    assert_eq!(n.get::<$state>().unwrap().0, 1);
+                )*
+            });
+        }
+    }
+}
+
+mod node_depends_on_child_and_parent {
+    use super::*;
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Node(i32);
+    dep!(node(Node, (Child, Parent)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Child(i32);
+    dep!(child(Child, (Child,)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Parent(i32);
+    dep!(parent(Parent, (Parent,)));
+
+    test_state!(state: (Child, Node, Parent));
+}
+
+mod child_depends_on_node_that_depends_on_parent {
+    use super::*;
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Node(i32);
+    dep!(node(Node, (Parent,)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Child(i32);
+    dep!(child(Child, (Node,)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Parent(i32);
+    dep!(parent(Parent, (Parent,)));
+
+    test_state!(state: (Child, Node, Parent));
+}
+
+mod parent_depends_on_node_that_depends_on_child {
+    use super::*;
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Node(i32);
+    dep!(node(Node, (Child,)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Child(i32);
+    dep!(child(Child, (Child,)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Parent(i32);
+    dep!(parent(Parent, (Node,)));
+
+    test_state!(state: (Child, Node, Parent));
+}
+
+mod node_depends_on_other_node_state {
+    use super::*;
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Node1(i32);
+    dep!(node(Node1, (Node2,)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Node2(i32);
+    dep!(node(Node2, ()));
+
+    test_state!(state: (Node1, Node2));
+}
+
+mod node_child_and_parent_state_depends_on_self {
+    use super::*;
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Node(i32);
+    dep!(node(Node, ()));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Child(i32);
+    dep!(child(Child, (Child,)));
+
+    #[derive(Debug, Clone, Default, PartialEq, Component)]
+    struct Parent(i32);
+    dep!(parent(Parent, (Parent,)));
+
+    test_state!(state: (Child, Node, Parent));
+}

+ 51 - 31
packages/native-core/tests/fuzzing.rs

@@ -1,12 +1,8 @@
 use dioxus::prelude::Props;
 use dioxus_core::*;
-use dioxus_native_core::{
-    node_ref::{AttributeMask, NodeView},
-    real_dom::RealDom,
-    state::{ParentDepState, State},
-    NodeMask, SendAnyMap,
-};
-use dioxus_native_core_macro::{sorted_str_slice, State};
+use dioxus_native_core::prelude::*;
+use dioxus_native_core_macro::partial_derive_state;
+use shipyard::Component;
 use std::cell::Cell;
 
 fn random_ns() -> Option<&'static str> {
@@ -232,7 +228,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute {
 
 static mut TEMPLATE_COUNT: usize = 0;
 
-#[derive(PartialEq, Props)]
+#[derive(PartialEq, Props, Component)]
 struct DepthProps {
     depth: usize,
     root: bool,
@@ -294,26 +290,48 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
     node
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct BlablaState {}
+#[derive(Debug, Clone, PartialEq, Eq, Default, Component)]
+pub struct BlablaState {
+    count: usize,
+}
 
-/// Font style are inherited by default if not specified otherwise by some of the supported attributes.
-impl ParentDepState for BlablaState {
-    type Ctx = ();
-    type DepState = (Self,);
+#[partial_derive_state]
+impl State for BlablaState {
+    type ParentDependencies = (Self,);
+    type ChildDependencies = ();
+    type NodeDependencies = ();
 
-    const NODE_MASK: NodeMask =
-        NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",])));
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        .with_attrs(AttributeMaskBuilder::Some(&["blabla"]))
+        .with_element();
 
-    fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
-        false
+    fn update<'a>(
+        &mut self,
+        _: NodeView,
+        _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _: &SendAnyMap,
+    ) -> bool {
+        if let Some((parent,)) = parent {
+            if parent.count != 0 {
+                self.count += 1;
+            }
+        }
+        true
     }
-}
 
-#[derive(Clone, State, Default, Debug)]
-pub struct NodeState {
-    #[parent_dep_state(blabla)]
-    blabla: BlablaState,
+    fn create<'a>(
+        node_view: NodeView<()>,
+        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> Self {
+        let mut myself = Self::default();
+        myself.update(node_view, node, parent, children, context);
+        myself
+    }
 }
 
 // test for panics when creating random nodes and templates
@@ -328,11 +346,12 @@ fn create() {
             },
         );
         let mutations = vdom.rebuild();
-        let mut rdom: RealDom<NodeState> = RealDom::new();
-        let (to_update, _diff) = rdom.apply_mutations(mutations);
+        let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]);
+        let mut dioxus_state = DioxusState::create(&mut rdom);
+        dioxus_state.apply_mutations(&mut rdom, mutations);
 
         let ctx = SendAnyMap::new();
-        rdom.update_state(to_update, ctx);
+        rdom.update_state(ctx);
     }
 }
 
@@ -349,17 +368,18 @@ fn diff() {
             },
         );
         let mutations = vdom.rebuild();
-        let mut rdom: RealDom<NodeState> = RealDom::new();
-        let (to_update, _diff) = rdom.apply_mutations(mutations);
+        let mut rdom: RealDom = RealDom::new([BlablaState::to_type_erased()]);
+        let mut dioxus_state = DioxusState::create(&mut rdom);
+        dioxus_state.apply_mutations(&mut rdom, mutations);
 
         let ctx = SendAnyMap::new();
-        rdom.update_state(to_update, ctx);
+        rdom.update_state(ctx);
         for _ in 0..10 {
             let mutations = vdom.render_immediate();
-            let (to_update, _diff) = rdom.apply_mutations(mutations);
+            dioxus_state.apply_mutations(&mut rdom, mutations);
 
             let ctx = SendAnyMap::new();
-            rdom.update_state(to_update, ctx);
+            rdom.update_state(ctx);
         }
     }
 }

+ 50 - 31
packages/native-core/tests/miri_native.rs

@@ -1,34 +1,51 @@
 use dioxus::prelude::*;
-use dioxus_native_core::{
-    node_ref::{AttributeMask, NodeView},
-    real_dom::RealDom,
-    state::{ParentDepState, State},
-    NodeMask, SendAnyMap,
-};
-use dioxus_native_core_macro::{sorted_str_slice, State};
-use std::sync::{Arc, Mutex};
+use dioxus_native_core::prelude::*;
+use dioxus_native_core_macro::partial_derive_state;
+use shipyard::Component;
 use tokio::time::sleep;
 
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct BlablaState {}
-
-/// Font style are inherited by default if not specified otherwise by some of the supported attributes.
-impl ParentDepState for BlablaState {
-    type Ctx = ();
-    type DepState = (Self,);
-
-    const NODE_MASK: NodeMask =
-        NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["blabla",])));
+#[derive(Debug, Clone, PartialEq, Eq, Default, Component)]
+pub struct BlablaState {
+    count: usize,
+}
 
-    fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
-        false
+#[partial_derive_state]
+impl State for BlablaState {
+    type ParentDependencies = (Self,);
+    type ChildDependencies = ();
+    type NodeDependencies = ();
+
+    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
+        .with_attrs(AttributeMaskBuilder::Some(&["blabla"]))
+        .with_element();
+
+    fn update<'a>(
+        &mut self,
+        _: NodeView,
+        _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        _: &SendAnyMap,
+    ) -> bool {
+        if let Some((parent,)) = parent {
+            if parent.count != 0 {
+                self.count += 1;
+            }
+        }
+        true
     }
-}
 
-#[derive(Clone, State, Default, Debug)]
-pub struct NodeState {
-    #[parent_dep_state(blabla)]
-    blabla: BlablaState,
+    fn create<'a>(
+        node_view: NodeView<()>,
+        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+        context: &SendAnyMap,
+    ) -> Self {
+        let mut myself = Self::default();
+        myself.update(node_view, node, parent, children, context);
+        myself
+    }
 }
 
 mod dioxus_elements {
@@ -76,6 +93,7 @@ mod dioxus_elements {
 
 #[test]
 fn native_core_is_okay() {
+    use std::sync::{Arc, Mutex};
     use std::time::Duration;
 
     fn app(cx: Scope) -> Element {
@@ -113,23 +131,24 @@ fn native_core_is_okay() {
         .unwrap();
 
     rt.block_on(async {
-        let rdom = Arc::new(Mutex::new(RealDom::<NodeState>::new()));
+        let rdom = Arc::new(Mutex::new(RealDom::new([BlablaState::to_type_erased()])));
+        let mut dioxus_state = DioxusState::create(&mut rdom.lock().unwrap());
         let mut dom = VirtualDom::new(app);
 
-        let muts = dom.rebuild();
-        let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(muts);
+        let mutations = dom.rebuild();
+        dioxus_state.apply_mutations(&mut rdom.lock().unwrap(), mutations);
 
         let ctx = SendAnyMap::new();
-        rdom.lock().unwrap().update_state(to_update, ctx);
+        rdom.lock().unwrap().update_state(ctx);
 
         for _ in 0..10 {
             dom.wait_for_work().await;
 
             let mutations = dom.render_immediate();
-            let (to_update, _diff) = rdom.lock().unwrap().apply_mutations(mutations);
+            dioxus_state.apply_mutations(&mut rdom.lock().unwrap(), mutations);
 
             let ctx = SendAnyMap::new();
-            rdom.lock().unwrap().update_state(to_update, ctx);
+            rdom.lock().unwrap().update_state(ctx);
         }
     });
 }

+ 440 - 0
packages/native-core/tests/passes.rs

@@ -0,0 +1,440 @@
+use dioxus_native_core::node::NodeType;
+use dioxus_native_core::prelude::*;
+use dioxus_native_core_macro::partial_derive_state;
+use rustc_hash::{FxHashMap, FxHashSet};
+use shipyard::Component;
+
+fn create_blank_element() -> NodeType {
+    NodeType::Element(ElementNode {
+        tag: "div".to_owned(),
+        namespace: None,
+        attributes: FxHashMap::default(),
+        listeners: FxHashSet::default(),
+    })
+}
+
+#[test]
+fn node_pass() {
+    #[derive(Debug, Default, Clone, PartialEq, Component)]
+    struct Number(i32);
+
+    #[partial_derive_state]
+    impl State for Number {
+        type ChildDependencies = ();
+        type NodeDependencies = ();
+        type ParentDependencies = ();
+        const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+        fn update<'a>(
+            &mut self,
+            _: NodeView,
+            _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: &SendAnyMap,
+        ) -> bool {
+            self.0 += 1;
+            true
+        }
+
+        fn create<'a>(
+            node_view: NodeView<()>,
+            node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            context: &SendAnyMap,
+        ) -> Self {
+            let mut myself = Self::default();
+            myself.update(node_view, node, parent, children, context);
+            myself
+        }
+    }
+
+    let mut tree: RealDom = RealDom::new([Number::to_type_erased()]);
+    tree.update_state(SendAnyMap::new());
+
+    assert_eq!(
+        tree.get(tree.root_id()).unwrap().get().as_deref(),
+        Some(&Number(1))
+    );
+
+    // mark the node as dirty
+    tree.get_mut(tree.root_id()).unwrap().get_mut::<Number>();
+
+    tree.update_state(SendAnyMap::new());
+    assert_eq!(
+        tree.get(tree.root_id()).unwrap().get().as_deref(),
+        Some(&Number(2))
+    );
+}
+
+#[test]
+fn dependant_node_pass() {
+    #[derive(Debug, Default, Clone, PartialEq, Component)]
+    struct AddNumber(i32);
+
+    #[partial_derive_state]
+    impl State for AddNumber {
+        type ChildDependencies = ();
+        type NodeDependencies = (SubtractNumber,);
+        type ParentDependencies = ();
+        const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+        fn update<'a>(
+            &mut self,
+            _: NodeView,
+            _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: &SendAnyMap,
+        ) -> bool {
+            self.0 += 1;
+            true
+        }
+
+        fn create<'a>(
+            node_view: NodeView<()>,
+            node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            context: &SendAnyMap,
+        ) -> Self {
+            let mut myself = Self::default();
+            myself.update(node_view, node, parent, children, context);
+            myself
+        }
+    }
+
+    #[derive(Debug, Default, Clone, PartialEq, Component)]
+    struct SubtractNumber(i32);
+
+    #[partial_derive_state]
+    impl State for SubtractNumber {
+        type ChildDependencies = ();
+        type NodeDependencies = ();
+        type ParentDependencies = ();
+        const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+        fn update<'a>(
+            &mut self,
+            _: NodeView,
+            _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: &SendAnyMap,
+        ) -> bool {
+            self.0 -= 1;
+            true
+        }
+
+        fn create<'a>(
+            node_view: NodeView<()>,
+            node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            context: &SendAnyMap,
+        ) -> Self {
+            let mut myself = Self::default();
+            myself.update(node_view, node, parent, children, context);
+            myself
+        }
+    }
+
+    let mut tree: RealDom = RealDom::new([
+        AddNumber::to_type_erased(),
+        SubtractNumber::to_type_erased(),
+    ]);
+    tree.update_state(SendAnyMap::new());
+
+    let root = tree.get(tree.root_id()).unwrap();
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
+    assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-1)));
+
+    // mark the subtract state as dirty, it should update the add state
+    tree.get_mut(tree.root_id())
+        .unwrap()
+        .get_mut::<SubtractNumber>();
+
+    tree.update_state(SendAnyMap::new());
+    let root = tree.get(tree.root_id()).unwrap();
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(2)));
+    assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
+
+    // mark the add state as dirty, it should ~not~ update the subtract state
+    tree.get_mut(tree.root_id()).unwrap().get_mut::<AddNumber>();
+
+    tree.update_state(SendAnyMap::new());
+    let root = tree.get(tree.root_id()).unwrap();
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(3)));
+    assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
+}
+
+#[test]
+fn independant_node_pass() {
+    #[derive(Debug, Default, Clone, PartialEq, Component)]
+    struct AddNumber(i32);
+
+    #[partial_derive_state]
+    impl State for AddNumber {
+        type ChildDependencies = ();
+        type NodeDependencies = ();
+        type ParentDependencies = ();
+
+        const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+        fn update<'a>(
+            &mut self,
+            _: NodeView,
+            _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: &SendAnyMap,
+        ) -> bool {
+            self.0 += 1;
+            true
+        }
+
+        fn create<'a>(
+            node_view: NodeView<()>,
+            node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            context: &SendAnyMap,
+        ) -> Self {
+            let mut myself = Self::default();
+            myself.update(node_view, node, parent, children, context);
+            myself
+        }
+    }
+
+    #[derive(Debug, Default, Clone, PartialEq, Component)]
+    struct SubtractNumber(i32);
+
+    #[partial_derive_state]
+    impl State for SubtractNumber {
+        type ChildDependencies = ();
+        type NodeDependencies = ();
+        type ParentDependencies = ();
+
+        const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+        fn update<'a>(
+            &mut self,
+            _: NodeView,
+            _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: &SendAnyMap,
+        ) -> bool {
+            self.0 -= 1;
+            true
+        }
+
+        fn create<'a>(
+            node_view: NodeView<()>,
+            node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            context: &SendAnyMap,
+        ) -> Self {
+            let mut myself = Self::default();
+            myself.update(node_view, node, parent, children, context);
+            myself
+        }
+    }
+
+    let mut tree: RealDom = RealDom::new([
+        AddNumber::to_type_erased(),
+        SubtractNumber::to_type_erased(),
+    ]);
+    tree.update_state(SendAnyMap::new());
+
+    let root = tree.get(tree.root_id()).unwrap();
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
+    assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-1)));
+
+    // mark the subtract state as dirty, it should ~not~ update the add state
+    tree.get_mut(tree.root_id())
+        .unwrap()
+        .get_mut::<SubtractNumber>();
+
+    tree.update_state(SendAnyMap::new());
+
+    let root = tree.get(tree.root_id()).unwrap();
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
+    assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
+
+    // mark the add state as dirty, it should ~not~ update the subtract state
+    tree.get_mut(tree.root_id()).unwrap().get_mut::<AddNumber>();
+
+    tree.update_state(SendAnyMap::new());
+
+    let root = tree.get(tree.root_id()).unwrap();
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(2)));
+    assert_eq!(root.get().as_deref(), Some(&SubtractNumber(-2)));
+}
+
+#[test]
+fn down_pass() {
+    #[derive(Debug, Clone, PartialEq, Component)]
+    struct AddNumber(i32);
+
+    impl Default for AddNumber {
+        fn default() -> Self {
+            Self(1)
+        }
+    }
+
+    #[partial_derive_state]
+    impl State for AddNumber {
+        type ChildDependencies = ();
+        type NodeDependencies = ();
+        type ParentDependencies = (AddNumber,);
+
+        const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+        fn update<'a>(
+            &mut self,
+            _: NodeView,
+            _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: &SendAnyMap,
+        ) -> bool {
+            if let Some((parent,)) = parent {
+                self.0 += parent.0;
+            }
+            true
+        }
+
+        fn create<'a>(
+            node_view: NodeView<()>,
+            node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            context: &SendAnyMap,
+        ) -> Self {
+            let mut myself = Self::default();
+            myself.update(node_view, node, parent, children, context);
+            myself
+        }
+    }
+
+    let mut tree: RealDom = RealDom::new([AddNumber::to_type_erased()]);
+    let grandchild1 = tree.create_node(create_blank_element());
+    let grandchild1 = grandchild1.id();
+    let mut child1 = tree.create_node(create_blank_element());
+    child1.add_child(grandchild1);
+    let child1 = child1.id();
+    let grandchild2 = tree.create_node(create_blank_element());
+    let grandchild2 = grandchild2.id();
+    let mut child2 = tree.create_node(create_blank_element());
+    child2.add_child(grandchild2);
+    let child2 = child2.id();
+    let mut parent = tree.get_mut(tree.root_id()).unwrap();
+    parent.add_child(child1);
+    parent.add_child(child2);
+
+    tree.update_state(SendAnyMap::new());
+
+    let root = tree.get(tree.root_id()).unwrap();
+    dbg!(root.id());
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(1)));
+
+    let child1 = tree.get(child1).unwrap();
+    dbg!(child1.id());
+    assert_eq!(child1.get().as_deref(), Some(&AddNumber(2)));
+
+    let grandchild1 = tree.get(grandchild1).unwrap();
+    assert_eq!(grandchild1.get().as_deref(), Some(&AddNumber(3)));
+
+    let child2 = tree.get(child2).unwrap();
+    assert_eq!(child2.get().as_deref(), Some(&AddNumber(2)));
+
+    let grandchild2 = tree.get(grandchild2).unwrap();
+    assert_eq!(grandchild2.get().as_deref(), Some(&AddNumber(3)));
+}
+
+#[test]
+fn up_pass() {
+    // Tree before:
+    // 1=\
+    //   1=\
+    //     1
+    //   1=\
+    //     1
+    // Tree after:
+    // 4=\
+    //   2=\
+    //     1
+    //   2=\
+    //     1
+
+    #[derive(Debug, Clone, PartialEq, Component)]
+    struct AddNumber(i32);
+
+    #[partial_derive_state]
+    impl State for AddNumber {
+        type ChildDependencies = (AddNumber,);
+        type NodeDependencies = ();
+        type ParentDependencies = ();
+
+        const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();
+
+        fn update<'a>(
+            &mut self,
+            _: NodeView,
+            _: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            _: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            _: &SendAnyMap,
+        ) -> bool {
+            self.0 += children.iter().map(|(i,)| i.0).sum::<i32>();
+            true
+        }
+
+        fn create<'a>(
+            node_view: NodeView<()>,
+            node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
+            parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
+            children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
+            context: &SendAnyMap,
+        ) -> Self {
+            let mut myself = Self(1);
+            myself.update(node_view, node, parent, children, context);
+            myself
+        }
+    }
+
+    let mut tree: RealDom = RealDom::new([AddNumber::to_type_erased()]);
+    let grandchild1 = tree.create_node(create_blank_element());
+    let grandchild1 = grandchild1.id();
+    let mut child1 = tree.create_node(create_blank_element());
+    child1.add_child(grandchild1);
+    let child1 = child1.id();
+    let grandchild2 = tree.create_node(create_blank_element());
+    let grandchild2 = grandchild2.id();
+    let mut child2 = tree.create_node(create_blank_element());
+    child2.add_child(grandchild2);
+    let child2 = child2.id();
+    let mut parent = tree.get_mut(tree.root_id()).unwrap();
+    parent.add_child(child1);
+    parent.add_child(child2);
+
+    tree.update_state(SendAnyMap::new());
+
+    let root = tree.get(tree.root_id()).unwrap();
+    assert_eq!(root.get().as_deref(), Some(&AddNumber(5)));
+
+    let child1 = tree.get(child1).unwrap();
+    assert_eq!(child1.get().as_deref(), Some(&AddNumber(2)));
+
+    let grandchild1 = tree.get(grandchild1).unwrap();
+    assert_eq!(grandchild1.get().as_deref(), Some(&AddNumber(1)));
+
+    let child2 = tree.get(child2).unwrap();
+    assert_eq!(child2.get().as_deref(), Some(&AddNumber(2)));
+
+    let grandchild2 = tree.get(grandchild2).unwrap();
+    assert_eq!(grandchild2.get().as_deref(), Some(&AddNumber(1)));
+}

+ 2 - 0
packages/rink/.gitignore

@@ -0,0 +1,2 @@
+/target
+Cargo.lock

+ 2 - 0
packages/rink/.vscode/spellright.dict

@@ -0,0 +1,2 @@
+esque
+Tui

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov