Refactor Mutations and simplify Templates (#578)
* update mutations to be path based rather than renderer integrated
This commit is contained in:
1 parent
020c4bf0f8
commit
3b7b503333
44 files changed
+2643
-3134
No files matched your search
+332
-242
File diff suppressed because it is too large.
Load diff
@@ -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,19 +139,38 @@ 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() {
|
||||
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) => result += s.as_ref(),
|
||||
TextTemplateSegment::Dynamic(idx) => result += self.text_segments[*idx],
|
||||
TextTemplateSegment::Static(s) => {
|
||||
let _ = result.write_str(s.as_ref());
|
||||
}
|
||||
TextTemplateSegment::Dynamic(idx) => {
|
||||
let _ = result.write_str(self.text_segments[*idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Resolve an attribute value
|
||||
|
||||
@@ -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,
|
||||
|
||||
+12
-12
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+102
-213
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
+135
-95
@@ -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,46 +465,40 @@ 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 TemplateElement { listeners, .. } = el;
|
||||
for listener_idx in listeners.as_ref() {
|
||||
let listener = dynamic_context.resolve_listener(*listener_idx);
|
||||
if listener.event == event.name {
|
||||
log::trace!("calling listener {:?}", listener.event);
|
||||
let (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) = ¤t.node_type {
|
||||
let TemplateElement { listeners, .. } = el;
|
||||
for listener_idx in listeners.as_ref() {
|
||||
let listener = dynamic_context.resolve_listener(*listener_idx);
|
||||
if listener.event == event.name {
|
||||
log::trace!("calling listener {:?}", listener.event);
|
||||
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
// todo: arcs are pretty heavy to clone
|
||||
// we really want to convert arc to rc
|
||||
// unfortunately, the SchedulerMsg must be send/sync to be sent across threads
|
||||
// we could convert arc to rc internally or something
|
||||
(cb)(AnyEvent {
|
||||
bubble_state: state.clone(),
|
||||
data: event.data.clone(),
|
||||
});
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
// todo: arcs are pretty heavy to clone
|
||||
// we really want to convert arc to rc
|
||||
// unfortunately, the SchedulerMsg must be send/sync to be sent across threads
|
||||
// we could convert arc to rc internally or something
|
||||
(cb)(AnyEvent {
|
||||
bubble_state: state.clone(),
|
||||
data: event.data.clone(),
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return template_ref.parent.get();
|
||||
}
|
||||
} 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 None;
|
||||
}
|
||||
} else {
|
||||
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,
|
||||
|
||||
+404
-166
@@ -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,22 +669,39 @@ 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>(
|
||||
@@ -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,41 +844,34 @@ 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)
|
||||
}
|
||||
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);
|
||||
.set_text(bump_str.into_bump_str(), Some(real_node_id.as_u64()));
|
||||
}
|
||||
TemplateNodeType::DynamicNode(_) => {}
|
||||
}
|
||||
diff_state.mutations.exit_template_ref();
|
||||
diff_state.element_stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user