Merge branch 'master' into jk/remove_node_safety

This commit is contained in:
Jonathan Kelley committed 2021-10-28 17:09:51 -04:00
commit db0004758c
45 files changed
+156 -156

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@ use crate::innerlude::*;
pub(crate) struct RealChildIterator<'a> {
scopes: &'a ResourcePool,
// Heuristcally we should never bleed into 4 completely nested fragments/components
// Heuristically we should never bleed into 4 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
// TODO: use const generics instead of the 4 estimation
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>,
+2 -2
View File
@@ -2,7 +2,7 @@
//! for components to be used within Nodes.
//!
//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
//! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
//! that ensures compile-time required and optional fields on cx.
use crate::innerlude::{Context, DomTree, LazyNodes, FC};
@@ -108,7 +108,7 @@ pub trait Properties: Sized {
///
/// # Safety
/// The user must know if their props are static, but if they make a mistake, UB happens
/// Therefore it's unsafe to memeoize.
/// Therefore it's unsafe to memoize.
unsafe fn memoize(&self, other: &Self) -> bool;
}
+5 -5
View File
@@ -132,7 +132,7 @@ impl<'src> Context<'src> {
/// fn Component(cx: Context<()>) -> VNode {
/// // Lazy assemble the VNode tree
/// let lazy_tree = html! {<div> "Hello World" </div>};
///
///
/// // Actually build the tree and allocate it
/// cx.render(lazy_tree)
/// }
@@ -203,7 +203,7 @@ impl<'src> Context<'src> {
.flatten();
}
/// Try to retrive a SharedState with type T from the any parent Scope.
/// Try to retrieve a SharedState with type T from the any parent Scope.
pub fn consume_state<T: 'static>(self) -> Option<Rc<T>> {
let getter = &self.scope.shared.get_shared_context;
let ty = TypeId::of::<T>();
@@ -225,7 +225,7 @@ impl<'src> Context<'src> {
/// todo!();
/// rsx!(cx, div { "Subtree {id}"})
/// };
/// ```
/// ```
pub fn create_subtree(self) -> Option<u32> {
self.scope.new_subtree()
}
@@ -298,9 +298,9 @@ impl<'src> Context<'src> {
}
const HOOK_ERR_MSG: &str = r###"
Unable to retrive the hook that was initialized in this index.
Unable to retrieve the hook that was initialized at this index.
Consult the `rules of hooks` to understand how to use hooks properly.
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Any function prefixed with "use" should not be called conditionally.
Functions prefixed with "use" should never be called conditionally.
"###;
+21 -21
View File
@@ -6,7 +6,7 @@
//! ## Notice:
//!
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancelation, NodeRefs, pausing, priority
//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancellation, NodeRefs, pausing, priority
//! scheduling, and additional batching operations.
//!
//! ## Implementation Details:
@@ -15,8 +15,8 @@
//! --------------------
//! 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 instantly during the diffing process, so the implementor may choose
//! to batch nodes if it is more performant for their application. The element IDs are indicies into the internal element
//! array. The expectation is that implemenetors will use the ID as an index into a Vec of real nodes, allowing for passive
//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
//! array. The expectation is that implementors 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 vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
@@ -30,34 +30,34 @@
//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is
//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
//! the platform.
//! the platform.
//!
//! 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.
//!
//! ### Suspense
//! ------------
//! Dioxus implements suspense slightly differently than React. In React, each fiber is manually progressed until it runs
//! Dioxus implements Suspense slightly differently than React. In React, each fiber is manually progressed until it runs
//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once
//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before
//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on.
//! Due to the frequent calls to "yield_now" we can get the pure "fetch-as-you-render" behavior of React fiber.
//! Due to the frequent calls to "yield_now" we can get the pure "fetch-as-you-render" behavior of React Fiber.
//!
//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to
//! DOM, but as a placeholder.
//!
//! Right now, the "suspense" queue is intertwined the hooks. In the future, we should allow any future to drive attributes
//! and contents, without the need for the "use_suspense" hook. In the interim, this is the quickest way to get suspense working.
//! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes
//! and contents, without the need for the "use_suspense" hook. In the interim, this is the quickest way to get Suspense working.
//!
//! ## 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 hold no dynamic content. We can
//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
//! calls to "create" propogate this information upwards. Structures like the one below are entirely static:
//! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
//! ```rust
//! 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 it's up to the reconciler to
//! 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.
@@ -70,11 +70,11 @@
//!
//! ## Garbage Collection
//! ---------------------
//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garabge
//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage
//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then
//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient.
//!
//! The IDs used by the key/map are just an index into a vec. This means that Dioxus will drive the key allocation strategy
//! The IDs used by the key/map are just an index into a Vec. This means that Dioxus will drive the key allocation strategy
//! 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.
//!
@@ -95,7 +95,7 @@ use DomEdit::*;
/// Our DiffMachine is an iterative tree differ.
///
/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
/// was origially implemented using recursive techniques, but Rust lacks the abilty to call async functions recursively,
/// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively,
/// meaning we could not "pause" the original diffing algorithm.
///
/// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically
@@ -194,7 +194,7 @@ impl<'bump> DiffMachine<'bump> {
};
if deadline_expired() {
log::debug!("Deadling expired before we could finished!");
log::debug!("Deadline expired before we could finished!");
return false;
}
}
@@ -591,7 +591,7 @@ impl<'bump> DiffMachine<'bump> {
}
// =============================================
// Utilites for creating new diff instructions
// Utilities for creating new diff instructions
// =============================================
// Diff the given set of old and new children.
@@ -607,7 +607,7 @@ impl<'bump> DiffMachine<'bump> {
//
// Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
//
// Frament nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
// Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
// to an element, and appending makes sense.
fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
// Remember, fragments can never be empty (they always have a single child)
@@ -757,7 +757,7 @@ impl<'bump> DiffMachine<'bump> {
self.remove_nodes(old_middle, true);
} else if old_middle.is_empty() {
// there were no old elements, so just create the new elements
// we need to find the right "foothold" though - we shouldnt use the "append" at all
// we need to find the right "foothold" though - we shouldn't use the "append" at all
if left_offset == 0 {
// insert at the beginning of the old list
let foothold = &old[old.len() - right_offset];
@@ -862,7 +862,7 @@ impl<'bump> DiffMachine<'bump> {
// Upon exit from this function, it will be restored to that same state.
fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
/*
1. Map the old keys into a numerical ordering based on indicies.
1. Map the old keys into a numerical ordering based on indices.
2. Create a map of old key to its index
3. Map each new key to the old key, carrying over the old index.
- IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
@@ -888,7 +888,7 @@ impl<'bump> DiffMachine<'bump> {
debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
// 1. Map the old keys into a numerical ordering based on indicies.
// 1. Map the old keys into a numerical ordering based on indices.
// 2. Create a map of old key to its index
// IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
let old_key_to_old_index = old
@@ -956,7 +956,7 @@ impl<'bump> DiffMachine<'bump> {
if old_index == u32::MAX as usize {
stack.create_node(new_node, MountType::Absorb);
} else {
// this funciton should never take LIS indicies
// this function should never take LIS indices
stack.push(DiffInstruction::PrepareMove { node: new_node });
stack.push(DiffInstruction::Diff {
new: new_node,
+1 -1
View File
@@ -111,7 +111,7 @@ impl<'bump> DiffStack<'bump> {
self.instructions.push(DiffInstruction::PopScope);
// Run the creation algorithm with this scope on the stack
// ?? I think we treat components as framgnets??
// ?? I think we treat components as fragments??
self.instructions.push(DiffInstruction::Create { node });
}
}
+6 -6
View File
@@ -35,7 +35,7 @@ pub struct UserEvent {
/// Priority of Event Triggers.
///
/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
///
@@ -69,17 +69,17 @@ pub enum EventPriority {
High = 2,
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
/// than "High Priority" events and will take presedence over low priority events.
/// than "High Priority" events and will take precedence over low priority events.
///
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
///
/// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
Medium = 1,
/// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
/// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
/// advanced to the front of the work queue until completed.
///
/// The primary user of Low Priority work is the asynchronous work system (suspense).
/// The primary user of Low Priority work is the asynchronous work system (Suspense).
///
/// This is considered "idle" work or "background" work.
Low = 0,
@@ -505,7 +505,7 @@ pub mod on {
/// _ => {}
/// }
/// ```
///
///
pub key: String,
/// Get the key code as an enum Variant.
@@ -525,7 +525,7 @@ pub mod on {
/// _ => {}
/// }
/// ```
///
///
pub key_code: KeyCode,
/// Indicate if the `alt` modifier key was pressed during this keyboard event
+2 -2
View File
@@ -6,9 +6,9 @@ use crate::FC;
/// Provides heuristics to the "SharedResources" object for improving allocation performance.
///
/// This heueristic engine records the memory footprint of bump arenas and hook lists for each component. These records are
/// This heuristics engine records the memory footprint of bump arenas and hook lists for each component. These records are
/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
/// that would slow down the diffing and initializion process.
/// that would slow down the diffing and initialization process.
///
///
pub struct HeuristicsEngine {
+2 -2
View File
@@ -30,10 +30,10 @@ impl HookList {
/// This resets the internal iterator count
/// It's okay that we've given out each hook, but now we have the opportunity to give it out again
/// Therefore, resetting is cosudered unsafe
/// Therefore, resetting is considered unsafe
///
/// This should only be ran by Dioxus itself before "running scope".
/// Dioxus knows how to descened through the tree to prevent mutable aliasing.
/// Dioxus knows how to descend through the tree to prevent mutable aliasing.
pub(crate) unsafe fn reset(&mut self) {
self.idx.set(0);
}
+1 -1
View File
@@ -1,6 +1,6 @@
//! Built-in hooks
//!
//! This module contains all the low-level built-in hooks that require 1st party support to work.
//! This module contains all the low-level built-in hooks that require first party support to work.
//!
//! Hooks:
//! - [`use_hook`]
+3 -3
View File
@@ -41,7 +41,7 @@ impl<'a> Mutations<'a> {
self.edits.push(InsertBefore { n, root });
}
// Remove Nodesfrom the dom
// Remove Nodes from the dom
pub(crate) fn remove(&mut self, id: u64) {
self.edits.push(Remove { root: id });
}
@@ -146,7 +146,7 @@ impl<'a> NodeRefMutation<'a> {
}
}
/// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the
/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
/// network or through FFI boundaries.
#[derive(Debug, PartialEq)]
#[cfg_attr(
@@ -164,7 +164,7 @@ pub enum DomEdit<'bump> {
many: u32,
},
// "Root" refers to the item direclty
// "Root" refers to the item directly
// it's a waste of an instruction to push the root directly
ReplaceWith {
root: u64,
+4 -4
View File
@@ -15,7 +15,7 @@ use std::{
/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
///
/// VNodes are designed to be lightweight and used with with a bump alloactor. To create a VNode, you can use either of:
/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
/// - the [`rsx`] macro
/// - the [`html`] macro
/// - the [`NodeFactory`] API
@@ -110,7 +110,7 @@ pub enum VNode<'src> {
/// # Example
///
/// ```rust
/// let node = cx.render(rsx! ( Fragment {} )).unwrap();
/// let node = cx.render(rsx! ( Fragment {} )).unwrap();
/// if let VNode::Fragment(frag) = node {
/// let root = &frag.children[0];
/// assert_eq!(root, VNode::Anchor);
@@ -159,7 +159,7 @@ pub struct VAnchor {
pub dom_id: Cell<Option<ElementId>>,
}
/// A bump-alloacted string slice and metadata.
/// A bump-allocated string slice and metadata.
pub struct VText<'src> {
pub text: &'src str,
@@ -593,7 +593,7 @@ impl<'a> NodeFactory<'a> {
/// impl IntoIterator<Item = impl IntoVNode<'a>>
/// ```
///
/// As such, all node creation must go through the factory, which is only availble in the component context.
/// As such, all node creation must go through the factory, which is only available in the component context.
/// These strict requirements make it possible to manage lifetimes and state.
pub trait IntoVNode<'a> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
+1 -1
View File
@@ -8,7 +8,7 @@ pub(crate) struct ResourcePool {
This *has* to be an UnsafeCell.
Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to
its bumpframe conents immutably.
its bumpframe contents immutably.
However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly
simple to audit.
+8 -8
View File
@@ -43,19 +43,19 @@ before the timeout, then Dioxus will work on any pending work in the interim. If
are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
future is paused and saved (self-referentially).
# Priorty System
# Priority System
So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
a "lower" priority. That way, we can interrupt long-running low-prioty work with short-running high-priority work.
a "lower" priority. That way, we can interrupt long-running low-priority work with short-running high-priority work.
React's priority system is quite complex.
There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
For Dioxus, we're going with a 4 tier priorty system:
For Dioxus, we're going with a 4 tier priority system:
- Sync: Things that need to be done by the next frame, like TextInput on controlled elements
- High: for events that block all others - clicks, keyboard, and hovers
- Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
@@ -132,7 +132,7 @@ pub(crate) struct Scheduler {
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
///
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
/// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
pub pool: ResourcePool,
pub heuristics: HeuristicsEngine,
@@ -187,7 +187,7 @@ impl Scheduler {
let sender = sender.clone();
Rc::new(move |id| {
//
log::debug!("scheduling immedate! {:?}", id);
log::debug!("scheduling immediate! {:?}", id);
sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()
})
},
@@ -387,7 +387,7 @@ impl Scheduler {
});
if let Some(scopeid) = self.dirty_scopes.pop() {
log::info!("handlng dirty scope {:?}", scopeid);
log::info!("handling dirty scope {:?}", scopeid);
if !ran_scopes.contains(&scopeid) {
ran_scopes.insert(scopeid);
log::debug!("about to run scope {:?}", scopeid);
@@ -461,7 +461,7 @@ impl Scheduler {
- When called, check for any UI events that might've been received since the last frame.
- Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
- If there are any pending discrete events, then elevate our priorty level. If our priority level is already "high,"
- If there are any pending discrete events, then elevate our priority level. If our priority level is already "high,"
then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
to bump the other work out of the way, or choose to process it so we don't have any conflicts.
@@ -581,7 +581,7 @@ impl Scheduler {
} else {
// todo: should this be a hard error?
log::warn!(
"Component failed to run succesfully during rebuild.
"Component failed to run successfully during rebuild.
This does not result in a failed rebuild, but indicates a logic failure within your app."
);
}
+4 -4
View File
@@ -17,7 +17,7 @@ use std::{
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
///
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
/// usecase they might have.
/// use case they might have.
pub struct Scope {
// Book-keeping about our spot in the arena
pub(crate) parent_idx: Option<ScopeId>,
@@ -233,20 +233,20 @@ impl Scope {
}
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
/// causuing UB in our tree.
/// causing UB in our tree.
///
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
/// any possible references to the data in the hook list.
///
/// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
/// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
/// all listeners and borrowed props so we can clear them here.
///
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
/// be dropped.
pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
// run the hooks (which hold an &mut Referrence)
// run the hooks (which hold an &mut Reference)
// right now, we don't drop
self.borrowed_props
.get_mut()
+7 -7
View File
@@ -3,7 +3,7 @@
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
//!
//! In this file, multiple items are defined. This file is big, but should be documented well to
//! navigate the innerworkings of the Dom. We try to keep these main mechanics in this file to limit
//! navigate the inner workings of the Dom. We try to keep these main mechanics in this file to limit
//! the possible exposed API surface (keep fields private). This particular implementation of VDOM
//! is extremely efficient, but relies on some unsafety under the hood to do things like manage
//! micro-heaps for components. We are currently working on refactoring the safety out into safe(r)
@@ -11,10 +11,10 @@
//!
//! Included is:
//! - The [`VirtualDom`] itself
//! - The [`Scope`] object for mangning component lifecycle
//! - The [`Scope`] object for managing component lifecycle
//! - The [`ActiveFrame`] object for managing the Scope`s microheap
//! - The [`Context`] object for exposing VirtualDOM API to components
//! - The [`NodeFactory`] object for lazyily exposing the `Context` API to the nodebuilder API
//! - The [`NodeFactory`] object for lazily exposing the `Context` API to the nodebuilder API
//!
//! This module includes just the barebones for a complete VirtualDOM API.
//! Additional functionality is defined in the respective files.
@@ -130,7 +130,7 @@ impl VirtualDom {
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
///
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
/// VirtualDom to be created just to retrive its channel receiver.
/// VirtualDom to be created just to retrieve its channel receiver.
pub fn new_with_props_and_scheduler<P: 'static + Send>(
root: FC<P>,
root_props: P,
@@ -173,7 +173,7 @@ impl VirtualDom {
/// Get the [`Scope`] for the root component.
///
/// This is useful for traversing the tree from the root for heuristics or altnerative renderers that use Dioxus
/// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
/// directly.
pub fn base_scope(&self) -> &Scope {
self.scheduler.pool.get_scope(self.base_scope).unwrap()
@@ -186,7 +186,7 @@ impl VirtualDom {
/// Update the root props of this VirtualDOM.
///
/// This method retuns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
/// This method returns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
/// so calling this method will block the main thread until computation is done.
///
/// ## Example
@@ -258,7 +258,7 @@ impl VirtualDom {
/// Compute a manual diff of the VirtualDOM between states.
///
/// This can be useful when state inside the DOM is remotely changed from the outside, but not propogated as an event.
/// This can be useful when state inside the DOM is remotely changed from the outside, but not propagated as an event.
///
/// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
/// to force an update of the DOM when the state of the app is changed outside of the app.