Forráskód Böngészése

Merge pull request #648 from Demonthos/fix-non-str-attributes

fix custom attribute value support
Jon Kelley 2 éve
szülő
commit
07e71956d4
36 módosított fájl, 812 hozzáadás és 717 törlés
  1. 1 1
      packages/core/Cargo.toml
  2. 9 3
      packages/core/src/arena.rs
  3. 12 20
      packages/core/src/create.rs
  4. 11 17
      packages/core/src/diff.rs
  5. 7 7
      packages/core/src/lib.rs
  6. 4 15
      packages/core/src/mutations.rs
  7. 133 15
      packages/core/src/nodes.rs
  8. 1 1
      packages/core/src/scope_arena.rs
  9. 21 6
      packages/core/src/scopes.rs
  10. 1 4
      packages/core/src/virtual_dom.rs
  11. 28 4
      packages/core/tests/attr_cleanup.rs
  12. 10 2
      packages/core/tests/boolattrs.rs
  13. 1 1
      packages/core/tests/borrowedstate.rs
  14. 3 1
      packages/core/tests/bubble_error.rs
  15. 10 8
      packages/core/tests/cycle.rs
  16. 15 13
      packages/core/tests/diff_component.rs
  17. 20 18
      packages/core/tests/diff_keyed_list.rs
  18. 72 62
      packages/core/tests/diff_unkeyed_list.rs
  19. 12 6
      packages/core/tests/kitchen_sink.rs
  20. 28 26
      packages/core/tests/suspense.rs
  21. 1 2
      packages/native-core-macro/Cargo.toml
  22. 2 1
      packages/native-core-macro/src/lib.rs
  23. 0 1
      packages/native-core-macro/tests/update_state.rs
  24. 2 2
      packages/native-core/Cargo.toml
  25. 2 1
      packages/native-core/src/lib.rs
  26. 61 18
      packages/native-core/src/node.rs
  27. 8 6
      packages/native-core/src/node_ref.rs
  28. 82 38
      packages/native-core/src/passes.rs
  29. 18 22
      packages/native-core/src/real_dom.rs
  30. 22 12
      packages/native-core/src/state.rs
  31. 181 371
      packages/native-core/src/tree.rs
  32. 1 1
      packages/native-core/src/utils/persistant_iterator.rs
  33. 1 1
      packages/tui/Cargo.toml
  34. 1 1
      packages/tui/src/focus.rs
  35. 26 7
      packages/web/src/dom.rs
  36. 5 3
      packages/web/src/lib.rs

+ 1 - 1
packages/core/Cargo.toml

@@ -44,4 +44,4 @@ pretty_assertions = "1.3.0"
 
 [features]
 default = []
-serialize = ["serde"]
+serialize = ["serde"]

+ 9 - 3
packages/core/src/arena.rs

@@ -153,11 +153,17 @@ impl VirtualDom {
         });
 
         // Now that all the references are gone, we can safely drop our own references in our listeners.
-        let mut listeners = scope.listeners.borrow_mut();
+        let mut listeners = scope.attributes_to_drop.borrow_mut();
         listeners.drain(..).for_each(|listener| {
             let listener = unsafe { &*listener };
-            if let AttributeValue::Listener(l) = &listener.value {
-                _ = l.take();
+            match &listener.value {
+                AttributeValue::Listener(l) => {
+                    _ = l.take();
+                }
+                AttributeValue::Any(a) => {
+                    _ = a.take();
+                }
+                _ => (),
             }
         });
     }

+ 12 - 20
packages/core/src/create.rs

@@ -1,5 +1,5 @@
 use crate::any_props::AnyProps;
-use crate::innerlude::{VComponent, VPlaceholder, VText};
+use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
@@ -285,7 +285,7 @@ impl<'b> VirtualDom {
         }
     }
 
-    fn write_attribute(&mut self, attribute: &crate::Attribute, id: ElementId) {
+    fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
         // Make sure we set the attribute's associated id
         attribute.mounted_element.set(id);
 
@@ -293,9 +293,17 @@ impl<'b> VirtualDom {
         let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) };
 
         match &attribute.value {
-            AttributeValue::Text(value) => {
+            AttributeValue::Listener(_) => {
+                self.mutations.push(NewEventListener {
+                    // all listeners start with "on"
+                    name: &unbounded_name[2..],
+                    id,
+                })
+            }
+            _ => {
                 // Safety: we promise not to re-alias this text later on after committing it to the mutation
-                let unbounded_value: &str = unsafe { std::mem::transmute(*value) };
+                let value: BorrowedAttributeValue<'b> = (&attribute.value).into();
+                let unbounded_value = unsafe { std::mem::transmute(value) };
 
                 self.mutations.push(SetAttribute {
                     name: unbounded_name,
@@ -304,22 +312,6 @@ impl<'b> VirtualDom {
                     id,
                 })
             }
-            AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
-                name: unbounded_name,
-                value: *value,
-                id,
-            }),
-            AttributeValue::Listener(_) => {
-                self.mutations.push(NewEventListener {
-                    // all listeners start with "on"
-                    name: &unbounded_name[2..],
-                    id,
-                })
-            }
-            AttributeValue::Float(_) => todo!(),
-            AttributeValue::Int(_) => todo!(),
-            AttributeValue::Any(_) => todo!(),
-            AttributeValue::None => todo!(),
         }
     }
 

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

@@ -1,7 +1,7 @@
 use crate::{
     any_props::AnyProps,
     arena::ElementId,
-    innerlude::{DirtyScope, VComponent, VPlaceholder, VText},
+    innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText},
     mutations::Mutation,
     nodes::RenderReturn,
     nodes::{DynamicNode, VNode},
@@ -150,22 +150,16 @@ impl<'b> VirtualDom {
         };
     }
 
-    fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) {
-        // todo: add more types of attribute values
-        match right_attr.value {
-            AttributeValue::Text(text) => {
-                let name = unsafe { std::mem::transmute(left_attr.name) };
-                let value = unsafe { std::mem::transmute(text) };
-                self.mutations.push(Mutation::SetAttribute {
-                    id: left_attr.mounted_element.get(),
-                    ns: right_attr.namespace,
-                    name,
-                    value,
-                });
-            }
-            // todo: more types of attribute values
-            _ => todo!("other attribute types"),
-        }
+    fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) {
+        let name = unsafe { std::mem::transmute(left_attr.name) };
+        let value: BorrowedAttributeValue<'b> = (&right_attr.value).into();
+        let value = unsafe { std::mem::transmute(value) };
+        self.mutations.push(Mutation::SetAttribute {
+            id: left_attr.mounted_element.get(),
+            ns: right_attr.namespace,
+            name,
+            value,
+        });
     }
 
     fn diff_vcomponent(

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

@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
 }
 
 pub use crate::innerlude::{
-    fc_to_builder, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element,
-    ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties,
-    RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template,
-    TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
+    fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
+    Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
+    Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
+    TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
 };
 
 /// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
     pub use crate::innerlude::{
-        fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
-        Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
-        Throw, VNode, VirtualDom,
+        fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
+        IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
+        Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
     };
 }
 

+ 4 - 15
packages/core/src/mutations.rs

@@ -1,6 +1,6 @@
 use rustc_hash::FxHashSet;
 
-use crate::{arena::ElementId, ScopeId, Template};
+use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Template};
 
 /// A container for all the relevant steps to modify the Real DOM
 ///
