wip: making progress on diffing and hydration

This commit is contained in:
Jonathan Kelley committed 2021-07-28 21:46:53 -04:00
1 parent e5c88fe3a4
commit 49856ccd68
34 files changed
+1023 -750

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
pub struct WebConfig {
pub(crate) hydrate: bool,
}
impl Default for WebConfig {
fn default() -> Self {
Self { hydrate: false }
}
}
impl WebConfig {
/// Enable SSR hydration
///
/// This enables Dioxus to pick up work from a pre-renderd HTML file. Hydration will completely skip over any async
/// work and suspended nodes.
///
/// Dioxus will load up all the elements with the `dio_el` data attribute into memory when the page is loaded.
///
pub fn hydrate(mut self, f: bool) -> Self {
self.hydrate = f;
self
}
}
@@ -8,12 +8,19 @@ use fxhash::FxHashMap;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
NodeList,
};
use crate::{nodeslab::NodeSlab, WebConfig};
pub struct WebsysDom {
pub stack: Stack,
nodes: Vec<Node>,
stack: Stack,
/// A map from ElementID (index) to Node
nodes: NodeSlab,
document: Document,
root: Element,
event_receiver: async_channel::Receiver<EventTrigger>,
@@ -33,11 +40,8 @@ pub struct WebsysDom {
last_node_was_text: bool,
}
impl WebsysDom {
pub fn new(root: Element) -> Self {
let document = window()
.expect("must have access to the window")
.document()
.expect("must have access to the Document");
pub fn new(root: Element, cfg: WebConfig) -> Self {
let document = load_document();
let (sender, receiver) = async_channel::unbounded::<EventTrigger>();
@@ -48,14 +52,36 @@ impl WebsysDom {
});
});
let mut nodes = Vec::with_capacity(1000);
let mut nodes = NodeSlab::new(2000);
let mut listeners = FxHashMap::default();
// let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
// re-hydrate the page - only supports one virtualdom per page
if cfg.hydrate {
// Load all the elements into the arena
let node_list: NodeList = document.query_selector_all("dio_el").unwrap();
let len = node_list.length() as usize;
for x in 0..len {
let node: Node = node_list.get(x as u32).unwrap();
let el: &Element = node.dyn_ref::<Element>().unwrap();
let id: String = el.get_attribute("dio_el").unwrap();
let id = id.parse::<usize>().unwrap();
// this autoresizes the vector if needed
nodes[id] = Some(node);
}
// Load all the event listeners into our listener register
}
let mut stack = Stack::with_capacity(10);
let root_node = root.clone().dyn_into::<Node>().unwrap();
stack.push(root_node);
Self {
stack: Stack::with_capacity(10),
stack,
nodes,
listeners: FxHashMap::default(),
listeners,
document,
event_receiver: receiver,
trigger: sender_callback,
@@ -97,19 +123,14 @@ impl WebsysDom {
}
}
fn push(&mut self, root: u64) {
// let key = DefaultKey::from(KeyData::from_ffi(root));
let key = root as usize;
let domnode = self.nodes.get_mut(key);
let domnode = &self.nodes[key];
let real_node: Node = match domnode {
Some(n) => n.clone(),
None => todo!(),
};
// let domnode = domnode.unwrap().as_mut().unwrap();
// .expect(&format!("Failed to pop know root: {:#?}", key))
// .unwrap();
self.stack.push(real_node);
}
// drop the node off the stack
@@ -205,7 +226,7 @@ impl WebsysDom {
let id = id as usize;
self.stack.push(textnode.clone());
*self.nodes.get_mut(id).unwrap() = textnode;
self.nodes[id] = Some(textnode);
}
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
@@ -227,7 +248,7 @@ impl WebsysDom {
let id = id as usize;
self.stack.push(el.clone());
*self.nodes.get_mut(id).unwrap() = el;
self.nodes[id] = Some(el);
// let nid = self.node_counter.?next();
// let nid = self.nodes.insert(el).data().as_ffi();
// log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
@@ -264,6 +285,9 @@ impl WebsysDom {
)
.unwrap();
el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
.unwrap();
// Register the callback to decode
if let Some(entry) = self.listeners.get_mut(event) {
@@ -512,26 +536,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
}
}
VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
// MouseEvent(Box::new(RawMouseEvent {
// alt_key: evt.alt_key(),
// button: evt.button() as i32,
// buttons: evt.buttons() as i32,
// client_x: evt.client_x(),
// client_y: evt.client_y(),
// ctrl_key: evt.ctrl_key(),
// meta_key: evt.meta_key(),
// page_x: evt.page_x(),
// page_y: evt.page_y(),
// screen_x: evt.screen_x(),
// screen_y: evt.screen_y(),
// shift_key: evt.shift_key(),
// get_modifier_state: GetModifierKey(Box::new(|f| {
// // evt.get_modifier_state(f)
// todo!("This is not yet implemented properly, sorry :(");
// })),
// }))
// todo!()
// VirtualEvent::MouseEvent()
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
@@ -645,3 +649,14 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
dioxus_core::events::EventPriority::High,
))
}
pub fn prepare_websys_dom() -> Element {
load_document().get_element_by_id("dioxusroot").unwrap()
}
pub fn load_document() -> Document {
web_sys::window()
.expect("should have access to the Window")
.document()
.expect("should have access to the Document")
}
+54 -33
View File
@@ -2,38 +2,66 @@
//! --------------
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
pub use crate::cfg::WebConfig;
use crate::dom::load_document;
use dioxus::prelude::{Context, Properties, VNode};
use dioxus::virtual_dom::VirtualDom;
pub use dioxus_core as dioxus;
use dioxus_core::error::Result;
use dioxus_core::{events::EventTrigger, prelude::FC};
use futures_util::{pin_mut, Stream, StreamExt};
use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node};
use js_sys::Iterator;
use web_sys::{window, Document, Element, Event, Node, NodeList};
mod cache;
mod new;
mod cfg;
mod dom;
mod nodeslab;
/// Launches the VirtualDOM from the specified component function.
///
/// This method will block the thread with `spawn_local`
///
/// # Example
///
///
///
pub fn launch<F>(root: FC<()>, config: F)
where
F: FnOnce(()),
F: FnOnce(WebConfig) -> WebConfig,
{
wasm_bindgen_futures::spawn_local(run(root))
launch_with_props(root, (), config)
}
/// Launches the VirtualDOM from the specified component function and props.
///
/// This method will block the thread with `spawn_local`
///
/// # Example
///
///
pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
where
T: Properties + 'static,
F: FnOnce(()),
F: FnOnce(WebConfig) -> WebConfig,
{
wasm_bindgen_futures::spawn_local(run_with_props(root, root_props))
let config = config(WebConfig::default());
let fut = run_with_props(root, root_props, config);
wasm_bindgen_futures::spawn_local(async {
match fut.await {
Ok(_) => log::error!("Your app completed running... somehow?"),
Err(e) => log::error!("Your app crashed! {}", e),
}
});
}
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
/// See DioxusErrors for more information on how these errors could occour.
///
/// # Example
///
/// ```ignore
/// fn main() {
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
@@ -42,29 +70,19 @@ where
///
/// Run the app to completion, panicing if any error occurs while rendering.
/// Pairs well with the wasm_bindgen async handler
pub async fn run(root: FC<()>) {
run_with_props(root, ()).await;
}
pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) {
pub async fn run_with_props<T: Properties + 'static>(
root: FC<T>,
root_props: T,
cfg: WebConfig,
) -> Result<()> {
let dom = VirtualDom::new_with_props(root, root_props);
event_loop(dom).await.expect("Event loop failed");
}
pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> {
use wasm_bindgen::JsCast;
let root_el = load_document().get_element_by_id("dioxus_root").unwrap();
let mut websys_dom = dom::WebsysDom::new(root_el, cfg);
let root = prepare_websys_dom();
let root_node = root.clone().dyn_into::<Node>().unwrap();
let mut websys_dom = crate::new::WebsysDom::new(root.clone());
websys_dom.stack.push(root_node.clone());
websys_dom.stack.push(root_node);
let mut edits = Vec::new();
internal_dom.rebuild(&mut websys_dom, &mut edits)?;
websys_dom.process_edits(&mut edits);
// let mut edits = Vec::new();
// internal_dom.rebuild(&mut websys_dom, &mut edits)?;
// websys_dom.process_edits(&mut edits);
log::info!("Going into event loop");
loop {
@@ -105,11 +123,14 @@ pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Res
Ok(())
}
fn prepare_websys_dom() -> Element {
web_sys::window()
.expect("should have access to the Window")
.document()
.expect("should have access to the Document")
.get_element_by_id("dioxusroot")
.unwrap()
fn iter_node_list() {}
// struct NodeListIter {
// node_list: NodeList,
// }
// impl Iterator for NodeListIter {}
struct HydrationNode {
id: usize,
node: Node,
}
+41
View File
@@ -0,0 +1,41 @@
use std::ops::{Index, IndexMut};
use web_sys::Node;
pub struct NodeSlab {
nodes: Vec<Option<Node>>,
}
impl NodeSlab {
pub fn new(capacity: usize) -> NodeSlab {
NodeSlab {
nodes: Vec::with_capacity(capacity),
}
}
fn insert_and_extend(&mut self, node: Node, id: usize) {
if id > self.nodes.len() * 3 {
panic!("Trying to insert an element way too far out of bounds");
}
if id < self.nodes.len() {}
}
}
impl Index<usize> for NodeSlab {
type Output = Option<Node>;
fn index(&self, index: usize) -> &Self::Output {
&self.nodes[index]
}
}
impl IndexMut<usize> for NodeSlab {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
if index >= self.nodes.len() * 3 {
panic!("Trying to mutate an element way too far out of bounds");
}
if index > self.nodes.len() {
self.nodes.resize_with(index, || None);
}
&mut self.nodes[index]
}
}