wip: enable more diffing

This commit is contained in:
Jonathan Kelley committed 2021-07-05 01:11:49 -04:00
1 parent b5e5ef171a
commit e8f29a8f8a
26 files changed
+1326 -367

No files matched your search

+4 -1
View File
@@ -10,7 +10,7 @@ license = "MIT/Apache-2.0"
[dependencies]
dioxus-core = { path="../core", version="0.1.2" }
js-sys = "0.3"
wasm-bindgen = "0.2.71"
wasm-bindgen = { version="0.2.71", features=["enable-interning"] }
lazy_static = "1.4.0"
wasm-bindgen-futures = "0.4.20"
wasm-logger = "0.2.0"
@@ -31,6 +31,7 @@ slotmap = "1.0.3"
version = "0.3.50"
features = [
"Comment",
"Attr",
"Document",
"Element",
"HtmlElement",
@@ -47,6 +48,7 @@ features = [
"MouseEvent",
"InputEvent",
"ClipboardEvent",
"NamedNodeMap",
"KeyboardEvent",
"TouchEvent",
"WheelEvent",
@@ -70,4 +72,5 @@ crate-type = ["cdylib", "rlib"]
[dev-dependencies]
im-rc = "15.0.0"
rand = { version="0.8.4", features=["small_rng"] }
separator = "0.4.1"
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
+6
View File
@@ -3,3 +3,9 @@
Build interactive user experiences directly in the web browser!
Dioxus-web provides a `WebsysRenderer` for the Dioxus Virtual Dom that handles events, progresses components, and updates the actual DOM using web-sys methods.
## Web-specific Optimizations
- Uses string interning of all common node types
- Optimistically interns short strings
- Builds trees completely before mounting them
+30 -43
View File
@@ -1,5 +1,6 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus::events::on::MouseEvent;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_web::*;
@@ -14,56 +15,42 @@ fn main() {
}
static App: FC<()> = |cx| {
let (state, set_state) = use_state(&cx, || 0);
cx.render(rsx! {
div {
h1 {"hello"}
C1 {}
C2 {}
section { class: "py-12 px-4 text-center"
div { class: "w-full max-w-2xl mx-auto"
span { class: "text-sm font-semibold"
"count: {state}"
}
div {
C1 {
onclick: move |_| set_state(state + 1)
"incr"
}
C1 {
onclick: move |_| set_state(state - 1)
"decr"
}
}
}
}
}
})
};
static C1: FC<()> = |cx| {
#[derive(Props)]
struct IncrementerProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn C1<'a, 'b>(cx: Context<'a, IncrementerProps<'b>>) -> VNode<'a> {
cx.render(rsx! {
button {
"numba 1"
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: {cx.onclick}
"becr"
{cx.children()}
}
})
};
static C2: FC<()> = |cx| {
cx.render(rsx! {
button {
"numba 2"
}
})
};
static DocExamples: FC<()> = |cx| {
//
let is_ready = false;
let items = (0..10).map(|i| rsx! { li {"{i}"} });
let _ = rsx! {
ul {
{items}
}
};
// rsx! {
// div {}
// h1 {}
// {""}
// "asbasd"
// dioxus::Fragment {
// //
// }
// }
cx.render(rsx! {
div {
{ is_ready.then(|| rsx!{ h1 {"We are ready!"} }) }
}
})
};
}
+181
View File
@@ -0,0 +1,181 @@
//! Example: Calculator
//! -------------------------
// use dioxus::events::on::*;
// use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_web::WebsysRenderer;
const STYLE: &str = include_str!("../../../examples/assets/calculator.css");
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
use dioxus::events::on::*;
use dioxus::prelude::*;
enum Operator {
Add,
Sub,
Mul,
Div,
}
static App: FC<()> = |cx| {
let (cur_val, set_cur_val) = use_state(&cx, || 0.0_f64);
let (operator, set_operator) = use_state(&cx, || None as Option<Operator>);
let (display_value, set_display_value) = use_state(&cx, || "".to_string());
let clear_display = display_value.eq("0");
let clear_text = if clear_display { "C" } else { "AC" };
let input_digit =
move |num: u8| {
log::warn!("Inputting digit {:#?}", num);
let mut new = display_value.clone();
new.push_str(num.to_string().as_str());
set_display_value(new);
};
let input_dot = move || {
let mut new = display_value.clone();
new.push_str(".");
set_display_value(new);
};
let perform_operation = move || {
if let Some(op) = operator.as_ref() {
let rhs = display_value.parse::<f64>().unwrap();
let new_val = match op {
Operator::Add => *cur_val + rhs,
Operator::Sub => *cur_val - rhs,
Operator::Mul => *cur_val * rhs,
Operator::Div => *cur_val / rhs,
};
set_cur_val(new_val);
set_display_value(new_val.to_string());
set_operator(None);
}
};
let toggle_sign = move |_| {
if display_value.starts_with("-") {
set_display_value(display_value.trim_start_matches("-").to_string())
} else {
set_display_value(format!("-{}", *display_value))
}
};
let toggle_percent = move |_| todo!();
let clear_key = move |_| {
set_display_value("0".to_string());
if !clear_display {
set_operator(None);
set_cur_val(0.0);
}
};
let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() {
KeyCode::Backspace => {
//
let mut new = display_value.clone();
if !new.as_str().eq("0") {
new.pop();
}
set_display_value(new);
},
KeyCode::_0 => input_digit(0),
KeyCode::_1 => input_digit(1),
KeyCode::_2 => input_digit(2),
KeyCode::_3 => input_digit(3),
KeyCode::_4 => input_digit(4),
KeyCode::_5 => input_digit(5),
KeyCode::_6 => input_digit(6),
KeyCode::_7 => input_digit(7),
KeyCode::_8 => input_digit(8),
KeyCode::_9 => input_digit(9),
KeyCode::Add => set_operator(Some(Operator::Add)),
KeyCode::Subtract => set_operator(Some(Operator::Sub)),
KeyCode::Divide => set_operator(Some(Operator::Div)),
KeyCode::Multiply => set_operator(Some(Operator::Mul)),
_ => {}
};
cx.render(rsx! {
div { class: "app"
onkeydown: {keydownhandler}
style {
"{STYLE}"
}
div { class: "calculator",
div { class: "input-keys"
CalculatorDisplay { val: *cur_val }
div { class: "function-keys"
CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
}
div { class: "digit-keys"
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
{(1..9).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
})}
}
div { class: "operator-keys"
CalculatorKey { name: "key-divide", onclick: move |_| set_operator(Some(Operator::Div)) "÷" }
CalculatorKey { name: "key-multiply", onclick: move |_| set_operator(Some(Operator::Mul)) "×" }
CalculatorKey { name: "key-subtract", onclick: move |_| set_operator(Some(Operator::Sub)) "−" }
CalculatorKey { name: "key-add", onclick: move |_| set_operator(Some(Operator::Add)) "+" }
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
}
}
}
}
})
};
#[derive(Props)]
struct CalculatorKeyProps<'a> {
/// Name!
name: &'static str,
/// Click!
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"
onclick: {cx.onclick}
{cx.children()}
}
})
}
#[derive(Props, PartialEq)]
struct CalculatorDisplayProps {
val: f64,
}
fn CalculatorDisplay(cx: Context<CalculatorDisplayProps>) -> VNode {
use separator::Separatable;
// Todo, add float support to the num-format crate
let formatted = cx.val.separated_string();
// TODO: make it autoscaling with css
cx.render(rsx! {
div { class: "calculator-display"
"{formatted}"
}
})
}
+6 -4
View File
@@ -3,15 +3,17 @@ use dioxus_core::prelude::*;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
// console_error_panic_hook::set_once();
// log::info!("hello world");
dioxus_web::intern_cache();
log::info!("hello world");
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
static Example: FC<()> = |cx| {
let nodes = (0..15).map(|f| rsx! (li { key: "{f}", "{f}"}));
let nodes = (0..500).map(|f| rsx! (li {"div"}));
cx.render(rsx! {
div {
span {
+294 -44
View File
@@ -77,49 +77,6 @@ impl WebsysRenderer {
.progress_with_event(&mut websys_dom, trigger)?;
}
// let edits = self.internal_dom.rebuild()?;
// log::debug!("Received edits: {:#?}", edits);
// edits.iter().for_each(|edit| {
// log::debug!("patching with {:?}", edit);
// patch_machine.handle_edit(edit);
// });
// patch_machine.reset();
// let root_node = body_element.first_child().unwrap();
// patch_machine.stack.push(root_node.clone());
// log::debug!("patch stack size {:?}", patch_machine.stack);
// Event loop waits for the receiver to finish up
// TODO! Connect the sender to the virtual dom's suspense system
// Suspense is basically an external event that can force renders to specific nodes
// while let Ok(event) = receiver.recv().await {
// log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
// log::debug!("patch stack size before {:#?}", patch_machine.stack);
// patch_machine.reset();
// patch_machine.stack.push(root_node.clone());
// self.internal_dom
// .progress_with_event(&mut websys_dom, event)?;
// let edits = self.internal_dom.progress_with_event(event)?;
// log::debug!("Received edits: {:#?}", edits);
// for edit in &edits {
// // log::debug!("edit stream {:?}", edit);
// // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
// patch_machine.handle_edit(edit);
// }
// log::debug!("patch stack size after {:#?}", patch_machine.stack);
// patch_machine.reset();
// our root node reference gets invalidated
// not sure why
// for now, just select the first child again.
// eventually, we'll just make our own root element instead of using body
// or just use body directly IDEK
// let root_node = body_element.first_child().unwrap();
// patch_machine.stack.push(root_node.clone());
// }
Ok(()) // should actually never return from this, should be an error, rustc just cant see it
}
}
@@ -135,7 +92,7 @@ fn prepare_websys_dom() -> Element {
// Build a dummy div
let container: &Element = body.as_ref();
container.set_inner_html("");
// container.set_inner_html("");
container
.append_child(
document
@@ -164,6 +121,299 @@ fn prepare_websys_dom() -> Element {
// };
// }
/// Wasm-bindgen has a performance option to intern commonly used phrases
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
/// We intern all the HTML tags and attributes, making most operations much faster.
///
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
pub fn intern_cache() {
let cached_words = [
// All the HTML Tags
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"command",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"menuitem",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr",
// All the event handlers
"Attribute",
"accept",
"accept-charset",
"accesskey",
"action",
"alt",
"async",
"autocomplete",
"autofocus",
"autoplay",
"charset",
"checked",
"cite",
"class",
"cols",
"colspan",
"content",
"contenteditable",
"controls",
"coords",
"data",
"data-*",
"datetime",
"default",
"defer",
"dir",
"dirname",
"disabled",
"download",
"draggable",
"enctype",
"for",
"form",
"formaction",
"headers",
"height",
"hidden",
"high",
"href",
"hreflang",
"http-equiv",
"id",
"ismap",
"kind",
"label",
"lang",
"list",
"loop",
"low",
"max",
"maxlength",
"media",
"method",
"min",
"multiple",
"muted",
"name",
"novalidate",
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
"onblur",
"oncanplay",
"oncanplaythrough",
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragleave",
"ondragover",
"ondragstart",
"ondrop",
"ondurationchange",
"onemptied",
"onended",
"onerror",
"onfocus",
"onhashchange",
"oninput",
"oninvalid",
"onkeydown",
"onkeypress",
"onkeyup",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onoffline",
"ononline",
"<body>",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
"<body>",
"onprogress",
"onratechange",
"onreset",
"onresize",
"onscroll",
"onsearch",
"onseeked",
"onseeking",
"onselect",
"onstalled",
"<body>",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunload",
"onvolumechange",
"onwaiting",
"onwheel",
"open",
"optimum",
"pattern",
"placeholder",
"poster",
"preload",
"readonly",
"rel",
"required",
"reversed",
"rows",
"rowspan",
"sandbox",
"scope",
"selected",
"shape",
"size",
"sizes",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"style",
"tabindex",
"target",
"title",
"translate",
"type",
"usemap",
"value",
"width",
"wrap",
];
for s in cached_words {
wasm_bindgen::intern(s);
}
}
#[cfg(test)]
mod tests {
use std::env;
+24 -7
View File
@@ -171,6 +171,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
tag: &str,
ns: Option<&'static str>,
) -> dioxus_core::virtual_dom::RealDomNode {
let tag = wasm_bindgen::intern(tag);
let el = match ns {
Some(ns) => self
.document
@@ -200,6 +201,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
el_id: usize,
real_id: RealDomNode,
) {
let event = wasm_bindgen::intern(event);
log::debug!(
"Called [`new_event_listener`]: {}, {:?}, {}, {:?}",
event,
@@ -237,7 +239,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
// Instead, we just build and immediately execute a closure that returns result
match decode_trigger(event) {
Ok(synthetic_event) => trigger.as_ref()(synthetic_event),
Err(_) => log::error!("Error decoding Dioxus event attribute."),
Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
};
}) as Box<dyn FnMut(&Event)>);
@@ -407,7 +409,7 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
#[derive(Debug)]
pub struct CustomMouseEvent(web_sys::MouseEvent);
impl dioxus_core::events::on::MouseEvent for CustomMouseEvent {
impl dioxus_core::events::on::MouseEventInner for CustomMouseEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
@@ -451,7 +453,7 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
self.0.get_modifier_state(key_code)
}
}
VirtualEvent::MouseEvent(Rc::new(CustomMouseEvent(evt)))
VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
// MouseEvent(Box::new(RawMouseEvent {
// alt_key: evt.alt_key(),
// button: evt.button() as i32,
@@ -547,25 +549,36 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
use anyhow::Context;
// for attr in {
let attrs = target.attributes();
for x in 0..attrs.length() {
let attr = attrs.item(x).unwrap();
log::debug!("attrs include: {:#?}", attr);
}
// }
// for attr in target.attributes() {
// log::debug!("attrs include: {:#?}", attr);
// }
// The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
let val: String = target
.get_attribute(&format!("dioxus-event-{}", typ))
.context("")?;
.context(format!("wrong format - received {:#?}", typ))?;
let mut fields = val.splitn(3, ".");
let gi_id = fields
.next()
.and_then(|f| f.parse::<u64>().ok())
.context("")?;
.context("failed to parse gi id")?;
let el_id = fields
.next()
.and_then(|f| f.parse::<usize>().ok())
.context("")?;
.context("failed to parse el id")?;
let real_id = fields
.next()
.and_then(|f| f.parse::<u64>().ok().map(RealDomNode::new))
.context("")?;
.context("failed to parse real id")?;
// Call the trigger
log::debug!("decoded gi_id: {}, li_idx: {}", gi_id, el_id);
@@ -575,6 +588,7 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
virtual_event_from_websys_event(event),
triggered_scope,
real_id,
dioxus_core::events::EventPriority::High,
))
}
@@ -584,3 +598,6 @@ impl ListenerMap {
false
}
}
struct JsStringCache {}
impl JsStringCache {}