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

fix custom attribute value support
This commit is contained in:
Jon Kelley authored and GitHub committed 2023-01-02 23:41:45 -05:00
commit 07e71956d4
35 files changed
+804 -709

No files matched your search

+9 -3
View File
@@ -153,11 +153,17 @@ impl VirtualDom {
}); });
// Now that all the references are gone, we can safely drop our own references in our listeners. // 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| { listeners.drain(..).for_each(|listener| {
let listener = unsafe { &*listener }; let listener = unsafe { &*listener };
if let AttributeValue::Listener(l) = &listener.value { match &listener.value {
_ = l.take(); AttributeValue::Listener(l) => {
_ = l.take();
}
AttributeValue::Any(a) => {
_ = a.take();
}
_ => (),
} }
}); });
} }
+12 -20
View File
@@ -1,5 +1,5 @@
use crate::any_props::AnyProps; 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::mutations::Mutation::*; use crate::mutations::Mutation::*;
use crate::nodes::VNode; 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 // Make sure we set the attribute's associated id
attribute.mounted_element.set(id); attribute.mounted_element.set(id);
@@ -293,9 +293,17 @@ impl<'b> VirtualDom {
let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) }; let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) };
match &attribute.value { 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 // 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 { self.mutations.push(SetAttribute {
name: unbounded_name, name: unbounded_name,
@@ -304,22 +312,6 @@ impl<'b> VirtualDom {
id, 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
View File
@@ -1,7 +1,7 @@
use crate::{ use crate::{
any_props::AnyProps, any_props::AnyProps,
arena::ElementId, arena::ElementId,
innerlude::{DirtyScope, VComponent, VPlaceholder, VText}, innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText},
mutations::Mutation, mutations::Mutation,
nodes::RenderReturn, nodes::RenderReturn,
nodes::{DynamicNode, VNode}, nodes::{DynamicNode, VNode},
@@ -150,22 +150,16 @@ impl<'b> VirtualDom {
}; };
} }
fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) { fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) {
// todo: add more types of attribute values let name = unsafe { std::mem::transmute(left_attr.name) };
match right_attr.value { let value: BorrowedAttributeValue<'b> = (&right_attr.value).into();
AttributeValue::Text(text) => { let value = unsafe { std::mem::transmute(value) };
let name = unsafe { std::mem::transmute(left_attr.name) }; self.mutations.push(Mutation::SetAttribute {
let value = unsafe { std::mem::transmute(text) }; id: left_attr.mounted_element.get(),
self.mutations.push(Mutation::SetAttribute { ns: right_attr.namespace,
id: left_attr.mounted_element.get(), name,
ns: right_attr.namespace, value,
name, });
value,
});
}
// todo: more types of attribute values
_ => todo!("other attribute types"),
}
} }
fn diff_vcomponent( fn diff_vcomponent(
+7 -7
View File
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
} }
pub use crate::innerlude::{ pub use crate::innerlude::{
fc_to_builder, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element, fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom, TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
}; };
/// The purpose of this module is to alleviate imports of many common types /// 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`]. /// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude { pub mod prelude {
pub use crate::innerlude::{ pub use crate::innerlude::{
fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties, fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
Throw, VNode, VirtualDom, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
}; };
} }
+4 -15
View File
@@ -1,6 +1,6 @@
use rustc_hash::FxHashSet; 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 /// 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), derive(serde::Serialize, serde::Deserialize),
serde(tag = "type") serde(tag = "type")
)] )]
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq)]
pub enum Mutation<'a> { pub enum Mutation<'a> {
/// Add these m children to the target element /// Add these m children to the target element
AppendChildren { AppendChildren {
@@ -193,8 +193,9 @@ pub enum Mutation<'a> {
SetAttribute { SetAttribute {
/// The name of the attribute to set. /// The name of the attribute to set.
name: &'a str, name: &'a str,
/// The value of the attribute. /// The value of the attribute.
value: &'a str, value: BorrowedAttributeValue<'a>,
/// The ID of the node to set the attribute of. /// The ID of the node to set the attribute of.
id: ElementId, id: ElementId,
@@ -204,18 +205,6 @@ pub enum Mutation<'a> {
ns: Option<&'a str>, 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. /// Set the textcontent of a node.
SetText { SetText {
/// The textcontent of the node /// The textcontent of the node
+133 -15
View File
@@ -5,8 +5,8 @@ use bumpalo::boxed::Box as BumpBox;
use bumpalo::Bump; use bumpalo::Bump;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::{Cell, RefCell, UnsafeCell}, cell::{self, Cell, RefCell, UnsafeCell},
fmt::Arguments, fmt::{Arguments, Debug},
future::Future, future::Future,
}; };
@@ -523,13 +523,108 @@ pub enum AttributeValue<'a> {
Listener(RefCell<Option<ListenerCb<'a>>>), Listener(RefCell<Option<ListenerCb<'a>>>),
/// An arbitrary value that implements PartialEq and is static /// 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 /// A "none" value, resulting in the removal of an attribute from the dom
None, 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> { impl<'a> std::fmt::Debug for AttributeValue<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0, (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Listener(_), Self::Listener(_)) => true, (Self::Listener(_), Self::Listener(_)) => true,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()), (Self::Any(l0), Self::Any(r0)) => {
_ => core::mem::discriminant(self) == core::mem::discriminant(other), let l0 = l0.borrow();
let r0 = r0.borrow();
l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap())
}
_ => false,
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub trait AnyValue { pub trait AnyValue: 'static {
fn any_cmp(&self, other: &dyn AnyValue) -> bool; 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 { fn any_cmp(&self, other: &dyn AnyValue) -> bool {
if self.type_id() != other.our_typeid() { if let Some(other) = other.as_any().downcast_ref() {
return false; self == other
} else {
false
} }
self == unsafe { &*(other as *const _ as *const T) }
} }
fn our_typeid(&self) -> TypeId { fn as_any(&self) -> &dyn Any {
self.type_id() self
} }
} }
@@ -742,26 +844,36 @@ pub trait IntoAttributeValue<'a> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'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 { impl<'a> IntoAttributeValue<'a> for &'a str {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Text(self) AttributeValue::Text(self)
} }
} }
impl<'a> IntoAttributeValue<'a> for f64 { impl<'a> IntoAttributeValue<'a> for f64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Float(self) AttributeValue::Float(self)
} }
} }
impl<'a> IntoAttributeValue<'a> for i64 { impl<'a> IntoAttributeValue<'a> for i64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Int(self) AttributeValue::Int(self)
} }
} }
impl<'a> IntoAttributeValue<'a> for bool { impl<'a> IntoAttributeValue<'a> for bool {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Bool(self) AttributeValue::Bool(self)
} }
} }
impl<'a> IntoAttributeValue<'a> for Arguments<'_> { impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> { fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
use bumpalo::core_alloc::fmt::Write; use bumpalo::core_alloc::fmt::Write;
@@ -770,3 +882,9 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
AttributeValue::Text(str_buf.into_bump_str()) 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
View File
@@ -44,7 +44,7 @@ impl VirtualDom {
hook_idx: Default::default(), hook_idx: Default::default(),
shared_contexts: Default::default(), shared_contexts: Default::default(),
borrowed_props: Default::default(), borrowed_props: Default::default(),
listeners: Default::default(), attributes_to_drop: Default::default(),
})) }))
} }
+21 -6
View File
@@ -7,7 +7,7 @@ use crate::{
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg}, innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
lazynodes::LazyNodes, lazynodes::LazyNodes,
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn}, 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 bumpalo::{boxed::Box as BumpBox, Bump};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
@@ -88,7 +88,7 @@ pub struct ScopeState {
pub(crate) spawned_tasks: FxHashSet<TaskId>, pub(crate) spawned_tasks: FxHashSet<TaskId>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>, 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) props: Option<Box<dyn AnyProps<'static>>>,
pub(crate) placeholder: Cell<Option<ElementId>>, pub(crate) placeholder: Cell<Option<ElementId>>,
@@ -374,11 +374,15 @@ impl<'src> ScopeState {
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self); 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 { for attr in element.dynamic_attrs {
if let AttributeValue::Listener(_) = attr.value { match attr.value {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; AttributeValue::Any(_) | AttributeValue::Listener(_) => {
listeners.push(unbounded); 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))) 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 /// 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 /// The error doesn't need to implement Error or any specific traits since the boundary
+1 -4
View File
@@ -641,10 +641,7 @@ impl VirtualDom {
/// Swap the current mutations with a new /// Swap the current mutations with a new
fn finalize(&mut self) -> Mutations { fn finalize(&mut self) -> Mutations {
// todo: make this a routine std::mem::take(&mut self.mutations)
let mut out = Mutations::default();
std::mem::swap(&mut self.mutations, &mut out);
out
} }
} }
+28 -4
View File
@@ -2,8 +2,10 @@
//! //!
//! This tests to ensure we clean it up //! This tests to ensure we clean it up
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation::*}; use dioxus::core::{ElementId, Mutation::*};
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_core::{AttributeValue, BorrowedAttributeValue};
#[test] #[test]
fn attrs_cycle() { fn attrs_cycle() {
@@ -22,6 +24,8 @@ fn attrs_cycle() {
} }
}); });
let bump = Bump::new();
assert_eq!( assert_eq!(
dom.rebuild().santize().edits, dom.rebuild().santize().edits,
[ [
@@ -36,8 +40,18 @@ fn attrs_cycle() {
[ [
LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
AssignId { path: &[0,], id: ElementId(3,) }, AssignId { path: &[0,], id: ElementId(3,) },
SetAttribute { name: "class", value: "1", id: ElementId(3,), ns: None }, SetAttribute {
SetAttribute { name: "id", value: "1", id: ElementId(3,), ns: None }, 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 }, ReplaceWith { id: ElementId(1,), m: 1 },
] ]
); );
@@ -57,8 +71,18 @@ fn attrs_cycle() {
[ [
LoadTemplate { name: "template", index: 0, id: ElementId(2) }, LoadTemplate { name: "template", index: 0, id: ElementId(2) },
AssignId { path: &[0], id: ElementId(3) }, AssignId { path: &[0], id: ElementId(3) },
SetAttribute { name: "class", value: "3", id: ElementId(3), ns: None }, SetAttribute {
SetAttribute { name: "id", value: "3", id: ElementId(3), ns: None }, 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 } ReplaceWith { id: ElementId(1), m: 1 }
] ]
); );
+10 -2
View File
@@ -1,15 +1,23 @@
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation::*}; use dioxus::core::{ElementId, Mutation::*};
use dioxus::prelude::*; use dioxus::prelude::*;
#[test] #[test]
fn bool_test() { fn bool_test() {
let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false }))); let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false })));
let bump = Bump::new();
assert_eq!( assert_eq!(
app.rebuild().santize().edits, app.rebuild().santize().edits,
[ [
LoadTemplate { name: "template", index: 0, id: ElementId(1) }, 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) }, AppendChildren { m: 1, id: ElementId(0) },
] ]
) );
} }
+1 -1
View File
@@ -18,7 +18,7 @@ fn test_borrowed_state() {
ReplacePlaceholder { path: &[0,], m: 1 }, ReplacePlaceholder { path: &[0,], m: 1 },
AppendChildren { m: 1, id: ElementId(0) }, AppendChildren { m: 1, id: ElementId(0) },
] ]
) );
} }
fn Parent(cx: Scope) -> Element { fn Parent(cx: Scope) -> Element {
+3 -1
View File
@@ -20,7 +20,9 @@ fn app(cx: Scope) -> Element {
fn bubbles_error() { fn bubbles_error() {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
let _edits = dom.rebuild().santize(); {
let _edits = dom.rebuild().santize();
}
dom.mark_dirty(ScopeId(0)); dom.mark_dirty(ScopeId(0));
+10 -8
View File
@@ -12,14 +12,16 @@ fn cycling_elements() {
}) })
}); });
let edits = dom.rebuild().santize(); {
assert_eq!( let edits = dom.rebuild().santize();
edits.edits, assert_eq!(
[ edits.edits,
LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, [
AppendChildren { m: 1, id: ElementId(0) }, LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
] AppendChildren { m: 1, id: ElementId(0) },
); ]
);
}
dom.mark_dirty(ScopeId(0)); dom.mark_dirty(ScopeId(0));
assert_eq!( assert_eq!(
+15 -13
View File
@@ -59,19 +59,21 @@ fn component_swap() {
} }
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
let edits = dom.rebuild().santize(); {
assert_eq!( let edits = dom.rebuild().santize();
edits.edits, 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(1) },
LoadTemplate { name: "template", index: 0, id: ElementId(3) }, LoadTemplate { name: "template", index: 0, id: ElementId(2) },
LoadTemplate { name: "template", index: 0, id: ElementId(4) }, LoadTemplate { name: "template", index: 0, id: ElementId(3) },
ReplacePlaceholder { path: &[1], m: 3 }, LoadTemplate { name: "template", index: 0, id: ElementId(4) },
LoadTemplate { name: "template", index: 0, id: ElementId(5) }, ReplacePlaceholder { path: &[1], m: 3 },
AppendChildren { m: 2, id: ElementId(0) } LoadTemplate { name: "template", index: 0, id: ElementId(5) },
] AppendChildren { m: 2, id: ElementId(0) }
); ]
);
}
dom.mark_dirty(ScopeId(0)); dom.mark_dirty(ScopeId(0));
assert_eq!( assert_eq!(
+20 -18
View File
@@ -20,22 +20,24 @@ fn keyed_diffing_out_of_order() {
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
}); });
assert_eq!( {
dom.rebuild().santize().edits, 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(1,) },
LoadTemplate { name: "template", index: 0, id: ElementId(3,) }, LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
LoadTemplate { name: "template", index: 0, id: ElementId(4,) }, LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) }, LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
LoadTemplate { name: "template", index: 0, id: ElementId(6,) }, LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
LoadTemplate { name: "template", index: 0, id: ElementId(7,) }, LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
LoadTemplate { name: "template", index: 0, id: ElementId(8,) }, LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
LoadTemplate { name: "template", index: 0, id: ElementId(9,) }, LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
LoadTemplate { name: "template", index: 0, id: ElementId(10,) }, LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
AppendChildren { m: 10, id: ElementId(0) }, LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
] AppendChildren { m: 10, id: ElementId(0) },
); ]
);
}
dom.mark_dirty(ScopeId(0)); dom.mark_dirty(ScopeId(0));
assert_eq!( assert_eq!(
@@ -44,7 +46,7 @@ fn keyed_diffing_out_of_order() {
PushRoot { id: ElementId(7,) }, PushRoot { id: ElementId(7,) },
InsertBefore { id: ElementId(5,), m: 1 }, InsertBefore { id: ElementId(5,), m: 1 },
] ]
) );
} }
/// Should result in moves only /// Should result in moves only
@@ -70,7 +72,7 @@ fn keyed_diffing_out_of_order_adds() {
PushRoot { id: ElementId(4,) }, PushRoot { id: ElementId(4,) },
InsertBefore { id: ElementId(1,), m: 2 }, InsertBefore { id: ElementId(1,), m: 2 },
] ]
) );
} }
/// Should result in moves only /// Should result in moves only
+68 -58
View File
@@ -315,66 +315,76 @@ fn remove_many() {
}) })
}); });
let edits = dom.rebuild().santize(); {
assert!(edits.templates.is_empty()); let edits = dom.rebuild().santize();
assert_eq!( assert!(edits.templates.is_empty());
edits.edits, assert_eq!(
[ edits.edits,
CreatePlaceholder { id: ElementId(1,) }, [
AppendChildren { id: ElementId(0), m: 1 }, CreatePlaceholder { id: ElementId(1,) },
] AppendChildren { id: ElementId(0), m: 1 },
); ]
);
}
dom.mark_dirty(ScopeId(0)); {
let edits = dom.render_immediate().santize(); dom.mark_dirty(ScopeId(0));
assert_eq!( let edits = dom.render_immediate().santize();
edits.edits, assert_eq!(
[ edits.edits,
LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, [
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) }, LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(1,), m: 1 }, 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(); dom.mark_dirty(ScopeId(0));
assert_eq!( let edits = dom.render_immediate().santize();
edits.edits, 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(1,) },
LoadTemplate { name: "template", index: 0, id: ElementId(5,) }, HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) }, LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
LoadTemplate { name: "template", index: 0, id: ElementId(7,) }, HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) }, LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
LoadTemplate { name: "template", index: 0, id: ElementId(9,) }, HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) }, LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
InsertAfter { id: ElementId(2,), m: 4 }, 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(); dom.mark_dirty(ScopeId(0));
assert_eq!( let edits = dom.render_immediate().santize();
edits.edits, assert_eq!(
[ edits.edits,
CreatePlaceholder { id: ElementId(11,) }, [
Remove { id: ElementId(9,) }, CreatePlaceholder { id: ElementId(11,) },
Remove { id: ElementId(7,) }, Remove { id: ElementId(9,) },
Remove { id: ElementId(5,) }, Remove { id: ElementId(7,) },
Remove { id: ElementId(1,) }, Remove { id: ElementId(5,) },
ReplaceWith { id: ElementId(2,), m: 1 }, Remove { id: ElementId(1,) },
] ReplaceWith { id: ElementId(2,), m: 1 },
); ]
);
}
dom.mark_dirty(ScopeId(0)); {
let edits = dom.render_immediate().santize(); dom.mark_dirty(ScopeId(0));
assert_eq!( let edits = dom.render_immediate().santize();
edits.edits, assert_eq!(
[ edits.edits,
LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, [
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) }, LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(11,), m: 1 }, HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
] ReplaceWith { id: ElementId(11,), m: 1 },
) ]
)
}
} }
+12 -6
View File
@@ -1,3 +1,4 @@
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation}; use dioxus::core::{ElementId, Mutation};
use dioxus::prelude::*; use dioxus::prelude::*;
@@ -26,17 +27,22 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
#[test] #[test]
fn dual_stream() { fn dual_stream() {
let mut dom = VirtualDom::new(basic_syntax_is_a_template); let mut dom = VirtualDom::new(basic_syntax_is_a_template);
let bump = Bump::new();
let edits = dom.rebuild().santize(); let edits = dom.rebuild().santize();
use Mutation::*; use Mutation::*;
assert_eq!( assert_eq!(edits.edits, {
edits.edits,
[ [
LoadTemplate { name: "template", index: 0, id: ElementId(1) }, 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) }, NewEventListener { name: "click", id: ElementId(1) },
HydrateText { path: &[0, 0], value: "123", id: ElementId(2) }, HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
AppendChildren { id: ElementId(0), m: 1 } AppendChildren { id: ElementId(0), m: 1 },
], ]
); });
} }
+26 -24
View File
@@ -9,32 +9,34 @@ use std::time::Duration;
async fn it_works() { async fn it_works() {
let mut dom = VirtualDom::new(app); 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 // We should at least get the top-level template in before pausing for the children
// note: we dont test template edits anymore // note: we dont test template edits anymore
// assert_eq!( // assert_eq!(
// mutations.templates, // mutations.templates,
// [ // [
// CreateElement { name: "div" }, // CreateElement { name: "div" },
// CreateStaticText { value: "Waiting for child..." }, // CreateStaticText { value: "Waiting for child..." },
// CreateStaticPlaceholder, // CreateStaticPlaceholder,
// AppendChildren { m: 2 }, // AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 } // SaveTemplate { name: "template", m: 1 }
// ] // ]
// ); // );
// And we should load it in and assign the placeholder properly // And we should load it in and assign the placeholder properly
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
LoadTemplate { name: "template", index: 0, id: ElementId(1) }, LoadTemplate { name: "template", index: 0, id: ElementId(1) },
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly? // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
// can we even? // can we even?
AssignId { path: &[1], id: ElementId(3) }, AssignId { path: &[1], id: ElementId(3) },
AppendChildren { m: 1, id: ElementId(0) }, AppendChildren { m: 1, id: ElementId(0) },
] ]
); );
}
// wait just a moment, not enough time for the boundary to resolve // wait just a moment, not enough time for the boundary to resolve
+1 -2
View File
@@ -18,10 +18,9 @@ proc-macro = true
syn = { version = "1.0.11", features = ["extra-traits"] } syn = { version = "1.0.11", features = ["extra-traits"] }
quote = "1.0" quote = "1.0"
dioxus-native-core = { path = "../native-core", version = "^0.2.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
[dev-dependencies]
dioxus = { path = "../dioxus" } dioxus = { path = "../dioxus" }
[dev-dependencies]
smallvec = "1.6" smallvec = "1.6"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
anymap = "0.12.1" anymap = "0.12.1"
+2 -1
View File
@@ -393,7 +393,8 @@ impl<'a> StateMember<'a> {
let ty = &self.mem.ty; let ty = &self.mem.ty;
let unit_type = &self.mem.unit_type; 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 dep_idents = self.dep_mems.iter().map(|m| &m.ident);
let impl_specific = match self.dep_kind { let impl_specific = match self.dep_kind {
DependencyKind::Node => { DependencyKind::Node => {
@@ -43,7 +43,6 @@ impl ParentDepState for ParentDepCallCounter {
const NODE_MASK: NodeMask = NodeMask::ALL; const NODE_MASK: NodeMask = NodeMask::ALL;
fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool { fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
self.0 += 1; self.0 += 1;
println!("ParentDepCallCounter::reduce on {:?}\n{}", _node, self.0);
true true
} }
} }
+1 -1
View File
@@ -18,7 +18,7 @@ dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
taffy = "0.2.1" taffy = "0.2.1"
smallvec = "1.6" smallvec = "1.6"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
anymap = "0.12.1" anymap = "1.0.0-beta.2"
slab = "0.4" slab = "0.4"
parking_lot = "0.12.1" parking_lot = "0.12.1"
crossbeam-deque = "0.8.2" crossbeam-deque = "0.8.2"
+2 -1
View File
@@ -1,3 +1,4 @@
use std::any::Any;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
pub use node_ref::NodeMask; pub use node_ref::NodeMask;
@@ -20,7 +21,7 @@ pub mod utils;
pub type RealNodeId = NodeId; pub type RealNodeId = NodeId;
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>; pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
pub type FxDashSet<K> = dashmap::DashSet<K, 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 /// Used in derived state macros
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
+61 -18
View File
@@ -1,43 +1,44 @@
use crate::{state::State, tree::NodeId}; use crate::{state::State, tree::NodeId};
use dioxus_core::ElementId; use dioxus_core::{AnyValue, BorrowedAttributeValue, ElementId};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use std::fmt::Debug;
/// The node is stored client side and stores only basic data about the node. /// The node is stored client side and stores only basic data about the node.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Node<S: State> { pub struct Node<S: State, V: FromAnyValue = ()> {
/// The transformed state of the node. /// The transformed state of the node.
pub state: S, pub state: S,
/// The raw data for the node /// The raw data for the node
pub node_data: NodeData, pub node_data: NodeData<V>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NodeData { pub struct NodeData<V: FromAnyValue = ()> {
/// The id of the node /// The id of the node
pub node_id: NodeId, pub node_id: NodeId,
/// The id of the node in the vdom. /// The id of the node in the vdom.
pub element_id: Option<ElementId>, pub element_id: Option<ElementId>,
/// Additional inforation specific to the node type /// 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. /// A type of node with data specific to the node type. The types are a subset of the [VNode] types.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum NodeType { pub enum NodeType<V: FromAnyValue = ()> {
Text { Text {
text: String, text: String,
}, },
Element { Element {
tag: String, tag: String,
namespace: Option<String>, namespace: Option<String>,
attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue>, attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
listeners: FxHashSet<String>, listeners: FxHashSet<String>,
}, },
Placeholder, Placeholder,
} }
impl<S: State> Node<S> { impl<S: State, V: FromAnyValue> Node<S, V> {
pub(crate) fn new(node_type: NodeType) -> Self { pub(crate) fn new(node_type: NodeType<V>) -> Self {
Node { Node {
state: S::default(), state: S::default(),
node_data: NodeData { node_data: NodeData {
@@ -64,24 +65,59 @@ pub struct OwnedAttributeDiscription {
/// An attribute on a DOM node, such as `id="my-thing"` or /// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`. /// `href="https://example.com"`.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct OwnedAttributeView<'a> { pub struct OwnedAttributeView<'a, V: FromAnyValue = ()> {
/// The discription of the attribute. /// The discription of the attribute.
pub attribute: &'a OwnedAttributeDiscription, pub attribute: &'a OwnedAttributeDiscription,
/// The value of the attribute. /// The value of the attribute.
pub value: &'a OwnedAttributeValue, pub value: &'a OwnedAttributeValue<V>,
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub enum OwnedAttributeValue { pub enum OwnedAttributeValue<V: FromAnyValue = ()> {
Text(String), Text(String),
Float(f32), Float(f64),
Int(i32), Int(i64),
Bool(bool), Bool(bool),
Custom(V),
None, 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> { pub fn as_text(&self) -> Option<&str> {
match self { match self {
OwnedAttributeValue::Text(text) => Some(text), 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 { match self {
OwnedAttributeValue::Float(float) => Some(*float), OwnedAttributeValue::Float(float) => Some(*float),
_ => None, _ => None,
} }
} }
pub fn as_int(&self) -> Option<i32> { pub fn as_int(&self) -> Option<i64> {
match self { match self {
OwnedAttributeValue::Int(int) => Some(*int), OwnedAttributeValue::Int(int) => Some(*int),
_ => None, _ => None,
@@ -116,4 +152,11 @@ impl OwnedAttributeValue {
_ => None, _ => None,
} }
} }
pub fn as_custom(&self) -> Option<&V> {
match self {
OwnedAttributeValue::Custom(custom) => Some(custom),
_ => None,
}
}
} }
+8 -6
View File
@@ -1,21 +1,21 @@
use dioxus_core::ElementId; use dioxus_core::ElementId;
use crate::{ use crate::{
node::{NodeData, NodeType, OwnedAttributeView}, node::{FromAnyValue, NodeData, NodeType, OwnedAttributeView},
state::union_ordered_iter, state::union_ordered_iter,
RealNodeId, RealNodeId,
}; };
/// A view into a [VNode] with limited access. /// A view into a [VNode] with limited access.
#[derive(Debug)] #[derive(Debug)]
pub struct NodeView<'a> { pub struct NodeView<'a, V: FromAnyValue = ()> {
inner: &'a NodeData, inner: &'a NodeData<V>,
mask: NodeMask, mask: NodeMask,
} }
impl<'a> NodeView<'a> { impl<'a, V: FromAnyValue> NodeView<'a, V> {
/// Create a new NodeView from a VNode, and mask. /// 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 { Self {
inner: node, inner: node,
mask: view, mask: view,
@@ -55,7 +55,9 @@ impl<'a> NodeView<'a> {
} }
/// Get any attributes that are enabled in the mask /// 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 { match &self.inner.node_type {
NodeType::Element { attributes, .. } => Some( NodeType::Element { attributes, .. } => Some(
attributes attributes
+82 -38
View File
@@ -310,11 +310,65 @@ impl<T> AnyPass<T> {
} }
} }
struct RawPointer<T>(*mut T); pub fn resolve_passes<T, Tr: TreeView<T> + Sync + Send>(
unsafe impl<T> Send for RawPointer<T> {} tree: &mut Tr,
unsafe impl<T> Sync for RawPointer<T> {} 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, tree: &mut Tr,
dirty_nodes: DirtyNodeStates, dirty_nodes: DirtyNodeStates,
mut passes: Vec<&AnyPass<T>>, mut passes: Vec<&AnyPass<T>>,
@@ -327,41 +381,31 @@ pub fn resolve_passes<T, Tr: TreeView<T>>(
let ctx = Arc::new(ctx); let ctx = Arc::new(ctx);
while !passes.is_empty() { while !passes.is_empty() {
let mut currently_borrowed = MemberMask::default(); let mut currently_borrowed = MemberMask::default();
std::thread::scope(|s| { let mut i = 0;
let mut i = 0; while i < passes.len() {
while i < passes.len() { let pass = &passes[i];
let pass = &passes[i]; let pass_id = pass.pass_id();
let pass_id = pass.pass_id(); let pass_mask = pass.mask();
let pass_mask = pass.mask(); if pass
if pass .dependancies()
.dependancies() .iter()
.iter() .all(|d| resolved_passes.contains(d) || *d == pass_id)
.all(|d| resolved_passes.contains(d) || *d == pass_id) && !pass_mask.overlaps(currently_borrowed)
&& !pass_mask.overlaps(currently_borrowed) {
{ let pass = passes.remove(i);
let pass = passes.remove(i); resolving.push(pass_id);
resolving.push(pass_id); currently_borrowed |= pass_mask;
currently_borrowed |= pass_mask; let dirty_states = dirty_states.clone();
let tree_mut = tree as *mut _; let nodes_updated = nodes_updated.clone();
let raw_ptr = RawPointer(tree_mut); let ctx = ctx.clone();
let dirty_states = dirty_states.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 nodes_updated = nodes_updated.clone(); let mut dirty = DirtyNodes::default();
let ctx = ctx.clone(); dirty_states.all_dirty(pass_id, &mut dirty, tree);
s.spawn(move || unsafe { pass.resolve(tree, dirty, &dirty_states, &nodes_updated, &ctx);
// let tree_mut: &mut Tr = &mut *raw_ptr.0; } else {
let raw = raw_ptr; i += 1;
// 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;
}
} }
// all passes are resolved at the end of the scope }
});
resolved_passes.extend(resolving.iter().copied()); resolved_passes.extend(resolving.iter().copied());
resolving.clear() resolving.clear()
} }
+18 -22
View File
@@ -270,26 +270,7 @@ impl<S: State> RealDom<S> {
namespace: ns.map(|s| s.to_string()), namespace: ns.map(|s| s.to_string()),
volatile: false, volatile: false,
}, },
crate::node::OwnedAttributeValue::Text(value.to_string()), OwnedAttributeValue::from(value),
);
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),
); );
mark_dirty( mark_dirty(
node_id, 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. /// 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, &mut self,
nodes_updated: DirtyNodeStates, nodes_updated: DirtyNodeStates,
ctx: SendAnyMap, ctx: SendAnyMap,
) -> FxDashSet<RealNodeId> { ) -> 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. /// 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> { impl<S: State> Deref for RealDom<S> {
type Target = Tree<Node<S>>; type Target = Tree<Node<S>>;
+22 -12
View File
@@ -1,8 +1,8 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use crate::node::Node; use crate::node::{FromAnyValue, Node};
use crate::node_ref::{NodeMask, NodeView}; 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::tree::TreeView;
use crate::{FxDashSet, RealNodeId, SendAnyMap}; 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. /// The context is passed to the [ChildDepState::reduce] when it is resolved.
type Ctx; 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. /// 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. /// 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>( fn reduce<'a>(
&mut self, &mut self,
node: NodeView, node: NodeView<'a, V>,
children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>, children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
ctx: &Self::Ctx, ctx: &Self::Ctx,
) -> bool ) -> 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. /// The context is passed to the [ParentDepState::reduce] when it is resolved.
type Ctx; 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. /// 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. /// 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>( fn reduce<'a>(
&mut self, &mut self,
node: NodeView, node: NodeView<'a, V>,
parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>, parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
ctx: &Self::Ctx, ctx: &Self::Ctx,
) -> bool; ) -> 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]. /// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState], [ParentDepState] or [NodeDepState].
type DepState: ElementBorrowable; type DepState: ElementBorrowable;
/// The state passed to [NodeDepState::reduce] when it is resolved. /// 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. /// 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>( fn reduce<'a>(
&mut self, &mut self,
node: NodeView, node: NodeView<'a, V>,
node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>, node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>,
ctx: &Self::Ctx, ctx: &Self::Ctx,
) -> bool; ) -> bool;
@@ -217,7 +217,7 @@ pub trait State: Default + Clone + 'static {
const MASKS: &'static [NodeMask]; const MASKS: &'static [NodeMask];
#[doc(hidden)] #[doc(hidden)]
fn update<T: TreeView<Node<Self>>>( fn update<T: TreeView<Node<Self>> + Sync + Send>(
dirty: DirtyNodeStates, dirty: DirtyNodeStates,
tree: &mut T, tree: &mut T,
ctx: SendAnyMap, ctx: SendAnyMap,
@@ -225,12 +225,22 @@ pub trait State: Default + Clone + 'static {
let passes = Self::PASSES.iter().collect(); let passes = Self::PASSES.iter().collect();
resolve_passes(tree, dirty, passes, ctx) 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 () { impl ChildDepState for () {
type Ctx = (); type Ctx = ();
type DepState = (); 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 where
Self::DepState: 'a, Self::DepState: 'a,
{ {
@@ -241,7 +251,7 @@ impl ChildDepState for () {
impl ParentDepState for () { impl ParentDepState for () {
type Ctx = (); type Ctx = ();
type DepState = (); type DepState = ();
fn reduce<'a>(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool { fn reduce<'a>(&mut self, _: NodeView<'a>, _: Option<()>, _: &Self::Ctx) -> bool {
false false
} }
} }
@@ -249,7 +259,7 @@ impl ParentDepState for () {
impl NodeDepState for () { impl NodeDepState for () {
type DepState = (); type DepState = ();
type Ctx = (); type Ctx = ();
fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool { fn reduce<'a>(&mut self, _: NodeView<'a>, _sibling: (), _: &Self::Ctx) -> bool {
false false
} }
} }
+181 -371
View File
@@ -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::collections::VecDeque;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc;
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)] #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
pub struct NodeId(pub usize); 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 children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>>;
fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, 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 children_ids(&self, id: NodeId) -> Option<&[NodeId]>; 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 parent_mut(&mut self, id: NodeId) -> Option<&mut T>;
fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, 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 parent_id(&self, id: NodeId) -> Option<NodeId>; fn parent_id(&self, id: NodeId) -> Option<NodeId>;
fn height(&self, id: NodeId) -> Option<u16>; 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 size(&self) -> usize;
fn traverse_depth_first(&self, mut f: impl FnMut(&T)) { 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); fn insert_after(&mut self, id: NodeId, new: NodeId);
} }
pub struct ChildNodeIterator<'a, T, Tr: TreeView<T>> { pub struct ChildNodeIterator<'a, T> {
tree: &'a Tr, nodes: &'a Slab<Node<T>>,
children_ids: &'a [NodeId], children_ids: Vec<NodeId>,
index: usize, index: usize,
node_type: PhantomData<T>, 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; type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.children_ids.get(self.index).map(|id| { self.children_ids.get(self.index).map(|id| {
self.index += 1; self.index += 1;
self.tree.get_unchecked(*id) &self.nodes.get(id.0).unwrap().value
}) })
} }
} }
pub struct ChildNodeIteratorMut<'a, T, Tr: TreeView<T> + 'a> { pub struct ChildNodeIteratorMut<'a, T> {
tree: *mut Tr, nodes: Vec<&'a mut Node<T>>,
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: 'a> Iterator for ChildNodeIteratorMut<'a, T> {
impl<'a, T, Tr: TreeView<T>> ChildNodeIteratorMut<'a, T, Tr> {
fn tree_mut(&mut self) -> &'a mut Tr {
unsafe { &mut *self.tree }
}
}
impl<'a, T: 'a, Tr: TreeView<T>> Iterator for ChildNodeIteratorMut<'a, T, Tr> {
type Item = &'a mut T; type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let owned = self.children_ids.get(self.index).copied(); self.nodes.pop().map(|node| &mut node.value)
match owned {
Some(id) => {
self.index += 1;
Some(self.tree_mut().get_mut_unchecked(id))
}
None => None,
}
} }
} }
impl<T> TreeView<T> for Tree<T> { impl<T> TreeView<T> for Tree<T> {
type Iterator<'a> = ChildNodeIterator<'a, T, Tree<T>> where T: 'a; type Iterator<'a> = ChildNodeIterator<'a, T> where T: 'a;
type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree<T>> where T: 'a; type IteratorMut<'a> = ChildNodeIteratorMut<'a, T> where T: 'a;
fn root(&self) -> NodeId { fn root(&self) -> NodeId {
self.root self.root
@@ -275,24 +224,26 @@ impl<T> TreeView<T> for Tree<T> {
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> { fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
self.children_ids(id).map(|children_ids| ChildNodeIterator { self.children_ids(id).map(|children_ids| ChildNodeIterator {
tree: self, nodes: &self.nodes,
children_ids, children_ids: children_ids.to_vec(),
index: 0, index: 0,
node_type: PhantomData, node_type: PhantomData,
}) })
} }
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> { fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
let raw_ptr = self as *mut Self; // Safety: No node has itself as a parent.
unsafe { if let Some(children_ids) = self.children_ids(id) {
// Safety: No node will appear as a child twice let children_ids = children_ids.to_vec();
self.children_ids(id) Some(ChildNodeIteratorMut {
.map(|children_ids| ChildNodeIteratorMut { nodes: unsafe {
tree: &mut *raw_ptr, self.nodes
children_ids, .get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id.0))
index: 0, .unwrap()
node_type: PhantomData, },
}) })
} else {
None
} }
} }
@@ -340,6 +291,39 @@ impl<T> TreeView<T> for Tree<T> {
fn size(&self) -> usize { fn size(&self) -> usize {
self.nodes.len() 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> { impl<T> TreeLike<T> for Tree<T> {
@@ -435,248 +419,6 @@ impl<T> TreeLike<T> for Tree<T> {
self.set_height(new, height); 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] #[test]
fn creation() { fn creation() {
let mut tree = Tree::new(1); let mut tree = Tree::new(1);
@@ -785,59 +527,6 @@ fn deletion() {
assert_eq!(tree.children_ids(parent).unwrap(), &[]); 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] #[test]
fn traverse_depth_first() { fn traverse_depth_first() {
let mut tree = Tree::new(0); let mut tree = Tree::new(0);
@@ -857,3 +546,124 @@ fn traverse_depth_first() {
node_count += 1; 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()
}
}
@@ -229,7 +229,7 @@ impl PersistantElementIter {
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
struct Empty {} struct Empty {}
impl State for 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] = &[]; const MASKS: &'static [crate::NodeMask] = &[];
} }
+1 -1
View File
@@ -27,7 +27,7 @@ futures = "0.3.19"
taffy = "0.2.1" taffy = "0.2.1"
smallvec = "1.6" smallvec = "1.6"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
anymap = "0.12.1" anymap = "1.0.0-beta.2"
futures-channel = "0.3.25" futures-channel = "0.3.25"
[dev-dependencies] [dev-dependencies]
+1 -1
View File
@@ -79,7 +79,7 @@ impl NodeDepState for Focus {
if let Some(index) = a if let Some(index) = a
.value .value
.as_int() .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) { match index.cmp(&0) {
Ordering::Less => FocusLevel::Unfocusable, Ordering::Less => FocusLevel::Unfocusable,
+26 -7
View File
@@ -7,7 +7,9 @@
//! - tests to ensure dyn_into works for various event types. //! - tests to ensure dyn_into works for various event types.
//! - Partial delegation?> //! - 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_html::{event_bubbles, CompositionData, FormData};
use dioxus_interpreter_js::{save_template, Channel}; use dioxus_interpreter_js::{save_template, Channel};
use futures_channel::mpsc; use futures_channel::mpsc;
@@ -175,16 +177,33 @@ impl WebsysDom {
value, value,
id, id,
ns, ns,
} => i.set_attribute(id.0 as u32, name, value, ns.unwrap_or_default()), } => match value {
SetBoolAttribute { name, value, id } => { BorrowedAttributeValue::Text(txt) => {
i.set_attribute(id.0 as u32, name, if *value { "true" } else { "false" }, "") 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), SetText { value, id } => i.set_text(id.0 as u32, value),
NewEventListener { name, id, .. } => { 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 } => { 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), Remove { id } => i.remove(id.0 as u32),
PushRoot { id } => i.push_root(id.0 as u32), PushRoot { id } => i.push_root(id.0 as u32),
+5 -3
View File
@@ -189,10 +189,12 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
// if should_hydrate { // if should_hydrate {
// } else { // } else {
let edits = dom.rebuild(); {
let edits = dom.rebuild();
websys_dom.load_templates(&edits.templates); websys_dom.load_templates(&edits.templates);
websys_dom.apply_edits(edits.edits); websys_dom.apply_edits(edits.edits);
}
// the mutations come back with nothing - we need to actually mount them // the mutations come back with nothing - we need to actually mount them
websys_dom.mount(); websys_dom.mount();