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 -40
View File
@@ -1,12 +1,9 @@
use dioxus_core::SchedulerMsg;
use dioxus_core::SetTemplateMsg;
use dioxus_core::VirtualDom;
use dioxus_rsx_interpreter::error::Error;
use dioxus_rsx_interpreter::{ErrorHandler, SetManyRsxMessage, RSX_CONTEXT};
use futures_channel::mpsc::unbounded;
use futures_channel::mpsc::UnboundedSender;
use futures_util::StreamExt;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use web_sys::{console, MessageEvent, WebSocket};
use web_sys::{MessageEvent, WebSocket};
pub(crate) fn init(dom: &VirtualDom) {
let window = web_sys::window().unwrap();
@@ -23,46 +20,18 @@ pub(crate) fn init(dom: &VirtualDom) {
);
let ws = WebSocket::new(&url).unwrap();
let mut channel = dom.get_scheduler_channel();
// change the rsx when new data is received
let cl = Closure::wrap(Box::new(|e: MessageEvent| {
let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
let msgs: SetManyRsxMessage = serde_json::from_str(&format!("{text}")).unwrap();
RSX_CONTEXT.extend(msgs);
let msg: SetTemplateMsg = serde_json::from_str(&format!("{text}")).unwrap();
channel
.start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
.unwrap();
}
}) as Box<dyn FnMut(MessageEvent)>);
ws.set_onmessage(Some(cl.as_ref().unchecked_ref()));
cl.forget();
let (error_channel_sender, mut error_channel_receiver) = unbounded();
struct WebErrorHandler {
sender: UnboundedSender<Error>,
}
impl ErrorHandler for WebErrorHandler {
fn handle_error(&self, err: dioxus_rsx_interpreter::error::Error) {
self.sender.unbounded_send(err).unwrap();
}
}
RSX_CONTEXT.set_error_handler(WebErrorHandler {
sender: error_channel_sender,
});
RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel());
// forward stream to the websocket
dom.base_scope().spawn_forever(async move {
while let Some(err) = error_channel_receiver.next().await {
if ws.ready_state() == WebSocket::OPEN {
ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
.unwrap();
} else {
console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into());
panic!("{}", err);
}
}
});
}