wip: it works but the page is backwards

This commit is contained in:
Jonathan Kelley committed 2021-08-24 12:43:46 -04:00
1 parent a0a977a12d
commit cdcd8611e8
19 files changed
+239 -349

No files matched your search

+1 -16
View File
@@ -1,16 +1 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn main() {}
fn fibonacci(n: u64) -> u64 {
match n {
0 => 1,
1 => 1,
n => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
+5 -10
View File
@@ -1,3 +1,4 @@
#![allow(non_snake_case, non_upper_case_globals)]
//! This benchmark tests just the overhead of Dioxus itself. //! This benchmark tests just the overhead of Dioxus itself.
//! //!
//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes //! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes
@@ -8,12 +9,10 @@
//! - Dioxus takes 3ms to create 1_000 rows //! - Dioxus takes 3ms to create 1_000 rows
//! - Dioxus takes 30ms to create 10_000 rows //! - Dioxus takes 30ms to create 10_000 rows
//! //!
//! As pure "overhead", these are really really good numbers, mostly slowed down by hitting the global allocator. //! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster. //! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
use std::fmt::Display; use criterion::{criterion_group, criterion_main, Criterion};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dioxus_core as dioxus; use dioxus_core as dioxus;
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
@@ -58,11 +57,12 @@ struct RowProps {
label: Label, label: Label,
} }
fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree { fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
let [adj, col, noun] = cx.label.0;
cx.render(rsx! { cx.render(rsx! {
tr { tr {
td { class:"col-md-1", "{cx.row_id}" } td { class:"col-md-1", "{cx.row_id}" }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ } td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
a { class: "lbl", "{cx.label}" } a { class: "lbl", "{adj}" "{col}" "{noun}" }
} }
td { class: "col-md-1" td { class: "col-md-1"
a { class: "remove", onclick: move |_| {/* remove */} a { class: "remove", onclick: move |_| {/* remove */}
@@ -86,11 +86,6 @@ impl Label {
]) ])
} }
} }
impl Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {} {}", self.0[0], self.0[1], self.0[2])
}
}
static ADJECTIVES: &[&str] = &[ static ADJECTIVES: &[&str] = &[
"pretty", "pretty",
+4 -5
View File
@@ -6,17 +6,16 @@ pub static Example: FC<()> = |cx| {
let list = (0..10).map(|f| LazyNodes::new(move |f| todo!())); let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
cx.render(LazyNodes::new(move |cx| { cx.render(LazyNodes::new(move |cx| {
let bump = cx.bump();
cx.raw_element( cx.raw_element(
"div", "div",
None, None,
&mut [], [],
&mut [], [],
cx.bump().alloc([ [
cx.text(format_args!("hello")), cx.text(format_args!("hello")),
cx.text(format_args!("hello")), cx.text(format_args!("hello")),
cx.fragment_from_iter(list), cx.fragment_from_iter(list),
]), ],
None, None,
) )
})) }))
-18
View File
@@ -1,18 +0,0 @@
fn main() {
// let g = rsx! {
// Fragment {
// // div {}
// // div {}
// // div {}
// // div {}
// // div {}
// // div {}
// // div {}
// // div {}
// // div {}
// }
// };
}
+13 -17
View File
@@ -1,31 +1,27 @@
fn main() {}
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
fn App(cx: Context<()>) -> DomTree { fn main() {}
//
fn app(cx: Context<()>) -> DomTree {
let vak = use_suspense( let vak = use_suspense(
cx, cx,
|| async {}, || async {},
|c, res| { |c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))),
//
c.render(LazyNodes::new(move |f| f.text(format_args!(""))))
},
); );
let d1 = cx.render(LazyNodes::new(move |f| { let d1 = cx.render(LazyNodes::new(move |f| {
f.raw_element( f.raw_element(
"div", "div",
None, None,
&mut [], [],
&[], [],
f.bump().alloc([ [
f.fragment_from_iter(vak), f.fragment_from_iter(vak),
f.text(format_args!("")), f.text(format_args!("")),
f.text(format_args!("")), f.text(format_args!("")),
f.text(format_args!("")), f.text(format_args!("")),
f.text(format_args!("")), f.text(format_args!("")),
]), ],
None, None,
) )
})); }));
@@ -34,15 +30,15 @@ fn App(cx: Context<()>) -> DomTree {
f.raw_element( f.raw_element(
"div", "div",
None, None,
&mut [], [],
&[], [],
f.bump().alloc([ [
f.text(format_args!("")), f.text(format_args!("")),
f.text(format_args!("")), f.text(format_args!("")),
f.text(format_args!("")), f.text(format_args!("")),
f.text(format_args!("")), f.text(format_args!("")),
f.fragment_from_iter(d1), d1.unwrap(),
]), ],
None, None,
) )
})) }))
-137
View File
@@ -1,137 +0,0 @@
// #![allow(unused, non_upper_case_globals, non_snake_case)]
// use bumpalo::Bump;
// use dioxus_core::nodebuilder::*;
// use dioxus_core::prelude::*;
// use std::{collections::HashMap, future::Future, marker::PhantomData};
fn main() {}
// fn main() {
// let mut vdom = VirtualDom::new_with_props(
// component,
// Props {
// blah: false,
// text: "blah".into(),
// },
// );
// vdom.progress();
// let somet = String::from("asd");
// let text = somet.as_str();
// /*
// this could be auto-generated via the macro
// this props is allocated in this
// but the component and props would like need to be cached
// we could box this fn, abstracting away the props requirement and just keep the entrance and allocator requirement
// How do we keep cached things around?
// Need some sort of caching mechanism
// how do we enter into a childscope from a parent scope?
// Problems:
// 1: Comp props need to be stored somewhere so we can re-evalute components when they receive updates
// 2: Trees are not evaluated
// */
// let example_caller = move |cx: &Bump| {
// todo!()
// // let p = Props { blah: true, text };
// // let c = Context { props: &p };
// // let r = component(&c);
// };
// // check the edit list
// }
// // ~~~ Text shared between components via props can be done with lifetimes! ~~~
// // Super duper efficient :)
// struct Props {
// blah: bool,
// text: String,
// // text: &'src str,
// }
// impl Properties for Props {
// fn new() -> Self {
// todo!()
// }
// }
// fn component<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
// // Write asynchronous rendering code that immediately returns a "suspended" VNode
// // The concurrent API will then progress this component when the future finishes
// // You can suspend the entire component, or just parts of it
// let product_list = cx.suspend(async {
// // Suspend the rendering that completes when the future is done
// match fetch_data().await {
// Ok(data) => html! { <div> "success!" </div>},
// Err(_) => html! { <div> "failure :(" </div>},
// }
// });
// // todo!()
// cx.render(html! {
// <div>
// <h1> "Products" </h1>
// <button> "hello!" </button>
// // Subnodes can even be suspended
// // When completely rendered, they won't cause the component itself to re-render, just their slot
// // <p> { product_list } </p>
// </div>
// })
// }
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
// // VNodes can be constructed via a builder or the html! macro
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
// // We can "view" them with Context for ultimate speed while inside components
// cx.render(|bump| {
// div(bump)
// .attr("class", "edit")
// .child(text("Hello"))
// .child(text(cx.cx.text.as_ref()))
// .finish()
// })
// }
// #[fc]
// fn EffcComp(cx: Context, name: &str) -> DomTree {
// // VNodes can be constructed via a builder or the html! macro
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
// // We can "view" them with Context for ultimate speed while inside components
// // use "phase" style allocation;
// cx.render(html! {
// <div>
// // your template goes here
// // feel free to directly use "name"
// </div>
// })
// }
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> DomTree<'a> {
// cx.suspend(async {
// let i: i32 = 0;
// // full suspended works great with just returning VNodes!
// // Feel free to capture the html! macro directly
// // Anything returned here is automatically viewed
// let tex = match i {
// 1 => html! { <div> </div> },
// 2 => html! { <div> </div> },
// _ => html! { <div> </div> },
// };
// if cx.cx.blah {
// html! { <div> </div> }
// } else {
// tex
// }
// })
// }
// /// An example of a datafetching service
// async fn fetch_data() -> Result<String, ()> {
// todo!()
// }
+19 -21
View File
@@ -89,7 +89,6 @@
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use crate::{arena::SharedResources, innerlude::*}; use crate::{arena::SharedResources, innerlude::*};
use futures_util::{Future, FutureExt};
use fxhash::{FxHashMap, FxHashSet}; use fxhash::{FxHashMap, FxHashSet};
use DomEdit::*; use DomEdit::*;
@@ -281,19 +280,20 @@ impl<'bump> DiffMachine<'bump> {
} = element; } = element;
let real_id = self.vdom.reserve_node(); let real_id = self.vdom.reserve_node();
dom_id.set(Some(real_id));
self.mutations.create_element(tag_name, *namespace, real_id); self.mutations.create_element(tag_name, *namespace, real_id);
self.stack.add_child_count(1); self.stack.add_child_count(1);
dom_id.set(Some(real_id)); let cur_scope_id = self.stack.current_scope().unwrap();
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
let cur_scope = self.stack.current_scope().unwrap();
listeners.iter().for_each(|listener| { listeners.iter().for_each(|listener| {
self.fix_listener(listener); self.attach_listener_to_scope(listener, scope);
listener.mounted_node.set(Some(real_id)); listener.mounted_node.set(Some(real_id));
self.mutations self.mutations
.new_event_listener(listener, cur_scope.clone()); .new_event_listener(listener, cur_scope_id.clone());
}); });
for attr in *attributes { for attr in *attributes {
@@ -466,16 +466,19 @@ impl<'bump> DiffMachine<'bump> {
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
// //
// TODO: take a more efficient path than this // TODO: take a more efficient path than this
let cur_scope = self.stack.current_scope().unwrap();
let cur_scope_id = self.stack.current_scope().unwrap();
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
if old.listeners.len() == new.listeners.len() { if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event { if old_l.event != new_l.event {
please_commit(&mut self.mutations.edits); please_commit(&mut self.mutations.edits);
self.mutations.remove_event_listener(old_l.event); self.mutations.remove_event_listener(old_l.event);
self.mutations.new_event_listener(new_l, cur_scope); self.mutations.new_event_listener(new_l, cur_scope_id);
} }
new_l.mounted_node.set(old_l.mounted_node.get()); new_l.mounted_node.set(old_l.mounted_node.get());
self.fix_listener(new_l); self.attach_listener_to_scope(new_l, scope);
} }
} else { } else {
please_commit(&mut self.mutations.edits); please_commit(&mut self.mutations.edits);
@@ -484,10 +487,8 @@ impl<'bump> DiffMachine<'bump> {
} }
for listener in new.listeners { for listener in new.listeners {
listener.mounted_node.set(Some(root)); listener.mounted_node.set(Some(root));
self.mutations.new_event_listener(listener, cur_scope); self.mutations.new_event_listener(listener, cur_scope_id);
self.attach_listener_to_scope(listener, scope);
// Make sure the listener gets attached to the scope list
self.fix_listener(listener);
} }
} }
@@ -1082,13 +1083,10 @@ impl<'bump> DiffMachine<'bump> {
} }
} }
fn fix_listener<'a>(&mut self, listener: &'a Listener<'a>) { /// Adds a listener closure to a scope during diff.
let scope_id = self.stack.current_scope(); fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
if let Some(scope_id) = scope_id { let mut queue = scope.listeners.borrow_mut();
let scope = self.vdom.get_scope(scope_id).unwrap(); let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
let mut queue = scope.listeners.borrow_mut(); queue.push(long_listener as *const _)
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
queue.push(long_listener as *const _)
}
} }
} }
+2 -3
View File
@@ -113,7 +113,6 @@ impl Scope {
child_nodes: ScopeChildren, child_nodes: ScopeChildren,
) { ) {
self.caller = caller; self.caller = caller;
// let child_nodes = unsafe { std::mem::transmute(child_nodes) };
let child_nodes = unsafe { child_nodes.extend_lifetime() }; let child_nodes = unsafe { child_nodes.extend_lifetime() };
self.child_nodes = child_nodes; self.child_nodes = child_nodes;
} }
@@ -237,8 +236,8 @@ impl Scope {
} }
pub fn consume_garbage(&self) -> Vec<&VNode> { pub fn consume_garbage(&self) -> Vec<&VNode> {
let mut garbage = self.pending_garbage.borrow_mut(); self.pending_garbage
garbage .borrow_mut()
.drain(..) .drain(..)
.map(|node| { .map(|node| {
// safety: scopes cannot cycle without their garbage being collected. these nodes are safe // safety: scopes cannot cycle without their garbage being collected. these nodes are safe
+8 -6
View File
@@ -7,6 +7,8 @@ use dioxus_core as dioxus;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
use DomEdit::*; use DomEdit::*;
const LOGGING_ENABLED: bool = false;
#[test] #[test]
fn test_original_diff() { fn test_original_diff() {
static App: FC<()> = |cx| { static App: FC<()> = |cx| {
@@ -57,7 +59,7 @@ async fn create() {
}) })
}; };
test_logging::set_up_logging(); test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mutations = dom.rebuild_async().await.unwrap(); let mutations = dom.rebuild_async().await.unwrap();
assert_eq!( assert_eq!(
@@ -98,7 +100,7 @@ async fn create_list() {
}) })
}; };
test_logging::set_up_logging(); test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mutations = dom.rebuild_async().await.unwrap(); let mutations = dom.rebuild_async().await.unwrap();
@@ -141,7 +143,7 @@ async fn create_simple() {
}) })
}; };
test_logging::set_up_logging(); test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mutations = dom.rebuild_async().await.unwrap(); let mutations = dom.rebuild_async().await.unwrap();
@@ -177,7 +179,7 @@ async fn create_components() {
}) })
}; };
test_logging::set_up_logging(); test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mutations = dom.rebuild_async().await.unwrap(); let mutations = dom.rebuild_async().await.unwrap();
@@ -223,7 +225,7 @@ async fn anchors() {
}) })
}; };
test_logging::set_up_logging(); test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mutations = dom.rebuild_async().await.unwrap(); let mutations = dom.rebuild_async().await.unwrap();
@@ -249,7 +251,7 @@ async fn suspended() {
cx.render(rsx! { {val} }) cx.render(rsx! { {val} })
}; };
test_logging::set_up_logging(); test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mutations = dom.rebuild_async().await.unwrap(); let mutations = dom.rebuild_async().await.unwrap();
+3 -1
View File
@@ -6,6 +6,8 @@ mod test_logging;
use dioxus_core as dioxus; use dioxus_core as dioxus;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
const LOGGING_ENABLED: bool = false;
#[async_std::test] #[async_std::test]
async fn test_iterative_create_components() { async fn test_iterative_create_components() {
static App: FC<()> = |cx| { static App: FC<()> = |cx| {
@@ -34,7 +36,7 @@ async fn test_iterative_create_components() {
}) })
} }
test_logging::set_up_logging(); test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
+57 -43
View File
@@ -1,12 +1,7 @@
//! Diffing Tests //! Diffing Tests
//! -------------
//!
//! These should always compile and run, but the result is not validat root: (), m: () ed for each test.
//! TODO: Validate the results beyond visual inspection.
use bumpalo::Bump; use bumpalo::Bump;
use anyhow::{Context, Result};
use dioxus::{ use dioxus::{
arena::SharedResources, diff::DiffMachine, prelude::*, DiffInstruction, DomEdit, MountType, arena::SharedResources, diff::DiffMachine, prelude::*, DiffInstruction, DomEdit, MountType,
}; };
@@ -18,7 +13,7 @@ use DomEdit::*;
// logging is wired up to the test harness // logging is wired up to the test harness
// feel free to enable while debugging // feel free to enable while debugging
const IS_LOGGING_ENABLED: bool = true; const IS_LOGGING_ENABLED: bool = false;
struct TestDom { struct TestDom {
bump: Bump, bump: Bump,
@@ -26,9 +21,7 @@ struct TestDom {
} }
impl TestDom { impl TestDom {
fn new() -> TestDom { fn new() -> TestDom {
if IS_LOGGING_ENABLED { test_logging::set_up_logging(IS_LOGGING_ENABLED);
test_logging::set_up_logging();
}
let bump = Bump::new(); let bump = Bump::new();
let resources = SharedResources::new(); let resources = SharedResources::new();
TestDom { bump, resources } TestDom { bump, resources }
@@ -82,8 +75,6 @@ impl TestDom {
let new = self.bump.alloc(self.render(right)); let new = self.bump.alloc(self.render(right));
// let mut create_edits = Vec::new();
let mut machine = DiffMachine::new_headless(&self.resources); let mut machine = DiffMachine::new_headless(&self.resources);
machine.stack.create_node(old, MountType::Append); machine.stack.create_node(old, MountType::Append);
@@ -343,16 +334,31 @@ fn two_fragments_with_differrent_elements_are_differet() {
let dom = TestDom::new(); let dom = TestDom::new();
let left = rsx!( let left = rsx!(
{ (0..2).map(|f| rsx! { div { }} ) } { (0..2).map(|_| rsx! { div { }} ) }
p {} p {}
); );
let right = rsx!( let right = rsx!(
{ (0..5).map(|f| rsx! (h1 { }) ) } { (0..5).map(|_| rsx! (h1 { }) ) }
p {} p {}
); );
let edits = dom.lazy_diff(left, right); let (create, changes) = dom.lazy_diff(left, right);
log::debug!("{:#?}", &edits); log::debug!("{:#?}", &changes);
assert_eq!(
changes.edits,
[
// create the new h1s
CreateElement { tag: "h1", id: 3 },
CreateElement { tag: "h1", id: 4 },
CreateElement { tag: "h1", id: 5 },
InsertAfter { root: 1, n: 3 },
// replace the divs with new h1s
CreateElement { tag: "h1", id: 6 },
ReplaceWith { root: 0, m: 1 },
CreateElement { tag: "h1", id: 7 },
ReplaceWith { root: 1, m: 1 },
]
);
} }
/// Should result in multiple nodes destroyed - with changes to the first nodes /// Should result in multiple nodes destroyed - with changes to the first nodes
@@ -452,14 +458,6 @@ fn keyed_diffing_order() {
); );
} }
#[test]
fn fragment_keys() {
let r = 1;
let p = rsx! {
Fragment { key: "asd {r}" }
};
}
/// Should result in moves, but not removals or additions /// Should result in moves, but not removals or additions
#[test] #[test]
fn keyed_diffing_out_of_order() { fn keyed_diffing_out_of_order() {
@@ -477,8 +475,12 @@ fn keyed_diffing_out_of_order() {
}) })
}); });
let edits = dom.lazy_diff(left, right); let (_, changes) = dom.lazy_diff(left, right);
log::debug!("{:?}", &edits.1); log::debug!("{:?}", &changes);
assert_eq!(
changes.edits,
[PushRoot { id: 6 }, InsertBefore { root: 4, n: 1 }]
);
} }
/// Should result in moves only /// Should result in moves only
@@ -655,21 +657,24 @@ fn keyed_diffing_additions_and_moves_on_ends() {
let right = rsx!({ let right = rsx!({
[/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| { [/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
// [/**/ 8, 7, 4, 5, 6, 9, 10 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }} rsx! { div { key: "{f}" }}
}) })
}); });
let (_, change) = dom.lazy_diff(left, right); let (_, change) = dom.lazy_diff(left, right);
log::debug!("{:?}", change); log::debug!("{:?}", change);
// assert_eq!( assert_eq!(
// change.edits, change.edits,
// [ [
// CreateElement { id: 5, tag: "div" }, // create 11, 12
// CreateElement { id: 6, tag: "div" }, CreateElement { tag: "div", id: 4 },
// InsertAfter { n: 2, root: 4 } CreateElement { tag: "div", id: 5 },
// ] InsertAfter { root: 2, n: 2 },
// ); // move 7 to the front
PushRoot { id: 3 },
InsertBefore { root: 0, n: 1 }
]
);
} }
#[test] #[test]
@@ -688,16 +693,25 @@ fn keyed_diffing_additions_and_moves_in_middle() {
}) })
}); });
// LIS: 4, 5, 6
let (_, change) = dom.lazy_diff(left, right); let (_, change) = dom.lazy_diff(left, right);
log::debug!("{:?}", change); log::debug!("{:#?}", change);
// assert_eq!( assert_eq!(
// change.edits, change.edits,
// [ [
// CreateElement { id: 5, tag: "div" }, // create 13, 17
// CreateElement { id: 6, tag: "div" }, CreateElement { tag: "div", id: 4 },
// InsertAfter { n: 2, root: 4 } CreateElement { tag: "div", id: 5 },
// ] InsertBefore { root: 1, n: 2 },
// ); // create 11, 12
CreateElement { tag: "div", id: 6 },
CreateElement { tag: "div", id: 7 },
InsertBefore { root: 2, n: 2 },
// move 7
PushRoot { id: 3 },
InsertBefore { root: 0, n: 1 }
]
);
} }
#[test] #[test]
+5 -1
View File
@@ -1,7 +1,11 @@
pub fn set_up_logging() { pub fn set_up_logging(enabled: bool) {
use fern::colors::{Color, ColoredLevelConfig}; use fern::colors::{Color, ColoredLevelConfig};
use log::debug; use log::debug;
if !enabled {
return;
}
// configure colors for the whole line // configure colors for the whole line
let colors_line = ColoredLevelConfig::new() let colors_line = ColoredLevelConfig::new()
.error(Color::Red) .error(Color::Red)
@@ -1,3 +1,6 @@
//! This module is not included anywhere.
//!
//! It is a prototype for a system that supports non-string attribute values.
trait AsAttributeValue: Sized { trait AsAttributeValue: Sized {
fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>; fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;
+25
View File
@@ -0,0 +1,25 @@
use dioxus::virtual_dom::VirtualDom;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
fn main() {
let mut dom = VirtualDom::new(App);
dom.rebuild().unwrap();
println!(
"{}",
dioxus_ssr::render_vdom(&dom, |c| c.newline(true).indent(true))
)
}
pub static App: FC<()> = |cx| {
cx.render(rsx!(
div {
class: "overflow-hidden"
ul {
{(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})}
}
"hello world!"
}
))
};
+1 -1
View File
@@ -13,7 +13,7 @@ fn main() {
let mut file = File::create("example.html").unwrap(); let mut file = File::create("example.html").unwrap();
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild().expect("failed to run virtualdom");
file.write_fmt(format_args!( file.write_fmt(format_args!(
"{}", "{}",
+8 -8
View File
@@ -46,7 +46,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
/// ```ignore /// ```ignore
/// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" })); /// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
/// let mut vdom = VirtualDom::new(App); /// let mut vdom = VirtualDom::new(App);
/// vdom.rebuild_in_place(); /// vdom.rebuild();
/// ///
/// let renderer = TextRenderer::new(&vdom); /// let renderer = TextRenderer::new(&vdom);
/// let output = format!("{}", renderer); /// let output = format!("{}", renderer);
@@ -74,7 +74,7 @@ impl<'a> TextRenderer<'a> {
} }
fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result { fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result {
match &node.kind { match &node {
VNode::Text(text) => { VNode::Text(text) => {
if self.cfg.indent { if self.cfg.indent {
for _ in 0..il { for _ in 0..il {
@@ -286,28 +286,28 @@ mod tests {
#[test] #[test]
fn to_string_works() { fn to_string_works() {
let mut dom = VirtualDom::new(SIMPLE_APP); let mut dom = VirtualDom::new(SIMPLE_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild().expect("failed to run virtualdom");
dbg!(render_vdom(&dom, |c| c)); dbg!(render_vdom(&dom, |c| c));
} }
#[test] #[test]
fn hydration() { fn hydration() {
let mut dom = VirtualDom::new(NESTED_APP); let mut dom = VirtualDom::new(NESTED_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild().expect("failed to run virtualdom");
dbg!(render_vdom(&dom, |c| c.pre_render(true))); dbg!(render_vdom(&dom, |c| c.pre_render(true)));
} }
#[test] #[test]
fn nested() { fn nested() {
let mut dom = VirtualDom::new(NESTED_APP); let mut dom = VirtualDom::new(NESTED_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild().expect("failed to run virtualdom");
dbg!(render_vdom(&dom, |c| c)); dbg!(render_vdom(&dom, |c| c));
} }
#[test] #[test]
fn fragment_app() { fn fragment_app() {
let mut dom = VirtualDom::new(FRAGMENT_APP); let mut dom = VirtualDom::new(FRAGMENT_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild().expect("failed to run virtualdom");
dbg!(render_vdom(&dom, |c| c)); dbg!(render_vdom(&dom, |c| c));
} }
@@ -319,7 +319,7 @@ mod tests {
let mut file = File::create("index.html").unwrap(); let mut file = File::create("index.html").unwrap();
let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX); let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild().expect("failed to run virtualdom");
file.write_fmt(format_args!( file.write_fmt(format_args!(
"{}", "{}",
@@ -337,7 +337,7 @@ mod tests {
}; };
let mut dom = VirtualDom::new(STLYE_APP); let mut dom = VirtualDom::new(STLYE_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild().expect("failed to run virtualdom");
dbg!(render_vdom(&dom, |c| c)); dbg!(render_vdom(&dom, |c| c));
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ fn main() {
} }
static App: FC<()> = |cx| { static App: FC<()> = |cx| {
let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 3);
cx.render(rsx! { cx.render(rsx! {
div { div {
+20 -20
View File
@@ -84,8 +84,8 @@ impl WebsysDom {
DomEdit::PushRoot { id: root } => self.push(root), DomEdit::PushRoot { id: root } => self.push(root),
DomEdit::PopRoot => self.pop(), DomEdit::PopRoot => self.pop(),
DomEdit::AppendChildren { many } => self.append_children(many), DomEdit::AppendChildren { many } => self.append_children(many),
DomEdit::ReplaceWith { n, m } => self.replace_with(n, m), DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
DomEdit::Remove => self.remove(), DomEdit::Remove { root } => self.remove(root),
DomEdit::RemoveAllChildren => self.remove_all_children(), DomEdit::RemoveAllChildren => self.remove_all_children(),
DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id), DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id),
DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id), DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id),
@@ -102,8 +102,8 @@ impl WebsysDom {
DomEdit::SetAttribute { field, value, ns } => self.set_attribute(field, value, ns), DomEdit::SetAttribute { field, value, ns } => self.set_attribute(field, value, ns),
DomEdit::RemoveAttribute { name } => self.remove_attribute(name), DomEdit::RemoveAttribute { name } => self.remove_attribute(name),
DomEdit::InsertAfter { n } => self.insert_after(n), DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
DomEdit::InsertBefore { n } => self.insert_before(n), DomEdit::InsertBefore { n, root } => self.insert_before(n, root),
} }
} }
} }
@@ -154,7 +154,7 @@ impl WebsysDom {
} }
} }
fn replace_with(&mut self, n: u32, m: u32) { fn replace_with(&mut self, m: u32, root: u64) {
log::debug!("Called [`replace_with`]"); log::debug!("Called [`replace_with`]");
let mut new_nodes = vec![]; let mut new_nodes = vec![];
@@ -162,19 +162,11 @@ impl WebsysDom {
new_nodes.push(self.stack.pop()); new_nodes.push(self.stack.pop());
} }
let mut old_nodes = vec![]; let old = self.nodes[root as usize].as_ref().unwrap();
for _ in 0..n {
old_nodes.push(self.stack.pop());
}
for node in &old_nodes[1..] {
node.dyn_ref::<Element>().unwrap().remove();
}
let old = old_nodes[0].clone();
let arr: js_sys::Array = new_nodes.iter().collect(); let arr: js_sys::Array = new_nodes.iter().collect();
let el = old.dyn_into::<Element>().unwrap(); let el = old.dyn_ref::<Element>().unwrap();
el.replace_with_with_node(&arr).unwrap(); el.replace_with_with_node(&arr).unwrap();
// let arr = js_sys::Array::from(); // let arr = js_sys::Array::from();
// TODO: use different-sized replace withs // TODO: use different-sized replace withs
@@ -205,9 +197,17 @@ impl WebsysDom {
// self.stack.push(new_node); // self.stack.push(new_node);
} }
fn remove(&mut self) { fn remove(&mut self, root: u64) {
log::debug!("Called [`remove`]"); log::debug!("Called [`remove`]");
todo!()
let node = self.nodes[root as usize].as_ref().unwrap();
if let Some(element) = node.dyn_ref::<Element>() {
element.remove();
} else {
if let Some(parent) = node.parent_node() {
parent.remove_child(&node).unwrap();
}
}
} }
fn remove_all_children(&mut self) { fn remove_all_children(&mut self) {
@@ -361,7 +361,7 @@ impl WebsysDom {
} }
} }
fn insert_after(&mut self, n: u32) { fn insert_after(&mut self, n: u32, root: u64) {
let mut new_nodes = vec![]; let mut new_nodes = vec![];
for _ in 0..n { for _ in 0..n {
new_nodes.push(self.stack.pop()); new_nodes.push(self.stack.pop());
@@ -380,7 +380,7 @@ impl WebsysDom {
// let el = self.stack.top(); // let el = self.stack.top();
} }
fn insert_before(&mut self, n: u32) { fn insert_before(&mut self, n: u32, root: u64) {
let n = n as usize; let n = n as usize;
let root = self let root = self
.stack .stack
+64 -41
View File
@@ -1,30 +1,56 @@
//! Dioxus WebSys //! Dioxus WebSys
//! -------------- //!
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. //! ## Overview
//! ------------
/* //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using WebSys. This web render for
From Google's guide on rAF and rIC: //! Dioxus is one of the more advanced renderers, supporting:
-------- //! - idle work
//! - animations
If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed, //! - jank-free rendering
which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside //! - noderefs
of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next //! - controlled components
frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout, //! - re-hydration
which is a potential performance bottleneck. //! - and more.
//!
Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable, //! The actual implementation is farily thin, with the heavy lifting happening inside the Dioxus Core crate.
and as such we could easily go past the deadline the browser provided. //!
//! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the //! validation of websys-specific features and not the general use of Dioxus.
browser with that type of work in mind. That means that our code will need to use a document fragment, which can then //!
be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback //! ## RequestAnimationFrame and RequestIdleCallback
to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback. //! ------------------------------------------------
//! React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
Essentially: //! running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
------------ //! main thread.
- Do the VDOM work during the idlecallback //!
- Do DOM work in the next requestAnimationFrame callback //! React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus,
*/ //! the diff phase is non-blocking, using "yield_now" to allow the browser to process other events. When the diff phase
//! is finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply.
//!
//! Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then
//! setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame
//!
//! From Google's guide on rAF and rIC:
//! -----------------------------------
//!
//! If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
//! which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
//! of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
//! frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
//! which is a potential performance bottleneck.
//!
//! Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
//! and as such we could easily go past the deadline the browser provided.
//!
//! The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
//! browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
//! be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
//! to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
//!
//! Essentially:
//! ------------
//! - Do the VDOM work during the idlecallback
//! - Do DOM work in the next requestAnimationFrame callback
use std::rc::Rc; use std::rc::Rc;
@@ -103,20 +129,22 @@ pub async fn run_with_props<T: Properties + 'static>(
) -> Result<()> { ) -> Result<()> {
let mut dom = VirtualDom::new_with_props(root, root_props); let mut dom = VirtualDom::new_with_props(root, root_props);
let hydrating = cfg.hydrate;
let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap(); let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
let tasks = dom.get_event_sender(); let tasks = dom.get_event_sender();
let sender_callback = Rc::new(move |event| tasks.unbounded_send(event).unwrap());
// initialize the virtualdom first let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
// if cfg.hydrate {
// dom.rebuild_in_place()?;
// }
let mut websys_dom = dom::WebsysDom::new( let mut mutations = dom.rebuild().unwrap();
root_el, log::info!("Mutations: {:#?}", mutations);
cfg,
Rc::new(move |event| tasks.unbounded_send(event).unwrap()), // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
); // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
if !hydrating {
websys_dom.process_edits(&mut mutations.edits);
}
loop { loop {
let deadline = gloo_timers::future::TimeoutFuture::new(16); let deadline = gloo_timers::future::TimeoutFuture::new(16);
@@ -127,8 +155,3 @@ pub async fn run_with_props<T: Properties + 'static>(
Ok(()) Ok(())
} }
struct HydrationNode {
id: usize,
node: Node,
}