Refactor Mutations and simplify Templates (#578)

* update mutations to be path based rather than renderer integrated
This commit is contained in:
Demonthos 2022-10-18 16:42:45 -05:00 committed by GitHub
parent 020c4bf0f8
commit 3b7b503333
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 2643 additions and 3134 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
use std::{marker::PhantomData, ops::Deref}; use std::{fmt::Write, marker::PhantomData, ops::Deref};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::{ use crate::{
template::{TemplateNodeId, TextTemplateSegment}, template::{TemplateNodeId, TextTemplateSegment},
AttributeValue, Listener, VNode, AttributeValue, Listener, TextTemplate, VNode,
}; };
/// A lazily initailized vector /// A lazily initailized vector
@ -96,28 +96,6 @@ where
nodes_with_listeners: listeners, nodes_with_listeners: listeners,
} }
} }
pub(crate) fn all_dynamic(&self) -> impl Iterator<Item = TemplateNodeId> + '_ {
self.nodes
.as_ref()
.iter()
.filter_map(|o| o.as_ref())
.chain(
self.text
.as_ref()
.iter()
.flat_map(|ids| ids.as_ref().iter()),
)
.copied()
.chain(
self.attributes
.as_ref()
.iter()
.flat_map(|ids| ids.as_ref().iter())
.map(|dynamic| dynamic.0),
)
.chain(self.nodes_with_listeners.as_ref().iter().copied())
}
} }
/// A dynamic node mapping that is stack allocated /// A dynamic node mapping that is stack allocated
@ -161,19 +139,38 @@ pub struct TemplateContext<'b> {
impl<'b> TemplateContext<'b> { impl<'b> TemplateContext<'b> {
/// Resolve text segments to a string /// Resolve text segments to a string
pub fn resolve_text<TextSegments, Text>(&self, text: &TextSegments) -> String pub fn resolve_text<TextSegments, Text>(
&self,
text: &TextTemplate<TextSegments, Text>,
) -> String
where where
TextSegments: AsRef<[TextTemplateSegment<Text>]>, TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>, Text: AsRef<str>,
{ {
let mut result = String::new(); let mut result = String::with_capacity(text.min_size);
for seg in text.as_ref() { self.resolve_text_into(text, &mut result);
result
}
/// Resolve text and writes the result
pub fn resolve_text_into<TextSegments, Text>(
&self,
text: &TextTemplate<TextSegments, Text>,
result: &mut impl Write,
) where
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
for seg in text.segments.as_ref() {
match seg { match seg {
TextTemplateSegment::Static(s) => result += s.as_ref(), TextTemplateSegment::Static(s) => {
TextTemplateSegment::Dynamic(idx) => result += self.text_segments[*idx], let _ = result.write_str(s.as_ref());
}
TextTemplateSegment::Dynamic(idx) => {
let _ = result.write_str(self.text_segments[*idx]);
}
} }
} }
result
} }
/// Resolve an attribute value /// Resolve an attribute value

View file

@ -3,7 +3,7 @@
//! //!
//! This is all kinda WIP, but the bones are there. //! This is all kinda WIP, but the bones are there.
use crate::{nodes::GlobalNodeId, ScopeId}; use crate::{ElementId, ScopeId};
use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc}; use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc};
pub(crate) struct BubbleState { pub(crate) struct BubbleState {
@ -58,7 +58,7 @@ pub struct UserEvent {
pub priority: EventPriority, pub priority: EventPriority,
/// The optional real node associated with the trigger /// The optional real node associated with the trigger
pub element: Option<GlobalNodeId>, pub element: Option<ElementId>,
/// The event type IE "onclick" or "onmouseover" /// The event type IE "onclick" or "onmouseover"
pub name: &'static str, pub name: &'static str,

View file

@ -69,19 +69,19 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{ pub use crate::innerlude::{
AnyEvent, ArbitraryAttributeValue, Attribute, AttributeDiscription, AttributeValue, AnyEvent, ArbitraryAttributeValue, Attribute, AttributeDiscription, AttributeValue,
CodeLocation, Component, DioxusElement, DomEdit, DynamicNodeMapping, Element, ElementId, CodeLocation, Component, DioxusElement, DomEdit, DynamicNodeMapping, Element, ElementId,
ElementIdIterator, EventHandler, EventPriority, GlobalNodeId, IntoAttributeValue, IntoVNode, ElementIdIterator, EventHandler, EventPriority, IntoAttributeValue, IntoVNode, LazyNodes,
LazyNodes, Listener, Mutations, NodeFactory, OwnedAttributeValue, Properties, Listener, Mutations, NodeFactory, OwnedAttributeValue, PathSeg, Properties, RendererTemplateId,
RendererTemplateId, SchedulerMsg, Scope, ScopeId, ScopeState, StaticCodeLocation, SchedulerMsg, Scope, ScopeId, ScopeState, StaticCodeLocation, StaticDynamicNodeMapping,
StaticDynamicNodeMapping, StaticTemplateNode, StaticTemplateNodes, TaskId, Template, StaticPathSeg, StaticTemplateNode, StaticTemplateNodes, StaticTraverse, TaskId, Template,
TemplateAttribute, TemplateAttributeValue, TemplateContext, TemplateElement, TemplateId, TemplateAttribute, TemplateAttributeValue, TemplateContext, TemplateElement, TemplateId,
TemplateNode, TemplateNodeId, TemplateNodeType, TemplateValue, TextTemplate, TemplateNode, TemplateNodeId, TemplateNodeType, TemplateValue, TextTemplate,
TextTemplateSegment, UiEvent, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, TextTemplateSegment, UiEvent, UpdateOp, UserEvent, VComponent, VElement, VFragment, VNode,
VText, VirtualDom, JS_MAX_INT, VPlaceholder, VText, VirtualDom,
}; };
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
pub use crate::innerlude::{ pub use crate::innerlude::{
OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedTemplateNode, OwnedTemplateNodes, OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedPathSeg, OwnedTemplateNode,
SetTemplateMsg, OwnedTemplateNodes, OwnedTraverse, SetTemplateMsg,
}; };
/// 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
@ -95,10 +95,10 @@ pub mod prelude {
fc_to_builder, AttributeDiscription, AttributeValue, Attributes, CodeLocation, Component, fc_to_builder, AttributeDiscription, AttributeValue, Attributes, CodeLocation, Component,
DioxusElement, Element, EventHandler, Fragment, IntoAttributeValue, LazyNodes, DioxusElement, Element, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
LazyStaticVec, NodeFactory, Properties, Scope, ScopeId, ScopeState, StaticAttributeValue, LazyStaticVec, NodeFactory, Properties, Scope, ScopeId, ScopeState, StaticAttributeValue,
StaticCodeLocation, StaticDynamicNodeMapping, StaticTemplate, StaticTemplateNodes, StaticCodeLocation, StaticDynamicNodeMapping, StaticPathSeg, StaticTemplate,
Template, TemplateAttribute, TemplateAttributeValue, TemplateContext, TemplateElement, StaticTemplateNodes, StaticTraverse, Template, TemplateAttribute, TemplateAttributeValue,
TemplateId, TemplateNode, TemplateNodeId, TemplateNodeType, TextTemplate, TemplateContext, TemplateElement, TemplateId, TemplateNode, TemplateNodeId,
TextTemplateSegment, VNode, VirtualDom, TemplateNodeType, TextTemplate, TextTemplateSegment, UpdateOp, VNode, VirtualDom,
}; };
} }

View file

@ -44,57 +44,53 @@ impl Debug for Mutations<'_> {
serde(tag = "type") serde(tag = "type")
)] )]
pub enum DomEdit<'bump> { pub enum DomEdit<'bump> {
/// Push the given root node onto our stack.
PushRoot {
/// The ID of the root node to push.
root: u64,
},
/// Pop the topmost node from our stack and append them to the node /// Pop the topmost node from our stack and append them to the node
/// at the top of the stack. /// at the top of the stack.
AppendChildren { AppendChildren {
/// How many nodes should be popped from the stack. /// The parent to append nodes to.
/// The node remaining on the stack will be the target for the append. root: Option<u64>,
many: u32,
/// The ids of the children to append.
children: Vec<u64>,
}, },
/// Replace a given (single) node with a handful of nodes currently on the stack. /// Replace a given (single) node with a handful of nodes currently on the stack.
ReplaceWith { ReplaceWith {
/// The ID of the node to be replaced. /// The ID of the node to be replaced.
root: u64, root: Option<u64>,
/// How many nodes should be popped from the stack to replace the target node. /// The ids of the nodes to replace the root with.
m: u32, nodes: Vec<u64>,
}, },
/// Insert a number of nodes after a given node. /// Insert a number of nodes after a given node.
InsertAfter { InsertAfter {
/// The ID of the node to insert after. /// The ID of the node to insert after.
root: u64, root: Option<u64>,
/// How many nodes should be popped from the stack to insert after the target node. /// The ids of the nodes to insert after the target node.
n: u32, nodes: Vec<u64>,
}, },
/// Insert a number of nodes before a given node. /// Insert a number of nodes before a given node.
InsertBefore { InsertBefore {
/// The ID of the node to insert before. /// The ID of the node to insert before.
root: u64, root: Option<u64>,
/// How many nodes should be popped from the stack to insert before the target node. /// The ids of the nodes to insert before the target node.
n: u32, nodes: Vec<u64>,
}, },
/// Remove a particular node from the DOM /// Remove a particular node from the DOM
Remove { Remove {
/// The ID of the node to remove. /// The ID of the node to remove.
root: u64, root: Option<u64>,
}, },
/// Create a new purely-text node /// Create a new purely-text node
CreateTextNode { CreateTextNode {
/// The ID the new node should have. /// The ID the new node should have.
root: u64, root: Option<u64>,
/// The textcontent of the node /// The textcontent of the node
text: &'bump str, text: &'bump str,
@ -103,82 +99,35 @@ pub enum DomEdit<'bump> {
/// Create a new purely-element node /// Create a new purely-element node
CreateElement { CreateElement {
/// The ID the new node should have. /// The ID the new node should have.
root: u64, root: Option<u64>,
/// The tagname of the node /// The tagname of the node
tag: &'bump str, tag: &'bump str,
/// The number of children nodes that will follow this message.
children: u32,
}, },
/// Create a new purely-comment node with a given namespace /// Create a new purely-comment node with a given namespace
CreateElementNs { CreateElementNs {
/// The ID the new node should have. /// The ID the new node should have.
root: u64, root: Option<u64>,
/// The namespace of the node /// The namespace of the node
tag: &'bump str, tag: &'bump str,
/// The namespace of the node (like `SVG`) /// The namespace of the node (like `SVG`)
ns: &'static str, ns: &'static str,
/// The number of children nodes that will follow this message.
children: u32,
}, },
/// Create a new placeholder node. /// Create a new placeholder node.
/// In most implementations, this will either be a hidden div or a comment node. /// In most implementations, this will either be a hidden div or a comment node.
CreatePlaceholder { CreatePlaceholder {
/// The ID the new node should have. /// The ID the new node should have.
root: u64, root: Option<u64>,
},
/// Create a new purely-text node in a template
CreateTextNodeTemplate {
/// The ID the new node should have.
root: u64,
/// The textcontent of the noden
text: &'bump str,
/// If the id of the node must be kept in the refrences
locally_static: bool,
},
/// Create a new purely-element node in a template
CreateElementTemplate {
/// The ID the new node should have.
root: u64,
/// The tagname of the node
tag: &'bump str,
/// If the id of the node must be kept in the refrences
locally_static: bool,
/// If any children of this node must be kept in the references
fully_static: bool,
},
/// Create a new purely-comment node with a given namespace in a template
CreateElementNsTemplate {
/// The ID the new node should have.
root: u64,
/// The namespace of the node
tag: &'bump str,
/// The namespace of the node (like `SVG`)
ns: &'static str,
/// If the id of the node must be kept in the refrences
locally_static: bool,
/// If any children of this node must be kept in the references
fully_static: bool,
},
/// Create a new placeholder node.
/// In most implementations, this will either be a hidden div or a comment node. in a template
/// Always both locally and fully static
CreatePlaceholderTemplate {
/// The ID the new node should have.
root: u64,
}, },
/// Create a new Event Listener. /// Create a new Event Listener.
@ -190,13 +139,13 @@ pub enum DomEdit<'bump> {
scope: ScopeId, scope: ScopeId,
/// The ID of the node to attach the listener to. /// The ID of the node to attach the listener to.
root: u64, root: Option<u64>,
}, },
/// Remove an existing Event Listener. /// Remove an existing Event Listener.
RemoveEventListener { RemoveEventListener {
/// The ID of the node to remove. /// The ID of the node to remove.
root: u64, root: Option<u64>,
/// The name of the event to remove. /// The name of the event to remove.
event: &'static str, event: &'static str,
@ -205,7 +154,7 @@ pub enum DomEdit<'bump> {
/// Set the textcontent of a node. /// Set the textcontent of a node.
SetText { SetText {
/// The ID of the node to set the textcontent of. /// The ID of the node to set the textcontent of.
root: u64, root: Option<u64>,
/// The textcontent of the node /// The textcontent of the node
text: &'bump str, text: &'bump str,
@ -214,7 +163,7 @@ pub enum DomEdit<'bump> {
/// Set the value of a node's attribute. /// Set the value of a node's attribute.
SetAttribute { SetAttribute {
/// The ID of the node to set the attribute of. /// The ID of the node to set the attribute of.
root: u64, root: Option<u64>,
/// The name of the attribute to set. /// The name of the attribute to set.
field: &'static str, field: &'static str,
@ -231,7 +180,7 @@ pub enum DomEdit<'bump> {
/// Remove an attribute from a node. /// Remove an attribute from a node.
RemoveAttribute { RemoveAttribute {
/// The ID of the node to remove. /// The ID of the node to remove.
root: u64, root: Option<u64>,
/// The name of the attribute to remove. /// The name of the attribute to remove.
name: &'static str, name: &'static str,
@ -240,45 +189,50 @@ pub enum DomEdit<'bump> {
ns: Option<&'bump str>, ns: Option<&'bump str>,
}, },
/// Manually pop a root node from the stack. /// Clones a node.
PopRoot {}, CloneNode {
/// The ID of the node to clone.
id: Option<u64>,
/// Enter a TemplateRef tree /// The ID of the new node.
EnterTemplateRef { new_id: u64,
/// The ID of the node to enter.
root: u64,
}, },
/// Exit a TemplateRef tree /// Clones the children of a node. (allows cloning fragments)
ExitTemplateRef {}, CloneNodeChildren {
/// The ID of the node to clone.
id: Option<u64>,
/// Create a refrence to a template node. /// The ID of the new node.
CreateTemplateRef { new_ids: Vec<u64>,
/// The ID of the new template refrence.
id: u64,
/// The ID of the template the node is refrencing.
template_id: u64,
}, },
/// Create a new templete. /// Navigates to the last node to the first child of the current node.
/// IMPORTANT: When adding nodes to a templete, id's will reset to zero, so they must be allocated on a different stack. FirstChild {},
/// It is recommended to use Cow<NativeNode>.
CreateTemplate { /// Navigates to the last node to the last child of the current node.
/// The ID of the new template. NextSibling {},
/// Navigates to the last node to the parent of the current node.
ParentNode {},
/// Stores the last node with a new id.
StoreWithId {
/// The ID of the node to store.
id: u64, id: u64,
}, },
/// Finish a templete /// Manually set the last node.
FinishTemplate { SetLastNode {
/// The number of root nodes in the template /// The ID to set the last node to.
len: u32, id: u64,
}, },
} }
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use DomEdit::*; use DomEdit::*;
#[allow(unused)]
impl<'a> Mutations<'a> { impl<'a> Mutations<'a> {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
@ -288,44 +242,29 @@ impl<'a> Mutations<'a> {
} }
} }
// Navigation pub(crate) fn replace_with(&mut self, root: Option<u64>, nodes: Vec<u64>) {
pub(crate) fn push_root(&mut self, root: impl Into<u64>) { self.edits.push(ReplaceWith { nodes, root });
let id = root.into();
self.edits.push(PushRoot { root: id });
} }
// Navigation pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
pub(crate) fn pop_root(&mut self) { self.edits.push(InsertAfter { nodes, root });
self.edits.push(PopRoot {});
} }
pub(crate) fn replace_with(&mut self, root: impl Into<u64>, m: u32) { pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
let root = root.into(); self.edits.push(InsertBefore { nodes, root });
self.edits.push(ReplaceWith { m, root });
} }
pub(crate) fn insert_after(&mut self, root: impl Into<u64>, n: u32) { pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
let root = root.into(); self.edits.push(AppendChildren { root, children });
self.edits.push(InsertAfter { n, root });
}
pub(crate) fn insert_before(&mut self, root: impl Into<u64>, n: u32) {
let root = root.into();
self.edits.push(InsertBefore { n, root });
}
pub(crate) fn append_children(&mut self, n: u32) {
self.edits.push(AppendChildren { many: n });
} }
// Remove Nodes from the dom // Remove Nodes from the dom
pub(crate) fn remove(&mut self, id: impl Into<u64>) { pub(crate) fn remove(&mut self, id: Option<u64>) {
self.edits.push(Remove { root: id.into() }); self.edits.push(Remove { root: id });
} }
// Create // Create
pub(crate) fn create_text_node(&mut self, text: &'a str, id: impl Into<u64>) { pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
let id = id.into();
self.edits.push(CreateTextNode { text, root: id }); self.edits.push(CreateTextNode { text, root: id });
} }
@ -333,66 +272,27 @@ impl<'a> Mutations<'a> {
&mut self, &mut self,
tag: &'static str, tag: &'static str,
ns: Option<&'static str>, ns: Option<&'static str>,
id: impl Into<u64>, id: Option<u64>,
children: u32,
) { ) {
let id = id.into();
match ns { match ns {
Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }), Some(ns) => self.edits.push(CreateElementNs {
None => self.edits.push(CreateElement { root: id, tag }),
}
}
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
pub(crate) fn create_placeholder(&mut self, id: impl Into<u64>) {
let id = id.into();
self.edits.push(CreatePlaceholder { root: id });
}
// Create
pub(crate) fn create_text_node_template(
&mut self,
text: &'a str,
id: impl Into<u64>,
locally_static: bool,
) {
let id = id.into();
self.edits.push(CreateTextNodeTemplate {
text,
root: id,
locally_static,
});
}
pub(crate) fn create_element_template(
&mut self,
tag: &'static str,
ns: Option<&'static str>,
id: impl Into<u64>,
locally_static: bool,
fully_static: bool,
) {
let id = id.into();
match ns {
Some(ns) => self.edits.push(CreateElementNsTemplate {
root: id, root: id,
ns, ns,
tag, tag,
locally_static, children,
fully_static,
}), }),
None => self.edits.push(CreateElementTemplate { None => self.edits.push(CreateElement {
root: id, root: id,
tag, tag,
locally_static, children,
fully_static,
}), }),
} }
} }
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
pub(crate) fn create_placeholder_template(&mut self, id: impl Into<u64>) { pub(crate) fn create_placeholder(&mut self, id: Option<u64>) {
let id = id.into(); self.edits.push(CreatePlaceholder { root: id });
self.edits.push(CreatePlaceholderTemplate { root: id });
} }
// events // events
@ -403,13 +303,7 @@ impl<'a> Mutations<'a> {
.. ..
} = listener; } = listener;
let element_id = match mounted_node.get().unwrap() { let element_id = Some(mounted_node.get().unwrap().into());
GlobalNodeId::TemplateId {
template_ref_id: _,
template_node_id,
} => template_node_id.into(),
GlobalNodeId::VNodeId(id) => id.into(),
};
self.edits.push(NewEventListener { self.edits.push(NewEventListener {
scope, scope,
@ -418,21 +312,16 @@ impl<'a> Mutations<'a> {
}); });
} }
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: impl Into<u64>) { pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option<u64>) {
self.edits.push(RemoveEventListener { self.edits.push(RemoveEventListener { event, root });
event,
root: root.into(),
});
} }
// modify // modify
pub(crate) fn set_text(&mut self, text: &'a str, root: impl Into<u64>) { pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
let root = root.into();
self.edits.push(SetText { text, root }); self.edits.push(SetText { text, root });
} }
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: impl Into<u64>) { pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
let root = root.into();
let Attribute { let Attribute {
value, attribute, .. value, attribute, ..
} = attribute; } = attribute;
@ -445,8 +334,7 @@ impl<'a> Mutations<'a> {
}); });
} }
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: impl Into<u64>) { pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option<u64>) {
let root = root.into();
let Attribute { attribute, .. } = attribute; let Attribute { attribute, .. } = attribute;
self.edits.push(RemoveAttribute { self.edits.push(RemoveAttribute {
@ -460,31 +348,32 @@ impl<'a> Mutations<'a> {
self.dirty_scopes.insert(scope); self.dirty_scopes.insert(scope);
} }
pub(crate) fn create_templete(&mut self, id: impl Into<u64>) { pub(crate) fn clone_node(&mut self, id: Option<u64>, new_id: u64) {
self.edits.push(CreateTemplate { id: id.into() }); self.edits.push(CloneNode { id, new_id });
} }
pub(crate) fn finish_templete(&mut self, len: u32) { pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
self.edits.push(FinishTemplate { len }); self.edits.push(CloneNodeChildren { id, new_ids });
} }
pub(crate) fn create_template_ref(&mut self, id: impl Into<u64>, template_id: u64) { pub(crate) fn first_child(&mut self) {
self.edits.push(CreateTemplateRef { self.edits.push(FirstChild {});
id: id.into(),
template_id,
})
} }
pub(crate) fn enter_template_ref(&mut self, id: impl Into<u64>) { pub(crate) fn next_sibling(&mut self) {
self.edits.push(EnterTemplateRef { root: id.into() }); self.edits.push(NextSibling {});
} }
pub(crate) fn exit_template_ref(&mut self) { pub(crate) fn parent_node(&mut self) {
if let Some(&DomEdit::EnterTemplateRef { .. }) = self.edits.last() { self.edits.push(ParentNode {});
self.edits.pop(); }
} else {
self.edits.push(ExitTemplateRef {}); pub(crate) fn store_with_id(&mut self, id: u64) {
} self.edits.push(StoreWithId { id });
}
pub(crate) fn set_last_node(&mut self, id: u64) {
self.edits.push(SetLastNode { id });
} }
} }

View file

@ -9,64 +9,16 @@ use crate::{
ScopeState, Template, TemplateId, ScopeState, Template, TemplateId,
}, },
lazynodes::LazyNodes, lazynodes::LazyNodes,
template::{TemplateNodeId, VTemplateRef}, template::VTemplateRef,
AnyEvent, Component, AnyEvent, Component,
}; };
use bumpalo::{boxed::Box as BumpBox, Bump}; use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
fmt::{Arguments, Debug, Formatter}, fmt::{Arguments, Debug, Formatter},
num::ParseIntError,
rc::Rc, rc::Rc,
str::FromStr,
}; };
/// The ID of a node in the vdom that is either standalone or in a template
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
pub enum GlobalNodeId {
/// The ID of a node and the template that contains it
TemplateId {
/// The template that contains the node
template_ref_id: ElementId,
/// The ID of the node in the template
template_node_id: TemplateNodeId,
},
/// The ID of a regular node
VNodeId(ElementId),
}
impl From<ElementId> for GlobalNodeId {
fn from(id: ElementId) -> GlobalNodeId {
GlobalNodeId::VNodeId(id)
}
}
impl PartialEq<ElementId> for GlobalNodeId {
fn eq(&self, other: &ElementId) -> bool {
match self {
GlobalNodeId::TemplateId { .. } => false,
GlobalNodeId::VNodeId(id) => id == other,
}
}
}
impl FromStr for GlobalNodeId {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((tmpl_id, node_id)) = s.split_once(',') {
Ok(GlobalNodeId::TemplateId {
template_ref_id: ElementId(tmpl_id.parse()?),
template_node_id: TemplateNodeId(node_id.parse()?),
})
} else {
Ok(GlobalNodeId::VNodeId(ElementId(s.parse()?)))
}
}
}
/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
/// ///
/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
@ -201,7 +153,7 @@ impl<'src> VNode<'src> {
VNode::Placeholder(el) => el.id.get(), VNode::Placeholder(el) => el.id.get(),
VNode::Fragment(_) => None, VNode::Fragment(_) => None,
VNode::Component(_) => None, VNode::Component(_) => None,
VNode::TemplateRef(t) => t.id.get(), VNode::TemplateRef(_) => None,
} }
} }
@ -252,7 +204,6 @@ impl Debug for VNode<'_> {
VNode::TemplateRef(temp) => s VNode::TemplateRef(temp) => s
.debug_struct("VNode::TemplateRef") .debug_struct("VNode::TemplateRef")
.field("template_id", &temp.template_id) .field("template_id", &temp.template_id)
.field("id", &temp.id)
.finish(), .finish(),
} }
} }
@ -340,7 +291,7 @@ pub struct VElement<'a> {
/// The parent of the Element (if any). /// The parent of the Element (if any).
/// ///
/// Used when bubbling events /// Used when bubbling events
pub parent: Cell<Option<GlobalNodeId>>, pub parent: Cell<Option<ElementId>>,
/// The Listeners of the VElement. /// The Listeners of the VElement.
pub listeners: &'a [Listener<'a>], pub listeners: &'a [Listener<'a>],
@ -456,7 +407,7 @@ pub struct Attribute<'a> {
pub struct Listener<'bump> { pub struct Listener<'bump> {
/// The ID of the node that this listener is mounted to /// The ID of the node that this listener is mounted to
/// Used to generate the event listener's ID on the DOM /// Used to generate the event listener's ID on the DOM
pub mounted_node: Cell<Option<GlobalNodeId>>, pub mounted_node: Cell<Option<ElementId>>,
/// The type of event to listen for. /// The type of event to listen for.
/// ///
@ -515,9 +466,11 @@ impl<'a, T> Default for EventHandler<'a, T> {
impl<T> EventHandler<'_, T> { impl<T> EventHandler<'_, T> {
/// Call this event handler with the appropriate event type /// Call this event handler with the appropriate event type
pub fn call(&self, event: T) { pub fn call(&self, event: T) {
log::trace!("calling event handler");
if let Some(callback) = self.callback.borrow_mut().as_mut() { if let Some(callback) = self.callback.borrow_mut().as_mut() {
callback(event); callback(event);
} }
log::trace!("done");
} }
/// Forcibly drop the internal handler callback, releasing memory /// Forcibly drop the internal handler callback, releasing memory
@ -870,9 +823,11 @@ impl<'a> NodeFactory<'a> {
borrow_mut.insert(id.clone(), Rc::new(RefCell::new(template))); borrow_mut.insert(id.clone(), Rc::new(RefCell::new(template)));
} }
VNode::TemplateRef(self.bump.alloc(VTemplateRef { VNode::TemplateRef(self.bump.alloc(VTemplateRef {
id: empty_cell(),
dynamic_context, dynamic_context,
template_id: id, template_id: id,
node_ids: RefCell::new(Vec::new()),
parent: Cell::new(None),
template_ref_id: Cell::new(None),
})) }))
} }
} }

