Subtree memorization / reactive templates (#488)

This commit adds subtree memoization to Dioxus.

Subtree memoization is basically a compile-time step that drastically 
reduces the amount of work the diffing engine needs to do at runtime by
extracting non-changing nodes out into a static "template." Templates 
are then understood by the various renderers in the ecosystem as a 
faster way of rendering the same items. 

For example, in the web, templates are simply a set of DOM Nodes created 
once and then cloned later. This is the same pattern frameworks like Lithtml
and SolidJS use to achieve near-perfect performance. 

Subtree memoization adds an additional level of complexity to Dioxus. The RSX
macro needs to be much smarter to identify changing/nonchanging nodes and
generate a mapping between the Template and its runtime counterparts.

This commit represents a working starter point for this work, adding support 
for templates for the web, desktop, liveview, ssr, and native-core renderers.
In the future we will try to shrink code generation, generally improve 
performance, and simplify our implementation.
This commit is contained in:
Demonthos authored and GitHub committed 2022-09-30 12:03:06 -07:00
1 parent b32fd2d2cd
commit 047ed1e553
80 files changed
+6774 -2552

No files matched your search

+30 -34
View File
@@ -2,16 +2,16 @@ use crate::{
real_dom::{NodeType, RealDom},
state::State,
};
use dioxus_core::{DomEdit, ElementId, Mutations};
use dioxus_core::{DomEdit, ElementId, GlobalNodeId, Mutations};
pub enum ElementProduced {
/// The iterator produced an element by progressing to the next node in a depth first order.
Progressed(ElementId),
Progressed(GlobalNodeId),
/// The iterator reached the end of the tree and looped back to the root
Looped(ElementId),
Looped(GlobalNodeId),
}
impl ElementProduced {
pub fn id(&self) -> ElementId {
pub fn id(&self) -> GlobalNodeId {
match self {
ElementProduced::Progressed(id) => *id,
ElementProduced::Looped(id) => *id,
@@ -50,13 +50,16 @@ impl NodePosition {
/// The iterator loops around when it reaches the end or the beginning.
pub struct PersistantElementIter {
// stack of elements and fragments
stack: smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
stack: smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
}
impl Default for PersistantElementIter {
fn default() -> Self {
PersistantElementIter {
stack: smallvec::smallvec![(ElementId(0), NodePosition::AtNode)],
stack: smallvec::smallvec![(
GlobalNodeId::VNodeId(dioxus_core::ElementId(0)),
NodePosition::AtNode
)],
}
}
}
@@ -74,8 +77,10 @@ impl PersistantElementIter {
.edits
.iter()
.filter_map(|e| {
// nodes within templates will never be removed
if let DomEdit::Remove { root } = e {
Some(*root)
let id = rdom.decode_id(*root);
Some(id)
} else {
None
}
@@ -85,7 +90,7 @@ impl PersistantElementIter {
if let Some(r) = self
.stack
.iter()
.position(|(el_id, _)| ids_removed.iter().any(|id| el_id.as_u64() == *id))
.position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id))
{
self.stack.truncate(r);
changed = true;
@@ -93,33 +98,24 @@ impl PersistantElementIter {
// if a child is removed or inserted before or at the current element, update the child index
for (el_id, child_idx) in self.stack.iter_mut() {
if let NodePosition::InChild(child_idx) = child_idx {
if let NodeType::Element { children, .. } = &rdom[*el_id].node_type {
if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type {
for m in &mutations.edits {
match m {
DomEdit::Remove { root } => {
if children
.iter()
.take(*child_idx + 1)
.any(|c| c.as_u64() == *root)
{
let id = rdom.decode_id(*root);
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx -= 1;
}
}
DomEdit::InsertBefore { root, n } => {
if children
.iter()
.take(*child_idx + 1)
.any(|c| c.as_u64() == *root)
{
let id = rdom.decode_id(*root);
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx += *n as usize;
}
}
DomEdit::InsertAfter { root, n } => {
if children
.iter()
.take(*child_idx)
.any(|c| c.as_u64() == *root)
{
let id = rdom.decode_id(*root);
if children.iter().take(*child_idx).any(|c| *c == id) {
*child_idx += *n as usize;
}
}
@@ -135,14 +131,14 @@ impl PersistantElementIter {
/// get the next element
pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
if self.stack.is_empty() {
let id = ElementId(0);
let id = GlobalNodeId::VNodeId(ElementId(0));
let new = (id, NodePosition::AtNode);
self.stack.push(new);
ElementProduced::Looped(id)
} else {
let (last, o_child_idx) = self.stack.last_mut().unwrap();
let node = &rdom[*last];
match &node.node_type {
match &node.node_data.node_type {
NodeType::Element { children, .. } => {
*o_child_idx = o_child_idx.map(|i| i + 1);
// if we have children, go to the next child
@@ -152,7 +148,7 @@ impl PersistantElementIter {
self.next(rdom)
} else {
let id = children[child_idx];
if let NodeType::Element { .. } = &rdom[id].node_type {
if let NodeType::Element { .. } = &rdom[id].node_data.node_type {
self.stack.push((id, NodePosition::AtNode));
}
ElementProduced::Progressed(id)
@@ -171,11 +167,11 @@ impl PersistantElementIter {
pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
// recursively add the last child element to the stack
fn push_back<S: State>(
stack: &mut smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
new_node: ElementId,
stack: &mut smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
new_node: GlobalNodeId,
rdom: &RealDom<S>,
) -> ElementId {
match &rdom[new_node].node_type {
) -> GlobalNodeId {
match &rdom[new_node].node_data.node_type {
NodeType::Element { children, .. } => {
if children.is_empty() {
new_node
@@ -188,12 +184,12 @@ impl PersistantElementIter {
}
}
if self.stack.is_empty() {
let new_node = ElementId(0);
let new_node = GlobalNodeId::VNodeId(ElementId(0));
ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
} else {
let (last, o_child_idx) = self.stack.last_mut().unwrap();
let node = &rdom[*last];
match &node.node_type {
match &node.node_data.node_type {
NodeType::Element { children, .. } => {
// if we have children, go to the next child
if let NodePosition::InChild(0) = o_child_idx {
@@ -227,7 +223,7 @@ impl PersistantElementIter {
}
}
fn pop(&mut self) -> ElementId {
fn pop(&mut self) -> GlobalNodeId {
self.stack.pop().unwrap().0
}
}