Merge branch 'upstream' into add-fuzzing-to-core

This commit is contained in:
Evan Almloff committed 2023-01-12 15:17:20 -06:00
commit 75932a43e0
124 files changed
+2138 -1620

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.checkOnSave.allTargets": false
}
+1 -1
View File
@@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
keywords = ["dom", "ui", "gui", "react"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+37 -26
View File
@@ -13,7 +13,7 @@ dioxus-core is a fast and featureful VirtualDom implementation written in and fo
- Error boundaries through the `anyhow` crate
- Customizable memoization
If just starting out, check out the Guides first.
If you are just starting, check out the Guides first.
# General Theory
@@ -21,7 +21,7 @@ The dioxus-core `VirtualDom` object is built around the concept of a `Template`.
Each component in the VirtualDom works as a dedicated render loop where re-renders are triggered by events external to the VirtualDom, or from the components themselves.
When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object, and calculates the differences of the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and new layout, Dioxus will write modifications to the `Mutations` object.
When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object and calculates the differences between the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and the new layout, Dioxus will write modifications to the `Mutations` object.
Dioxus expects the target renderer to save its nodes in a list. Each element is given a numerical ID which can be used to directly index into that list for O(1) lookups.
@@ -33,37 +33,48 @@ The `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler s
First, start with your app:
```rust, ignore
```rust
# use dioxus::core::Mutations;
use dioxus::prelude::*;
// First, declare a root component
fn app(cx: Scope) -> Element {
cx.render(rsx!( div { "hello world" } ))
cx.render(rsx!{
div { "hello world" }
})
}
fn main() {
// Next, create a new VirtualDom using this app as the root component.
let mut dom = VirtualDom::new(app);
// The initial render of the dom will generate a stream of edits for the real dom to apply
let mutations = dom.rebuild();
// Somehow, you can apply these edits to the real dom
apply_edits_to_real_dom(mutations);
}
# fn apply_edits_to_real_dom(mutations: Mutations) {}
```
Then, we'll want to create a new VirtualDom using this app as the root component.
```rust, ignore
let mut dom = VirtualDom::new(app);
```
To build the app into a stream of mutations, we'll use [`VirtualDom::rebuild`]:
```rust, ignore
let mutations = dom.rebuild();
apply_edits_to_real_dom(mutations);
```
We can then wait for any asynchronous components or pending futures using the `wait_for_work()` method. If we have a deadline, then we can use render_with_deadline instead:
```rust
# #![allow(unused)]
# use dioxus::prelude::*;
```rust, ignore
# use std::time::Duration;
# async fn wait(mut dom: VirtualDom) {
// Wait for the dom to be marked dirty internally
dom.wait_for_work().await;
// Or wait for a deadline and then collect edits
dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
# }
```
If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
If an event occurs from outside the VirtualDom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
```rust, ignore
loop {
@@ -88,18 +99,18 @@ Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus
- React: hooks, concurrency, suspense
- Dodrio: bump allocation, double buffering, and some diffing architecture
Dioxus-core leverages some really cool techniques and hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features:
Dioxus-core hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features:
- managed lifetimes for borrowed data
- placeholder approach for suspended vnodes
- fiber/interruptible diffing algorithm
- custom memory allocator for vnodes and all text content
- custom memory allocator for VNodes and all text content
- support for fragments w/ lazy normalization
- slab allocator for scopes
- mirrored-slab approach for remote vdoms
- mirrored-slab approach for remote VirtualDoms
- dedicated subtrees for rendering into separate contexts from the same app
There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, it is possible that zero allocations will need to be performed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, no allocations may be needed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
All in all, Dioxus treats memory as a valuable resource. Combined with the memory-efficient footprint of Wasm compilation, Dioxus apps can scale to thousands of components and still stay snappy.
@@ -112,5 +123,5 @@ The final implementation of Dioxus must:
- Be concurrent. Components should be able to pause rendering to let the screen paint the next frame.
- Be disconnected from a specific renderer (no WebSys dependency in the core crate).
- Support server-side-rendering (SSR). VNodes should render to a string that can be served via a web server.
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
- Be "live". Components should be able to be both server-rendered and client rendered without needing frontend APIs.
- Be modular. Components and hooks should work anywhere without worrying about the target platform.
+12 -3
View File
@@ -1,9 +1,9 @@
use crate::nodes::RenderReturn;
use bumpalo::Bump;
use std::cell::Cell;
use std::cell::{Cell, UnsafeCell};
pub(crate) struct BumpFrame {
pub bump: Bump,
pub bump: UnsafeCell<Bump>,
pub node: Cell<*const RenderReturn<'static>>,
}
@@ -11,7 +11,7 @@ impl BumpFrame {
pub(crate) fn new(capacity: usize) -> Self {
let bump = Bump::with_capacity(capacity);
Self {
bump,
bump: UnsafeCell::new(bump),
node: Cell::new(std::ptr::null()),
}
}
@@ -26,4 +26,13 @@ impl BumpFrame {
unsafe { std::mem::transmute(&*node) }
}
pub(crate) fn bump(&self) -> &Bump {
unsafe { &*self.bump.get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
unsafe { &mut *self.bump.get() }
}
}
+25 -21
View File
@@ -81,9 +81,7 @@ impl<'b> VirtualDom {
// The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways
if !self.templates.contains_key(&node.template.get().name) {
self.register_template(node.template.get());
}
self.register_template(node.template.get());
// we know that this will generate at least one mutation per node
self.mutations
@@ -389,30 +387,36 @@ impl<'b> VirtualDom {
/// Insert a new template into the VirtualDom's template registry
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
// First, make sure we mark the template as seen, regardless if we process it
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
let byte_index = byte_index.parse::<usize>().unwrap();
// if hot reloading is enabled, then we need to check for a template that has overriten this one
#[cfg(debug_assertions)]
if let Some(mut new_template) = self
// First, check if we've already seen this template
if self
.templates
.get_mut(path)
.and_then(|map| map.remove(&usize::MAX))
.get(&path)
.filter(|set| set.contains_key(&byte_index))
.is_none()
{
// the byte index of the hot reloaded template could be different
new_template.name = template.name;
template = new_template;
}
// if hot reloading is enabled, then we need to check for a template that has overriten this one
#[cfg(debug_assertions)]
if let Some(mut new_template) = self
.templates
.get_mut(path)
.and_then(|map| map.remove(&usize::MAX))
{
// the byte index of the hot reloaded template could be different
new_template.name = template.name;
template = new_template;
}
self.templates
.entry(path)
.or_default()
.insert(byte_index, template);
self.templates
.entry(path)
.or_default()
.insert(byte_index, template);
// If it's all dynamic nodes, then we don't need to register it
if !template.is_completely_dynamic() {
self.mutations.templates.push(template);
// If it's all dynamic nodes, then we don't need to register it
if !template.is_completely_dynamic() {
self.mutations.templates.push(template);
}
}
}
+1
View File
@@ -11,6 +11,7 @@
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
#[allow(unused_imports)]
use smallbox::{smallbox, space::S16, SmallBox};
use crate::{innerlude::VNode, ScopeState};
-1
View File
@@ -819,7 +819,6 @@ impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
}
// Note that we're using the E as a generic but this is never crafted anyways.
#[doc(hidden)]
pub struct FromNodeIterator;
impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
where
+4 -3
View File
@@ -31,7 +31,8 @@ impl VirtualDom {
// If the task completes...
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
// Remove it from the scope so we dont try to double drop it when the scope dropes
self.scopes[task.scope.0].spawned_tasks.remove(&id);
let scope = &self.scopes[task.scope.0];
scope.spawned_tasks.borrow_mut().remove(&id);
// Remove it from the scheduler
tasks.try_remove(id.0);
@@ -63,10 +64,10 @@ impl VirtualDom {
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
let scope = &mut self.scopes[scope_id.0];
let scope = &self.scopes[scope_id.0];
let arena = scope.current_frame();
let ret = arena.bump.alloc(match new_nodes {
let ret = arena.bump().alloc(match new_nodes {
Some(new) => RenderReturn::Ready(new),
None => RenderReturn::default(),
});
+7 -16
View File
@@ -7,7 +7,6 @@ use crate::{
scopes::{ScopeId, ScopeState},
virtual_dom::VirtualDom,
};
use bumpalo::Bump;
use futures_util::FutureExt;
use std::{
mem,
@@ -48,11 +47,10 @@ impl VirtualDom {
}))
}
fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
self.scope_stack
.last()
.copied()
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> {
let id = self.scope_stack.last().copied()?;
let scope = self.scopes.get(id.0)?;
Some(scope.as_ref())
}
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
@@ -62,17 +60,10 @@ impl VirtualDom {
self.ensure_drop_safety(scope_id);
let mut new_nodes = unsafe {
let scope = self.scopes[scope_id.0].as_mut();
self.scopes[scope_id.0].previous_frame().bump_mut().reset();
// if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient
if scope.previous_frame().bump.allocated_bytes() == 0 {
scope.previous_frame_mut().bump =
Bump::with_capacity(scope.current_frame().bump.allocated_bytes());
} else {
scope.previous_frame_mut().bump.reset();
}
let scope = &self.scopes[scope_id.0];
// Make sure to reset the hook counter so we give out hooks in the right order
scope.hook_idx.set(0);
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
@@ -142,7 +133,7 @@ impl VirtualDom {
let frame = scope.previous_frame();
// set the new head of the bump frame
let allocated = &*frame.bump.alloc(new_nodes);
let allocated = &*frame.bump().alloc(new_nodes);
frame.node.set(allocated);
// And move the render generation forward by one
+4 -12
View File
@@ -73,7 +73,7 @@ pub struct ScopeState {
pub(crate) node_arena_1: BumpFrame,
pub(crate) node_arena_2: BumpFrame,
pub(crate) parent: Option<*mut ScopeState>,
pub(crate) parent: Option<*const ScopeState>,
pub(crate) id: ScopeId,
pub(crate) height: u32,
@@ -85,7 +85,7 @@ pub struct ScopeState {
pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
pub(crate) tasks: Rc<Scheduler>,
pub(crate) spawned_tasks: FxHashSet<TaskId>,
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
@@ -111,14 +111,6 @@ impl<'src> ScopeState {
}
}
pub(crate) fn previous_frame_mut(&mut self) -> &mut BumpFrame {
match self.render_cnt.get() % 2 {
1 => &mut self.node_arena_1,
0 => &mut self.node_arena_2,
_ => unreachable!(),
}
}
/// Get the name of this component
pub fn name(&self) -> &str {
self.name
@@ -139,7 +131,7 @@ impl<'src> ScopeState {
/// If you need to allocate items that need to be dropped, use bumpalo's box.
pub fn bump(&self) -> &Bump {
// note that this is actually the previous frame since we use that as scratch space while the component is rendering
&self.previous_frame().bump
self.previous_frame().bump()
}
/// Get a handle to the currently active head node arena for this Scope
@@ -556,7 +548,7 @@ impl<'src> ScopeState {
#[allow(clippy::mut_from_ref)]
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
let cur_hook = self.hook_idx.get();
let mut hook_list = self.hook_list.borrow_mut();
let mut hook_list = self.hook_list.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
if cur_hook >= hook_list.len() {
hook_list.push(self.hook_arena.alloc(initializer()));
+37 -4
View File
@@ -24,7 +24,9 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
///
/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
///
/// ```rust, ignore
/// ```rust
/// # use dioxus::prelude::*;
///
/// #[derive(Props, PartialEq)]
/// struct AppProps {
/// title: String
@@ -39,7 +41,17 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
///
/// Components may be composed to make complex apps.
///
/// ```rust, ignore
/// ```rust
/// # #![allow(unused)]
/// # use dioxus::prelude::*;
///
/// # #[derive(Props, PartialEq)]
/// # struct AppProps {
/// # title: String
/// # }
///
/// static ROUTES: &str = "";
///
/// fn App(cx: Scope<AppProps>) -> Element {
/// cx.render(rsx!(
/// NavBar { routes: ROUTES }
@@ -47,12 +59,33 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
/// Footer {}
/// ))
/// }
///
/// #[inline_props]
/// fn NavBar(cx: Scope, routes: &'static str) -> Element {
/// cx.render(rsx! {
/// div { "Routes: {routes}" }
/// })
/// }
///
/// fn Footer(cx: Scope) -> Element {
/// cx.render(rsx! { div { "Footer" } })
/// }
///
/// #[inline_props]
/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
/// cx.render(rsx! {
/// div { id: "title", children }
/// })
/// }
/// ```
///
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
/// draw the UI.
///
/// ```rust, ignore
/// ```rust
/// # use dioxus::prelude::*;
/// # fn App(cx: Scope) -> Element { cx.render(rsx! { div {} }) }
///
/// let mut vdom = VirtualDom::new(App);
/// let edits = vdom.rebuild();
/// ```
@@ -549,7 +582,7 @@ impl VirtualDom {
loop {
// first, unload any complete suspense trees
for finished_fiber in self.finished_fibers.drain(..) {
let scope = &mut self.scopes[finished_fiber.0];
let scope = &self.scopes[finished_fiber.0];
let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
self.mutations
+1 -1
View File
@@ -5,7 +5,7 @@
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation::*};
use dioxus::prelude::*;
use dioxus_core::{AttributeValue, BorrowedAttributeValue};
use dioxus_core::BorrowedAttributeValue;
#[test]
fn attrs_cycle() {
+6 -30
View File
@@ -1,13 +1,11 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use futures_util::Future;
#[test]
fn catches_panic() {
let mut dom = VirtualDom::new(app);
let a = dom.rebuild();
dbg!(a);
_ = dom.rebuild();
}
fn app(cx: Scope) -> Element {
@@ -16,43 +14,21 @@ fn app(cx: Scope) -> Element {
h1 { "Title" }
NoneChild {}
ThrowChild {}
}
})
}
fn NoneChild(cx: Scope) -> Element {
fn NoneChild(_cx: Scope) -> Element {
None
}
fn PanicChild(cx: Scope) -> Element {
panic!("Rendering panicked for whatever reason");
cx.render(rsx! {
h1 { "It works!" }
})
}
fn ThrowChild(cx: Scope) -> Element {
cx.throw(std::io::Error::new(std::io::ErrorKind::AddrInUse, "asd"))?;
let g: i32 = "123123".parse().throw(cx)?;
let _g: i32 = "123123".parse().throw(cx)?;
cx.render(rsx! {
div {}
})
}
fn custom_allocator(cx: Scope) -> Element {
let g = String::new();
let p = g.as_str();
let g2 = cx.use_hook(|| 123);
// cx.spawn(async move {
// //
// // println!("Thig is {p}");
// });
None
}
+67 -202
View File
@@ -137,213 +137,78 @@ fn free_works_on_root_hooks() {
assert_eq!(Rc::strong_count(&ptr), 1);
}
// #[test]
// fn old_props_arent_stale() {
// fn app(cx: Scope) -> Element {
// dbg!("rendering parent");
// let cnt = cx.use_hook(|| 0);
// *cnt += 1;
#[test]
fn supports_async() {
use std::time::Duration;
use tokio::time::sleep;
// if *cnt == 1 {
// render!(div { Child { a: "abcdef".to_string() } })
// } else {
// render!(div { Child { a: "abcdef".to_string() } })
// }
// }
fn app(cx: Scope) -> Element {
let colors = use_state(cx, || vec!["green", "blue", "red"]);
let padding = use_state(cx, || 10);
// #[derive(Props, PartialEq)]
// struct ChildProps {
// a: String,
// }
// fn Child(cx: Scope<ChildProps>) -> Element {
// dbg!("rendering child", &cx.props.a);
// render!(div { "child {cx.props.a}" })
// }
use_effect(cx, colors, |colors| async move {
sleep(Duration::from_millis(1000)).await;
colors.with_mut(|colors| colors.reverse());
});
// let mut dom = new_dom(app, ());
// let _ = dom.rebuild();
use_effect(cx, padding, |padding| async move {
sleep(Duration::from_millis(10)).await;
padding.with_mut(|padding| {
if *padding < 65 {
*padding += 1;
} else {
*padding = 5;
}
});
});
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
let big = colors[0];
let mid = colors[1];
let small = colors[2];
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
cx.render(rsx! {
div {
background: "{big}",
height: "stretch",
width: "stretch",
padding: "50",
label {
"hello",
}
div {
background: "{mid}",
height: "auto",
width: "stretch",
padding: "{padding}",
label {
"World",
}
div {
background: "{small}",
height: "auto",
width: "stretch",
padding: "20",
label {
"ddddddd",
}
}
},
}
})
}
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap();
// dbg!("forcing update to child");
rt.block_on(async {
let mut dom = VirtualDom::new(app);
let _ = dom.rebuild();
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
// dom.work_with_deadline(|| false);
// }
// #[test]
// fn basic() {
// fn app(cx: Scope) -> Element {
// render!(div {
// Child { a: "abcdef".to_string() }
// })
// }
// #[derive(Props, PartialEq)]
// struct ChildProps {
// a: String,
// }
// fn Child(cx: Scope<ChildProps>) -> Element {
// dbg!("rendering child", &cx.props.a);
// render!(div { "child {cx.props.a}" })
// }
// let mut dom = new_dom(app, ());
// let _ = dom.rebuild();
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// }
// #[test]
// fn leak_thru_children() {
// fn app(cx: Scope) -> Element {
// cx.render(rsx! {
// Child {
// name: "asd".to_string(),
// }
// });
// cx.render(rsx! {
// div {}
// })
// }
// #[inline_props]
// fn Child(cx: Scope, name: String) -> Element {
// render!(div { "child {name}" })
// }
// let mut dom = new_dom(app, ());
// let _ = dom.rebuild();
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// }
// #[test]
// fn test_pass_thru() {
// #[inline_props]
// fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element {
// cx.render(rsx! {
// header {
// nav { children }
// }
// })
// }
// fn NavMenu(cx: Scope) -> Element {
// render!( NavBrand {}
// div {
// NavStart {}
// NavEnd {}
// }
// )
// }
// fn NavBrand(cx: Scope) -> Element {
// render!(div {})
// }
// fn NavStart(cx: Scope) -> Element {
// render!(div {})
// }
// fn NavEnd(cx: Scope) -> Element {
// render!(div {})
// }
// #[inline_props]
// fn MainContainer<'a>(
// cx: Scope,
// nav: Element<'a>,
// body: Element<'a>,
// footer: Element<'a>,
// ) -> Element {
// cx.render(rsx! {
// div {
// class: "columns is-mobile",
// div {
// class: "column is-full",
// nav,
// body,
// footer,
// }
// }
// })
// }
// fn app(cx: Scope) -> Element {
// let nav = cx.render(rsx! {
// NavContainer {
// NavMenu {}
// }
// });
// let body = cx.render(rsx! {
// div {}
// });
// let footer = cx.render(rsx! {
// div {}
// });
// cx.render(rsx! {
// MainContainer {
// nav: nav,
// body: body,
// footer: footer,
// }
// })
// }
// let mut dom = new_dom(app, ());
// let _ = dom.rebuild();
// for _ in 0..40 {
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
// dom.work_with_deadline(|| false);
// }
// }
for _ in 0..10 {
dom.wait_for_work().await;
let _edits = dom.render_immediate();
}
});
}
+40 -34
View File
@@ -5,42 +5,48 @@ use std::future::IntoFuture;
use std::rc::Rc;
use std::time::Duration;
#[tokio::test]
async fn it_works() {
let mut dom = VirtualDom::new(app);
{
let mutations = dom.rebuild().santize();
// We should at least get the top-level template in before pausing for the children
// note: we dont test template edits anymore
// assert_eq!(
// mutations.templates,
// [
// CreateElement { name: "div" },
// CreateStaticText { value: "Waiting for child..." },
// CreateStaticPlaceholder,
// AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 }
// ]
// );
// And we should load it in and assign the placeholder properly
assert_eq!(
mutations.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
// can we even?
AssignId { path: &[1], id: ElementId(3) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
}
#[test]
fn it_works() {
// wait just a moment, not enough time for the boundary to resolve
dom.wait_for_work().await;
tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
.block_on(async {
let mut dom = VirtualDom::new(app);
{
let mutations = dom.rebuild().santize();
// We should at least get the top-level template in before pausing for the children
// note: we dont test template edits anymore
// assert_eq!(
// mutations.templates,
// [
// CreateElement { name: "div" },
// CreateStaticText { value: "Waiting for child..." },
// CreateStaticPlaceholder,
// AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 }
// ]
// );
// And we should load it in and assign the placeholder properly
assert_eq!(
mutations.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
// can we even?
AssignId { path: &[1], id: ElementId(3) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
}
dom.wait_for_work().await;
});
}
fn app(cx: Scope) -> Element {