View file

@ -1,19 +1,11 @@
use crate::{ use crate::{innerlude::*, template::TemplateNodeId, unsafe_utils::extend_vnode};
dynamic_template_context::TemplateContext,
innerlude::*,
template::{
TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId, TemplateNodeType,
TemplateValue, TextTemplateSegment,
},
unsafe_utils::extend_vnode,
};
use bumpalo::Bump; use bumpalo::Bump;
use futures_channel::mpsc::UnboundedSender; use futures_channel::mpsc::UnboundedSender;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use slab::Slab; use slab::Slab;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::{Cell, Ref, RefCell}, cell::{Cell, RefCell},
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
future::Future, future::Future,
pin::Pin, pin::Pin,
@ -21,6 +13,17 @@ use std::{
sync::Arc, sync::Arc,
}; };
pub(crate) enum NodePtr {
VNode(*const VNode<'static>),
TemplateNode {
template_ref: TemplateRefId,
node_id: TemplateNodeId,
},
Phantom,
}
pub(crate) type NodeSlab = Slab<NodePtr>;
/// for traceability, we use the raw fn pointer to identify the function /// for traceability, we use the raw fn pointer to identify the function
/// we also get the component name, but that's not necessarily unique in the app /// we also get the component name, but that's not necessarily unique in the app
pub(crate) type ComponentPtr = *mut std::os::raw::c_void; pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
@ -40,12 +43,16 @@ pub(crate) struct ScopeArena {
pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>, pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>, pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
pub free_scopes: RefCell<Vec<*mut ScopeState>>, pub free_scopes: RefCell<Vec<*mut ScopeState>>,
pub nodes: RefCell<Slab<*const VNode<'static>>>, // All nodes are stored here. This mimics the allocations needed on the renderer side.
// Some allocations are needed on the renderer to render nodes in templates that are
// not needed in dioxus, for these allocations None is used so that the id is preseved and passive memory managment is preserved.
pub nodes: RefCell<NodeSlab>,
pub tasks: Rc<TaskQueue>, pub tasks: Rc<TaskQueue>,
pub template_resolver: RefCell<TemplateResolver>, pub template_resolver: RefCell<TemplateResolver>,
pub templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>, pub templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>,
// this is used to store intermidiate artifacts of creating templates, so that the lifetime aligns with Mutations<'bump>. // this is used to store intermidiate artifacts of creating templates, so that the lifetime aligns with Mutations<'bump>.
pub template_bump: Bump, pub template_bump: Bump,
pub template_refs: RefCell<Slab<*const VTemplateRef<'static>>>,
} }
impl ScopeArena { impl ScopeArena {
@ -68,7 +75,9 @@ impl ScopeArena {
let node = bump.alloc(VNode::Element(el)); let node = bump.alloc(VNode::Element(el));
let mut nodes = Slab::new(); let mut nodes = Slab::new();
let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) }); let root_id = nodes.insert(NodePtr::VNode(unsafe {
std::mem::transmute(node as *const _)
}));
debug_assert_eq!(root_id, 0); debug_assert_eq!(root_id, 0);
@ -88,6 +97,7 @@ impl ScopeArena {
template_resolver: RefCell::new(TemplateResolver::default()), template_resolver: RefCell::new(TemplateResolver::default()),
templates: Rc::new(RefCell::new(FxHashMap::default())), templates: Rc::new(RefCell::new(FxHashMap::default())),
template_bump: Bump::new(), template_bump: Bump::new(),
template_refs: RefCell::new(Slab::new()),
} }
} }
@ -107,7 +117,7 @@ impl ScopeArena {
fc_ptr: ComponentPtr, fc_ptr: ComponentPtr,
vcomp: Box<dyn AnyProps>, vcomp: Box<dyn AnyProps>,
parent_scope: Option<ScopeId>, parent_scope: Option<ScopeId>,
container: GlobalNodeId, container: ElementId,
) -> ScopeId { ) -> ScopeId {
// Increment the ScopeId system. ScopeIDs are never reused // Increment the ScopeId system. ScopeIDs are never reused
let new_scope_id = ScopeId(self.scope_gen.get()); let new_scope_id = ScopeId(self.scope_gen.get());
@ -217,13 +227,61 @@ impl ScopeArena {
let key = entry.key(); let key = entry.key();
let id = ElementId(key); let id = ElementId(key);
let node = unsafe { extend_vnode(node) }; let node = unsafe { extend_vnode(node) };
entry.insert(node as *const _); entry.insert(NodePtr::VNode(node as *const _));
id
}
pub fn reserve_template_ref<'a>(&self, template_ref: &'a VTemplateRef<'a>) -> TemplateRefId {
let mut refs = self.template_refs.borrow_mut();
let entry = refs.vacant_entry();
let key = entry.key();
let id = TemplateRefId(key);
let static_ref: &VTemplateRef<'static> = unsafe { std::mem::transmute(template_ref) };
entry.insert(static_ref as *const _);
id
}
pub fn reserve_template_node(
&self,
template_ref_id: TemplateRefId,
node_id: TemplateNodeId,
) -> ElementId {
let mut els = self.nodes.borrow_mut();
let entry = els.vacant_entry();
let key = entry.key();
let id = ElementId(key);
entry.insert(NodePtr::TemplateNode {
template_ref: template_ref_id,
node_id,
});
id
}
pub fn reserve_phantom_node(&self) -> ElementId {
let mut els = self.nodes.borrow_mut();
let entry = els.vacant_entry();
let key = entry.key();
let id = ElementId(key);
entry.insert(NodePtr::Phantom);
id id
} }
pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) { pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
let node = unsafe { extend_vnode(node) }; let node = unsafe { extend_vnode(node) };
*self.nodes.borrow_mut().get_mut(id.0).unwrap() = node; *self.nodes.borrow_mut().get_mut(id.0).unwrap() = NodePtr::VNode(node);
}
pub fn update_template_ref<'a>(
&self,
template_ref_id: TemplateRefId,
template_ref: &'a VTemplateRef<'a>,
) {
let template_ref = unsafe { std::mem::transmute(template_ref) };
*self
.template_refs
.borrow_mut()
.get_mut(template_ref_id.0)
.unwrap() = template_ref;
} }
pub fn collect_garbage(&self, id: ElementId) { pub fn collect_garbage(&self, id: ElementId) {
@ -324,7 +382,7 @@ impl ScopeArena {
scope.cycle_frame(); scope.cycle_frame();
} }
pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: GlobalNodeId) { pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) {
let nodes = self.nodes.borrow(); let nodes = self.nodes.borrow();
let mut cur_el = Some(element); let mut cur_el = Some(element);
@ -335,39 +393,10 @@ impl ScopeArena {
// stop bubbling if canceled // stop bubbling if canceled
return; return;
} }
match id { if let Some(ptr) = nodes.get(id.0) {
GlobalNodeId::TemplateId { match ptr {
template_ref_id, NodePtr::VNode(ptr) => {
template_node_id, let real_el = unsafe { &**ptr };
} => {
log::trace!(
"looking for listener in {:?} in node {:?}",
template_ref_id,
template_node_id
);
if let Some(template) = nodes.get(template_ref_id.0) {
let template = unsafe { &**template };
if let VNode::TemplateRef(template_ref) = template {
let templates = self.templates.borrow();
let template = templates.get(&template_ref.template_id).unwrap();
cur_el = template.borrow().with_node(
template_node_id,
bubble_template,
bubble_template,
(
&nodes,
&template_ref.dynamic_context,
event,
&state,
template_ref_id,
),
);
}
}
}
GlobalNodeId::VNodeId(id) => {
if let Some(el) = nodes.get(id.0) {
let real_el = unsafe { &**el };
log::trace!("looking for listener on {:?}", real_el); log::trace!("looking for listener on {:?}", real_el);
if let VNode::Element(real_el) = real_el { if let VNode::Element(real_el) = real_el {
@ -393,24 +422,42 @@ impl ScopeArena {
cur_el = real_el.parent.get(); cur_el = real_el.parent.get();
} }
} }
NodePtr::TemplateNode {
node_id,
template_ref,
} => {
let template_refs = self.template_refs.borrow();
let template_ptr = template_refs.get(template_ref.0).unwrap();
let template_ref = unsafe { &**template_ptr };
log::trace!("looking for listener in node {:?}", node_id);
let templates = self.templates.borrow();
let template = templates.get(&template_ref.template_id).unwrap();
cur_el = template.borrow().with_nodes(
bubble_template,
bubble_template,
(*node_id, template_ref, event, &state),
);
}
_ => panic!("Expected Real Node"),
} }
} }
if !event.bubbles { if !event.bubbles {
return; return;
} }
} }
fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text>( fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text, Nodes>(
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>, nodes: &Nodes,
ctx: ( ctx: (
&Ref<Slab<*const VNode>>, TemplateNodeId,
&TemplateContext<'b>, &VTemplateRef<'b>,
&UserEvent, &UserEvent,
&Rc<BubbleState>, &Rc<BubbleState>,
ElementId,
), ),
) -> Option<GlobalNodeId> ) -> Option<ElementId>
where where
Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
Attributes: AsRef<[TemplateAttribute<V>]>, Attributes: AsRef<[TemplateAttribute<V>]>,
V: TemplateValue, V: TemplateValue,
Children: AsRef<[TemplateNodeId]>, Children: AsRef<[TemplateNodeId]>,
@ -418,46 +465,40 @@ impl ScopeArena {
TextSegments: AsRef<[TextTemplateSegment<Text>]>, TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>, Text: AsRef<str>,
{ {
let (vnodes, dynamic_context, event, state, template_ref_id) = ctx; let (start, template_ref, event, state) = ctx;
if let TemplateNodeType::Element(el) = &node.node_type { let dynamic_context = &template_ref.dynamic_context;
let TemplateElement { listeners, .. } = el; let mut current = &nodes.as_ref()[start.0];
for listener_idx in listeners.as_ref() { loop {
let listener = dynamic_context.resolve_listener(*listener_idx); if let TemplateNodeType::Element(el) = &current.node_type {
if listener.event == event.name { let TemplateElement { listeners, .. } = el;
log::trace!("calling listener {:?}", listener.event); for listener_idx in listeners.as_ref() {
let listener = dynamic_context.resolve_listener(*listener_idx);
if listener.event == event.name {
log::trace!("calling listener {:?}", listener.event);
let mut cb = listener.callback.borrow_mut(); let mut cb = listener.callback.borrow_mut();
if let Some(cb) = cb.as_mut() { if let Some(cb) = cb.as_mut() {
// todo: arcs are pretty heavy to clone // todo: arcs are pretty heavy to clone
// we really want to convert arc to rc // we really want to convert arc to rc
// unfortunately, the SchedulerMsg must be send/sync to be sent across threads // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
// we could convert arc to rc internally or something // we could convert arc to rc internally or something
(cb)(AnyEvent { (cb)(AnyEvent {
bubble_state: state.clone(), bubble_state: state.clone(),
data: event.data.clone(), data: event.data.clone(),
}); });
}
break;
} }
break;
} }
}
if let Some(id) = el.parent { if let Some(id) = current.parent {
Some(GlobalNodeId::TemplateId { current = &nodes.as_ref()[id.0];
template_ref_id, } else {
template_node_id: id, return template_ref.parent.get();
}) }
} else { } else {
vnodes.get(template_ref_id.0).and_then(|el| { return None;
let real_el = unsafe { &**el };
if let VNode::Element(real_el) = real_el {
real_el.parent.get()
} else {
None
}
})
} }
} else {
None
} }
} }
} }
@ -484,11 +525,10 @@ impl ScopeArena {
// this is totally okay since all our nodes are always in a valid state // this is totally okay since all our nodes are always in a valid state
pub fn get_element(&self, id: ElementId) -> Option<&VNode> { pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
self.nodes self.nodes.borrow().get(id.0).and_then(|ptr| match ptr {
.borrow() NodePtr::VNode(ptr) => Some(unsafe { extend_vnode(&**ptr) }),
.get(id.0) _ => None,
.copied() })
.map(|ptr| unsafe { extend_vnode(&*ptr) })
} }
} }
@ -569,7 +609,7 @@ pub struct TaskId {
/// use case they might have. /// use case they might have.
pub struct ScopeState { pub struct ScopeState {
pub(crate) parent_scope: Option<*mut ScopeState>, pub(crate) parent_scope: Option<*mut ScopeState>,
pub(crate) container: GlobalNodeId, pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId, pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32, pub(crate) height: u32,
pub(crate) fnptr: ComponentPtr, pub(crate) fnptr: ComponentPtr,

View file

@ -51,26 +51,30 @@
//! To minimize the cost of allowing hot reloading on applications that do not use it there are &'static and owned versions of template nodes, and dynamic node mapping. //! To minimize the cost of allowing hot reloading on applications that do not use it there are &'static and owned versions of template nodes, and dynamic node mapping.
//! //!
//! Notes: //! Notes:
//! 1) Why does the template need to exist outside of the virtual dom? //! 1) The template allow diffing to scale with reactivity.
//! The main use of the template is skipping diffing on static parts of the dom, but it is also used to make renderes more efficient. Renderers can create a template once and the clone it into place.
//! When the renderers clone the template we could those new nodes as normal vnodes, but that would interfere with the passive memory management of the nodes. This would mean that static nodes memory must be managed by the virtual dom even though those static nodes do not exist in the virtual dom.
//! 2) The template allow diffing to scale with reactivity.
//! With a virtual dom the diffing cost scales with the number of nodes in the dom. With templates the cost scales with the number of dynamic parts of the dom. The dynamic template context links any parts of the template that can change which allows the diffing algorithm to skip traversing the template and find what part to hydrate in constant time. //! With a virtual dom the diffing cost scales with the number of nodes in the dom. With templates the cost scales with the number of dynamic parts of the dom. The dynamic template context links any parts of the template that can change which allows the diffing algorithm to skip traversing the template and find what part to hydrate in constant time.
/// The maxiumum integer in JS use once_cell::unsync::OnceCell;
pub const JS_MAX_INT: u64 = 9007199254740991; use std::{
cell::{Cell, RefCell},
hash::Hash,
marker::PhantomData,
ptr,
};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::{cell::Cell, hash::Hash, marker::PhantomData, ops::Index};
use bumpalo::Bump; use bumpalo::Bump;
use crate::{ use crate::{
diff::DiffState, dynamic_template_context::TemplateContext, innerlude::GlobalNodeId, diff::DiffState, dynamic_template_context::TemplateContext, nodes::AttributeDiscription,
nodes::AttributeDiscription, Attribute, AttributeValue, ElementId, Mutations, scopes::ScopeArena, Attribute, AttributeValue, ElementId, Mutations, OwnedAttributeValue,
OwnedAttributeValue, StaticDynamicNodeMapping, OwnedDynamicNodeMapping, StaticDynamicNodeMapping,
}; };
#[derive(Debug, Clone, Copy)]
pub(crate) struct TemplateRefId(pub usize);
/// The location of a charicter. Used to track the location of rsx calls for hot reloading. /// The location of a charicter. Used to track the location of rsx calls for hot reloading.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr( #[cfg_attr(
@ -140,10 +144,8 @@ impl Hash for CodeLocation {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self { match self {
CodeLocation::Static(loc) => { CodeLocation::Static(loc) => {
loc.crate_path.hash(state); let loc: &'static _ = *loc;
loc.file_path.hash(state); state.write_usize((loc as *const _) as usize);
state.write_u32(loc.line);
state.write_u32(loc.column);
} }
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => { CodeLocation::Dynamic(loc) => {
@ -160,7 +162,7 @@ impl Hash for CodeLocation {
impl PartialEq for CodeLocation { impl PartialEq for CodeLocation {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match (self, other) { match (self, other) {
(Self::Static(l), Self::Static(r)) => l == r, (Self::Static(l), Self::Static(r)) => ptr::eq(*l, *r),
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
(Self::Dynamic(l), Self::Dynamic(r)) => l == r, (Self::Dynamic(l), Self::Dynamic(r)) => l == r,
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
@ -278,25 +280,161 @@ impl From<RendererTemplateId> for u64 {
#[cfg_attr(feature = "serialize", serde(transparent))] #[cfg_attr(feature = "serialize", serde(transparent))]
pub struct TemplateNodeId(pub usize); pub struct TemplateNodeId(pub usize);
impl From<TemplateNodeId> for u64 {
fn from(id: TemplateNodeId) -> u64 {
JS_MAX_INT / 2 + id.0 as u64
}
}
/// A refrence to a template along with any context needed to hydrate it /// A refrence to a template along with any context needed to hydrate it
pub struct VTemplateRef<'a> { pub struct VTemplateRef<'a> {
pub id: Cell<Option<ElementId>>, pub(crate) template_ref_id: Cell<Option<TemplateRefId>>,
pub template_id: TemplateId, pub template_id: TemplateId,
pub dynamic_context: TemplateContext<'a>, pub dynamic_context: TemplateContext<'a>,
/// The parent of the template
pub(crate) parent: Cell<Option<ElementId>>,
// any nodes that already have ids assigned to them in the renderer
pub node_ids: RefCell<Vec<OnceCell<ElementId>>>,
} }
impl<'a> VTemplateRef<'a> { impl<'a> VTemplateRef<'a> {
// update the template with content from the dynamic context // update the template with content from the dynamic context
pub(crate) fn hydrate<'b>(&self, template: &'b Template, diff_state: &mut DiffState<'a>) { pub(crate) fn hydrate<'b: 'a>(
&'b self,
parent: ElementId,
template: &Template,
diff_state: &mut DiffState<'a>,
) {
fn traverse_seg<'b, T, O, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
seg: &PathSeg<T, O>,
nodes: &Nodes,
diff_state: &mut DiffState<'b>,
template_ref: &'b VTemplateRef<'b>,
parent: ElementId,
) where
Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
Attributes: AsRef<[TemplateAttribute<V>]>,
V: TemplateValue,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
T: Traversable<O>,
O: AsRef<[UpdateOp]>,
{
let mut current_node_id = None;
let mut temp_id = false;
for op in seg.ops.as_ref() {
match op {
UpdateOp::StoreNode(id) => {
if let Some(real_id) = template_ref.try_get_node_id(*id) {
current_node_id = Some(real_id);
nodes.as_ref()[id.0].hydrate(real_id, diff_state, template_ref);
} else {
let real_id = diff_state.scopes.reserve_template_node(
template_ref.template_ref_id.get().unwrap(),
*id,
);
current_node_id = Some(real_id);
template_ref.set_node_id(*id, real_id);
diff_state.mutations.store_with_id(real_id.as_u64());
nodes.as_ref()[id.0].hydrate(real_id, diff_state, template_ref);
}
}
UpdateOp::AppendChild(id) => {
let node = &nodes.as_ref()[id.0];
match &node.node_type {
TemplateNodeType::DynamicNode(idx) => {
if current_node_id.is_none() {
// create a temporary node to come back to later
let id = diff_state.scopes.reserve_phantom_node();
diff_state.mutations.store_with_id(id.as_u64());
temp_id = true;
current_node_id = Some(id);
}
let id = current_node_id.unwrap();
let mut created = Vec::new();
let node = template_ref.dynamic_context.resolve_node(*idx);
diff_state.create_node(id, node, &mut created);
diff_state.mutations.set_last_node(id.as_u64());
diff_state.mutations.append_children(None, created);
}
_ => panic!("can only insert dynamic nodes"),
}
}
UpdateOp::InsertBefore(id) | UpdateOp::InsertAfter(id) => {
let node = &nodes.as_ref()[id.0];
match &node.node_type {
TemplateNodeType::DynamicNode(idx) => {
if current_node_id.is_none() {
// create a temporary node to come back to later
let id = diff_state.scopes.reserve_phantom_node();
diff_state.mutations.store_with_id(id.as_u64());
temp_id = true;
current_node_id = Some(id);
}
let id = current_node_id.unwrap();
let mut created = Vec::new();
let node = template_ref.dynamic_context.resolve_node(*idx);
diff_state.create_node(parent, node, &mut created);
diff_state.mutations.set_last_node(id.as_u64());
match op {
UpdateOp::InsertBefore(_) => {
diff_state.mutations.insert_before(None, created);
}
UpdateOp::InsertAfter(_) => {
diff_state.mutations.insert_after(None, created);
}
_ => unreachable!(),
}
}
_ => panic!("can only insert dynamic nodes"),
}
}
}
}
match (seg.traverse.first_child(), seg.traverse.next_sibling()) {
(Some(child), Some(sibling)) => {
if current_node_id.is_none() {
// create a temporary node to come back to later
let id = diff_state.scopes.reserve_phantom_node();
diff_state.mutations.store_with_id(id.as_u64());
temp_id = true;
current_node_id = Some(id);
}
let id = current_node_id.unwrap();
diff_state.mutations.first_child();
traverse_seg(child, nodes, diff_state, template_ref, id);
diff_state.mutations.set_last_node(id.as_u64());
diff_state.mutations.next_sibling();
traverse_seg(sibling, nodes, diff_state, template_ref, parent);
}
(Some(seg), None) => {
if current_node_id.is_none() {
let id = diff_state.scopes.reserve_phantom_node();
diff_state.mutations.store_with_id(id.as_u64());
temp_id = true;
current_node_id = Some(id);
}
let id = current_node_id.unwrap();
diff_state.mutations.first_child();
traverse_seg(seg, nodes, diff_state, template_ref, id);
}
(None, Some(seg)) => {
diff_state.mutations.next_sibling();
traverse_seg(seg, nodes, diff_state, template_ref, parent);
}
(None, None) => {}
}
if temp_id {
if let Some(id) = current_node_id {
// remove the temporary node
diff_state.scopes.collect_garbage(id);
}
}
}
fn hydrate_inner<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>( fn hydrate_inner<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
nodes: &Nodes, nodes: &Nodes,
ctx: (&mut DiffState<'b>, &VTemplateRef<'b>, &Template), ctx: (
&mut DiffState<'b>,
&'b VTemplateRef<'b>,
&Template,
ElementId,
),
) where ) where
Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>, Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
Attributes: AsRef<[TemplateAttribute<V>]>, Attributes: AsRef<[TemplateAttribute<V>]>,
@ -306,14 +444,50 @@ impl<'a> VTemplateRef<'a> {
TextSegments: AsRef<[TextTemplateSegment<Text>]>, TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>, Text: AsRef<str>,
{ {
let (diff_state, template_ref, template) = ctx; let (diff_state, template_ref, template, parent) = ctx;
for id in template.all_dynamic() {
let dynamic_node = &nodes.as_ref()[id.0]; match template {
dynamic_node.hydrate(diff_state, template_ref); Template::Static(s) => {
if let Some(seg) = &s.dynamic_path {
diff_state.mutations.set_last_node(
template_ref.get_node_id(template.root_nodes()[0]).as_u64(),
);
traverse_seg(seg, nodes, diff_state, template_ref, parent);
}
}
Template::Owned(o) => {
if let Some(seg) = &o.dynamic_path {
diff_state.mutations.set_last_node(
template_ref.get_node_id(template.root_nodes()[0]).as_u64(),
);
traverse_seg(seg, nodes, diff_state, template_ref, parent);
}
}
} }
} }
template.with_nodes(hydrate_inner, hydrate_inner, (diff_state, self, template)); template.with_nodes(
hydrate_inner,
hydrate_inner,
(diff_state, self, template, parent),
);
}
pub(crate) fn get_node_id(&self, id: TemplateNodeId) -> ElementId {
self.try_get_node_id(id).unwrap()
}
pub(crate) fn try_get_node_id(&self, id: TemplateNodeId) -> Option<ElementId> {
let node_ids = self.node_ids.borrow();
node_ids.get(id.0).and_then(|cell| cell.get().copied())
}
pub(crate) fn set_node_id(&self, id: TemplateNodeId, real_id: ElementId) {
let mut ids = self.node_ids.borrow_mut();
if ids.len() <= id.0 {
ids.resize(id.0 + 1, OnceCell::new());
}
ids[id.0].set(real_id).unwrap();
} }
} }
@ -326,6 +500,8 @@ pub struct StaticTemplate {
pub root_nodes: StaticRootNodes, pub root_nodes: StaticRootNodes,
/// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced. /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced.
pub dynamic_mapping: StaticDynamicNodeMapping, pub dynamic_mapping: StaticDynamicNodeMapping,
/// The path to take to update the template with dynamic content (starts from the first root node)
pub dynamic_path: Option<StaticPathSeg>,
} }
/// A template that is created at runtime /// A template that is created at runtime
@ -341,7 +517,9 @@ pub struct OwnedTemplate {
/// The ids of the root nodes in the template /// The ids of the root nodes in the template
pub root_nodes: OwnedRootNodes, pub root_nodes: OwnedRootNodes,
/// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced. /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced.
pub dynamic_mapping: crate::OwnedDynamicNodeMapping, pub dynamic_mapping: OwnedDynamicNodeMapping,
/// The path to take to update the template with dynamic content
pub dynamic_path: Option<OwnedPathSeg>,
} }
/// A template used to skip diffing on some static parts of the rsx /// A template used to skip diffing on some static parts of the rsx
@ -355,19 +533,18 @@ pub enum Template {
} }
impl Template { impl Template {
pub(crate) fn create<'b>( pub(crate) fn create<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: ElementId) {
&self, let children = match self {
mutations: &mut Mutations<'b>, Template::Static(s) => self.count_real_nodes(s.root_nodes),
bump: &'b Bump, #[cfg(any(feature = "hot-reload", debug_assertions))]
id: RendererTemplateId, Template::Owned(o) => self.count_real_nodes(&o.root_nodes),
) { };
mutations.create_templete(id); mutations.create_element("template", None, Some(id.into()), children as u32);
let empty = match self { let empty = match self {
Template::Static(s) => s.nodes.is_empty(), Template::Static(s) => s.nodes.is_empty(),
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => o.nodes.is_empty(), Template::Owned(o) => o.nodes.is_empty(),
}; };
let mut len = 0;
if !empty { if !empty {
let roots = match self { let roots = match self {
Template::Static(s) => s.root_nodes, Template::Static(s) => s.root_nodes,
@ -375,11 +552,9 @@ impl Template {
Template::Owned(o) => &o.root_nodes, Template::Owned(o) => &o.root_nodes,
}; };
for root in roots { for root in roots {
len += 1;
self.create_node(mutations, bump, *root); self.create_node(mutations, bump, *root);
} }
} }
mutations.finish_templete(len as u32);
} }
fn create_node<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: TemplateNodeId) { fn create_node<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: TemplateNodeId) {
@ -395,9 +570,6 @@ impl Template {
Text: AsRef<str>, Text: AsRef<str>,
{ {
let (mutations, bump, template) = ctx; let (mutations, bump, template) = ctx;
let id = node.id;
let locally_static = node.locally_static;
let fully_static = node.fully_static;
match &node.node_type { match &node.node_type {
TemplateNodeType::Element(el) => { TemplateNodeType::Element(el) => {
let TemplateElement { let TemplateElement {
@ -407,12 +579,11 @@ impl Template {
children, children,
.. ..
} = el; } = el;
mutations.create_element_template( mutations.create_element(
tag, tag,
*namespace, *namespace,
id, None,
locally_static, template.count_real_nodes(children.as_ref()) as u32,
fully_static,
); );
for attr in attributes.as_ref() { for attr in attributes.as_ref() {
if let TemplateAttributeValue::Static(val) = &attr.value { if let TemplateAttributeValue::Static(val) = &attr.value {
@ -422,34 +593,24 @@ impl Template {
is_static: true, is_static: true,
value: val, value: val,
}; };
mutations.set_attribute(bump.alloc(attribute), id); mutations.set_attribute(bump.alloc(attribute), None);
} }
} }
let mut children_created = 0;
for child in children.as_ref() { for child in children.as_ref() {
template.create_node(mutations, bump, *child); template.create_node(mutations, bump, *child);
children_created += 1;
} }
mutations.append_children(children_created);
} }
TemplateNodeType::Text(text) => { TemplateNodeType::Text(text) => {
let mut text_iter = text.segments.as_ref().iter(); let mut text_iter = text.segments.as_ref().iter();
if let (Some(TextTemplateSegment::Static(txt)), None) = if let (Some(TextTemplateSegment::Static(txt)), None) =
(text_iter.next(), text_iter.next()) (text_iter.next(), text_iter.next())
{ {
mutations.create_text_node_template( mutations.create_text_node(bump.alloc_str(txt.as_ref()), None);
bump.alloc_str(txt.as_ref()),
id,
locally_static,
);
} else { } else {
mutations.create_text_node_template("", id, locally_static); mutations.create_text_node("", None);
} }
} }
TemplateNodeType::DynamicNode(_) => { TemplateNodeType::DynamicNode(_) => {}
mutations.create_placeholder_template(id);
}
} }
} }
self.with_node( self.with_node(
@ -496,10 +657,10 @@ impl Template {
} }
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
pub(crate) fn with_nodes<'a, F1, F2, Ctx>(&'a self, mut f1: F1, mut f2: F2, ctx: Ctx) pub(crate) fn with_nodes<'a, F1, F2, Ctx, R>(&'a self, mut f1: F1, mut f2: F2, ctx: Ctx) -> R
where where
F1: FnMut(&'a &'static [StaticTemplateNode], Ctx), F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
F2: FnMut(&'a Vec<OwnedTemplateNode>, Ctx), F2: FnMut(&'a Vec<OwnedTemplateNode>, Ctx) -> R,
{ {
match self { match self {
Template::Static(s) => f1(&s.nodes, ctx), Template::Static(s) => f1(&s.nodes, ctx),
@ -508,22 +669,39 @@ impl Template {
} }
#[cfg(not(any(feature = "hot-reload", debug_assertions)))] #[cfg(not(any(feature = "hot-reload", debug_assertions)))]
pub(crate) fn with_nodes<'a, F1, F2, Ctx>(&'a self, mut f1: F1, _f2: F2, ctx: Ctx) pub(crate) fn with_nodes<'a, F1, F2, Ctx, R>(&'a self, mut f1: F1, _f2: F2, ctx: Ctx) -> R
where where
F1: FnMut(&'a &'static [StaticTemplateNode], Ctx), F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
F2: FnMut(&'a &'static [StaticTemplateNode], Ctx), F2: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
{ {
match self { match self {
Template::Static(s) => f1(&s.nodes, ctx), Template::Static(s) => f1(&s.nodes, ctx),
} }
} }
pub(crate) fn all_dynamic<'a>(&'a self) -> Box<dyn Iterator<Item = TemplateNodeId> + 'a> { fn count_real_nodes(&self, ids: &[TemplateNodeId]) -> usize {
match self { fn count_real_nodes_inner<Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
Template::Static(s) => Box::new(s.dynamic_mapping.all_dynamic()), nodes: &Nodes,
#[cfg(any(feature = "hot-reload", debug_assertions))] id: TemplateNodeId,
Template::Owned(o) => Box::new(o.dynamic_mapping.all_dynamic()), ) -> usize
where
Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
Attributes: AsRef<[TemplateAttribute<V>]>,
V: TemplateValue,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
match &nodes.as_ref()[id.0].node_type {
TemplateNodeType::DynamicNode(_) => 0,
TemplateNodeType::Element(_) => 1,
TemplateNodeType::Text(_) => 1,
}
} }
ids.iter()
.map(|id| self.with_nodes(count_real_nodes_inner, count_real_nodes_inner, *id))
.sum()
} }
pub(crate) fn volatile_attributes<'a>( pub(crate) fn volatile_attributes<'a>(
@ -542,14 +720,6 @@ impl Template {
} }
} }
pub(crate) fn get_dynamic_nodes_for_node_index(&self, idx: usize) -> Option<TemplateNodeId> {
match self {
Template::Static(s) => s.dynamic_mapping.nodes[idx],
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => o.dynamic_mapping.nodes[idx],
}
}
pub(crate) fn get_dynamic_nodes_for_text_index(&self, idx: usize) -> &[TemplateNodeId] { pub(crate) fn get_dynamic_nodes_for_text_index(&self, idx: usize) -> &[TemplateNodeId] {
match self { match self {
Template::Static(s) => s.dynamic_mapping.text[idx], Template::Static(s) => s.dynamic_mapping.text[idx],
@ -568,6 +738,14 @@ impl Template {
Template::Owned(o) => o.dynamic_mapping.attributes[idx].as_ref(), Template::Owned(o) => o.dynamic_mapping.attributes[idx].as_ref(),
} }
} }
pub(crate) fn root_nodes(&self) -> &[TemplateNodeId] {
match self {
Template::Static(s) => s.root_nodes,
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => &o.root_nodes,
}
}
} }
/// A array of stack allocated Template nodes /// A array of stack allocated Template nodes
@ -607,7 +785,7 @@ pub type OwnedRootNodes = Vec<TemplateNodeId>;
/// Templates can only contain a limited subset of VNodes and keys are not needed, as diffing will be skipped. /// Templates can only contain a limited subset of VNodes and keys are not needed, as diffing will be skipped.
/// Dynamic parts of the Template are inserted into the VNode using the `TemplateContext` by traversing the tree in order and filling in dynamic parts /// Dynamic parts of the Template are inserted into the VNode using the `TemplateContext` by traversing the tree in order and filling in dynamic parts
/// This template node is generic over the storage of the nodes to allow for owned and &'static versions. /// This template node is generic over the storage of the nodes to allow for owned and &'static versions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr( #[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)), all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize) derive(serde::Serialize, serde::Deserialize)
@ -623,12 +801,13 @@ where
{ {
/// The ID of the [`TemplateNode`]. Note that this is not an elenemt id, and should be allocated seperately from VNodes on the frontend. /// The ID of the [`TemplateNode`]. Note that this is not an elenemt id, and should be allocated seperately from VNodes on the frontend.
pub id: TemplateNodeId, pub id: TemplateNodeId,
/// If the id of the node must be kept in the refrences /// The depth of the node in the template node tree
pub locally_static: bool, /// Root nodes have a depth of 0
/// If any children of this node must be kept in the references pub depth: usize,
pub fully_static: bool,
/// The type of the [`TemplateNode`]. /// The type of the [`TemplateNode`].
pub node_type: TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>, pub node_type: TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>,
/// The parent of this node.
pub parent: Option<TemplateNodeId>,
} }
impl<Attributes, V, Children, Listeners, TextSegments, Text> impl<Attributes, V, Children, Listeners, TextSegments, Text>
@ -641,14 +820,12 @@ where
TextSegments: AsRef<[TextTemplateSegment<Text>]>, TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>, Text: AsRef<str>,
{ {
fn hydrate<'b>(&self, diff_state: &mut DiffState<'b>, template_ref: &VTemplateRef<'b>) { fn hydrate<'b>(
let real_id = template_ref.id.get().unwrap(); &self,
real_node_id: ElementId,
diff_state.element_stack.push(GlobalNodeId::TemplateId { diff_state: &mut DiffState<'b>,
template_ref_id: real_id, template_ref: &'b VTemplateRef<'b>,
template_node_id: self.id, ) {
});
diff_state.mutations.enter_template_ref(real_id);
match &self.node_type { match &self.node_type {
TemplateNodeType::Element(el) => { TemplateNodeType::Element(el) => {
let TemplateElement { let TemplateElement {
@ -667,41 +844,34 @@ where
is_static: false, is_static: false,
}; };
let scope_bump = diff_state.current_scope_bump(); let scope_bump = diff_state.current_scope_bump();
diff_state diff_state.mutations.set_attribute(
.mutations scope_bump.alloc(attribute),
.set_attribute(scope_bump.alloc(attribute), self.id); Some(real_node_id.as_u64()),
);
} }
} }
for listener_idx in listeners.as_ref() { for listener_idx in listeners.as_ref() {
let listener = template_ref.dynamic_context.resolve_listener(*listener_idx); let listener = template_ref.dynamic_context.resolve_listener(*listener_idx);
let global_id = GlobalNodeId::TemplateId { listener.mounted_node.set(Some(real_node_id));
template_ref_id: real_id,
template_node_id: self.id,
};
listener.mounted_node.set(Some(global_id));
diff_state diff_state
.mutations .mutations
.new_event_listener(listener, diff_state.current_scope()); .new_event_listener(listener, diff_state.current_scope());
} }
} }
TemplateNodeType::Text(text) => { TemplateNodeType::Text(text) => {
let new_text = template_ref
.dynamic_context
.resolve_text(&text.segments.as_ref());
let scope_bump = diff_state.current_scope_bump(); let scope_bump = diff_state.current_scope_bump();
let mut bump_str =
bumpalo::collections::String::with_capacity_in(text.min_size, scope_bump);
template_ref
.dynamic_context
.resolve_text_into(text, &mut bump_str);
diff_state diff_state
.mutations .mutations
.set_text(scope_bump.alloc(new_text), self.id) .set_text(bump_str.into_bump_str(), Some(real_node_id.as_u64()));
}
TemplateNodeType::DynamicNode(idx) => {
// this will only be triggered for root elements
let created =
diff_state.create_node(template_ref.dynamic_context.resolve_node(*idx));
diff_state.mutations.replace_with(self.id, created as u32);
} }
TemplateNodeType::DynamicNode(_) => {}
} }
diff_state.mutations.exit_template_ref();
diff_state.element_stack.pop();
} }
} }
@ -824,48 +994,6 @@ where
DynamicNode(usize), DynamicNode(usize),
} }
impl<Attributes, V, Children, Listeners, TextSegments, Text>
TemplateNodeType<Attributes, V, Children, Listeners, TextSegments, Text>
where
Attributes: AsRef<[TemplateAttribute<V>]>,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
V: TemplateValue,
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
/// Returns if this node, and its children, are static.
pub fn fully_static<Nodes: Index<usize, Output = Self>>(&self, nodes: &Nodes) -> bool {
self.locally_static()
&& match self {
TemplateNodeType::Element(e) => e
.children
.as_ref()
.iter()
.all(|c| nodes[c.0].fully_static(nodes)),
TemplateNodeType::Text(_) => true,
TemplateNodeType::DynamicNode(_) => unreachable!(),
}
}
/// Returns if this node is static.
pub fn locally_static(&self) -> bool {
match self {
TemplateNodeType::Element(e) => {
e.attributes.as_ref().iter().all(|a| match a.value {
TemplateAttributeValue::Static(_) => true,
TemplateAttributeValue::Dynamic(_) => false,
}) && e.listeners.as_ref().is_empty()
}
TemplateNodeType::Text(t) => t.segments.as_ref().iter().all(|seg| match seg {
TextTemplateSegment::Static(_) => true,
TextTemplateSegment::Dynamic(_) => false,
}),
TemplateNodeType::DynamicNode(_) => false,
}
}
}
type StaticStr = &'static str; type StaticStr = &'static str;
/// A element template /// A element template
@ -899,8 +1027,6 @@ where
pub children: Children, pub children: Children,
/// The ids of the listeners of the element /// The ids of the listeners of the element
pub listeners: Listeners, pub listeners: Listeners,
/// The parent of the element
pub parent: Option<TemplateNodeId>,
value: PhantomData<V>, value: PhantomData<V>,
} }
@ -918,7 +1044,6 @@ where
attributes: Attributes, attributes: Attributes,
children: Children, children: Children,
listeners: Listeners, listeners: Listeners,
parent: Option<TemplateNodeId>,
) -> Self { ) -> Self {
TemplateElement { TemplateElement {
tag, tag,
@ -926,7 +1051,6 @@ where
attributes, attributes,
children, children,
listeners, listeners,
parent,
value: PhantomData, value: PhantomData,
} }
} }
@ -945,6 +1069,8 @@ where
{ {
/// The segments of the template. /// The segments of the template.
pub segments: Segments, pub segments: Segments,
/// The minimum size of the output text.
pub min_size: usize,
text: PhantomData<Text>, text: PhantomData<Text>,
} }
@ -954,9 +1080,10 @@ where
Text: AsRef<str>, Text: AsRef<str>,
{ {
/// create a new template from the segments it is composed of. /// create a new template from the segments it is composed of.
pub const fn new(segments: Segments) -> Self { pub const fn new(segments: Segments, min_size: usize) -> Self {
TextTemplate { TextTemplate {
segments, segments,
min_size,
text: PhantomData, text: PhantomData,
} }
} }
@ -1005,8 +1132,7 @@ pub enum StaticAttributeValue {
#[derive(Default)] #[derive(Default)]
pub(crate) struct TemplateResolver { pub(crate) struct TemplateResolver {
// maps a id to the rendererid and if that template needs to be re-created // maps a id to the rendererid and if that template needs to be re-created
pub template_id_mapping: FxHashMap<TemplateId, (RendererTemplateId, bool)>, pub template_id_mapping: FxHashMap<TemplateId, (ElementId, bool)>,
pub template_count: usize,
} }
impl TemplateResolver { impl TemplateResolver {
@ -1028,15 +1154,14 @@ impl TemplateResolver {
pub fn get_or_create_client_id( pub fn get_or_create_client_id(
&mut self, &mut self,
template_id: &TemplateId, template_id: &TemplateId,
) -> (RendererTemplateId, bool) { scopes: &ScopeArena,
) -> (ElementId, bool) {
if let Some(id) = self.template_id_mapping.get(template_id) { if let Some(id) = self.template_id_mapping.get(template_id) {
*id *id
} else { } else {
let id = self.template_count; let renderer_id = scopes.reserve_phantom_node();
let renderer_id = RendererTemplateId(id);
self.template_id_mapping self.template_id_mapping
.insert(template_id.clone(), (renderer_id, false)); .insert(template_id.clone(), (renderer_id, false));
self.template_count += 1;
(renderer_id, true) (renderer_id, true)
} }
} }
@ -1047,3 +1172,116 @@ impl TemplateResolver {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct SetTemplateMsg(pub TemplateId, pub OwnedTemplate); pub struct SetTemplateMsg(pub TemplateId, pub OwnedTemplate);
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// A path segment that lives on the heap.
pub type OwnedPathSeg = PathSeg<OwnedTraverse, Vec<UpdateOp>>;
#[cfg(any(feature = "hot-reload", debug_assertions))]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
#[derive(Debug, Clone, PartialEq)]
/// A traverse message that lives on the heap.
pub enum OwnedTraverse {
/// Halt traversal
Halt,
/// Traverse to the first child of the current node.
FirstChild(Box<OwnedPathSeg>),
/// Traverse to the next sibling of the current node.
NextSibling(Box<OwnedPathSeg>),
/// Traverse to the both the first child and next sibling of the current node.
Both(Box<(OwnedPathSeg, OwnedPathSeg)>),
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
impl Traversable<Vec<UpdateOp>> for OwnedTraverse {
fn first_child(&self) -> Option<&OwnedPathSeg> {
match self {
OwnedTraverse::FirstChild(p) => Some(p),
OwnedTraverse::Both(ps) => Some(&ps.0),
_ => None,
}
}
fn next_sibling(&self) -> Option<&OwnedPathSeg> {
match self {
OwnedTraverse::NextSibling(p) => Some(p),
OwnedTraverse::Both(ps) => Some(&ps.1),
_ => None,
}
}
}
/// A path segment that lives on the stack.
pub type StaticPathSeg = PathSeg<StaticTraverse, &'static [UpdateOp]>;
#[derive(Debug, Clone, PartialEq)]
/// A traverse message that lives on the stack.
pub enum StaticTraverse {
/// Halt traversal
Halt,
/// Traverse to the first child of the current node.
FirstChild(&'static StaticPathSeg),
/// Traverse to the next sibling of the current node.
NextSibling(&'static StaticPathSeg),
/// Traverse to the both the first child and next sibling of the current node.
Both(&'static (StaticPathSeg, StaticPathSeg)),
}
impl Traversable<&'static [UpdateOp]> for StaticTraverse {
fn first_child(&self) -> Option<&StaticPathSeg> {
match self {
StaticTraverse::FirstChild(p) => Some(p),
StaticTraverse::Both((p, _)) => Some(p),
_ => None,
}
}
fn next_sibling(&self) -> Option<&StaticPathSeg> {
match self {
StaticTraverse::NextSibling(p) => Some(p),
StaticTraverse::Both((_, p)) => Some(p),
_ => None,
}
}
}
pub trait Traversable<O: AsRef<[UpdateOp]>>
where
Self: Sized,
{
fn first_child(&self) -> Option<&PathSeg<Self, O>>;
fn next_sibling(&self) -> Option<&PathSeg<Self, O>>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
/// A path segment that defines a way to traverse a template node and resolve dynamic sections.
pub struct PathSeg<T: Traversable<O>, O: AsRef<[UpdateOp]>> {
/// The operation to perform on the current node.
pub ops: O,
/// The next traversal step.
pub traverse: T,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
/// A operation that can be applied to a template node when intially updating it.
pub enum UpdateOp {
/// Store a dynamic node on the renderer
StoreNode(TemplateNodeId),
/// Insert a dynamic node before the current node
InsertBefore(TemplateNodeId),
/// Insert a dynamic node after the current node
InsertAfter(TemplateNodeId),
/// Append a dynamic node to the current node
AppendChild(TemplateNodeId),
}

View file

@ -103,6 +103,7 @@ use std::{collections::VecDeque, iter::FromIterator, task::Poll};
/// } /// }
/// ``` /// ```
pub struct VirtualDom { pub struct VirtualDom {
root: ElementId,
scopes: ScopeArena, scopes: ScopeArena,
pending_messages: VecDeque<SchedulerMsg>, pending_messages: VecDeque<SchedulerMsg>,
@ -230,10 +231,11 @@ impl VirtualDom {
render_fn: root, render_fn: root,
}), }),
None, None,
GlobalNodeId::VNodeId(ElementId(0)), ElementId(0),
); );
Self { Self {
root: ElementId(0),
scopes, scopes,
channel, channel,
dirty_scopes: IndexSet::from_iter([ScopeId(0)]), dirty_scopes: IndexSet::from_iter([ScopeId(0)]),
@ -495,7 +497,7 @@ impl VirtualDom {
self.scopes.run_scope(scopeid); self.scopes.run_scope(scopeid);
diff_state.diff_scope(scopeid); diff_state.diff_scope(self.root, scopeid);
let DiffState { mutations, .. } = diff_state; let DiffState { mutations, .. } = diff_state;
@ -551,15 +553,15 @@ impl VirtualDom {
let mut diff_state = DiffState::new(&self.scopes); let mut diff_state = DiffState::new(&self.scopes);
self.scopes.run_scope(scope_id); self.scopes.run_scope(scope_id);
diff_state
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
diff_state.scope_stack.push(scope_id); diff_state.scope_stack.push(scope_id);
let node = self.scopes.fin_head(scope_id); let node = self.scopes.fin_head(scope_id);
let created = diff_state.create_node(node); let mut created = Vec::new();
diff_state.create_node(self.root, node, &mut created);
diff_state.mutations.append_children(created as u32); diff_state
.mutations
.append_children(Some(self.root.as_u64()), created);
self.dirty_scopes.clear(); self.dirty_scopes.clear();
assert!(self.dirty_scopes.is_empty()); assert!(self.dirty_scopes.is_empty());
@ -609,9 +611,8 @@ impl VirtualDom {
diff_machine.force_diff = true; diff_machine.force_diff = true;
diff_machine.scope_stack.push(scope_id); diff_machine.scope_stack.push(scope_id);
let scope = diff_machine.scopes.get_scope(scope_id).unwrap(); let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
diff_machine.element_stack.push(scope.container);
diff_machine.diff_node(old, new); diff_machine.diff_node(scope.container, old, new);
diff_machine.mutations diff_machine.mutations
} }
@ -650,11 +651,8 @@ impl VirtualDom {
/// ``` /// ```
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes); let mut machine = DiffState::new(&self.scopes);
machine
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
machine.scope_stack.push(ScopeId(0)); machine.scope_stack.push(ScopeId(0));
machine.diff_node(old, new); machine.diff_node(self.root, old, new);
machine.mutations machine.mutations
} }
@ -675,12 +673,12 @@ impl VirtualDom {
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> { pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes); let mut machine = DiffState::new(&self.scopes);
machine.scope_stack.push(ScopeId(0)); machine.scope_stack.push(ScopeId(0));
machine
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
let node = self.render_vnodes(nodes); let node = self.render_vnodes(nodes);
let created = machine.create_node(node); let mut created = Vec::new();
machine.mutations.append_children(created as u32); machine.create_node(self.root, node, &mut created);
machine
.mutations
.append_children(Some(self.root.as_u64()), created);
machine.mutations machine.mutations
} }
@ -706,16 +704,15 @@ impl VirtualDom {
let mut create = DiffState::new(&self.scopes); let mut create = DiffState::new(&self.scopes);
create.scope_stack.push(ScopeId(0)); create.scope_stack.push(ScopeId(0));
let mut created = Vec::new();
create.create_node(self.root, old, &mut created);
create create
.element_stack .mutations
.push(GlobalNodeId::VNodeId(ElementId(0))); .append_children(Some(self.root.as_u64()), created);
let created = create.create_node(old);
create.mutations.append_children(created as u32);
let mut edit = DiffState::new(&self.scopes); let mut edit = DiffState::new(&self.scopes);
edit.scope_stack.push(ScopeId(0)); edit.scope_stack.push(ScopeId(0));
edit.element_stack.push(GlobalNodeId::VNodeId(ElementId(0))); edit.diff_node(self.root, old, new);
edit.diff_node(old, new);
(create.mutations, edit.mutations) (create.mutations, edit.mutations)
} }

View file

@ -3,7 +3,8 @@
use std::any::Any; use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
use dioxus_core::{EventPriority, GlobalNodeId, UserEvent}; use dioxus_core::ElementId;
use dioxus_core::{EventPriority, UserEvent};
use dioxus_html::event_bubbles; use dioxus_html::event_bubbles;
use dioxus_html::on::*; use dioxus_html::on::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -37,7 +38,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct ImEvent { struct ImEvent {
event: String, event: String,
mounted_dom_id: GlobalNodeId, mounted_dom_id: ElementId,
contents: serde_json::Value, contents: serde_json::Value,
} }

View file

@ -30,29 +30,15 @@ fn test_original_diff() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateTemplate { id: 0 }, // create template
CreateElementTemplate { CreateElement { root: Some(1), tag: "template", children: 1 },
root: 4503599627370495, CreateElement { root: None, tag: "div", children: 1 },
tag: "div", CreateElement { root: None, tag: "div", children: 1 },
locally_static: true, CreateTextNode { root: None, text: "Hello, world!" },
fully_static: true // clone template
}, CloneNodeChildren { id: Some(1), new_ids: vec![2] },
CreateElementTemplate { // add to root
root: 4503599627370496, AppendChildren { root: Some(0), children: vec![2] },
tag: "div",
locally_static: true,
fully_static: true
},
CreateTextNodeTemplate {
root: 4503599627370497,
text: "Hello, world!",
locally_static: true
},
AppendChildren { many: 1 },
AppendChildren { many: 1 },
FinishTemplate { len: 1 },
CreateTemplateRef { id: 1, template_id: 0 },
AppendChildren { many: 1 }
] ]
); );
} }
@ -83,49 +69,27 @@ fn create() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateTemplate { id: 0 }, // create template
CreateElementTemplate { CreateElement { root: Some(1), tag: "template", children: 1 },
root: 4503599627370495, CreateElement { root: None, tag: "div", children: 1 },
tag: "div", CreateElement { root: None, tag: "div", children: 2 },
locally_static: true, CreateTextNode { root: None, text: "Hello, world!" },
fully_static: false CreateElement { root: None, tag: "div", children: 1 },
}, CreateElement { root: None, tag: "div", children: 1 },
CreateElementTemplate { CreatePlaceholder { root: None },
root: 4503599627370496, // clone template
tag: "div", CloneNodeChildren { id: Some(1), new_ids: vec![2] },
locally_static: true, CreateTextNode { root: Some(3), text: "hello" },
fully_static: false CreateTextNode { root: Some(4), text: "world" },
}, // update template
CreateTextNodeTemplate { SetLastNode { id: 2 },
root: 4503599627370497, FirstChild {},
text: "Hello, world!", FirstChild {},
locally_static: true NextSibling {},
}, FirstChild {},
CreateElementTemplate { FirstChild {},
root: 4503599627370498, ReplaceWith { root: None, nodes: vec![3, 4] },
tag: "div", AppendChildren { root: Some(0), children: vec![2] }
locally_static: true,
fully_static: false
},
CreateElementTemplate {
root: 4503599627370499,
tag: "div",
locally_static: true,
fully_static: false
},
CreatePlaceholderTemplate { root: 4503599627370500 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
AppendChildren { many: 2 },
AppendChildren { many: 1 },
FinishTemplate { len: 1 },
CreateTemplateRef { id: 1, template_id: 0 },
EnterTemplateRef { root: 1 },
CreateTextNode { root: 2, text: "hello" },
CreateTextNode { root: 3, text: "world" },
ReplaceWith { root: 4503599627370500, m: 2 },
ExitTemplateRef {},
AppendChildren { many: 1 }
] ]
); );
} }
@ -143,24 +107,19 @@ fn create_list() {
let mut dom = new_dom(APP, ()); let mut dom = new_dom(APP, ());
let mutations = dom.rebuild(); let mutations = dom.rebuild();
// copilot wrote this test :P
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateTemplate { id: 0 }, // create template
CreateElementTemplate { CreateElement { root: Some(1), tag: "template", children: 1 },
root: 4503599627370495, CreateElement { root: None, tag: "div", children: 1 },
tag: "div", CreateTextNode { root: None, text: "hello" },
locally_static: true, // clone template
fully_static: true CloneNodeChildren { id: Some(1), new_ids: vec![2] },
}, CloneNodeChildren { id: Some(1), new_ids: vec![3] },
CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true }, CloneNodeChildren { id: Some(1), new_ids: vec![4] },
AppendChildren { many: 1 }, // add to root
FinishTemplate { len: 1 }, AppendChildren { root: Some(0), children: vec![2, 3, 4] },
CreateTemplateRef { id: 1, template_id: 0 },
CreateTemplateRef { id: 2, template_id: 0 },
CreateTemplateRef { id: 3, template_id: 0 },
AppendChildren { many: 3 }
] ]
); );
} }
@ -179,42 +138,19 @@ fn create_simple() {
let mut dom = new_dom(APP, ()); let mut dom = new_dom(APP, ());
let mutations = dom.rebuild(); let mutations = dom.rebuild();
// copilot wrote this test :P
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateTemplate { id: 0 }, // create template
CreateElementTemplate { CreateElement { root: Some(1), tag: "template", children: 4 },
root: 4503599627370495, CreateElement { root: None, tag: "div", children: 0 },
tag: "div", CreateElement { root: None, tag: "div", children: 0 },
locally_static: true, CreateElement { root: None, tag: "div", children: 0 },
fully_static: true CreateElement { root: None, tag: "div", children: 0 },
}, // clone template
AppendChildren { many: 0 }, CloneNodeChildren { id: Some(1), new_ids: vec![2, 3, 4, 5] },
CreateElementTemplate { // add to root
root: 4503599627370496, AppendChildren { root: Some(0), children: vec![2, 3, 4, 5] },
tag: "div",
locally_static: true,
fully_static: true
},
AppendChildren { many: 0 },
CreateElementTemplate {
root: 4503599627370497,
tag: "div",
locally_static: true,
fully_static: true
},
AppendChildren { many: 0 },
CreateElementTemplate {
root: 4503599627370498,
tag: "div",
locally_static: true,
fully_static: true
},
AppendChildren { many: 0 },
FinishTemplate { len: 4 },
CreateTemplateRef { id: 1, template_id: 0 },
AppendChildren { many: 1 }
] ]
); );
} }
@ -247,46 +183,35 @@ fn create_components() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateTemplate { id: 0 }, // create template
CreateElementTemplate { CreateElement { root: Some(1), tag: "template", children: 3 },
root: 4503599627370495, CreateElement { root: None, tag: "h1", children: 0 },
tag: "h1", CreateElement { root: None, tag: "div", children: 1 },
locally_static: true, CreatePlaceholder { root: None },
fully_static: true CreateElement { root: None, tag: "p", children: 0 },
}, // clone template
AppendChildren { many: 0 }, CloneNodeChildren { id: Some(1), new_ids: vec![2, 3, 4] },
CreateElementTemplate { // update template
root: 4503599627370496, CreateTextNode { root: Some(5), text: "abc1" },
tag: "div", SetLastNode { id: 3 },
locally_static: true, FirstChild {},
fully_static: false ReplaceWith { root: None, nodes: vec![5] },
}, // clone template
CreatePlaceholderTemplate { root: 4503599627370497 }, CloneNodeChildren { id: Some(1), new_ids: vec![6, 7, 8] },
AppendChildren { many: 1 }, // update template
CreateElementTemplate { CreateTextNode { root: Some(9), text: "abc2" },
root: 4503599627370498, SetLastNode { id: 7 },
tag: "p", FirstChild {},
locally_static: true, ReplaceWith { root: None, nodes: vec![9] },
fully_static: true // clone template
}, CloneNodeChildren { id: Some(1), new_ids: vec![10, 11, 12] },
AppendChildren { many: 0 }, // update template
FinishTemplate { len: 3 }, CreateTextNode { root: Some(13), text: "abc3" },
CreateTemplateRef { id: 1, template_id: 0 }, SetLastNode { id: 11 },
EnterTemplateRef { root: 1 }, FirstChild {},
CreateTextNode { root: 2, text: "abc1" }, ReplaceWith { root: None, nodes: vec![13] },
ReplaceWith { root: 4503599627370497, m: 1 }, // add to root
ExitTemplateRef {}, AppendChildren { root: Some(0), children: vec![2, 3, 4, 6, 7, 8, 10, 11, 12] }
CreateTemplateRef { id: 3, template_id: 0 },
EnterTemplateRef { root: 3 },
CreateTextNode { root: 4, text: "abc2" },
ReplaceWith { root: 4503599627370497, m: 1 },
ExitTemplateRef {},
CreateTemplateRef { id: 5, template_id: 0 },
EnterTemplateRef { root: 5 },
CreateTextNode { root: 6, text: "abc3" },
ReplaceWith { root: 4503599627370497, m: 1 },
ExitTemplateRef {},
AppendChildren { many: 3 }
] ]
); );
} }
@ -302,22 +227,19 @@ fn anchors() {
let mut dom = new_dom(App, ()); let mut dom = new_dom(App, ());
let mutations = dom.rebuild(); let mutations = dom.rebuild();
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateTemplate { id: 0 }, // create template
CreateElementTemplate { CreateElement { root: Some(1), tag: "template", children: 1 },
root: 4503599627370495, CreateElement { root: None, tag: "div", children: 1 },
tag: "div", CreateTextNode { root: None, text: "hello" },
locally_static: true, // clone template
fully_static: true CloneNodeChildren { id: Some(1), new_ids: vec![2] },
}, CreatePlaceholder { root: Some(3) },
CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true }, // add to root
AppendChildren { many: 1 }, AppendChildren { root: Some(0), children: vec![2, 3] },
FinishTemplate { len: 1 },
CreateTemplateRef { id: 1, template_id: 0 },
CreatePlaceholder { root: 2 },
AppendChildren { many: 2 }
] ]
); );
} }

