diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 47652e262..7158b3970 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -44,4 +44,4 @@ pretty_assertions = "1.3.0" [features] default = [] -serialize = ["serde"] +serialize = ["serde"] \ No newline at end of file diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 36323730a..a11fc69ba 100644 --- a/packages/core/src/arena.rs +++ b/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(); + } + _ => (), } }); } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index f54684410..7e5633f12 100644 --- a/packages/core/src/create.rs +++ b/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!(), } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index efd534fe7..7227ee6e7 100644 --- a/packages/core/src/diff.rs +++ b/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( diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f13c20b43..c4f162580 100644 --- a/packages/core/src/lib.rs +++ b/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, }; } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index f8553cd46..551069c45 100644 --- a/packages/core/src/mutations.rs +++ b/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 diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index dee2f32b3..bb9f6734b 100644 --- a/packages/core/src/nodes.rs +++ b/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>>), /// An arbitrary value that implements PartialEq and is static - Any(BumpBox<'a, dyn AnyValue>), + Any(RefCell>>), /// A "none" value, resulting in the removal of an attribute from the dom None, } -type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + 'a>; +pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + '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(_: &cell::Ref<'_, dyn AnyValue>, _: S) -> Result +where + S: serde::Serializer, +{ + panic!("Any cannot be serialized") +} + +#[cfg(feature = "serialize")] +fn deserialize_any_value<'de, 'a, D>(_: D) -> Result, 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 AnyValue for T { +impl 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))) + } +} diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 8d3de2b45..86625f86c 100644 --- a/packages/core/src/scope_arena.rs +++ b/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(), })) } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 1945cb32b..b0189cbde 100644 --- a/packages/core/src/scopes.rs +++ b/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, pub(crate) borrowed_props: RefCell>>, - pub(crate) listeners: RefCell>>, + pub(crate) attributes_to_drop: RefCell>>, pub(crate) props: Option>>, pub(crate) placeholder: Cell>, @@ -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(&'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 diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a5a1f5ce6..289cecf93 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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) } } diff --git a/packages/core/tests/attr_cleanup.rs b/packages/core/tests/attr_cleanup.rs index 8889f77c1..2e3cf5d17 100644 --- a/packages/core/tests/attr_cleanup.rs +++ b/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 } ] ); diff --git a/packages/core/tests/boolattrs.rs b/packages/core/tests/boolattrs.rs index 5cd2ad1ad..0d02bf8af 100644 --- a/packages/core/tests/boolattrs.rs +++ b/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) }, ] - ) + ); } diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 0aace9c97..1b83789ec 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/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 { diff --git a/packages/core/tests/bubble_error.rs b/packages/core/tests/bubble_error.rs index 9c165c52f..7d6966c46 100644 --- a/packages/core/tests/bubble_error.rs +++ b/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)); diff --git a/packages/core/tests/cycle.rs b/packages/core/tests/cycle.rs index 133b04658..10498f5cc 100644 --- a/packages/core/tests/cycle.rs +++ b/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!( diff --git a/packages/core/tests/diff_component.rs b/packages/core/tests/diff_component.rs index 2b74f8302..69ab6de6d 100644 --- a/packages/core/tests/diff_component.rs +++ b/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!( diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs index 81013849d..be26e00c0 100644 --- a/packages/core/tests/diff_keyed_list.rs +++ b/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 diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs index 09bf3b215..4934f6080 100644 --- a/packages/core/tests/diff_unkeyed_list.rs +++ b/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 }, - ] - ); + { + 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(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, + [ + 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, + [ + 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 }, - ] - ) + { + 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 }, + ] + ) + } } diff --git a/packages/core/tests/kitchen_sink.rs b/packages/core/tests/kitchen_sink.rs index fca6fcbaa..191d1da7c 100644 --- a/packages/core/tests/kitchen_sink.rs +++ b/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 }, + ] + }); } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 9fac13937..775ca3d71 100644 --- a/packages/core/tests/suspense.rs +++ b/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(); + { + 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 } - // ] - // ); + // 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) }, - ] - ); + // 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 diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index bff83ec42..759d8ff00 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/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" diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 5bf50afcc..75b814347 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/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 => { diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index e2f04c72c..fc36c42ed 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/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 } } diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 309fba781..9958b8a02 100644 --- a/packages/native-core/Cargo.toml +++ b/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" } \ No newline at end of file +dioxus = { path = "../dioxus", version = "^0.2.1" } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index ae739af86..1a8bb0ce0 100644 --- a/packages/native-core/src/lib.rs +++ b/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 = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; -pub type SendAnyMap = anymap::Map; +pub type SendAnyMap = anymap::Map; /// Used in derived state macros #[derive(Eq, PartialEq)] diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 4530fd350..f5e93e2f5 100644 --- a/packages/native-core/src/node.rs +++ b/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 { +pub struct Node { /// The transformed state of the node. pub state: S, /// The raw data for the node - pub node_data: NodeData, + pub node_data: NodeData, } #[derive(Debug, Clone)] -pub struct NodeData { +pub struct NodeData { /// The id of the node pub node_id: NodeId, /// The id of the node in the vdom. pub element_id: Option, /// Additional inforation specific to the node type - pub node_type: NodeType, + pub node_type: NodeType, } /// 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 { Text { text: String, }, Element { tag: String, namespace: Option, - attributes: FxHashMap, + attributes: FxHashMap>, listeners: FxHashSet, }, Placeholder, } -impl Node { - pub(crate) fn new(node_type: NodeType) -> Self { +impl Node { + pub(crate) fn new(node_type: NodeType) -> 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, } -#[derive(Clone, Debug)] -pub enum OwnedAttributeValue { +#[derive(Clone)] +pub enum OwnedAttributeValue { 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 Debug for OwnedAttributeValue { + 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 From> for OwnedAttributeValue { + 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 OwnedAttributeValue { 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 { + pub fn as_float(&self) -> Option { match self { OwnedAttributeValue::Float(float) => Some(*float), _ => None, } } - pub fn as_int(&self) -> Option { + pub fn as_int(&self) -> Option { 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, + } + } } diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index d178c2d18..8d4523efc 100644 --- a/packages/native-core/src/node_ref.rs +++ b/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, 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, 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> + 'b> { + pub fn attributes<'b>( + &'b self, + ) -> Option> + 'b> { match &self.inner.node_type { NodeType::Element { attributes, .. } => Some( attributes diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index bdc7c7e5b..44331ae37 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -310,11 +310,65 @@ impl AnyPass { } } -struct RawPointer(*mut T); -unsafe impl Send for RawPointer {} -unsafe impl Sync for RawPointer {} +pub fn resolve_passes + Sync + Send>( + tree: &mut Tr, + dirty_nodes: DirtyNodeStates, + passes: Vec<&AnyPass>, + ctx: SendAnyMap, +) -> FxDashSet { + 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 = 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>( +pub fn resolve_passes_single_threaded>( tree: &mut Tr, dirty_nodes: DirtyNodeStates, mut passes: Vec<&AnyPass>, @@ -327,41 +381,31 @@ pub fn resolve_passes>( 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() } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 00b9afb03..4ef4d89c0 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -270,26 +270,7 @@ impl RealDom { 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 RealDom { } /// 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 { - 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 RealDom { } } +impl RealDom +where + Tree>: 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 { + S::update(nodes_updated, &mut self.tree, ctx) + } +} + impl Deref for RealDom { type Target = Tree>; diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 7e6c5266a..2e8eed085 100644 --- a/packages/native-core/src/state.rs +++ b/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 { /// 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::ElementBorrowed<'a>>, ctx: &Self::Ctx, ) -> bool @@ -138,7 +138,7 @@ pub trait ChildDepState { /// } /// } /// ``` -pub trait ParentDepState { +pub trait ParentDepState { /// 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<::ElementBorrowed<'a>>, ctx: &Self::Ctx, ) -> bool; @@ -193,7 +193,7 @@ pub trait ParentDepState { /// } /// } /// ``` -pub trait NodeDepState { +pub trait NodeDepState { /// 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: ::ElementBorrowed<'a>, ctx: &Self::Ctx, ) -> bool; @@ -217,7 +217,7 @@ pub trait State: Default + Clone + 'static { const MASKS: &'static [NodeMask]; #[doc(hidden)] - fn update>>( + fn update> + 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>>( + dirty: DirtyNodeStates, + tree: &mut T, + ctx: SendAnyMap, + ) -> FxDashSet { + 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, _: &Self::Ctx) -> bool + fn reduce<'a>(&mut self, _: NodeView<'a>, _: impl Iterator, _: &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 } } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 0547fc3a9..760afe87b 100644 --- a/packages/native-core/src/tree.rs +++ b/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: Sized { fn children_mut(&mut self, id: NodeId) -> Option>; - 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: 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; fn height(&self, id: NodeId) -> Option; - fn map &T2, FMut: Fn(&mut T) -> &mut T2>( - &mut self, - map: F, - map_mut: FMut, - ) -> TreeMap { - 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: TreeView { fn insert_after(&mut self, id: NodeId, new: NodeId); } -pub struct ChildNodeIterator<'a, T, Tr: TreeView> { - tree: &'a Tr, - children_ids: &'a [NodeId], +pub struct ChildNodeIterator<'a, T> { + nodes: &'a Slab>, + children_ids: Vec, index: usize, node_type: PhantomData, } -impl<'a, T: 'a, Tr: TreeView> 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.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 + 'a> { - tree: *mut Tr, - children_ids: &'a [NodeId], - index: usize, - node_type: PhantomData, +pub struct ChildNodeIteratorMut<'a, T> { + nodes: Vec<&'a mut Node>, } -unsafe impl<'a, T, Tr: TreeView + 'a> Sync for ChildNodeIteratorMut<'a, T, Tr> {} - -impl<'a, T, Tr: TreeView> ChildNodeIteratorMut<'a, T, Tr> { - fn tree_mut(&mut self) -> &'a mut Tr { - unsafe { &mut *self.tree } - } -} - -impl<'a, T: 'a, Tr: TreeView> 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 { - 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 TreeView for Tree { - type Iterator<'a> = ChildNodeIterator<'a, T, Tree> where T: 'a; - type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree> 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 TreeView for Tree { fn children(&self, id: NodeId) -> Option> { 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> { - 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 TreeView for Tree { 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 TreeLike for Tree { @@ -435,248 +419,6 @@ impl TreeLike for Tree { self.set_height(new, height); } } - -pub struct TreeMap<'a, T1, T2, Tr, F, FMut> -where - Tr: TreeView, - F: Fn(&T1) -> &T2, - FMut: Fn(&mut T1) -> &mut T2, -{ - tree: &'a mut Tr, - map: F, - map_mut: FMut, - in_node_type: PhantomData, - out_node_type: PhantomData, -} - -impl<'a, T1, T2, Tr, F, FMut> TreeMap<'a, T1, T2, Tr, F, FMut> -where - Tr: TreeView, - 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 for TreeMap<'a, T1, T2, Tr, F, FMut> -where - Tr: TreeView, - 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.children_ids(id).map(|children_ids| ChildNodeIterator { - tree: self, - children_ids, - index: 0, - node_type: PhantomData, - }) - } - - fn children_mut(&mut self, id: NodeId) -> Option> { - 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 { - self.tree.parent_id(id) - } - - fn height(&self, id: NodeId) -> Option { - 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> { - tree: Arc>, - node_locks: Arc>>, - node_type: PhantomData, -} - -impl<'a, T, Tr: TreeView> 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 { - let locks = self.node_locks.read(); - locks.get(id.0).map(|lock| lock.is_locked()) - } -} - -unsafe impl<'a, T, Tr: TreeView> Send for SharedView<'a, T, Tr> {} -unsafe impl<'a, T, Tr: TreeView> Sync for SharedView<'a, T, Tr> {} -impl<'a, T, Tr: TreeView> 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> 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(&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> TreeView 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.with_node(id, |t| t.children(id)) - } - - fn children_mut(&mut self, id: NodeId) -> Option> { - 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 { - self.with_node(id, |t| t.parent_id(id)) - } - - fn height(&self, id: NodeId) -> Option { - 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 { + data: Vec>, + free: VecDeque, +} + +impl Default for Slab { + fn default() -> Self { + Self::new() + } +} + +impl Slab { + 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, + ) -> Option> { + 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 { + 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() + } +} diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 75bcf9064..7a34b90ad 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/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>] = &[]; + const PASSES: &'static [crate::AnyPass>] = &[]; const MASKS: &'static [crate::NodeMask] = &[]; } diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 94aaf3296..154988f95 100644 --- a/packages/tui/Cargo.toml +++ b/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] diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 90d9e9da0..57f1c9e1f 100644 --- a/packages/tui/src/focus.rs +++ b/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::().ok())) + .or_else(|| a.value.as_text().and_then(|v| v.parse::().ok())) { match index.cmp(&0) { Ordering::Less => FocusLevel::Unfocusable, diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 33d2c1b81..427adc742 100644 --- a/packages/web/src/dom.rs +++ b/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), diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 877f72403..db2d1c7a9 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -189,10 +189,12 @@ pub async fn run_with_props(root: fn(Scope) -> 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();