add simple suspense methods
This commit is contained in:
11 files changed
+52
-189
No files matched your search
@@ -5,10 +5,9 @@ use crate::mutations::Mutation::*;
|
|||||||
use crate::nodes::VNode;
|
use crate::nodes::VNode;
|
||||||
use crate::nodes::{DynamicNode, TemplateNode};
|
use crate::nodes::{DynamicNode, TemplateNode};
|
||||||
use crate::virtual_dom::VirtualDom;
|
use crate::virtual_dom::VirtualDom;
|
||||||
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext, Template};
|
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, Template};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::rc::Rc;
|
|
||||||
use TemplateNode::*;
|
use TemplateNode::*;
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
@@ -445,7 +444,7 @@ impl<'b> VirtualDom {
|
|||||||
match node {
|
match node {
|
||||||
Text(text) => self.create_dynamic_text(template, text, idx),
|
Text(text) => self.create_dynamic_text(template, text, idx),
|
||||||
Placeholder(place) => self.create_placeholder(place, template, idx),
|
Placeholder(place) => self.create_placeholder(place, template, idx),
|
||||||
Component(component) => self.create_component_node(template, component, idx),
|
Component(component) => self.create_component_node(template, component),
|
||||||
Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
|
Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,7 +501,6 @@ impl<'b> VirtualDom {
|
|||||||
&mut self,
|
&mut self,
|
||||||
template: &'b VNode<'b>,
|
template: &'b VNode<'b>,
|
||||||
component: &'b VComponent<'b>,
|
component: &'b VComponent<'b>,
|
||||||
idx: usize,
|
|
||||||
) -> usize {
|
) -> usize {
|
||||||
use RenderReturn::*;
|
use RenderReturn::*;
|
||||||
|
|
||||||
@@ -512,7 +510,8 @@ impl<'b> VirtualDom {
|
|||||||
component.scope.set(Some(scope));
|
component.scope.set(Some(scope));
|
||||||
|
|
||||||
match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
|
match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
|
||||||
Ready(t) => self.mount_component(scope, template, t, idx),
|
// Create the component's root element
|
||||||
|
Ready(t) => self.create_scope(scope, t),
|
||||||
Aborted(t) => self.mount_aborted(template, t),
|
Aborted(t) => self.mount_aborted(template, t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,60 +528,6 @@ impl<'b> VirtualDom {
|
|||||||
.unwrap_or_else(|| component.scope.get().unwrap())
|
.unwrap_or_else(|| component.scope.get().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_component(
|
|
||||||
&mut self,
|
|
||||||
scope: ScopeId,
|
|
||||||
parent: &'b VNode<'b>,
|
|
||||||
new: &'b VNode<'b>,
|
|
||||||
idx: usize,
|
|
||||||
) -> usize {
|
|
||||||
// Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary
|
|
||||||
// is encountered
|
|
||||||
let mutations_to_this_point = self.mutations.edits.len();
|
|
||||||
|
|
||||||
// Create the component's root element
|
|
||||||
let created = self.create_scope(scope, new);
|
|
||||||
|
|
||||||
// If there are no suspense leaves below us, then just don't bother checking anything suspense related
|
|
||||||
if self.collected_leaves.is_empty() {
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense
|
|
||||||
let boundary = match self.scopes[scope].has_context::<Rc<SuspenseContext>>() {
|
|
||||||
Some(boundary) => boundary,
|
|
||||||
_ => return created,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
|
|
||||||
let new_id = self.next_element(new, parent.template.get().node_paths[idx]);
|
|
||||||
|
|
||||||
// Now connect everything to the boundary
|
|
||||||
self.scopes[scope].placeholder.set(Some(new_id));
|
|
||||||
|
|
||||||
// This involves breaking off the mutations to this point, and then creating a new placeholder for the boundary
|
|
||||||
// Note that we break off dynamic mutations only - since static mutations aren't rendered immediately
|
|
||||||
let split_off = unsafe {
|
|
||||||
std::mem::transmute::<Vec<Mutation>, Vec<Mutation>>(
|
|
||||||
self.mutations.edits.split_off(mutations_to_this_point),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
boundary.mutations.borrow_mut().edits.extend(split_off);
|
|
||||||
boundary.created_on_stack.set(created);
|
|
||||||
boundary
|
|
||||||
.waiting_on
|
|
||||||
.borrow_mut()
|
|
||||||
.extend(self.collected_leaves.drain(..));
|
|
||||||
|
|
||||||
// Now assign the placeholder in the DOM
|
|
||||||
self.mutations.push(AssignId {
|
|
||||||
id: new_id,
|
|
||||||
path: &parent.template.get().node_paths[idx][1..],
|
|
||||||
});
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize {
|
fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize {
|
||||||
let id = self.next_element(parent, &[]);
|
let id = self.next_element(parent, &[]);
|
||||||
self.mutations.push(Mutation::CreatePlaceholder { id });
|
self.mutations.push(Mutation::CreatePlaceholder { id });
|
||||||
|
|||||||
@@ -124,9 +124,8 @@ impl<'b> VirtualDom {
|
|||||||
.dynamic_nodes
|
.dynamic_nodes
|
||||||
.iter()
|
.iter()
|
||||||
.zip(right_template.dynamic_nodes.iter())
|
.zip(right_template.dynamic_nodes.iter())
|
||||||
.enumerate()
|
.for_each(|(left_node, right_node)| {
|
||||||
.for_each(|(idx, (left_node, right_node))| {
|
self.diff_dynamic_node(left_node, right_node, right_template);
|
||||||
self.diff_dynamic_node(left_node, right_node, right_template, idx);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure the roots get transferred over while we're here
|
// Make sure the roots get transferred over while we're here
|
||||||
@@ -145,13 +144,12 @@ impl<'b> VirtualDom {
|
|||||||
left_node: &'b DynamicNode<'b>,
|
left_node: &'b DynamicNode<'b>,
|
||||||
right_node: &'b DynamicNode<'b>,
|
right_node: &'b DynamicNode<'b>,
|
||||||
node: &'b VNode<'b>,
|
node: &'b VNode<'b>,
|
||||||
idx: usize,
|
|
||||||
) {
|
) {
|
||||||
match (left_node, right_node) {
|
match (left_node, right_node) {
|
||||||
(Text(left), Text(right)) => self.diff_vtext(left, right, node),
|
(Text(left), Text(right)) => self.diff_vtext(left, right, node),
|
||||||
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
|
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
|
||||||
(Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
|
(Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
|
||||||
(Component(left), Component(right)) => self.diff_vcomponent(left, right, node, idx),
|
(Component(left), Component(right)) => self.diff_vcomponent(left, right, node),
|
||||||
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right),
|
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right),
|
||||||
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
|
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
|
||||||
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
|
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
|
||||||
@@ -175,7 +173,6 @@ impl<'b> VirtualDom {
|
|||||||
left: &'b VComponent<'b>,
|
left: &'b VComponent<'b>,
|
||||||
right: &'b VComponent<'b>,
|
right: &'b VComponent<'b>,
|
||||||
right_template: &'b VNode<'b>,
|
right_template: &'b VNode<'b>,
|
||||||
idx: usize,
|
|
||||||
) {
|
) {
|
||||||
if std::ptr::eq(left, right) {
|
if std::ptr::eq(left, right) {
|
||||||
return;
|
return;
|
||||||
@@ -183,7 +180,7 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
// Replace components that have different render fns
|
// Replace components that have different render fns
|
||||||
if left.render_fn != right.render_fn {
|
if left.render_fn != right.render_fn {
|
||||||
return self.replace_vcomponent(right_template, right, idx, left);
|
return self.replace_vcomponent(right_template, right, left);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the new vcomponent has the right scopeid associated to it
|
// Make sure the new vcomponent has the right scopeid associated to it
|
||||||
@@ -220,10 +217,9 @@ impl<'b> VirtualDom {
|
|||||||
&mut self,
|
&mut self,
|
||||||
right_template: &'b VNode<'b>,
|
right_template: &'b VNode<'b>,
|
||||||
right: &'b VComponent<'b>,
|
right: &'b VComponent<'b>,
|
||||||
idx: usize,
|
|
||||||
left: &'b VComponent<'b>,
|
left: &'b VComponent<'b>,
|
||||||
) {
|
) {
|
||||||
let m = self.create_component_node(right_template, right, idx);
|
let m = self.create_component_node(right_template, right);
|
||||||
|
|
||||||
let pre_edits = self.mutations.edits.len();
|
let pre_edits = self.mutations.edits.len();
|
||||||
|
|
||||||
@@ -282,8 +278,7 @@ impl<'b> VirtualDom {
|
|||||||
None => self.replace(left, [right]),
|
None => self.replace(left, [right]),
|
||||||
Some(components) => components
|
Some(components) => components
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.for_each(|(l, r)| self.diff_vcomponent(l, r, right)),
|
||||||
.for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
|
fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
|
||||||
Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
|
Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
|
||||||
Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
|
Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped,
|
||||||
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
|
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText,
|
||||||
VirtualDom,
|
VirtualDom,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use crate::ScopeId;
|
use crate::ScopeId;
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
|
|
||||||
mod suspense;
|
|
||||||
mod task;
|
mod task;
|
||||||
mod wait;
|
mod wait;
|
||||||
|
|
||||||
pub use suspense::*;
|
|
||||||
pub use task::*;
|
pub use task::*;
|
||||||
|
|
||||||
/// The type of message that can be sent to the scheduler.
|
/// The type of message that can be sent to the scheduler.
|
||||||
|
|||||||
@@ -11,17 +11,10 @@ use std::{
|
|||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An ID representing an ongoing suspended component
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
|
||||||
pub(crate) struct SuspenseId(pub usize);
|
|
||||||
|
|
||||||
/// A boundary in the VirtualDom that captures all suspended components below it
|
/// A boundary in the VirtualDom that captures all suspended components below it
|
||||||
pub struct SuspenseContext {
|
pub struct SuspenseContext {
|
||||||
pub(crate) id: ScopeId,
|
pub(crate) id: ScopeId,
|
||||||
pub(crate) waiting_on: RefCell<HashSet<SuspenseId>>,
|
pub(crate) waiting_on: RefCell<HashSet<ScopeId>>,
|
||||||
pub(crate) mutations: RefCell<Mutations<'static>>,
|
|
||||||
pub(crate) placeholder: Cell<Option<ElementId>>,
|
|
||||||
pub(crate) created_on_stack: Cell<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SuspenseContext {
|
impl SuspenseContext {
|
||||||
@@ -30,9 +23,10 @@ impl SuspenseContext {
|
|||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
waiting_on: Default::default(),
|
waiting_on: Default::default(),
|
||||||
mutations: RefCell::new(Mutations::default()),
|
|
||||||
placeholder: Cell::new(None),
|
|
||||||
created_on_stack: Cell::new(0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mark_suspend(&self, id: ScopeId) {
|
||||||
|
self.waiting_on.borrow_mut().insert(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{innerlude::SuspenseContext, ScopeId, TaskId, VirtualDom};
|
use crate::{TaskId, VirtualDom};
|
||||||
use std::{rc::Rc, task::Context};
|
use std::task::Context;
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
/// Handle notifications by tasks inside the scheduler
|
/// Handle notifications by tasks inside the scheduler
|
||||||
@@ -27,10 +27,4 @@ impl VirtualDom {
|
|||||||
tasks.try_remove(id.0);
|
tasks.try_remove(id.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc<SuspenseContext> {
|
|
||||||
self.scopes[id]
|
|
||||||
.consume_context::<Rc<SuspenseContext>>()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,10 @@ impl VirtualDom {
|
|||||||
name,
|
name,
|
||||||
props: Some(props),
|
props: Some(props),
|
||||||
tasks: self.scheduler.clone(),
|
tasks: self.scheduler.clone(),
|
||||||
placeholder: Default::default(),
|
|
||||||
node_arena_1: BumpFrame::new(0),
|
node_arena_1: BumpFrame::new(0),
|
||||||
node_arena_2: BumpFrame::new(0),
|
node_arena_2: BumpFrame::new(0),
|
||||||
spawned_tasks: Default::default(),
|
spawned_tasks: Default::default(),
|
||||||
|
suspended: Default::default(),
|
||||||
render_cnt: Default::default(),
|
render_cnt: Default::default(),
|
||||||
hooks: Default::default(),
|
hooks: Default::default(),
|
||||||
hook_idx: Default::default(),
|
hook_idx: Default::default(),
|
||||||
@@ -54,6 +54,7 @@ impl VirtualDom {
|
|||||||
self.scopes[scope_id].previous_frame().bump_mut().reset();
|
self.scopes[scope_id].previous_frame().bump_mut().reset();
|
||||||
|
|
||||||
let scope = &self.scopes[scope_id];
|
let scope = &self.scopes[scope_id];
|
||||||
|
scope.suspended.set(false);
|
||||||
|
|
||||||
scope.hook_idx.set(0);
|
scope.hook_idx.set(0);
|
||||||
|
|
||||||
@@ -82,6 +83,10 @@ impl VirtualDom {
|
|||||||
id: scope.id,
|
id: scope.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if scope.suspended.get() {
|
||||||
|
self.suspended_scopes.insert(scope.id);
|
||||||
|
}
|
||||||
|
|
||||||
// rebind the lifetime now that its stored internally
|
// rebind the lifetime now that its stored internally
|
||||||
unsafe { allocated.extend_lifetime_ref() }
|
unsafe { allocated.extend_lifetime_ref() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
any_props::VProps,
|
any_props::VProps,
|
||||||
arena::ElementId,
|
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
||||||
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
|
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
|
||||||
@@ -169,6 +168,7 @@ pub struct ScopeState {
|
|||||||
pub(crate) id: ScopeId,
|
pub(crate) id: ScopeId,
|
||||||
|
|
||||||
pub(crate) height: u32,
|
pub(crate) height: u32,
|
||||||
|
pub(crate) suspended: Cell<bool>,
|
||||||
|
|
||||||
pub(crate) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
|
pub(crate) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
|
||||||
pub(crate) hook_idx: Cell<usize>,
|
pub(crate) hook_idx: Cell<usize>,
|
||||||
@@ -182,7 +182,6 @@ pub struct ScopeState {
|
|||||||
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
|
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
|
||||||
|
|
||||||
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
|
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
|
||||||
pub(crate) placeholder: Cell<Option<ElementId>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> ScopeState {
|
impl<'src> ScopeState {
|
||||||
@@ -655,6 +654,12 @@ impl<'src> ScopeState {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark this component as suspended and then return None
|
||||||
|
pub fn suspend(&self) -> Option<()> {
|
||||||
|
self.suspended.set(true);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Store a value between renders. The foundational hook for all other hooks.
|
/// Store a value between renders. The foundational hook for all other hooks.
|
||||||
///
|
///
|
||||||
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
|
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ use crate::{
|
|||||||
mutations::Mutation,
|
mutations::Mutation,
|
||||||
nodes::RenderReturn,
|
nodes::RenderReturn,
|
||||||
nodes::{Template, TemplateId},
|
nodes::{Template, TemplateId},
|
||||||
scheduler::SuspenseId,
|
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
AttributeValue, Element, Event, Scope, SuspenseContext,
|
AttributeValue, Element, Event, Scope,
|
||||||
};
|
};
|
||||||
use futures_util::{pin_mut, StreamExt};
|
use futures_util::{pin_mut, StreamExt};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
|
||||||
|
|
||||||
/// A virtual node system that progresses user events and diffs UI trees.
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -186,11 +185,9 @@ pub struct VirtualDom {
|
|||||||
|
|
||||||
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
|
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
|
||||||
pub(crate) scope_stack: Vec<ScopeId>,
|
pub(crate) scope_stack: Vec<ScopeId>,
|
||||||
pub(crate) collected_leaves: Vec<SuspenseId>,
|
|
||||||
|
|
||||||
// Whenever a suspense tree is finished, we push its boundary onto this stack.
|
// Currently suspended scopes
|
||||||
// When "render_with_deadline" is called, we pop the stack and return the mutations
|
pub(crate) suspended_scopes: FxHashSet<ScopeId>,
|
||||||
pub(crate) finished_fibers: Vec<ScopeId>,
|
|
||||||
|
|
||||||
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
|
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
|
||||||
|
|
||||||
@@ -262,8 +259,7 @@ impl VirtualDom {
|
|||||||
elements: Default::default(),
|
elements: Default::default(),
|
||||||
scope_stack: Vec::new(),
|
scope_stack: Vec::new(),
|
||||||
dirty_scopes: BTreeSet::new(),
|
dirty_scopes: BTreeSet::new(),
|
||||||
collected_leaves: Vec::new(),
|
suspended_scopes: FxHashSet::default(),
|
||||||
finished_fibers: Vec::new(),
|
|
||||||
mutations: Mutations::default(),
|
mutations: Mutations::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -272,12 +268,6 @@ impl VirtualDom {
|
|||||||
"app",
|
"app",
|
||||||
);
|
);
|
||||||
|
|
||||||
// The root component is always a suspense boundary for any async children
|
|
||||||
// This could be unexpected, so we might rethink this behavior later
|
|
||||||
//
|
|
||||||
// We *could* just panic if the suspense boundary is not found
|
|
||||||
root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0))));
|
|
||||||
|
|
||||||
// Unlike react, we provide a default error boundary that just renders the error as a string
|
// Unlike react, we provide a default error boundary that just renders the error as a string
|
||||||
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
|
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
|
||||||
|
|
||||||
@@ -319,26 +309,6 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine whether or not a scope is currently in a suspended state
|
|
||||||
///
|
|
||||||
/// This does not mean the scope is waiting on its own futures, just that the tree that the scope exists in is
|
|
||||||
/// currently suspended.
|
|
||||||
pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
|
||||||
!self.scopes[id]
|
|
||||||
.consume_context::<Rc<SuspenseContext>>()
|
|
||||||
.unwrap()
|
|
||||||
.waiting_on
|
|
||||||
.borrow()
|
|
||||||
.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine if the tree is at all suspended. Used by SSR and other outside mechanisms to determine if the tree is
|
|
||||||
/// ready to be rendered.
|
|
||||||
pub fn has_suspended_work(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
// !self.scheduler.leaves.borrow().is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
|
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
|
||||||
///
|
///
|
||||||
/// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
|
/// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
|
||||||
@@ -495,7 +465,7 @@ impl VirtualDom {
|
|||||||
Ok(None) => return,
|
Ok(None) => return,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// If we have any dirty scopes, or finished fiber trees then we should exit
|
// If we have any dirty scopes, or finished fiber trees then we should exit
|
||||||
if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() {
|
if !self.dirty_scopes.is_empty() || !self.suspended_scopes.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,6 +566,19 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render the virtual dom, waiting for all suspense to be finished
|
||||||
|
///
|
||||||
|
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
|
||||||
|
pub async fn wait_for_suspense(&mut self) {
|
||||||
|
loop {
|
||||||
|
if self.suspended_scopes.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wait_for_work().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Render what you can given the timeline and then move on
|
/// Render what you can given the timeline and then move on
|
||||||
///
|
///
|
||||||
/// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
|
/// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
|
||||||
@@ -607,26 +590,6 @@ impl VirtualDom {
|
|||||||
self.process_events();
|
self.process_events();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// first, unload any complete suspense trees
|
|
||||||
for finished_fiber in self.finished_fibers.drain(..) {
|
|
||||||
let scope = &self.scopes[finished_fiber];
|
|
||||||
let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
|
|
||||||
|
|
||||||
self.mutations
|
|
||||||
.templates
|
|
||||||
.append(&mut context.mutations.borrow_mut().templates);
|
|
||||||
|
|
||||||
self.mutations
|
|
||||||
.edits
|
|
||||||
.append(&mut context.mutations.borrow_mut().edits);
|
|
||||||
|
|
||||||
// TODO: count how many nodes are on the stack?
|
|
||||||
self.mutations.push(Mutation::ReplaceWith {
|
|
||||||
id: context.placeholder.get().unwrap(),
|
|
||||||
m: 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, diff any dirty scopes
|
// Next, diff any dirty scopes
|
||||||
// We choose not to poll the deadline since we complete pretty quickly anyways
|
// We choose not to poll the deadline since we complete pretty quickly anyways
|
||||||
if let Some(dirty) = self.dirty_scopes.iter().next().cloned() {
|
if let Some(dirty) = self.dirty_scopes.iter().next().cloned() {
|
||||||
@@ -637,40 +600,9 @@ impl VirtualDom {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update
|
|
||||||
if self.is_scope_suspended(dirty.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the current mutations length so we can split them into boundary
|
|
||||||
let mutations_to_this_point = self.mutations.edits.len();
|
|
||||||
|
|
||||||
// Run the scope and get the mutations
|
// Run the scope and get the mutations
|
||||||
self.run_scope(dirty.id);
|
self.run_scope(dirty.id);
|
||||||
self.diff_scope(dirty.id);
|
self.diff_scope(dirty.id);
|
||||||
|
|
||||||
// If suspended leaves are present, then we should find the boundary for this scope and attach things
|
|
||||||
// No placeholder necessary since this is a diff
|
|
||||||
if !self.collected_leaves.is_empty() {
|
|
||||||
let mut boundary = self.scopes[dirty.id]
|
|
||||||
.consume_context::<Rc<SuspenseContext>>()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let boundary_mut = boundary.borrow_mut();
|
|
||||||
|
|
||||||
// Attach mutations
|
|
||||||
boundary_mut
|
|
||||||
.mutations
|
|
||||||
.borrow_mut()
|
|
||||||
.edits
|
|
||||||
.extend(self.mutations.edits.split_off(mutations_to_this_point));
|
|
||||||
|
|
||||||
// Attach suspended leaves
|
|
||||||
boundary
|
|
||||||
.waiting_on
|
|
||||||
.borrow_mut()
|
|
||||||
.extend(self.collected_leaves.drain(..));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's more work, then just continue, plenty of work to do
|
// If there's more work, then just continue, plenty of work to do
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_core::SuspenseContext;
|
|
||||||
|
|
||||||
/// Ensure no issues with not calling rebuild
|
/// Ensure no issues with not calling rebuild
|
||||||
#[test]
|
#[test]
|
||||||
@@ -17,8 +16,4 @@ fn root_node_isnt_null() {
|
|||||||
|
|
||||||
// The height should be 0
|
// The height should be 0
|
||||||
assert_eq!(scope.height(), 0);
|
assert_eq!(scope.height(), 0);
|
||||||
|
|
||||||
// There should be a default suspense context
|
|
||||||
// todo: there should also be a default error boundary
|
|
||||||
assert!(scope.has_context::<Rc<SuspenseContext>>().is_some());
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use dioxus::core::ElementId;
|
use dioxus::core::ElementId;
|
||||||
use dioxus::core::{Mutation::*, SuspenseContext};
|
use dioxus::core::Mutation::*;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use std::future::IntoFuture;
|
use std::future::IntoFuture;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|||||||
Reference in new issue
Block a user