View file

@ -25,14 +25,17 @@ fn html_and_rsx_generate_the_same_output() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { root: 1, tag: "div" }, CreateElement { root: Some(1,), tag: "div", children: 0 },
CreateTextNode { root: 2, text: "Hello world" }, CreateTextNode { root: Some(2,), text: "Hello world" },
AppendChildren { many: 1 }, AppendChildren { root: Some(1,), children: vec![2] },
AppendChildren { many: 1 }, AppendChildren { root: Some(0,), children: vec![1] },
] ]
); );
assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]); assert_eq!(
change.edits,
[SetText { root: Some(2,), text: "Goodbye world" },]
);
} }
/// Should result in 3 elements on the stack /// Should result in 3 elements on the stack
@ -49,16 +52,16 @@ fn fragments_create_properly() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { root: 1, tag: "div" }, CreateElement { root: Some(1,), tag: "div", children: 0 },
CreateTextNode { root: 2, text: "Hello a" }, CreateTextNode { root: Some(2,), text: "Hello a" },
AppendChildren { many: 1 }, AppendChildren { root: Some(1,), children: vec![2,] },
CreateElement { root: 3, tag: "div" }, CreateElement { root: Some(3,), tag: "div", children: 0 },
CreateTextNode { root: 4, text: "Hello b" }, CreateTextNode { root: Some(4,), text: "Hello b" },
AppendChildren { many: 1 }, AppendChildren { root: Some(3,), children: vec![4,] },
CreateElement { root: 5, tag: "div" }, CreateElement { root: Some(5,), tag: "div", children: 0 },
CreateTextNode { root: 6, text: "Hello c" }, CreateTextNode { root: Some(6,), text: "Hello c" },
AppendChildren { many: 1 }, AppendChildren { root: Some(5,), children: vec![6,] },
AppendChildren { many: 3 }, AppendChildren { root: Some(0,), children: vec![1, 3, 5,] },
] ]
); );
} }
@ -75,13 +78,16 @@ fn empty_fragments_create_anchors() {
assert_eq!( assert_eq!(
create.edits, create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] [
CreatePlaceholder { root: Some(1,) },
AppendChildren { root: Some(0,), children: vec![1,] },
]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { root: 2, tag: "div" }, CreateElement { root: Some(2,), tag: "div", children: 0 },
ReplaceWith { m: 1, root: 1 } ReplaceWith { root: Some(1,), nodes: vec![2,] },
] ]
); );
} }
@ -95,20 +101,24 @@ fn empty_fragments_create_many_anchors() {
let right = rsx_without_templates!({ (0..5).map(|_f| rsx_without_templates! { div {}}) }); let right = rsx_without_templates!({ (0..5).map(|_f| rsx_without_templates! { div {}}) });
let (create, change) = dom.diff_lazynodes(left, right); let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
create.edits, create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] [
CreatePlaceholder { root: Some(1,) },
AppendChildren { root: Some(0,), children: vec![1,] },
]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { root: 2, tag: "div" }, CreateElement { root: Some(2,), tag: "div", children: 0 },
CreateElement { root: 3, tag: "div" }, CreateElement { root: Some(3,), tag: "div", children: 0 },
CreateElement { root: 4, tag: "div" }, CreateElement { root: Some(4,), tag: "div", children: 0 },
CreateElement { root: 5, tag: "div" }, CreateElement { root: Some(5,), tag: "div", children: 0 },
CreateElement { root: 6, tag: "div" }, CreateElement { root: Some(6,), tag: "div", children: 0 },
ReplaceWith { m: 5, root: 1 } ReplaceWith { root: Some(1,), nodes: vec![2, 3, 4, 5, 6,] },
] ]
); );
} }
@ -127,24 +137,28 @@ fn empty_fragments_create_anchors_with_many_children() {
}); });
let (create, change) = dom.diff_lazynodes(left, right); let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
create.edits, create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] [
CreatePlaceholder { root: Some(1,) },
AppendChildren { root: Some(0,), children: vec![1,] },
]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { tag: "div", root: 2 }, CreateElement { root: Some(2,), tag: "div", children: 0 },
CreateTextNode { text: "hello: 0", root: 3 }, CreateTextNode { root: Some(3,), text: "hello: 0" },
AppendChildren { many: 1 }, AppendChildren { root: Some(2,), children: vec![3,] },
CreateElement { tag: "div", root: 4 }, CreateElement { root: Some(4,), tag: "div", children: 0 },
CreateTextNode { text: "hello: 1", root: 5 }, CreateTextNode { root: Some(5,), text: "hello: 1" },
AppendChildren { many: 1 }, AppendChildren { root: Some(4,), children: vec![5,] },
CreateElement { tag: "div", root: 6 }, CreateElement { root: Some(6,), tag: "div", children: 0 },
CreateTextNode { text: "hello: 2", root: 7 }, CreateTextNode { root: Some(7,), text: "hello: 2" },
AppendChildren { many: 1 }, AppendChildren { root: Some(6,), children: vec![7,] },
ReplaceWith { root: 1, m: 3 } ReplaceWith { root: Some(1,), nodes: vec![2, 4, 6,] },
] ]
); );
} }
@ -162,25 +176,26 @@ fn many_items_become_fragment() {
let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) }); let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
let (create, change) = dom.diff_lazynodes(left, right); let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { root: 1, tag: "div" }, CreateElement { root: Some(1,), tag: "div", children: 0 },
CreateTextNode { text: "hello", root: 2 }, CreateTextNode { root: Some(2,), text: "hello" },
AppendChildren { many: 1 }, AppendChildren { root: Some(1,), children: vec![2,] },
CreateElement { root: 3, tag: "div" }, CreateElement { root: Some(3,), tag: "div", children: 0 },
CreateTextNode { text: "hello", root: 4 }, CreateTextNode { root: Some(4,), text: "hello" },
AppendChildren { many: 1 }, AppendChildren { root: Some(3,), children: vec![4,] },
AppendChildren { many: 2 }, AppendChildren { root: Some(0,), children: vec![1, 3,] },
] ]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreatePlaceholder { root: 5 }, CreatePlaceholder { root: Some(5,) },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: Some(1,), nodes: vec![5,] },
Remove { root: 3 }, Remove { root: Some(3,) },
] ]
); );
} }
@ -220,20 +235,17 @@ fn two_fragments_with_differrent_elements_are_differet() {
); );
let (_create, changes) = dom.diff_lazynodes(left, right); let (_create, changes) = dom.diff_lazynodes(left, right);
println!("{:#?}", &changes);
assert_eq!( assert_eq!(
changes.edits, changes.edits,
[ [
// create the new h1s CreateElement { root: Some(4,), tag: "h1", children: 0 },
CreateElement { tag: "h1", root: 4 }, CreateElement { root: Some(5,), tag: "h1", children: 0 },
CreateElement { tag: "h1", root: 5 }, CreateElement { root: Some(6,), tag: "h1", children: 0 },
CreateElement { tag: "h1", root: 6 }, InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] },
InsertAfter { root: 2, n: 3 }, CreateElement { root: Some(7,), tag: "h1", children: 0 },
// replace the divs with new h1s ReplaceWith { root: Some(1,), nodes: vec![7,] }, // notice how 1 gets re-used
CreateElement { tag: "h1", root: 7 }, CreateElement { root: Some(1,), tag: "h1", children: 0 },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: Some(2,), nodes: vec![1,] },
CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used
ReplaceWith { root: 2, m: 1 },
] ]
); );
} }
@ -253,16 +265,17 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
); );
let (create, change) = dom.diff_lazynodes(left, right); let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { root: 1, tag: "div" }, CreateElement { root: Some(1,), tag: "div", children: 0 },
CreateElement { root: 2, tag: "div" }, CreateElement { root: Some(2,), tag: "div", children: 0 },
CreateElement { root: 3, tag: "div" }, CreateElement { root: Some(3,), tag: "div", children: 0 },
CreateElement { root: 4, tag: "div" }, CreateElement { root: Some(4,), tag: "div", children: 0 },
CreateElement { root: 5, tag: "div" }, CreateElement { root: Some(5,), tag: "div", children: 0 },
CreateElement { root: 6, tag: "p" }, CreateElement { root: Some(6,), tag: "p", children: 0 },
AppendChildren { many: 6 }, AppendChildren { root: Some(0,), children: vec![1, 2, 3, 4, 5, 6,] },
] ]
); );
@ -271,13 +284,13 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
Remove { root: 3 }, Remove { root: Some(3,) },
Remove { root: 4 }, Remove { root: Some(4,) },
Remove { root: 5 }, Remove { root: Some(5,) },
CreateElement { root: 5, tag: "h1" }, // 3 gets reused CreateElement { root: Some(5,), tag: "h1", children: 0 }, // 5 gets reused
ReplaceWith { root: 1, m: 1 }, // 1 gets deleted ReplaceWith { root: Some(1,), nodes: vec![5,] }, // 1 gets deleted
CreateElement { root: 1, tag: "h1" }, // 1 gets reused CreateElement { root: Some(1,), tag: "h1", children: 0 }, // 1 gets reused
ReplaceWith { root: 2, m: 1 }, ReplaceWith { root: Some(2,), nodes: vec![1,] },
] ]
); );
} }
@ -297,22 +310,23 @@ fn two_fragments_with_same_elements_are_differet() {
); );
let (create, change) = dom.diff_lazynodes(left, right); let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
create.edits, create.edits,
[ [
CreateElement { root: 1, tag: "div" }, CreateElement { root: Some(1,), tag: "div", children: 0 },
CreateElement { root: 2, tag: "div" }, CreateElement { root: Some(2,), tag: "div", children: 0 },
CreateElement { root: 3, tag: "p" }, CreateElement { root: Some(3,), tag: "p", children: 0 },
AppendChildren { many: 3 }, AppendChildren { root: Some(0,), children: vec![1, 2, 3,] },
] ]
); );
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { root: 4, tag: "div" }, CreateElement { root: Some(4,), tag: "div", children: 0 },
CreateElement { root: 5, tag: "div" }, CreateElement { root: Some(5,), tag: "div", children: 0 },
CreateElement { root: 6, tag: "div" }, CreateElement { root: Some(6,), tag: "div", children: 0 },
InsertAfter { root: 2, n: 3 }, InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] },
] ]
); );
} }
@ -332,9 +346,14 @@ fn keyed_diffing_order() {
); );
let (create, change) = dom.diff_lazynodes(left, right); let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
change.edits, change.edits,
[Remove { root: 3 }, Remove { root: 4 }, Remove { root: 5 },] [
Remove { root: Some(3,) },
Remove { root: Some(4,) },
Remove { root: Some(5,) },
]
); );
} }
@ -356,10 +375,10 @@ fn keyed_diffing_out_of_order() {
}); });
let (_, changes) = dom.diff_lazynodes(left, right); let (_, changes) = dom.diff_lazynodes(left, right);
println!("{:?}", &changes);
assert_eq!( assert_eq!(
changes.edits, changes.edits,
[PushRoot { root: 7 }, InsertBefore { root: 5, n: 1 }] [InsertBefore { root: Some(5,), nodes: vec![7,] },]
); );
} }
@ -381,16 +400,13 @@ fn keyed_diffing_out_of_order_adds() {
}); });
let (_, change) = dom.diff_lazynodes(left, right); let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [InsertBefore { root: Some(1,), nodes: vec![5, 4,] },]
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 1 }
]
); );
} }
/// Should result in moves onl /// Should result in moves only
#[test] #[test]
fn keyed_diffing_out_of_order_adds_2() { fn keyed_diffing_out_of_order_adds_2() {
let dom = new_dom(); let dom = new_dom();
@ -408,13 +424,10 @@ fn keyed_diffing_out_of_order_adds_2() {
}); });
let (_, change) = dom.diff_lazynodes(left, right); let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [InsertBefore { root: Some(1,), nodes: vec![4, 5,] },]
PushRoot { root: 4 },
PushRoot { root: 5 },
InsertBefore { n: 2, root: 1 }
]
); );
} }
@ -436,13 +449,10 @@ fn keyed_diffing_out_of_order_adds_3() {
}); });
let (_, change) = dom.diff_lazynodes(left, right); let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [InsertBefore { root: Some(2,), nodes: vec![5, 4,] },]
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 2 }
]
); );
} }
@ -464,13 +474,10 @@ fn keyed_diffing_out_of_order_adds_4() {
}); });
let (_, change) = dom.diff_lazynodes(left, right); let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [InsertBefore { root: Some(3), nodes: vec![5, 4,] },]
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 3 }
]
); );
} }
@ -494,7 +501,7 @@ fn keyed_diffing_out_of_order_adds_5() {
let (_, change) = dom.diff_lazynodes(left, right); let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
change.edits, change.edits,
[PushRoot { root: 5 }, InsertBefore { n: 1, root: 4 }] [InsertBefore { root: Some(4), nodes: vec![5] }]
); );
} }
@ -515,12 +522,13 @@ fn keyed_diffing_additions() {
}); });
let (_, change) = dom.diff_lazynodes(left, right); let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
CreateElement { root: 6, tag: "div" }, CreateElement { root: Some(6,), tag: "div", children: 0 },
CreateElement { root: 7, tag: "div" }, CreateElement { root: Some(7,), tag: "div", children: 0 },
InsertAfter { n: 2, root: 5 } InsertAfter { root: Some(5,), nodes: vec![6, 7,] },
] ]
); );
} }
@ -547,12 +555,11 @@ fn keyed_diffing_additions_and_moves_on_ends() {
change.edits, change.edits,
[ [
// create 11, 12 // create 11, 12
CreateElement { tag: "div", root: 5 }, CreateElement { root: Some(5), tag: "div", children: 0 },
CreateElement { tag: "div", root: 6 }, CreateElement { root: Some(6), tag: "div", children: 0 },
InsertAfter { root: 3, n: 2 }, InsertAfter { root: Some(3), nodes: vec![5, 6] },
// move 7 to the front // // move 7 to the front
PushRoot { root: 4 }, InsertBefore { root: Some(1), nodes: vec![4] }
InsertBefore { root: 1, n: 1 }
] ]
); );
} }
@ -580,16 +587,15 @@ fn keyed_diffing_additions_and_moves_in_middle() {
change.edits, change.edits,
[ [
// create 5, 6 // create 5, 6
CreateElement { tag: "div", root: 5 }, CreateElement { root: Some(5,), tag: "div", children: 0 },
CreateElement { tag: "div", root: 6 }, CreateElement { root: Some(6,), tag: "div", children: 0 },
InsertBefore { root: 3, n: 2 }, InsertBefore { root: Some(3,), nodes: vec![5, 6,] },
// create 7, 8 // create 7, 8
CreateElement { tag: "div", root: 7 }, CreateElement { root: Some(7,), tag: "div", children: 0 },
CreateElement { tag: "div", root: 8 }, CreateElement { root: Some(8,), tag: "div", children: 0 },
InsertBefore { root: 2, n: 2 }, InsertBefore { root: Some(2,), nodes: vec![7, 8,] },
// move 7 // move 7
PushRoot { root: 4 }, InsertBefore { root: Some(1,), nodes: vec![4,] },
InsertBefore { root: 1, n: 1 }
] ]
); );
} }
@ -616,18 +622,16 @@ fn controlled_keyed_diffing_out_of_order() {
assert_eq!( assert_eq!(
changes.edits, changes.edits,
[ [
Remove { root: 4 },
// move 4 to after 6
PushRoot { root: 1 },
InsertAfter { n: 1, root: 3 },
// remove 7 // remove 7
Remove { root: Some(4,) },
// move 4 to after 6
InsertAfter { root: Some(3,), nodes: vec![1,] },
// create 9 and insert before 6 // create 9 and insert before 6
CreateElement { root: 4, tag: "div" }, CreateElement { root: Some(4,), tag: "div", children: 0 },
InsertBefore { n: 1, root: 3 }, InsertBefore { root: Some(3,), nodes: vec![4,] },
// create 0 and insert before 5 // create 0 and insert before 5
CreateElement { root: 5, tag: "div" }, CreateElement { root: Some(5,), tag: "div", children: 0 },
InsertBefore { n: 1, root: 2 }, InsertBefore { root: Some(2,), nodes: vec![5,] },
] ]
); );
} }
@ -653,11 +657,10 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
assert_eq!( assert_eq!(
changes.edits, changes.edits,
[ [
Remove { root: 5 }, Remove { root: Some(5,) },
CreateElement { root: 5, tag: "div" }, CreateElement { root: Some(5,), tag: "div", children: 0 },
InsertBefore { n: 1, root: 3 }, InsertBefore { root: Some(3,), nodes: vec![5,] },
PushRoot { root: 4 }, InsertBefore { root: Some(1,), nodes: vec![4,] },
InsertBefore { n: 1, root: 1 },
] ]
); );
} }
@ -687,11 +690,11 @@ fn remove_list() {
assert_eq!( assert_eq!(
changes.edits, changes.edits,
// remove 5, 4, 3
[ [
// remove 5, 4, 3 Remove { root: Some(3) },
Remove { root: 3 }, Remove { root: Some(4) },
Remove { root: 4 }, Remove { root: Some(5) }
Remove { root: 5 },
] ]
); );
} }
@ -720,9 +723,9 @@ fn remove_list_nokeyed() {
changes.edits, changes.edits,
[ [
// remove 5, 4, 3 // remove 5, 4, 3
Remove { root: 3 }, Remove { root: Some(3) },
Remove { root: 4 }, Remove { root: Some(4) },
Remove { root: 5 }, Remove { root: Some(5) },
] ]
); );
} }
@ -745,10 +748,8 @@ fn add_nested_elements() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
PushRoot { root: 1 }, CreateElement { root: Some(2), tag: "div", children: 0 },
CreateElement { root: 2, tag: "div" }, AppendChildren { root: Some(1), children: vec![2] },
AppendChildren { many: 1 },
PopRoot {},
] ]
); );
} }
@ -772,8 +773,8 @@ fn add_listeners() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 }, NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) },
NewEventListener { event_name: "keydown", scope: ScopeId(0), root: 1 }, NewEventListener { event_name: "keydown", scope: ScopeId(0), root: Some(1) },
] ]
); );
} }
@ -797,8 +798,8 @@ fn remove_listeners() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
RemoveEventListener { event: "keyup", root: 1 }, RemoveEventListener { event: "keyup", root: Some(1) },
RemoveEventListener { event: "keydown", root: 1 }, RemoveEventListener { event: "keydown", root: Some(1) },
] ]
); );
} }
@ -823,8 +824,8 @@ fn diff_listeners() {
assert_eq!( assert_eq!(
change.edits, change.edits,
[ [
RemoveEventListener { root: 1, event: "keydown" }, RemoveEventListener { root: Some(1), event: "keydown" },
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 } NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) }
] ]
); );
} }

