Files
dioxus/packages/core/src/template.rs
T
Demonthos 3b7b503333 Refactor Mutations and simplify Templates (#578)
* update mutations to be path based rather than renderer integrated
2022-10-18 14:42:45 -07:00

1288 lines
49 KiB
Rust

//! Templates are used to skip diffing on any static parts of the rsx.
//! TemplateNodes are different from VNodes in that they can contain partial dynamic and static content in the same node.
//! For example:
//! ```
//! rsx! {
//! div {
//! color: "{color}",
//! "Hello, world",
//! "{dynamic_text_1}",
//! "{dynamic_text_2}",
//! dynamic_iterator
//! }
//! }
//! ```
//! The above will turn into a template that contains information on how to build div { "Hello, world" } and then every refrence to the template will hydrate with the value of dynamic_text_1, dynamic_text_2, dynamic_iterator, and the color property.
//! The rsx macro will both generate the template and the `DynamicNodeMapping` struct that contains the information on what parts of the template depend on each value of the dynamic context.
//! In templates with many dynamic parts, this allows the diffing algorithm to skip traversing the template to find what part to hydrate.
//! Each dynamic part will contain a index into the dynamic context to determine what value to use. The indexes are origionally ordered by traversing the tree depth first from the root.
//! The indexes for the above would be as follows:
//! ```
//! rsx! {
//! div {
//! color: "{color}", // attribute index 0
//! "Hello, world",
//! "{dynamic_text_1}", // text index 0
//! "{dynamic_text_2}", // text index 1
//! dynamic_iterator // node index 0
//! }
//! }
//! ```
//! Including these indexes allows hot reloading to move the dynamic parts of the template around.
//! The templates generated by rsx are stored as 'static refrences, but you can change the template at runtime to allow hot reloading.
//! The template could be replaced with a new one at runtime:
//! ```
//! rsx! {
//! div {
//! "Hello, world",
//! dynamic_iterator // node index 0
//! h1 {
//! background_color: "{color}" // attribute index 0
//! "{dynamic_text_2}", // text index 1
//! }
//! h1 {
//! color: "{color}", // attribute index 0
//! "{dynamic_text_1}", // text index 0
//! }
//! }
//! }
//! ```
//! Notice how the indecies are no longer in depth first traversal order, and indecies are no longer unique. Attributes and dynamic parts of the text can be duplicated, but dynamic vnodes and componets cannot.
//! 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) 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.
use once_cell::unsync::OnceCell;
use std::{
cell::{Cell, RefCell},
hash::Hash,
marker::PhantomData,
ptr,
};
use rustc_hash::FxHashMap;
use bumpalo::Bump;
use crate::{
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(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize)
)]
pub struct StaticCodeLocation {
/// the path to the crate that contains the location
pub crate_path: &'static str,
/// the path within the crate to the file that contains the location
pub file_path: &'static str,
/// the line number of the location
pub line: u32,
/// the column number of the location
pub column: u32,
}
/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(any(feature = "hot-reload", debug_assertions))]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct OwnedCodeLocation {
/// the path to the crate that contains the location
pub crate_path: String,
/// the path within the crate to the file that contains the location
pub file_path: String,
/// the line number of the location
pub line: u32,
/// the column number of the location
pub column: u32,
}
/// The location of a charicter. Used to track the location of rsx calls for hot reloading.
#[derive(Clone, Eq, Debug)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize)
)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
serde(untagged)
)]
pub enum CodeLocation {
/// A loctation that is created at compile time.
Static(&'static StaticCodeLocation),
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// A loctation that is created at runtime.
Dynamic(Box<OwnedCodeLocation>),
}
#[cfg(all(feature = "serialize", any(feature = "hot-reload", debug_assertions)))]
impl<'de> serde::Deserialize<'de> for CodeLocation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Self::Dynamic(Box::new(OwnedCodeLocation::deserialize(
deserializer,
)?)))
}
}
impl Hash for CodeLocation {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
CodeLocation::Static(loc) => {
let loc: &'static _ = *loc;
state.write_usize((loc as *const _) as usize);
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => {
let (crate_path, file_path): (&str, &str) = (&loc.crate_path, &loc.file_path);
crate_path.hash(state);
file_path.hash(state);
state.write_u32(loc.line);
state.write_u32(loc.column);
}
}
}
}
impl PartialEq for CodeLocation {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(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))]
(Self::Static(l), Self::Dynamic(r)) => **r == **l,
#[cfg(any(feature = "hot-reload", debug_assertions))]
(Self::Dynamic(l), Self::Static(r)) => **l == **r,
}
}
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
impl PartialEq<StaticCodeLocation> for OwnedCodeLocation {
fn eq(&self, other: &StaticCodeLocation) -> bool {
self.crate_path == other.crate_path
&& self.file_path == other.file_path
&& self.line == other.line
&& self.column == other.column
}
}
impl CodeLocation {
/// Get the line number of the location.
pub fn line(&self) -> u32 {
match self {
CodeLocation::Static(loc) => loc.line,
#[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => loc.line,
}
}
/// Get the column number of the location.
pub fn column(&self) -> u32 {
match self {
CodeLocation::Static(loc) => loc.column,
#[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => loc.column,
}
}
/// Get the path within the crate to the location.
pub fn file_path(&self) -> &str {
match self {
CodeLocation::Static(loc) => loc.file_path,
#[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => loc.file_path.as_str(),
}
}
/// Get the path of the crate to the location.
pub fn crate_path(&self) -> &str {
match self {
CodeLocation::Static(loc) => loc.crate_path,
#[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => loc.crate_path.as_str(),
}
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// Create an owned code location from a code location.
pub fn to_owned(&self) -> OwnedCodeLocation {
match self {
CodeLocation::Static(loc) => OwnedCodeLocation {
crate_path: loc.crate_path.to_owned(),
file_path: loc.file_path.to_owned(),
line: loc.line,
column: loc.column,
},
#[cfg(any(feature = "hot-reload", debug_assertions))]
CodeLocation::Dynamic(loc) => *loc.clone(),
}
}
}
/// get the code location of the code that called this function
#[macro_export]
macro_rules! get_line_num {
() => {{
const LOC: CodeLocation = CodeLocation::Static(&StaticCodeLocation {
crate_path: env!("CARGO_MANIFEST_DIR"),
file_path: file!(),
line: line!(),
column: column!(),
});
LOC
}};
}
/// An Template's unique identifier within the vdom.
///
/// `TemplateId` is a refrence to the location in the code the template was created.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct TemplateId(pub CodeLocation);
/// An Template's unique identifier within the renderer.
///
/// `RendererTemplateId` is a unique id of the template sent to the renderer. It is unique across time.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct RendererTemplateId(pub usize);
impl From<RendererTemplateId> for u64 {
fn from(id: RendererTemplateId) -> u64 {
id.0 as u64
}
}
/// A TemplateNode's unique identifier.
///
/// `TemplateNodeId` is a `usize` that is only unique across the template that contains it, it is not unique across multaple instances of that template.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(transparent))]
pub struct TemplateNodeId(pub usize);
/// A refrence to a template along with any context needed to hydrate it
pub struct VTemplateRef<'a> {
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: '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>,
&'b VTemplateRef<'b>,
&Template,
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>,
{
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, 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();
}
}
/// A template that is created at compile time
#[derive(Debug, PartialEq)]
pub struct StaticTemplate {
/// The nodes in the template
pub nodes: StaticTemplateNodes,
/// The ids of the root nodes in the template
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
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
#[cfg(any(feature = "hot-reload", debug_assertions))]
pub struct OwnedTemplate {
/// The nodes in the template
pub nodes: OwnedTemplateNodes,
/// 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: 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
#[derive(Debug, Clone, PartialEq)]
pub enum Template {
/// A template that is createded at compile time
Static(&'static StaticTemplate),
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// A template that is created at runtime
Owned(OwnedTemplate),
}
impl Template {
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(),
};
if !empty {
let roots = match self {
Template::Static(s) => s.root_nodes,
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => &o.root_nodes,
};
for root in roots {
self.create_node(mutations, bump, *root);
}
}
}
fn create_node<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: TemplateNodeId) {
fn crate_node_inner<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
ctx: (&mut Mutations<'b>, &'b Bump, &Template),
) where
Attributes: AsRef<[TemplateAttribute<V>]>,
V: TemplateValue,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
let (mutations, bump, template) = ctx;
match &node.node_type {
TemplateNodeType::Element(el) => {
let TemplateElement {
tag,
namespace,
attributes,
children,
..
} = el;
mutations.create_element(
tag,
*namespace,
None,
template.count_real_nodes(children.as_ref()) as u32,
);
for attr in attributes.as_ref() {
if let TemplateAttributeValue::Static(val) = &attr.value {
let val: AttributeValue<'b> = val.allocate(bump);
let attribute = Attribute {
attribute: attr.attribute,
is_static: true,
value: val,
};
mutations.set_attribute(bump.alloc(attribute), None);
}
}
for child in children.as_ref() {
template.create_node(mutations, bump, *child);
}
}
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(bump.alloc_str(txt.as_ref()), None);
} else {
mutations.create_text_node("", None);
}
}
TemplateNodeType::DynamicNode(_) => {}
}
}
self.with_node(
id,
crate_node_inner,
crate_node_inner,
(mutations, bump, self),
);
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
pub(crate) fn with_node<F1, F2, Ctx, R>(
&self,
id: TemplateNodeId,
mut f1: F1,
mut f2: F2,
ctx: Ctx,
) -> R
where
F1: FnMut(&StaticTemplateNode, Ctx) -> R,
F2: FnMut(&OwnedTemplateNode, Ctx) -> R,
{
match self {
Template::Static(s) => f1(&s.nodes[id.0], ctx),
Template::Owned(o) => f2(&o.nodes[id.0], ctx),
}
}
#[cfg(not(any(feature = "hot-reload", debug_assertions)))]
pub(crate) fn with_node<F1, F2, Ctx, R>(
&self,
id: TemplateNodeId,
mut f1: F1,
_f2: F2,
ctx: Ctx,
) -> R
where
F1: FnMut(&StaticTemplateNode, Ctx) -> R,
F2: FnMut(&StaticTemplateNode, Ctx) -> R,
{
match self {
Template::Static(s) => f1(&s.nodes[id.0], ctx),
}
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
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) -> R,
F2: FnMut(&'a Vec<OwnedTemplateNode>, Ctx) -> R,
{
match self {
Template::Static(s) => f1(&s.nodes, ctx),
Template::Owned(o) => f2(&o.nodes, ctx),
}
}
#[cfg(not(any(feature = "hot-reload", debug_assertions)))]
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) -> R,
F2: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R,
{
match self {
Template::Static(s) => f1(&s.nodes, ctx),
}
}
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,
) -> Box<dyn Iterator<Item = (TemplateNodeId, usize)> + 'a> {
match self {
Template::Static(s) => Box::new(
s.dynamic_mapping
.volatile_attributes
.as_ref()
.iter()
.copied(),
),
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => Box::new(o.dynamic_mapping.volatile_attributes.iter().copied()),
}
}
pub(crate) fn get_dynamic_nodes_for_text_index(&self, idx: usize) -> &[TemplateNodeId] {
match self {
Template::Static(s) => s.dynamic_mapping.text[idx],
#[cfg(any(feature = "hot-reload", debug_assertions))]
Template::Owned(o) => o.dynamic_mapping.text[idx].as_ref(),
}
}
pub(crate) fn get_dynamic_nodes_for_attribute_index(
&self,
idx: usize,
) -> &[(TemplateNodeId, usize)] {
match self {
Template::Static(s) => s.dynamic_mapping.attributes[idx],
#[cfg(any(feature = "hot-reload", debug_assertions))]
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
pub type StaticTemplateNodes = &'static [StaticTemplateNode];
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// A vec of heep allocated Template nodes
pub type OwnedTemplateNodes = Vec<OwnedTemplateNode>;
/// A stack allocated Template node
pub type StaticTemplateNode = TemplateNode<
&'static [TemplateAttribute<StaticAttributeValue>],
StaticAttributeValue,
&'static [TemplateNodeId],
&'static [usize],
&'static [TextTemplateSegment<&'static str>],
&'static str,
>;
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// A heap allocated Template node
pub type OwnedTemplateNode = TemplateNode<
Vec<TemplateAttribute<OwnedAttributeValue>>,
OwnedAttributeValue,
Vec<TemplateNodeId>,
Vec<usize>,
Vec<TextTemplateSegment<String>>,
String,
>;
/// A stack allocated list of root Template nodes
pub type StaticRootNodes = &'static [TemplateNodeId];
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// A heap allocated list of root Template nodes
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, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>
where
Attributes: AsRef<[TemplateAttribute<V>]>,
V: TemplateValue,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
/// 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,
/// 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>
TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>
where
Attributes: AsRef<[TemplateAttribute<V>]>,
V: TemplateValue,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
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 {
attributes,
listeners,
..
} = el;
for attr in attributes.as_ref() {
if let TemplateAttributeValue::Dynamic(idx) = attr.value {
let attribute = Attribute {
attribute: attr.attribute,
value: template_ref
.dynamic_context
.resolve_attribute(idx)
.to_owned(),
is_static: false,
};
let scope_bump = diff_state.current_scope_bump();
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);
listener.mounted_node.set(Some(real_node_id));
diff_state
.mutations
.new_event_listener(listener, diff_state.current_scope());
}
}
TemplateNodeType::Text(text) => {
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(bump_str.into_bump_str(), Some(real_node_id.as_u64()));
}
TemplateNodeType::DynamicNode(_) => {}
}
}
}
/// A template for an attribute
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct TemplateAttribute<V: TemplateValue> {
/// The discription of the attribute
pub attribute: AttributeDiscription,
/// The value of the attribute
pub value: TemplateAttributeValue<V>,
}
/// A template attribute value that is either dynamic or static
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub enum TemplateAttributeValue<V: TemplateValue> {
/// A static attribute
Static(V),
/// A dynamic attribute
Dynamic(usize),
}
/// The value for an attribute in a template
pub trait TemplateValue {
/// Allocates the attribute in a bump allocator
fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b>;
}
impl TemplateValue for StaticAttributeValue {
fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b> {
match self.clone() {
StaticAttributeValue::Text(txt) => AttributeValue::Text(bump.alloc_str(txt)),
StaticAttributeValue::Bytes(bytes) => {
AttributeValue::Bytes(bump.alloc_slice_copy(bytes))
}
StaticAttributeValue::Float32(f) => AttributeValue::Float32(f),
StaticAttributeValue::Float64(f) => AttributeValue::Float64(f),
StaticAttributeValue::Int32(i) => AttributeValue::Int32(i),
StaticAttributeValue::Int64(i) => AttributeValue::Int64(i),
StaticAttributeValue::Uint32(u) => AttributeValue::Uint32(u),
StaticAttributeValue::Uint64(u) => AttributeValue::Uint64(u),
StaticAttributeValue::Bool(b) => AttributeValue::Bool(b),
StaticAttributeValue::Vec3Float(f1, f2, f3) => AttributeValue::Vec3Float(f1, f2, f3),
StaticAttributeValue::Vec3Int(i1, i2, i3) => AttributeValue::Vec3Int(i1, i2, i3),
StaticAttributeValue::Vec3Uint(u1, u2, u3) => AttributeValue::Vec3Uint(u1, u2, u3),
StaticAttributeValue::Vec4Float(f1, f2, f3, f4) => {
AttributeValue::Vec4Float(f1, f2, f3, f4)
}
StaticAttributeValue::Vec4Int(i1, i2, i3, i4) => {
AttributeValue::Vec4Int(i1, i2, i3, i4)
}
StaticAttributeValue::Vec4Uint(u1, u2, u3, u4) => {
AttributeValue::Vec4Uint(u1, u2, u3, u4)
}
}
}
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
impl TemplateValue for OwnedAttributeValue {
fn allocate<'b>(&self, bump: &'b Bump) -> AttributeValue<'b> {
match self.clone() {
OwnedAttributeValue::Text(txt) => AttributeValue::Text(bump.alloc(txt)),
OwnedAttributeValue::Bytes(bytes) => AttributeValue::Bytes(bump.alloc(bytes)),
OwnedAttributeValue::Float32(f) => AttributeValue::Float32(f),
OwnedAttributeValue::Float64(f) => AttributeValue::Float64(f),
OwnedAttributeValue::Int32(i) => AttributeValue::Int32(i),
OwnedAttributeValue::Int64(i) => AttributeValue::Int64(i),
OwnedAttributeValue::Uint32(u) => AttributeValue::Uint32(u),
OwnedAttributeValue::Uint64(u) => AttributeValue::Uint64(u),
OwnedAttributeValue::Bool(b) => AttributeValue::Bool(b),
OwnedAttributeValue::Vec3Float(f1, f2, f3) => AttributeValue::Vec3Float(f1, f2, f3),
OwnedAttributeValue::Vec3Int(i1, i2, i3) => AttributeValue::Vec3Int(i1, i2, i3),
OwnedAttributeValue::Vec3Uint(u1, u2, u3) => AttributeValue::Vec3Uint(u1, u2, u3),
OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => {
AttributeValue::Vec4Float(f1, f2, f3, f4)
}
OwnedAttributeValue::Vec4Int(i1, i2, i3, i4) => AttributeValue::Vec4Int(i1, i2, i3, i4),
OwnedAttributeValue::Vec4Uint(u1, u2, u3, u4) => {
AttributeValue::Vec4Uint(u1, u2, u3, u4)
}
OwnedAttributeValue::Any(owned) => {
AttributeValue::Any(crate::ArbitraryAttributeValue {
value: bump.alloc(owned.value),
cmp: owned.cmp,
})
}
}
}
}
/// The kind of node the template is.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub enum 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>,
{
/// A element node (e.g. div{}).
Element(TemplateElement<Attributes, V, Children, Listeners>),
/// A text node (e.g. "Hello World").
Text(TextTemplate<TextSegments, Text>),
/// A dynamic node (e.g. (0..10).map(|i| cx.render(rsx!{div{}})))
/// The index in the dynamic node array this node should be replaced with
DynamicNode(usize),
}
type StaticStr = &'static str;
/// A element template
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct TemplateElement<Attributes, V, Children, Listeners>
where
Attributes: AsRef<[TemplateAttribute<V>]>,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
V: TemplateValue,
{
/// The tag name of the element
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
serde(deserialize_with = "crate::util::deserialize_static_leaky")
)]
pub tag: StaticStr,
/// The namespace of the element
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
serde(deserialize_with = "crate::util::deserialize_static_leaky_ns")
)]
pub namespace: Option<StaticStr>,
/// The attributes that modify the element
pub attributes: Attributes,
/// The ids of the children of the element
pub children: Children,
/// The ids of the listeners of the element
pub listeners: Listeners,
value: PhantomData<V>,
}
impl<Attributes, V, Children, Listeners> TemplateElement<Attributes, V, Children, Listeners>
where
Attributes: AsRef<[TemplateAttribute<V>]>,
Children: AsRef<[TemplateNodeId]>,
Listeners: AsRef<[usize]>,
V: TemplateValue,
{
/// create a new element template
pub const fn new(
tag: &'static str,
namespace: Option<&'static str>,
attributes: Attributes,
children: Children,
listeners: Listeners,
) -> Self {
TemplateElement {
tag,
namespace,
attributes,
children,
listeners,
value: PhantomData,
}
}
}
/// A template for some text that may contain dynamic segments for example "Hello {name}" contains the static segment "Hello " and the dynamic segment "{name}".
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct TextTemplate<Segments, Text>
where
Segments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
/// The segments of the template.
pub segments: Segments,
/// The minimum size of the output text.
pub min_size: usize,
text: PhantomData<Text>,
}
impl<Segments, Text> TextTemplate<Segments, Text>
where
Segments: AsRef<[TextTemplateSegment<Text>]>,
Text: AsRef<str>,
{
/// create a new template from the segments it is composed of.
pub const fn new(segments: Segments, min_size: usize) -> Self {
TextTemplate {
segments,
min_size,
text: PhantomData,
}
}
}
/// A segment of a text template that may be dynamic or static.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
derive(serde::Serialize, serde::Deserialize)
)]
pub enum TextTemplateSegment<Text>
where
Text: AsRef<str>,
{
/// A constant text segment
Static(Text),
/// A dynamic text segment
Dynamic(usize),
}
/// A template value that is created at compile time that is sync.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub enum StaticAttributeValue {
Text(&'static str),
Float32(f32),
Float64(f64),
Int32(i32),
Int64(i64),
Uint32(u32),
Uint64(u64),
Bool(bool),
Vec3Float(f32, f32, f32),
Vec3Int(i32, i32, i32),
Vec3Uint(u32, u32, u32),
Vec4Float(f32, f32, f32, f32),
Vec4Int(i32, i32, i32, i32),
Vec4Uint(u32, u32, u32, u32),
Bytes(&'static [u8]),
}
#[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, (ElementId, bool)>,
}
impl TemplateResolver {
#[cfg(any(feature = "hot-reload", debug_assertions))]
pub fn mark_dirty(&mut self, id: &TemplateId) {
if let Some((_, dirty)) = self.template_id_mapping.get_mut(id) {
println!("marking dirty {:?}", id);
*dirty = true;
} else {
println!("failed {:?}", id);
}
}
pub fn is_dirty(&self, id: &TemplateId) -> bool {
matches!(self.template_id_mapping.get(id), Some((_, true)))
}
// returns (id, if the id was created)
pub fn get_or_create_client_id(
&mut self,
template_id: &TemplateId,
scopes: &ScopeArena,
) -> (ElementId, bool) {
if let Some(id) = self.template_id_mapping.get(template_id) {
*id
} else {
let renderer_id = scopes.reserve_phantom_node();
self.template_id_mapping
.insert(template_id.clone(), (renderer_id, false));
(renderer_id, true)
}
}
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// A message telling the virtual dom to set a template
#[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),
}