@@ -61,7 +61,7 @@ impl<'a> Mutations<'a> {
     derive(serde::Serialize, serde::Deserialize),
     serde(tag = "type")
 )]
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq)]
 pub enum Mutation<'a> {
     /// Add these m children to the target element
     AppendChildren {
@@ -193,8 +193,9 @@ pub enum Mutation<'a> {
     SetAttribute {
         /// The name of the attribute to set.
         name: &'a str,
+
         /// The value of the attribute.
-        value: &'a str,
+        value: BorrowedAttributeValue<'a>,
 
         /// The ID of the node to set the attribute of.
         id: ElementId,
@@ -204,18 +205,6 @@ pub enum Mutation<'a> {
         ns: Option<&'a str>,
     },
 
-    /// Set the value of a node's attribute.
-    SetBoolAttribute {
-        /// The name of the attribute to set.
-        name: &'a str,
-
-        /// The value of the attribute.
-        value: bool,
-
-        /// The ID of the node to set the attribute of.
-        id: ElementId,
-    },
-
     /// Set the textcontent of a node.
     SetText {
         /// The textcontent of the node

+ 133 - 15
packages/core/src/nodes.rs

@@ -5,8 +5,8 @@ use bumpalo::boxed::Box as BumpBox;
 use bumpalo::Bump;
 use std::{
     any::{Any, TypeId},
-    cell::{Cell, RefCell, UnsafeCell},
-    fmt::Arguments,
+    cell::{self, Cell, RefCell, UnsafeCell},
+    fmt::{Arguments, Debug},
     future::Future,
 };
 
@@ -523,13 +523,108 @@ pub enum AttributeValue<'a> {
     Listener(RefCell<Option<ListenerCb<'a>>>),
 
     /// An arbitrary value that implements PartialEq and is static
-    Any(BumpBox<'a, dyn AnyValue>),
+    Any(RefCell<Option<BumpBox<'a, dyn AnyValue>>>),
 
     /// A "none" value, resulting in the removal of an attribute from the dom
     None,
 }
 
-type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
+pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
+
+/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed
+///
+/// These varients are used to communicate what the value of an attribute is that needs to be updated
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(untagged))]
+pub enum BorrowedAttributeValue<'a> {
+    /// Text attribute
+    Text(&'a str),
+
+    /// A float
+    Float(f64),
+
+    /// Signed integer
+    Int(i64),
+
+    /// Boolean
+    Bool(bool),
+
+    /// An arbitrary value that implements PartialEq and is static
+    #[cfg_attr(
+        feature = "serialize",
+        serde(
+            deserialize_with = "deserialize_any_value",
+            serialize_with = "serialize_any_value"
+        )
+    )]
+    Any(std::cell::Ref<'a, dyn AnyValue>),
+
+    /// A "none" value, resulting in the removal of an attribute from the dom
+    None,
+}
+
+impl<'a> From<&'a AttributeValue<'a>> for BorrowedAttributeValue<'a> {
+    fn from(value: &'a AttributeValue<'a>) -> Self {
+        match value {
+            AttributeValue::Text(value) => BorrowedAttributeValue::Text(value),
+            AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value),
+            AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value),
+            AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value),
+            AttributeValue::Listener(_) => {
+                panic!("A listener cannot be turned into a borrowed value")
+            }
+            AttributeValue::Any(value) => {
+                let value = value.borrow();
+                BorrowedAttributeValue::Any(std::cell::Ref::map(value, |value| {
+                    &**value.as_ref().unwrap()
+                }))
+            }
+            AttributeValue::None => BorrowedAttributeValue::None,
+        }
+    }
+}
+
+impl Debug for BorrowedAttributeValue<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
+            Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
+            Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
+            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
+            Self::Any(_) => f.debug_tuple("Any").field(&"...").finish(),
+            Self::None => write!(f, "None"),
+        }
+    }
+}
+
+impl PartialEq for BorrowedAttributeValue<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self::Text(l0), Self::Text(r0)) => l0 == r0,
+            (Self::Float(l0), Self::Float(r0)) => l0 == r0,
+            (Self::Int(l0), Self::Int(r0)) => l0 == r0,
+            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
+            (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(&**r0),
+            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
+        }
+    }
+}
+
+#[cfg(feature = "serialize")]
+fn serialize_any_value<S>(_: &cell::Ref<'_, dyn AnyValue>, _: S) -> Result<S::Ok, S::Error>
+where
+    S: serde::Serializer,
+{
+    panic!("Any cannot be serialized")
+}
+
+#[cfg(feature = "serialize")]
+fn deserialize_any_value<'de, 'a, D>(_: D) -> Result<cell::Ref<'a, dyn AnyValue>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    panic!("Any cannot be deserialized")
+}
 
 impl<'a> std::fmt::Debug for AttributeValue<'a> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -553,29 +648,36 @@ impl<'a> PartialEq for AttributeValue<'a> {
             (Self::Int(l0), Self::Int(r0)) => l0 == r0,
             (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
             (Self::Listener(_), Self::Listener(_)) => true,
-            (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
-            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
+            (Self::Any(l0), Self::Any(r0)) => {
+                let l0 = l0.borrow();
+                let r0 = r0.borrow();
+                l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap())
+            }
+            _ => false,
         }
     }
 }
 
 #[doc(hidden)]
-pub trait AnyValue {
+pub trait AnyValue: 'static {
     fn any_cmp(&self, other: &dyn AnyValue) -> bool;
-    fn our_typeid(&self) -> TypeId;
+    fn as_any(&self) -> &dyn Any;
+    fn type_id(&self) -> TypeId {
+        self.as_any().type_id()
+    }
 }
 
-impl<T: PartialEq + Any> AnyValue for T {
+impl<T: Any + PartialEq + 'static> AnyValue for T {
     fn any_cmp(&self, other: &dyn AnyValue) -> bool {
-        if self.type_id() != other.our_typeid() {
-            return false;
+        if let Some(other) = other.as_any().downcast_ref() {
+            self == other
+        } else {
+            false
         }
-
-        self == unsafe { &*(other as *const _ as *const T) }
     }
 
-    fn our_typeid(&self) -> TypeId {
-        self.type_id()
+    fn as_any(&self) -> &dyn Any {
+        self
     }
 }
 
@@ -742,26 +844,36 @@ pub trait IntoAttributeValue<'a> {
     fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
 }
 
+impl<'a> IntoAttributeValue<'a> for AttributeValue<'a> {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        self
+    }
+}
+
 impl<'a> IntoAttributeValue<'a> for &'a str {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Text(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for f64 {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Float(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for i64 {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Int(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for bool {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
         AttributeValue::Bool(self)
     }
 }
+
 impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
     fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
         use bumpalo::core_alloc::fmt::Write;
@@ -770,3 +882,9 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
         AttributeValue::Text(str_buf.into_bump_str())
     }
 }
+
+impl<'a> IntoAttributeValue<'a> for BumpBox<'a, dyn AnyValue> {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Any(RefCell::new(Some(self)))
+    }
+}

+ 1 - 1
packages/core/src/scope_arena.rs

@@ -44,7 +44,7 @@ impl VirtualDom {
             hook_idx: Default::default(),
             shared_contexts: Default::default(),
             borrowed_props: Default::default(),
-            listeners: Default::default(),
+            attributes_to_drop: Default::default(),
         }))
     }
 

+ 21 - 6
packages/core/src/scopes.rs