View file

@ -37,37 +37,31 @@ fn test_early_abort() {
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
CreateTemplate { id: 0 }, // create template
CreateElementTemplate { CreateElement { root: Some(1), tag: "template", children: 1 },
root: 4503599627370495, CreateElement { root: None, tag: "div", children: 1 },
tag: "div", CreateTextNode { root: None, text: "Hello, world!" },
locally_static: true, // clone template
fully_static: true CloneNodeChildren { id: Some(1), new_ids: vec![2] },
}, AppendChildren { root: Some(0), children: vec![2] }
CreateTextNodeTemplate {
root: 4503599627370496,
text: "Hello, world!",
locally_static: true
},
AppendChildren { many: 1 },
FinishTemplate { len: 1 },
CreateTemplateRef { id: 1, template_id: 0 },
AppendChildren { many: 1 }
] ]
); );
let edits = dom.hard_diff(ScopeId(0));
assert_eq!(
edits.edits,
[CreatePlaceholder { root: 2 }, ReplaceWith { root: 1, m: 1 },],
);
let edits = dom.hard_diff(ScopeId(0)); let edits = dom.hard_diff(ScopeId(0));
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
CreateTemplateRef { id: 1, template_id: 0 }, // gets reused CreatePlaceholder { root: Some(3) },
ReplaceWith { root: 2, m: 1 } ReplaceWith { root: Some(2), nodes: vec![3] }
]
);
let edits = dom.hard_diff(ScopeId(0));
assert_eq!(
edits.edits,
[
CloneNodeChildren { id: Some(1), new_ids: vec![2] },
ReplaceWith { root: Some(3), nodes: vec![2] }
] ]
); );
} }

