fix custom attribute value support

This commit is contained in:
Evan Almloff 2022-12-09 16:18:37 -06:00
parent 937eb1f0f0
commit 8a1c96a68c
28 changed files with 369 additions and 276 deletions

View file

@ -144,8 +144,8 @@ impl Buffer {
let mut total = 0; let mut total = 0;
for attr in attributes { for attr in attributes {
if self.current_span_is_primary(attr.attr.flart()) { if self.current_span_is_primary(attr.attr.start()) {
'line: for line in self.src[..attr.attr.flart().start().line - 1].iter().rev() { 'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
match (line.trim().starts_with("//"), line.is_empty()) { match (line.trim().starts_with("//"), line.is_empty()) {
(true, _) => return 100000, (true, _) => return 100000,
(_, true) => continue 'line, (_, true) => continue 'line,

View file

@ -157,7 +157,7 @@ impl Buffer {
while let Some(attr) = attr_iter.next() { while let Some(attr) = attr_iter.next() {
self.indent += 1; self.indent += 1;
if !sameline { if !sameline {
self.write_comments(attr.attr.flart())?; self.write_comments(attr.attr.start())?;
} }
self.indent -= 1; self.indent -= 1;

View file

@ -105,27 +105,10 @@ impl<'b> VirtualDom {
attribute.mounted_element.set(id); attribute.mounted_element.set(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_name = 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) => {
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_value = unsafe { std::mem::transmute(*value) };
self.mutations.push(SetAttribute {
name: unbounded_name,
value: unbounded_value,
ns: attribute.namespace,
id,
})
}
AttributeValue::Bool(value) => {
self.mutations.push(SetBoolAttribute {
name: unbounded_name,
value: *value,
id,
})
}
AttributeValue::Listener(_) => { AttributeValue::Listener(_) => {
self.mutations.push(NewEventListener { self.mutations.push(NewEventListener {
// all listeners start with "on" // all listeners start with "on"
@ -134,10 +117,18 @@ impl<'b> VirtualDom {
id, id,
}) })
} }
AttributeValue::Float(_) => todo!(), _ => {
AttributeValue::Int(_) => todo!(), // Safety: we promise not to re-alias this text later on after committing it to the mutation
AttributeValue::Any(_) => todo!(), let unbounded_value =
AttributeValue::None => todo!(), unsafe { std::mem::transmute(attribute.value.clone()) };
self.mutations.push(SetAttribute {
name: unbounded_name,
value: unbounded_value,
ns: attribute.namespace,
id,
})
}
} }
// Only push the dynamic attributes forward if they match the current path (same element) // Only push the dynamic attributes forward if they match the current path (same element)

View file

@ -77,20 +77,14 @@ impl<'b> VirtualDom {
if left_attr.value != right_attr.value || left_attr.volatile { if left_attr.value != right_attr.value || left_attr.volatile {
// todo: add more types of attribute values // todo: add more types of attribute values
match right_attr.value { let name = unsafe { std::mem::transmute(left_attr.name) };
AttributeValue::Text(text) => { let value = unsafe { std::mem::transmute(right_attr.value.clone()) };
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"),
}
} }
} }

View file

@ -70,10 +70,10 @@ pub(crate) mod innerlude {
} }
pub use crate::innerlude::{ pub use crate::innerlude::{
fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event, fc_to_builder, AnyValueBox, Attribute, AttributeValue, Component, DynamicNode, Element,
Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation, Mutations,
ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId,
TemplateNode, VComponent, VNode, VText, VirtualDom, 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, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope, fc_to_builder, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
VirtualDom, TemplateNode, VNode, VirtualDom,
}; };
} }

View file

@ -1,6 +1,6 @@
use fxhash::FxHashSet; use fxhash::FxHashSet;
use crate::{arena::ElementId, ScopeId, Template}; use crate::{arena::ElementId, AttributeValue, 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
/// ///
@ -48,7 +48,7 @@ impl<'a> Mutations<'a> {
/// Push a new mutation into the dom_edits list /// Push a new mutation into the dom_edits list
pub(crate) fn push(&mut self, mutation: Mutation<'static>) { pub(crate) fn push(&mut self, mutation: Mutation<'static>) {
self.edits.push(mutation) unsafe { self.edits.push(std::mem::transmute(mutation)) }
} }
} }
@ -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: AttributeValue<'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

View file

@ -8,6 +8,7 @@ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
fmt::Arguments, fmt::Arguments,
future::Future, future::Future,
rc::Rc,
}; };
pub type TemplateId = &'static str; pub type TemplateId = &'static str;
@ -88,7 +89,7 @@ impl<'a> VNode<'a> {
pub(crate) fn clear_listeners(&self) { pub(crate) fn clear_listeners(&self) {
for attr in self.dynamic_attrs { for attr in self.dynamic_attrs {
if let AttributeValue::Listener(l) = &attr.value { if let AttributeValue::Listener(l) = &attr.value {
l.borrow_mut().take(); l.0.borrow_mut().take();
} }
} }
} }
@ -317,6 +318,9 @@ pub struct Attribute<'a> {
/// ///
/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`] /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
/// variant. /// variant.
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
#[derive(Clone)]
pub enum AttributeValue<'a> { pub enum AttributeValue<'a> {
/// Text attribute /// Text attribute
Text(&'a str), Text(&'a str),
@ -331,16 +335,67 @@ pub enum AttributeValue<'a> {
Bool(bool), Bool(bool),
/// A listener, like "onclick" /// A listener, like "onclick"
Listener(RefCell<Option<ListenerCb<'a>>>), Listener(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(AnyValueBox),
/// 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 ListenerCbInner<'a> = RefCell<Option<BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>>>;
pub struct ListenerCb<'a>(pub ListenerCbInner<'a>);
impl Clone for ListenerCb<'_> {
fn clone(&self) -> Self {
panic!("ListenerCb cannot be cloned")
}
}
#[cfg(feature = "serialize")]
impl<'a> serde::Serialize for ListenerCb<'a> {
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("ListenerCb cannot be serialized")
}
}
#[cfg(feature = "serialize")]
impl<'de, 'a> serde::Deserialize<'de> for ListenerCb<'a> {
fn deserialize<D>(_: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("ListenerCb cannot be deserialized")
}
}
/// A boxed value that implements PartialEq and Any
#[derive(Clone)]
pub struct AnyValueBox(pub Rc<dyn AnyValue>);
#[cfg(feature = "serialize")]
impl serde::Serialize for AnyValueBox {
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("AnyValueBox cannot be serialized")
}
}
#[cfg(feature = "serialize")]
impl<'de> serde::Deserialize<'de> for AnyValueBox {
fn deserialize<D>(_: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("AnyValueBox 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 {
@ -364,8 +419,8 @@ 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)) => l0.0.any_cmp(r0.0.as_ref()),
_ => core::mem::discriminant(self) == core::mem::discriminant(other), _ => false,
} }
} }
} }
@ -559,21 +614,25 @@ impl<'a> IntoAttributeValue<'a> for &'a str {
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;
@ -582,3 +641,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 AnyValueBox {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Any(self)
}
}

View file

@ -4,7 +4,7 @@ use crate::{
arena::ElementId, arena::ElementId,
bump_frame::BumpFrame, bump_frame::BumpFrame,
innerlude::{DynamicNode, EventHandler, VComponent, VText}, innerlude::{DynamicNode, EventHandler, VComponent, VText},
innerlude::{Scheduler, SchedulerMsg}, innerlude::{ListenerCb, Scheduler, SchedulerMsg},
lazynodes::LazyNodes, lazynodes::LazyNodes,
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn}, nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
Attribute, AttributeValue, Element, Event, Properties, TaskId, Attribute, AttributeValue, Element, Event, Properties, TaskId,
@ -483,7 +483,7 @@ impl<'src> ScopeState {
})) }))
}; };
AttributeValue::Listener(RefCell::new(Some(boxed))) AttributeValue::Listener(ListenerCb(RefCell::new(Some(boxed))))
} }
/// Store a value between renders. The foundational hook for all other hooks. /// Store a value between renders. The foundational hook for all other hooks.

View file

@ -386,7 +386,7 @@ impl VirtualDom {
// We check the bubble state between each call to see if the event has been stopped from bubbling // We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() { for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener { if let AttributeValue::Listener(listener) = listener {
if let Some(cb) = listener.borrow_mut().as_deref_mut() { if let Some(cb) = listener.0.borrow_mut().as_deref_mut() {
cb(uievent.clone()); cb(uievent.clone());
} }
@ -493,7 +493,7 @@ impl VirtualDom {
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"), RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
} }
self.finalize() unsafe { std::mem::transmute(self.finalize()) }
} }
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
@ -591,7 +591,7 @@ impl VirtualDom {
// If there's no pending suspense, then we have no reason to wait for anything // If there's no pending suspense, then we have no reason to wait for anything
if self.scheduler.leaves.borrow().is_empty() { if self.scheduler.leaves.borrow().is_empty() {
return self.finalize(); return unsafe { std::mem::transmute(self.finalize()) };
} }
// Poll the suspense leaves in the meantime // Poll the suspense leaves in the meantime
@ -605,13 +605,13 @@ impl VirtualDom {
if let Either::Left((_, _)) = select(&mut deadline, pinned).await { if let Either::Left((_, _)) = select(&mut deadline, pinned).await {
// release the borrowed // release the borrowed
drop(work); drop(work);
return self.finalize(); return unsafe { std::mem::transmute(self.finalize()) };
} }
} }
} }
/// Swap the current mutations with a new /// Swap the current mutations with a new
fn finalize(&mut self) -> Mutations { fn finalize(&mut self) -> Mutations<'static> {
// todo: make this a routine // todo: make this a routine
let mut out = Mutations::default(); let mut out = Mutations::default();
std::mem::swap(&mut self.mutations, &mut out); std::mem::swap(&mut self.mutations, &mut out);

View file

@ -2,6 +2,7 @@
//! //!
//! 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::*;
@ -22,6 +23,8 @@ fn attrs_cycle() {
} }
}); });
let bump = Bump::new();
assert_eq!( assert_eq!(
dom.rebuild().santize().edits, dom.rebuild().santize().edits,
[ [
@ -36,8 +39,13 @@ 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: "1".into_value(&bump),
id: ElementId(3,),
ns: None
},
SetAttribute { name: "id", value: "1".into_value(&bump), id: ElementId(3,), ns: None },
ReplaceWith { id: ElementId(1,), m: 1 }, ReplaceWith { id: ElementId(1,), m: 1 },
] ]
); );
@ -57,8 +65,13 @@ fn attrs_cycle() {
[ [
LoadTemplate { name: "template", index: 0, id: ElementId(2) }, LoadTemplate { name: "template", index: 0, id: ElementId(2) },
AssignId { path: &[0], id: ElementId(1) }, AssignId { path: &[0], id: ElementId(1) },
SetAttribute { name: "class", value: "3", id: ElementId(1), ns: None }, SetAttribute {
SetAttribute { name: "id", value: "3", id: ElementId(1), ns: None }, name: "class",
value: "3".into_value(&bump),
id: ElementId(1),
ns: None
},
SetAttribute { name: "id", value: "3".into_value(&bump), id: ElementId(1), ns: None },
ReplaceWith { id: ElementId(3), m: 1 } ReplaceWith { id: ElementId(3), m: 1 }
] ]
); );

View file

@ -1,15 +1,22 @@
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: false.into_value(&bump),
id: ElementId(1,),
ns: None
},
AppendChildren { m: 1, id: ElementId(0) }, AppendChildren { m: 1, id: ElementId(0) },
] ]
) );
} }

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 {

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));

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!(

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!(

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

View file

@ -314,66 +314,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,
Remove { id: ElementId(9,) }, [
Remove { id: ElementId(7,) }, Remove { id: ElementId(9,) },
Remove { id: ElementId(5,) }, Remove { id: ElementId(7,) },
Remove { id: ElementId(1,) }, Remove { id: ElementId(5,) },
CreatePlaceholder { id: ElementId(3,) }, Remove { id: ElementId(1,) },
ReplaceWith { id: ElementId(2,), m: 1 }, CreatePlaceholder { id: ElementId(3,) },
] 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(1,) }, LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
ReplaceWith { id: ElementId(3,), m: 1 }, HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) },
] ReplaceWith { id: ElementId(3,), m: 1 },
) ]
)
}
} }

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: "123".into_value(&bump),
id: ElementId(1),
ns: None,
},
NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) }, NewEventListener { name: "click", scope: ScopeId(0), 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 },
], ]
); });
} }

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

View file

@ -42,7 +42,7 @@ fn create_rows(c: &mut Criterion) {
c.bench_function("create rows", |b| { c.bench_function("create rows", |b| {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
dom.rebuild(); let _ = dom.rebuild();
b.iter(|| { b.iter(|| {
let g = dom.rebuild(); let g = dom.rebuild();

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"

View file

@ -306,8 +306,7 @@ impl Member {
+ field.ty.to_token_stream().to_string().as_str()) + field.ty.to_token_stream().to_string().as_str())
.as_str(), .as_str(),
Span::call_site(), Span::call_site(),
) ),
.into(),
ident: field.ident.as_ref()?.clone(), ident: field.ident.as_ref()?.clone(),
}) })
} }

View file

@ -1,6 +1,7 @@
use crate::{state::State, tree::NodeId}; use crate::{state::State, tree::NodeId};
use dioxus_core::ElementId; use dioxus_core::{AnyValueBox, AttributeValue, 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)]
@ -72,15 +73,43 @@ pub struct OwnedAttributeView<'a> {
pub value: &'a OwnedAttributeValue, pub value: &'a OwnedAttributeValue,
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub enum OwnedAttributeValue { pub enum OwnedAttributeValue {
Text(String), Text(String),
Float(f32), Float(f64),
Int(i32), Int(i64),
Bool(bool), Bool(bool),
Any(AnyValueBox),
None, None,
} }
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::Any(_) => f.debug_tuple("Any").finish(),
Self::None => write!(f, "None"),
}
}
}
impl From<AttributeValue<'_>> for OwnedAttributeValue {
fn from(value: AttributeValue<'_>) -> Self {
match value {
AttributeValue::Text(text) => Self::Text(text.to_string()),
AttributeValue::Float(float) => Self::Float(float),
AttributeValue::Int(int) => Self::Int(int),
AttributeValue::Bool(bool) => Self::Bool(bool),
AttributeValue::Any(any) => Self::Any(any),
AttributeValue::None => Self::None,
_ => Self::None,
}
}
}
impl OwnedAttributeValue { impl OwnedAttributeValue {
pub fn as_text(&self) -> Option<&str> { pub fn as_text(&self) -> Option<&str> {
match self { match self {
@ -89,14 +118,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,

View file

@ -102,11 +102,7 @@ impl<S: State> RealDom<S> {
self.tree.add_child(node_id, child_id); self.tree.add_child(node_id, child_id);
} }
fn create_template_node( fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId {
&mut self,
node: &TemplateNode,
mutations_vec: &mut FxHashMap<RealNodeId, NodeMask>,
) -> RealNodeId {
match node { match node {
TemplateNode::Element { TemplateNode::Element {
tag, tag,
@ -139,27 +135,18 @@ impl<S: State> RealDom<S> {
}); });
let node_id = self.create_node(node); let node_id = self.create_node(node);
for child in *children { for child in *children {
let child_id = self.create_template_node(child, mutations_vec); let child_id = self.create_template_node(child);
self.add_child(node_id, child_id); self.add_child(node_id, child_id);
} }
node_id node_id
} }
TemplateNode::Text { text } => { TemplateNode::Text { text } => self.create_node(Node::new(NodeType::Text {
let node_id = self.create_node(Node::new(NodeType::Text { text: text.to_string(),
text: text.to_string(), })),
})); TemplateNode::Dynamic { .. } => self.create_node(Node::new(NodeType::Placeholder)),
node_id TemplateNode::DynamicText { .. } => self.create_node(Node::new(NodeType::Text {
} text: String::new(),
TemplateNode::Dynamic { .. } => { })),
let node_id = self.create_node(Node::new(NodeType::Placeholder));
node_id
}
TemplateNode::DynamicText { .. } => {
let node_id = self.create_node(Node::new(NodeType::Text {
text: String::new(),
}));
node_id
}
} }
} }
@ -172,7 +159,7 @@ impl<S: State> RealDom<S> {
for template in mutations.templates { for template in mutations.templates {
let mut template_root_ids = Vec::new(); let mut template_root_ids = Vec::new();
for root in template.roots { for root in template.roots {
let id = self.create_template_node(root, &mut nodes_updated); let id = self.create_template_node(root);
template_root_ids.push(id); template_root_ids.push(id);
} }
self.templates self.templates
@ -283,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,

View file

@ -211,7 +211,7 @@ pub enum ElementAttr {
} }
impl ElementAttr { impl ElementAttr {
pub fn flart(&self) -> Span { pub fn start(&self) -> Span {
match self { match self {
ElementAttr::AttrText { name, .. } => name.span(), ElementAttr::AttrText { name, .. } => name.span(),
ElementAttr::AttrExpression { name, .. } => name.span(), ElementAttr::AttrExpression { name, .. } => name.span(),
@ -265,7 +265,7 @@ impl ToTokens for ElementAttrNamed {
ElementAttr::CustomAttrText { name, value } => { ElementAttr::CustomAttrText { name, value } => {
quote! { quote! {
__cx.attr( __cx.attr(
dioxus_elements::#el_name::#name.0, #name,
#value, #value,
None, None,
false false
@ -275,7 +275,7 @@ impl ToTokens for ElementAttrNamed {
ElementAttr::CustomAttrExpression { name, value } => { ElementAttr::CustomAttrExpression { name, value } => {
quote! { quote! {
__cx.attr( __cx.attr(
dioxus_elements::#el_name::#name.0, #name,
#value, #value,
None, None,
false false

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,

View file

@ -75,10 +75,21 @@ impl WebsysDom {
value, value,
id, id,
ns, ns,
} => i.SetAttribute(id.0 as u32, name, value.into(), ns), } => match value {
SetBoolAttribute { name, value, id } => { dioxus_core::AttributeValue::Text(txt) => {
i.SetBoolAttribute(id.0 as u32, name, value) i.SetAttribute(id.0 as u32, name, txt.into(), ns)
} }
dioxus_core::AttributeValue::Float(f) => {
i.SetAttribute(id.0 as u32, name, f.into(), ns)
}
dioxus_core::AttributeValue::Int(n) => {
i.SetAttribute(id.0 as u32, name, n.into(), ns)
}
dioxus_core::AttributeValue::Bool(b) => {
i.SetBoolAttribute(id.0 as u32, name, b)
}
_ => unreachable!(),
},
SetText { value, id } => i.SetText(id.0 as u32, value.into()), SetText { value, id } => i.SetText(id.0 as u32, value.into()),
NewEventListener { name, id, .. } => { NewEventListener { name, id, .. } => {
self.interpreter.NewEventListener( self.interpreter.NewEventListener(

View file

@ -187,10 +187,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();