@@ -7,7 +7,7 @@ use crate::{
     innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
     lazynodes::LazyNodes,
     nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
-    Attribute, AttributeValue, Element, Event, Properties, TaskId,
+    AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -88,7 +88,7 @@ pub struct ScopeState {
     pub(crate) spawned_tasks: FxHashSet<TaskId>,
 
     pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
-    pub(crate) listeners: RefCell<Vec<*const Attribute<'static>>>,
+    pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
 
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
     pub(crate) placeholder: Cell<Option<ElementId>>,
@@ -374,11 +374,15 @@ impl<'src> ScopeState {
     pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
         let element = rsx.call(self);
 
-        let mut listeners = self.listeners.borrow_mut();
+        let mut listeners = self.attributes_to_drop.borrow_mut();
         for attr in element.dynamic_attrs {
-            if let AttributeValue::Listener(_) = attr.value {
-                let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
-                listeners.push(unbounded);
+            match attr.value {
+                AttributeValue::Any(_) | AttributeValue::Listener(_) => {
+                    let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
+                    listeners.push(unbounded);
+                }
+
+                _ => (),
             }
         }
 
@@ -509,6 +513,17 @@ impl<'src> ScopeState {
         AttributeValue::Listener(RefCell::new(Some(boxed)))
     }
 
+    /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
+    pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue<'src> {
+        // safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
+        // This is the suggested way to build a bumpbox
+        //
+        // In theory, we could just use regular boxes
+        let boxed: BumpBox<'src, dyn AnyValue> =
+            unsafe { BumpBox::from_raw(self.bump().alloc(value)) };
+        AttributeValue::Any(RefCell::new(Some(boxed)))
+    }
+
     /// Inject an error into the nearest error boundary and quit rendering
     ///
     /// The error doesn't need to implement Error or any specific traits since the boundary

+ 1 - 4
packages/core/src/virtual_dom.rs

@@ -641,10 +641,7 @@ impl VirtualDom {
 
     /// Swap the current mutations with a new
     fn finalize(&mut self) -> Mutations {
-        // todo: make this a routine
-        let mut out = Mutations::default();
-        std::mem::swap(&mut self.mutations, &mut out);
-        out
+        std::mem::take(&mut self.mutations)
     }
 }
 

+ 28 - 4
packages/core/tests/attr_cleanup.rs

@@ -2,8 +2,10 @@
 //!
 //! This tests to ensure we clean it up
 
+use bumpalo::Bump;
 use dioxus::core::{ElementId, Mutation::*};
 use dioxus::prelude::*;
+use dioxus_core::{AttributeValue, BorrowedAttributeValue};
 
 #[test]
 fn attrs_cycle() {
@@ -22,6 +24,8 @@ fn attrs_cycle() {
         }
     });
 
+    let bump = Bump::new();
+
     assert_eq!(
         dom.rebuild().santize().edits,
         [
@@ -36,8 +40,18 @@ fn attrs_cycle() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             AssignId { path: &[0,], id: ElementId(3,) },
-            SetAttribute { name: "class", value: "1", id: ElementId(3,), ns: None },
-            SetAttribute { name: "id", value: "1", id: ElementId(3,), ns: None },
+            SetAttribute {
+                name: "class",
+                value: (&*bump.alloc("1".into_value(&bump))).into(),
+                id: ElementId(3,),
+                ns: None
+            },
+            SetAttribute {
+                name: "id",
+                value: (&*bump.alloc("1".into_value(&bump))).into(),
+                id: ElementId(3,),
+                ns: None
+            },
             ReplaceWith { id: ElementId(1,), m: 1 },
         ]
     );
@@ -57,8 +71,18 @@ fn attrs_cycle() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2) },
             AssignId { path: &[0], id: ElementId(3) },
-            SetAttribute { name: "class", value: "3", id: ElementId(3), ns: None },
-            SetAttribute { name: "id", value: "3", id: ElementId(3), ns: None },
+            SetAttribute {
+                name: "class",
+                value: BorrowedAttributeValue::Text("3"),
+                id: ElementId(3),
+                ns: None
+            },
+            SetAttribute {
+                name: "id",
+                value: BorrowedAttributeValue::Text("3"),
+                id: ElementId(3),
+                ns: None
+            },
             ReplaceWith { id: ElementId(1), m: 1 }
         ]
     );

+ 10 - 2
packages/core/tests/boolattrs.rs

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

+ 1 - 1
packages/core/tests/borrowedstate.rs

@@ -18,7 +18,7 @@ fn test_borrowed_state() {
             ReplacePlaceholder { path: &[0,], m: 1 },
             AppendChildren { m: 1, id: ElementId(0) },
         ]
-    )
+    );
 }
 
 fn Parent(cx: Scope) -> Element {

+ 3 - 1
packages/core/tests/bubble_error.rs

@@ -20,7 +20,9 @@ fn app(cx: Scope) -> Element {
 fn bubbles_error() {
     let mut dom = VirtualDom::new(app);
 
-    let _edits = dom.rebuild().santize();
+    {
+        let _edits = dom.rebuild().santize();
+    }
 
     dom.mark_dirty(ScopeId(0));
 

+ 10 - 8
packages/core/tests/cycle.rs

@@ -12,14 +12,16 @@ fn cycling_elements() {
         })
     });
 
-    let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            AppendChildren { m: 1, id: ElementId(0) },
-        ]
-    );
+    {
+        let edits = dom.rebuild().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                AppendChildren { m: 1, id: ElementId(0) },
+            ]
+        );
+    }
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(

+ 15 - 13
packages/core/tests/diff_component.rs

@@ -59,19 +59,21 @@ fn component_swap() {
     }
 
     let mut dom = VirtualDom::new(app);
-    let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(2) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(3) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(4) },
-            ReplacePlaceholder { path: &[1], m: 3 },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5) },
-            AppendChildren { m: 2, id: ElementId(0) }
-        ]
-    );
+    {
+        let edits = dom.rebuild().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(2) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(3) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(4) },
+                ReplacePlaceholder { path: &[1], m: 3 },
+                LoadTemplate { name: "template", index: 0, id: ElementId(5) },
+                AppendChildren { m: 2, id: ElementId(0) }
+            ]
+        );
+    }
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(

+ 20 - 18
packages/core/tests/diff_keyed_list.rs

@@ -20,22 +20,24 @@ fn keyed_diffing_out_of_order() {
         cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
     });
 
-    assert_eq!(
-        dom.rebuild().santize().edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
-            AppendChildren { m: 10, id: ElementId(0) },
-        ]
-    );
+    {
+        assert_eq!(
+            dom.rebuild().santize().edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
+                AppendChildren { m: 10, id: ElementId(0) },
+            ]
+        );
+    }
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
@@ -44,7 +46,7 @@ fn keyed_diffing_out_of_order() {
             PushRoot { id: ElementId(7,) },
             InsertBefore { id: ElementId(5,), m: 1 },
         ]
-    )
+    );
 }
 
 /// Should result in moves only
@@ -70,7 +72,7 @@ fn keyed_diffing_out_of_order_adds() {
             PushRoot { id: ElementId(4,) },
             InsertBefore { id: ElementId(1,), m: 2 },
         ]
-    )
+    );
 }
 
 /// Should result in moves only

+ 72 - 62
packages/core/tests/diff_unkeyed_list.rs

@@ -315,66 +315,76 @@ fn remove_many() {
         })
     });
 