View file

@ -62,14 +62,14 @@ fn events_generate() {
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
CreateElement { tag: "div", root: 1 }, CreateElement { root: Some(1), tag: "div", children: 0 },
NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 }, NewEventListener { event_name: "click", scope: ScopeId(0), root: Some(1) },
CreateElement { tag: "div", root: 2 }, CreateElement { root: Some(2), tag: "div", children: 0 },
CreateTextNode { text: "nested", root: 3 }, CreateTextNode { root: Some(3), text: "nested" },
AppendChildren { many: 1 }, AppendChildren { root: Some(2), children: vec![3] },
CreateTextNode { text: "Click me!", root: 4 }, CreateTextNode { root: Some(4), text: "Click me!" },
AppendChildren { many: 2 }, AppendChildren { root: Some(1), children: vec![2, 4] },
AppendChildren { many: 1 }, AppendChildren { root: Some(0), children: vec![1] }
] ]
) )
} }
@ -105,24 +105,24 @@ fn components_generate() {
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
CreateTextNode { text: "Text0", root: 1 }, CreateTextNode { root: Some(1), text: "Text0" },
AppendChildren { many: 1 }, AppendChildren { root: Some(0), children: vec![1] }
] ]
); );
assert_eq!( assert_eq!(
dom.hard_diff(ScopeId(0)).edits, dom.hard_diff(ScopeId(0)).edits,
[ [
CreateElement { tag: "div", root: 2 }, CreateElement { root: Some(2), tag: "div", children: 0 },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: Some(1), nodes: vec![2] }
] ]
); );
assert_eq!( assert_eq!(
dom.hard_diff(ScopeId(0)).edits, dom.hard_diff(ScopeId(0)).edits,
[ [
CreateTextNode { text: "Text2", root: 1 }, CreateTextNode { root: Some(1), text: "Text2" },
ReplaceWith { root: 2, m: 1 }, ReplaceWith { root: Some(2), nodes: vec![1] }
] ]
); );
@ -130,40 +130,43 @@ fn components_generate() {
assert_eq!( assert_eq!(
dom.hard_diff(ScopeId(0)).edits, dom.hard_diff(ScopeId(0)).edits,
[ [
CreateElement { tag: "h1", root: 2 }, CreateElement { root: Some(2), tag: "h1", children: 0 },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: Some(1), nodes: vec![2] }
] ]
); );
// placeholder // placeholder
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },]
);
assert_eq!( assert_eq!(
dom.hard_diff(ScopeId(0)).edits, dom.hard_diff(ScopeId(0)).edits,
[ [
CreateTextNode { text: "text 3", root: 2 }, CreatePlaceholder { root: Some(1) },
ReplaceWith { root: 1, m: 1 }, ReplaceWith { root: Some(2), nodes: vec![1] }
] ]
); );
assert_eq!( assert_eq!(
dom.hard_diff(ScopeId(0)).edits, dom.hard_diff(ScopeId(0)).edits,
[ [
CreateTextNode { text: "text 0", root: 1 }, CreateTextNode { root: Some(2), text: "text 3" },
CreateTextNode { text: "text 1", root: 3 }, ReplaceWith { root: Some(1), nodes: vec![2] }
ReplaceWith { root: 2, m: 2 },
] ]
); );
assert_eq!( assert_eq!(
dom.hard_diff(ScopeId(0)).edits, dom.hard_diff(ScopeId(0)).edits,
[ [
CreateElement { tag: "h1", root: 2 }, CreateTextNode { text: "text 0", root: Some(1) },
ReplaceWith { root: 1, m: 1 }, CreateTextNode { text: "text 1", root: Some(3) },
Remove { root: 3 }, ReplaceWith { root: Some(2), nodes: vec![1, 3] },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "h1", root: Some(2), children: 0 },
ReplaceWith { root: Some(1), nodes: vec![2] },
Remove { root: Some(3) },
] ]
); );
} }

View file

@ -45,10 +45,10 @@ fn nested_passthru_creates() {
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
CreateElement { tag: "div", root: 1 }, CreateElement { tag: "div", root: Some(1), children: 0 },
CreateTextNode { text: "hi", root: 2 }, CreateTextNode { text: "hi", root: Some(2) },
AppendChildren { many: 1 }, AppendChildren { root: Some(1), children: vec![2] },
AppendChildren { many: 1 }, AppendChildren { root: Some(0), children: vec![1] },
] ]
) )
} }
@ -88,13 +88,13 @@ fn nested_passthru_creates_add() {
assert_eq!( assert_eq!(
edits.edits, edits.edits,
[ [
CreateTextNode { text: "1", root: 1 }, CreateTextNode { text: "1", root: Some(1) },
CreateTextNode { text: "2", root: 2 }, CreateTextNode { text: "2", root: Some(2) },
CreateTextNode { text: "3", root: 3 }, CreateTextNode { text: "3", root: Some(3) },
CreateElement { tag: "div", root: 4 }, CreateElement { tag: "div", root: Some(4), children: 0 },
CreateTextNode { text: "hi", root: 5 }, CreateTextNode { text: "hi", root: Some(5) },
AppendChildren { many: 1 }, AppendChildren { root: Some(4), children: vec![5] },
AppendChildren { many: 4 }, AppendChildren { root: Some(0), children: vec![1, 2, 3, 4] },
] ]
) )
} }

View file

@ -25,8 +25,8 @@ fn shared_state_test() {
assert_eq!( assert_eq!(
edits, edits,
[ [
CreateTextNode { root: 1, text: "Hello, world!" }, CreateTextNode { root: Some(1), text: "Hello, world!" },
AppendChildren { many: 1 }, AppendChildren { root: Some(0), children: vec![1] },
] ]
); );
} }

View file

@ -63,38 +63,22 @@ fn conditional_rendering() {
assert_eq!( assert_eq!(
mutations.edits, mutations.edits,
[ [
CreateTemplate { id: 0 }, CreateElement { root: Some(1), tag: "template", children: 3 },
CreateElementTemplate { CreateElement { root: None, tag: "h1", children: 1 },
root: 4503599627370495, CreateTextNode { root: None, text: "hello" },
tag: "h1", CreatePlaceholder { root: None },
locally_static: true, CreatePlaceholder { root: None },
fully_static: true CloneNodeChildren { id: Some(1), new_ids: vec![2, 3, 4] },
}, CreateElement { root: Some(5), tag: "template", children: 1 },
CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true }, CreateElement { root: None, tag: "span", children: 1 },
AppendChildren { many: 1 }, CreateTextNode { root: None, text: "a" },
CreatePlaceholderTemplate { root: 4503599627370497 }, CloneNodeChildren { id: Some(5), new_ids: vec![6] },
CreatePlaceholderTemplate { root: 4503599627370498 }, SetLastNode { id: 3 },
FinishTemplate { len: 3 }, ReplaceWith { root: None, nodes: vec![6] },
CreateTemplateRef { id: 1, template_id: 0 }, CreatePlaceholder { root: Some(7) },
EnterTemplateRef { root: 1 }, SetLastNode { id: 4 },
CreateTemplate { id: 1 }, ReplaceWith { root: None, nodes: vec![7] },
CreateElementTemplate { AppendChildren { root: Some(0), children: vec![2, 3, 4] }
root: 4503599627370495,
tag: "span",
locally_static: true,
fully_static: true
},
CreateTextNodeTemplate { root: 4503599627370496, text: "a", locally_static: true },
AppendChildren { many: 1 },
FinishTemplate { len: 1 },
CreateTemplateRef { id: 2, template_id: 1 },
ReplaceWith { root: 4503599627370497, m: 1 },
ExitTemplateRef {},
EnterTemplateRef { root: 1 },
CreatePlaceholder { root: 3 },
ReplaceWith { root: 4503599627370498, m: 1 },
ExitTemplateRef {},
AppendChildren { many: 1 }
] ]
) )
} }

View file

