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 crate::{
template::{TemplateNodeId, TextTemplateSegment},
AttributeValue, Listener, VNode,
AttributeValue, Listener, TextTemplate, VNode,
};
/// A lazily initailized vector
@ -96,28 +96,6 @@ where
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
@ -161,21 +139,40 @@ pub struct TemplateContext<'b> {
impl<'b> TemplateContext<'b> {
/// 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
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
let mut result = String::new();
for seg in text.as_ref() {
match seg {
TextTemplateSegment::Static(s) => result += s.as_ref(),
TextTemplateSegment::Dynamic(idx) => result += self.text_segments[*idx],
}
}
let mut result = String::with_capacity(text.min_size);
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 {
TextTemplateSegment::Static(s) => {
let _ = result.write_str(s.as_ref());
}
TextTemplateSegment::Dynamic(idx) => {
let _ = result.write_str(self.text_segments[*idx]);
}
}
}
}
/// Resolve an attribute value
pub fn resolve_attribute(&self, idx: usize) -> &'b AttributeValue<'b> {
&self.attributes[idx]

View file

@ -3,7 +3,7 @@
//!
//! 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};
pub(crate) struct BubbleState {
@ -58,7 +58,7 @@ pub struct UserEvent {
pub priority: EventPriority,
/// The optional real node associated with the trigger
pub element: Option<GlobalNodeId>,
pub element: Option<ElementId>,
/// The event type IE "onclick" or "onmouseover"
pub name: &'static str,

View file

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

View file

@ -44,57 +44,53 @@ impl Debug for Mutations<'_> {
serde(tag = "type")
)]
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
/// at the top of the stack.
AppendChildren {
/// How many nodes should be popped from the stack.
/// The node remaining on the stack will be the target for the append.
many: u32,
/// The parent to append nodes to.
root: Option<u64>,
/// The ids of the children to append.
children: Vec<u64>,
},
/// Replace a given (single) node with a handful of nodes currently on the stack.
ReplaceWith {
/// 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.
m: u32,
/// The ids of the nodes to replace the root with.
nodes: Vec<u64>,
},
/// Insert a number of nodes after a given node.
InsertAfter {
/// 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.
n: u32,
/// The ids of the nodes to insert after the target node.
nodes: Vec<u64>,
},
/// Insert a number of nodes before a given node.
InsertBefore {
/// 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.
n: u32,
/// The ids of the nodes to insert before the target node.
nodes: Vec<u64>,
},
/// Remove a particular node from the DOM
Remove {
/// The ID of the node to remove.
root: u64,
root: Option<u64>,
},
/// Create a new purely-text node
CreateTextNode {
/// The ID the new node should have.
root: u64,
root: Option<u64>,
/// The textcontent of the node
text: &'bump str,
@ -103,82 +99,35 @@ pub enum DomEdit<'bump> {
/// Create a new purely-element node
CreateElement {
/// The ID the new node should have.
root: u64,
root: Option<u64>,
/// The tagname of the node
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
CreateElementNs {
/// The ID the new node should have.
root: u64,
root: Option<u64>,
/// The namespace of the node
tag: &'bump str,
/// The namespace of the node (like `SVG`)
ns: &'static str,
/// The number of children nodes that will follow this message.
children: u32,
},
/// Create a new placeholder node.
/// In most implementations, this will either be a hidden div or a comment node.
CreatePlaceholder {
/// The ID the new node should have.
root: 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,
root: Option<u64>,
},
/// Create a new Event Listener.
@ -190,13 +139,13 @@ pub enum DomEdit<'bump> {
scope: ScopeId,
/// The ID of the node to attach the listener to.
root: u64,
root: Option<u64>,
},
/// Remove an existing Event Listener.
RemoveEventListener {
/// The ID of the node to remove.
root: u64,
root: Option<u64>,
/// The name of the event to remove.
event: &'static str,
@ -205,7 +154,7 @@ pub enum DomEdit<'bump> {
/// Set the textcontent of a node.
SetText {
/// The ID of the node to set the textcontent of.
root: u64,
root: Option<u64>,
/// The textcontent of the node
text: &'bump str,
@ -214,7 +163,7 @@ pub enum DomEdit<'bump> {
/// Set the value of a node's attribute.
SetAttribute {
/// The ID of the node to set the attribute of.
root: u64,
root: Option<u64>,
/// The name of the attribute to set.
field: &'static str,
@ -231,7 +180,7 @@ pub enum DomEdit<'bump> {
/// Remove an attribute from a node.
RemoveAttribute {
/// The ID of the node to remove.
root: u64,
root: Option<u64>,
/// The name of the attribute to remove.
name: &'static str,
@ -240,45 +189,50 @@ pub enum DomEdit<'bump> {
ns: Option<&'bump str>,
},
/// Manually pop a root node from the stack.
PopRoot {},
/// Clones a node.
CloneNode {
/// The ID of the node to clone.
id: Option<u64>,
/// Enter a TemplateRef tree
EnterTemplateRef {
/// The ID of the node to enter.
root: u64,
/// The ID of the new node.
new_id: u64,
},
/// Exit a TemplateRef tree
ExitTemplateRef {},
/// Clones the children of a node. (allows cloning fragments)
CloneNodeChildren {
/// The ID of the node to clone.
id: Option<u64>,
/// Create a refrence to a template node.
CreateTemplateRef {
/// The ID of the new template refrence.
id: u64,
/// The ID of the template the node is refrencing.
template_id: u64,
/// The ID of the new node.
new_ids: Vec<u64>,
},
/// Create a new templete.
/// IMPORTANT: When adding nodes to a templete, id's will reset to zero, so they must be allocated on a different stack.
/// It is recommended to use Cow<NativeNode>.
CreateTemplate {
/// The ID of the new template.
/// Navigates to the last node to the first child of the current node.
FirstChild {},
/// Navigates to the last node to the last child of the current node.
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,
},
/// Finish a templete
FinishTemplate {
/// The number of root nodes in the template
len: u32,
/// Manually set the last node.
SetLastNode {
/// The ID to set the last node to.
id: u64,
},
}
use rustc_hash::FxHashSet;
use DomEdit::*;
#[allow(unused)]
impl<'a> Mutations<'a> {
pub(crate) fn new() -> Self {
Self {
@ -288,44 +242,29 @@ impl<'a> Mutations<'a> {
}
}
// Navigation
pub(crate) fn push_root(&mut self, root: impl Into<u64>) {
let id = root.into();
self.edits.push(PushRoot { root: id });
pub(crate) fn replace_with(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(ReplaceWith { nodes, root });
}
// Navigation
pub(crate) fn pop_root(&mut self) {
self.edits.push(PopRoot {});
pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(InsertAfter { nodes, root });
}
pub(crate) fn replace_with(&mut self, root: impl Into<u64>, m: u32) {
let root = root.into();
self.edits.push(ReplaceWith { m, root });
pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(InsertBefore { nodes, root });
}
pub(crate) fn insert_after(&mut self, root: impl Into<u64>, n: u32) {
let root = root.into();
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 });
pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
self.edits.push(AppendChildren { root, children });
}
// Remove Nodes from the dom
pub(crate) fn remove(&mut self, id: impl Into<u64>) {
self.edits.push(Remove { root: id.into() });
pub(crate) fn remove(&mut self, id: Option<u64>) {
self.edits.push(Remove { root: id });
}
// Create
pub(crate) fn create_text_node(&mut self, text: &'a str, id: impl Into<u64>) {
let id = id.into();
pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
self.edits.push(CreateTextNode { text, root: id });
}
@ -333,66 +272,27 @@ impl<'a> Mutations<'a> {
&mut self,
tag: &'static str,
ns: Option<&'static str>,
id: impl Into<u64>,
id: Option<u64>,
children: u32,
) {
let id = id.into();
match ns {
Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }),
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 {
Some(ns) => self.edits.push(CreateElementNs {
root: id,
ns,
tag,
locally_static,
fully_static,
children,
}),
None => self.edits.push(CreateElementTemplate {
None => self.edits.push(CreateElement {
root: id,
tag,
locally_static,
fully_static,
children,
}),
}
}
// 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>) {
let id = id.into();
self.edits.push(CreatePlaceholderTemplate { root: id });
pub(crate) fn create_placeholder(&mut self, id: Option<u64>) {
self.edits.push(CreatePlaceholder { root: id });
}
// events
@ -403,13 +303,7 @@ impl<'a> Mutations<'a> {
..
} = listener;
let element_id = match mounted_node.get().unwrap() {
GlobalNodeId::TemplateId {
template_ref_id: _,
template_node_id,
} => template_node_id.into(),
GlobalNodeId::VNodeId(id) => id.into(),
};
let element_id = Some(mounted_node.get().unwrap().into());
self.edits.push(NewEventListener {
scope,
@ -418,21 +312,16 @@ impl<'a> Mutations<'a> {
});
}
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: impl Into<u64>) {
self.edits.push(RemoveEventListener {
event,
root: root.into(),
});
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option<u64>) {
self.edits.push(RemoveEventListener { event, root });
}
// modify
pub(crate) fn set_text(&mut self, text: &'a str, root: impl Into<u64>) {
let root = root.into();
pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
self.edits.push(SetText { text, root });
}
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: impl Into<u64>) {
let root = root.into();
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
let Attribute {
value, attribute, ..
} = attribute;
@ -445,8 +334,7 @@ impl<'a> Mutations<'a> {
});
}
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: impl Into<u64>) {
let root = root.into();
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option<u64>) {
let Attribute { attribute, .. } = attribute;
self.edits.push(RemoveAttribute {
@ -460,31 +348,32 @@ impl<'a> Mutations<'a> {
self.dirty_scopes.insert(scope);
}
pub(crate) fn create_templete(&mut self, id: impl Into<u64>) {
self.edits.push(CreateTemplate { id: id.into() });
pub(crate) fn clone_node(&mut self, id: Option<u64>, new_id: u64) {
self.edits.push(CloneNode { id, new_id });
}
pub(crate) fn finish_templete(&mut self, len: u32) {
self.edits.push(FinishTemplate { len });
pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
self.edits.push(CloneNodeChildren { id, new_ids });
}
pub(crate) fn create_template_ref(&mut self, id: impl Into<u64>, template_id: u64) {
self.edits.push(CreateTemplateRef {
id: id.into(),
template_id,
})
pub(crate) fn first_child(&mut self) {
self.edits.push(FirstChild {});
}
pub(crate) fn enter_template_ref(&mut self, id: impl Into<u64>) {
self.edits.push(EnterTemplateRef { root: id.into() });
pub(crate) fn next_sibling(&mut self) {
self.edits.push(NextSibling {});
}
pub(crate) fn exit_template_ref(&mut self) {
if let Some(&DomEdit::EnterTemplateRef { .. }) = self.edits.last() {
self.edits.pop();
} else {
self.edits.push(ExitTemplateRef {});
pub(crate) fn parent_node(&mut self) {
self.edits.push(ParentNode {});
}
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,
},
lazynodes::LazyNodes,
template::{TemplateNodeId, VTemplateRef},
template::VTemplateRef,
AnyEvent, Component,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{
cell::{Cell, RefCell},
fmt::{Arguments, Debug, Formatter},
num::ParseIntError,
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.
///
/// 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::Fragment(_) => 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
.debug_struct("VNode::TemplateRef")
.field("template_id", &temp.template_id)
.field("id", &temp.id)
.finish(),
}
}
@ -340,7 +291,7 @@ pub struct VElement<'a> {
/// The parent of the Element (if any).
///
/// Used when bubbling events
pub parent: Cell<Option<GlobalNodeId>>,
pub parent: Cell<Option<ElementId>>,
/// The Listeners of the VElement.
pub listeners: &'a [Listener<'a>],
@ -456,7 +407,7 @@ pub struct Attribute<'a> {
pub struct Listener<'bump> {
/// The ID of the node that this listener is mounted to
/// 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.
///
@ -515,9 +466,11 @@ impl<'a, T> Default for EventHandler<'a, T> {
impl<T> EventHandler<'_, T> {
/// Call this event handler with the appropriate event type
pub fn call(&self, event: T) {
log::trace!("calling event handler");
if let Some(callback) = self.callback.borrow_mut().as_mut() {
callback(event);
}
log::trace!("done");
}
/// 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)));
}
VNode::TemplateRef(self.bump.alloc(VTemplateRef {
id: empty_cell(),
dynamic_context,
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::{
dynamic_template_context::TemplateContext,
innerlude::*,
template::{
TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId, TemplateNodeType,
TemplateValue, TextTemplateSegment,
},
unsafe_utils::extend_vnode,
};
use crate::{innerlude::*, template::TemplateNodeId, unsafe_utils::extend_vnode};
use bumpalo::Bump;
use futures_channel::mpsc::UnboundedSender;
use rustc_hash::FxHashMap;
use slab::Slab;
use std::{
any::{Any, TypeId},
cell::{Cell, Ref, RefCell},
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
future::Future,
pin::Pin,
@ -21,6 +13,17 @@ use std::{
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
/// 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;
@ -40,12 +43,16 @@ pub(crate) struct ScopeArena {
pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
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 template_resolver: RefCell<TemplateResolver>,
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>.
pub template_bump: Bump,
pub template_refs: RefCell<Slab<*const VTemplateRef<'static>>>,
}
impl ScopeArena {
@ -68,7 +75,9 @@ impl ScopeArena {
let node = bump.alloc(VNode::Element(el));
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);
@ -88,6 +97,7 @@ impl ScopeArena {
template_resolver: RefCell::new(TemplateResolver::default()),
templates: Rc::new(RefCell::new(FxHashMap::default())),
template_bump: Bump::new(),
template_refs: RefCell::new(Slab::new()),
}
}
@ -107,7 +117,7 @@ impl ScopeArena {
fc_ptr: ComponentPtr,
vcomp: Box<dyn AnyProps>,
parent_scope: Option<ScopeId>,
container: GlobalNodeId,
container: ElementId,
) -> ScopeId {
// Increment the ScopeId system. ScopeIDs are never reused
let new_scope_id = ScopeId(self.scope_gen.get());
@ -217,13 +227,61 @@ impl ScopeArena {
let key = entry.key();
let id = ElementId(key);
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
}
pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
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) {
@ -324,7 +382,7 @@ impl ScopeArena {
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 mut cur_el = Some(element);
@ -335,39 +393,10 @@ impl ScopeArena {
// stop bubbling if canceled
return;
}
match id {
GlobalNodeId::TemplateId {
template_ref_id,
template_node_id,
} => {
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 };
if let Some(ptr) = nodes.get(id.0) {
match ptr {
NodePtr::VNode(ptr) => {
let real_el = unsafe { &**ptr };
log::trace!("looking for listener on {:?}", real_el);
if let VNode::Element(real_el) = real_el {
@ -393,24 +422,42 @@ impl ScopeArena {
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 {
return;
}
}
fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text, Nodes>(
nodes: &Nodes,
ctx: (
&Ref<Slab<*const VNode>>,
&TemplateContext<'b>,
TemplateNodeId,
&VTemplateRef<'b>,
&UserEvent,
&Rc<BubbleState>,
ElementId,
),
) -> Option<GlobalNodeId>
) -> Option<ElementId>
where
Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
Attributes: AsRef<[TemplateAttribute<V>]>,
V: TemplateValue,
Children: AsRef<[TemplateNodeId]>,
@ -418,8 +465,11 @@ impl ScopeArena {
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
let (vnodes, dynamic_context, event, state, template_ref_id) = ctx;
if let TemplateNodeType::Element(el) = &node.node_type {
let (start, template_ref, event, state) = ctx;
let dynamic_context = &template_ref.dynamic_context;
let mut current = &nodes.as_ref()[start.0];
loop {
if let TemplateNodeType::Element(el) = &current.node_type {
let TemplateElement { listeners, .. } = el;
for listener_idx in listeners.as_ref() {
let listener = dynamic_context.resolve_listener(*listener_idx);
@ -441,23 +491,14 @@ impl ScopeArena {
}
}
if let Some(id) = el.parent {
Some(GlobalNodeId::TemplateId {
template_ref_id,
template_node_id: id,
})
if let Some(id) = current.parent {
current = &nodes.as_ref()[id.0];
} else {
vnodes.get(template_ref_id.0).and_then(|el| {
let real_el = unsafe { &**el };
if let VNode::Element(real_el) = real_el {
real_el.parent.get()
} else {
None
}
})
return template_ref.parent.get();
}
} else {
None
return None;
}
}
}
}
@ -484,11 +525,10 @@ impl ScopeArena {
// this is totally okay since all our nodes are always in a valid state
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
self.nodes
.borrow()
.get(id.0)
.copied()
.map(|ptr| unsafe { extend_vnode(&*ptr) })
self.nodes.borrow().get(id.0).and_then(|ptr| match ptr {
NodePtr::VNode(ptr) => Some(unsafe { extend_vnode(&**ptr) }),
_ => None,
})
}
}
@ -569,7 +609,7 @@ pub struct TaskId {
/// use case they might have.
pub struct ScopeState {
pub(crate) parent_scope: Option<*mut ScopeState>,
pub(crate) container: GlobalNodeId,
pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
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.
//!
//! Notes:
//! 1) Why does the template need to exist outside of the virtual dom?
//! 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.
//! 1) 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.
/// The maxiumum integer in JS
pub const JS_MAX_INT: u64 = 9007199254740991;
use once_cell::unsync::OnceCell;
use std::{
cell::{Cell, RefCell},
hash::Hash,
marker::PhantomData,
ptr,
};
use rustc_hash::FxHashMap;
use std::{cell::Cell, hash::Hash, marker::PhantomData, ops::Index};
use bumpalo::Bump;
use crate::{
diff::DiffState, dynamic_template_context::TemplateContext, innerlude::GlobalNodeId,
nodes::AttributeDiscription, Attribute, AttributeValue, ElementId, Mutations,
OwnedAttributeValue, StaticDynamicNodeMapping,
diff::DiffState, dynamic_template_context::TemplateContext, nodes::AttributeDiscription,
scopes::ScopeArena, Attribute, AttributeValue, ElementId, Mutations, OwnedAttributeValue,
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.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
@ -140,10 +144,8 @@ impl Hash for CodeLocation {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
CodeLocation::Static(loc) => {
loc.crate_path.hash(state);
loc.file_path.hash(state);
state.write_u32(loc.line);
state.write_u32(loc.column);
let loc: &'static _ = *loc;
state.write_usize((loc as *const _) as usize);
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => {
@ -160,7 +162,7 @@ impl Hash for CodeLocation {
impl PartialEq for CodeLocation {
fn eq(&self, other: &Self) -> bool {
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))]
(Self::Dynamic(l), Self::Dynamic(r)) => l == r,
#[cfg(any(feature = "hot-reload", debug_assertions))]
@ -278,25 +280,161 @@ impl From<RendererTemplateId> for u64 {
#[cfg_attr(feature = "serialize", serde(transparent))]
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
pub struct VTemplateRef<'a> {
pub id: Cell<Option<ElementId>>,
pub(crate) template_ref_id: Cell<Option<TemplateRefId>>,
pub template_id: TemplateId,
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> {
// 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>(
nodes: &Nodes,
ctx: (&mut DiffState<'b>, &VTemplateRef<'b>, &Template),
ctx: (
&mut DiffState<'b>,
&'b VTemplateRef<'b>,
&Template,
ElementId,
),
) where
Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
Attributes: AsRef<[TemplateAttribute<V>]>,
@ -306,14 +444,50 @@ impl<'a> VTemplateRef<'a> {
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
let (diff_state, template_ref, template) = ctx;
for id in template.all_dynamic() {
let dynamic_node = &nodes.as_ref()[id.0];
dynamic_node.hydrate(diff_state, template_ref);
let (diff_state, template_ref, template, parent) = ctx;
match template {
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,
/// 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,
/// 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
@ -341,7 +517,9 @@ pub struct OwnedTemplate {
/// The ids of the root nodes in the template
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.
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
@ -355,19 +533,18 @@ pub enum Template {
}
impl Template {
pub(crate) fn create<'b>(
&self,
mutations: &mut Mutations<'b>,
bump: &'b Bump,
id: RendererTemplateId,
) {
mutations.create_templete(id);
pub(crate) fn create<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: ElementId) {
let children = match self {
Template::Static(s) => self.count_real_nodes(s.root_nodes),
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => self.count_real_nodes(&o.root_nodes),
};
mutations.create_element("template", None, Some(id.into()), children as u32);
let empty = match self {
Template::Static(s) => s.nodes.is_empty(),
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => o.nodes.is_empty(),
};
let mut len = 0;
if !empty {
let roots = match self {
Template::Static(s) => s.root_nodes,
@ -375,11 +552,9 @@ impl Template {
Template::Owned(o) => &o.root_nodes,
};
for root in roots {
len += 1;
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) {
@ -395,9 +570,6 @@ impl Template {
Text: AsRef<str>,
{
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 {
TemplateNodeType::Element(el) => {
let TemplateElement {
@ -407,12 +579,11 @@ impl Template {
children,
..
} = el;
mutations.create_element_template(
mutations.create_element(
tag,
*namespace,
id,
locally_static,
fully_static,
None,
template.count_real_nodes(children.as_ref()) as u32,
);
for attr in attributes.as_ref() {
if let TemplateAttributeValue::Static(val) = &attr.value {
@ -422,34 +593,24 @@ impl Template {
is_static: true,
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() {
template.create_node(mutations, bump, *child);
children_created += 1;
}
mutations.append_children(children_created);
}
TemplateNodeType::Text(text) => {
let mut text_iter = text.segments.as_ref().iter();
if let (Some(TextTemplateSegment::Static(txt)), None) =
(text_iter.next(), text_iter.next())
{
mutations.create_text_node_template(
bump.alloc_str(txt.as_ref()),
id,
locally_static,
);
mutations.create_text_node(bump.alloc_str(txt.as_ref()), None);
} else {
mutations.create_text_node_template("", id, locally_static);
mutations.create_text_node("", None);
}
}
TemplateNodeType::DynamicNode(_) => {
mutations.create_placeholder_template(id);
}
TemplateNodeType::DynamicNode(_) => {}
}
}
self.with_node(
@ -496,10 +657,10 @@ impl Template {
}
#[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
F1: FnMut(&'a &'static [StaticTemplateNode], Ctx),
F2: FnMut(&'a Vec<OwnedTemplateNode>, Ctx),
F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
F2: FnMut(&'a Vec<OwnedTemplateNode>, Ctx) -> R,
{
match self {
Template::Static(s) => f1(&s.nodes, ctx),
@ -508,23 +669,40 @@ impl Template {
}
#[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
F1: FnMut(&'a &'static [StaticTemplateNode], Ctx),
F2: FnMut(&'a &'static [StaticTemplateNode], Ctx),
F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
F2: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
{
match self {
Template::Static(s) => f1(&s.nodes, ctx),
}
}
pub(crate) fn all_dynamic<'a>(&'a self) -> Box<dyn Iterator<Item = TemplateNodeId> + 'a> {
match self {
Template::Static(s) => Box::new(s.dynamic_mapping.all_dynamic()),
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => Box::new(o.dynamic_mapping.all_dynamic()),
fn count_real_nodes(&self, ids: &[TemplateNodeId]) -> usize {
fn count_real_nodes_inner<Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
nodes: &Nodes,
id: TemplateNodeId,
) -> 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>(
&'a self,
@ -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] {
match self {
Template::Static(s) => s.dynamic_mapping.text[idx],
@ -568,6 +738,14 @@ impl Template {
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
@ -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.
/// 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.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
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.
pub id: TemplateNodeId,
/// If the id of the node must be kept in the refrences
pub locally_static: bool,
/// If any children of this node must be kept in the references
pub fully_static: bool,
/// The depth of the node in the template node tree
/// Root nodes have a depth of 0
pub depth: usize,
/// The type of the [`TemplateNode`].
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>
@ -641,14 +820,12 @@ where
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
fn hydrate<'b>(&self, diff_state: &mut DiffState<'b>, template_ref: &VTemplateRef<'b>) {
let real_id = template_ref.id.get().unwrap();
diff_state.element_stack.push(GlobalNodeId::TemplateId {
template_ref_id: real_id,
template_node_id: self.id,
});
diff_state.mutations.enter_template_ref(real_id);
fn hydrate<'b>(
&self,
real_node_id: ElementId,
diff_state: &mut DiffState<'b>,
template_ref: &'b VTemplateRef<'b>,
) {
match &self.node_type {
TemplateNodeType::Element(el) => {
let TemplateElement {
@ -667,42 +844,35 @@ where
is_static: false,
};
let scope_bump = diff_state.current_scope_bump();
diff_state
.mutations
.set_attribute(scope_bump.alloc(attribute), self.id);
diff_state.mutations.set_attribute(
scope_bump.alloc(attribute),
Some(real_node_id.as_u64()),
);
}
}
for listener_idx in listeners.as_ref() {
let listener = template_ref.dynamic_context.resolve_listener(*listener_idx);
let global_id = GlobalNodeId::TemplateId {
template_ref_id: real_id,
template_node_id: self.id,
};
listener.mounted_node.set(Some(global_id));
listener.mounted_node.set(Some(real_node_id));
diff_state
.mutations
.new_event_listener(listener, diff_state.current_scope());
}
}
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 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
.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();
}
}
/// A template for an attribute
@ -824,48 +994,6 @@ where
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;
/// A element template
@ -899,8 +1027,6 @@ where
pub children: Children,
/// The ids of the listeners of the element
pub listeners: Listeners,
/// The parent of the element
pub parent: Option<TemplateNodeId>,
value: PhantomData<V>,
}
@ -918,7 +1044,6 @@ where
attributes: Attributes,
children: Children,
listeners: Listeners,
parent: Option<TemplateNodeId>,
) -> Self {
TemplateElement {
tag,
@ -926,7 +1051,6 @@ where
attributes,
children,
listeners,
parent,
value: PhantomData,
}
}
@ -945,6 +1069,8 @@ where
{
/// The segments of the template.
pub segments: Segments,
/// The minimum size of the output text.
pub min_size: usize,
text: PhantomData<Text>,
}
@ -954,9 +1080,10 @@ where
Text: AsRef<str>,
{
/// 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 {
segments,
min_size,
text: PhantomData,
}
}
@ -1005,8 +1132,7 @@ pub enum StaticAttributeValue {
#[derive(Default)]
pub(crate) struct TemplateResolver {
// 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_count: usize,
pub template_id_mapping: FxHashMap<TemplateId, (ElementId, bool)>,
}
impl TemplateResolver {
@ -1028,15 +1154,14 @@ impl TemplateResolver {
pub fn get_or_create_client_id(
&mut self,
template_id: &TemplateId,
) -> (RendererTemplateId, bool) {
scopes: &ScopeArena,
) -> (ElementId, bool) {
if let Some(id) = self.template_id_mapping.get(template_id) {
*id
} else {
let id = self.template_count;
let renderer_id = RendererTemplateId(id);
let renderer_id = scopes.reserve_phantom_node();
self.template_id_mapping
.insert(template_id.clone(), (renderer_id, false));
self.template_count += 1;
(renderer_id, true)
}
}
@ -1047,3 +1172,116 @@ impl TemplateResolver {
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
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 {
root: ElementId,
scopes: ScopeArena,
pending_messages: VecDeque<SchedulerMsg>,
@ -230,10 +231,11 @@ impl VirtualDom {
render_fn: root,
}),
None,
GlobalNodeId::VNodeId(ElementId(0)),
ElementId(0),
);
Self {
root: ElementId(0),
scopes,
channel,
dirty_scopes: IndexSet::from_iter([ScopeId(0)]),
@ -495,7 +497,7 @@ impl VirtualDom {
self.scopes.run_scope(scopeid);
diff_state.diff_scope(scopeid);
diff_state.diff_scope(self.root, scopeid);
let DiffState { mutations, .. } = diff_state;
@ -551,15 +553,15 @@ impl VirtualDom {
let mut diff_state = DiffState::new(&self.scopes);
self.scopes.run_scope(scope_id);
diff_state
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
diff_state.scope_stack.push(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();
assert!(self.dirty_scopes.is_empty());
@ -609,9 +611,8 @@ impl VirtualDom {
diff_machine.force_diff = true;
diff_machine.scope_stack.push(scope_id);
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
}
@ -650,11 +651,8 @@ impl VirtualDom {
/// ```
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes);
machine
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
machine.scope_stack.push(ScopeId(0));
machine.diff_node(old, new);
machine.diff_node(self.root, old, new);
machine.mutations
}
@ -675,12 +673,12 @@ impl VirtualDom {
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes);
machine.scope_stack.push(ScopeId(0));
machine
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
let node = self.render_vnodes(nodes);
let created = machine.create_node(node);
machine.mutations.append_children(created as u32);
let mut created = Vec::new();
machine.create_node(self.root, node, &mut created);
machine
.mutations
.append_children(Some(self.root.as_u64()), created);
machine.mutations
}
@ -706,16 +704,15 @@ impl VirtualDom {
let mut create = DiffState::new(&self.scopes);
create.scope_stack.push(ScopeId(0));
let mut created = Vec::new();
create.create_node(self.root, old, &mut created);
create
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
let created = create.create_node(old);
create.mutations.append_children(created as u32);
.mutations
.append_children(Some(self.root.as_u64()), created);
let mut edit = DiffState::new(&self.scopes);
edit.scope_stack.push(ScopeId(0));
edit.element_stack.push(GlobalNodeId::VNodeId(ElementId(0)));
edit.diff_node(old, new);
edit.diff_node(self.root, old, new);
(create.mutations, edit.mutations)
}

View file

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

View file

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

View file

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

View file

@ -37,37 +37,31 @@ fn test_early_abort() {
assert_eq!(
edits.edits,
[
CreateTemplate { id: 0 },
CreateElementTemplate {
root: 4503599627370495,
tag: "div",
locally_static: true,
fully_static: true
},
CreateTextNodeTemplate {
root: 4503599627370496,
text: "Hello, world!",
locally_static: true
},
AppendChildren { many: 1 },
FinishTemplate { len: 1 },
CreateTemplateRef { id: 1, template_id: 0 },
AppendChildren { many: 1 }
// create template
CreateElement { root: Some(1), tag: "template", children: 1 },
CreateElement { root: None, tag: "div", children: 1 },
CreateTextNode { root: None, text: "Hello, world!" },
// clone template
CloneNodeChildren { id: Some(1), new_ids: vec![2] },
AppendChildren { root: Some(0), children: vec![2] }
]
);
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));
assert_eq!(
edits.edits,
[
CreateTemplateRef { id: 1, template_id: 0 }, // gets reused
ReplaceWith { root: 2, m: 1 }
CreatePlaceholder { root: Some(3) },
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!(
edits.edits,
[
CreateElement { tag: "div", root: 1 },
NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 },
CreateElement { tag: "div", root: 2 },
CreateTextNode { text: "nested", root: 3 },
AppendChildren { many: 1 },
CreateTextNode { text: "Click me!", root: 4 },
AppendChildren { many: 2 },
AppendChildren { many: 1 },
CreateElement { root: Some(1), tag: "div", children: 0 },
NewEventListener { event_name: "click", scope: ScopeId(0), root: Some(1) },
CreateElement { root: Some(2), tag: "div", children: 0 },
CreateTextNode { root: Some(3), text: "nested" },
AppendChildren { root: Some(2), children: vec![3] },
CreateTextNode { root: Some(4), text: "Click me!" },
AppendChildren { root: Some(1), children: vec![2, 4] },
AppendChildren { root: Some(0), children: vec![1] }
]
)
}
@ -105,24 +105,24 @@ fn components_generate() {
assert_eq!(
edits.edits,
[
CreateTextNode { text: "Text0", root: 1 },
AppendChildren { many: 1 },
CreateTextNode { root: Some(1), text: "Text0" },
AppendChildren { root: Some(0), children: vec![1] }
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "div", root: 2 },
ReplaceWith { root: 1, m: 1 },
CreateElement { root: Some(2), tag: "div", children: 0 },
ReplaceWith { root: Some(1), nodes: vec![2] }
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "Text2", root: 1 },
ReplaceWith { root: 2, m: 1 },
CreateTextNode { root: Some(1), text: "Text2" },
ReplaceWith { root: Some(2), nodes: vec![1] }
]
);
@ -130,40 +130,43 @@ fn components_generate() {
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "h1", root: 2 },
ReplaceWith { root: 1, m: 1 },
CreateElement { root: Some(2), tag: "h1", children: 0 },
ReplaceWith { root: Some(1), nodes: vec![2] }
]
);
// placeholder
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "text 3", root: 2 },
ReplaceWith { root: 1, m: 1 },
CreatePlaceholder { root: Some(1) },
ReplaceWith { root: Some(2), nodes: vec![1] }
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "text 0", root: 1 },
CreateTextNode { text: "text 1", root: 3 },
ReplaceWith { root: 2, m: 2 },
CreateTextNode { root: Some(2), text: "text 3" },
ReplaceWith { root: Some(1), nodes: vec![2] }
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "h1", root: 2 },
ReplaceWith { root: 1, m: 1 },
Remove { root: 3 },
CreateTextNode { text: "text 0", root: Some(1) },
CreateTextNode { text: "text 1", root: Some(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!(
edits.edits,
[
CreateElement { tag: "div", root: 1 },
CreateTextNode { text: "hi", root: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
CreateElement { tag: "div", root: Some(1), children: 0 },
CreateTextNode { text: "hi", root: Some(2) },
AppendChildren { root: Some(1), children: vec![2] },
AppendChildren { root: Some(0), children: vec![1] },
]
)
}
@ -88,13 +88,13 @@ fn nested_passthru_creates_add() {
assert_eq!(
edits.edits,
[
CreateTextNode { text: "1", root: 1 },
CreateTextNode { text: "2", root: 2 },
CreateTextNode { text: "3", root: 3 },
CreateElement { tag: "div", root: 4 },
CreateTextNode { text: "hi", root: 5 },
AppendChildren { many: 1 },
AppendChildren { many: 4 },
CreateTextNode { text: "1", root: Some(1) },
CreateTextNode { text: "2", root: Some(2) },
CreateTextNode { text: "3", root: Some(3) },
CreateElement { tag: "div", root: Some(4), children: 0 },
CreateTextNode { text: "hi", root: Some(5) },
AppendChildren { root: Some(4), children: vec![5] },
AppendChildren { root: Some(0), children: vec![1, 2, 3, 4] },
]
)
}

View file

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

View file

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

View file

@ -15,102 +15,83 @@ extern "C" {
pub fn SetNode(this: &Interpreter, id: usize, node: Node);
#[wasm_bindgen(method)]
pub fn PushRoot(this: &Interpreter, root: u64);
pub fn AppendChildren(this: &Interpreter, root: Option<u64>, children: Vec<u64>);
#[wasm_bindgen(method)]
pub fn PopRoot(this: &Interpreter);
pub fn ReplaceWith(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
#[wasm_bindgen(method)]
pub fn AppendChildren(this: &Interpreter, many: u32);
pub fn InsertAfter(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
#[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)]
pub fn InsertAfter(this: &Interpreter, root: u64, n: u32);
pub fn Remove(this: &Interpreter, root: Option<u64>);
#[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)]
pub fn Remove(this: &Interpreter, root: u64);
pub fn CreateElement(this: &Interpreter, tag: &str, root: Option<u64>, children: u32);
#[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)]
pub fn CreateElement(this: &Interpreter, tag: &str, root: 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);
pub fn CreatePlaceholder(this: &Interpreter, root: Option<u64>);
#[wasm_bindgen(method)]
pub fn NewEventListener(
this: &Interpreter,
name: &str,
root: u64,
root: Option<u64>,
handler: &Function,
bubbles: bool,
);
#[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)]
pub fn SetText(this: &Interpreter, root: u64, text: JsValue);
pub fn SetText(this: &Interpreter, root: Option<u64>, text: JsValue);
#[wasm_bindgen(method)]
pub fn SetAttribute(
this: &Interpreter,
root: u64,
root: Option<u64>,
field: &str,
value: JsValue,
ns: Option<&str>,
);
#[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)]
pub fn CreateTemplateRef(this: &Interpreter, id: u64, template_id: u64);
pub fn CloneNode(this: &Interpreter, root: Option<u64>, new_id: u64);
#[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)]
pub fn FinishTemplate(this: &Interpreter, len: u32);
pub fn FirstChild(this: &Interpreter);
#[wasm_bindgen(method)]
pub fn EnterTemplateRef(this: &Interpreter, id: u64);
pub fn NextSibling(this: &Interpreter);
#[wasm_bindgen(method)]
pub fn ExitTemplateRef(this: &Interpreter);
pub fn ParentNode(this: &Interpreter);
#[wasm_bindgen(method)]
pub fn CreateElementTemplate(
this: &Interpreter,
tag: &str,
root: u64,
locally_static: bool,
fully_static: bool,
);
pub fn StoreWithId(this: &Interpreter, id: u64);
#[wasm_bindgen(method)]
pub fn CreateElementNsTemplate(
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);
pub fn SetLastNode(this: &Interpreter, id: 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() {
let root = window.document.getElementById("main");
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 {
constructor(root) {
// bubbling events can listen at the root element
@ -185,188 +58,155 @@ class ListenerMap {
export class Interpreter {
constructor(root) {
this.root = root;
this.stack = [root];
this.templateInProgress = null;
this.insideTemplateRef = [];
this.lastNode = root;
this.listeners = new ListenerMap(root);
this.handlers = {};
this.nodes = [root];
this.templates = [];
this.parents = [];
}
top() {
return this.stack[this.stack.length - 1];
checkAppendParent() {
if (this.parents.length > 0) {
const lastParent = this.parents[this.parents.length - 1];
lastParent[1]--;
if (lastParent[1] === 0) {
this.parents.pop();
}
pop() {
return this.stack.pop();
}
currentTemplateId() {
if (this.insideTemplateRef.length) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
}
else {
return null;
lastParent[0].appendChild(this.lastNode);
}
}
getId(id) {
if (this.templateInProgress !== null) {
return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
AppendChildren(root, children) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
}
else {
return this.nodes[id];
for (let i = 0; i < children.length; i++) {
node.appendChild(this.nodes[children[i]]);
}
}
SetNode(id, node) {
if (this.templateInProgress !== null) {
id -= templateIdLimit;
node.tmplId = id;
this.templates[this.templateInProgress].nodes[id] = node;
ReplaceWith(root, nodes) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
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;
let els = [];
for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
}
node.replaceWith(...els);
}
else {
this.nodes[id] = node;
InsertAfter(root, nodes) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
let els = [];
for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
}
PushRoot(root) {
const node = this.getId(root);
this.stack.push(node);
node.after(...els);
}
PopRoot() {
this.stack.pop();
InsertBefore(root, nodes) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
AppendChildren(many) {
let root = this.stack[this.stack.length - (1 + many)];
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) {
let root = this.getId(root_id);
if (root instanceof TemplateRef) {
this.InsertBefore(root_id, m);
this.Remove(root_id);
}
else {
let els = this.stack.splice(this.stack.length - m).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
root.replaceWith(...els);
}
}
InsertAfter(root, n) {
const old = this.getId(root);
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
if (old instanceof TemplateRef) {
const last = old.last();
last.after(...new_nodes);
}
else {
old.after(...new_nodes);
}
}
InsertBefore(root, n) {
const old = this.getId(root);
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
if (old instanceof TemplateRef) {
const first = old.first();
first.before(...new_nodes);
}
else {
old.before(...new_nodes);
let els = [];
for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
}
node.before(...els);
}
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 instanceof TemplateRef) {
for (let child of node.roots) {
child.remove();
}
}
else {
node.remove();
}
}
}
CreateTextNode(text, root) {
const node = document.createTextNode(text);
this.stack.push(node);
this.SetNode(root, node);
this.lastNode = document.createTextNode(text);
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
CreateElement(tag, root) {
const el = document.createElement(tag);
this.stack.push(el);
this.SetNode(root, el);
}
CreateElementNs(tag, root, ns) {
let el = document.createElementNS(ns, tag);
this.stack.push(el);
this.SetNode(root, el);
CreateElement(tag, root, children) {
this.lastNode = document.createElement(tag);
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
}
CreateElementNs(tag, root, ns, children) {
this.lastNode = document.createElementNS(ns, tag);
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
}
CreatePlaceholder(root) {
let el = document.createElement("pre");
el.hidden = true;
this.stack.push(el);
this.SetNode(root, el);
this.lastNode = document.createElement("pre");
this.lastNode.hidden = true;
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
}
NewEventListener(event_name, root, handler, bubbles) {
const element = this.getId(root);
if (root >= templateIdLimit) {
let currentTemplateRefId = this.currentTemplateId();
root -= templateIdLimit;
element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
else {
element.setAttribute("data-dioxus-id", `${root}`);
}
this.listeners.create(event_name, element, handler, bubbles);
node.setAttribute("data-dioxus-id", `${root}`);
this.listeners.create(event_name, node, handler, bubbles);
}
RemoveEventListener(root, event_name, bubbles) {
const element = this.getId(root);
element.removeAttribute(`data-dioxus-id`);
this.listeners.remove(element, event_name, bubbles);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
node.removeAttribute(`data-dioxus-id`);
this.listeners.remove(node, event_name, bubbles);
}
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) {
const name = field;
const node = this.getId(root);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns === "style") {
// @ts-ignore
node.style[name] = value;
@ -400,7 +240,12 @@ export class Interpreter {
}
RemoveAttribute(root, field, ns) {
const name = field;
const node = this.getId(root);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns == "style") {
node.style.removeProperty(name);
} else if (ns !== null || ns !== undefined) {
@ -417,94 +262,82 @@ export class Interpreter {
node.removeAttribute(name);
}
}
CreateTemplateRef(id, template_id) {
const el = this.templates[template_id].ref(id);
this.nodes[id] = el;
this.stack.push(el);
CloneNode(old, new_id) {
let node;
if (old === null) {
node = this.lastNode;
} else {
node = this.nodes[old];
}
CreateTemplate(template_id) {
this.templateInProgress = template_id;
this.templates[template_id] = new Template(template_id, 0);
this.nodes[new_id] = node.cloneNode(true);
}
FinishTemplate(many) {
this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many));
this.templateInProgress = null;
CloneNodeChildren(old, new_ids) {
let node;
if (old === null) {
node = this.lastNode;
} else {
node = this.nodes[old];
}
EnterTemplateRef(id) {
this.insideTemplateRef.push(this.nodes[id]);
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;
}
ExitTemplateRef() {
this.insideTemplateRef.pop();
}
FirstChild() {
this.lastNode = this.lastNode.firstChild;
}
NextSibling() {
this.lastNode = this.lastNode.nextSibling;
}
ParentNode() {
this.lastNode = this.lastNode.parentNode;
}
StoreWithId(id) {
this.nodes[id] = this.lastNode;
}
SetLastNode(root) {
this.lastNode = this.nodes[root];
}
handleEdits(edits) {
for (let edit of edits) {
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) {
switch (edit.type) {
case "PushRoot":
this.PushRoot(BigInt(edit.root));
this.PushRoot(edit.root);
break;
case "AppendChildren":
this.AppendChildren(edit.many);
this.AppendChildren(edit.root, edit.children);
break;
case "ReplaceWith":
this.ReplaceWith(BigInt(edit.root), edit.m);
this.ReplaceWith(edit.root, edit.nodes);
break;
case "InsertAfter":
this.InsertAfter(BigInt(edit.root), edit.n);
this.InsertAfter(edit.root, edit.nodes);
break;
case "InsertBefore":
this.InsertBefore(BigInt(edit.root), edit.n);
this.InsertBefore(edit.root, edit.nodes);
break;
case "Remove":
this.Remove(BigInt(edit.root));
this.Remove(edit.root);
break;
case "CreateTextNode":
this.CreateTextNode(edit.text, BigInt(edit.root));
this.CreateTextNode(edit.text, edit.root);
break;
case "CreateElement":
this.CreateElement(edit.tag, BigInt(edit.root));
this.CreateElement(edit.tag, edit.root, edit.children);
break;
case "CreateElementNs":
this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns);
this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children);
break;
case "CreatePlaceholder":
this.CreatePlaceholder(BigInt(edit.root));
this.CreatePlaceholder(edit.root);
break;
case "RemoveEventListener":
this.RemoveEventListener(BigInt(edit.root), edit.event_name);
this.RemoveEventListener(edit.root, edit.event_name);
break;
case "NewEventListener":
// this handler is only provided on desktop implementations since this
@ -588,16 +421,7 @@ export class Interpreter {
if (realId === null) {
return;
}
if (realId.includes(",")) {
realId = realId.split(',');
realId = {
template_ref_id: parseInt(realId[0]),
template_node_id: parseInt(realId[1]),
};
}
else {
realId = parseInt(realId);
}
window.ipc.postMessage(
serializeIpcMessage("user_event", {
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;
case "SetText":
this.SetText(BigInt(edit.root), edit.text);
this.SetText(edit.root, edit.text);
break;
case "SetAttribute":
this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns);
this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
break;
case "RemoveAttribute":
this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns);
this.RemoveAttribute(edit.root, edit.name, edit.ns);
break;
case "PopRoot":
this.PopRoot();
case "CloneNode":
this.CloneNode(edit.id, edit.new_id);
break;
case "CreateTemplateRef":
this.CreateTemplateRef(BigInt(edit.id), edit.template_id);
case "CloneNodeChildren":
this.CloneNodeChildren(edit.id, edit.new_ids);
break;
case "CreateTemplate":
this.CreateTemplate(BigInt(edit.id));
case "FirstChild":
this.FirstChild();
break;
case "FinishTemplate":
this.FinishTemplate(edit.len);
case "NextSibling":
this.NextSibling();
break;
case "EnterTemplateRef":
this.EnterTemplateRef(BigInt(edit.root));
case "ParentNode":
this.ParentNode();
break;
case "ExitTemplateRef":
this.ExitTemplateRef();
case "StoreWithId":
this.StoreWithId(BigInt(edit.id));
break;
case "CreateElementTemplate":
this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static);
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));
case "SetLastNode":
this.SetLastNode(BigInt(edit.id));
break;
}
}

View file

@ -5,7 +5,7 @@
use std::any::Any;
use std::sync::Arc;
use dioxus_core::GlobalNodeId;
use dioxus_core::ElementId;
use dioxus_core::{EventPriority, UserEvent};
use dioxus_html::event_bubbles;
use dioxus_html::on::*;
@ -26,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
#[derive(serde::Serialize, serde::Deserialize)]
struct ImEvent {
event: String,
mounted_dom_id: GlobalNodeId,
mounted_dom_id: ElementId,
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 {
constructor(root) {
// bubbling events can listen at the root element
@ -208,188 +87,155 @@ class ListenerMap {
class Interpreter {
constructor(root) {
this.root = root;
this.stack = [root];
this.templateInProgress = null;
this.insideTemplateRef = [];
this.lastNode = root;
this.listeners = new ListenerMap(root);
this.handlers = {};
this.nodes = [root];
this.templates = [];
this.parents = [];
}
top() {
return this.stack[this.stack.length - 1];
checkAppendParent() {
if (this.parents.length > 0) {
const lastParent = this.parents[this.parents.length - 1];
lastParent[1]--;
if (lastParent[1] === 0) {
this.parents.pop();
}
pop() {
return this.stack.pop();
}
currentTemplateId() {
if (this.insideTemplateRef.length) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
}
else {
return null;
lastParent[0].appendChild(this.lastNode);
}
}
getId(id) {
if (this.templateInProgress !== null) {
return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
AppendChildren(root, children) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
}
else {
return this.nodes[id];
for (let i = 0; i < children.length; i++) {
node.appendChild(this.nodes[children[i]]);
}
}
SetNode(id, node) {
if (this.templateInProgress !== null) {
id -= templateIdLimit;
node.tmplId = id;
this.templates[this.templateInProgress].nodes[id] = node;
ReplaceWith(root, nodes) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
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;
let els = [];
for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
}
node.replaceWith(...els);
}
else {
this.nodes[id] = node;
InsertAfter(root, nodes) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
let els = [];
for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
}
PushRoot(root) {
const node = this.getId(root);
this.stack.push(node);
node.after(...els);
}
PopRoot() {
this.stack.pop();
InsertBefore(root, nodes) {
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
AppendChildren(many) {
let root = this.stack[this.stack.length - (1 + many)];
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) {
let root = this.getId(root_id);
if (root instanceof TemplateRef) {
this.InsertBefore(root_id, m);
this.Remove(root_id);
}
else {
let els = this.stack.splice(this.stack.length - m).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
root.replaceWith(...els);
}
}
InsertAfter(root, n) {
const old = this.getId(root);
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
if (old instanceof TemplateRef) {
const last = old.last();
last.after(...new_nodes);
}
else {
old.after(...new_nodes);
}
}
InsertBefore(root, n) {
const old = this.getId(root);
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
if (el instanceof TemplateRef) {
return el.getFragment();
}
else {
return el;
}
});
if (old instanceof TemplateRef) {
const first = old.first();
first.before(...new_nodes);
}
else {
old.before(...new_nodes);
let els = [];
for (let i = 0; i < nodes.length; i++) {
els.push(this.nodes[nodes[i]])
}
node.before(...els);
}
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 instanceof TemplateRef) {
for (let child of node.roots) {
child.remove();
}
}
else {
node.remove();
}
}
}
CreateTextNode(text, root) {
const node = document.createTextNode(text);
this.stack.push(node);
this.SetNode(root, node);
this.lastNode = document.createTextNode(text);
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
CreateElement(tag, root) {
const el = document.createElement(tag);
this.stack.push(el);
this.SetNode(root, el);
}
CreateElementNs(tag, root, ns) {
let el = document.createElementNS(ns, tag);
this.stack.push(el);
this.SetNode(root, el);
CreateElement(tag, root, children) {
this.lastNode = document.createElement(tag);
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
}
CreateElementNs(tag, root, ns, children) {
this.lastNode = document.createElementNS(ns, tag);
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
if (children > 0) {
this.parents.push([this.lastNode, children]);
}
}
CreatePlaceholder(root) {
let el = document.createElement("pre");
el.hidden = true;
this.stack.push(el);
this.SetNode(root, el);
this.lastNode = document.createElement("pre");
this.lastNode.hidden = true;
this.checkAppendParent();
if (root != null) {
this.nodes[root] = this.lastNode;
}
}
NewEventListener(event_name, root, handler, bubbles) {
const element = this.getId(root);
if (root >= templateIdLimit) {
let currentTemplateRefId = this.currentTemplateId();
root -= templateIdLimit;
element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
else {
element.setAttribute("data-dioxus-id", `${root}`);
}
this.listeners.create(event_name, element, handler, bubbles);
node.setAttribute("data-dioxus-id", `${root}`);
this.listeners.create(event_name, node, handler, bubbles);
}
RemoveEventListener(root, event_name, bubbles) {
const element = this.getId(root);
element.removeAttribute(`data-dioxus-id`);
this.listeners.remove(element, event_name, bubbles);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
node.removeAttribute(`data-dioxus-id`);
this.listeners.remove(node, event_name, bubbles);
}
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) {
const name = field;
const node = this.getId(root);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns === "style") {
// @ts-ignore
node.style[name] = value;
@ -423,7 +269,12 @@ class Interpreter {
}
RemoveAttribute(root, field, ns) {
const name = field;
const node = this.getId(root);
let node;
if (root == null) {
node = this.lastNode;
} else {
node = this.nodes[root];
}
if (ns == "style") {
node.style.removeProperty(name);
} else if (ns !== null || ns !== undefined) {
@ -440,94 +291,82 @@ class Interpreter {
node.removeAttribute(name);
}
}
CreateTemplateRef(id, template_id) {
const el = this.templates[template_id].ref(id);
this.nodes[id] = el;
this.stack.push(el);
CloneNode(old, new_id) {
let node;
if (old === null) {
node = this.lastNode;
} else {
node = this.nodes[old];
}
CreateTemplate(template_id) {
this.templateInProgress = template_id;
this.templates[template_id] = new Template(template_id, 0);
this.nodes[new_id] = node.cloneNode(true);
}
FinishTemplate(many) {
this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many));
this.templateInProgress = null;
CloneNodeChildren(old, new_ids) {
let node;
if (old === null) {
node = this.lastNode;
} else {
node = this.nodes[old];
}
EnterTemplateRef(id) {
this.insideTemplateRef.push(this.nodes[id]);
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;
}
ExitTemplateRef() {
this.insideTemplateRef.pop();
}
FirstChild() {
this.lastNode = this.lastNode.firstChild;
}
NextSibling() {
this.lastNode = this.lastNode.nextSibling;
}
ParentNode() {
this.lastNode = this.lastNode.parentNode;
}
StoreWithId(id) {
this.nodes[id] = this.lastNode;
}
SetLastNode(root) {
this.lastNode = this.nodes[root];
}
handleEdits(edits) {
for (let edit of edits) {
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) {
switch (edit.type) {
case "PushRoot":
this.PushRoot(BigInt(edit.root));
this.PushRoot(edit.root);
break;
case "AppendChildren":
this.AppendChildren(edit.many);
this.AppendChildren(edit.root, edit.children);
break;
case "ReplaceWith":
this.ReplaceWith(BigInt(edit.root), edit.m);
this.ReplaceWith(edit.root, edit.nodes);
break;
case "InsertAfter":
this.InsertAfter(BigInt(edit.root), edit.n);
this.InsertAfter(edit.root, edit.nodes);
break;
case "InsertBefore":
this.InsertBefore(BigInt(edit.root), edit.n);
this.InsertBefore(edit.root, edit.nodes);
break;
case "Remove":
this.Remove(BigInt(edit.root));
this.Remove(edit.root);
break;
case "CreateTextNode":
this.CreateTextNode(edit.text, BigInt(edit.root));
this.CreateTextNode(edit.text, edit.root);
break;
case "CreateElement":
this.CreateElement(edit.tag, BigInt(edit.root));
this.CreateElement(edit.tag, edit.root, edit.children);
break;
case "CreateElementNs":
this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns);
this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children);
break;
case "CreatePlaceholder":
this.CreatePlaceholder(BigInt(edit.root));
this.CreatePlaceholder(edit.root);
break;
case "RemoveEventListener":
this.RemoveEventListener(BigInt(edit.root), edit.event_name);
this.RemoveEventListener(edit.root, edit.event_name);
break;
case "NewEventListener":
// this handler is only provided on desktop implementations since this
@ -547,7 +386,7 @@ class Interpreter {
event.preventDefault();
const href = target.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) {
window.ipc.send(
window.ipc.postMessage(
serializeIpcMessage("browser_open", { href })
);
}
@ -611,16 +450,7 @@ class Interpreter {
if (realId === null) {
return;
}
if (realId.includes(",")) {
realId = realId.split(',');
realId = {
template_ref_id: parseInt(realId[0]),
template_node_id: parseInt(realId[1]),
};
}
else {
realId = parseInt(realId);
}
window.ipc.send(
serializeIpcMessage("user_event", {
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;
case "SetText":
this.SetText(BigInt(edit.root), edit.text);
this.SetText(edit.root, edit.text);
break;
case "SetAttribute":
this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns);
this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
break;
case "RemoveAttribute":
this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns);
this.RemoveAttribute(edit.root, edit.name, edit.ns);
break;
case "PopRoot":
this.PopRoot();
case "CloneNode":
this.CloneNode(edit.id, edit.new_id);
break;
case "CreateTemplateRef":
this.CreateTemplateRef(BigInt(edit.id), edit.template_id);
case "CloneNodeChildren":
this.CloneNodeChildren(edit.id, edit.new_ids);
break;
case "CreateTemplate":
this.CreateTemplate(BigInt(edit.id));
case "FirstChild":
this.FirstChild();
break;
case "FinishTemplate":
this.FinishTemplate(edit.len);
case "NextSibling":
this.NextSibling();
break;
case "EnterTemplateRef":
this.EnterTemplateRef(BigInt(edit.root));
case "ParentNode":
this.ParentNode();
break;
case "ExitTemplateRef":
this.ExitTemplateRef();
case "StoreWithId":
this.StoreWithId(BigInt(edit.id));
break;
case "CreateElementTemplate":
this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static);
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));
case "SetLastNode":
this.SetLastNode(BigInt(edit.id));
break;
}
}

View file

@ -164,59 +164,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let gen = quote! {
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>>(
dirty: &[(dioxus_core::GlobalNodeId, dioxus_native_core::node_ref::NodeMask)],
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_native_core::RealNodeId, dioxus_native_core::node_ref::NodeMask)],
state_tree: &'a mut T,
rdom: &'a T2,
ctx: &anymap::AnyMap,
) -> rustc_hash::FxHashSet<dioxus_core::GlobalNodeId>{
#[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))
}
}
) -> rustc_hash::FxHashSet<dioxus_native_core::RealNodeId>{
#[derive(Clone, Copy)]
struct MembersDirty {
#(#members: bool, )*
@ -238,7 +191,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let mut dirty_elements = rustc_hash::FxHashSet::default();
// 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 {
let members_dirty = MembersDirty {
@ -408,7 +361,7 @@ impl<'a> StateStruct<'a> {
let insert = dep.child.iter().map(|d|{
if *d == mem {
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
.binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){
resolution_order.insert(
@ -453,7 +406,7 @@ impl<'a> StateStruct<'a> {
let insert = dep.parent.iter().map(|d| {
if *d == mem {
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
.binary_search(&seeking){
resolution_order.insert(
@ -508,7 +461,7 @@ impl<'a> StateStruct<'a> {
DependencyKind::Parent => {
quote! {
// 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();
let mut i = 0;
while i < resolution_order.len(){
@ -528,7 +481,7 @@ impl<'a> StateStruct<'a> {
DependencyKind::Child => {
quote! {
// 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| {
height_ordering1.cmp(&height_ordering2).reverse()
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,78 @@
use std::cmp::Ordering;
use dioxus_core::ElementId;
pub mod layout_attributes;
pub mod node_ref;
pub mod real_dom;
pub mod state;
pub mod template;
#[doc(hidden)]
pub mod traversable;
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::{
real_dom::{NodeData, NodeType, OwnedAttributeView},
state::union_ordered_iter,
RealNodeId,
};
/// A view into a [VNode] with limited access.
@ -22,8 +21,8 @@ impl<'a> NodeView<'a> {
}
/// Get the id of the node
pub fn id(&self) -> GlobalNodeId {
self.inner.id
pub fn id(&self) -> RealNodeId {
self.inner.id.unwrap()
}
/// 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 anymap::AnyMap;
use dioxus_core::GlobalNodeId;
use rustc_hash::FxHashSet;
use crate::node_ref::{NodeMask, NodeView};
use crate::real_dom::NodeData;
use crate::traversable::Traversable;
use crate::RealNodeId;
use anymap::AnyMap;
use rustc_hash::FxHashSet;
/// Join two sorted iterators
pub(crate) fn union_ordered_iter<T: Ord + Debug>(
@ -209,14 +208,14 @@ pub trait State: Default + Clone {
#[doc(hidden)]
fn update<
'a,
T: Traversable<Node = Self, Id = GlobalNodeId>,
T2: Traversable<Node = NodeData, Id = GlobalNodeId>,
T: Traversable<Node = Self, Id = RealNodeId>,
T2: Traversable<Node = NodeData, Id = RealNodeId>,
>(
dirty: &[(GlobalNodeId, NodeMask)],
dirty: &[(RealNodeId, NodeMask)],
state_tree: &'a mut T,
rdom: &'a T2,
ctx: &AnyMap,
) -> FxHashSet<GlobalNodeId>;
) -> FxHashSet<RealNodeId>;
}
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::{
real_dom::{NodeType, RealDom},
state::State,
RealNodeId,
};
use dioxus_core::{DomEdit, ElementId, GlobalNodeId, Mutations};
use dioxus_core::{DomEdit, ElementId, Mutations};
pub enum ElementProduced {
/// 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
Looped(GlobalNodeId),
Looped(RealNodeId),
}
impl ElementProduced {
pub fn id(&self) -> GlobalNodeId {
pub fn id(&self) -> RealNodeId {
match self {
ElementProduced::Progressed(id) => *id,
ElementProduced::Looped(id) => *id,
@ -50,16 +51,13 @@ impl NodePosition {
/// The iterator loops around when it reaches the end or the beginning.
pub struct PersistantElementIter {
// stack of elements and fragments
stack: smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
}
impl Default for PersistantElementIter {
fn default() -> Self {
PersistantElementIter {
stack: smallvec::smallvec![(
GlobalNodeId::VNodeId(dioxus_core::ElementId(0)),
NodePosition::AtNode
)],
stack: smallvec::smallvec![(RealNodeId::ElementId(ElementId(0)), NodePosition::AtNode)],
}
}
}
@ -79,7 +77,7 @@ impl PersistantElementIter {
.filter_map(|e| {
// nodes within templates will never be removed
if let DomEdit::Remove { root } = e {
let id = rdom.decode_id(*root);
let id = rdom.resolve_maybe_id(*root);
Some(id)
} else {
None
@ -102,21 +100,23 @@ impl PersistantElementIter {
for m in &mutations.edits {
match m {
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) {
*child_idx -= 1;
}
}
DomEdit::InsertBefore { root, n } => {
let id = rdom.decode_id(*root);
DomEdit::InsertBefore { root, nodes } => {
let id = rdom.resolve_maybe_id(*root);
let n = nodes.len();
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 } => {
let id = rdom.decode_id(*root);
DomEdit::InsertAfter { root, nodes } => {
let id = rdom.resolve_maybe_id(*root);
let n = nodes.len();
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
pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
if self.stack.is_empty() {
let id = GlobalNodeId::VNodeId(ElementId(0));
let id = RealNodeId::ElementId(ElementId(0));
let new = (id, NodePosition::AtNode);
self.stack.push(new);
ElementProduced::Looped(id)
@ -167,10 +167,10 @@ impl PersistantElementIter {
pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
// recursively add the last child element to the stack
fn push_back<S: State>(
stack: &mut smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
new_node: GlobalNodeId,
stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
new_node: RealNodeId,
rdom: &RealDom<S>,
) -> GlobalNodeId {
) -> RealNodeId {
match &rdom[new_node].node_data.node_type {
NodeType::Element { children, .. } => {
if children.is_empty() {
@ -184,7 +184,7 @@ impl PersistantElementIter {
}
}
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))
} else {
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
}
}

View file

@ -1,5 +1,6 @@
use dioxus_core::{
OwnedAttributeValue, TemplateAttributeValue, TemplateNodeId, TextTemplate, TextTemplateSegment,
OwnedAttributeValue, OwnedPathSeg, OwnedTraverse, TemplateAttributeValue, TemplateNodeId,
TextTemplate, TextTemplateSegment, UpdateOp,
};
use proc_macro2::TokenStream;
use quote::TokenStreamExt;
@ -54,7 +55,7 @@ struct TemplateElementBuilder {
attributes: Vec<TemplateAttributeBuilder>,
children: Vec<TemplateNodeId>,
listeners: Vec<usize>,
parent: Option<TemplateNodeId>,
locally_static: bool,
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
@ -73,7 +74,7 @@ impl TemplateElementBuilder {
attributes,
children,
listeners,
parent,
..
} = self;
let (element_tag, element_ns) =
element_to_static_str(&tag.to_string()).ok_or_else(|| {
@ -94,7 +95,6 @@ impl TemplateElementBuilder {
owned_attributes,
children,
listeners,
parent,
))
}
}
@ -106,19 +106,12 @@ impl ToTokens for TemplateElementBuilder {
attributes,
children,
listeners,
parent,
..
} = self;
let children = children.iter().map(|id| {
let raw = id.0;
quote! {TemplateNodeId(#raw)}
});
let parent = match parent {
Some(id) => {
let raw = id.0;
quote! {Some(TemplateNodeId(#raw))}
}
None => quote! {None},
};
tokens.append_all(quote! {
TemplateElement::new(
dioxus_elements::#tag::TAG_NAME,
@ -126,7 +119,6 @@ impl ToTokens for TemplateElementBuilder {
&[#(#attributes),*],
&[#(#children),*],
&[#(#listeners),*],
#parent,
)
})
}
@ -279,12 +271,18 @@ impl ToTokens for TemplateNodeTypeBuilder {
TemplateNodeType::Element(#el)
}),
TemplateNodeTypeBuilder::Text(txt) => {
let mut length = 0;
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)),
});
tokens.append_all(quote! {
TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*]))
TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*], #length))
});
}
TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! {
@ -296,62 +294,54 @@ impl ToTokens for TemplateNodeTypeBuilder {
struct TemplateNodeBuilder {
id: TemplateNodeId,
depth: usize,
parent: Option<TemplateNodeId>,
node_type: TemplateNodeTypeBuilder,
fully_static: bool,
}
impl TemplateNodeBuilder {
#[cfg(any(feature = "hot-reload", debug_assertions))]
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)?;
Ok(OwnedTemplateNode {
id,
node_type,
locally_static: false,
fully_static: false,
parent,
depth,
})
}
fn is_fully_static(&self, nodes: &Vec<TemplateNodeBuilder>) -> bool {
self.is_locally_static()
&& match &self.node_type {
TemplateNodeTypeBuilder::Element(el) => el
.children
.iter()
.all(|child| nodes[child.0].is_fully_static(nodes)),
TemplateNodeTypeBuilder::Text(_) => true,
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;
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
id,
node_type,
parent,
depth,
..
} = self;
let raw_id = id.0;
let fully_static = self.is_fully_static(nodes);
let locally_static = self.is_locally_static();
let parent = match parent {
Some(id) => {
let id = id.0;
quote! {Some(TemplateNodeId(#id))}
}
None => quote! {None},
};
tokens.append_all(quote! {
TemplateNode {
id: TemplateNodeId(#raw_id),
node_type: #node_type,
locally_static: #locally_static,
fully_static: #fully_static,
parent: #parent,
depth: #depth,
}
})
}
@ -369,8 +359,8 @@ impl TemplateBuilder {
pub fn from_roots(roots: Vec<BodyNode>) -> Option<Self> {
let mut builder = Self::default();
for root in roots {
let id = builder.build_node(root, None);
for (i, root) in roots.into_iter().enumerate() {
let id = builder.build_node(root, None, vec![i], 0);
builder.root_nodes.push(id);
}
@ -391,18 +381,25 @@ impl TemplateBuilder {
fn from_roots_always(roots: Vec<BodyNode>) -> Self {
let mut builder = Self::default();
for root in roots {
let id = builder.build_node(root, None);
for (i, root) in roots.into_iter().enumerate() {
let id = builder.build_node(root, None, vec![i], 0);
builder.root_nodes.push(id);
}
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());
match node {
BodyNode::Element(el) => {
let mut locally_static = true;
let mut attributes = Vec::new();
let mut listeners = Vec::new();
for attr in el.attributes {
@ -417,6 +414,7 @@ impl TemplateBuilder {
),
})
} else {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Ident(name),
@ -436,6 +434,7 @@ impl TemplateBuilder {
),
})
} else {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Str(name),
@ -446,6 +445,7 @@ impl TemplateBuilder {
}
}
ElementAttr::AttrExpression { name, value } => {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Ident(name),
@ -455,6 +455,7 @@ impl TemplateBuilder {
})
}
ElementAttr::CustomAttrExpression { name, value } => {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Str(name),
@ -464,6 +465,7 @@ impl TemplateBuilder {
})
}
ElementAttr::EventTokens { name, tokens } => {
locally_static = false;
listeners.push(self.dynamic_context.add_listener(name, tokens))
}
}
@ -481,16 +483,25 @@ impl TemplateBuilder {
attributes,
children: Vec::new(),
listeners,
parent,
locally_static,
}),
parent,
depth,
fully_static: false,
});
let children: Vec<_> = el
.children
.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();
let children_fully_static = children.iter().all(|c| self.nodes[c.0].fully_static);
let parent = &mut self.nodes[id.0];
parent.fully_static = locally_static && children_fully_static;
if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type {
element.children = children;
}
@ -502,16 +513,25 @@ impl TemplateBuilder {
node_type: TemplateNodeTypeBuilder::DynamicNode(
self.dynamic_context.add_node(BodyNode::Component(comp)),
),
parent,
depth,
fully_static: false,
});
}
BodyNode::Text(txt) => {
let mut segments = Vec::new();
let mut length = 0;
let mut fully_static = true;
for segment in txt.segments {
segments.push(match segment {
Segment::Literal(lit) => TextTemplateSegment::Static(lit),
Segment::Literal(lit) => {
length += lit.len();
TextTemplateSegment::Static(lit)
}
Segment::Formatted(fmted) => {
fully_static = false;
TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted))
}
})
@ -519,7 +539,10 @@ impl TemplateBuilder {
self.nodes.push(TemplateNodeBuilder {
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(
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> {
let mut nodes = Vec::new();
let dynamic_mapping = self.dynamic_mapping(&nodes);
let dynamic_path = self.dynamic_path();
for node in self.nodes {
nodes.push(node.try_into_owned(location)?);
}
@ -616,6 +643,7 @@ impl TemplateBuilder {
nodes,
root_nodes: self.root_nodes,
dynamic_mapping,
dynamic_path,
})
}
@ -684,6 +712,114 @@ impl TemplateBuilder {
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 {
@ -759,10 +895,18 @@ impl ToTokens for TemplateBuilder {
});
let mut nodes_quoted = TokenStream::new();
for n in nodes {
n.to_tokens(&mut nodes_quoted, nodes);
n.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! {
{
const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted];
@ -791,6 +935,7 @@ impl ToTokens for TemplateBuilder {
nodes: __NODES,
root_nodes: __ROOT_NODES,
dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS),
dynamic_path: #dynamic_path,
});
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;
let text = dynamic_context.resolve_text(&txt.segments);
let text = dynamic_context.resolve_text(txt);
write!(f, "{}", text)?
}

View file

@ -1,3 +1,4 @@
use dioxus::core_macro::rsx_without_templates;
use dioxus::prelude::*;
use dioxus_tui::query::Query;
use dioxus_tui::Size;
@ -10,7 +11,8 @@ fn app(cx: Scope) -> Element {
let hue = use_state(&cx, || 0.0);
let brightness = use_state(&cx, || 0.0);
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{
width: "100%",
background_color: "hsl({hue}, 70%, {brightness}%)",

View file

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

View file

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

View file

@ -2,6 +2,7 @@ use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
};
use dioxus_core::*;
use dioxus_native_core::RealNodeId;
use rustc_hash::{FxHashMap, FxHashSet};
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
@ -190,7 +191,8 @@ impl InnerInputState {
self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
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 {
scope_id: None,
priority: EventPriority::Medium,
@ -208,7 +210,7 @@ impl InnerInputState {
bubbles: event_bubbles("focusin"),
});
}
if let Some(id) = old_focus {
if let Some(RealNodeId::ElementId(id)) = old_focus {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
@ -243,13 +245,13 @@ impl InnerInputState {
fn try_create_event(
name: &'static str,
data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut FxHashSet<GlobalNodeId>,
will_bubble: &mut FxHashSet<RealNodeId>,
resolved_events: &mut Vec<UserEvent>,
node: &Node,
dom: &Dom,
) {
// 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) {
let mut parent = node.node_data.parent;
while let Some(parent_id) = parent {
@ -260,7 +262,7 @@ impl InnerInputState {
scope_id: None,
priority: EventPriority::Medium,
name,
element: Some(id),
element: Some(id.as_element_id()),
data,
bubbles: event_bubbles(name),
})
@ -547,7 +549,7 @@ impl InnerInputState {
let currently_contains = layout_contains_point(node_layout, new_pos);
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 {
@ -665,7 +667,7 @@ impl RinkInputHandler {
scope_id: None,
priority: EventPriority::Medium,
name: event,
element: Some(node.node_data.id),
element: Some(node.mounted_id().as_element_id()),
data: data.clone(),
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::real_dom::OwnedAttributeView;
use dioxus_native_core::state::ChildDepState;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::sorted_str_slice;
use taffy::prelude::*;
@ -100,7 +101,7 @@ impl ChildDepState for TaffyLayout {
}
// 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("height", "100%", &mut style);
}

View file

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

View file

@ -7,7 +7,7 @@
//! - tests to ensure dyn_into works for various event types.
//! - Partial delegation?>
use dioxus_core::{DomEdit, SchedulerMsg, UserEvent};
use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
use dioxus_html::event_bubbles;
use dioxus_interpreter_js::Interpreter;
use js_sys::Function;
@ -43,7 +43,7 @@ impl WebsysDom {
break Ok(UserEvent {
name: event_name_from_typ(&typ),
data: virtual_event_from_websys_event(event.clone(), target.clone()),
element: Some(id),
element: Some(ElementId(id)),
scope_id: None,
priority: dioxus_core::EventPriority::Medium,
bubbles: event.bubbles(),
@ -107,18 +107,25 @@ impl WebsysDom {
pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
for edit in edits.drain(..) {
match edit {
DomEdit::PushRoot { root } => self.interpreter.PushRoot(root),
DomEdit::PopRoot {} => self.interpreter.PopRoot(),
DomEdit::AppendChildren { many } => self.interpreter.AppendChildren(many),
DomEdit::ReplaceWith { root, m } => self.interpreter.ReplaceWith(root, m),
DomEdit::InsertAfter { root, n } => self.interpreter.InsertAfter(root, n),
DomEdit::InsertBefore { root, n } => self.interpreter.InsertBefore(root, n),
DomEdit::AppendChildren { root, children } => {
self.interpreter.AppendChildren(root, children);
}
DomEdit::ReplaceWith { root, nodes } => self.interpreter.ReplaceWith(root, nodes),
DomEdit::InsertAfter { root, nodes } => self.interpreter.InsertAfter(root, nodes),
DomEdit::InsertBefore { root, nodes } => self.interpreter.InsertBefore(root, nodes),
DomEdit::Remove { root } => self.interpreter.Remove(root),
DomEdit::CreateElement { tag, root } => self.interpreter.CreateElement(tag, root),
DomEdit::CreateElementNs { tag, root, ns } => {
self.interpreter.CreateElementNs(tag, root, ns)
}
DomEdit::CreateElement {
root,
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::NewEventListener {
event_name, root, ..
@ -157,45 +164,15 @@ impl WebsysDom {
let value = serde_wasm_bindgen::to_value(&value).unwrap();
self.interpreter.SetAttribute(root, field, value, ns)
}
DomEdit::CreateTemplateRef { id, template_id } => {
self.interpreter.CreateTemplateRef(id, template_id)
}
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::CloneNode { id, new_id } => self.interpreter.CloneNode(id, new_id),
DomEdit::CloneNodeChildren { id, new_ids } => {
self.interpreter.CloneNodeChildren(id, new_ids)
}
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 {
let global_id = listener.mounted_node.get().unwrap();
match global_id {
dioxus_core::GlobalNodeId::TemplateId {
template_ref_id,
template_node_id: id,
} => {
self.interpreter.EnterTemplateRef(template_ref_id.into());
let id = listener.mounted_node.get().unwrap();
self.interpreter.NewEventListener(
listener.event,
id.into(),
Some(id.as_u64()),
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() {