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

+52 -8
View File
@@ -128,6 +128,10 @@ pub enum SchedulerMsg {
/// Mark all components as dirty and update them
DirtyAll,
#[cfg(any(feature = "hot-reload", debug_assertions))]
/// Mark a template as dirty, used for hot reloading
SetTemplate(Box<SetTemplateMsg>),
/// New tasks from components that should be polled when the next poll is ready
NewTask(ScopeId),
}
@@ -226,8 +230,7 @@ impl VirtualDom {
render_fn: root,
}),
None,
ElementId(0),
0,
GlobalNodeId::VNodeId(ElementId(0)),
);
Self {
@@ -398,6 +401,25 @@ impl VirtualDom {
self.dirty_scopes.insert(*id);
}
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
SchedulerMsg::SetTemplate(msg) => {
let SetTemplateMsg(id, tmpl) = *msg;
if self
.scopes
.templates
.borrow_mut()
.insert(
id.clone(),
std::rc::Rc::new(std::cell::RefCell::new(Template::Owned(tmpl))),
)
.is_some()
{
self.scopes.template_resolver.borrow_mut().mark_dirty(&id)
}
// mark any scopes that used the template as dirty
self.process_message(SchedulerMsg::DirtyAll);
}
}
}
@@ -449,6 +471,7 @@ impl VirtualDom {
#[allow(unused)]
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
let mut committed_mutations = vec![];
self.scopes.template_bump.reset();
while !self.dirty_scopes.is_empty() {
let scopes = &self.scopes;
@@ -524,11 +547,13 @@ impl VirtualDom {
/// ```
pub fn rebuild(&mut self) -> Mutations {
let scope_id = ScopeId(0);
let mut diff_state = DiffState::new(&self.scopes);
let mut diff_state = DiffState::new(&self.scopes);
self.scopes.run_scope(scope_id);
diff_state.element_stack.push(ElementId(0));
diff_state
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
diff_state.scope_stack.push(scope_id);
let node = self.scopes.fin_head(scope_id);
@@ -625,7 +650,9 @@ 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(ElementId(0));
machine
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
machine.scope_stack.push(ScopeId(0));
machine.diff_node(old, new);
@@ -648,7 +675,9 @@ 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(ElementId(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);
@@ -677,17 +706,32 @@ impl VirtualDom {
let mut create = DiffState::new(&self.scopes);
create.scope_stack.push(ScopeId(0));
create.element_stack.push(ElementId(0));
create
.element_stack
.push(GlobalNodeId::VNodeId(ElementId(0)));
let created = create.create_node(old);
create.mutations.append_children(created as u32);
let mut edit = DiffState::new(&self.scopes);
edit.scope_stack.push(ScopeId(0));
edit.element_stack.push(ElementId(0));
edit.element_stack.push(GlobalNodeId::VNodeId(ElementId(0)));
edit.diff_node(old, new);
(create.mutations, edit.mutations)
}
/// Runs a function with the template associated with a given id.
pub fn with_template<R>(&self, id: &TemplateId, mut f: impl FnMut(&Template) -> R) -> R {
self.scopes
.templates
.borrow()
.get(id)
.map(|inner| {
let borrow = &*inner;
f(&borrow.borrow())
})
.unwrap()
}
}
/*