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

+9 -9
View File
@@ -1,6 +1,6 @@
#![allow(unused, non_upper_case_globals, non_snake_case)]
use dioxus::prelude::*;
use dioxus::{core_macro::rsx_without_templates, prelude::*};
use dioxus_core::{DomEdit, Mutations, SchedulerMsg, ScopeId};
use std::rc::Rc;
use DomEdit::*;
@@ -11,12 +11,12 @@ fn shared_state_test() {
static App: Component = |cx| {
cx.provide_context(Rc::new(MySharedState("world!")));
cx.render(rsx!(Child {}))
cx.render(rsx_without_templates!(Child {}))
};
static Child: Component = |cx| {
let shared = cx.consume_context::<Rc<MySharedState>>()?;
cx.render(rsx!("Hello, {shared.0}"))
cx.render(rsx_without_templates!("Hello, {shared.0}"))
};
let mut dom = VirtualDom::new(App);
@@ -42,13 +42,13 @@ fn swap_test() {
cx.provide_context(Rc::new(MySharedState("world!")));
let child = match *val % 2 {
0 => rsx!(
0 => rsx_without_templates!(
Child1 {
Child1 { }
Child2 { }
}
),
_ => rsx!(
_ => rsx_without_templates!(
Child2 {
Child2 { }
Child2 { }
@@ -56,7 +56,7 @@ fn swap_test() {
),
};
cx.render(rsx!(
cx.render(rsx_without_templates!(
Router {
div { child }
}
@@ -65,14 +65,14 @@ fn swap_test() {
#[inline_props]
fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
cx.render(rsx!(div { children }))
cx.render(rsx_without_templates!(div { children }))
}
#[inline_props]
fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
println!("Child1: {}", shared.0);
cx.render(rsx! {
cx.render(rsx_without_templates! {
div {
"{shared.0}",
children
@@ -84,7 +84,7 @@ fn swap_test() {
fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
println!("Child2: {}", shared.0);
cx.render(rsx! {
cx.render(rsx_without_templates! {
h1 {
"{shared.0}",
children