wip: working on async diff
This commit is contained in:
1 parent
882d69571d
commit
f41cff571f
7 files changed
+198
-109
No files matched your search
+8
-1
@@ -5,7 +5,7 @@ use dioxus::ssr;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut vdom = VirtualDom::new(App);
|
let mut vdom = VirtualDom::new(App);
|
||||||
vdom.rebuild_in_place().expect("Rebuilding failed");
|
// vdom.rebuild_in_place().expect("Rebuilding failed");
|
||||||
println!("{}", ssr::render_vdom(&vdom, |c| c));
|
println!("{}", ssr::render_vdom(&vdom, |c| c));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,3 +17,10 @@ static App: FC<()> = |cx| {
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MyProps<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
}
|
||||||
|
fn App2<'a>(cx: Context<'a, MyProps>) -> DomTree<'a> {
|
||||||
|
None
|
||||||
|
}
|
||||||
+51
-35
@@ -1,9 +1,12 @@
|
|||||||
//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
|
//! This module contains the stateful PriorityFiber and all methods to diff VNodes, their properties, and their children.
|
||||||
//! The DiffMachine calculates the diffs between the old and new frames, updates the new nodes, and modifies the real dom.
|
//!
|
||||||
|
//! The [`PriorityFiber`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
|
||||||
|
//! of mutations for the RealDom to apply.
|
||||||
//!
|
//!
|
||||||
//! ## Notice:
|
//! ## Notice:
|
||||||
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
|
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
|
||||||
//! Components, Fragments, Suspense, SubTree memoization, and additional batching operations.
|
//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancelation, NodeRefs, and additional
|
||||||
|
//! batching operations.
|
||||||
//!
|
//!
|
||||||
//! ## Implementation Details:
|
//! ## Implementation Details:
|
||||||
//!
|
//!
|
||||||
@@ -11,12 +14,13 @@
|
|||||||
//! --------------------
|
//! --------------------
|
||||||
//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
|
//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
|
||||||
//! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
|
//! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
|
||||||
//! to batch nodes if it is more performant for their application. The expectation is that renderers use a Slotmap for nodes
|
//! to batch nodes if it is more performant for their application. The element IDs are indicies into the internal element
|
||||||
//! whose keys can be converted to u64 on FFI boundaries.
|
//! array. The expectation is that implemenetors will use the ID as an index into a Vec of real nodes, allowing for passive
|
||||||
|
//! garbage collection as the VirtualDOM replaces old nodes.
|
||||||
//!
|
//!
|
||||||
//! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we
|
//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
|
||||||
//! always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly and
|
//! we always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly
|
||||||
//! brick the user's page.
|
//! and brick the user's page.
|
||||||
//!
|
//!
|
||||||
//! ### Fragment Support
|
//! ### Fragment Support
|
||||||
//!
|
//!
|
||||||
@@ -26,6 +30,9 @@
|
|||||||
//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. This is
|
//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. This is
|
||||||
//! slightly inefficient, but represents a such an uncommon use case that it is not worth optimizing.
|
//! slightly inefficient, but represents a such an uncommon use case that it is not worth optimizing.
|
||||||
//!
|
//!
|
||||||
|
//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
|
||||||
|
//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
|
||||||
|
//!
|
||||||
//! ## Subtree Memoization
|
//! ## Subtree Memoization
|
||||||
//! -----------------------
|
//! -----------------------
|
||||||
//! We also employ "subtree memoization" which saves us from having to check trees which take no dynamic content. We can
|
//! We also employ "subtree memoization" which saves us from having to check trees which take no dynamic content. We can
|
||||||
@@ -35,13 +42,15 @@
|
|||||||
//! rsx!( div { class: "hello world", "this node is entirely static" } )
|
//! rsx!( div { class: "hello world", "this node is entirely static" } )
|
||||||
//! ```
|
//! ```
|
||||||
//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
|
//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
|
||||||
//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP
|
//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
|
||||||
|
//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
|
||||||
|
//! a way of short-circuiting the most expensive checks.
|
||||||
//!
|
//!
|
||||||
//! ## Bloom Filter and Heuristics
|
//! ## Bloom Filter and Heuristics
|
||||||
//! ------------------------------
|
//! ------------------------------
|
||||||
//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
|
//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
|
||||||
//! currently very rough, but will get better as time goes on. For FFI, we recommend using a bloom filter to cache strings.
|
//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a
|
||||||
//!
|
//! bump arena after first render, the number of hooks, and the number of nodes in the tree.
|
||||||
//!
|
//!
|
||||||
//! ## Garbage Collection
|
//! ## Garbage Collection
|
||||||
//! ---------------------
|
//! ---------------------
|
||||||
@@ -53,11 +62,6 @@
|
|||||||
//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
|
//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
|
||||||
//! for the client. As new nodes are created, old nodes will be over-written.
|
//! for the client. As new nodes are created, old nodes will be over-written.
|
||||||
//!
|
//!
|
||||||
//! HEADS-UP:
|
|
||||||
//! For now, deferred garabge collection is disabled. The code-paths are almost wired up, but it's quite complex to
|
|
||||||
//! get working safely and efficiently. For now, garabge is collected immediately during diffing. This adds extra
|
|
||||||
//! overhead, but is faster to implement in the short term.
|
|
||||||
//!
|
|
||||||
//! Further Reading and Thoughts
|
//! Further Reading and Thoughts
|
||||||
//! ----------------------------
|
//! ----------------------------
|
||||||
//! There are more ways of increasing diff performance here that are currently not implemented.
|
//! There are more ways of increasing diff performance here that are currently not implemented.
|
||||||
@@ -67,13 +71,16 @@
|
|||||||
use crate::{arena::SharedResources, innerlude::*};
|
use crate::{arena::SharedResources, innerlude::*};
|
||||||
use futures_util::Future;
|
use futures_util::Future;
|
||||||
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
|
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||||
|
use indexmap::IndexSet;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use std::{any::Any, cell::Cell, cmp::Ordering, marker::PhantomData, pin::Pin};
|
use std::{
|
||||||
|
any::Any, cell::Cell, cmp::Ordering, collections::HashSet, marker::PhantomData, pin::Pin,
|
||||||
|
};
|
||||||
use DomEdit::*;
|
use DomEdit::*;
|
||||||
|
|
||||||
pub struct DiffMachine<'r, 'bump> {
|
pub struct DiffMachine<'bump> {
|
||||||
pub vdom: &'bump SharedResources,
|
vdom: &'bump SharedResources,
|
||||||
|
|
||||||
pub mutations: Mutations<'bump>,
|
pub mutations: Mutations<'bump>,
|
||||||
|
|
||||||
@@ -81,14 +88,10 @@ pub struct DiffMachine<'r, 'bump> {
|
|||||||
|
|
||||||
pub diffed: FxHashSet<ScopeId>,
|
pub diffed: FxHashSet<ScopeId>,
|
||||||
|
|
||||||
// will be used later for garbage collection
|
|
||||||
// we check every seen node and then schedule its eventual deletion
|
|
||||||
pub seen_scopes: FxHashSet<ScopeId>,
|
pub seen_scopes: FxHashSet<ScopeId>,
|
||||||
|
|
||||||
_r: PhantomData<&'r ()>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
impl<'bump> DiffMachine<'bump> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
edits: Mutations<'bump>,
|
edits: Mutations<'bump>,
|
||||||
cur_scope: ScopeId,
|
cur_scope: ScopeId,
|
||||||
@@ -100,7 +103,6 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
vdom: shared,
|
vdom: shared,
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
seen_scopes: FxHashSet::default(),
|
seen_scopes: FxHashSet::default(),
|
||||||
_r: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,12 +117,17 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
vdom: shared,
|
vdom: shared,
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
seen_scopes: FxHashSet::default(),
|
seen_scopes: FxHashSet::default(),
|
||||||
_r: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make incremental progress on the current task
|
||||||
|
pub fn work(&mut self, is_ready: impl FnMut() -> bool) -> Result<FiberResult> {
|
||||||
|
todo!()
|
||||||
|
// Ok(FiberResult::D)
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
pub fn diff_scope(&mut self, id: ScopeId) -> Result<()> {
|
pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> {
|
||||||
let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?;
|
let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?;
|
||||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||||
self.diff_node(old, new);
|
self.diff_node(old, new);
|
||||||
@@ -133,7 +140,11 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
// the real stack should be what it is coming in and out of this function (ideally empty)
|
// the real stack should be what it is coming in and out of this function (ideally empty)
|
||||||
//
|
//
|
||||||
// each function call assumes the stack is fresh (empty).
|
// each function call assumes the stack is fresh (empty).
|
||||||
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
pub async fn diff_node(
|
||||||
|
&mut self,
|
||||||
|
old_node: &'bump VNode<'bump>,
|
||||||
|
new_node: &'bump VNode<'bump>,
|
||||||
|
) {
|
||||||
match (&old_node.kind, &new_node.kind) {
|
match (&old_node.kind, &new_node.kind) {
|
||||||
// Handle the "sane" cases first.
|
// Handle the "sane" cases first.
|
||||||
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
||||||
@@ -264,7 +275,8 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
false => {
|
false => {
|
||||||
// the props are different...
|
// the props are different...
|
||||||
scope.run_scope().unwrap();
|
scope.run_scope().unwrap();
|
||||||
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
|
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head())
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +308,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
// This is the case where options or direct vnodes might be used.
|
// This is the case where options or direct vnodes might be used.
|
||||||
// In this case, it's faster to just skip ahead to their diff
|
// In this case, it's faster to just skip ahead to their diff
|
||||||
if old.children.len() == 1 && new.children.len() == 1 {
|
if old.children.len() == 1 && new.children.len() == 1 {
|
||||||
self.diff_node(&old.children[0], &new.children[0]);
|
self.diff_node(&old.children[0], &new.children[0]).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1059,7 +1071,11 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
// [... parent]
|
// [... parent]
|
||||||
//
|
//
|
||||||
// the change list stack is in the same state when this function returns.
|
// the change list stack is in the same state when this function returns.
|
||||||
fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
|
async fn diff_non_keyed_children(
|
||||||
|
&mut self,
|
||||||
|
old: &'bump [VNode<'bump>],
|
||||||
|
new: &'bump [VNode<'bump>],
|
||||||
|
) {
|
||||||
// Handled these cases in `diff_children` before calling this function.
|
// Handled these cases in `diff_children` before calling this function.
|
||||||
//
|
//
|
||||||
debug_assert!(!new.is_empty());
|
debug_assert!(!new.is_empty());
|
||||||
@@ -1099,15 +1115,15 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
self.edit_pop();
|
self.edit_pop();
|
||||||
|
|
||||||
// diff the rest
|
// diff the rest
|
||||||
new.iter()
|
for (new_child, old_child) in new.iter().zip(old.iter()) {
|
||||||
.zip(old.iter())
|
self.diff_node(old_child, new_child).await
|
||||||
.for_each(|(new_child, old_child)| self.diff_node(old_child, new_child));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// old.len == new.len -> no nodes added/removed, but perhaps changed
|
// old.len == new.len -> no nodes added/removed, but perhaps changed
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
for (new_child, old_child) in new.iter().zip(old.iter()) {
|
for (new_child, old_child) in new.iter().zip(old.iter()) {
|
||||||
self.diff_node(old_child, new_child);
|
self.diff_node(old_child, new_child).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::scope::*;
|
pub use crate::scope::*;
|
||||||
pub use crate::util::*;
|
pub use crate::util::*;
|
||||||
pub use crate::virtual_dom::*;
|
pub use crate::virtual_dom::*;
|
||||||
|
pub use crate::yield_now::*;
|
||||||
|
|
||||||
pub type DomTree<'a> = Option<VNode<'a>>;
|
pub type DomTree<'a> = Option<VNode<'a>>;
|
||||||
pub type FC<P> = fn(Context<P>) -> DomTree;
|
pub type FC<P> = fn(Context<P>) -> DomTree;
|
||||||
@@ -73,3 +74,4 @@ pub mod scope;
|
|||||||
pub mod signals;
|
pub mod signals;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod virtual_dom;
|
pub mod virtual_dom;
|
||||||
|
pub mod yield_now;
|
||||||
@@ -1,3 +1,26 @@
|
|||||||
|
//! Provides resumable task scheduling for Dioxus.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! ## Design
|
||||||
|
//!
|
||||||
|
//! The recent React fiber architecture rewrite enabled pauseable and resumable diffing through the development of
|
||||||
|
//! something called a "Fiber." Fibers were created to provide a way of "saving a stack frame", making it possible to
|
||||||
|
//! resume said stack frame at a later time, or to drop it altogether. This made it possible to
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
@@ -11,6 +34,7 @@ use futures_util::Future;
|
|||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
|
|
||||||
@@ -74,13 +98,15 @@ pub struct Scheduler {
|
|||||||
|
|
||||||
shared: SharedResources,
|
shared: SharedResources,
|
||||||
|
|
||||||
high_priorty: PriorityFiber<'static>,
|
waypoints: VecDeque<Waypoint>,
|
||||||
medium_priority: PriorityFiber<'static>,
|
|
||||||
low_priority: PriorityFiber<'static>,
|
high_priorty: PriortySystem,
|
||||||
|
medium_priority: PriortySystem,
|
||||||
|
low_priority: PriortySystem,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum FiberResult<'a> {
|
pub enum FiberResult<'a> {
|
||||||
Done(Mutations<'a>),
|
Done(&'a mut Mutations<'a>),
|
||||||
Interrupted,
|
Interrupted,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,10 +123,11 @@ impl Scheduler {
|
|||||||
garbage_scopes: HashSet::new(),
|
garbage_scopes: HashSet::new(),
|
||||||
|
|
||||||
current_priority: EventPriority::Low,
|
current_priority: EventPriority::Low,
|
||||||
|
waypoints: VecDeque::new(),
|
||||||
|
|
||||||
high_priorty: PriorityFiber::new(),
|
high_priorty: PriortySystem::new(),
|
||||||
medium_priority: PriorityFiber::new(),
|
medium_priority: PriortySystem::new(),
|
||||||
low_priority: PriorityFiber::new(),
|
low_priority: PriortySystem::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,21 +250,26 @@ impl Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_work(&self) -> bool {
|
pub fn has_work(&self) -> bool {
|
||||||
let has_work = self.high_priorty.has_work()
|
self.waypoints.len() > 0
|
||||||
|| self.medium_priority.has_work()
|
|
||||||
|| self.low_priority.has_work();
|
|
||||||
!has_work
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_pending_garbage(&self) -> bool {
|
pub fn has_pending_garbage(&self) -> bool {
|
||||||
!self.garbage_scopes.is_empty()
|
!self.garbage_scopes.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_current_fiber<'a>(&'a mut self) -> &mut DiffMachine<'a> {
|
||||||
|
let fib = match self.current_priority {
|
||||||
|
EventPriority::High => &mut self.high_priorty,
|
||||||
|
EventPriority::Medium => &mut self.medium_priority,
|
||||||
|
EventPriority::Low => &mut self.low_priority,
|
||||||
|
};
|
||||||
|
unsafe { std::mem::transmute(fib) }
|
||||||
|
}
|
||||||
|
|
||||||
/// If a the fiber finishes its works (IE needs to be committed) the scheduler will drop the dirty scope
|
/// If a the fiber finishes its works (IE needs to be committed) the scheduler will drop the dirty scope
|
||||||
pub fn work_with_deadline(
|
pub fn work_with_deadline(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
|
mut deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
|
||||||
is_deadline_reached: &mut impl FnMut() -> bool,
|
|
||||||
) -> FiberResult {
|
) -> FiberResult {
|
||||||
// check if we need to elevate priority
|
// check if we need to elevate priority
|
||||||
self.current_priority = match (
|
self.current_priority = match (
|
||||||
@@ -250,13 +282,10 @@ impl Scheduler {
|
|||||||
(false, false, _) => EventPriority::Low,
|
(false, false, _) => EventPriority::Low,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut current_fiber = match self.current_priority {
|
let mut is_ready = || -> bool { (&mut deadline).now_or_never().is_some() };
|
||||||
EventPriority::High => &mut self.high_priorty,
|
|
||||||
EventPriority::Medium => &mut self.medium_priority,
|
|
||||||
EventPriority::Low => &mut self.low_priority,
|
|
||||||
};
|
|
||||||
|
|
||||||
todo!()
|
// TODO: remove this unwrap - proprogate errors out
|
||||||
|
self.get_current_fiber().work(is_ready).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// waits for a trigger, canceling early if the deadline is reached
|
// waits for a trigger, canceling early if the deadline is reached
|
||||||
@@ -374,45 +403,46 @@ pub struct DirtyScope {
|
|||||||
start_tick: u32,
|
start_tick: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// fibers in dioxus aren't exactly the same as React's. Our fibers are more like a "saved state" of the diffing algorithm.
|
/*
|
||||||
pub struct PriorityFiber<'a> {
|
A "waypoint" represents a frozen unit in time for the DiffingMachine to resume from. Whenever the deadline runs out
|
||||||
// scopes that haven't been updated yet
|
while diffing, the diffing algorithm generates a Waypoint in order to easily resume from where it left off. Waypoints are
|
||||||
pending_scopes: Vec<ScopeId>,
|
fairly expensive to create, especially for big trees, so it's a good idea to pre-allocate them.
|
||||||
|
|
||||||
pending_nodes: Vec<*const VNode<'a>>,
|
Waypoints are created pessimisticly, and are only generated when an "Error" state is bubbled out of the diffing machine.
|
||||||
|
This saves us from wasting cycles book-keeping waypoints for 99% of edits where the deadline is not reached.
|
||||||
|
*/
|
||||||
|
pub struct Waypoint {
|
||||||
|
// the progenitor of this waypoint
|
||||||
|
root: ScopeId,
|
||||||
|
|
||||||
// WIP edits
|
edits: Vec<DomEdit<'static>>,
|
||||||
edits: Vec<DomEdit<'a>>,
|
|
||||||
|
|
||||||
started: bool,
|
// a saved position in the tree
|
||||||
|
// these indicies continue to map through the tree into children nodes.
|
||||||
|
// A sequence of usizes is all that is needed to represent the path to a node.
|
||||||
|
tree_position: SmallVec<[usize; 10]>,
|
||||||
|
|
||||||
// a fiber is finished when no more scopes or nodes are pending
|
seen_scopes: HashSet<ScopeId>,
|
||||||
completed: bool,
|
|
||||||
|
|
||||||
dirty_scopes: IndexSet<ScopeId>,
|
invalidate_scopes: HashSet<ScopeId>,
|
||||||
|
|
||||||
wip_edits: Vec<DomEdit<'a>>,
|
priority_level: EventPriority,
|
||||||
|
|
||||||
current_batch_scopes: HashSet<ScopeId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriorityFiber<'_> {
|
pub struct PriortySystem {
|
||||||
fn new() -> Self {
|
pub pending_scopes: Vec<ScopeId>,
|
||||||
|
pub dirty_scopes: IndexSet<ScopeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PriortySystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pending_scopes: Vec::new(),
|
pending_scopes: Default::default(),
|
||||||
pending_nodes: Vec::new(),
|
dirty_scopes: Default::default(),
|
||||||
edits: Vec::new(),
|
|
||||||
started: false,
|
|
||||||
completed: false,
|
|
||||||
dirty_scopes: IndexSet::new(),
|
|
||||||
wip_edits: Vec::new(),
|
|
||||||
current_batch_scopes: HashSet::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_work(&self) -> bool {
|
fn has_work(&self) -> bool {
|
||||||
self.dirty_scopes.is_empty()
|
self.pending_scopes.len() > 0 || self.dirty_scopes.len() > 0
|
||||||
&& self.wip_edits.is_empty()
|
|
||||||
&& self.current_batch_scopes.is_empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,11 +178,12 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This method will not wait for any suspended nodes to complete.
|
/// This method will not wait for any suspended nodes to complete.
|
||||||
pub fn run_immediate<'s>(&'s mut self) -> Result<Mutations<'s>> {
|
pub fn run_immediate<'s>(&'s mut self) -> Result<Mutations<'s>> {
|
||||||
use futures_util::FutureExt;
|
todo!()
|
||||||
let mut is_ready = || false;
|
// use futures_util::FutureExt;
|
||||||
self.run_with_deadline_and_is_ready(futures_util::future::ready(()), &mut is_ready)
|
// let mut is_ready = || false;
|
||||||
.now_or_never()
|
// self.run_with_deadline(futures_util::future::ready(()), &mut is_ready)
|
||||||
.expect("this future will always resolve immediately")
|
// .now_or_never()
|
||||||
|
// .expect("this future will always resolve immediately")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the virtualdom with no time limit.
|
/// Runs the virtualdom with no time limit.
|
||||||
@@ -240,25 +241,6 @@ impl VirtualDom {
|
|||||||
pub async fn run_with_deadline<'s>(
|
pub async fn run_with_deadline<'s>(
|
||||||
&'s mut self,
|
&'s mut self,
|
||||||
deadline: impl Future<Output = ()>,
|
deadline: impl Future<Output = ()>,
|
||||||
) -> Result<Mutations<'s>> {
|
|
||||||
use futures_util::FutureExt;
|
|
||||||
|
|
||||||
let deadline_future = deadline.shared();
|
|
||||||
let mut is_ready_deadline = deadline_future.clone();
|
|
||||||
let mut is_ready = || -> bool { (&mut is_ready_deadline).now_or_never().is_some() };
|
|
||||||
|
|
||||||
self.run_with_deadline_and_is_ready(deadline_future, &mut is_ready)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the virtualdom with a deadline and a custom "check" function.
|
|
||||||
///
|
|
||||||
/// Designed this way so "run_immediate" can re-use all the same rendering logic as "run_with_deadline" but the work
|
|
||||||
/// queue is completely drained;
|
|
||||||
async fn run_with_deadline_and_is_ready<'s>(
|
|
||||||
&'s mut self,
|
|
||||||
deadline: impl Future<Output = ()>,
|
|
||||||
is_ready: &mut impl FnMut() -> bool,
|
|
||||||
) -> Result<Mutations<'s>> {
|
) -> Result<Mutations<'s>> {
|
||||||
let mut committed_mutations = Mutations::new();
|
let mut committed_mutations = Mutations::new();
|
||||||
let mut deadline = Box::pin(deadline.fuse());
|
let mut deadline = Box::pin(deadline.fuse());
|
||||||
@@ -286,7 +268,7 @@ impl VirtualDom {
|
|||||||
|
|
||||||
// Work through the current subtree, and commit the results when it finishes
|
// Work through the current subtree, and commit the results when it finishes
|
||||||
// When the deadline expires, give back the work
|
// When the deadline expires, give back the work
|
||||||
match self.scheduler.work_with_deadline(&mut deadline, is_ready) {
|
match self.scheduler.work_with_deadline(&mut deadline) {
|
||||||
FiberResult::Done(mut mutations) => {
|
FiberResult::Done(mut mutations) => {
|
||||||
committed_mutations.extend(&mut mutations);
|
committed_mutations.extend(&mut mutations);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
// use crate::task::{Context, Poll};
|
||||||
|
|
||||||
|
/// Cooperatively gives up a timeslice to the task scheduler.
|
||||||
|
///
|
||||||
|
/// Calling this function will move the currently executing future to the back
|
||||||
|
/// of the execution queue, making room for other futures to execute. This is
|
||||||
|
/// especially useful after running CPU-intensive operations inside a future.
|
||||||
|
///
|
||||||
|
/// See also [`task::spawn_blocking`].
|
||||||
|
///
|
||||||
|
/// [`task::spawn_blocking`]: fn.spawn_blocking.html
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # async_std::task::block_on(async {
|
||||||
|
/// #
|
||||||
|
/// use async_std::task;
|
||||||
|
///
|
||||||
|
/// task::yield_now().await;
|
||||||
|
/// #
|
||||||
|
/// # })
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub async fn yield_now() {
|
||||||
|
YieldNow(false).await
|
||||||
|
}
|
||||||
|
|
||||||
|
struct YieldNow(bool);
|
||||||
|
|
||||||
|
impl Future for YieldNow {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
// The futures executor is implemented as a FIFO queue, so all this future
|
||||||
|
// does is re-schedule the future back to the end of the queue, giving room
|
||||||
|
// for other futures to progress.
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
if !self.0 {
|
||||||
|
self.0 = true;
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,9 +108,9 @@ pub async fn run_with_props<T: Properties + 'static>(
|
|||||||
let tasks = dom.get_event_sender();
|
let tasks = dom.get_event_sender();
|
||||||
|
|
||||||
// initialize the virtualdom first
|
// initialize the virtualdom first
|
||||||
if cfg.hydrate {
|
// if cfg.hydrate {
|
||||||
dom.rebuild_in_place()?;
|
// dom.rebuild_in_place()?;
|
||||||
}
|
// }
|
||||||
|
|
||||||
let mut websys_dom = dom::WebsysDom::new(
|
let mut websys_dom = dom::WebsysDom::new(
|
||||||
root_el,
|
root_el,
|
||||||
|
|||||||
Reference in new issue
Block a user