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:
1 parent
b32fd2d2cd
commit
047ed1e553
80 files changed
+6774
-2552
No files matched your search
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in new issue
Block a user