-    let edits = dom.rebuild().santize();
-    assert!(edits.templates.is_empty());
-    assert_eq!(
-        edits.edits,
-        [
-            CreatePlaceholder { id: ElementId(1,) },
-            AppendChildren { id: ElementId(0), m: 1 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
-            ReplaceWith { id: ElementId(1,), m: 1 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
-            HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
-            HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
-            LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
-            HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
-            InsertAfter { id: ElementId(2,), m: 4 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            CreatePlaceholder { id: ElementId(11,) },
-            Remove { id: ElementId(9,) },
-            Remove { id: ElementId(7,) },
-            Remove { id: ElementId(5,) },
-            Remove { id: ElementId(1,) },
-            ReplaceWith { id: ElementId(2,), m: 1 },
-        ]
-    );
-
-    dom.mark_dirty(ScopeId(0));
-    let edits = dom.render_immediate().santize();
-    assert_eq!(
-        edits.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
-            HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
-            ReplaceWith { id: ElementId(11,), m: 1 },
-        ]
-    )
+    {
+        let edits = dom.rebuild().santize();
+        assert!(edits.templates.is_empty());
+        assert_eq!(
+            edits.edits,
+            [
+                CreatePlaceholder { id: ElementId(1,) },
+                AppendChildren { id: ElementId(0), m: 1 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+                HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
+                ReplaceWith { id: ElementId(1,), m: 1 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
+                HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
+                HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
+                HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
+                LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
+                HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
+                InsertAfter { id: ElementId(2,), m: 4 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                CreatePlaceholder { id: ElementId(11,) },
+                Remove { id: ElementId(9,) },
+                Remove { id: ElementId(7,) },
+                Remove { id: ElementId(5,) },
+                Remove { id: ElementId(1,) },
+                ReplaceWith { id: ElementId(2,), m: 1 },
+            ]
+        );
+    }
+
+    {
+        dom.mark_dirty(ScopeId(0));
+        let edits = dom.render_immediate().santize();
+        assert_eq!(
+            edits.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
+                HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
+                ReplaceWith { id: ElementId(11,), m: 1 },
+            ]
+        )
+    }
 }

+ 12 - 6
packages/core/tests/kitchen_sink.rs

@@ -1,3 +1,4 @@
+use bumpalo::Bump;
 use dioxus::core::{ElementId, Mutation};
 use dioxus::prelude::*;
 
@@ -26,17 +27,22 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
 #[test]
 fn dual_stream() {
     let mut dom = VirtualDom::new(basic_syntax_is_a_template);
+    let bump = Bump::new();
     let edits = dom.rebuild().santize();
 
     use Mutation::*;
-    assert_eq!(
-        edits.edits,
+    assert_eq!(edits.edits, {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
+            SetAttribute {
+                name: "class",
+                value: (&*bump.alloc("123".into_value(&bump))).into(),
+                id: ElementId(1),
+                ns: None,
+            },
             NewEventListener { name: "click", id: ElementId(1) },
             HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
-            AppendChildren { id: ElementId(0), m: 1 }
-        ],
-    );
+            AppendChildren { id: ElementId(0), m: 1 },
+        ]
+    });
 }

+ 28 - 26
packages/core/tests/suspense.rs

@@ -9,32 +9,34 @@ use std::time::Duration;
 async fn it_works() {
     let mut dom = VirtualDom::new(app);
 
-    let mutations = dom.rebuild().santize();
-
-    // We should at least get the top-level template in before pausing for the children
-    // note: we dont test template edits anymore
-    // assert_eq!(
-    //     mutations.templates,
-    //     [
-    //         CreateElement { name: "div" },
-    //         CreateStaticText { value: "Waiting for child..." },
-    //         CreateStaticPlaceholder,
-    //         AppendChildren { m: 2 },
-    //         SaveTemplate { name: "template", m: 1 }
-    //     ]
-    // );
-
-    // And we should load it in and assign the placeholder properly
-    assert_eq!(
-        mutations.edits,
-        [
-            LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
-            // can we even?
-            AssignId { path: &[1], id: ElementId(3) },
-            AppendChildren { m: 1, id: ElementId(0) },
-        ]
-    );
+    {
+        let mutations = dom.rebuild().santize();
+
+        // We should at least get the top-level template in before pausing for the children
+        // note: we dont test template edits anymore
+        // assert_eq!(
+        //     mutations.templates,
+        //     [
+        //         CreateElement { name: "div" },
+        //         CreateStaticText { value: "Waiting for child..." },
+        //         CreateStaticPlaceholder,
+        //         AppendChildren { m: 2 },
+        //         SaveTemplate { name: "template", m: 1 }
+        //     ]
+        // );
+
+        // And we should load it in and assign the placeholder properly
+        assert_eq!(
+            mutations.edits,
+            [
+                LoadTemplate { name: "template", index: 0, id: ElementId(1) },
+                // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
+                // can we even?
+                AssignId { path: &[1], id: ElementId(3) },
+                AppendChildren { m: 1, id: ElementId(0) },
+            ]
+        );
+    }
 
     // wait just a moment, not enough time for the boundary to resolve
 

+ 1 - 2
packages/native-core-macro/Cargo.toml

@@ -18,10 +18,9 @@ proc-macro = true
 syn = { version = "1.0.11", features = ["extra-traits"] }
 quote = "1.0"
 dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
-
-[dev-dependencies]
 dioxus = { path = "../dioxus" }
 
+[dev-dependencies]
 smallvec = "1.6"
 rustc-hash = "1.1.0"
 anymap = "0.12.1"

+ 2 - 1
packages/native-core-macro/src/lib.rs

@@ -393,7 +393,8 @@ impl<'a> StateMember<'a> {
 
         let ty = &self.mem.ty;
         let unit_type = &self.mem.unit_type;
-        let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(unsafe{&*{&node.node_data as *const _}}, #ty::NODE_MASK));
+        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 => {

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

@@ -43,7 +43,6 @@ impl ParentDepState for ParentDepCallCounter {
     const NODE_MASK: NodeMask = NodeMask::ALL;
     fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
         self.0 += 1;
-        println!("ParentDepCallCounter::reduce on {:?}\n{}", _node, self.0);
         true
     }
 }

+ 2 - 2
packages/native-core/Cargo.toml

@@ -18,7 +18,7 @@ dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
 taffy = "0.2.1"
 smallvec = "1.6"
 rustc-hash = "1.1.0"
-anymap = "0.12.1"
+anymap = "1.0.0-beta.2"
 slab = "0.4"
 parking_lot = "0.12.1"
 crossbeam-deque = "0.8.2"
@@ -26,4 +26,4 @@ dashmap = "5.4.0"
 
 [dev-dependencies]
 rand = "0.8.5"
-dioxus = { path = "../dioxus", version = "^0.2.1" }
+dioxus = { path = "../dioxus", version = "^0.2.1" }

+ 2 - 1
packages/native-core/src/lib.rs

@@ -1,3 +1,4 @@
+use std::any::Any;
 use std::hash::BuildHasherDefault;
 
 pub use node_ref::NodeMask;
@@ -20,7 +21,7 @@ pub mod utils;
 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 anymap::any::Any + Send + Sync + 'static>;
+pub type SendAnyMap = anymap::Map<dyn Any + Send + Sync + 'static>;
 
 /// Used in derived state macros
 #[derive(Eq, PartialEq)]

+ 61 - 18
packages/native-core/src/node.rs

@@ -1,43 +1,44 @@
 use crate::{state::State, tree::NodeId};
-use dioxus_core::ElementId;
+use dioxus_core::{AnyValue, BorrowedAttributeValue, ElementId};
 use rustc_hash::{FxHashMap, FxHashSet};
+use std::fmt::Debug;
 
 /// The node is stored client side and stores only basic data about the node.
 #[derive(Debug, Clone)]
-pub struct Node<S: State> {
+pub struct Node<S: State, V: FromAnyValue = ()> {
     /// The transformed state of the node.
     pub state: S,
     /// The raw data for the node
-    pub node_data: NodeData,
+    pub node_data: NodeData<V>,
 }
 
 #[derive(Debug, Clone)]
-pub struct NodeData {
+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,
+    pub node_type: NodeType<V>,
 }
 
 /// A type of node with data specific to the node type. The types are a subset of the [VNode] types.
 #[derive(Debug, Clone)]
-pub enum NodeType {
+pub enum NodeType<V: FromAnyValue = ()> {
     Text {
         text: String,
     },
     Element {
         tag: String,
         namespace: Option<String>,
-        attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue>,
+        attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
         listeners: FxHashSet<String>,
     },
     Placeholder,
 }
 
-impl<S: State> Node<S> {
-    pub(crate) fn new(node_type: NodeType) -> Self {
+impl<S: State, V: FromAnyValue> Node<S, V> {
+    pub(crate) fn new(node_type: NodeType<V>) -> Self {
         Node {
             state: S::default(),
             node_data: NodeData {
@@ -64,24 +65,59 @@ pub struct OwnedAttributeDiscription {
 /// An attribute on a DOM node, such as `id="my-thing"` or
 /// `href="https://example.com"`.
 #[derive(Clone, Copy, Debug)]
-pub struct OwnedAttributeView<'a> {
+pub struct OwnedAttributeView<'a, V: FromAnyValue = ()> {
     /// The discription of the attribute.
     pub attribute: &'a OwnedAttributeDiscription,
 
     /// The value of the attribute.
-    pub value: &'a OwnedAttributeValue,
+    pub value: &'a OwnedAttributeValue<V>,
 }
 
-#[derive(Clone, Debug)]
-pub enum OwnedAttributeValue {
+#[derive(Clone)]
+pub enum OwnedAttributeValue<V: FromAnyValue = ()> {
     Text(String),
-    Float(f32),
-    Int(i32),
+    Float(f64),
+    Int(i64),
     Bool(bool),
+    Custom(V),
     None,
 }
 
-impl OwnedAttributeValue {
+pub trait FromAnyValue {
+    fn from_any_value(value: &dyn AnyValue) -> Self;
+}
+
+impl FromAnyValue for () {
+    fn from_any_value(_: &dyn AnyValue) -> Self {}
+}
+
+impl<V: FromAnyValue> Debug for OwnedAttributeValue<V> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
+            Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
+            Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
+            Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
+            Self::Custom(_) => f.debug_tuple("Any").finish(),
+            Self::None => write!(f, "None"),
+        }
+    }
+}
+
+impl<V: FromAnyValue> From<BorrowedAttributeValue<'_>> for OwnedAttributeValue<V> {
+    fn from(value: 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 => Self::None,
+        }
+    }
+}
+
+impl<V: FromAnyValue> OwnedAttributeValue<V> {
     pub fn as_text(&self) -> Option<&str> {
         match self {
             OwnedAttributeValue::Text(text) => Some(text),
@@ -89,14 +125,14 @@ impl OwnedAttributeValue {
         }
     }
 
-    pub fn as_float(&self) -> Option<f32> {
+    pub fn as_float(&self) -> Option<f64> {
         match self {
             OwnedAttributeValue::Float(float) => Some(*float),
             _ => None,
         }
     }
 
-    pub fn as_int(&self) -> Option<i32> {
+    pub fn as_int(&self) -> Option<i64> {
         match self {
             OwnedAttributeValue::Int(int) => Some(*int),
             _ => None,
@@ -116,4 +152,11 @@ impl OwnedAttributeValue {
             _ => None,
         }
     }
+
+    pub fn as_custom(&self) -> Option<&V> {
+        match self {
+            OwnedAttributeValue::Custom(custom) => Some(custom),
+            _ => None,
+        }
+    }
 }

+ 8 - 6
packages/native-core/src/node_ref.rs

@@ -1,21 +1,21 @@
 use dioxus_core::ElementId;
 
 use crate::{
-    node::{NodeData, NodeType, OwnedAttributeView},
+    node::{FromAnyValue, NodeData, NodeType, OwnedAttributeView},
     state::union_ordered_iter,
     RealNodeId,
 };
 
 /// A view into a [VNode] with limited access.
 #[derive(Debug)]
-pub struct NodeView<'a> {
-    inner: &'a NodeData,
+pub struct NodeView<'a, V: FromAnyValue = ()> {
+    inner: &'a NodeData<V>,
     mask: NodeMask,
 }
 
-impl<'a> NodeView<'a> {
+impl<'a, V: FromAnyValue> NodeView<'a, V> {
     /// Create a new NodeView from a VNode, and mask.
-    pub fn new(node: &'a NodeData, view: NodeMask) -> Self {
+    pub fn new(node: &'a NodeData<V>, view: NodeMask) -> Self {
         Self {
             inner: node,
             mask: view,
@@ -55,7 +55,9 @@ impl<'a> NodeView<'a> {
     }
 
     /// Get any attributes that are enabled in the mask
-    pub fn attributes<'b>(&'b self) -> Option<impl Iterator<Item = OwnedAttributeView<'a>> + 'b> {
+    pub fn attributes<'b>(
+        &'b self,
+    ) -> Option<impl Iterator<Item = OwnedAttributeView<'a, V>> + 'b> {
         match &self.inner.node_type {
             NodeType::Element { attributes, .. } => Some(
                 attributes

+ 82 - 38
packages/native-core/src/passes.rs

@@ -310,11 +310,65 @@ impl<T> AnyPass<T> {
     }
 }
 
-struct RawPointer<T>(*mut T);
-unsafe impl<T> Send for RawPointer<T> {}
-unsafe impl<T> Sync for RawPointer<T> {}
+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<T, Tr: TreeView<T>>(
+pub fn resolve_passes_single_threaded<T, Tr: TreeView<T>>(
     tree: &mut Tr,
     dirty_nodes: DirtyNodeStates,
     mut passes: Vec<&AnyPass<T>>,
@@ -327,41 +381,31 @@ pub fn resolve_passes<T, Tr: TreeView<T>>(
     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 tree_mut = tree as *mut _;
-                    let raw_ptr = RawPointer(tree_mut);
-                    let dirty_states = dirty_states.clone();
-                    let nodes_updated = nodes_updated.clone();
-                    let ctx = ctx.clone();
-                    s.spawn(move || unsafe {
-                        // let tree_mut: &mut Tr = &mut *raw_ptr.0;
-                        let raw = raw_ptr;
-                        // 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: &mut Tr = &mut *raw.0;
-                        let mut dirty = DirtyNodes::default();
-                        dirty_states.all_dirty(pass_id, &mut dirty, tree_mut);
-                        pass.resolve(tree_mut, dirty, &dirty_states, &nodes_updated, &ctx);
-                    });
-                } else {
-                    i += 1;
-                }
+        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();
+                // 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 mut dirty = DirtyNodes::default();
+                dirty_states.all_dirty(pass_id, &mut dirty, tree);
+                pass.resolve(tree, 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()
     }

+ 18 - 22
packages/native-core/src/real_dom.rs

@@ -270,26 +270,7 @@ impl<S: State> RealDom<S> {
                                 namespace: ns.map(|s| s.to_string()),
                                 volatile: false,
                             },
-                            crate::node::OwnedAttributeValue::Text(value.to_string()),
-                        );
-                        mark_dirty(
-                            node_id,
-                            NodeMask::new_with_attrs(AttributeMask::single(name)),
-                            &mut nodes_updated,
-                        );
-                    }
-                }
-                SetBoolAttribute { name, value, id } => {
-                    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 {
-                        attributes.insert(
-                            OwnedAttributeDiscription {
-                                name: name.to_string(),
-                                namespace: None,
-                                volatile: false,
-                            },
-                            crate::node::OwnedAttributeValue::Bool(value),
+                            OwnedAttributeValue::from(value),
                         );
                         mark_dirty(
                             node_id,
@@ -358,12 +339,12 @@ impl<S: State> RealDom<S> {
     }
 
     /// 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(
+    pub fn update_state_single_threaded(
         &mut self,
         nodes_updated: DirtyNodeStates,
         ctx: SendAnyMap,
     ) -> FxDashSet<RealNodeId> {
-        S::update(nodes_updated, &mut self.tree, ctx)
+        S::update_single_threaded(nodes_updated, &mut self.tree, ctx)
     }
 
     /// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up.
@@ -415,6 +396,21 @@ impl<S: State> RealDom<S> {
     }
 }
 
+impl<S: State + Sync> RealDom<S>
+where
+    Tree<Node<S>>: 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)
+    }
+}
+
 impl<S: State> Deref for RealDom<S> {
     type Target = Tree<Node<S>>;
 

+ 22 - 12
packages/native-core/src/state.rs

@@ -1,8 +1,8 @@
 use std::cmp::Ordering;
 
-use crate::node::Node;
+use crate::node::{FromAnyValue, Node};
 use crate::node_ref::{NodeMask, NodeView};
-use crate::passes::{resolve_passes, AnyPass, DirtyNodeStates};
+use crate::passes::{resolve_passes, resolve_passes_single_threaded, AnyPass, DirtyNodeStates};
 use crate::tree::TreeView;
 use crate::{FxDashSet, RealNodeId, SendAnyMap};
 
@@ -76,7 +76,7 @@ pub(crate) fn union_ordered_iter<'a>(
 ///     }
 /// }
 /// ```
-pub trait ChildDepState {
+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.
@@ -87,7 +87,7 @@ pub trait ChildDepState {
     /// 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,
+        node: NodeView<'a, V>,
         children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
         ctx: &Self::Ctx,
     ) -> bool
@@ -138,7 +138,7 @@ pub trait ChildDepState {
 ///     }
 /// }
 /// ```
-pub trait ParentDepState {
+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.
@@ -149,7 +149,7 @@ pub trait ParentDepState {
     /// 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,
+        node: NodeView<'a, V>,
         parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
         ctx: &Self::Ctx,
     ) -> bool;
@@ -193,7 +193,7 @@ pub trait ParentDepState {
 ///     }
 /// }
 /// ```
-pub trait NodeDepState {
+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.
@@ -203,7 +203,7 @@ pub trait NodeDepState {
     /// 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,
+        node: NodeView<'a, V>,
         node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>,
         ctx: &Self::Ctx,
     ) -> bool;
@@ -217,7 +217,7 @@ pub trait State: Default + Clone + 'static {
     const MASKS: &'static [NodeMask];
 
     #[doc(hidden)]
-    fn update<T: TreeView<Node<Self>>>(
+    fn update<T: TreeView<Node<Self>> + Sync + Send>(
         dirty: DirtyNodeStates,
         tree: &mut T,
         ctx: SendAnyMap,
@@ -225,12 +225,22 @@ pub trait State: Default + Clone + 'static {
         let passes = Self::PASSES.iter().collect();
         resolve_passes(tree, dirty, passes, ctx)
     }
+
+    #[doc(hidden)]
+    fn update_single_threaded<T: TreeView<Node<Self>>>(
+        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, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
+    fn reduce<'a>(&mut self, _: NodeView<'a>, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
     where
         Self::DepState: 'a,
     {
@@ -241,7 +251,7 @@ impl ChildDepState for () {
 impl ParentDepState for () {
     type Ctx = ();
     type DepState = ();
-    fn reduce<'a>(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool {
+    fn reduce<'a>(&mut self, _: NodeView<'a>, _: Option<()>, _: &Self::Ctx) -> bool {
         false
     }
 }
@@ -249,7 +259,7 @@ impl ParentDepState for () {
 impl NodeDepState for () {
     type DepState = ();
     type Ctx = ();
-    fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool {
+    fn reduce<'a>(&mut self, _: NodeView<'a>, _sibling: (), _: &Self::Ctx) -> bool {
         false
     }
 }

+ 181 - 371
packages/native-core/src/tree.rs

@@ -1,11 +1,5 @@
-use core::panic;
-use parking_lot::lock_api::RawMutex as _;
-use parking_lot::{RawMutex, RwLock};
-use slab::Slab;
-use std::cell::UnsafeCell;
 use std::collections::VecDeque;
 use std::marker::PhantomData;
-use std::sync::Arc;
 
 #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
 pub struct NodeId(pub usize);
@@ -93,17 +87,7 @@ pub trait TreeView<T>: Sized {
 
     fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>>;
 
-    fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> {
-        let mut_ptr: *mut Self = self;
-        unsafe {
-            // Safety: No node has itself as a child.
-            (*mut_ptr).get_mut(id).and_then(|parent| {
-                (*mut_ptr)
-                    .children_mut(id)
-                    .map(|children| (parent, children))
-            })
-        }
-    }
+    fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)>;
 
     fn children_ids(&self, id: NodeId) -> Option<&[NodeId]>;
 
@@ -111,28 +95,12 @@ pub trait TreeView<T>: Sized {
 
     fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>;
 
-    fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> {
-        let mut_ptr: *mut Self = self;
-        unsafe {
-            // Safety: No node has itself as a parent.
-            (*mut_ptr)
-                .get_mut(id)
-                .map(|node| (node, (*mut_ptr).parent_mut(id)))
-        }
-    }
+    fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>;
 
     fn parent_id(&self, id: NodeId) -> Option<NodeId>;
 
     fn height(&self, id: NodeId) -> Option<u16>;
 
-    fn map<T2, F: Fn(&T) -> &T2, FMut: Fn(&mut T) -> &mut T2>(
-        &mut self,
-        map: F,
-        map_mut: FMut,
-    ) -> TreeMap<T, T2, Self, F, FMut> {
-        TreeMap::new(self, map, map_mut)
-    }
-
     fn size(&self) -> usize;
 
     fn traverse_depth_first(&self, mut f: impl FnMut(&T)) {
@@ -208,58 +176,39 @@ pub trait TreeLike<T>: TreeView<T> {
     fn insert_after(&mut self, id: NodeId, new: NodeId);
 }
 
-pub struct ChildNodeIterator<'a, T, Tr: TreeView<T>> {
-    tree: &'a Tr,
-    children_ids: &'a [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, Tr: TreeView<T>> Iterator for ChildNodeIterator<'a, T, Tr> {
+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.tree.get_unchecked(*id)
+            &self.nodes.get(id.0).unwrap().value
         })
     }
 }
 
-pub struct ChildNodeIteratorMut<'a, T, Tr: TreeView<T> + 'a> {
-    tree: *mut Tr,
-    children_ids: &'a [NodeId],
-    index: usize,
-    node_type: PhantomData<T>,
-}
-
-unsafe impl<'a, T, Tr: TreeView<T> + 'a> Sync for ChildNodeIteratorMut<'a, T, Tr> {}
-
-impl<'a, T, Tr: TreeView<T>> ChildNodeIteratorMut<'a, T, Tr> {
-    fn tree_mut(&mut self) -> &'a mut Tr {
-        unsafe { &mut *self.tree }
-    }
+pub struct ChildNodeIteratorMut<'a, T> {
+    nodes: Vec<&'a mut Node<T>>,
 }
 
-impl<'a, T: 'a, Tr: TreeView<T>> Iterator for ChildNodeIteratorMut<'a, T, Tr> {
+impl<'a, T: 'a> Iterator for ChildNodeIteratorMut<'a, T> {
     type Item = &'a mut T;
 
     fn next(&mut self) -> Option<Self::Item> {
-        let owned = self.children_ids.get(self.index).copied();
-        match owned {
-            Some(id) => {
-                self.index += 1;
-
-                Some(self.tree_mut().get_mut_unchecked(id))
-            }
-            None => None,
-        }
+        self.nodes.pop().map(|node| &mut node.value)
     }
 }
 
 impl<T> TreeView<T> for Tree<T> {
-    type Iterator<'a> = ChildNodeIterator<'a, T, Tree<T>> where T: 'a;
-    type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree<T>> where T: 'a;
+    type Iterator<'a> = ChildNodeIterator<'a, T> where T: 'a;
+    type IteratorMut<'a> = ChildNodeIteratorMut<'a, T> where T: 'a;
 
     fn root(&self) -> NodeId {
         self.root
@@ -275,24 +224,26 @@ impl<T> TreeView<T> for Tree<T> {
 
     fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
         self.children_ids(id).map(|children_ids| ChildNodeIterator {
-            tree: self,
-            children_ids,
+            nodes: &self.nodes,
+            children_ids: children_ids.to_vec(),
             index: 0,
             node_type: PhantomData,
         })
     }
 
     fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
-        let raw_ptr = self as *mut Self;
-        unsafe {
-            // Safety: No node will appear as a child twice
-            self.children_ids(id)
-                .map(|children_ids| ChildNodeIteratorMut {
-                    tree: &mut *raw_ptr,
-                    children_ids,
-                    index: 0,
-                    node_type: PhantomData,
-                })
+        // 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
         }
     }
 
@@ -340,6 +291,39 @@ impl<T> TreeView<T> for Tree<T> {
     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
+        }
+    }
 }
 
 impl<T> TreeLike<T> for Tree<T> {
@@ -435,248 +419,6 @@ impl<T> TreeLike<T> for Tree<T> {
         self.set_height(new, height);
     }
 }
-
-pub struct TreeMap<'a, T1, T2, Tr, F, FMut>
-where
-    Tr: TreeView<T1>,
-    F: Fn(&T1) -> &T2,
-    FMut: Fn(&mut T1) -> &mut T2,
-{
-    tree: &'a mut Tr,
-    map: F,
-    map_mut: FMut,
-    in_node_type: PhantomData<T1>,
-    out_node_type: PhantomData<T2>,
-}
-
-impl<'a, T1, T2, Tr, F, FMut> TreeMap<'a, T1, T2, Tr, F, FMut>
-where
-    Tr: TreeView<T1>,
-    F: Fn(&T1) -> &T2,
-    FMut: Fn(&mut T1) -> &mut T2,
-{
-    pub fn new(tree: &'a mut Tr, map: F, map_mut: FMut) -> Self {
-        TreeMap {
-            tree,
-            map,
-            map_mut,
-            in_node_type: PhantomData,
-            out_node_type: PhantomData,
-        }
-    }
-}
-
-impl<'a, T1, T2, Tr, F, FMut> TreeView<T2> for TreeMap<'a, T1, T2, Tr, F, FMut>
-where
-    Tr: TreeView<T1>,
-    F: Fn(&T1) -> &T2,
-    FMut: Fn(&mut T1) -> &mut T2,
-{
-    type Iterator<'b> = ChildNodeIterator<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>>
-    where
-        T2: 'b,
-        Self:'b;
-    type IteratorMut<'b> = ChildNodeIteratorMut<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>>
-    where
-        T2: 'b,
-        Self:'b;
-
-    fn root(&self) -> NodeId {
-        self.tree.root()
-    }
-
-    fn get(&self, id: NodeId) -> Option<&T2> {
-        self.tree.get(id).map(|node| (self.map)(node))
-    }
-
-    fn get_mut(&mut self, id: NodeId) -> Option<&mut T2> {
-        self.tree.get_mut(id).map(|node| (self.map_mut)(node))
-    }
-
-    fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
-        self.children_ids(id).map(|children_ids| ChildNodeIterator {
-            tree: self,
-            children_ids,
-            index: 0,
-            node_type: PhantomData,
-        })
-    }
-
-    fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
-        let raw_ptr = self as *mut Self;
-        unsafe {
-            // Safety: No node can be a child twice.
-            self.children_ids(id)
-                .map(|children_ids| ChildNodeIteratorMut {
-                    tree: &mut *raw_ptr,
-                    children_ids,
-                    index: 0,
-                    node_type: PhantomData,
-                })
-        }
-    }
-
-    fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
-        self.tree.children_ids(id)
-    }
-
-    fn parent(&self, id: NodeId) -> Option<&T2> {
-        self.tree.parent(id).map(|node| (self.map)(node))
-    }
-
-    fn parent_mut(&mut self, id: NodeId) -> Option<&mut T2> {
-        self.tree.parent_mut(id).map(|node| (self.map_mut)(node))
-    }
-
-    fn parent_id(&self, id: NodeId) -> Option<NodeId> {
-        self.tree.parent_id(id)
-    }
-
-    fn height(&self, id: NodeId) -> Option<u16> {
-        self.tree.height(id)
-    }
-
-    fn get_unchecked(&self, id: NodeId) -> &T2 {
-        (self.map)(self.tree.get_unchecked(id))
-    }
-
-    fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T2 {
-        (self.map_mut)(self.tree.get_mut_unchecked(id))
-    }
-
-    fn size(&self) -> usize {
-        self.tree.size()
-    }
-}
-
-/// A view into a tree that can be shared between multiple threads. Nodes are locked invividually.
-pub struct SharedView<'a, T, Tr: TreeView<T>> {
-    tree: Arc<UnsafeCell<&'a mut Tr>>,
-    node_locks: Arc<RwLock<Vec<RawMutex>>>,
-    node_type: PhantomData<T>,
-}
-
-impl<'a, T, Tr: TreeView<T>> SharedView<'a, T, Tr> {
-    /// Checks if a node is currently locked. Returns None if the node does not exist.
-    pub fn check_lock(&self, id: NodeId) -> Option<bool> {
-        let locks = self.node_locks.read();
-        locks.get(id.0).map(|lock| lock.is_locked())
-    }
-}
-
-unsafe impl<'a, T, Tr: TreeView<T>> Send for SharedView<'a, T, Tr> {}
-unsafe impl<'a, T, Tr: TreeView<T>> Sync for SharedView<'a, T, Tr> {}
-impl<'a, T, Tr: TreeView<T>> Clone for SharedView<'a, T, Tr> {
-    fn clone(&self) -> Self {
-        Self {
-            tree: self.tree.clone(),
-            node_locks: self.node_locks.clone(),
-            node_type: PhantomData,
-        }
-    }
-}
-
-impl<'a, T, Tr: TreeView<T>> SharedView<'a, T, Tr> {
-    pub fn new(tree: &'a mut Tr) -> Self {
-        let tree = Arc::new(UnsafeCell::new(tree));
-        let mut node_locks = Vec::new();
-        for _ in 0..unsafe { (*tree.get()).size() } {
-            node_locks.push(RawMutex::INIT);
-        }
-        Self {
-            tree,
-            node_locks: Arc::new(RwLock::new(node_locks)),
-            node_type: PhantomData,
-        }
-    }
-
-    fn lock_node(&self, node: NodeId) {
-        let read = self.node_locks.read();
-        let lock = read.get(node.0);
-        match lock {
-            Some(lock) => lock.lock(),
-            None => {
-                drop(read);
-                let mut write = self.node_locks.write();
-                write.resize_with(node.0 + 1, || RawMutex::INIT);
-                unsafe { write.get_unchecked(node.0).lock() }
-            }
-        }
-    }
-
-    fn unlock_node(&self, node: NodeId) {
-        let read = self.node_locks.read();
-        let lock = read.get(node.0);
-        match lock {
-            Some(lock) => unsafe { lock.unlock() },
-            None => {
-                panic!("unlocking node that was not locked")
-            }
-        }
-    }
-
-    fn with_node<R>(&self, node_id: NodeId, f: impl FnOnce(&'a mut Tr) -> R) -> R {
-        self.lock_node(node_id);
-        let tree = unsafe { &mut *self.tree.get() };
-        let r = f(tree);
-        self.unlock_node(node_id);
-        r
-    }
-}
-
-impl<'a, T, Tr: TreeView<T>> TreeView<T> for SharedView<'a, T, Tr> {
-    type Iterator<'b> = Tr::Iterator<'b> where T: 'b, Self: 'b;
-
-    type IteratorMut<'b>=Tr::IteratorMut<'b>
-    where
-        T: 'b,
-        Self: 'b;
-
-    fn root(&self) -> NodeId {
-        unsafe { (*self.tree.get()).root() }
-    }
-
-    fn get(&self, id: NodeId) -> Option<&T> {
-        self.with_node(id, |t| t.get(id))
-    }
-
-    fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
-        self.with_node(id, |t| t.get_mut(id))
-    }
-
-    fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
-        self.with_node(id, |t| t.children(id))
-    }
-
-    fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
-        self.with_node(id, |t| t.children_mut(id))
-    }
-
-    fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
-        self.with_node(id, |t| t.children_ids(id))
-    }
-
-    fn parent(&self, id: NodeId) -> Option<&T> {
-        self.with_node(id, |t| t.get(id))
-    }
-
-    fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> {
-        self.with_node(id, |t| t.parent_mut(id))
-    }
-
-    fn parent_id(&self, id: NodeId) -> Option<NodeId> {
-        self.with_node(id, |t| t.parent_id(id))
-    }
-
-    fn height(&self, id: NodeId) -> Option<u16> {
-        unsafe { (*self.tree.get()).height(id) }
-    }
-
-    fn size(&self) -> usize {
-        unsafe { (*self.tree.get()).size() }
-    }
-}
-
 #[test]
 fn creation() {
     let mut tree = Tree::new(1);
@@ -785,59 +527,6 @@ fn deletion() {
     assert_eq!(tree.children_ids(parent).unwrap(), &[]);
 }
 
-#[test]
-fn shared_view() {
-    use std::thread;
-    let mut tree = Tree::new(1);
-    let parent = tree.root();
-    let child = tree.create_node(0);
-    tree.add_child(parent, child);
-
-    let shared = SharedView::new(&mut tree);
-
-    thread::scope(|s| {
-        let (mut shared1, mut shared2, mut shared3) =
-            (shared.clone(), shared.clone(), shared.clone());
-        s.spawn(move || {
-            assert_eq!(*shared1.get_mut(parent).unwrap(), 1);
-            assert_eq!(*shared1.get_mut(child).unwrap(), 0);
-        });
-        s.spawn(move || {
-            assert_eq!(*shared2.get_mut(child).unwrap(), 0);
-            assert_eq!(*shared2.get_mut(parent).unwrap(), 1);
-        });
-        s.spawn(move || {
-            assert_eq!(*shared3.get_mut(parent).unwrap(), 1);
-            assert_eq!(*shared3.get_mut(child).unwrap(), 0);
-        });
-    });
-}
-
-#[test]
-fn map() {
-    #[derive(Debug, PartialEq)]
-    struct Value {
-        value: i32,
-    }
-    impl Value {
-        fn new(value: i32) -> Self {
-            Self { value }
-        }
-    }
-    let mut tree = Tree::new(Value::new(1));
-    let parent = tree.root();
-    let child = tree.create_node(Value::new(0));
-    tree.add_child(parent, child);
-
-    let mut mapped = tree.map(|x| &x.value, |x| &mut x.value);
-
-    *mapped.get_mut(child).unwrap() = 1;
-    *mapped.get_mut(parent).unwrap() = 2;
-
-    assert_eq!(*tree.get(parent).unwrap(), Value::new(2));
-    assert_eq!(*tree.get(child).unwrap(), Value::new(1));
-}
-
 #[test]
 fn traverse_depth_first() {
     let mut tree = Tree::new(0);
@@ -857,3 +546,124 @@ fn traverse_depth_first() {
         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()
+    }
+}

+ 1 - 1
packages/native-core/src/utils/persistant_iterator.rs

@@ -229,7 +229,7 @@ impl PersistantElementIter {
 #[derive(Default, Clone, Debug)]
 struct Empty {}
 impl State for Empty {
-    const PASSES: &'static [crate::AnyPass<crate::node::Node<Self>>] = &[];
+    const PASSES: &'static [crate::AnyPass<crate::node::Node<Self, ()>>] = &[];
 
     const MASKS: &'static [crate::NodeMask] = &[];
 }

+ 1 - 1
packages/tui/Cargo.toml

@@ -27,7 +27,7 @@ futures = "0.3.19"
 taffy = "0.2.1"
 smallvec = "1.6"
 rustc-hash = "1.1.0"
-anymap = "0.12.1"
+anymap = "1.0.0-beta.2"
 futures-channel = "0.3.25"
 
 [dev-dependencies]

+ 1 - 1
packages/tui/src/focus.rs

@@ -79,7 +79,7 @@ impl NodeDepState for Focus {
                 if let Some(index) = a
                     .value
                     .as_int()
-                    .or_else(|| a.value.as_text().and_then(|v| v.parse::<i32>().ok()))
+                    .or_else(|| a.value.as_text().and_then(|v| v.parse::<i64>().ok()))
                 {
                     match index.cmp(&0) {
                         Ordering::Less => FocusLevel::Unfocusable,

+ 26 - 7
packages/web/src/dom.rs

@@ -7,7 +7,9 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?>
 
-use dioxus_core::{ElementId, Mutation, Template, TemplateAttribute, TemplateNode};
+use dioxus_core::{
+    BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
+};
 use dioxus_html::{event_bubbles, CompositionData, FormData};
 use dioxus_interpreter_js::{save_template, Channel};
 use futures_channel::mpsc;
@@ -175,16 +177,33 @@ impl WebsysDom {
                     value,
                     id,
                     ns,
-                } => i.set_attribute(id.0 as u32, name, value, ns.unwrap_or_default()),
-                SetBoolAttribute { name, value, id } => {
-                    i.set_attribute(id.0 as u32, name, if *value { "true" } else { "false" }, "")
-                }
+                } => match value {
+                    BorrowedAttributeValue::Text(txt) => {
+                        i.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
+                    }
+                    BorrowedAttributeValue::Float(f) => {
+                        i.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
+                    }
+                    BorrowedAttributeValue::Int(n) => {
+                        i.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
+                    }
+                    BorrowedAttributeValue::Bool(b) => i.set_attribute(
+                        id.0 as u32,
+                        name,
+                        if *b { "true" } else { "false" },
+                        ns.unwrap_or_default(),
+                    ),
+                    BorrowedAttributeValue::None => {
+                        i.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
+                    }
+                    _ => unreachable!(),
+                },
                 SetText { value, id } => i.set_text(id.0 as u32, value),
                 NewEventListener { name, id, .. } => {
-                    i.new_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8);
+                    i.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8);
                 }
                 RemoveEventListener { name, id } => {
-                    i.remove_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8)
+                    i.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
                 }
                 Remove { id } => i.remove(id.0 as u32),
                 PushRoot { id } => i.push_root(id.0 as u32),

+ 5 - 3
packages/web/src/lib.rs

@@ -189,10 +189,12 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
 
     // if should_hydrate {
     // } else {
-    let edits = dom.rebuild();
+    {
+        let edits = dom.rebuild();
 
-    websys_dom.load_templates(&edits.templates);
-    websys_dom.apply_edits(edits.edits);
+        websys_dom.load_templates(&edits.templates);
+        websys_dom.apply_edits(edits.edits);
+    }
 
     // the mutations come back with nothing - we need to actually mount them
     websys_dom.mount();