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 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);
fn main() {}
+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.
//!
//! 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 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.
use std::fmt::Display;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use criterion::{criterion_group, criterion_main, Criterion};
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_html as dioxus_elements;
@@ -58,11 +57,12 @@ struct RowProps {
label: Label,
}
fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
let [adj, col, noun] = cx.label.0;
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.row_id}" }
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"
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] = &[
"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!()));
cx.render(LazyNodes::new(move |cx| {
let bump = cx.bump();
cx.raw_element(
"div",
None,
&mut [],
&mut [],
cx.bump().alloc([
[],
[],
[
cx.text(format_args!("hello")),
cx.text(format_args!("hello")),
cx.fragment_from_iter(list),
]),
],
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::*;
fn App(cx: Context<()>) -> DomTree {
//
fn main() {}
fn app(cx: Context<()>) -> DomTree {
let vak = use_suspense(
cx,
|| async {},
|c, res| {
//
c.render(LazyNodes::new(move |f| f.text(format_args!(""))))
},
|c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))),
);
let d1 = cx.render(LazyNodes::new(move |f| {
f.raw_element(
"div",
None,
&mut [],
&[],
f.bump().alloc([
[],
[],
[
f.fragment_from_iter(vak),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
]),
],
None,
)
}));
@@ -34,15 +30,15 @@ fn App(cx: Context<()>) -> DomTree {
f.raw_element(
"div",
None,
&mut [],
&[],
f.bump().alloc([
[],
[],
[
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.fragment_from_iter(d1),
]),
d1.unwrap(),
],
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/
use crate::{arena::SharedResources, innerlude::*};
use futures_util::{Future, FutureExt};
use fxhash::{FxHashMap, FxHashSet};
use DomEdit::*;
@@ -281,19 +280,20 @@ impl<'bump> DiffMachine<'bump> {
} = element;
let real_id = self.vdom.reserve_node();
dom_id.set(Some(real_id));
self.mutations.create_element(tag_name, *namespace, real_id);
self.stack.add_child_count(1);
dom_id.set(Some(real_id));
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();
listeners.iter().for_each(|listener| {
self.fix_listener(listener);
self.attach_listener_to_scope(listener, scope);
listener.mounted_node.set(Some(real_id));
self.mutations
.new_event_listener(listener, cur_scope.clone());
.new_event_listener(listener, cur_scope_id.clone());
});
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)
//
// 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() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
please_commit(&mut self.mutations.edits);
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());
self.fix_listener(new_l);
self.attach_listener_to_scope(new_l, scope);
}
} else {
please_commit(&mut self.mutations.edits);
@@ -484,10 +487,8 @@ impl<'bump> DiffMachine<'bump> {
}
for listener in new.listeners {
listener.mounted_node.set(Some(root));
self.mutations.new_event_listener(listener, cur_scope);
// Make sure the listener gets attached to the scope list
self.fix_listener(listener);
self.mutations.new_event_listener(listener, cur_scope_id);
self.attach_listener_to_scope(listener, scope);
}
}
@@ -1082,13 +1083,10 @@ impl<'bump> DiffMachine<'bump> {
}
}
fn fix_listener<'a>(&mut self, listener: &'a Listener<'a>) {
let scope_id = self.stack.current_scope();
if let Some(scope_id) = scope_id {
let scope = self.vdom.get_scope(scope_id).unwrap();
let mut queue = scope.listeners.borrow_mut();
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
queue.push(long_listener as *const _)
}
/// Adds a listener closure to a scope during diff.
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
let mut queue = scope.listeners.borrow_mut();
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,
) {
self.caller = caller;
// let child_nodes = unsafe { std::mem::transmute(child_nodes) };
let child_nodes = unsafe { child_nodes.extend_lifetime() };
self.child_nodes = child_nodes;
}
@@ -237,8 +236,8 @@ impl Scope {
}
pub fn consume_garbage(&self) -> Vec<&VNode> {
let mut garbage = self.pending_garbage.borrow_mut();
garbage
self.pending_garbage
.borrow_mut()
.drain(..)
.map(|node| {
// 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 DomEdit::*;
const LOGGING_ENABLED: bool = false;
#[test]
fn test_original_diff() {
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 mutations = dom.rebuild_async().await.unwrap();
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 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 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 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 mutations = dom.rebuild_async().await.unwrap();
@@ -249,7 +251,7 @@ async fn suspended() {
cx.render(rsx! { {val} })
};
test_logging::set_up_logging();
test_logging::set_up_logging(LOGGING_ENABLED);
let mut dom = VirtualDom::new(App);
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_html as dioxus_elements;
const LOGGING_ENABLED: bool = false;
#[async_std::test]
async fn test_iterative_create_components() {
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);
+57 -43
View File
@@ -1,12 +1,7 @@
//! 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 anyhow::{Context, Result};
use dioxus::{
arena::SharedResources, diff::DiffMachine, prelude::*, DiffInstruction, DomEdit, MountType,
};
@@ -18,7 +13,7 @@ use DomEdit::*;
// logging is wired up to the test harness
// feel free to enable while debugging
const IS_LOGGING_ENABLED: bool = true;
const IS_LOGGING_ENABLED: bool = false;
struct TestDom {
bump: Bump,
@@ -26,9 +21,7 @@ struct TestDom {
}
impl TestDom {
fn new() -> TestDom {
if IS_LOGGING_ENABLED {
test_logging::set_up_logging();
}
test_logging::set_up_logging(IS_LOGGING_ENABLED);
let bump = Bump::new();
let resources = SharedResources::new();
TestDom { bump, resources }
@@ -82,8 +75,6 @@ impl TestDom {
let new = self.bump.alloc(self.render(right));
// let mut create_edits = Vec::new();
let mut machine = DiffMachine::new_headless(&self.resources);
machine.stack.create_node(old, MountType::Append);
@@ -343,16 +334,31 @@ fn two_fragments_with_differrent_elements_are_differet() {
let dom = TestDom::new();
let left = rsx!(
{ (0..2).map(|f| rsx! { div { }} ) }
{ (0..2).map(|_| rsx! { div { }} ) }
p {}
);
let right = rsx!(
{ (0..5).map(|f| rsx! (h1 { }) ) }
{ (0..5).map(|_| rsx! (h1 { }) ) }
p {}
);
let edits = dom.lazy_diff(left, right);
log::debug!("{:#?}", &edits);
let (create, changes) = dom.lazy_diff(left, right);
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
@@ -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
#[test]
fn keyed_diffing_out_of_order() {
@@ -477,8 +475,12 @@ fn keyed_diffing_out_of_order() {
})
});
let edits = dom.lazy_diff(left, right);
log::debug!("{:?}", &edits.1);
let (_, changes) = dom.lazy_diff(left, right);
log::debug!("{:?}", &changes);
assert_eq!(
changes.edits,
[PushRoot { id: 6 }, InsertBefore { root: 4, n: 1 }]
);
}
/// Should result in moves only
@@ -655,21 +657,24 @@ fn keyed_diffing_additions_and_moves_on_ends() {
let right = rsx!({
[/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
// [/**/ 8, 7, 4, 5, 6, 9, 10 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.lazy_diff(left, right);
log::debug!("{:?}", change);
// assert_eq!(
// change.edits,
// [
// CreateElement { id: 5, tag: "div" },
// CreateElement { id: 6, tag: "div" },
// InsertAfter { n: 2, root: 4 }
// ]
// );
assert_eq!(
change.edits,
[
// create 11, 12
CreateElement { tag: "div", id: 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]
@@ -688,16 +693,25 @@ fn keyed_diffing_additions_and_moves_in_middle() {
})
});
// LIS: 4, 5, 6
let (_, change) = dom.lazy_diff(left, right);
log::debug!("{:?}", change);
// assert_eq!(
// change.edits,
// [
// CreateElement { id: 5, tag: "div" },
// CreateElement { id: 6, tag: "div" },
// InsertAfter { n: 2, root: 4 }
// ]
// );
log::debug!("{:#?}", change);
assert_eq!(
change.edits,
[
// create 13, 17
CreateElement { tag: "div", id: 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]
+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 log::debug;
if !enabled {
return;
}
// configure colors for the whole line
let colors_line = ColoredLevelConfig::new()
.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 {
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 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!(
"{}",
+8 -8
View File
@@ -46,7 +46,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
/// ```ignore
/// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
/// let mut vdom = VirtualDom::new(App);
/// vdom.rebuild_in_place();
/// vdom.rebuild();
///
/// let renderer = TextRenderer::new(&vdom);
/// 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 {
match &node.kind {
match &node {
VNode::Text(text) => {
if self.cfg.indent {
for _ in 0..il {
@@ -286,28 +286,28 @@ mod tests {
#[test]
fn to_string_works() {
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));
}
#[test]
fn hydration() {
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)));
}
#[test]
fn nested() {
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));
}
#[test]
fn 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));
}
@@ -319,7 +319,7 @@ mod tests {
let mut file = File::create("index.html").unwrap();
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!(
"{}",
@@ -337,7 +337,7 @@ mod tests {
};
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));
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ fn main() {
}
static App: FC<()> = |cx| {
let mut count = use_state(cx, || 0);
let mut count = use_state(cx, || 3);
cx.render(rsx! {
div {
+20 -20
View File
@@ -84,8 +84,8 @@ impl WebsysDom {
DomEdit::PushRoot { id: root } => self.push(root),
DomEdit::PopRoot => self.pop(),
DomEdit::AppendChildren { many } => self.append_children(many),
DomEdit::ReplaceWith { n, m } => self.replace_with(n, m),
DomEdit::Remove => self.remove(),
DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
DomEdit::Remove { root } => self.remove(root),
DomEdit::RemoveAllChildren => self.remove_all_children(),
DomEdit::CreateTextNode { text, id } => self.create_text_node(text, 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::RemoveAttribute { name } => self.remove_attribute(name),
DomEdit::InsertAfter { n } => self.insert_after(n),
DomEdit::InsertBefore { n } => self.insert_before(n),
DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
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`]");
let mut new_nodes = vec![];
@@ -162,19 +162,11 @@ impl WebsysDom {
new_nodes.push(self.stack.pop());
}
let mut old_nodes = vec![];
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 old = self.nodes[root as usize].as_ref().unwrap();
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();
// let arr = js_sys::Array::from();
// TODO: use different-sized replace withs
@@ -205,9 +197,17 @@ impl WebsysDom {
// self.stack.push(new_node);
}
fn remove(&mut self) {
fn remove(&mut self, root: u64) {
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) {
@@ -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![];
for _ in 0..n {
new_nodes.push(self.stack.pop());
@@ -380,7 +380,7 @@ impl WebsysDom {
// 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 root = self
.stack
+64 -41
View File
@@ -1,30 +1,56 @@
//! Dioxus WebSys
//! --------------
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
/*
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
*/
//!
//! ## Overview
//! ------------
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using WebSys. This web render for
//! Dioxus is one of the more advanced renderers, supporting:
//! - idle work
//! - animations
//! - jank-free rendering
//! - noderefs
//! - controlled components
//! - re-hydration
//! - and more.
//!
//! The actual implementation is farily thin, with the heavy lifting happening inside the Dioxus Core crate.
//!
//! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
//! validation of websys-specific features and not the general use of Dioxus.
//!
//! ## RequestAnimationFrame and RequestIdleCallback
//! ------------------------------------------------
//! React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
//! running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
//! main thread.
//!
//! 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;
@@ -103,20 +129,22 @@ pub async fn run_with_props<T: Properties + 'static>(
) -> Result<()> {
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 tasks = dom.get_event_sender();
let sender_callback = Rc::new(move |event| tasks.unbounded_send(event).unwrap());
// initialize the virtualdom first
// if cfg.hydrate {
// dom.rebuild_in_place()?;
// }
let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
let mut websys_dom = dom::WebsysDom::new(
root_el,
cfg,
Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
);
let mut mutations = dom.rebuild().unwrap();
log::info!("Mutations: {:#?}", mutations);
// 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 {
let deadline = gloo_timers::future::TimeoutFuture::new(16);
@@ -127,8 +155,3 @@ pub async fn run_with_props<T: Properties + 'static>(
Ok(())
}
struct HydrationNode {
id: usize,
node: Node,
}