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
+18
-11
@@ -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;
|
||||
}
|
||||
|
||||
@@ -243,23 +243,24 @@ impl InnerInputState {
|
||||
fn try_create_event(
|
||||
name: &'static str,
|
||||
data: Arc<dyn Any + Send + Sync>,
|
||||
will_bubble: &mut FxHashSet<ElementId>,
|
||||
will_bubble: &mut FxHashSet<GlobalNodeId>,
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
node: &Node,
|
||||
dom: &Dom,
|
||||
) {
|
||||
// only trigger event if the event was not triggered already by a child
|
||||
if will_bubble.insert(node.id) {
|
||||
let mut parent = node.parent;
|
||||
let id = node.node_data.id;
|
||||
if will_bubble.insert(id) {
|
||||
let mut parent = node.node_data.parent;
|
||||
while let Some(parent_id) = parent {
|
||||
will_bubble.insert(parent_id);
|
||||
parent = dom[parent_id].parent;
|
||||
parent = dom[parent_id].node_data.parent;
|
||||
}
|
||||
resolved_events.push(UserEvent {
|
||||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name,
|
||||
element: Some(node.id),
|
||||
element: Some(id),
|
||||
data,
|
||||
bubbles: event_bubbles(name),
|
||||
})
|
||||
@@ -546,7 +547,7 @@ impl InnerInputState {
|
||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
||||
|
||||
if currently_contains && node.state.focus.level.focusable() {
|
||||
focus_id = Some(node.id);
|
||||
focus_id = Some(node.node_data.id);
|
||||
}
|
||||
});
|
||||
if let Some(id) = focus_id {
|
||||
@@ -565,7 +566,7 @@ fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout {
|
||||
let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap();
|
||||
let mut current = node;
|
||||
|
||||
while let Some(parent_id) = current.parent {
|
||||
while let Some(parent_id) = current.node_data.parent {
|
||||
let parent = &dom[parent_id];
|
||||
current = parent;
|
||||
let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
|
||||
@@ -664,7 +665,7 @@ impl RinkInputHandler {
|
||||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name: event,
|
||||
element: Some(node.id),
|
||||
element: Some(node.node_data.id),
|
||||
data: data.clone(),
|
||||
bubbles: event_bubbles(event),
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::rc::Rc;
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::layout_attributes::apply_layout_attributes;
|
||||
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
|
||||
use dioxus_native_core::real_dom::OwnedAttributeView;
|
||||
use dioxus_native_core::state::ChildDepState;
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
use taffy::prelude::*;
|
||||
@@ -84,10 +85,17 @@ impl ChildDepState for TaffyLayout {
|
||||
}
|
||||
} else {
|
||||
// gather up all the styles from the attribute list
|
||||
for Attribute { name, value, .. } in node.attributes() {
|
||||
assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok());
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_layout_attributes(name, text, &mut style);
|
||||
if let Some(attributes) = node.attributes() {
|
||||
for OwnedAttributeView {
|
||||
attribute, value, ..
|
||||
} in attributes
|
||||
{
|
||||
assert!(SORTED_LAYOUT_ATTRS
|
||||
.binary_search(&attribute.name.as_ref())
|
||||
.is_ok());
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_layout_attributes(&&attribute.name, text, &mut style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
||||
let to_update = rdom.apply_mutations(vec![mutations]);
|
||||
let mut any_map = AnyMap::new();
|
||||
any_map.insert(taffy.clone());
|
||||
let _to_rerender = rdom.update_state(&dom, to_update, any_map);
|
||||
let _to_rerender = rdom.update_state(to_update, any_map);
|
||||
}
|
||||
|
||||
render_vdom(
|
||||
@@ -133,8 +133,10 @@ fn render_vdom(
|
||||
terminal.clear().unwrap();
|
||||
}
|
||||
|
||||
let mut to_rerender: fxhash::FxHashSet<ElementId> =
|
||||
vec![ElementId(0)].into_iter().collect();
|
||||
let mut to_rerender: fxhash::FxHashSet<GlobalNodeId> =
|
||||
vec![GlobalNodeId::VNodeId(ElementId(0))]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut updated = true;
|
||||
|
||||
loop {
|
||||
@@ -250,7 +252,7 @@ fn render_vdom(
|
||||
// update the style and layout
|
||||
let mut any_map = AnyMap::new();
|
||||
any_map.insert(taffy.clone());
|
||||
to_rerender = rdom.update_state(vdom, to_update, any_map);
|
||||
to_rerender = rdom.update_state(to_update, any_map);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,11 +61,11 @@ impl NodeDepState<()> for PreventDefault {
|
||||
_sibling: (),
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
let new = match node
|
||||
.attributes()
|
||||
.find(|a| a.name == "dioxus-prevent-default")
|
||||
.and_then(|a| a.value.as_text())
|
||||
{
|
||||
let new = match node.attributes().and_then(|mut attrs| {
|
||||
attrs
|
||||
.find(|a| a.attribute.name == "dioxus-prevent-default")
|
||||
.and_then(|a| a.value.as_text())
|
||||
}) {
|
||||
Some("onfocus") => PreventDefault::Focus,
|
||||
Some("onkeypress") => PreventDefault::KeyPress,
|
||||
Some("onkeyrelease") => PreventDefault::KeyRelease,
|
||||
|
||||
@@ -25,7 +25,7 @@ pub(crate) fn render_vnode(
|
||||
) {
|
||||
use dioxus_native_core::real_dom::NodeType;
|
||||
|
||||
if let NodeType::Placeholder = &node.node_type {
|
||||
if let NodeType::Placeholder = &node.node_data.node_type {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) fn render_vnode(
|
||||
let Point { x, y } = location;
|
||||
let Size { width, height } = size;
|
||||
|
||||
match &node.node_type {
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Text { text } => {
|
||||
#[derive(Default)]
|
||||
struct Label<'a> {
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
- [ ] pub aspect_ratio: Number,
|
||||
*/
|
||||
|
||||
use dioxus_core::Attribute;
|
||||
use dioxus_native_core::{
|
||||
layout_attributes::parse_value,
|
||||
node_ref::{AttributeMask, NodeMask, NodeView},
|
||||
real_dom::OwnedAttributeView,
|
||||
state::ParentDepState,
|
||||
};
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
@@ -79,9 +79,14 @@ impl ParentDepState for StyleModifier {
|
||||
}
|
||||
|
||||
// gather up all the styles from the attribute list
|
||||
for Attribute { name, value, .. } in node.attributes() {
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_style_attributes(name, text, &mut new);
|
||||
if let Some(attrs) = node.attributes() {
|
||||
for OwnedAttributeView {
|
||||
attribute, value, ..
|
||||
} in attrs
|
||||
{
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_style_attributes(&attribute.name, text, &mut new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user