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

+18 -11
View File
@@ -1,6 +1,6 @@
use crate::{node::PreventDefault, Dom};
use dioxus_core::ElementId;
use dioxus_core::GlobalNodeId;
use dioxus_native_core::utils::{ElementProduced, PersistantElementIter};
use dioxus_native_core_macro::sorted_str_slice;
@@ -69,7 +69,10 @@ impl NodeDepState<()> for Focus {
fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool {
let new = Focus {
level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
level: if let Some(a) = node
.attributes()
.and_then(|mut a| a.find(|a| a.attribute.name == "tabindex"))
{
if let Some(index) = a
.value
.as_int32()
@@ -87,8 +90,12 @@ impl NodeDepState<()> for Focus {
}
} else if node
.listeners()
.iter()
.any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok())
.map(|mut listeners| {
listeners
.any(|l| FOCUS_EVENTS.binary_search(&l).is_ok())
.then(|| ())
})
.is_some()
{
FocusLevel::Focusable
} else {
@@ -110,7 +117,7 @@ const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]);
#[derive(Default)]
pub(crate) struct FocusState {
pub(crate) focus_iter: PersistantElementIter,
pub(crate) last_focused_id: Option<ElementId>,
pub(crate) last_focused_id: Option<GlobalNodeId>,
pub(crate) focus_level: FocusLevel,
pub(crate) dirty: bool,
}
@@ -224,9 +231,9 @@ impl FocusState {
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
fn remove_children(
to_prune: &mut [&mut Option<ElementId>],
to_prune: &mut [&mut Option<GlobalNodeId>],
rdom: &Dom,
removed: ElementId,
removed: GlobalNodeId,
) {
for opt in to_prune.iter_mut() {
if let Some(id) = opt {
@@ -235,7 +242,7 @@ impl FocusState {
}
}
}
if let NodeType::Element { children, .. } = &rdom[removed].node_type {
if let NodeType::Element { children, .. } = &rdom[removed].node_data.node_type {
for child in children {
remove_children(to_prune, rdom, *child);
}
@@ -249,19 +256,19 @@ impl FocusState {
dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
ElementId(*root as usize),
rdom.decode_id(*root),
),
dioxus_core::DomEdit::Remove { root } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
ElementId(*root as usize),
rdom.decode_id(*root),
),
_ => (),
}
}
}
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: ElementId) {
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: GlobalNodeId) {
if let Some(old) = self.last_focused_id.replace(id) {
rdom[old].state.focused = false;
}