diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index d83b41b0c..c3ea4dc2c 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,8 +1,8 @@ use std::marker::PhantomData; use crate::{ - factory::{ComponentReturn, RenderReturn}, innerlude::Scoped, + nodes::{ComponentReturn, RenderReturn}, scopes::{Scope, ScopeState}, Element, }; diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index efc947ff3..d6af9b051 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,9 +1,13 @@ use crate::{ - factory::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode, + nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode, ScopeId, }; use bumpalo::boxed::Box as BumpBox; +/// An Element's unique identifier. +/// +/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is +/// unmounted, then the `ElementId` will be reused for a new component. #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct ElementId(pub usize); diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index 581452836..44ea4f2d6 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -1,4 +1,4 @@ -use crate::factory::RenderReturn; +use crate::nodes::RenderReturn; use bumpalo::Bump; use std::cell::Cell; @@ -8,7 +8,7 @@ pub(crate) struct BumpFrame { } impl BumpFrame { - pub fn new(capacity: usize) -> Self { + pub(crate) fn new(capacity: usize) -> Self { let bump = Bump::with_capacity(capacity); Self { bump, @@ -17,7 +17,7 @@ impl BumpFrame { } /// Creates a new lifetime out of thin air - pub unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> { + pub(crate) unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> { let node = self.node.get(); if node.is_null() { diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 0559c3473..feaa276e4 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,13 +1,12 @@ use std::cell::Cell; -use crate::factory::RenderReturn; use crate::innerlude::{VComponent, VText}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; use crate::nodes::{DynamicNode, TemplateNode}; use crate::virtual_dom::VirtualDom; -use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext}; +use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext}; impl<'b> VirtualDom { /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. @@ -25,7 +24,7 @@ impl<'b> VirtualDom { pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize { // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways - if !self.templates.contains_key(&template.template.id) { + if !self.templates.contains_key(&template.template.name) { self.register_template(template); } @@ -75,7 +74,7 @@ impl<'b> VirtualDom { template.root_ids[root_idx].set(this_id); self.mutations.push(LoadTemplate { - name: template.template.id, + name: template.template.name, index: root_idx, id: this_id, }); @@ -204,7 +203,7 @@ impl<'b> VirtualDom { fn register_template(&mut self, template: &'b VNode<'b>) { // First, make sure we mark the template as seen, regardless if we process it self.templates - .insert(template.template.id, template.template); + .insert(template.template.name, template.template); // If it's all dynamic nodes, then we don't need to register it // Quickly run through and see if it's all just dynamic nodes @@ -342,7 +341,7 @@ impl<'b> VirtualDom { // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense let boundary = match self.scopes[scope.0].has_context::() { - Some(boundary) => boundary, + Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) }, _ => return created, }; diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 4ac5553b2..c0b14d0bd 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -2,9 +2,9 @@ use std::cell::Cell; use crate::{ arena::ElementId, - factory::RenderReturn, innerlude::{DirtyScope, VComponent, VText}, mutations::Mutation, + nodes::RenderReturn, nodes::{DynamicNode, VNode}, scopes::ScopeId, virtual_dom::VirtualDom, @@ -63,10 +63,10 @@ impl<'b> VirtualDom { fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { println!( "diffing node {:#?},\n\n{:#?}", - left_template.template.id, right_template.template.id + left_template.template.name, right_template.template.name ); - if left_template.template.id != right_template.template.id { + if left_template.template.name != right_template.template.name { return self.light_diff_templates(left_template, right_template); } diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs deleted file mode 100644 index b4217184d..000000000 --- a/packages/core/src/factory.rs +++ /dev/null @@ -1,197 +0,0 @@ -use crate::{innerlude::DynamicNode, AttributeValue, Element, LazyNodes, ScopeState, VNode}; -use bumpalo::boxed::Box as BumpBox; -use bumpalo::Bump; -use std::fmt::Arguments; -use std::future::Future; - -#[doc(hidden)] -pub trait ComponentReturn<'a, A = ()> { - fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>; -} - -impl<'a> ComponentReturn<'a> for Element<'a> { - fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> { - RenderReturn::Sync(self) - } -} - -#[doc(hidden)] -pub struct AsyncMarker; -impl<'a, F> ComponentReturn<'a, AsyncMarker> for F -where - F: Future> + 'a, -{ - fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> { - let f: &mut dyn Future> = cx.bump().alloc(self); - RenderReturn::Async(unsafe { BumpBox::from_raw(f) }) - } -} - -pub enum RenderReturn<'a> { - /// A currently-available element - Sync(Element<'a>), - - /// An ongoing future that will resolve to a [`Element`] - Async(BumpBox<'a, dyn Future> + 'a>), -} - -impl<'a> RenderReturn<'a> { - pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> { - unsafe { std::mem::transmute(self) } - } - pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> { - unsafe { std::mem::transmute(self) } - } -} - -#[doc(hidden)] -pub trait IntoDynNode<'a, A = ()> { - fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>; -} - -impl<'a> IntoDynNode<'a> for () { - fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - DynamicNode::placeholder() - } -} -impl<'a> IntoDynNode<'a> for VNode<'a> { - fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - DynamicNode::Fragment(_cx.bump().alloc([self])) - } -} -impl<'a> IntoDynNode<'a> for Element<'a> { - fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - match self { - Ok(val) => val.into_vnode(_cx), - _ => DynamicNode::placeholder(), - } - } -} - -impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option { - fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - match self { - Some(val) => val.into_vnode(_cx), - None => DynamicNode::placeholder(), - } - } -} - -impl<'a> IntoDynNode<'a> for &Element<'a> { - fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - match self.as_ref() { - Ok(val) => val.clone().into_vnode(_cx), - _ => DynamicNode::placeholder(), - } - } -} - -impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> { - fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> { - DynamicNode::Fragment(cx.bump().alloc([self.call(cx)])) - } -} - -impl<'a> IntoDynNode<'_> for &'a str { - fn into_vnode(self, cx: &ScopeState) -> DynamicNode { - cx.text_node(format_args!("{}", self)) - } -} - -impl IntoDynNode<'_> for String { - fn into_vnode(self, cx: &ScopeState) -> DynamicNode { - cx.text_node(format_args!("{}", self)) - } -} - -impl<'b> IntoDynNode<'b> for Arguments<'_> { - fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> { - cx.text_node(self) - } -} - -impl<'a> IntoDynNode<'a> for &'a VNode<'a> { - fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - DynamicNode::Fragment(_cx.bump().alloc([VNode { - parent: self.parent, - template: self.template, - root_ids: self.root_ids, - key: self.key, - dynamic_nodes: self.dynamic_nodes, - dynamic_attrs: self.dynamic_attrs, - }])) - } -} - -pub trait IntoTemplate<'a> { - fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>; -} -impl<'a> IntoTemplate<'a> for VNode<'a> { - fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> { - self - } -} -impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> { - fn into_template(self, cx: &'a ScopeState) -> VNode<'a> { - self.call(cx) - } -} - -// Note that we're using the E as a generic but this is never crafted anyways. -#[doc(hidden)] -pub struct FromNodeIterator; -impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T -where - T: Iterator, - I: IntoTemplate<'a>, -{ - fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> { - let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); - - for node in self { - nodes.push(node.into_template(cx)); - } - - let children = nodes.into_bump_slice(); - - match children.len() { - 0 => DynamicNode::placeholder(), - _ => DynamicNode::Fragment(children), - } - } -} - -/// A value that can be converted into an attribute value -pub trait IntoAttributeValue<'a> { - /// Convert into an attribute value - fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>; -} - -impl<'a> IntoAttributeValue<'a> for &'a str { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Text(self) - } -} -impl<'a> IntoAttributeValue<'a> for f32 { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Float(self) - } -} -impl<'a> IntoAttributeValue<'a> for i32 { - 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; - let mut str_buf = bumpalo::collections::String::new_in(bump); - str_buf.write_fmt(self).unwrap(); - AttributeValue::Text(str_buf.into_bump_str()) - } -} diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index ebe9104e1..d5dfd3c50 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -26,17 +26,16 @@ use crate::innerlude::*; /// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>(_cx: Scope<'a, FragmentProps<'a>>) -> Element { - // let children = cx.props.0.as_ref().unwrap(); - todo!() - // Ok(VNode { - // key: children.key.clone(), - // parent: children.parent.clone(), - // template: children.template, - // root_ids: children.root_ids, - // dynamic_nodes: children.dynamic_nodes, - // dynamic_attrs: children.dynamic_attrs, - // }) +pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { + let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?; + Ok(VNode { + key: children.key.clone(), + parent: children.parent.clone(), + template: children.template, + root_ids: children.root_ids, + dynamic_nodes: children.dynamic_nodes, + dynamic_attrs: children.dynamic_attrs, + }) } pub struct FragmentProps<'a>(Element<'a>); @@ -96,7 +95,7 @@ impl<'a> Properties for FragmentProps<'a> { type Builder = FragmentBuilder<'a, false>; const IS_STATIC: bool = false; fn builder() -> Self::Builder { - todo!() + FragmentBuilder(VNode::empty()) } unsafe fn memoize(&self, _other: &Self) -> bool { false diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 35333ca40..020b307d9 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -17,7 +17,7 @@ use std::mem; /// A concrete type provider for closures that build [`VNode`] structures. /// /// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over -/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`]. +/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching. /// /// /// ```rust, ignore diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index c4d10493c..613a4a115 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -9,7 +9,6 @@ mod diff; mod dirty_scope; mod error_boundary; mod events; -mod factory; mod fragment; mod lazynodes; mod mutations; @@ -25,10 +24,10 @@ pub(crate) mod innerlude { pub use crate::dirty_scope::*; pub use crate::error_boundary::*; pub use crate::events::*; - pub use crate::factory::RenderReturn; pub use crate::fragment::*; pub use crate::lazynodes::*; pub use crate::mutations::*; + pub use crate::nodes::RenderReturn; pub use crate::nodes::*; pub use crate::properties::*; pub use crate::scheduler::*; @@ -72,9 +71,9 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event, - Fragment, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, - Scoped, SuspenseBoundary, SuspenseContext, TaskId, Template, TemplateAttribute, TemplateNode, - VComponent, VNode, VText, VirtualDom, + Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, + ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute, + TemplateNode, VComponent, VNode, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 0369c2da8..3687cd956 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -4,9 +4,13 @@ use crate::{arena::ElementId, ScopeId, Template}; /// A container for all the relevant steps to modify the Real DOM /// -/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also -/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has -/// applied the edits. +/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the +/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes +/// in the form of a [`Mutation`]. +/// +/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately. +/// +/// Templates, however, apply to all subtrees, not just target subtree. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. #[derive(Debug, Default)] @@ -32,17 +36,13 @@ impl<'a> Mutations<'a> { /// /// Used really only for testing pub fn santize(mut self) -> Self { - todo!() - // self.templates - // .iter_mut() - // .chain(self.dom_edits.iter_mut()) - // .for_each(|edit| match edit { - // Mutation::LoadTemplate { name, .. } => *name = "template", - // Mutation::SaveTemplate { name, .. } => *name = "template", - // _ => {} - // }); + for edit in self.edits.iter_mut() { + if let Mutation::LoadTemplate { name, .. } = edit { + *name = "template" + } + } - // self + self } /// Push a new mutation into the dom_edits list @@ -51,10 +51,10 @@ impl<'a> Mutations<'a> { } } -/* -each subtree has its own numbering scheme -*/ - +/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state +/// of the Dioxus VirtualDom. +/// +/// These edits can be serialized and sent over the network or through any interface #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -62,37 +62,111 @@ each subtree has its own numbering scheme )] #[derive(Debug, PartialEq, Eq)] pub enum Mutation<'a> { - AssignId { - path: &'static [u8], + /// Add these m children to the target element + AppendChildren { + /// The ID of the element being mounted to id: ElementId, - }, - CreatePlaceholder { - id: ElementId, - }, - CreateTextNode { - value: &'a str, - id: ElementId, - }, - HydrateText { - path: &'static [u8], - value: &'a str, - id: ElementId, - }, - LoadTemplate { - name: &'static str, - index: usize, - id: ElementId, - }, - - // Take the current element and replace it with the element with the given id. - ReplaceWith { - id: ElementId, + /// The number of nodes on the stack m: usize, }, - ReplacePlaceholder { + /// Assign the element at the given path the target ElementId. + /// + /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per + /// element, hence the use of a single byte. + /// + /// + AssignId { + /// The path of the child of the topmost node on the stack + /// + /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child. + /// `[0,1,2]` represents 1st child's 2nd child's 3rd child. path: &'static [u8], + + /// The ID we're assigning to this element/placeholder. + /// + /// This will be used later to modify the element or replace it with another element. + id: ElementId, + }, + + /// Create an placeholder int he DOM that we will use later. + /// + /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing + CreatePlaceholder { + /// The ID we're assigning to this element/placeholder. + /// + /// This will be used later to modify the element or replace it with another element. + id: ElementId, + }, + + /// Create a node specifically for text with the given value + CreateTextNode { + /// The text content of this text node + value: &'a str, + + /// The ID we're assigning to this specific text nodes + /// + /// This will be used later to modify the element or replace it with another element. + id: ElementId, + }, + + /// Hydrate an existing text node at the given path with the given text. + /// + /// Assign this text node the given ID since we will likely need to modify this text at a later point + HydrateText { + /// The path of the child of the topmost node on the stack + /// + /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child. + /// `[0,1,2]` represents 1st child's 2nd child's 3rd child. + path: &'static [u8], + + /// The value of the textnode that we want to set the placeholder with + value: &'a str, + + /// The ID we're assigning to this specific text nodes + /// + /// This will be used later to modify the element or replace it with another element. + id: ElementId, + }, + + /// Load and clone an existing node from a template saved under that specific name + /// + /// Dioxus guarantees that the renderer will have already been provided the template. + /// When the template is picked up in the template list, it should be saved under its "name" - here, the name + LoadTemplate { + /// The "name" of the template. When paired with `rsx!`, this is autogenerated + name: &'static str, + + /// Which root are we loading from the template? + /// + /// The template is stored as a list of nodes. This index represents the position of that root + index: usize, + + /// The ID we're assigning to this element being loaded from the template + /// + /// This will be used later to move the element around in lists + id: ElementId, + }, + + /// Replace the target element (given by its ID) with the topmost m nodes on the stack + ReplaceWith { + /// The ID of the node we're going to replace with + id: ElementId, + + /// The number of nodes on the stack to use to replace + m: usize, + }, + + /// Replace an existing element in the template at the given path with the m nodes on the stack + ReplacePlaceholder { + /// The path of the child of the topmost node on the stack + /// + /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child. + /// `[0,1,2]` represents 1st child's 2nd child's 3rd child. + path: &'static [u8], + + /// The number of nodes on the stack to use to replace m: usize, }, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index df8c96def..27eed58d4 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,12 +1,32 @@ -use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState}; +use crate::{ + any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState, +}; use bumpalo::boxed::Box as BumpBox; +use bumpalo::Bump; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, + fmt::Arguments, + future::Future, }; pub type TemplateId = &'static str; +/// The actual state of the component's most recent computation +/// +/// Because Dioxus accepts components in the form of `async fn(Scope) -> Result`, we need to support both +/// sync and async versions. +/// +/// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor +/// you might need to handle the case where there's no node immediately ready. +pub enum RenderReturn<'a> { + /// A currently-available element + Sync(Element<'a>), + + /// An ongoing future that will resolve to a [`Element`] + Async(BumpBox<'a, dyn Future> + 'a>), +} + /// A reference to a template along with any context needed to hydrate it /// /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping @@ -36,6 +56,7 @@ pub struct VNode<'a> { } impl<'a> VNode<'a> { + /// Create a template with no nodes that will be skipped over during diffing pub fn empty() -> Element<'a> { Ok(VNode { key: None, @@ -44,7 +65,7 @@ impl<'a> VNode<'a> { dynamic_nodes: &[], dynamic_attrs: &[], template: Template { - id: "dioxus-empty", + name: "dioxus-empty", roots: &[], node_paths: &[], attr_paths: &[], @@ -52,6 +73,9 @@ impl<'a> VNode<'a> { }) } + /// Load a dynamic root at the given index + /// + /// Returns [`None`] if the root is actually a static node (Element/Text) pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> { match &self.template.roots[idx] { TemplateNode::Element { .. } | TemplateNode::Text(_) => None, @@ -62,52 +86,139 @@ impl<'a> VNode<'a> { } } +/// A static layout of a UI tree that describes a set of dynamic and static nodes. +/// +/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any +/// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use +/// its static description of the UI to skip immediately to the dynamic nodes during diffing. +/// +/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of +/// ways, with the suggested approach being the unique code location (file, line, col, etc). #[cfg_attr(feature = "serialize", derive(serde::Serialize))] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] pub struct Template<'a> { - pub id: &'a str, + /// The name of the template. This must be unique across your entire program for template diffing to work properly + /// + /// If two templates have the same name, it's likely that Dioxus will panic when diffing. + pub name: &'a str, + + /// The list of template nodes that make up the template + /// + /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm. pub roots: &'a [TemplateNode<'a>], + + /// The paths of each node relative to the root of the template. + /// + /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the + /// topmost element, not the `roots` field. pub node_paths: &'a [&'a [u8]], + + /// The paths of each dynamic attribute relative to the root of the template + /// + /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the + /// topmost element, not the `roots` field. pub attr_paths: &'a [&'a [u8]], } -#[derive(Debug, Clone, Copy)] +/// A statically known node in a layout. +/// +/// This can be created at compile time, saving the VirtualDom time when diffing the tree +#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub enum TemplateNode<'a> { + /// An statically known element in the dom. + /// + /// In HTML this would be something like `
` Element { + /// The name of the element + /// + /// IE for a div, it would be the string "div" tag: &'a str, + + /// The namespace of the element + /// + /// In HTML, this would be a valid URI that defines a namespace for all elements below it + /// SVG is an example of this namespace namespace: Option<&'a str>, + + /// A list of possibly dynamic attribues for this element + /// + /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`. attrs: &'a [TemplateAttribute<'a>], + + /// A list of template nodes that define another set of template nodes children: &'a [TemplateNode<'a>], - inner_opt: bool, }, + + /// This template node is just a piece of static text Text(&'a str), + + /// This template node is unknown, and needs to be created at runtime. Dynamic(usize), + + /// This template node is known to be some text, but needs to be created at runtime + /// + /// This is separate from the pure Dynamic variant for various optimizations DynamicText(usize), } +/// A node created at runtime +/// +/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index #[derive(Debug)] pub enum DynamicNode<'a> { + /// A component node + /// + /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and + /// assigned scope are dynamic. + /// + /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap + /// the render function at runtime Component(VComponent<'a>), + + /// A text node Text(VText<'a>), + + /// A placeholder + /// + /// Used by suspense when a node isn't ready and by fragments that don't render anything + /// + /// In code, this is just an ElementId whose initial value is set to 0 upon creation Placeholder(Cell), + + /// A list of VNodes. + /// + /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering + /// or iterators. Fragment(&'a [VNode<'a>]), } -impl<'a> DynamicNode<'a> { - pub fn is_component(&self) -> bool { - matches!(self, DynamicNode::Component(_)) - } - pub fn placeholder() -> Self { +impl Default for DynamicNode<'_> { + fn default() -> Self { Self::Placeholder(Default::default()) } } +/// An instance of a child component pub struct VComponent<'a> { + /// The name of this component pub name: &'static str, + + /// Are the props valid for the 'static lifetime? + /// + /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it. + /// + /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement pub static_props: bool, + + /// The assigned Scope for this component pub scope: Cell>, + + /// The function pointer of the component, known at compile time + /// + /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key pub render_fn: *const (), + pub(crate) props: Cell + 'a>>>, } @@ -121,65 +232,92 @@ impl<'a> std::fmt::Debug for VComponent<'a> { } } +/// An instance of some text, mounted to the DOM #[derive(Debug)] pub struct VText<'a> { - pub id: Cell, + /// The actual text itself pub value: &'a str, + + /// The ID of this node in the real DOM + pub id: Cell, } -#[derive(Debug)] +/// An attribute of the TemplateNode, created at compile time +#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum TemplateAttribute<'a> { + /// This attribute is entirely known at compile time, enabling Static { + /// The name of this attribute. + /// + /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href" name: &'a str, + + /// The value of this attribute, known at compile time + /// + /// Currently this only accepts &str, so values, even if they're known at compile time, are not known value: &'a str, + + /// The namespace of this attribute. Does not exist in the HTML spec namespace: Option<&'a str>, - volatile: bool, }, + + /// The attribute in this position is actually determined dynamically at runtime + /// + /// This is the index into the dynamic_attributes field on the container VNode Dynamic(usize), } +/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` #[derive(Debug)] pub struct Attribute<'a> { + /// The name of the attribute. pub name: &'a str, + + /// The value of the attribute pub value: AttributeValue<'a>, + + /// The namespace of the attribute. + /// + /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups. pub namespace: Option<&'static str>, + + /// The element in the DOM that this attribute belongs to pub mounted_element: Cell, + + /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated pub volatile: bool, } +/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements +/// +/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`] +/// variant. pub enum AttributeValue<'a> { + /// Text attribute Text(&'a str), - Float(f32), - Int(i32), + + /// A float + Float(f64), + + /// Signed integer + Int(i64), + + /// Boolean Bool(bool), + + /// A listener, like "onclick" Listener(RefCell>>), + + /// An arbitrary value that implements PartialEq and is static Any(BumpBox<'a, dyn AnyValue>), + + /// A "none" value, resulting in the removal of an attribute from the dom None, } type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + 'a>; -impl<'a> AttributeValue<'a> { - pub fn new_listener( - cx: &'a ScopeState, - mut callback: impl FnMut(Event) + 'a, - ) -> AttributeValue<'a> { - let boxed: BumpBox<'a, dyn FnMut(_) + 'a> = unsafe { - BumpBox::from_raw(cx.bump().alloc(move |event: Event| { - if let Ok(data) = event.data.downcast::() { - callback(Event { - propogates: event.propogates, - data, - }) - } - })) - }; - - AttributeValue::Listener(RefCell::new(Some(boxed))) - } -} - impl<'a> std::fmt::Debug for AttributeValue<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -208,20 +346,7 @@ impl<'a> PartialEq for AttributeValue<'a> { } } -impl<'a> AttributeValue<'a> { - pub fn matches_type(&self, other: &'a AttributeValue<'a>) -> bool { - matches!( - (self, other), - (Self::Text(_), Self::Text(_)) - | (Self::Float(_), Self::Float(_)) - | (Self::Int(_), Self::Int(_)) - | (Self::Bool(_), Self::Bool(_)) - | (Self::Listener(_), Self::Listener(_)) - | (Self::Any(_), Self::Any(_)) - ) - } -} - +#[doc(hidden)] pub trait AnyValue { fn any_cmp(&self, other: &dyn AnyValue) -> bool; fn our_typeid(&self) -> TypeId; @@ -241,9 +366,185 @@ impl AnyValue for T { } } -#[test] -fn what_are_the_sizes() { - dbg!(std::mem::size_of::()); - dbg!(std::mem::size_of::