diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index 45799ca62..79ad7dfb7 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -144,8 +144,8 @@ impl Buffer { let mut total = 0; for attr in attributes { - if self.current_span_is_primary(attr.attr.flart()) { - 'line: for line in self.src[..attr.attr.flart().start().line - 1].iter().rev() { + if self.current_span_is_primary(attr.attr.start()) { + 'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() { match (line.trim().starts_with("//"), line.is_empty()) { (true, _) => return 100000, (_, true) => continue 'line, diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index fd4529766..b5f1c2f84 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -157,7 +157,7 @@ impl Buffer { while let Some(attr) = attr_iter.next() { self.indent += 1; if !sameline { - self.write_comments(attr.attr.flart())?; + self.write_comments(attr.attr.start())?; } self.indent -= 1; diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index cc63a0e8b..4804897a2 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -105,27 +105,10 @@ impl<'b> VirtualDom { attribute.mounted_element.set(id); // 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 { - 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(_) => { self.mutations.push(NewEventListener { // all listeners start with "on" @@ -134,10 +117,18 @@ impl<'b> VirtualDom { id, }) } - AttributeValue::Float(_) => todo!(), - AttributeValue::Int(_) => todo!(), - AttributeValue::Any(_) => todo!(), - AttributeValue::None => todo!(), + _ => { + // Safety: we promise not to re-alias this text later on after committing it to the mutation + let unbounded_value = + 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) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 0384192e2..b2d90a2ca 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -77,20 +77,14 @@ impl<'b> VirtualDom { if left_attr.value != right_attr.value || left_attr.volatile { // todo: add more types of attribute values - match right_attr.value { - AttributeValue::Text(text) => { - let name = unsafe { std::mem::transmute(left_attr.name) }; - let value = unsafe { std::mem::transmute(text) }; - self.mutations.push(Mutation::SetAttribute { - id: left_attr.mounted_element.get(), - ns: right_attr.namespace, - name, - value, - }); - } - // todo: more types of attribute values - _ => todo!("other attribute types"), - } + let name = unsafe { std::mem::transmute(left_attr.name) }; + let value = unsafe { std::mem::transmute(right_attr.value.clone()) }; + self.mutations.push(Mutation::SetAttribute { + id: left_attr.mounted_element.get(), + ns: right_attr.namespace, + name, + value, + }); } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 613a4a115..0de74c911 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -70,10 +70,10 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event, - Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, - ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute, - TemplateNode, VComponent, VNode, VText, VirtualDom, + fc_to_builder, AnyValueBox, Attribute, AttributeValue, Component, DynamicNode, Element, + ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation, Mutations, + Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, + Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types @@ -81,9 +81,9 @@ pub use crate::innerlude::{ /// This includes types like [`Scope`], [`Element`], and [`Component`]. pub mod prelude { pub use crate::innerlude::{ - fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope, - ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode, - VirtualDom, + fc_to_builder, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes, + Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, + TemplateNode, VNode, VirtualDom, }; } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2512ae9fa..938458a3d 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,6 +1,6 @@ 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 /// @@ -48,7 +48,7 @@ impl<'a> Mutations<'a> { /// Push a new mutation into the dom_edits list 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), serde(tag = "type") )] -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub enum Mutation<'a> { /// Add these m children to the target element AppendChildren { @@ -193,8 +193,9 @@ pub enum Mutation<'a> { SetAttribute { /// The name of the attribute to set. name: &'a str, + /// The value of the attribute. - value: &'a str, + value: AttributeValue<'a>, /// The ID of the node to set the attribute of. id: ElementId, @@ -204,18 +205,6 @@ pub enum Mutation<'a> { ns: Option<&'a str>, }, - /// Set the value of a node's attribute. - SetBoolAttribute { - /// The name of the attribute to set. - name: &'a str, - - /// The value of the attribute. - value: bool, - - /// The ID of the node to set the attribute of. - id: ElementId, - }, - /// Set the textcontent of a node. SetText { /// The textcontent of the node diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 81ed5da06..c776da967 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -8,6 +8,7 @@ use std::{ cell::{Cell, RefCell}, fmt::Arguments, future::Future, + rc::Rc, }; pub type TemplateId = &'static str; @@ -88,7 +89,7 @@ impl<'a> VNode<'a> { pub(crate) fn clear_listeners(&self) { for attr in self.dynamic_attrs { 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`] /// variant. +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serialize", serde(untagged))] +#[derive(Clone)] pub enum AttributeValue<'a> { /// Text attribute Text(&'a str), @@ -331,16 +335,67 @@ pub enum AttributeValue<'a> { Bool(bool), /// A listener, like "onclick" - Listener(RefCell>>), + Listener(ListenerCb<'a>), /// 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 None, } -type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + 'a>; +pub type ListenerCbInner<'a> = RefCell) + '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(&self, _: S) -> Result + where + S: serde::Serializer, + { + panic!("ListenerCb cannot be serialized") + } +} + +#[cfg(feature = "serialize")] +impl<'de, 'a> serde::Deserialize<'de> for ListenerCb<'a> { + fn deserialize(_: D) -> Result + 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); + +#[cfg(feature = "serialize")] +impl serde::Serialize for AnyValueBox { + fn serialize(&self, _: S) -> Result + where + S: serde::Serializer, + { + panic!("AnyValueBox cannot be serialized") + } +} + +#[cfg(feature = "serialize")] +impl<'de> serde::Deserialize<'de> for AnyValueBox { + fn deserialize(_: D) -> Result + where + D: serde::Deserializer<'de>, + { + panic!("AnyValueBox cannot be deserialized") + } +} impl<'a> std::fmt::Debug for AttributeValue<'a> { 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::Bool(l0), Self::Bool(r0)) => l0 == r0, (Self::Listener(_), Self::Listener(_)) => true, - (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()), - _ => core::mem::discriminant(self) == core::mem::discriminant(other), + (Self::Any(l0), Self::Any(r0)) => l0.0.any_cmp(r0.0.as_ref()), + _ => false, } } } @@ -559,21 +614,25 @@ impl<'a> IntoAttributeValue<'a> for &'a str { AttributeValue::Text(self) } } + impl<'a> IntoAttributeValue<'a> for f64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Float(self) } } + impl<'a> IntoAttributeValue<'a> for i64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Int(self) } } + impl<'a> IntoAttributeValue<'a> for bool { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Bool(self) } } + impl<'a> IntoAttributeValue<'a> for Arguments<'_> { fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> { use bumpalo::core_alloc::fmt::Write; @@ -582,3 +641,9 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> { AttributeValue::Text(str_buf.into_bump_str()) } } + +impl<'a> IntoAttributeValue<'a> for AnyValueBox { + fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { + AttributeValue::Any(self) + } +} diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index b1bbb42fa..31ac50440 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -4,7 +4,7 @@ use crate::{ arena::ElementId, bump_frame::BumpFrame, innerlude::{DynamicNode, EventHandler, VComponent, VText}, - innerlude::{Scheduler, SchedulerMsg}, + innerlude::{ListenerCb, Scheduler, SchedulerMsg}, lazynodes::LazyNodes, nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn}, 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. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index ced92acb0..4c01512e2 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -386,7 +386,7 @@ impl VirtualDom { // We check the bubble state between each call to see if the event has been stopped from bubbling for listener in listeners.drain(..).rev() { 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()); } @@ -493,7 +493,7 @@ impl VirtualDom { 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 @@ -591,7 +591,7 @@ impl VirtualDom { // If there's no pending suspense, then we have no reason to wait for anything if self.scheduler.leaves.borrow().is_empty() { - return self.finalize(); + return unsafe { std::mem::transmute(self.finalize()) }; } // Poll the suspense leaves in the meantime @@ -605,13 +605,13 @@ impl VirtualDom { if let Either::Left((_, _)) = select(&mut deadline, pinned).await { // release the borrowed drop(work); - return self.finalize(); + return unsafe { std::mem::transmute(self.finalize()) }; } } } /// Swap the current mutations with a new - fn finalize(&mut self) -> Mutations { + fn finalize(&mut self) -> Mutations<'static> { // todo: make this a routine let mut out = Mutations::default(); std::mem::swap(&mut self.mutations, &mut out); diff --git a/packages/core/tests/attr_cleanup.rs b/packages/core/tests/attr_cleanup.rs index c0aa9a1da..c8e1baa1f 100644 --- a/packages/core/tests/attr_cleanup.rs +++ b/packages/core/tests/attr_cleanup.rs @@ -2,6 +2,7 @@ //! //! This tests to ensure we clean it up +use bumpalo::Bump; use dioxus::core::{ElementId, Mutation::*}; use dioxus::prelude::*; @@ -22,6 +23,8 @@ fn attrs_cycle() { } }); + let bump = Bump::new(); + assert_eq!( dom.rebuild().santize().edits, [ @@ -36,8 +39,13 @@ fn attrs_cycle() { [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, AssignId { path: &[0,], id: ElementId(3,) }, - SetAttribute { name: "class", value: "1", id: ElementId(3,), ns: None }, - SetAttribute { name: "id", value: "1", id: ElementId(3,), ns: None }, + SetAttribute { + name: "class", + value: "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 }, ] ); @@ -57,8 +65,13 @@ fn attrs_cycle() { [ LoadTemplate { name: "template", index: 0, id: ElementId(2) }, AssignId { path: &[0], id: ElementId(1) }, - SetAttribute { name: "class", value: "3", id: ElementId(1), ns: None }, - SetAttribute { name: "id", value: "3", id: ElementId(1), ns: None }, + SetAttribute { + 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 } ] ); diff --git a/packages/core/tests/boolattrs.rs b/packages/core/tests/boolattrs.rs index 5cd2ad1ad..c2ad70333 100644 --- a/packages/core/tests/boolattrs.rs +++ b/packages/core/tests/boolattrs.rs @@ -1,15 +1,22 @@ +use bumpalo::Bump; use dioxus::core::{ElementId, Mutation::*}; use dioxus::prelude::*; #[test] fn bool_test() { let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false }))); + let bump = Bump::new(); assert_eq!( app.rebuild().santize().edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, - SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) }, + SetAttribute { + name: "hidden", + value: false.into_value(&bump), + id: ElementId(1,), + ns: None + }, AppendChildren { m: 1, id: ElementId(0) }, ] - ) + ); } diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 0aace9c97..1b83789ec 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -18,7 +18,7 @@ fn test_borrowed_state() { ReplacePlaceholder { path: &[0,], m: 1 }, AppendChildren { m: 1, id: ElementId(0) }, ] - ) + ); } fn Parent(cx: Scope) -> Element { diff --git a/packages/core/tests/bubble_error.rs b/packages/core/tests/bubble_error.rs index 3dbfe80a9..ebbbc613c 100644 --- a/packages/core/tests/bubble_error.rs +++ b/packages/core/tests/bubble_error.rs @@ -20,7 +20,9 @@ fn app(cx: Scope) -> Element { fn bubbles_error() { let mut dom = VirtualDom::new(app); - let _edits = dom.rebuild().santize(); + { + let _edits = dom.rebuild().santize(); + } dom.mark_dirty(ScopeId(0)); diff --git a/packages/core/tests/cycle.rs b/packages/core/tests/cycle.rs index 133b04658..10498f5cc 100644 --- a/packages/core/tests/cycle.rs +++ b/packages/core/tests/cycle.rs @@ -12,14 +12,16 @@ fn cycling_elements() { }) }); - let edits = dom.rebuild().santize(); - assert_eq!( - edits.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, - AppendChildren { m: 1, id: ElementId(0) }, - ] - ); + { + let edits = dom.rebuild().santize(); + assert_eq!( + edits.edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, + AppendChildren { m: 1, id: ElementId(0) }, + ] + ); + } dom.mark_dirty(ScopeId(0)); assert_eq!( diff --git a/packages/core/tests/diff_component.rs b/packages/core/tests/diff_component.rs index 2b74f8302..69ab6de6d 100644 --- a/packages/core/tests/diff_component.rs +++ b/packages/core/tests/diff_component.rs @@ -59,19 +59,21 @@ fn component_swap() { } let mut dom = VirtualDom::new(app); - let edits = dom.rebuild().santize(); - assert_eq!( - edits.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(1) }, - LoadTemplate { name: "template", index: 0, id: ElementId(2) }, - LoadTemplate { name: "template", index: 0, id: ElementId(3) }, - LoadTemplate { name: "template", index: 0, id: ElementId(4) }, - ReplacePlaceholder { path: &[1], m: 3 }, - LoadTemplate { name: "template", index: 0, id: ElementId(5) }, - AppendChildren { m: 2, id: ElementId(0) } - ] - ); + { + let edits = dom.rebuild().santize(); + assert_eq!( + edits.edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(1) }, + LoadTemplate { name: "template", index: 0, id: ElementId(2) }, + LoadTemplate { name: "template", index: 0, id: ElementId(3) }, + LoadTemplate { name: "template", index: 0, id: ElementId(4) }, + ReplacePlaceholder { path: &[1], m: 3 }, + LoadTemplate { name: "template", index: 0, id: ElementId(5) }, + AppendChildren { m: 2, id: ElementId(0) } + ] + ); + } dom.mark_dirty(ScopeId(0)); assert_eq!( diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs index 81013849d..be26e00c0 100644 --- a/packages/core/tests/diff_keyed_list.rs +++ b/packages/core/tests/diff_keyed_list.rs @@ -20,22 +20,24 @@ fn keyed_diffing_out_of_order() { cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) }); - assert_eq!( - dom.rebuild().santize().edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(3,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(4,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(5,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(6,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(7,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(8,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(9,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(10,) }, - AppendChildren { m: 10, id: ElementId(0) }, - ] - ); + { + assert_eq!( + dom.rebuild().santize().edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(3,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(4,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(5,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(6,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(7,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(8,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(9,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(10,) }, + AppendChildren { m: 10, id: ElementId(0) }, + ] + ); + } dom.mark_dirty(ScopeId(0)); assert_eq!( @@ -44,7 +46,7 @@ fn keyed_diffing_out_of_order() { PushRoot { id: ElementId(7,) }, InsertBefore { id: ElementId(5,), m: 1 }, ] - ) + ); } /// Should result in moves only @@ -70,7 +72,7 @@ fn keyed_diffing_out_of_order_adds() { PushRoot { id: ElementId(4,) }, InsertBefore { id: ElementId(1,), m: 2 }, ] - ) + ); } /// Should result in moves only diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs index 415aa79c0..84fd65604 100644 --- a/packages/core/tests/diff_unkeyed_list.rs +++ b/packages/core/tests/diff_unkeyed_list.rs @@ -314,66 +314,76 @@ fn remove_many() { }) }); - let edits = dom.rebuild().santize(); - assert!(edits.templates.is_empty()); - assert_eq!( - edits.edits, - [ - CreatePlaceholder { id: ElementId(1,) }, - AppendChildren { id: ElementId(0), m: 1 }, - ] - ); + { + let edits = dom.rebuild().santize(); + assert!(edits.templates.is_empty()); + assert_eq!( + edits.edits, + [ + CreatePlaceholder { id: ElementId(1,) }, + AppendChildren { id: ElementId(0), m: 1 }, + ] + ); + } - dom.mark_dirty(ScopeId(0)); - let edits = dom.render_immediate().santize(); - assert_eq!( - edits.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, - HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) }, - ReplaceWith { id: ElementId(1,), m: 1 }, - ] - ); + { + dom.mark_dirty(ScopeId(0)); + let edits = dom.render_immediate().santize(); + assert_eq!( + edits.edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, + HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) }, + ReplaceWith { id: ElementId(1,), m: 1 }, + ] + ); + } - dom.mark_dirty(ScopeId(0)); - let edits = dom.render_immediate().santize(); - assert_eq!( - edits.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, - HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(5,) }, - HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(7,) }, - HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) }, - LoadTemplate { name: "template", index: 0, id: ElementId(9,) }, - HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) }, - InsertAfter { id: ElementId(2,), m: 4 }, - ] - ); + { + dom.mark_dirty(ScopeId(0)); + let edits = dom.render_immediate().santize(); + assert_eq!( + edits.edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, + HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(5,) }, + HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(7,) }, + HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) }, + LoadTemplate { name: "template", index: 0, id: ElementId(9,) }, + HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) }, + InsertAfter { id: ElementId(2,), m: 4 }, + ] + ); + } - dom.mark_dirty(ScopeId(0)); - let edits = dom.render_immediate().santize(); - assert_eq!( - edits.edits, - [ - Remove { id: ElementId(9,) }, - Remove { id: ElementId(7,) }, - Remove { id: ElementId(5,) }, - Remove { id: ElementId(1,) }, - CreatePlaceholder { id: ElementId(3,) }, - ReplaceWith { id: ElementId(2,), m: 1 }, - ] - ); + { + dom.mark_dirty(ScopeId(0)); + let edits = dom.render_immediate().santize(); + assert_eq!( + edits.edits, + [ + Remove { id: ElementId(9,) }, + Remove { id: ElementId(7,) }, + Remove { id: ElementId(5,) }, + Remove { id: ElementId(1,) }, + CreatePlaceholder { id: ElementId(3,) }, + ReplaceWith { id: ElementId(2,), m: 1 }, + ] + ); + } - dom.mark_dirty(ScopeId(0)); - let edits = dom.render_immediate().santize(); - assert_eq!( - edits.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, - HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) }, - ReplaceWith { id: ElementId(3,), m: 1 }, - ] - ) + { + dom.mark_dirty(ScopeId(0)); + let edits = dom.render_immediate().santize(); + assert_eq!( + edits.edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, + HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) }, + ReplaceWith { id: ElementId(3,), m: 1 }, + ] + ) + } } diff --git a/packages/core/tests/kitchen_sink.rs b/packages/core/tests/kitchen_sink.rs index 78ef608cc..1201f4e91 100644 --- a/packages/core/tests/kitchen_sink.rs +++ b/packages/core/tests/kitchen_sink.rs @@ -1,3 +1,4 @@ +use bumpalo::Bump; use dioxus::core::{ElementId, Mutation}; use dioxus::prelude::*; @@ -26,17 +27,22 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element { #[test] fn dual_stream() { let mut dom = VirtualDom::new(basic_syntax_is_a_template); + let bump = Bump::new(); let edits = dom.rebuild().santize(); use Mutation::*; - assert_eq!( - edits.edits, + assert_eq!(edits.edits, { [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, - SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None }, + SetAttribute { + name: "class", + value: "123".into_value(&bump), + id: ElementId(1), + ns: None, + }, NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) }, HydrateText { path: &[0, 0], value: "123", id: ElementId(2) }, - AppendChildren { id: ElementId(0), m: 1 } - ], - ); + AppendChildren { id: ElementId(0), m: 1 }, + ] + }); } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 9fac13937..775ca3d71 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -9,32 +9,34 @@ use std::time::Duration; async fn it_works() { let mut dom = VirtualDom::new(app); - let mutations = dom.rebuild().santize(); + { + let mutations = dom.rebuild().santize(); - // We should at least get the top-level template in before pausing for the children - // note: we dont test template edits anymore - // assert_eq!( - // mutations.templates, - // [ - // CreateElement { name: "div" }, - // CreateStaticText { value: "Waiting for child..." }, - // CreateStaticPlaceholder, - // AppendChildren { m: 2 }, - // SaveTemplate { name: "template", m: 1 } - // ] - // ); + // We should at least get the top-level template in before pausing for the children + // note: we dont test template edits anymore + // assert_eq!( + // mutations.templates, + // [ + // CreateElement { name: "div" }, + // CreateStaticText { value: "Waiting for child..." }, + // CreateStaticPlaceholder, + // AppendChildren { m: 2 }, + // SaveTemplate { name: "template", m: 1 } + // ] + // ); - // And we should load it in and assign the placeholder properly - assert_eq!( - mutations.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(1) }, - // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly? - // can we even? - AssignId { path: &[1], id: ElementId(3) }, - AppendChildren { m: 1, id: ElementId(0) }, - ] - ); + // And we should load it in and assign the placeholder properly + assert_eq!( + mutations.edits, + [ + LoadTemplate { name: "template", index: 0, id: ElementId(1) }, + // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly? + // can we even? + AssignId { path: &[1], id: ElementId(3) }, + AppendChildren { m: 1, id: ElementId(0) }, + ] + ); + } // wait just a moment, not enough time for the boundary to resolve diff --git a/packages/dioxus/benches/jsframework.rs b/packages/dioxus/benches/jsframework.rs index 24ca2fe69..b3405b2bd 100644 --- a/packages/dioxus/benches/jsframework.rs +++ b/packages/dioxus/benches/jsframework.rs @@ -42,7 +42,7 @@ fn create_rows(c: &mut Criterion) { c.bench_function("create rows", |b| { let mut dom = VirtualDom::new(app); - dom.rebuild(); + let _ = dom.rebuild(); b.iter(|| { let g = dom.rebuild(); diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index bff83ec42..759d8ff00 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -18,10 +18,9 @@ proc-macro = true syn = { version = "1.0.11", features = ["extra-traits"] } quote = "1.0" dioxus-native-core = { path = "../native-core", version = "^0.2.0" } - -[dev-dependencies] dioxus = { path = "../dioxus" } +[dev-dependencies] smallvec = "1.6" rustc-hash = "1.1.0" anymap = "0.12.1" diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index dda96b512..5bf50afcc 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -306,8 +306,7 @@ impl Member { + field.ty.to_token_stream().to_string().as_str()) .as_str(), Span::call_site(), - ) - .into(), + ), ident: field.ident.as_ref()?.clone(), }) } diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 4530fd350..7220e0fab 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,6 +1,7 @@ use crate::{state::State, tree::NodeId}; -use dioxus_core::ElementId; +use dioxus_core::{AnyValueBox, AttributeValue, ElementId}; use rustc_hash::{FxHashMap, FxHashSet}; +use std::fmt::Debug; /// The node is stored client side and stores only basic data about the node. #[derive(Debug, Clone)] @@ -72,15 +73,43 @@ pub struct OwnedAttributeView<'a> { pub value: &'a OwnedAttributeValue, } -#[derive(Clone, Debug)] +#[derive(Clone)] pub enum OwnedAttributeValue { Text(String), - Float(f32), - Int(i32), + Float(f64), + Int(i64), Bool(bool), + Any(AnyValueBox), 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> 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 { pub fn as_text(&self) -> Option<&str> { match self { @@ -89,14 +118,14 @@ impl OwnedAttributeValue { } } - pub fn as_float(&self) -> Option { + pub fn as_float(&self) -> Option { match self { OwnedAttributeValue::Float(float) => Some(*float), _ => None, } } - pub fn as_int(&self) -> Option { + pub fn as_int(&self) -> Option { match self { OwnedAttributeValue::Int(int) => Some(*int), _ => None, diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 7d56b0dca..637a8d7ce 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -102,11 +102,7 @@ impl RealDom { self.tree.add_child(node_id, child_id); } - fn create_template_node( - &mut self, - node: &TemplateNode, - mutations_vec: &mut FxHashMap, - ) -> RealNodeId { + fn create_template_node(&mut self, node: &TemplateNode) -> RealNodeId { match node { TemplateNode::Element { tag, @@ -139,27 +135,18 @@ impl RealDom { }); let node_id = self.create_node(node); 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); } node_id } - TemplateNode::Text { text } => { - let node_id = self.create_node(Node::new(NodeType::Text { - text: text.to_string(), - })); - node_id - } - 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 - } + TemplateNode::Text { text } => self.create_node(Node::new(NodeType::Text { + text: text.to_string(), + })), + TemplateNode::Dynamic { .. } => self.create_node(Node::new(NodeType::Placeholder)), + TemplateNode::DynamicText { .. } => self.create_node(Node::new(NodeType::Text { + text: String::new(), + })), } } @@ -172,7 +159,7 @@ impl RealDom { for template in mutations.templates { let mut template_root_ids = Vec::new(); 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); } self.templates @@ -283,26 +270,7 @@ impl RealDom { namespace: ns.map(|s| s.to_string()), volatile: false, }, - crate::node::OwnedAttributeValue::Text(value.to_string()), - ); - mark_dirty( - node_id, - NodeMask::new_with_attrs(AttributeMask::single(name)), - &mut nodes_updated, - ); - } - } - SetBoolAttribute { name, value, id } => { - let node_id = self.element_to_node_id(id); - let node = self.tree.get_mut(node_id).unwrap(); - if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type { - attributes.insert( - OwnedAttributeDiscription { - name: name.to_string(), - namespace: None, - volatile: false, - }, - crate::node::OwnedAttributeValue::Bool(value), + OwnedAttributeValue::from(value), ); mark_dirty( node_id, diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 307059f9d..88699b79e 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -211,7 +211,7 @@ pub enum ElementAttr { } impl ElementAttr { - pub fn flart(&self) -> Span { + pub fn start(&self) -> Span { match self { ElementAttr::AttrText { name, .. } => name.span(), ElementAttr::AttrExpression { name, .. } => name.span(), @@ -265,7 +265,7 @@ impl ToTokens for ElementAttrNamed { ElementAttr::CustomAttrText { name, value } => { quote! { __cx.attr( - dioxus_elements::#el_name::#name.0, + #name, #value, None, false @@ -275,7 +275,7 @@ impl ToTokens for ElementAttrNamed { ElementAttr::CustomAttrExpression { name, value } => { quote! { __cx.attr( - dioxus_elements::#el_name::#name.0, + #name, #value, None, false diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 90d9e9da0..57f1c9e1f 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -79,7 +79,7 @@ impl NodeDepState for Focus { if let Some(index) = a .value .as_int() - .or_else(|| a.value.as_text().and_then(|v| v.parse::().ok())) + .or_else(|| a.value.as_text().and_then(|v| v.parse::().ok())) { match index.cmp(&0) { Ordering::Less => FocusLevel::Unfocusable, diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 0cdf46b3e..96d95bc89 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -75,10 +75,21 @@ impl WebsysDom { value, id, ns, - } => i.SetAttribute(id.0 as u32, name, value.into(), ns), - SetBoolAttribute { name, value, id } => { - i.SetBoolAttribute(id.0 as u32, name, value) - } + } => match value { + dioxus_core::AttributeValue::Text(txt) => { + 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()), NewEventListener { name, id, .. } => { self.interpreter.NewEventListener( diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 62a2bf4dd..cf206b698 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -187,10 +187,12 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop // if should_hydrate { // } else { - let edits = dom.rebuild(); + { + let edits = dom.rebuild(); - websys_dom.load_templates(&edits.templates); - websys_dom.apply_edits(edits.edits); + websys_dom.load_templates(&edits.templates); + websys_dom.apply_edits(edits.edits); + } // the mutations come back with nothing - we need to actually mount them websys_dom.mount();