@ -15,102 +15,83 @@ extern "C" {
pub fn SetNode(this: &Interpreter, id: usize, node: Node); pub fn SetNode(this: &Interpreter, id: usize, node: Node);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn PushRoot(this: &Interpreter, root: u64); pub fn AppendChildren(this: &Interpreter, root: Option<u64>, children: Vec<u64>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn PopRoot(this: &Interpreter); pub fn ReplaceWith(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn AppendChildren(this: &Interpreter, many: u32); pub fn InsertAfter(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32); pub fn InsertBefore(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn InsertAfter(this: &Interpreter, root: u64, n: u32); pub fn Remove(this: &Interpreter, root: Option<u64>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn InsertBefore(this: &Interpreter, root: u64, n: u32); pub fn CreateTextNode(this: &Interpreter, text: JsValue, root: Option<u64>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn Remove(this: &Interpreter, root: u64); pub fn CreateElement(this: &Interpreter, tag: &str, root: Option<u64>, children: u32);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn CreateTextNode(this: &Interpreter, text: JsValue, root: u64); pub fn CreateElementNs(
this: &Interpreter,
tag: &str,
root: Option<u64>,
ns: &str,
children: u32,
);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn CreateElement(this: &Interpreter, tag: &str, root: u64); pub fn CreatePlaceholder(this: &Interpreter, root: Option<u64>);
#[wasm_bindgen(method)]
pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str);
#[wasm_bindgen(method)]
pub fn CreatePlaceholder(this: &Interpreter, root: u64);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn NewEventListener( pub fn NewEventListener(
this: &Interpreter, this: &Interpreter,
name: &str, name: &str,
root: u64, root: Option<u64>,
handler: &Function, handler: &Function,
bubbles: bool, bubbles: bool,
); );
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str, bubbles: bool); pub fn RemoveEventListener(this: &Interpreter, root: Option<u64>, name: &str, bubbles: bool);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn SetText(this: &Interpreter, root: u64, text: JsValue); pub fn SetText(this: &Interpreter, root: Option<u64>, text: JsValue);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn SetAttribute( pub fn SetAttribute(
this: &Interpreter, this: &Interpreter,
root: u64, root: Option<u64>,
field: &str, field: &str,
value: JsValue, value: JsValue,
ns: Option<&str>, ns: Option<&str>,
); );
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str, ns: Option<&str>); pub fn RemoveAttribute(this: &Interpreter, root: Option<u64>, field: &str, ns: Option<&str>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn CreateTemplateRef(this: &Interpreter, id: u64, template_id: u64); pub fn CloneNode(this: &Interpreter, root: Option<u64>, new_id: u64);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn CreateTemplate(this: &Interpreter, id: u64); pub fn CloneNodeChildren(this: &Interpreter, root: Option<u64>, new_ids: Vec<u64>);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn FinishTemplate(this: &Interpreter, len: u32); pub fn FirstChild(this: &Interpreter);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn EnterTemplateRef(this: &Interpreter, id: u64); pub fn NextSibling(this: &Interpreter);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn ExitTemplateRef(this: &Interpreter); pub fn ParentNode(this: &Interpreter);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn CreateElementTemplate( pub fn StoreWithId(this: &Interpreter, id: u64);
this: &Interpreter,
tag: &str,
root: u64,
locally_static: bool,
fully_static: bool,
);
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub fn CreateElementNsTemplate( pub fn SetLastNode(this: &Interpreter, id: u64);
this: &Interpreter,
tag: &str,
id: u64,
ns: &str,
locally_static: bool,
fully_static: bool,
);
#[wasm_bindgen(method)]
pub fn CreateTextNodeTemplate(this: &Interpreter, text: &str, root: u64, locally_static: bool);
#[wasm_bindgen(method)]
pub fn CreatePlaceholderTemplate(this: &Interpreter, root: u64);
} }

View file

@ -1,7 +1,3 @@
// id > Number.MAX_SAFE_INTEGER/2 in template ref
// id <= Number.MAX_SAFE_INTEGER/2 in global nodes
const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2);
export function main() { export function main() {
let root = window.document.getElementById("main"); let root = window.document.getElementById("main");
if (root != null) { if (root != null) {
@ -10,129 +6,6 @@ export function main() {
} }
} }
class TemplateRef {
constructor(fragment, dynamicNodePaths, roots, id) {
this.fragment = fragment;
this.dynamicNodePaths = dynamicNodePaths;
this.roots = roots;
this.id = id;
this.placed = false;
this.nodes = [];
}
build(id) {
if (!this.nodes[id]) {
let current = this.fragment;
const path = this.dynamicNodePaths[id];
for (let i = 0; i < path.length; i++) {
const idx = path[i];
current = current.firstChild;
for (let i2 = 0; i2 < idx; i2++) {
current = current.nextSibling;
}
}
this.nodes[id] = current;
}
}
get(id) {
this.build(id);
return this.nodes[id];
}
parent() {
return this.roots[0].parentNode;
}
first() {
return this.roots[0];
}
last() {
return this.roots[this.roots.length - 1];
}
move() {
// move the root nodes into a new template
this.fragment = new DocumentFragment();
for (let n of this.roots) {
this.fragment.appendChild(n);
}
}
getFragment() {
if (!this.placed) {
this.placed = true;
}
else {
this.move();
}
return this.fragment;
}
}
class Template {
constructor(template_id, id) {
this.nodes = [];
this.dynamicNodePaths = [];
this.template_id = template_id;
this.id = id;
this.template = document.createElement("template");
}
finalize(roots) {
for (let i = 0; i < roots.length; i++) {
let node = roots[i];
let path = [i];
const is_element = node.nodeType == 1;
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
if (!locally_static) {
this.dynamicNodePaths[node.tmplId] = [...path];
}
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
if (traverse_children) {
this.createIds(path, node);
}
this.template.content.appendChild(node);
}
document.head.appendChild(this.template);
}
createIds(path, root) {
let i = 0;
for (let node = root.firstChild; node != null; node = node.nextSibling) {
let new_path = [...path, i];
const is_element = node.nodeType == 1;
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
if (!locally_static) {
this.dynamicNodePaths[node.tmplId] = [...new_path];
}
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
if (traverse_children) {
this.createIds(new_path, node);
}
i++;
}
}
ref(id) {
const template = this.template.content.cloneNode(true);
let roots = [];
this.reconstructingRefrencesIndex = 0;
for (let node = template.firstChild; node != null; node = node.nextSibling) {
roots.push(node);
}
let ref = new TemplateRef(template, this.dynamicNodePaths, roots, id);
// resolve ids for any nodes that can change
for (let i = 0; i < this.dynamicNodePaths.length; i++) {
if (this.dynamicNodePaths[i]) {
ref.build(i);
}
}
return ref;
}
}
class ListenerMap { class ListenerMap {
constructor(root) { constructor(root) {
// bubbling events can listen at the root element // bubbling events can listen at the root element
@ -185,188 +58,155 @@ class ListenerMap {
export class Interpreter { export class Interpreter {
constructor(root) { constructor(root) {
this.root = root; this.root = root;
this.stack = [root]; this.lastNode = root;
this.templateInProgress = null;
this.insideTemplateRef = [];
this.listeners = new ListenerMap(root); this.listeners = new ListenerMap(root);
this.handlers = {}; this.handlers = {};
this.nodes = [root]; this.nodes = [root];
this.templates = []; this.parents = [];
} }
top() { checkAppendParent() {
return this.stack[this.stack.length - 1]; if (this.parents.length > 0) {
} const lastParent = this.parents[this.parents.length - 1];
pop() { lastParent[1]--;
return this.stack.pop(); if (lastParent[1] === 0) {
} this.parents.pop();
currentTemplateId() {
if (this.insideTemplateRef.length) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
}
else {
return null;
}
}
getId(id) {
if (this.templateInProgress !== null) {
return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
}
else {
return this.nodes[id];
}
}
SetNode(id, node) {
if (this.templateInProgress !== null) {
id -= templateIdLimit;
node.tmplId = id;
this.templates[this.templateInProgress].nodes[id] = node;
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
id -= templateIdLimit;
let last = this.insideTemplateRef[this.insideTemplateRef.length - 1];
last.childNodes[id] = node;
if (last.nodeCache[id]) {
last.nodeCache[id] = node;
} }
} lastParent[0].appendChild(this.lastNode);
else {
this.nodes[id] = node;
} }
} }
PushRoot(root) { AppendChildren(root, children) {
const node = this.getId(root); let node;
this.stack.push(node); if (root == null) {
} node = this.lastNode;
PopRoot() { } else {
this.stack.pop(); node = this.nodes[root];
} }
AppendChildren(many) { for (let i = 0; i < children.length; i++) {
let root = this.stack[this.stack.length - (1 + many)]; node.appendChild(this.nodes[children[i]]);
let to_add = this.stack.splice(this.stack.length - many);
for (let i = 0; i < many; i++) {
const child = to_add[i];
if (child instanceof TemplateRef) {
root.appendChild(child.getFragment());
}
else {
root.appendChild(child);
}
} }
} }
ReplaceWith(root_id, m) { ReplaceWith(root, nodes) {
let root = this.getId(root_id); let node;
if (root instanceof TemplateRef) { if (root == null) {
this.InsertBefore(root_id, m); node = this.lastNode;
this.Remove(root_id); } else {
node = this.nodes[root];
} }
else { let els = [];
let els = this.stack.splice(this.stack.length - m).map(function (el) { for (let i = 0; i < nodes.length; i++) {
if (el instanceof TemplateRef) { els.push(this.nodes[nodes[i]])
return el.getFragment();
}
else {
return el;
}
});
root.replaceWith(...els);
} }
node.replaceWith(...els);
} }
InsertAfter(root, n) { InsertAfter(root, nodes) {
const old = this.getId(root); let node;
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { if (root == null) {
if (el instanceof TemplateRef) { node = this.lastNode;
return el.getFragment(); } else {
} node = this.nodes[root];
else {
return el;
}
});
if (old instanceof TemplateRef) {
const last = old.last();
last.after(...new_nodes);
} }
else { let els = [];
old.after(...new_nodes); for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
} }
node.after(...els);
} }
InsertBefore(root, n) { InsertBefore(root, nodes) {
const old = this.getId(root); let node;
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { if (root == null) {
if (el instanceof TemplateRef) { node = this.lastNode;
return el.getFragment(); } else {
} node = this.nodes[root];
else {
return el;
}
});
if (old instanceof TemplateRef) {
const first = old.first();
first.before(...new_nodes);
} }
else { let els = [];
old.before(...new_nodes); for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
} }
node.before(...els);
} }
Remove(root) { Remove(root) {
let node = this.getId(root); let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (node !== undefined) { if (node !== undefined) {
if (node instanceof TemplateRef) { node.remove();
for (let child of node.roots) {
child.remove();
}
}
else {
node.remove();
}
} }
} }
CreateTextNode(text, root) { CreateTextNode(text, root) {
const node = document.createTextNode(text); this.lastNode = document.createTextNode(text);
this.stack.push(node); this.checkAppendParent();
this.SetNode(root, node); if (root != null) {
this.nodes[root] = this.lastNode;
}
} }
CreateElement(tag, root) { CreateElement(tag, root, children) {
const el = document.createElement(tag); this.lastNode = document.createElement(tag);
this.stack.push(el); this.checkAppendParent();
this.SetNode(root, el); if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
} }
CreateElementNs(tag, root, ns) { CreateElementNs(tag, root, ns, children) {
let el = document.createElementNS(ns, tag); this.lastNode = document.createElementNS(ns, tag);
this.stack.push(el); this.checkAppendParent();
this.SetNode(root, el); if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
} }
CreatePlaceholder(root) { CreatePlaceholder(root) {
let el = document.createElement("pre"); this.lastNode = document.createElement("pre");
el.hidden = true; this.lastNode.hidden = true;
this.stack.push(el); this.checkAppendParent();
this.SetNode(root, el); if (root != null) {
this.nodes[root] = this.lastNode;
}
} }
NewEventListener(event_name, root, handler, bubbles) { NewEventListener(event_name, root, handler, bubbles) {
const element = this.getId(root); let node;
if (root >= templateIdLimit) { if (root == null) {
let currentTemplateRefId = this.currentTemplateId(); node = this.lastNode;
root -= templateIdLimit; } else {
element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`); node = this.nodes[root];
} }
else { node.setAttribute("data-dioxus-id", `${root}`);
element.setAttribute("data-dioxus-id", `${root}`); this.listeners.create(event_name, node, handler, bubbles);
}
this.listeners.create(event_name, element, handler, bubbles);
} }
RemoveEventListener(root, event_name, bubbles) { RemoveEventListener(root, event_name, bubbles) {
const element = this.getId(root); let node;
element.removeAttribute(`data-dioxus-id`); if (root == null) {
this.listeners.remove(element, event_name, bubbles); node = this.lastNode;
} else {
node = this.nodes[root];
}
node.removeAttribute(`data-dioxus-id`);
this.listeners.remove(node, event_name, bubbles);
} }
SetText(root, text) { SetText(root, text) {
this.getId(root).data = text; let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
node.data = text;
} }
SetAttribute(root, field, value, ns) { SetAttribute(root, field, value, ns) {
const name = field; const name = field;
const node = this.getId(root); let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns === "style") { if (ns === "style") {
// @ts-ignore // @ts-ignore
node.style[name] = value; node.style[name] = value;
@ -400,7 +240,12 @@ export class Interpreter {
} }
RemoveAttribute(root, field, ns) { RemoveAttribute(root, field, ns) {
const name = field; const name = field;
const node = this.getId(root); let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns == "style") { if (ns == "style") {
node.style.removeProperty(name); node.style.removeProperty(name);
} else if (ns !== null || ns !== undefined) { } else if (ns !== null || ns !== undefined) {
@ -417,94 +262,82 @@ export class Interpreter {
node.removeAttribute(name); node.removeAttribute(name);
} }
} }
CreateTemplateRef(id, template_id) { CloneNode(old, new_id) {
const el = this.templates[template_id].ref(id); let node;
this.nodes[id] = el; if (old === null) {
this.stack.push(el); node = this.lastNode;
} else {
node = this.nodes[old];
}
this.nodes[new_id] = node.cloneNode(true);
} }
CreateTemplate(template_id) { CloneNodeChildren(old, new_ids) {
this.templateInProgress = template_id; let node;
this.templates[template_id] = new Template(template_id, 0); if (old === null) {
node = this.lastNode;
} else {
node = this.nodes[old];
}
const old_node = node.cloneNode(true);
let i = 0;
for (let node = old_node.firstChild; i < new_ids.length; node = node.nextSibling) {
this.nodes[new_ids[i++]] = node;
}
} }
FinishTemplate(many) { FirstChild() {
this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many)); this.lastNode = this.lastNode.firstChild;
this.templateInProgress = null;
} }
EnterTemplateRef(id) { NextSibling() {
this.insideTemplateRef.push(this.nodes[id]); this.lastNode = this.lastNode.nextSibling;
} }
ExitTemplateRef() { ParentNode() {
this.insideTemplateRef.pop(); this.lastNode = this.lastNode.parentNode;
}
StoreWithId(id) {
this.nodes[id] = this.lastNode;
}
SetLastNode(root) {
this.lastNode = this.nodes[root];
} }
handleEdits(edits) { handleEdits(edits) {
for (let edit of edits) { for (let edit of edits) {
this.handleEdit(edit); this.handleEdit(edit);
} }
} }
CreateElementTemplate(tag, root, locally_static, fully_static) {
const el = document.createElement(tag);
this.stack.push(el);
this.SetNode(root, el);
if (!locally_static)
el.setAttribute("data-dioxus-dynamic", "true");
if (fully_static)
el.setAttribute("data-dioxus-fully-static", fully_static);
}
CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) {
const el = document.createElementNS(ns, tag);
this.stack.push(el);
this.SetNode(root, el);
if (!locally_static)
el.setAttribute("data-dioxus-dynamic", "true");
if (fully_static)
el.setAttribute("data-dioxus-fully-static", fully_static);
}
CreateTextNodeTemplate(text, root, locally_static) {
const node = document.createTextNode(text);
this.stack.push(node);
this.SetNode(root, node);
}
CreatePlaceholderTemplate(root) {
const el = document.createElement("pre");
el.setAttribute("data-dioxus-dynamic", "true");
el.hidden = true;
this.stack.push(el);
this.SetNode(root, el);
}
handleEdit(edit) { handleEdit(edit) {
switch (edit.type) { switch (edit.type) {
case "PushRoot": case "PushRoot":
this.PushRoot(BigInt(edit.root)); this.PushRoot(edit.root);
break; break;
case "AppendChildren": case "AppendChildren":
this.AppendChildren(edit.many); this.AppendChildren(edit.root, edit.children);
break; break;
case "ReplaceWith": case "ReplaceWith":
this.ReplaceWith(BigInt(edit.root), edit.m); this.ReplaceWith(edit.root, edit.nodes);
break; break;
case "InsertAfter": case "InsertAfter":
this.InsertAfter(BigInt(edit.root), edit.n); this.InsertAfter(edit.root, edit.nodes);
break; break;
case "InsertBefore": case "InsertBefore":
this.InsertBefore(BigInt(edit.root), edit.n); this.InsertBefore(edit.root, edit.nodes);
break; break;
case "Remove": case "Remove":
this.Remove(BigInt(edit.root)); this.Remove(edit.root);
break; break;
case "CreateTextNode": case "CreateTextNode":
this.CreateTextNode(edit.text, BigInt(edit.root)); this.CreateTextNode(edit.text, edit.root);
break; break;
case "CreateElement": case "CreateElement":
this.CreateElement(edit.tag, BigInt(edit.root)); this.CreateElement(edit.tag, edit.root, edit.children);
break; break;
case "CreateElementNs": case "CreateElementNs":
this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns); this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children);
break; break;
case "CreatePlaceholder": case "CreatePlaceholder":
this.CreatePlaceholder(BigInt(edit.root)); this.CreatePlaceholder(edit.root);
break; break;
case "RemoveEventListener": case "RemoveEventListener":
this.RemoveEventListener(BigInt(edit.root), edit.event_name); this.RemoveEventListener(edit.root, edit.event_name);
break; break;
case "NewEventListener": case "NewEventListener":
// this handler is only provided on desktop implementations since this // this handler is only provided on desktop implementations since this
@ -588,16 +421,7 @@ export class Interpreter {
if (realId === null) { if (realId === null) {
return; return;
} }
if (realId.includes(",")) { realId = parseInt(realId);
realId = realId.split(',');
realId = {
template_ref_id: parseInt(realId[0]),
template_node_id: parseInt(realId[1]),
};
}
else {
realId = parseInt(realId);
}
window.ipc.postMessage( window.ipc.postMessage(
serializeIpcMessage("user_event", { serializeIpcMessage("user_event", {
event: edit.event_name, event: edit.event_name,
@ -607,47 +431,38 @@ export class Interpreter {
); );
} }
}; };
this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name)); this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name));
break; break;
case "SetText": case "SetText":
this.SetText(BigInt(edit.root), edit.text); this.SetText(edit.root, edit.text);
break; break;
case "SetAttribute": case "SetAttribute":
this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns); this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
break; break;
case "RemoveAttribute": case "RemoveAttribute":
this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns); this.RemoveAttribute(edit.root, edit.name, edit.ns);
break; break;
case "PopRoot": case "CloneNode":
this.PopRoot(); this.CloneNode(edit.id, edit.new_id);
break; break;
case "CreateTemplateRef": case "CloneNodeChildren":
this.CreateTemplateRef(BigInt(edit.id), edit.template_id); this.CloneNodeChildren(edit.id, edit.new_ids);
break; break;
case "CreateTemplate": case "FirstChild":
this.CreateTemplate(BigInt(edit.id)); this.FirstChild();
break; break;
case "FinishTemplate": case "NextSibling":
this.FinishTemplate(edit.len); this.NextSibling();
break; break;
case "EnterTemplateRef": case "ParentNode":
this.EnterTemplateRef(BigInt(edit.root)); this.ParentNode();
break; break;
case "ExitTemplateRef": case "StoreWithId":
this.ExitTemplateRef(); this.StoreWithId(BigInt(edit.id));
break; break;
case "CreateElementTemplate": case "SetLastNode":
this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static); this.SetLastNode(BigInt(edit.id));
break;
case "CreateElementNsTemplate":
this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static);
break;
case "CreateTextNodeTemplate":
this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static);
break;
case "CreatePlaceholderTemplate":
this.CreatePlaceholderTemplate(BigInt(edit.root));
break; break;
} }
} }

View file

@ -5,7 +5,7 @@
use std::any::Any; use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
use dioxus_core::GlobalNodeId; use dioxus_core::ElementId;
use dioxus_core::{EventPriority, UserEvent}; use dioxus_core::{EventPriority, UserEvent};
use dioxus_html::event_bubbles; use dioxus_html::event_bubbles;
use dioxus_html::on::*; use dioxus_html::on::*;
@ -26,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
struct ImEvent { struct ImEvent {
event: String, event: String,
mounted_dom_id: GlobalNodeId, mounted_dom_id: ElementId,
contents: serde_json::Value, contents: serde_json::Value,
} }

View file

@ -35,127 +35,6 @@ class IPC {
} }
} }
// id > Number.MAX_SAFE_INTEGER/2 in template ref
// id <= Number.MAX_SAFE_INTEGER/2 in global nodes
const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2);
class TemplateRef {
constructor(fragment, dynamicNodePaths, roots, id) {
this.fragment = fragment;
this.dynamicNodePaths = dynamicNodePaths;
this.roots = roots;
this.id = id;
this.placed = false;
this.nodes = [];
}
build(id) {
if (!this.nodes[id]) {
let current = this.fragment;
const path = this.dynamicNodePaths[id];
for (let i = 0; i < path.length; i++) {
const idx = path[i];
current = current.firstChild;
for (let i2 = 0; i2 < idx; i2++) {
current = current.nextSibling;
}
}
this.nodes[id] = current;
}
}
get(id) {
this.build(id);
return this.nodes[id];
}
parent() {
return this.roots[0].parentNode;
}
first() {
return this.roots[0];
}
last() {
return this.roots[this.roots.length - 1];
}
move() {
// move the root nodes into a new template
this.fragment = new DocumentFragment();
for (let n of this.roots) {
this.fragment.appendChild(n);
}
}
getFragment() {
if (!this.placed) {
this.placed = true;
}
else {
this.move();
}
return this.fragment;
}
}
class Template {
constructor(template_id, id) {
this.nodes = [];
this.dynamicNodePaths = [];
this.template_id = template_id;
this.id = id;
this.template = document.createElement("template");
this.reconstructingRefrencesIndex = null;
}
finalize(roots) {
for (let i = 0; i < roots.length; i++) {
let node = roots[i];
let path = [i];
const is_element = node.nodeType == 1;
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
if (!locally_static) {
this.dynamicNodePaths[node.tmplId] = [...path];
}
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
if (traverse_children) {
this.createIds(path, node);
}
this.template.content.appendChild(node);
}
document.head.appendChild(this.template);
}
createIds(path, root) {
let i = 0;
for (let node = root.firstChild; node != null; node = node.nextSibling) {
let new_path = [...path, i];
const is_element = node.nodeType == 1;
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
if (!locally_static) {
this.dynamicNodePaths[node.tmplId] = [...new_path];
}
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
if (traverse_children) {
this.createIds(new_path, node);
}
i++;
}
}
ref(id) {
const template = this.template.content.cloneNode(true);
let roots = [];
this.reconstructingRefrencesIndex = 0;
for (let node = template.firstChild; node != null; node = node.nextSibling) {
roots.push(node);
}
return new TemplateRef(template, this.dynamicNodePaths, roots, id);
}
}
class ListenerMap { class ListenerMap {
constructor(root) { constructor(root) {
// bubbling events can listen at the root element // bubbling events can listen at the root element
@ -208,188 +87,155 @@ class ListenerMap {
class Interpreter { class Interpreter {
constructor(root) { constructor(root) {
this.root = root; this.root = root;
this.stack = [root]; this.lastNode = root;
this.templateInProgress = null;
this.insideTemplateRef = [];
this.listeners = new ListenerMap(root); this.listeners = new ListenerMap(root);
this.handlers = {}; this.handlers = {};
this.nodes = [root]; this.nodes = [root];
this.templates = []; this.parents = [];
} }
top() { checkAppendParent() {
return this.stack[this.stack.length - 1]; if (this.parents.length > 0) {
} const lastParent = this.parents[this.parents.length - 1];
pop() { lastParent[1]--;
return this.stack.pop(); if (lastParent[1] === 0) {
} this.parents.pop();
currentTemplateId() {
if (this.insideTemplateRef.length) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
}
else {
return null;
}
}
getId(id) {
if (this.templateInProgress !== null) {
return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
}
else {
return this.nodes[id];
}
}
SetNode(id, node) {
if (this.templateInProgress !== null) {
id -= templateIdLimit;
node.tmplId = id;
this.templates[this.templateInProgress].nodes[id] = node;
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
id -= templateIdLimit;
let last = this.insideTemplateRef[this.insideTemplateRef.length - 1];
last.childNodes[id] = node;
if (last.nodeCache[id]) {
last.nodeCache[id] = node;
} }
} lastParent[0].appendChild(this.lastNode);
else {
this.nodes[id] = node;
} }
} }
PushRoot(root) { AppendChildren(root, children) {
const node = this.getId(root); let node;
this.stack.push(node); if (root == null) {
} node = this.lastNode;
PopRoot() { } else {
this.stack.pop(); node = this.nodes[root];
} }
AppendChildren(many) { for (let i = 0; i < children.length; i++) {
let root = this.stack[this.stack.length - (1 + many)]; node.appendChild(this.nodes[children[i]]);
let to_add = this.stack.splice(this.stack.length - many);
for (let i = 0; i < many; i++) {
const child = to_add[i];
if (child instanceof TemplateRef) {
root.appendChild(child.getFragment());
}
else {
root.appendChild(child);
}
} }
} }
ReplaceWith(root_id, m) { ReplaceWith(root, nodes) {
let root = this.getId(root_id); let node;
if (root instanceof TemplateRef) { if (root == null) {
this.InsertBefore(root_id, m); node = this.lastNode;
this.Remove(root_id); } else {
node = this.nodes[root];
} }
else { let els = [];
let els = this.stack.splice(this.stack.length - m).map(function (el) { for (let i = 0; i < nodes.length; i++) {
if (el instanceof TemplateRef) { els.push(this.nodes[nodes[i]])
return el.getFragment();
}
else {
return el;
}
});
root.replaceWith(...els);
} }
node.replaceWith(...els);
} }
InsertAfter(root, n) { InsertAfter(root, nodes) {
const old = this.getId(root); let node;
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { if (root == null) {
if (el instanceof TemplateRef) { node = this.lastNode;
return el.getFragment(); } else {
} node = this.nodes[root];
else {
return el;
}
});
if (old instanceof TemplateRef) {
const last = old.last();
last.after(...new_nodes);
} }
else { let els = [];
old.after(...new_nodes); for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
} }
node.after(...els);
} }
InsertBefore(root, n) { InsertBefore(root, nodes) {
const old = this.getId(root); let node;
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { if (root == null) {
if (el instanceof TemplateRef) { node = this.lastNode;
return el.getFragment(); } else {
} node = this.nodes[root];
else {
return el;
}
});
if (old instanceof TemplateRef) {
const first = old.first();
first.before(...new_nodes);
} }
else { let els = [];
old.before(...new_nodes); for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
} }
node.before(...els);
} }
Remove(root) { Remove(root) {
let node = this.getId(root); let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (node !== undefined) { if (node !== undefined) {
if (node instanceof TemplateRef) { node.remove();
for (let child of node.roots) {
child.remove();
}
}
else {
node.remove();
}
} }
} }
CreateTextNode(text, root) { CreateTextNode(text, root) {
const node = document.createTextNode(text); this.lastNode = document.createTextNode(text);
this.stack.push(node); this.checkAppendParent();
this.SetNode(root, node); if (root != null) {
this.nodes[root] = this.lastNode;
}
} }
CreateElement(tag, root) { CreateElement(tag, root, children) {
const el = document.createElement(tag); this.lastNode = document.createElement(tag);
this.stack.push(el); this.checkAppendParent();
this.SetNode(root, el); if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
} }
CreateElementNs(tag, root, ns) { CreateElementNs(tag, root, ns, children) {
let el = document.createElementNS(ns, tag); this.lastNode = document.createElementNS(ns, tag);
this.stack.push(el); this.checkAppendParent();
this.SetNode(root, el); if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
} }
CreatePlaceholder(root) { CreatePlaceholder(root) {
let el = document.createElement("pre"); this.lastNode = document.createElement("pre");
el.hidden = true; this.lastNode.hidden = true;
this.stack.push(el); this.checkAppendParent();
this.SetNode(root, el); if (root != null) {
this.nodes[root] = this.lastNode;
}
} }
NewEventListener(event_name, root, handler, bubbles) { NewEventListener(event_name, root, handler, bubbles) {
const element = this.getId(root); let node;
if (root >= templateIdLimit) { if (root == null) {
let currentTemplateRefId = this.currentTemplateId(); node = this.lastNode;
root -= templateIdLimit; } else {
element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`); node = this.nodes[root];
} }
else { node.setAttribute("data-dioxus-id", `${root}`);
element.setAttribute("data-dioxus-id", `${root}`); this.listeners.create(event_name, node, handler, bubbles);
}
this.listeners.create(event_name, element, handler, bubbles);
} }
RemoveEventListener(root, event_name, bubbles) { RemoveEventListener(root, event_name, bubbles) {
const element = this.getId(root); let node;
element.removeAttribute(`data-dioxus-id`); if (root == null) {
this.listeners.remove(element, event_name, bubbles); node = this.lastNode;
} else {
node = this.nodes[root];
}
node.removeAttribute(`data-dioxus-id`);
this.listeners.remove(node, event_name, bubbles);
} }
SetText(root, text) { SetText(root, text) {
this.getId(root).data = text; let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
node.data = text;
} }
SetAttribute(root, field, value, ns) { SetAttribute(root, field, value, ns) {
const name = field; const name = field;
const node = this.getId(root); let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns === "style") { if (ns === "style") {
// @ts-ignore // @ts-ignore
node.style[name] = value; node.style[name] = value;
@ -423,7 +269,12 @@ class Interpreter {
} }
RemoveAttribute(root, field, ns) { RemoveAttribute(root, field, ns) {
const name = field; const name = field;
const node = this.getId(root); let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns == "style") { if (ns == "style") {
node.style.removeProperty(name); node.style.removeProperty(name);
} else if (ns !== null || ns !== undefined) { } else if (ns !== null || ns !== undefined) {
@ -440,94 +291,82 @@ class Interpreter {
node.removeAttribute(name); node.removeAttribute(name);
} }
} }
CreateTemplateRef(id, template_id) { CloneNode(old, new_id) {
const el = this.templates[template_id].ref(id); let node;
this.nodes[id] = el; if (old === null) {
this.stack.push(el); node = this.lastNode;
} else {
node = this.nodes[old];
}
this.nodes[new_id] = node.cloneNode(true);
} }
CreateTemplate(template_id) { CloneNodeChildren(old, new_ids) {
this.templateInProgress = template_id; let node;
this.templates[template_id] = new Template(template_id, 0); if (old === null) {
node = this.lastNode;
} else {
node = this.nodes[old];
}
const old_node = node.cloneNode(true);
let i = 0;
for (let node = old_node.firstChild; i < new_ids.length; node = node.nextSibling) {
this.nodes[new_ids[i++]] = node;
}
} }
FinishTemplate(many) { FirstChild() {
this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many)); this.lastNode = this.lastNode.firstChild;
this.templateInProgress = null;
} }
EnterTemplateRef(id) { NextSibling() {
this.insideTemplateRef.push(this.nodes[id]); this.lastNode = this.lastNode.nextSibling;
} }
ExitTemplateRef() { ParentNode() {
this.insideTemplateRef.pop(); this.lastNode = this.lastNode.parentNode;
}
StoreWithId(id) {
this.nodes[id] = this.lastNode;
}
SetLastNode(root) {
this.lastNode = this.nodes[root];
} }
handleEdits(edits) { handleEdits(edits) {
for (let edit of edits) { for (let edit of edits) {
this.handleEdit(edit); this.handleEdit(edit);
} }
} }
CreateElementTemplate(tag, root, locally_static, fully_static) {
const el = document.createElement(tag);
this.stack.push(el);
this.SetNode(root, el);
if (!locally_static)
el.setAttribute("data-dioxus-dynamic", "true");
if (fully_static)
el.setAttribute("data-dioxus-fully-static", fully_static);
}
CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) {
const el = document.createElementNS(ns, tag);
this.stack.push(el);
this.SetNode(root, el);
if (!locally_static)
el.setAttribute("data-dioxus-dynamic", "true");
if (fully_static)
el.setAttribute("data-dioxus-fully-static", fully_static);
}
CreateTextNodeTemplate(text, root, locally_static) {
const node = document.createTextNode(text);
this.stack.push(node);
this.SetNode(root, node);
}
CreatePlaceholderTemplate(root) {
const el = document.createElement("pre");
el.setAttribute("data-dioxus-dynamic", "true");
el.hidden = true;
this.stack.push(el);
this.SetNode(root, el);
}
handleEdit(edit) { handleEdit(edit) {
switch (edit.type) { switch (edit.type) {
case "PushRoot": case "PushRoot":
this.PushRoot(BigInt(edit.root)); this.PushRoot(edit.root);
break; break;
case "AppendChildren": case "AppendChildren":
this.AppendChildren(edit.many); this.AppendChildren(edit.root, edit.children);
break; break;
case "ReplaceWith": case "ReplaceWith":
this.ReplaceWith(BigInt(edit.root), edit.m); this.ReplaceWith(edit.root, edit.nodes);
break; break;
case "InsertAfter": case "InsertAfter":
this.InsertAfter(BigInt(edit.root), edit.n); this.InsertAfter(edit.root, edit.nodes);
break; break;
case "InsertBefore": case "InsertBefore":
this.InsertBefore(BigInt(edit.root), edit.n); this.InsertBefore(edit.root, edit.nodes);
break; break;
case "Remove": case "Remove":
this.Remove(BigInt(edit.root)); this.Remove(edit.root);
break; break;
case "CreateTextNode": case "CreateTextNode":
this.CreateTextNode(edit.text, BigInt(edit.root)); this.CreateTextNode(edit.text, edit.root);
break; break;
case "CreateElement": case "CreateElement":
this.CreateElement(edit.tag, BigInt(edit.root)); this.CreateElement(edit.tag, edit.root, edit.children);
break; break;
case "CreateElementNs": case "CreateElementNs":
this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns); this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children);
break; break;
case "CreatePlaceholder": case "CreatePlaceholder":
this.CreatePlaceholder(BigInt(edit.root)); this.CreatePlaceholder(edit.root);
break; break;
case "RemoveEventListener": case "RemoveEventListener":
this.RemoveEventListener(BigInt(edit.root), edit.event_name); this.RemoveEventListener(edit.root, edit.event_name);
break; break;
case "NewEventListener": case "NewEventListener":
// this handler is only provided on desktop implementations since this // this handler is only provided on desktop implementations since this
@ -547,7 +386,7 @@ class Interpreter {
event.preventDefault(); event.preventDefault();
const href = target.getAttribute("href"); const href = target.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) { if (href !== "" && href !== null && href !== undefined) {
window.ipc.send( window.ipc.postMessage(
serializeIpcMessage("browser_open", { href }) serializeIpcMessage("browser_open", { href })
); );
} }
@ -611,16 +450,7 @@ class Interpreter {
if (realId === null) { if (realId === null) {
return; return;
} }
if (realId.includes(",")) { realId = parseInt(realId);
realId = realId.split(',');
realId = {
template_ref_id: parseInt(realId[0]),
template_node_id: parseInt(realId[1]),
};
}
else {
realId = parseInt(realId);
}
window.ipc.send( window.ipc.send(
serializeIpcMessage("user_event", { serializeIpcMessage("user_event", {
event: edit.event_name, event: edit.event_name,
@ -630,47 +460,38 @@ class Interpreter {
); );
} }
}; };
this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name)); this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name));
break; break;
case "SetText": case "SetText":
this.SetText(BigInt(edit.root), edit.text); this.SetText(edit.root, edit.text);
break; break;
case "SetAttribute": case "SetAttribute":
this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns); this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
break; break;
case "RemoveAttribute": case "RemoveAttribute":
this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns); this.RemoveAttribute(edit.root, edit.name, edit.ns);
break; break;
case "PopRoot": case "CloneNode":
this.PopRoot(); this.CloneNode(edit.id, edit.new_id);
break; break;
case "CreateTemplateRef": case "CloneNodeChildren":
this.CreateTemplateRef(BigInt(edit.id), edit.template_id); this.CloneNodeChildren(edit.id, edit.new_ids);
break; break;
case "CreateTemplate": case "FirstChild":
this.CreateTemplate(BigInt(edit.id)); this.FirstChild();
break; break;
case "FinishTemplate": case "NextSibling":
this.FinishTemplate(edit.len); this.NextSibling();
break; break;
case "EnterTemplateRef": case "ParentNode":
this.EnterTemplateRef(BigInt(edit.root)); this.ParentNode();
break; break;
case "ExitTemplateRef": case "StoreWithId":
this.ExitTemplateRef(); this.StoreWithId(BigInt(edit.id));
break; break;
case "CreateElementTemplate": case "SetLastNode":
this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static); this.SetLastNode(BigInt(edit.id));
break;
case "CreateElementNsTemplate":
this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static);
break;
case "CreateTextNodeTemplate":
this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static);
break;
case "CreatePlaceholderTemplate":
this.CreatePlaceholderTemplate(BigInt(edit.root));
break; break;
} }
} }

View file

@ -164,59 +164,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let gen = quote! { let gen = quote! {
impl State for #type_name { impl State for #type_name {
fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_core::GlobalNodeId>,T2: dioxus_native_core::traversable::Traversable<Node = dioxus_native_core::real_dom::NodeData, Id = dioxus_core::GlobalNodeId>>( fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_native_core::RealNodeId>,T2: dioxus_native_core::traversable::Traversable<Node = dioxus_native_core::real_dom::NodeData, Id = dioxus_native_core::RealNodeId>>(
dirty: &[(dioxus_core::GlobalNodeId, dioxus_native_core::node_ref::NodeMask)], dirty: &[(dioxus_native_core::RealNodeId, dioxus_native_core::node_ref::NodeMask)],
state_tree: &'a mut T, state_tree: &'a mut T,
rdom: &'a T2, rdom: &'a T2,
ctx: &anymap::AnyMap, ctx: &anymap::AnyMap,
) -> rustc_hash::FxHashSet<dioxus_core::GlobalNodeId>{ ) -> rustc_hash::FxHashSet<dioxus_native_core::RealNodeId>{
#[derive(Eq, PartialEq)]
struct HeightOrdering {
height: u16,
id: dioxus_core::GlobalNodeId,
}
impl HeightOrdering {
fn new(height: u16, id: dioxus_core::GlobalNodeId) -> Self {
HeightOrdering {
height,
id,
}
}
}
// not the ordering after height is just for deduplication it can be any ordering as long as it is consistent
impl Ord for HeightOrdering {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.height.cmp(&other.height).then(match (self.id, other.id){
(
dioxus_core::GlobalNodeId::TemplateId {
template_ref_id,
template_node_id,
},
dioxus_core::GlobalNodeId::TemplateId {
template_ref_id: o_template_ref_id,
template_node_id: o_template_node_id,
},
) => template_ref_id
.0
.cmp(&o_template_ref_id.0)
.then(template_node_id.0.cmp(&o_template_node_id.0)),
(dioxus_core::GlobalNodeId::TemplateId { .. }, dioxus_core::GlobalNodeId::VNodeId(_)) => std::cmp::Ordering::Less,
(dioxus_core::GlobalNodeId::VNodeId(_), dioxus_core::GlobalNodeId::TemplateId { .. }) => {
std::cmp::Ordering::Greater
}
(dioxus_core::GlobalNodeId::VNodeId(s_id), dioxus_core::GlobalNodeId::VNodeId(o_id)) => s_id.0.cmp(&o_id.0),
})
}
}
impl PartialOrd for HeightOrdering {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other))
}
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct MembersDirty { struct MembersDirty {
#(#members: bool, )* #(#members: bool, )*
@ -238,7 +191,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let mut dirty_elements = rustc_hash::FxHashSet::default(); let mut dirty_elements = rustc_hash::FxHashSet::default();
// the states of any elements that are dirty // the states of any elements that are dirty
let mut states: rustc_hash::FxHashMap<dioxus_core::GlobalNodeId, MembersDirty> = rustc_hash::FxHashMap::default(); let mut states: rustc_hash::FxHashMap<dioxus_native_core::RealNodeId, MembersDirty> = rustc_hash::FxHashMap::default();
for (id, mask) in dirty { for (id, mask) in dirty {
let members_dirty = MembersDirty { let members_dirty = MembersDirty {
@ -408,7 +361,7 @@ impl<'a> StateStruct<'a> {
let insert = dep.child.iter().map(|d|{ let insert = dep.child.iter().map(|d|{
if *d == mem { if *d == mem {
quote! { quote! {
let seeking = HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id); let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id);
if let Err(idx) = resolution_order if let Err(idx) = resolution_order
.binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){ .binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){
resolution_order.insert( resolution_order.insert(
@ -453,7 +406,7 @@ impl<'a> StateStruct<'a> {
let insert = dep.parent.iter().map(|d| { let insert = dep.parent.iter().map(|d| {
if *d == mem { if *d == mem {
quote! { quote! {
let seeking = HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id); let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id);
if let Err(idx) = resolution_order if let Err(idx) = resolution_order
.binary_search(&seeking){ .binary_search(&seeking){
resolution_order.insert( resolution_order.insert(
@ -508,7 +461,7 @@ impl<'a> StateStruct<'a> {
DependencyKind::Parent => { DependencyKind::Parent => {
quote! { quote! {
// resolve parent dependant state // resolve parent dependant state
let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>(); let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
resolution_order.sort(); resolution_order.sort();
let mut i = 0; let mut i = 0;
while i < resolution_order.len(){ while i < resolution_order.len(){
@ -528,7 +481,7 @@ impl<'a> StateStruct<'a> {
DependencyKind::Child => { DependencyKind::Child => {
quote! { quote! {
// resolve child dependant state // resolve child dependant state
let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>(); let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
resolution_order.sort_by(|height_ordering1, height_ordering2| { resolution_order.sort_by(|height_ordering1, height_ordering2| {
height_ordering1.cmp(&height_ordering2).reverse() height_ordering1.cmp(&height_ordering2).reverse()
}); });

View file

@ -1,5 +1,4 @@
use anymap::AnyMap; use anymap::AnyMap;
use dioxus::core as dioxus_core;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_native_core::node_ref::*; use dioxus_native_core::node_ref::*;
use dioxus_native_core::real_dom::*; use dioxus_native_core::real_dom::*;

View file

@ -1,7 +1,8 @@
use dioxus::core::{self as dioxus_core, GlobalNodeId}; use dioxus::core::ElementId;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::real_dom::RealDom;
use dioxus_native_core::state::State; use dioxus_native_core::state::State;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::State; use dioxus_native_core_macro::State;
#[derive(State, Default, Clone)] #[derive(State, Default, Clone)]
@ -33,37 +34,13 @@ fn remove_node() {
let _to_update = dom.apply_mutations(vec![create]); let _to_update = dom.apply_mutations(vec![create]);
assert_eq!( assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0);
dom[GlobalNodeId::TemplateId { assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1);
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(0),
}]
.node_data
.height,
1
);
assert_eq!(
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(1),
}]
.node_data
.height,
2
);
dom.apply_mutations(vec![edit]); dom.apply_mutations(vec![edit]);
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 3);
assert_eq!( assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0);
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(2),
template_node_id: dioxus::prelude::TemplateNodeId(0),
}]
.node_data
.height,
1
);
} }
#[test] #[test]
@ -90,36 +67,12 @@ fn add_node() {
let _to_update = dom.apply_mutations(vec![create]); let _to_update = dom.apply_mutations(vec![create]);
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 2);
assert_eq!( assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1);
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(0),
}]
.node_data
.height,
1
);
dom.apply_mutations(vec![update]); dom.apply_mutations(vec![update]);
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 3);
assert_eq!( assert_eq!(dom[RealNodeId::ElementId(ElementId(3))].node_data.height, 0);
dom[GlobalNodeId::TemplateId { assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1);
template_ref_id: dioxus_core::ElementId(2),
template_node_id: dioxus::prelude::TemplateNodeId(0),
}]
.node_data
.height,
1
);
assert_eq!(
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(2),
template_node_id: dioxus::prelude::TemplateNodeId(1),
}]
.node_data
.height,
2
);
} }

View file

@ -1,7 +1,8 @@
use dioxus::core::{self as dioxus_core, GlobalNodeId}; use dioxus::core::ElementId;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::real_dom::RealDom;
use dioxus_native_core::state::State; use dioxus_native_core::state::State;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::State; use dioxus_native_core_macro::State;
#[derive(Default, Clone, State)] #[derive(Default, Clone, State)]
@ -24,16 +25,8 @@ fn initial_build_simple() {
let _to_update = dom.apply_mutations(vec![mutations]); let _to_update = dom.apply_mutations(vec![mutations]);
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 2);
assert_eq!( assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1);
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(0),
}]
.node_data
.height,
1
);
} }
#[test] #[test]
@ -60,59 +53,11 @@ fn initial_build_with_children() {
let mut dom: RealDom<Empty> = RealDom::new(); let mut dom: RealDom<Empty> = RealDom::new();
let _to_update = dom.apply_mutations(vec![mutations]); let _to_update = dom.apply_mutations(vec![mutations]);
assert_eq!(dom.size(), 1); assert_eq!(dom.size(), 2);
assert_eq!( assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1);
dom[GlobalNodeId::TemplateId { assert_eq!(dom[RealNodeId::UnaccessableId(6)].node_data.height, 2);
template_ref_id: dioxus_core::ElementId(1), assert_eq!(dom[RealNodeId::UnaccessableId(5)].node_data.height, 3);
template_node_id: dioxus::prelude::TemplateNodeId(0), assert_eq!(dom[RealNodeId::UnaccessableId(8)].node_data.height, 3);
}] assert_eq!(dom[RealNodeId::UnaccessableId(10)].node_data.height, 3);
.node_data assert_eq!(dom[RealNodeId::UnaccessableId(9)].node_data.height, 4);
.height,
1
);
assert_eq!(
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(1),
}]
.node_data
.height,
2
);
assert_eq!(
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(2),
}]
.node_data
.height,
3
);
assert_eq!(
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(3),
}]
.node_data
.height,
3
);
assert_eq!(
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(4),
}]
.node_data
.height,
4
);
assert_eq!(
dom[GlobalNodeId::TemplateId {
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(5),
}]
.node_data
.height,
3
);
} }

View file

@ -1,4 +1,3 @@
use dioxus::core as dioxus_core;
use dioxus::core_macro::rsx_without_templates; use dioxus::core_macro::rsx_without_templates;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_native_core::{ use dioxus_native_core::{

View file

@ -1,12 +1,11 @@
use anymap::AnyMap; use anymap::AnyMap;
use dioxus::core::ElementId; use dioxus::core::ElementId;
use dioxus::core::{self as dioxus_core, GlobalNodeId};
use dioxus::core::{AttributeValue, DomEdit, Mutations}; use dioxus::core::{AttributeValue, DomEdit, Mutations};
use dioxus::core_macro::rsx_without_templates; use dioxus::core_macro::rsx_without_templates;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_native_core::node_ref::*;
use dioxus_native_core::real_dom::*; use dioxus_native_core::real_dom::*;
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
use dioxus_native_core::{node_ref::*, RealNodeId};
use dioxus_native_core_macro::State; use dioxus_native_core_macro::State;
#[derive(Debug, Clone, Default, State)] #[derive(Debug, Clone, Default, State)]
@ -195,10 +194,7 @@ fn state_initial() {
ctx.insert(42u32); ctx.insert(42u32);
let _to_rerender = dom.update_state(nodes_updated, ctx); let _to_rerender = dom.update_state(nodes_updated, ctx);
let root_div = &dom[GlobalNodeId::TemplateId { let root_div = &dom[RealNodeId::ElementId(ElementId(2))];
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(0),
}];
assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
assert_eq!( assert_eq!(
root_div.state.bubbled.1, root_div.state.bubbled.1,
@ -218,10 +214,7 @@ fn state_initial() {
assert_eq!(root_div.state.node.0, Some("div".to_string())); assert_eq!(root_div.state.node.0, Some("div".to_string()));
assert_eq!(root_div.state.node.1, vec![]); assert_eq!(root_div.state.node.1, vec![]);
let child_p = &dom[GlobalNodeId::TemplateId { let child_p = &dom[RealNodeId::UnaccessableId(3)];
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(1),
}];
assert_eq!(child_p.state.bubbled.0, Some("p".to_string())); assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
assert_eq!(child_p.state.bubbled.1, Vec::new()); assert_eq!(child_p.state.bubbled.1, Vec::new());
assert_eq!(child_p.state.pushed.0, Some("p".to_string())); assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
@ -241,10 +234,7 @@ fn state_initial() {
vec![("color".to_string(), "red".to_string())] vec![("color".to_string(), "red".to_string())]
); );
let child_h1 = &dom[GlobalNodeId::TemplateId { let child_h1 = &dom[RealNodeId::UnaccessableId(4)];
template_ref_id: dioxus_core::ElementId(1),
template_node_id: dioxus::prelude::TemplateNodeId(2),
}];
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string())); assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
assert_eq!(child_h1.state.bubbled.1, Vec::new()); assert_eq!(child_h1.state.bubbled.1, Vec::new());
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string())); assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
@ -313,7 +303,7 @@ fn state_reduce_parent_called_minimally_on_update() {
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let nodes_updated = dom.apply_mutations(vec![Mutations { let nodes_updated = dom.apply_mutations(vec![Mutations {
edits: vec![DomEdit::SetAttribute { edits: vec![DomEdit::SetAttribute {
root: 1, root: Some(1),
field: "width", field: "width",
value: AttributeValue::Text("99%"), value: AttributeValue::Text("99%"),
ns: Some("style"), ns: Some("style"),
@ -382,7 +372,7 @@ fn state_reduce_child_called_minimally_on_update() {
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
let nodes_updated = dom.apply_mutations(vec![Mutations { let nodes_updated = dom.apply_mutations(vec![Mutations {
edits: vec![DomEdit::SetAttribute { edits: vec![DomEdit::SetAttribute {
root: 4, root: Some(4),
field: "width", field: "width",
value: AttributeValue::Text("99%"), value: AttributeValue::Text("99%"),
ns: Some("style"), ns: Some("style"),
@ -395,7 +385,7 @@ fn state_reduce_child_called_minimally_on_update() {
dom.traverse_depth_first(|n| { dom.traverse_depth_first(|n| {
assert_eq!( assert_eq!(
n.state.part1.child_counter.0, n.state.part1.child_counter.0,
if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id { if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id {
if id > 4 { if id > 4 {
1 1
} else { } else {
@ -407,7 +397,7 @@ fn state_reduce_child_called_minimally_on_update() {
); );
assert_eq!( assert_eq!(
n.state.child_counter.0, n.state.child_counter.0,
if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id { if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id {
if id > 4 { if id > 4 {
1 1
} else { } else {

View file

@ -19,6 +19,7 @@ taffy = "0.1.0"
smallvec = "1.6" smallvec = "1.6"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
anymap = "0.12.1" anymap = "0.12.1"
slab = "0.4"
[dev-dependencies] [dev-dependencies]
rand = "0.8.5" rand = "0.8.5"

View file

@ -1,8 +1,78 @@
use std::cmp::Ordering;
use dioxus_core::ElementId;
pub mod layout_attributes; pub mod layout_attributes;
pub mod node_ref; pub mod node_ref;
pub mod real_dom; pub mod real_dom;
pub mod state; pub mod state;
pub mod template;
#[doc(hidden)] #[doc(hidden)]
pub mod traversable; pub mod traversable;
pub mod utils; pub mod utils;
/// A id for a node that lives in the real dom.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RealNodeId {
ElementId(ElementId),
UnaccessableId(usize),
}
impl RealNodeId {
pub fn as_element_id(&self) -> ElementId {
match self {
RealNodeId::ElementId(id) => *id,
RealNodeId::UnaccessableId(_) => panic!("Expected element id"),
}
}
pub fn as_unaccessable_id(&self) -> usize {
match self {
RealNodeId::ElementId(_) => panic!("Expected unaccessable id"),
RealNodeId::UnaccessableId(id) => *id,
}
}
}
impl Ord for RealNodeId {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl PartialOrd for RealNodeId {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::ElementId(a), Self::ElementId(b)) => a.0.partial_cmp(&b.0),
(Self::UnaccessableId(a), Self::UnaccessableId(b)) => a.partial_cmp(b),
(Self::ElementId(_), Self::UnaccessableId(_)) => Some(Ordering::Greater),
(Self::UnaccessableId(_), Self::ElementId(_)) => Some(Ordering::Less),
}
}
}
/// Used in derived state macros
#[derive(Eq, PartialEq)]
#[doc(hidden)]
pub struct HeightOrdering {
pub height: u16,
pub id: RealNodeId,
}
impl HeightOrdering {
pub fn new(height: u16, id: RealNodeId) -> Self {
HeightOrdering { height, id }
}
}
// not the ordering after height is just for deduplication it can be any ordering as long as it is consistent
impl Ord for HeightOrdering {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.height.cmp(&other.height).then(self.id.cmp(&other.id))
}
}
impl PartialOrd for HeightOrdering {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

View file

@ -1,8 +1,7 @@
use dioxus_core::*;
use crate::{ use crate::{
real_dom::{NodeData, NodeType, OwnedAttributeView}, real_dom::{NodeData, NodeType, OwnedAttributeView},
state::union_ordered_iter, state::union_ordered_iter,
RealNodeId,
}; };
/// A view into a [VNode] with limited access. /// A view into a [VNode] with limited access.
@ -22,8 +21,8 @@ impl<'a> NodeView<'a> {
} }
/// Get the id of the node /// Get the id of the node
pub fn id(&self) -> GlobalNodeId { pub fn id(&self) -> RealNodeId {
self.inner.id self.inner.id.unwrap()
} }
/// Get the tag of the node if the tag is enabled in the mask /// Get the tag of the node if the tag is enabled in the mask

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,11 @@
use std::{cmp::Ordering, fmt::Debug}; use std::{cmp::Ordering, fmt::Debug};
use anymap::AnyMap;
use dioxus_core::GlobalNodeId;
use rustc_hash::FxHashSet;
use crate::node_ref::{NodeMask, NodeView}; use crate::node_ref::{NodeMask, NodeView};
use crate::real_dom::NodeData; use crate::real_dom::NodeData;
use crate::traversable::Traversable; use crate::traversable::Traversable;
use crate::RealNodeId;
use anymap::AnyMap;
use rustc_hash::FxHashSet;
/// Join two sorted iterators /// Join two sorted iterators
pub(crate) fn union_ordered_iter<T: Ord + Debug>( pub(crate) fn union_ordered_iter<T: Ord + Debug>(
@ -209,14 +208,14 @@ pub trait State: Default + Clone {
#[doc(hidden)] #[doc(hidden)]
fn update< fn update<
'a, 'a,
T: Traversable<Node = Self, Id = GlobalNodeId>, T: Traversable<Node = Self, Id = RealNodeId>,
T2: Traversable<Node = NodeData, Id = GlobalNodeId>, T2: Traversable<Node = NodeData, Id = RealNodeId>,
>( >(
dirty: &[(GlobalNodeId, NodeMask)], dirty: &[(RealNodeId, NodeMask)],
state_tree: &'a mut T, state_tree: &'a mut T,
rdom: &'a T2, rdom: &'a T2,
ctx: &AnyMap, ctx: &AnyMap,
) -> FxHashSet<GlobalNodeId>; ) -> FxHashSet<RealNodeId>;
} }
impl ChildDepState for () { impl ChildDepState for () {

View file

@ -1,44 +0,0 @@
use dioxus_core::{GlobalNodeId, TemplateNodeId};
use crate::{real_dom::Node, state::State};
#[derive(Debug, Default)]
pub struct NativeTemplate<S: State> {
pub(crate) nodes: Vec<Option<Box<Node<S>>>>,
pub(crate) roots: Vec<usize>,
}
impl<S: State> NativeTemplate<S> {
pub fn insert(&mut self, node: Node<S>) {
let id = node.node_data.id;
match id {
GlobalNodeId::TemplateId {
template_node_id: TemplateNodeId(id),
..
} => {
self.nodes.resize(id + 1, None);
self.nodes[id] = Some(Box::new(node));
}
GlobalNodeId::VNodeId(_) => panic!("Cannot insert a VNode into a template"),
}
}
}
#[derive(Debug)]
pub(crate) enum TemplateRefOrNode<S: State> {
Ref {
nodes: Vec<Option<Box<Node<S>>>>,
roots: Vec<GlobalNodeId>,
parent: Option<GlobalNodeId>,
},
Node(Node<S>),
}
impl<S: State> TemplateRefOrNode<S> {
pub fn parent(&self) -> Option<GlobalNodeId> {
match self {
TemplateRefOrNode::Ref { parent, .. } => *parent,
TemplateRefOrNode::Node(node) => node.node_data.parent,
}
}
}

View file

@ -1,17 +1,18 @@
use crate::{ use crate::{
real_dom::{NodeType, RealDom}, real_dom::{NodeType, RealDom},
state::State, state::State,
RealNodeId,
}; };
use dioxus_core::{DomEdit, ElementId, GlobalNodeId, Mutations}; use dioxus_core::{DomEdit, ElementId, Mutations};
pub enum ElementProduced { pub enum ElementProduced {
/// The iterator produced an element by progressing to the next node in a depth first order. /// The iterator produced an element by progressing to the next node in a depth first order.
Progressed(GlobalNodeId), Progressed(RealNodeId),
/// The iterator reached the end of the tree and looped back to the root /// The iterator reached the end of the tree and looped back to the root
Looped(GlobalNodeId), Looped(RealNodeId),
} }
impl ElementProduced { impl ElementProduced {
pub fn id(&self) -> GlobalNodeId { pub fn id(&self) -> RealNodeId {
match self { match self {
ElementProduced::Progressed(id) => *id, ElementProduced::Progressed(id) => *id,
ElementProduced::Looped(id) => *id, ElementProduced::Looped(id) => *id,
@ -50,16 +51,13 @@ impl NodePosition {
/// The iterator loops around when it reaches the end or the beginning. /// The iterator loops around when it reaches the end or the beginning.
pub struct PersistantElementIter { pub struct PersistantElementIter {
// stack of elements and fragments // stack of elements and fragments
stack: smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>, stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
} }
impl Default for PersistantElementIter { impl Default for PersistantElementIter {
fn default() -> Self { fn default() -> Self {
PersistantElementIter { PersistantElementIter {
stack: smallvec::smallvec![( stack: smallvec::smallvec![(RealNodeId::ElementId(ElementId(0)), NodePosition::AtNode)],
GlobalNodeId::VNodeId(dioxus_core::ElementId(0)),
NodePosition::AtNode
)],
} }
} }
} }
@ -79,7 +77,7 @@ impl PersistantElementIter {
.filter_map(|e| { .filter_map(|e| {
// nodes within templates will never be removed // nodes within templates will never be removed
if let DomEdit::Remove { root } = e { if let DomEdit::Remove { root } = e {
let id = rdom.decode_id(*root); let id = rdom.resolve_maybe_id(*root);
Some(id) Some(id)
} else { } else {
None None
@ -102,21 +100,23 @@ impl PersistantElementIter {
for m in &mutations.edits { for m in &mutations.edits {
match m { match m {
DomEdit::Remove { root } => { DomEdit::Remove { root } => {
let id = rdom.decode_id(*root); let id = rdom.resolve_maybe_id(*root);
if children.iter().take(*child_idx + 1).any(|c| *c == id) { if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx -= 1; *child_idx -= 1;
} }
} }
DomEdit::InsertBefore { root, n } => { DomEdit::InsertBefore { root, nodes } => {
let id = rdom.decode_id(*root); let id = rdom.resolve_maybe_id(*root);
let n = nodes.len();
if children.iter().take(*child_idx + 1).any(|c| *c == id) { if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx += *n as usize; *child_idx += n as usize;
} }
} }
DomEdit::InsertAfter { root, n } => { DomEdit::InsertAfter { root, nodes } => {
let id = rdom.decode_id(*root); let id = rdom.resolve_maybe_id(*root);
let n = nodes.len();
if children.iter().take(*child_idx).any(|c| *c == id) { if children.iter().take(*child_idx).any(|c| *c == id) {
*child_idx += *n as usize; *child_idx += n as usize;
} }
} }
_ => (), _ => (),
@ -131,7 +131,7 @@ impl PersistantElementIter {
/// get the next element /// get the next element
pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced { pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
if self.stack.is_empty() { if self.stack.is_empty() {
let id = GlobalNodeId::VNodeId(ElementId(0)); let id = RealNodeId::ElementId(ElementId(0));
let new = (id, NodePosition::AtNode); let new = (id, NodePosition::AtNode);
self.stack.push(new); self.stack.push(new);
ElementProduced::Looped(id) ElementProduced::Looped(id)
@ -167,10 +167,10 @@ impl PersistantElementIter {
pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced { pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
// recursively add the last child element to the stack // recursively add the last child element to the stack
fn push_back<S: State>( fn push_back<S: State>(
stack: &mut smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>, stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
new_node: GlobalNodeId, new_node: RealNodeId,
rdom: &RealDom<S>, rdom: &RealDom<S>,
) -> GlobalNodeId { ) -> RealNodeId {
match &rdom[new_node].node_data.node_type { match &rdom[new_node].node_data.node_type {
NodeType::Element { children, .. } => { NodeType::Element { children, .. } => {
if children.is_empty() { if children.is_empty() {
@ -184,7 +184,7 @@ impl PersistantElementIter {
} }
} }
if self.stack.is_empty() { if self.stack.is_empty() {
let new_node = GlobalNodeId::VNodeId(ElementId(0)); let new_node = RealNodeId::ElementId(ElementId(0));
ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
} else { } else {
let (last, o_child_idx) = self.stack.last_mut().unwrap(); let (last, o_child_idx) = self.stack.last_mut().unwrap();
@ -223,7 +223,7 @@ impl PersistantElementIter {
} }
} }
fn pop(&mut self) -> GlobalNodeId { fn pop(&mut self) -> RealNodeId {
self.stack.pop().unwrap().0 self.stack.pop().unwrap().0
} }
} }

View file

@ -1,5 +1,6 @@
use dioxus_core::{ use dioxus_core::{
OwnedAttributeValue, TemplateAttributeValue, TemplateNodeId, TextTemplate, TextTemplateSegment, OwnedAttributeValue, OwnedPathSeg, OwnedTraverse, TemplateAttributeValue, TemplateNodeId,
TextTemplate, TextTemplateSegment, UpdateOp,
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::TokenStreamExt; use quote::TokenStreamExt;
@ -54,7 +55,7 @@ struct TemplateElementBuilder {
attributes: Vec<TemplateAttributeBuilder>, attributes: Vec<TemplateAttributeBuilder>,
children: Vec<TemplateNodeId>, children: Vec<TemplateNodeId>,
listeners: Vec<usize>, listeners: Vec<usize>,
parent: Option<TemplateNodeId>, locally_static: bool,
} }
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
@ -73,7 +74,7 @@ impl TemplateElementBuilder {
attributes, attributes,
children, children,
listeners, listeners,
parent, ..
} = self; } = self;
let (element_tag, element_ns) = let (element_tag, element_ns) =
element_to_static_str(&tag.to_string()).ok_or_else(|| { element_to_static_str(&tag.to_string()).ok_or_else(|| {
@ -94,7 +95,6 @@ impl TemplateElementBuilder {
owned_attributes, owned_attributes,
children, children,
listeners, listeners,
parent,
)) ))
} }
} }
@ -106,19 +106,12 @@ impl ToTokens for TemplateElementBuilder {
attributes, attributes,
children, children,
listeners, listeners,
parent, ..
} = self; } = self;
let children = children.iter().map(|id| { let children = children.iter().map(|id| {
let raw = id.0; let raw = id.0;
quote! {TemplateNodeId(#raw)} quote! {TemplateNodeId(#raw)}
}); });
let parent = match parent {
Some(id) => {
let raw = id.0;
quote! {Some(TemplateNodeId(#raw))}
}
None => quote! {None},
};
tokens.append_all(quote! { tokens.append_all(quote! {
TemplateElement::new( TemplateElement::new(
dioxus_elements::#tag::TAG_NAME, dioxus_elements::#tag::TAG_NAME,
@ -126,7 +119,6 @@ impl ToTokens for TemplateElementBuilder {
&[#(#attributes),*], &[#(#attributes),*],
&[#(#children),*], &[#(#children),*],
&[#(#listeners),*], &[#(#listeners),*],
#parent,
) )
}) })
} }
@ -279,12 +271,18 @@ impl ToTokens for TemplateNodeTypeBuilder {
TemplateNodeType::Element(#el) TemplateNodeType::Element(#el)
}), }),
TemplateNodeTypeBuilder::Text(txt) => { TemplateNodeTypeBuilder::Text(txt) => {
let mut length = 0;
let segments = txt.segments.iter().map(|seg| match seg { let segments = txt.segments.iter().map(|seg| match seg {
TextTemplateSegment::Static(s) => quote!(TextTemplateSegment::Static(#s)), TextTemplateSegment::Static(s) => {
length += s.len();
quote!(TextTemplateSegment::Static(#s))
}
TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)), TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)),
}); });
tokens.append_all(quote! { tokens.append_all(quote! {
TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*])) TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*], #length))
}); });
} }
TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! { TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! {
@ -296,62 +294,54 @@ impl ToTokens for TemplateNodeTypeBuilder {
struct TemplateNodeBuilder { struct TemplateNodeBuilder {
id: TemplateNodeId, id: TemplateNodeId,
depth: usize,
parent: Option<TemplateNodeId>,
node_type: TemplateNodeTypeBuilder, node_type: TemplateNodeTypeBuilder,
fully_static: bool,
} }
impl TemplateNodeBuilder { impl TemplateNodeBuilder {
#[cfg(any(feature = "hot-reload", debug_assertions))] #[cfg(any(feature = "hot-reload", debug_assertions))]
fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNode, Error> { fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNode, Error> {
let TemplateNodeBuilder { id, node_type } = self; let TemplateNodeBuilder {
id,
node_type,
parent,
depth,
..
} = self;
let node_type = node_type.try_into_owned(location)?; let node_type = node_type.try_into_owned(location)?;
Ok(OwnedTemplateNode { Ok(OwnedTemplateNode {
id, id,
node_type, node_type,
locally_static: false, parent,
fully_static: false, depth,
}) })
} }
fn is_fully_static(&self, nodes: &Vec<TemplateNodeBuilder>) -> bool { fn to_tokens(&self, tokens: &mut TokenStream) {
self.is_locally_static() let Self {
&& match &self.node_type { id,
TemplateNodeTypeBuilder::Element(el) => el node_type,
.children parent,
.iter() depth,
.all(|child| nodes[child.0].is_fully_static(nodes)), ..
TemplateNodeTypeBuilder::Text(_) => true, } = self;
TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(),
}
}
fn is_locally_static(&self) -> bool {
match &self.node_type {
TemplateNodeTypeBuilder::Element(el) => {
el.attributes.iter().all(|attr| match &attr.value {
TemplateAttributeValue::Static(_) => true,
TemplateAttributeValue::Dynamic(_) => false,
}) && el.listeners.is_empty()
}
TemplateNodeTypeBuilder::Text(txt) => txt.segments.iter().all(|seg| match seg {
TextTemplateSegment::Static(_) => true,
TextTemplateSegment::Dynamic(_) => false,
}),
TemplateNodeTypeBuilder::DynamicNode(_) => false,
}
}
fn to_tokens(&self, tokens: &mut TokenStream, nodes: &Vec<TemplateNodeBuilder>) {
let Self { id, node_type } = self;
let raw_id = id.0; let raw_id = id.0;
let fully_static = self.is_fully_static(nodes); let parent = match parent {
let locally_static = self.is_locally_static(); Some(id) => {
let id = id.0;
quote! {Some(TemplateNodeId(#id))}
}
None => quote! {None},
};
tokens.append_all(quote! { tokens.append_all(quote! {
TemplateNode { TemplateNode {
id: TemplateNodeId(#raw_id), id: TemplateNodeId(#raw_id),
node_type: #node_type, node_type: #node_type,
locally_static: #locally_static, parent: #parent,
fully_static: #fully_static, depth: #depth,
} }
}) })
} }
@ -369,8 +359,8 @@ impl TemplateBuilder {
pub fn from_roots(roots: Vec<BodyNode>) -> Option<Self> { pub fn from_roots(roots: Vec<BodyNode>) -> Option<Self> {
let mut builder = Self::default(); let mut builder = Self::default();
for root in roots { for (i, root) in roots.into_iter().enumerate() {
let id = builder.build_node(root, None); let id = builder.build_node(root, None, vec![i], 0);
builder.root_nodes.push(id); builder.root_nodes.push(id);
} }
@ -391,18 +381,25 @@ impl TemplateBuilder {
fn from_roots_always(roots: Vec<BodyNode>) -> Self { fn from_roots_always(roots: Vec<BodyNode>) -> Self {
let mut builder = Self::default(); let mut builder = Self::default();
for root in roots { for (i, root) in roots.into_iter().enumerate() {
let id = builder.build_node(root, None); let id = builder.build_node(root, None, vec![i], 0);
builder.root_nodes.push(id); builder.root_nodes.push(id);
} }
builder builder
} }
fn build_node(&mut self, node: BodyNode, parent: Option<TemplateNodeId>) -> TemplateNodeId { fn build_node(
&mut self,
node: BodyNode,
parent: Option<TemplateNodeId>,
path: Vec<usize>,
depth: usize,
) -> TemplateNodeId {
let id = TemplateNodeId(self.nodes.len()); let id = TemplateNodeId(self.nodes.len());
match node { match node {
BodyNode::Element(el) => { BodyNode::Element(el) => {
let mut locally_static = true;
let mut attributes = Vec::new(); let mut attributes = Vec::new();
let mut listeners = Vec::new(); let mut listeners = Vec::new();
for attr in el.attributes { for attr in el.attributes {
@ -417,6 +414,7 @@ impl TemplateBuilder {
), ),
}) })
} else { } else {
locally_static = false;
attributes.push(TemplateAttributeBuilder { attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(), element_tag: el.name.clone(),
name: AttributeName::Ident(name), name: AttributeName::Ident(name),
@ -436,6 +434,7 @@ impl TemplateBuilder {
), ),
}) })
} else { } else {
locally_static = false;
attributes.push(TemplateAttributeBuilder { attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(), element_tag: el.name.clone(),
name: AttributeName::Str(name), name: AttributeName::Str(name),
@ -446,6 +445,7 @@ impl TemplateBuilder {
} }
} }
ElementAttr::AttrExpression { name, value } => { ElementAttr::AttrExpression { name, value } => {
locally_static = false;
attributes.push(TemplateAttributeBuilder { attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(), element_tag: el.name.clone(),
name: AttributeName::Ident(name), name: AttributeName::Ident(name),
@ -455,6 +455,7 @@ impl TemplateBuilder {
}) })
} }
ElementAttr::CustomAttrExpression { name, value } => { ElementAttr::CustomAttrExpression { name, value } => {
locally_static = false;
attributes.push(TemplateAttributeBuilder { attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(), element_tag: el.name.clone(),
name: AttributeName::Str(name), name: AttributeName::Str(name),
@ -464,6 +465,7 @@ impl TemplateBuilder {
}) })
} }
ElementAttr::EventTokens { name, tokens } => { ElementAttr::EventTokens { name, tokens } => {
locally_static = false;
listeners.push(self.dynamic_context.add_listener(name, tokens)) listeners.push(self.dynamic_context.add_listener(name, tokens))
} }
} }
@ -481,16 +483,25 @@ impl TemplateBuilder {
attributes, attributes,
children: Vec::new(), children: Vec::new(),
listeners, listeners,
parent, locally_static,
}), }),
parent,
depth,
fully_static: false,
}); });
let children: Vec<_> = el let children: Vec<_> = el
.children .children
.into_iter() .into_iter()
.map(|child| self.build_node(child, Some(id))) .enumerate()
.map(|(i, child)| {
let mut new_path = path.clone();
new_path.push(i);
self.build_node(child, Some(id), new_path, depth + 1)
})
.collect(); .collect();
let children_fully_static = children.iter().all(|c| self.nodes[c.0].fully_static);
let parent = &mut self.nodes[id.0]; let parent = &mut self.nodes[id.0];
parent.fully_static = locally_static && children_fully_static;
if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type { if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type {
element.children = children; element.children = children;
} }
@ -502,16 +513,25 @@ impl TemplateBuilder {
node_type: TemplateNodeTypeBuilder::DynamicNode( node_type: TemplateNodeTypeBuilder::DynamicNode(
self.dynamic_context.add_node(BodyNode::Component(comp)), self.dynamic_context.add_node(BodyNode::Component(comp)),
), ),
parent,
depth,
fully_static: false,
}); });
} }
BodyNode::Text(txt) => { BodyNode::Text(txt) => {
let mut segments = Vec::new(); let mut segments = Vec::new();
let mut length = 0;
let mut fully_static = true;
for segment in txt.segments { for segment in txt.segments {
segments.push(match segment { segments.push(match segment {
Segment::Literal(lit) => TextTemplateSegment::Static(lit), Segment::Literal(lit) => {
length += lit.len();
TextTemplateSegment::Static(lit)
}
Segment::Formatted(fmted) => { Segment::Formatted(fmted) => {
fully_static = false;
TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted)) TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted))
} }
}) })
@ -519,7 +539,10 @@ impl TemplateBuilder {
self.nodes.push(TemplateNodeBuilder { self.nodes.push(TemplateNodeBuilder {
id, id,
node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments)), node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments, length)),
parent,
depth,
fully_static,
}); });
} }
@ -529,6 +552,9 @@ impl TemplateBuilder {
node_type: TemplateNodeTypeBuilder::DynamicNode( node_type: TemplateNodeTypeBuilder::DynamicNode(
self.dynamic_context.add_node(BodyNode::RawExpr(expr)), self.dynamic_context.add_node(BodyNode::RawExpr(expr)),
), ),
parent,
depth,
fully_static: false,
}); });
} }
} }
@ -608,6 +634,7 @@ impl TemplateBuilder {
pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplate, Error> { pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplate, Error> {
let mut nodes = Vec::new(); let mut nodes = Vec::new();
let dynamic_mapping = self.dynamic_mapping(&nodes); let dynamic_mapping = self.dynamic_mapping(&nodes);
let dynamic_path = self.dynamic_path();
for node in self.nodes { for node in self.nodes {
nodes.push(node.try_into_owned(location)?); nodes.push(node.try_into_owned(location)?);
} }
@ -616,6 +643,7 @@ impl TemplateBuilder {
nodes, nodes,
root_nodes: self.root_nodes, root_nodes: self.root_nodes,
dynamic_mapping, dynamic_mapping,
dynamic_path,
}) })
} }
@ -684,6 +712,114 @@ impl TemplateBuilder {
listener_mapping, listener_mapping,
) )
} }
fn dynamic_path(&self) -> Option<OwnedPathSeg> {
let mut last_seg: Option<OwnedPathSeg> = None;
let mut nodes_to_insert_after = Vec::new();
// iterating from the last root to the first
for root in self.root_nodes.iter().rev() {
let root_node = &self.nodes[root.0];
if let TemplateNodeTypeBuilder::DynamicNode(_) = root_node.node_type {
match &mut last_seg {
// if there has been no static nodes, we can append the child to the parent node
None => nodes_to_insert_after.push(*root),
// otherwise we insert the child before the last static node
Some(seg) => {
seg.ops.push(UpdateOp::InsertBefore(*root));
}
}
} else if let Some(mut new) = self.construct_path_segment(*root) {
if let Some(last) = last_seg.take() {
match new.traverse {
OwnedTraverse::Halt => {
new.traverse = OwnedTraverse::NextSibling(Box::new(last));
}
OwnedTraverse::FirstChild(b) => {
new.traverse = OwnedTraverse::Both(Box::new((*b, last)));
}
_ => unreachable!(),
}
} else {
for node in nodes_to_insert_after.drain(..) {
new.ops.push(UpdateOp::InsertAfter(node));
}
}
last_seg = Some(new);
} else if let Some(last) = last_seg.take() {
last_seg = Some(OwnedPathSeg {
ops: Vec::new(),
traverse: OwnedTraverse::NextSibling(Box::new(last)),
});
}
}
last_seg
}
fn construct_path_segment(&self, node_id: TemplateNodeId) -> Option<OwnedPathSeg> {
let n = &self.nodes[node_id.0];
if n.fully_static {
return None;
}
match &n.node_type {
TemplateNodeTypeBuilder::Element(el) => {
let mut last_seg: Option<OwnedPathSeg> = None;
let mut children_to_append = Vec::new();
// iterating from the last child to the first
for child in el.children.iter().rev() {
let child_node = &self.nodes[child.0];
if let TemplateNodeTypeBuilder::DynamicNode(_) = child_node.node_type {
match &mut last_seg {
// if there has been no static nodes, we can append the child to the parent node
None => children_to_append.push(*child),
// otherwise we insert the child before the last static node
Some(seg) => {
seg.ops.push(UpdateOp::InsertBefore(*child));
}
}
} else if let Some(mut new) = self.construct_path_segment(*child) {
if let Some(last) = last_seg.take() {
match new.traverse {
OwnedTraverse::Halt => {
new.traverse = OwnedTraverse::NextSibling(Box::new(last));
}
OwnedTraverse::FirstChild(b) => {
new.traverse = OwnedTraverse::Both(Box::new((*b, last)));
}
_ => unreachable!(),
}
}
last_seg = Some(new);
} else if let Some(last) = last_seg.take() {
last_seg = Some(OwnedPathSeg {
ops: Vec::new(),
traverse: OwnedTraverse::NextSibling(Box::new(last)),
});
}
}
let mut ops = Vec::new();
if !el.locally_static || n.parent.is_none() {
ops.push(UpdateOp::StoreNode(node_id));
}
for child in children_to_append.into_iter().rev() {
ops.push(UpdateOp::AppendChild(child));
}
Some(OwnedPathSeg {
ops,
traverse: match last_seg {
Some(last) => OwnedTraverse::FirstChild(Box::new(last)),
None => OwnedTraverse::Halt,
},
})
}
TemplateNodeTypeBuilder::Text(_) => Some(OwnedPathSeg {
ops: vec![UpdateOp::StoreNode(n.id)],
traverse: dioxus_core::OwnedTraverse::Halt,
}),
TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(
"constructing path segment for dynamic nodes is handled in the parent node"
),
}
}
} }
impl ToTokens for TemplateBuilder { impl ToTokens for TemplateBuilder {
@ -759,10 +895,18 @@ impl ToTokens for TemplateBuilder {
}); });
let mut nodes_quoted = TokenStream::new(); let mut nodes_quoted = TokenStream::new();
for n in nodes { for n in nodes {
n.to_tokens(&mut nodes_quoted, nodes); n.to_tokens(&mut nodes_quoted);
quote! {,}.to_tokens(&mut nodes_quoted); quote! {,}.to_tokens(&mut nodes_quoted);
} }
let dynamic_path = match self.dynamic_path() {
Some(seg) => {
let seg = quote_owned_segment(seg);
quote! {Some(#seg)}
}
None => quote! {None},
};
let quoted = quote! { let quoted = quote! {
{ {
const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted]; const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted];
@ -791,6 +935,7 @@ impl ToTokens for TemplateBuilder {
nodes: __NODES, nodes: __NODES,
root_nodes: __ROOT_NODES, root_nodes: __ROOT_NODES,
dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS), dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS),
dynamic_path: #dynamic_path,
}); });
let __bump = __cx.bump(); let __bump = __cx.bump();
@ -874,3 +1019,67 @@ impl ToTokens for DynamicTemplateContextBuilder {
}) })
} }
} }
fn quote_owned_segment(seg: OwnedPathSeg) -> proc_macro2::TokenStream {
let OwnedPathSeg { ops, traverse } = seg;
let ops = ops
.into_iter()
.map(|op| match op {
UpdateOp::StoreNode(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::StoreNode(#id))
}
UpdateOp::InsertBefore(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::InsertBefore(#id))
}
UpdateOp::InsertAfter(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::InsertAfter(#id))
}
UpdateOp::AppendChild(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::AppendChild(#id))
}
})
.collect::<Vec<_>>();
let traverse = quote_owned_traverse(traverse);
quote! {
StaticPathSeg {
ops: &[#(#ops),*],
traverse: #traverse,
}
}
}
fn quote_owned_traverse(traverse: OwnedTraverse) -> proc_macro2::TokenStream {
match traverse {
OwnedTraverse::Halt => {
quote! {StaticTraverse::Halt}
}
OwnedTraverse::FirstChild(seg) => {
let seg = quote_owned_segment(*seg);
quote! {StaticTraverse::FirstChild(&#seg)}
}
OwnedTraverse::NextSibling(seg) => {
let seg = quote_owned_segment(*seg);
quote! {StaticTraverse::NextSibling(&#seg)}
}
OwnedTraverse::Both(b) => {
let (child, sibling) = *b;
let child = quote_owned_segment(child);
let sibling = quote_owned_segment(sibling);
quote! {StaticTraverse::Both(&(#child, #sibling))}
}
}
}
fn quote_template_node_id(id: TemplateNodeId) -> proc_macro2::TokenStream {
let raw = id.0;
quote! {
TemplateNodeId(#raw)
}
}

View file

@ -433,7 +433,7 @@ impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
*last_node_was_text = true; *last_node_was_text = true;
let text = dynamic_context.resolve_text(&txt.segments); let text = dynamic_context.resolve_text(txt);
write!(f, "{}", text)? write!(f, "{}", text)?
} }

View file

@ -1,3 +1,4 @@
use dioxus::core_macro::rsx_without_templates;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_tui::query::Query; use dioxus_tui::query::Query;
use dioxus_tui::Size; use dioxus_tui::Size;
@ -10,7 +11,8 @@ fn app(cx: Scope) -> Element {
let hue = use_state(&cx, || 0.0); let hue = use_state(&cx, || 0.0);
let brightness = use_state(&cx, || 0.0); let brightness = use_state(&cx, || 0.0);
let tui_query: Query = cx.consume_context().unwrap(); let tui_query: Query = cx.consume_context().unwrap();
cx.render(rsx! { // disable templates so that every node has an id and can be queried
cx.render(rsx_without_templates! {
div{ div{
width: "100%", width: "100%",
background_color: "hsl({hue}, 70%, {brightness}%)", background_color: "hsl({hue}, 70%, {brightness}%)",

View file

@ -1,9 +1,10 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_tui::Config;
fn main() { fn main() {
dioxus_tui::launch(app); dioxus_tui::launch_cfg(app, Config::default());
} }
#[derive(Props, PartialEq)] #[derive(Props, PartialEq)]

View file

@ -1,7 +1,9 @@
use crate::{node::PreventDefault, Dom}; use crate::{node::PreventDefault, Dom};
use dioxus_core::GlobalNodeId; use dioxus_native_core::{
use dioxus_native_core::utils::{ElementProduced, PersistantElementIter}; utils::{ElementProduced, PersistantElementIter},
RealNodeId,
};
use dioxus_native_core_macro::sorted_str_slice; use dioxus_native_core_macro::sorted_str_slice;
use std::{cmp::Ordering, num::NonZeroU16}; use std::{cmp::Ordering, num::NonZeroU16};
@ -90,7 +92,7 @@ impl NodeDepState<()> for Focus {
} }
} else if node } else if node
.listeners() .listeners()
.map(|mut listeners| { .and_then(|mut listeners| {
listeners listeners
.any(|l| FOCUS_EVENTS.binary_search(&l).is_ok()) .any(|l| FOCUS_EVENTS.binary_search(&l).is_ok())
.then_some(()) .then_some(())
@ -117,7 +119,7 @@ const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]);
#[derive(Default)] #[derive(Default)]
pub(crate) struct FocusState { pub(crate) struct FocusState {
pub(crate) focus_iter: PersistantElementIter, pub(crate) focus_iter: PersistantElementIter,
pub(crate) last_focused_id: Option<GlobalNodeId>, pub(crate) last_focused_id: Option<RealNodeId>,
pub(crate) focus_level: FocusLevel, pub(crate) focus_level: FocusLevel,
pub(crate) dirty: bool, pub(crate) dirty: bool,
} }
@ -216,6 +218,9 @@ impl FocusState {
} }
if let Some(id) = next_focus { if let Some(id) = next_focus {
if !rdom[id].state.focus.level.focusable() {
panic!()
}
rdom[id].state.focused = true; rdom[id].state.focused = true;
if let Some(old) = self.last_focused_id.replace(id) { if let Some(old) = self.last_focused_id.replace(id) {
rdom[old].state.focused = false; rdom[old].state.focused = false;
@ -231,9 +236,9 @@ impl FocusState {
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) { pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
fn remove_children( fn remove_children(
to_prune: &mut [&mut Option<GlobalNodeId>], to_prune: &mut [&mut Option<RealNodeId>],
rdom: &Dom, rdom: &Dom,
removed: GlobalNodeId, removed: RealNodeId,
) { ) {
for opt in to_prune.iter_mut() { for opt in to_prune.iter_mut() {
if let Some(id) = opt { if let Some(id) = opt {
@ -256,19 +261,19 @@ impl FocusState {
dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children( dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
&mut [&mut self.last_focused_id], &mut [&mut self.last_focused_id],
rdom, rdom,
rdom.decode_id(*root), rdom.resolve_maybe_id(*root),
), ),
dioxus_core::DomEdit::Remove { root } => remove_children( dioxus_core::DomEdit::Remove { root } => remove_children(
&mut [&mut self.last_focused_id], &mut [&mut self.last_focused_id],
rdom, rdom,
rdom.decode_id(*root), rdom.resolve_maybe_id(*root),
), ),
_ => (), _ => (),
} }
} }
} }
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: GlobalNodeId) { pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: RealNodeId) {
if let Some(old) = self.last_focused_id.replace(id) { if let Some(old) = self.last_focused_id.replace(id) {
rdom[old].state.focused = false; rdom[old].state.focused = false;
} }

View file

@ -2,6 +2,7 @@ use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
}; };
use dioxus_core::*; use dioxus_core::*;
use dioxus_native_core::RealNodeId;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
@ -190,7 +191,8 @@ impl InnerInputState {
self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom); self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
if old_focus != self.focus_state.last_focused_id { if old_focus != self.focus_state.last_focused_id {
if let Some(id) = self.focus_state.last_focused_id { // elements with listeners will always have a element id
if let Some(RealNodeId::ElementId(id)) = self.focus_state.last_focused_id {
resolved_events.push(UserEvent { resolved_events.push(UserEvent {
scope_id: None, scope_id: None,
priority: EventPriority::Medium, priority: EventPriority::Medium,
@ -208,7 +210,7 @@ impl InnerInputState {
bubbles: event_bubbles("focusin"), bubbles: event_bubbles("focusin"),
}); });
} }
if let Some(id) = old_focus { if let Some(RealNodeId::ElementId(id)) = old_focus {
resolved_events.push(UserEvent { resolved_events.push(UserEvent {
scope_id: None, scope_id: None,
priority: EventPriority::Medium, priority: EventPriority::Medium,
@ -243,13 +245,13 @@ impl InnerInputState {
fn try_create_event( fn try_create_event(
name: &'static str, name: &'static str,
data: Arc<dyn Any + Send + Sync>, data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut FxHashSet<GlobalNodeId>, will_bubble: &mut FxHashSet<RealNodeId>,
resolved_events: &mut Vec<UserEvent>, resolved_events: &mut Vec<UserEvent>,
node: &Node, node: &Node,
dom: &Dom, dom: &Dom,
) { ) {
// only trigger event if the event was not triggered already by a child // only trigger event if the event was not triggered already by a child
let id = node.node_data.id; let id = node.mounted_id();
if will_bubble.insert(id) { if will_bubble.insert(id) {
let mut parent = node.node_data.parent; let mut parent = node.node_data.parent;
while let Some(parent_id) = parent { while let Some(parent_id) = parent {
@ -260,7 +262,7 @@ impl InnerInputState {
scope_id: None, scope_id: None,
priority: EventPriority::Medium, priority: EventPriority::Medium,
name, name,
element: Some(id), element: Some(id.as_element_id()),
data, data,
bubbles: event_bubbles(name), bubbles: event_bubbles(name),
}) })
@ -547,7 +549,7 @@ impl InnerInputState {
let currently_contains = layout_contains_point(node_layout, new_pos); let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && node.state.focus.level.focusable() { if currently_contains && node.state.focus.level.focusable() {
focus_id = Some(node.node_data.id); focus_id = Some(node.mounted_id());
} }
}); });
if let Some(id) = focus_id { if let Some(id) = focus_id {
@ -665,7 +667,7 @@ impl RinkInputHandler {
scope_id: None, scope_id: None,
priority: EventPriority::Medium, priority: EventPriority::Medium,
name: event, name: event,
element: Some(node.node_data.id), element: Some(node.mounted_id().as_element_id()),
data: data.clone(), data: data.clone(),
bubbles: event_bubbles(event), bubbles: event_bubbles(event),
}); });

View file

@ -6,6 +6,7 @@ use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
use dioxus_native_core::real_dom::OwnedAttributeView; use dioxus_native_core::real_dom::OwnedAttributeView;
use dioxus_native_core::state::ChildDepState; use dioxus_native_core::state::ChildDepState;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::sorted_str_slice; use dioxus_native_core_macro::sorted_str_slice;
use taffy::prelude::*; use taffy::prelude::*;
@ -100,7 +101,7 @@ impl ChildDepState for TaffyLayout {
} }
// the root node fills the entire area // the root node fills the entire area
if node.id() == ElementId(0) { if node.id() == RealNodeId::ElementId(ElementId(0)) {
apply_layout_attributes("width", "100%", &mut style); apply_layout_attributes("width", "100%", &mut style);
apply_layout_attributes("height", "100%", &mut style); apply_layout_attributes("height", "100%", &mut style);
} }

View file

@ -7,7 +7,7 @@ use crossterm::{
}; };
use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*; use dioxus_core::*;
use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::{real_dom::RealDom, RealNodeId};
use focus::FocusState; use focus::FocusState;
use futures::{ use futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender}, channel::mpsc::{UnboundedReceiver, UnboundedSender},
@ -133,8 +133,8 @@ fn render_vdom(
terminal.clear().unwrap(); terminal.clear().unwrap();
} }
let mut to_rerender: rustc_hash::FxHashSet<GlobalNodeId> = let mut to_rerender: rustc_hash::FxHashSet<RealNodeId> =
vec![GlobalNodeId::VNodeId(ElementId(0))] vec![RealNodeId::ElementId(ElementId(0))]
.into_iter() .into_iter()
.collect(); .collect();
let mut updated = true; let mut updated = true;

View file

@ -7,7 +7,7 @@
//! - tests to ensure dyn_into works for various event types. //! - tests to ensure dyn_into works for various event types.
//! - Partial delegation?> //! - Partial delegation?>
use dioxus_core::{DomEdit, SchedulerMsg, UserEvent}; use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
use dioxus_html::event_bubbles; use dioxus_html::event_bubbles;
use dioxus_interpreter_js::Interpreter; use dioxus_interpreter_js::Interpreter;
use js_sys::Function; use js_sys::Function;
@ -43,7 +43,7 @@ impl WebsysDom {
break Ok(UserEvent { break Ok(UserEvent {
name: event_name_from_typ(&typ), name: event_name_from_typ(&typ),
data: virtual_event_from_websys_event(event.clone(), target.clone()), data: virtual_event_from_websys_event(event.clone(), target.clone()),
element: Some(id), element: Some(ElementId(id)),
scope_id: None, scope_id: None,
priority: dioxus_core::EventPriority::Medium, priority: dioxus_core::EventPriority::Medium,
bubbles: event.bubbles(), bubbles: event.bubbles(),
@ -107,18 +107,25 @@ impl WebsysDom {
pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) { pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
for edit in edits.drain(..) { for edit in edits.drain(..) {
match edit { match edit {
DomEdit::PushRoot { root } => self.interpreter.PushRoot(root), DomEdit::AppendChildren { root, children } => {
DomEdit::PopRoot {} => self.interpreter.PopRoot(), self.interpreter.AppendChildren(root, children);
DomEdit::AppendChildren { many } => self.interpreter.AppendChildren(many), }
DomEdit::ReplaceWith { root, m } => self.interpreter.ReplaceWith(root, m), DomEdit::ReplaceWith { root, nodes } => self.interpreter.ReplaceWith(root, nodes),
DomEdit::InsertAfter { root, n } => self.interpreter.InsertAfter(root, n), DomEdit::InsertAfter { root, nodes } => self.interpreter.InsertAfter(root, nodes),
DomEdit::InsertBefore { root, n } => self.interpreter.InsertBefore(root, n), DomEdit::InsertBefore { root, nodes } => self.interpreter.InsertBefore(root, nodes),
DomEdit::Remove { root } => self.interpreter.Remove(root), DomEdit::Remove { root } => self.interpreter.Remove(root),
DomEdit::CreateElement { tag, root } => self.interpreter.CreateElement(tag, root), DomEdit::CreateElement {
DomEdit::CreateElementNs { tag, root, ns } => { root,
self.interpreter.CreateElementNs(tag, root, ns) tag,
} children,
} => self.interpreter.CreateElement(tag, root, children),
DomEdit::CreateElementNs {
root,
tag,
ns,
children,
} => self.interpreter.CreateElementNs(tag, root, ns, children),
DomEdit::CreatePlaceholder { root } => self.interpreter.CreatePlaceholder(root), DomEdit::CreatePlaceholder { root } => self.interpreter.CreatePlaceholder(root),
DomEdit::NewEventListener { DomEdit::NewEventListener {
event_name, root, .. event_name, root, ..
@ -157,45 +164,15 @@ impl WebsysDom {
let value = serde_wasm_bindgen::to_value(&value).unwrap(); let value = serde_wasm_bindgen::to_value(&value).unwrap();
self.interpreter.SetAttribute(root, field, value, ns) self.interpreter.SetAttribute(root, field, value, ns)
} }
DomEdit::CreateTemplateRef { id, template_id } => { DomEdit::CloneNode { id, new_id } => self.interpreter.CloneNode(id, new_id),
self.interpreter.CreateTemplateRef(id, template_id) DomEdit::CloneNodeChildren { id, new_ids } => {
} self.interpreter.CloneNodeChildren(id, new_ids)
DomEdit::CreateTemplate { id } => self.interpreter.CreateTemplate(id),
DomEdit::FinishTemplate { len } => self.interpreter.FinishTemplate(len),
DomEdit::EnterTemplateRef { root } => self.interpreter.EnterTemplateRef(root),
DomEdit::ExitTemplateRef {} => self.interpreter.ExitTemplateRef(),
DomEdit::CreateTextNodeTemplate {
root,
text,
locally_static,
} => self
.interpreter
.CreateTextNodeTemplate(text, root, locally_static),
DomEdit::CreateElementTemplate {
root,
tag,
locally_static,
fully_static,
} => {
self.interpreter
.CreateElementTemplate(tag, root, locally_static, fully_static)
}
DomEdit::CreateElementNsTemplate {
root,
tag,
ns,
locally_static,
fully_static,
} => self.interpreter.CreateElementNsTemplate(
tag,
root,
ns,
locally_static,
fully_static,
),
DomEdit::CreatePlaceholderTemplate { root } => {
self.interpreter.CreatePlaceholderTemplate(root)
} }
DomEdit::FirstChild {} => self.interpreter.FirstChild(),
DomEdit::NextSibling {} => self.interpreter.NextSibling(),
DomEdit::ParentNode {} => self.interpreter.ParentNode(),
DomEdit::StoreWithId { id } => self.interpreter.StoreWithId(id),
DomEdit::SetLastNode { id } => self.interpreter.SetLastNode(id),
} }
} }
} }

View file

@ -108,30 +108,13 @@ impl WebsysDom {
} }
for listener in vel.listeners { for listener in vel.listeners {
let global_id = listener.mounted_node.get().unwrap(); let id = listener.mounted_node.get().unwrap();
match global_id { self.interpreter.NewEventListener(
dioxus_core::GlobalNodeId::TemplateId { listener.event,
template_ref_id, Some(id.as_u64()),
template_node_id: id, self.handler.as_ref().unchecked_ref(),
} => { event_bubbles(listener.event),
self.interpreter.EnterTemplateRef(template_ref_id.into()); );
self.interpreter.NewEventListener(
listener.event,
id.into(),
self.handler.as_ref().unchecked_ref(),
event_bubbles(listener.event),
);
self.interpreter.ExitTemplateRef();
}
dioxus_core::GlobalNodeId::VNodeId(id) => {
self.interpreter.NewEventListener(
listener.event,
id.into(),
self.handler.as_ref().unchecked_ref(),
event_bubbles(listener.event),
);
}
}
} }
if !vel.listeners.is_empty() { if !vel.listeners.is_empty() {