Pārlūkot izejas kodu

Add flush_sync and pre_render methods to core

Jonathan Kelley 1 gadu atpakaļ
vecāks
revīzija
7b0dcb3206

+ 2 - 0
packages/core/Cargo.toml

@@ -14,12 +14,14 @@ rustc-hash = { workspace = true }
 longest-increasing-subsequence = "0.1.0"
 longest-increasing-subsequence = "0.1.0"
 futures-util = { workspace = true, default-features = false, features = [
 futures-util = { workspace = true, default-features = false, features = [
     "alloc",
     "alloc",
+    "std",
 ] }
 ] }
 slab = { workspace = true }
 slab = { workspace = true }
 futures-channel = { workspace = true }
 futures-channel = { workspace = true }
 tracing = { workspace = true }
 tracing = { workspace = true }
 serde = { version = "1", features = ["derive"], optional = true }
 serde = { version = "1", features = ["derive"], optional = true }
 
 
+
 [dev-dependencies]
 [dev-dependencies]
 tokio = { workspace = true, features = ["full"] }
 tokio = { workspace = true, features = ["full"] }
 dioxus = { workspace = true }
 dioxus = { workspace = true }

+ 2 - 10
packages/core/README.md

@@ -41,7 +41,7 @@ use dioxus::prelude::*;
 fn app() -> Element {
 fn app() -> Element {
     rsx!{
     rsx!{
         div { "hello world" }
         div { "hello world" }
-    })
+    }
 }
 }
 
 
 fn main() {
 fn main() {
@@ -49,13 +49,8 @@ fn main() {
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     // The initial render of the dom will generate a stream of edits for the real dom to apply
     // 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);
+    let mutations = dom.rebuild_to_vec();
 }
 }
-
-# fn apply_edits_to_real_dom(mutations: Mutations) {}
 ```
 ```
 
 
 
 
@@ -68,9 +63,6 @@ We can then wait for any asynchronous components or pending futures using the `w
 # async fn wait(mut dom: VirtualDom) {
 # async fn wait(mut dom: VirtualDom) {
 // Wait for the dom to be marked dirty internally
 // Wait for the dom to be marked dirty internally
 dom.wait_for_work().await;
 dom.wait_for_work().await;
-
-// Or wait for a deadline and then collect edits
-let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
 # }
 # }
 ```
 ```
 
 

+ 1 - 1
packages/core/src/arena.rs

@@ -70,7 +70,7 @@ impl VirtualDom {
     pub(crate) fn drop_scope(&mut self, id: ScopeId) {
     pub(crate) fn drop_scope(&mut self, id: ScopeId) {
         let height = {
         let height = {
             let scope = self.scopes.remove(id.0);
             let scope = self.scopes.remove(id.0);
-            let context = scope.context();
+            let context = scope.state();
             context.height
             context.height
         };
         };
 
 

+ 3 - 3
packages/core/src/diff/component.rs

@@ -82,7 +82,7 @@ impl VNode {
             tracing::trace!(
             tracing::trace!(
                 "Memoized props for component {:#?} ({})",
                 "Memoized props for component {:#?} ({})",
                 scope_id,
                 scope_id,
-                old_scope.context().name
+                old_scope.state().name
             );
             );
             return;
             return;
         }
         }
@@ -94,7 +94,7 @@ impl VNode {
         let new = dom.run_scope(scope_id);
         let new = dom.run_scope(scope_id);
         dom.diff_scope(to, scope_id, new);
         dom.diff_scope(to, scope_id, new);
 
 
-        let height = dom.runtime.get_context(scope_id).unwrap().height;
+        let height = dom.runtime.get_state(scope_id).unwrap().height;
         dom.dirty_scopes.remove(&DirtyScope {
         dom.dirty_scopes.remove(&DirtyScope {
             height,
             height,
             id: scope_id,
             id: scope_id,
@@ -130,7 +130,7 @@ impl VNode {
         // Load up a ScopeId for this vcomponent. If it's already mounted, then we can just use that
         // Load up a ScopeId for this vcomponent. If it's already mounted, then we can just use that
         let scope = dom
         let scope = dom
             .new_scope(component.props.duplicate(), component.name)
             .new_scope(component.props.duplicate(), component.name)
-            .context()
+            .state()
             .id;
             .id;
 
 
         // Store the scope id for the next render
         // Store the scope id for the next render

+ 48 - 11
packages/core/src/global_context.rs

@@ -1,8 +1,6 @@
-use std::sync::Arc;
-
-use futures_util::Future;
-
 use crate::{runtime::Runtime, Element, ScopeId, Task};
 use crate::{runtime::Runtime, Element, ScopeId, Task};
+use futures_util::{future::poll_fn, Future};
+use std::sync::Arc;
 
 
 /// Get the current scope id
 /// Get the current scope id
 pub fn current_scope_id() -> Option<ScopeId> {
 pub fn current_scope_id() -> Option<ScopeId> {
@@ -30,7 +28,7 @@ pub fn consume_context<T: 'static + Clone>() -> T {
 /// Consume context from the current scope
 /// Consume context from the current scope
 pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
 pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
     Runtime::with(|rt| {
     Runtime::with(|rt| {
-        rt.get_context(scope_id)
+        rt.get_state(scope_id)
             .and_then(|cx| cx.consume_context::<T>())
             .and_then(|cx| cx.consume_context::<T>())
     })
     })
     .flatten()
     .flatten()
@@ -53,9 +51,7 @@ pub fn provide_root_context<T: 'static + Clone>(value: T) -> Option<T> {
 
 
 /// Suspends the current component
 /// Suspends the current component
 pub fn suspend() -> Option<Element> {
 pub fn suspend() -> Option<Element> {
-    Runtime::with_current_scope(|cx| {
-        cx.suspend();
-    });
+    Runtime::with_current_scope(|cx| cx.suspend());
     None
     None
 }
 }
 
 
@@ -87,7 +83,7 @@ pub fn remove_future(id: Task) {
 /// # Example
 /// # Example
 ///
 ///
 /// ```
 /// ```
-/// use dioxus_core::ScopeState;
+/// use dioxus_core::use_hook;
 ///
 ///
 /// // prints a greeting on the initial render
 /// // prints a greeting on the initial render
 /// pub fn use_hello_world() {
 /// pub fn use_hello_world() {
@@ -98,6 +94,47 @@ pub fn use_hook<State: Clone + 'static>(initializer: impl FnOnce() -> State) ->
     Runtime::with_current_scope(|cx| cx.use_hook(initializer)).expect("to be in a dioxus runtime")
     Runtime::with_current_scope(|cx| cx.use_hook(initializer)).expect("to be in a dioxus runtime")
 }
 }
 
 
+/// Push a function to be run before the next render
+/// This is a hook and will always run, so you can't unschedule it
+/// Will run for every progression of suspense, though this might change in the future
+pub fn use_before_render(f: impl FnMut() + 'static) {
+    Runtime::with_current_scope(|cx| cx.push_before_render(f));
+}
+
+/// Wait for the virtualdom to finish its sync work before proceeding
+///
+/// This is useful if you've just triggered an update and want to wait for it to finish before proceeding with valid
+/// DOM nodes.
+pub async fn flush_sync() {
+    let mut polled = false;
+
+    let _task =
+        FlushKey(Runtime::with(|rt| rt.add_to_flush_table()).expect("to be in a dioxus runtime"));
+
+    // Poll without giving the waker to anyone
+    // The runtime will manually wake this task up when it's ready
+    poll_fn(|_| {
+        if !polled {
+            polled = true;
+            futures_util::task::Poll::Pending
+        } else {
+            futures_util::task::Poll::Ready(())
+        }
+    })
+    .await;
+
+    // If the the future got polled, then we don't need to prevent it from being dropped
+    // This would all be solved with generational indicies on tasks
+    std::mem::forget(_task);
+
+    struct FlushKey(Task);
+    impl Drop for FlushKey {
+        fn drop(&mut self) {
+            Runtime::with(|rt| rt.flush_table.borrow_mut().remove(&self.0));
+        }
+    }
+}
+
 /// Get the current render since the inception of this component
 /// Get the current render since the inception of this component
 ///
 ///
 /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
 /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
@@ -138,7 +175,7 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
 /// (created with [`use_effect`](crate::use_effect)).
 /// (created with [`use_effect`](crate::use_effect)).
 ///
 ///
 /// Example:
 /// Example:
-/// ```rust
+/// ```rust, ignore
 /// use dioxus::prelude::*;
 /// use dioxus::prelude::*;
 ///
 ///
 /// fn app() -> Element {
 /// fn app() -> Element {
@@ -176,7 +213,7 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
 ///         }
 ///         }
 ///     });
 ///     });
 ///
 ///
-///     use_on_destroy({
+///     use_drop({
 ///         to_owned![original_scroll_position];
 ///         to_owned![original_scroll_position];
 ///         /// restore scroll to the top of the page
 ///         /// restore scroll to the top of the page
 ///         move || {
 ///         move || {

+ 9 - 8
packages/core/src/lib.rs

@@ -59,6 +59,7 @@ pub(crate) mod innerlude {
     ///     example()
     ///     example()
     /// )
     /// )
     /// ```
     /// ```
+    ///
     /// ## React-Style
     /// ## React-Style
     /// ```rust, ignore
     /// ```rust, ignore
     /// fn Example(cx: Props) -> Element {
     /// fn Example(cx: Props) -> Element {
@@ -86,14 +87,14 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
 pub mod prelude {
     pub use crate::innerlude::{
     pub use crate::innerlude::{
-        consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation,
-        has_context, needs_update, parent_scope, provide_context, provide_root_context,
+        consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, flush_sync,
+        generation, has_context, needs_update, parent_scope, provide_context, provide_root_context,
         remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
         remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
-        try_consume_context, use_drop, use_error_boundary, use_hook, use_hook_with_cleanup,
-        AnyValue, Attribute, Component, ComponentFunction, Element, ErrorBoundary, Event,
-        EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoDynNode,
-        OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, SuperFrom,
-        SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner,
-        VirtualDom,
+        try_consume_context, use_before_render, use_drop, use_error_boundary, use_hook,
+        use_hook_with_cleanup, AnyValue, Attribute, Component, ComponentFunction, Element,
+        ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
+        IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId,
+        ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw,
+        VNode, VNodeInner, VirtualDom,
     };
     };
 }
 }

+ 2 - 2
packages/core/src/nodes.rs

@@ -105,7 +105,7 @@ pub struct VNodeInner {
     /// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
     /// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
     ///
     ///
     /// For example:
     /// For example:
-    /// ```rust
+    /// ```rust, ignore
     /// div {
     /// div {
     ///     class: "{class}",
     ///     class: "{class}",
     ///     ..attrs,
     ///     ..attrs,
@@ -116,7 +116,7 @@ pub struct VNodeInner {
     /// ```
     /// ```
     ///
     ///
     /// Would be represented as:
     /// Would be represented as:
-    /// ```rust
+    /// ```rust, ignore
     /// [
     /// [
     ///     [class, every attribute in attrs sorted by name], // Slot 0 in the template
     ///     [class, every attribute in attrs sorted by name], // Slot 0 in the template
     ///     [color], // Slot 1 in the template
     ///     [color], // Slot 1 in the template

+ 36 - 41
packages/core/src/runtime.rs

@@ -1,12 +1,13 @@
+use rustc_hash::FxHashSet;
+
 use crate::{
 use crate::{
     innerlude::{LocalTask, SchedulerMsg},
     innerlude::{LocalTask, SchedulerMsg},
-    scope_context::ScopeContext,
+    scope_context::Scope,
     scopes::ScopeId,
     scopes::ScopeId,
     Task,
     Task,
 };
 };
 use std::{
 use std::{
     cell::{Cell, Ref, RefCell},
     cell::{Cell, Ref, RefCell},
-    collections::VecDeque,
     rc::Rc,
     rc::Rc,
 };
 };
 
 
@@ -16,7 +17,7 @@ thread_local! {
 
 
 /// A global runtime that is shared across all scopes that provides the async runtime and context API
 /// A global runtime that is shared across all scopes that provides the async runtime and context API
 pub struct Runtime {
 pub struct Runtime {
-    pub(crate) scope_contexts: RefCell<Vec<Option<ScopeContext>>>,
+    pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
 
 
     // We use this to track the current scope
     // We use this to track the current scope
     pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
     pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
@@ -29,10 +30,10 @@ pub struct Runtime {
     /// Tasks created with cx.spawn
     /// Tasks created with cx.spawn
     pub(crate) tasks: RefCell<slab::Slab<Rc<LocalTask>>>,
     pub(crate) tasks: RefCell<slab::Slab<Rc<LocalTask>>>,
 
 
-    /// Queued tasks that are waiting to be polled
-    pub(crate) queued_tasks: Rc<RefCell<VecDeque<Task>>>,
-
     pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
     pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+
+    // Tasks waiting to be manually resumed when we call wait_for_work
+    pub(crate) flush_table: RefCell<FxHashSet<Task>>,
 }
 }
 
 
 impl Runtime {
 impl Runtime {
@@ -40,11 +41,11 @@ impl Runtime {
         Rc::new(Self {
         Rc::new(Self {
             sender,
             sender,
             rendering: Cell::new(true),
             rendering: Cell::new(true),
-            scope_contexts: Default::default(),
+            scope_states: Default::default(),
             scope_stack: Default::default(),
             scope_stack: Default::default(),
             current_task: Default::default(),
             current_task: Default::default(),
             tasks: Default::default(),
             tasks: Default::default(),
-            queued_tasks: Rc::new(RefCell::new(VecDeque::new())),
+            flush_table: Default::default(),
         })
         })
     }
     }
 
 
@@ -54,35 +55,39 @@ impl Runtime {
     }
     }
 
 
     /// Create a scope context. This slab is synchronized with the scope slab.
     /// Create a scope context. This slab is synchronized with the scope slab.
-    pub(crate) fn create_context_at(&self, id: ScopeId, context: ScopeContext) {
-        let mut contexts = self.scope_contexts.borrow_mut();
-        if contexts.len() <= id.0 {
-            contexts.resize_with(id.0 + 1, Default::default);
+    pub(crate) fn create_scope(&self, context: Scope) {
+        let id = context.id;
+        let mut scopes = self.scope_states.borrow_mut();
+        if scopes.len() <= id.0 {
+            scopes.resize_with(id.0 + 1, Default::default);
         }
         }
-        contexts[id.0] = Some(context);
+        scopes[id.0] = Some(context);
     }
     }
 
 
-    pub(crate) fn remove_context(self: &Rc<Self>, id: ScopeId) {
+    pub(crate) fn remove_scope(self: &Rc<Self>, id: ScopeId) {
         {
         {
-            let borrow = self.scope_contexts.borrow();
+            let borrow = self.scope_states.borrow();
             if let Some(scope) = &borrow[id.0] {
             if let Some(scope) = &borrow[id.0] {
                 let _runtime_guard = RuntimeGuard::new(self.clone());
                 let _runtime_guard = RuntimeGuard::new(self.clone());
                 // Manually drop tasks, hooks, and contexts inside of the runtime
                 // Manually drop tasks, hooks, and contexts inside of the runtime
                 self.on_scope(id, || {
                 self.on_scope(id, || {
-                    // Drop all spawned tasks
+                    // Drop all spawned tasks - order doesn't matter since tasks don't rely on eachother
+                    // In theory nested tasks might not like this
                     for id in scope.spawned_tasks.take() {
                     for id in scope.spawned_tasks.take() {
                         self.remove_task(id);
                         self.remove_task(id);
                     }
                     }
 
 
-                    // Drop all hooks
-                    scope.hooks.take();
+                    // Drop all hooks in reverse order in case a hook depends on another hook.
+                    for hook in scope.hooks.take().drain(..).rev() {
+                        drop(hook);
+                    }
 
 
                     // Drop all contexts
                     // Drop all contexts
                     scope.shared_contexts.take();
                     scope.shared_contexts.take();
                 });
                 });
             }
             }
         }
         }
-        self.scope_contexts.borrow_mut()[id.0].take();
+        self.scope_states.borrow_mut()[id.0].take();
     }
     }
 
 
     /// Get the current scope id
     /// Get the current scope id
@@ -104,11 +109,11 @@ impl Runtime {
         o
         o
     }
     }
 
 
-    /// Get the context for any scope given its ID
+    /// Get the state for any scope given its ID
     ///
     ///
     /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
     /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
-    pub(crate) fn get_context(&self, id: ScopeId) -> Option<Ref<'_, ScopeContext>> {
-        Ref::filter_map(self.scope_contexts.borrow(), |contexts| {
+    pub(crate) fn get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
+        Ref::filter_map(self.scope_states.borrow(), |contexts| {
             contexts.get(id.0).and_then(|f| f.as_ref())
             contexts.get(id.0).and_then(|f| f.as_ref())
         })
         })
         .ok()
         .ok()
@@ -125,34 +130,22 @@ impl Runtime {
     }
     }
 
 
     /// Runs a function with the current runtime
     /// Runs a function with the current runtime
-    pub(crate) fn with<F, R>(f: F) -> Option<R>
-    where
-        F: FnOnce(&Runtime) -> R,
-    {
-        RUNTIMES.with(|stack| {
-            let stack = stack.borrow();
-            stack.last().map(|r| f(r))
-        })
+    pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> Option<R> {
+        RUNTIMES.with(|stack| stack.borrow().last().map(|r| f(r)))
     }
     }
 
 
     /// Runs a function with the current scope
     /// Runs a function with the current scope
-    pub(crate) fn with_current_scope<F, R>(f: F) -> Option<R>
-    where
-        F: FnOnce(&ScopeContext) -> R,
-    {
+    pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Option<R> {
         Self::with(|rt| {
         Self::with(|rt| {
             rt.current_scope_id()
             rt.current_scope_id()
-                .and_then(|scope| rt.get_context(scope).map(|sc| f(&sc)))
+                .and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
         })
         })
         .flatten()
         .flatten()
     }
     }
 
 
     /// Runs a function with the current scope
     /// Runs a function with the current scope
-    pub(crate) fn with_scope<F, R>(scope: ScopeId, f: F) -> Option<R>
-    where
-        F: FnOnce(&ScopeContext) -> R,
-    {
-        Self::with(|rt| rt.get_context(scope).map(|sc| f(&sc))).flatten()
+    pub(crate) fn with_scope<R>(scope: ScopeId, f: impl FnOnce(&Scope) -> R) -> Option<R> {
+        Self::with(|rt| rt.get_state(scope).map(|sc| f(&sc))).flatten()
     }
     }
 }
 }
 
 
@@ -182,7 +175,9 @@ impl Runtime {
 /// }
 /// }
 ///
 ///
 /// fn Component(cx: ComponentProps) -> Element {
 /// fn Component(cx: ComponentProps) -> Element {
-///     cx.use_hook(|| RuntimeGuard::new(cx.runtime.clone()));
+///     use_hook(|| {
+///         let _guard = RuntimeGuard::new(cx.runtime.clone());
+///     });
 ///
 ///
 ///     rsx! { div {} }
 ///     rsx! { div {} }
 /// }
 /// }

+ 18 - 9
packages/core/src/scope_arena.rs

@@ -2,7 +2,7 @@ use crate::{
     any_props::{AnyProps, BoxedAnyProps},
     any_props::{AnyProps, BoxedAnyProps},
     innerlude::{DirtyScope, ScopeState},
     innerlude::{DirtyScope, ScopeState},
     nodes::RenderReturn,
     nodes::RenderReturn,
-    scope_context::ScopeContext,
+    scope_context::Scope,
     scopes::ScopeId,
     scopes::ScopeId,
     virtual_dom::VirtualDom,
     virtual_dom::VirtualDom,
 };
 };
@@ -11,7 +11,7 @@ impl VirtualDom {
     pub(super) fn new_scope(&mut self, props: BoxedAnyProps, name: &'static str) -> &ScopeState {
     pub(super) fn new_scope(&mut self, props: BoxedAnyProps, name: &'static str) -> &ScopeState {
         let parent_id = self.runtime.current_scope_id();
         let parent_id = self.runtime.current_scope_id();
         let height = parent_id
         let height = parent_id
-            .and_then(|parent_id| self.runtime.get_context(parent_id).map(|f| f.height + 1))
+            .and_then(|parent_id| self.runtime.get_state(parent_id).map(|f| f.height + 1))
             .unwrap_or(0);
             .unwrap_or(0);
         let entry = self.scopes.vacant_entry();
         let entry = self.scopes.vacant_entry();
         let id = ScopeId(entry.key());
         let id = ScopeId(entry.key());
@@ -19,34 +19,43 @@ impl VirtualDom {
         let scope = entry.insert(ScopeState {
         let scope = entry.insert(ScopeState {
             runtime: self.runtime.clone(),
             runtime: self.runtime.clone(),
             context_id: id,
             context_id: id,
-
             props,
             props,
             last_rendered_node: Default::default(),
             last_rendered_node: Default::default(),
         });
         });
 
 
-        let context = ScopeContext::new(name, id, parent_id, height);
-        self.runtime.create_context_at(id, context);
+        self.runtime
+            .create_scope(Scope::new(name, id, parent_id, height));
 
 
         scope
         scope
     }
     }
 
 
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn {
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn {
-        self.runtime.scope_stack.borrow_mut().push(scope_id);
+        debug_assert!(
+            crate::Runtime::current().is_some(),
+            "Must be in a dioxus runtime"
+        );
 
 
+        self.runtime.scope_stack.borrow_mut().push(scope_id);
         let scope = &self.scopes[scope_id.0];
         let scope = &self.scopes[scope_id.0];
         let new_nodes = {
         let new_nodes = {
-            let context = scope.context();
+            let context = scope.state();
+
             context.suspended.set(false);
             context.suspended.set(false);
             context.hook_index.set(0);
             context.hook_index.set(0);
 
 
+            // Run all pre-render hooks
+            for pre_run in context.before_render.borrow_mut().iter_mut() {
+                pre_run();
+            }
+
             // safety: due to how we traverse the tree, we know that the scope is not currently aliased
             // safety: due to how we traverse the tree, we know that the scope is not currently aliased
             let props: &dyn AnyProps = &*scope.props;
             let props: &dyn AnyProps = &*scope.props;
 
 
-            let span = tracing::trace_span!("render", scope = %scope.context().name);
+            let span = tracing::trace_span!("render", scope = %scope.state().name);
             span.in_scope(|| props.render())
             span.in_scope(|| props.render())
         };
         };
 
 
-        let context = scope.context();
+        let context = scope.state();
 
 
         // And move the render generation forward by one
         // And move the render generation forward by one
         context.render_count.set(context.render_count.get() + 1);
         context.render_count.set(context.render_count.get() + 1);

+ 13 - 8
packages/core/src/scope_context.rs

@@ -10,7 +10,7 @@ use std::{
 /// A component's state separate from its props.
 /// A component's state separate from its props.
 ///
 ///
 /// This struct exists to provide a common interface for all scopes without relying on generics.
 /// This struct exists to provide a common interface for all scopes without relying on generics.
-pub(crate) struct ScopeContext {
+pub(crate) struct Scope {
     pub(crate) name: &'static str,
     pub(crate) name: &'static str,
     pub(crate) id: ScopeId,
     pub(crate) id: ScopeId,
     pub(crate) parent_id: Option<ScopeId>,
     pub(crate) parent_id: Option<ScopeId>,
@@ -21,13 +21,12 @@ pub(crate) struct ScopeContext {
     // Note: the order of the hook and context fields is important. The hooks field must be dropped before the contexts field in case a hook drop implementation tries to access a context.
     // Note: the order of the hook and context fields is important. The hooks field must be dropped before the contexts field in case a hook drop implementation tries to access a context.
     pub(crate) hooks: RefCell<Vec<Box<dyn Any>>>,
     pub(crate) hooks: RefCell<Vec<Box<dyn Any>>>,
     pub(crate) hook_index: Cell<usize>,
     pub(crate) hook_index: Cell<usize>,
-
     pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
     pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
-
     pub(crate) spawned_tasks: RefCell<FxHashSet<Task>>,
     pub(crate) spawned_tasks: RefCell<FxHashSet<Task>>,
+    pub(crate) before_render: RefCell<Vec<Box<dyn FnMut()>>>,
 }
 }
 
 
-impl ScopeContext {
+impl Scope {
     pub(crate) fn new(
     pub(crate) fn new(
         name: &'static str,
         name: &'static str,
         id: ScopeId,
         id: ScopeId,
@@ -45,6 +44,7 @@ impl ScopeContext {
             spawned_tasks: RefCell::new(FxHashSet::default()),
             spawned_tasks: RefCell::new(FxHashSet::default()),
             hooks: RefCell::new(vec![]),
             hooks: RefCell::new(vec![]),
             hook_index: Cell::new(0),
             hook_index: Cell::new(0),
+            before_render: RefCell::new(vec![]),
         }
         }
     }
     }
 
 
@@ -109,7 +109,7 @@ impl ScopeContext {
         let mut search_parent = self.parent_id;
         let mut search_parent = self.parent_id;
         let cur_runtime = Runtime::with(|runtime| {
         let cur_runtime = Runtime::with(|runtime| {
             while let Some(parent_id) = search_parent {
             while let Some(parent_id) = search_parent {
-                let parent = runtime.get_context(parent_id).unwrap();
+                let parent = runtime.get_state(parent_id).unwrap();
                 tracing::trace!(
                 tracing::trace!(
                     "looking for context {} ({:?}) in {}",
                     "looking for context {} ({:?}) in {}",
                     std::any::type_name::<T>(),
                     std::any::type_name::<T>(),
@@ -212,7 +212,7 @@ impl ScopeContext {
     pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
     pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
         Runtime::with(|runtime| {
         Runtime::with(|runtime| {
             runtime
             runtime
-                .get_context(ScopeId::ROOT)
+                .get_state(ScopeId::ROOT)
                 .unwrap()
                 .unwrap()
                 .provide_context(context)
                 .provide_context(context)
         })
         })
@@ -256,9 +256,10 @@ impl ScopeContext {
     /// # Example
     /// # Example
     ///
     ///
     /// ```
     /// ```
+    /// # use dioxus::prelude::*;
     /// // prints a greeting on the initial render
     /// // prints a greeting on the initial render
     /// pub fn use_hello_world() {
     /// pub fn use_hello_world() {
-    ///     cx.use_hook(|| println!("Hello, world!"));
+    ///     use_hook(|| println!("Hello, world!"));
     /// }
     /// }
     /// ```
     /// ```
     pub fn use_hook<State: Clone + 'static>(&self, initializer: impl FnOnce() -> State) -> State {
     pub fn use_hook<State: Clone + 'static>(&self, initializer: impl FnOnce() -> State) -> State {
@@ -287,6 +288,10 @@ impl ScopeContext {
             )
             )
     }
     }
 
 
+    pub fn push_before_render(&self, f: impl FnMut() + 'static) {
+        self.before_render.borrow_mut().push(Box::new(f));
+    }
+
     /// Get the current render since the inception of this component
     /// Get the current render since the inception of this component
     ///
     ///
     /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
     /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
@@ -320,7 +325,7 @@ impl ScopeId {
     /// Consume context from the current scope
     /// Consume context from the current scope
     pub fn consume_context_from_scope<T: 'static + Clone>(self, scope_id: ScopeId) -> Option<T> {
     pub fn consume_context_from_scope<T: 'static + Clone>(self, scope_id: ScopeId) -> Option<T> {
         Runtime::with(|rt| {
         Runtime::with(|rt| {
-            rt.get_context(scope_id)
+            rt.get_state(scope_id)
                 .and_then(|cx| cx.consume_context::<T>())
                 .and_then(|cx| cx.consume_context::<T>())
         })
         })
         .flatten()
         .flatten()

+ 4 - 4
packages/core/src/scopes.rs

@@ -1,5 +1,5 @@
 use crate::{
 use crate::{
-    any_props::BoxedAnyProps, nodes::RenderReturn, runtime::Runtime, scope_context::ScopeContext,
+    any_props::BoxedAnyProps, nodes::RenderReturn, runtime::Runtime, scope_context::Scope,
 };
 };
 use std::{cell::Ref, fmt::Debug, rc::Rc};
 use std::{cell::Ref, fmt::Debug, rc::Rc};
 
 
@@ -39,7 +39,7 @@ pub struct ScopeState {
 
 
 impl Drop for ScopeState {
 impl Drop for ScopeState {
     fn drop(&mut self) {
     fn drop(&mut self) {
-        self.runtime.remove_context(self.context_id);
+        self.runtime.remove_scope(self.context_id);
     }
     }
 }
 }
 
 
@@ -63,7 +63,7 @@ impl ScopeState {
         self.last_rendered_node.as_ref()
         self.last_rendered_node.as_ref()
     }
     }
 
 
-    pub(crate) fn context(&self) -> Ref<'_, ScopeContext> {
-        self.runtime.get_context(self.context_id).unwrap()
+    pub(crate) fn state(&self) -> Ref<'_, Scope> {
+        self.runtime.get_state(self.context_id).unwrap()
     }
     }
 }
 }

+ 53 - 34
packages/core/src/tasks.rs

@@ -1,10 +1,10 @@
 use crate::innerlude::{remove_future, spawn, Runtime};
 use crate::innerlude::{remove_future, spawn, Runtime};
 use crate::ScopeId;
 use crate::ScopeId;
 use futures_util::task::ArcWake;
 use futures_util::task::ArcWake;
-use std::future::Future;
 use std::pin::Pin;
 use std::pin::Pin;
 use std::sync::Arc;
 use std::sync::Arc;
 use std::task::Waker;
 use std::task::Waker;
+use std::{cell::Cell, future::Future};
 use std::{cell::RefCell, rc::Rc};
 use std::{cell::RefCell, rc::Rc};
 
 
 /// A task's unique identifier.
 /// A task's unique identifier.
@@ -34,6 +34,25 @@ impl Task {
     pub fn stop(self) {
     pub fn stop(self) {
         remove_future(self);
         remove_future(self);
     }
     }
+
+    /// Pause the task.
+    pub fn pause(&self) {
+        Runtime::with(|rt| rt.tasks.borrow()[self.0].active.set(false));
+    }
+
+    /// Check if the task is paused.
+    pub fn paused(&self) -> bool {
+        Runtime::with(|rt| !rt.tasks.borrow()[self.0].active.get()).unwrap_or_default()
+    }
+
+    /// Resume the task.
+    pub fn resume(&self) {
+        Runtime::with(|rt| {
+            // set the active flag, and then ping the scheduler to ensure the task gets queued
+            rt.tasks.borrow()[self.0].active.set(true);
+            _ = rt.sender.unbounded_send(SchedulerMsg::TaskNotified(*self));
+        });
+    }
 }
 }
 
 
 impl Runtime {
 impl Runtime {
@@ -55,9 +74,10 @@ impl Runtime {
             let task_id = Task(entry.key());
             let task_id = Task(entry.key());
 
 
             let task = Rc::new(LocalTask {
             let task = Rc::new(LocalTask {
+                scope,
+                active: Cell::new(true),
                 parent: self.current_task(),
                 parent: self.current_task(),
                 task: RefCell::new(Box::pin(task)),
                 task: RefCell::new(Box::pin(task)),
-                scope,
                 waker: futures_util::task::waker(Arc::new(LocalTaskHandle {
                 waker: futures_util::task::waker(Arc::new(LocalTaskHandle {
                     id: task_id,
                     id: task_id,
                     tx: self.sender.clone(),
                     tx: self.sender.clone(),
@@ -73,18 +93,33 @@ impl Runtime {
         debug_assert!(self.tasks.try_borrow_mut().is_ok());
         debug_assert!(self.tasks.try_borrow_mut().is_ok());
         debug_assert!(task.task.try_borrow_mut().is_ok());
         debug_assert!(task.task.try_borrow_mut().is_ok());
 
 
-        let mut cx = std::task::Context::from_waker(&task.waker);
-
-        if !task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
-            self.sender
-                .unbounded_send(SchedulerMsg::TaskNotified(task_id))
-                .expect("Scheduler should exist");
-        }
+        self.sender
+            .unbounded_send(SchedulerMsg::TaskNotified(task_id))
+            .expect("Scheduler should exist");
 
 
         task_id
         task_id
     }
     }
 
 
+    /// Get the currently running task
+    pub fn current_task(&self) -> Option<Task> {
+        self.current_task.get()
+    }
+
+    /// Get the parent task of the given task, if it exists
+    pub fn parent_task(&self, task: Task) -> Option<Task> {
+        self.tasks.borrow().get(task.0)?.parent
+    }
+
+    /// Add this task to the queue of tasks that will manually get poked when the scheduler is flushed
+    pub(crate) fn add_to_flush_table(&self) -> Task {
+        let value = self.current_task().unwrap();
+        self.flush_table.borrow_mut().insert(value);
+        value
+    }
+
     pub(crate) fn handle_task_wakeup(&self, id: Task) {
     pub(crate) fn handle_task_wakeup(&self, id: Task) {
+        debug_assert!(Runtime::current().is_some(), "Must be in a dioxus runtime");
+
         let task = self.tasks.borrow().get(id.0).cloned();
         let task = self.tasks.borrow().get(id.0).cloned();
 
 
         // The task was removed from the scheduler, so we can just ignore it
         // The task was removed from the scheduler, so we can just ignore it
@@ -92,6 +127,11 @@ impl Runtime {
             return;
             return;
         };
         };
 
 
+        // If a task woke up but is paused, we can just ignore it
+        if !task.active.get() {
+            return;
+        }
+
         let mut cx = std::task::Context::from_waker(&task.waker);
         let mut cx = std::task::Context::from_waker(&task.waker);
 
 
         // update the scope stack
         // update the scope stack
@@ -99,10 +139,9 @@ impl Runtime {
         self.rendering.set(false);
         self.rendering.set(false);
         self.current_task.set(Some(id));
         self.current_task.set(Some(id));
 
 
-        // If the task completes...
         if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
         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
             // Remove it from the scope so we dont try to double drop it when the scope dropes
-            self.get_context(task.scope)
+            self.get_state(task.scope)
                 .unwrap()
                 .unwrap()
                 .spawned_tasks
                 .spawned_tasks
                 .borrow_mut()
                 .borrow_mut()
@@ -118,31 +157,11 @@ impl Runtime {
         self.current_task.set(None);
         self.current_task.set(None);
     }
     }
 
 
-    /// Take a queued task from the scheduler
-    pub(crate) fn take_queued_task(&self) -> Option<Task> {
-        self.queued_tasks.borrow_mut().pop_front()
-    }
-
     /// Drop the future with the given TaskId
     /// Drop the future with the given TaskId
     ///
     ///
     /// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
     /// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
     pub(crate) fn remove_task(&self, id: Task) -> Option<Rc<LocalTask>> {
     pub(crate) fn remove_task(&self, id: Task) -> Option<Rc<LocalTask>> {
-        let task = self.tasks.borrow_mut().try_remove(id.0);
-
-        // Remove the task from the queued tasks so we don't poll a different task with the same id
-        self.queued_tasks.borrow_mut().retain(|t| *t != id);
-
-        task
-    }
-
-    /// Get the currently running task
-    pub fn current_task(&self) -> Option<Task> {
-        self.current_task.get()
-    }
-
-    /// Get the parent task of the given task, if it exists
-    pub fn parent_task(&self, task: Task) -> Option<Task> {
-        self.tasks.borrow().get(task.0)?.parent
+        self.tasks.borrow_mut().try_remove(id.0)
     }
     }
 }
 }
 
 
@@ -152,6 +171,7 @@ pub(crate) struct LocalTask {
     parent: Option<Task>,
     parent: Option<Task>,
     task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
     task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
     waker: Waker,
     waker: Waker,
+    active: Cell<bool>,
 }
 }
 
 
 /// The type of message that can be sent to the scheduler.
 /// The type of message that can be sent to the scheduler.
@@ -173,8 +193,7 @@ struct LocalTaskHandle {
 
 
 impl ArcWake for LocalTaskHandle {
 impl ArcWake for LocalTaskHandle {
     fn wake_by_ref(arc_self: &Arc<Self>) {
     fn wake_by_ref(arc_self: &Arc<Self>) {
-        // This can fail if the scheduler has been dropped while the application is shutting down
-        let _ = arc_self
+        _ = arc_self
             .tx
             .tx
             .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
             .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
     }
     }

+ 75 - 132
packages/core/src/virtual_dom.rs

@@ -13,12 +13,12 @@ use crate::{
     nodes::{Template, TemplateId},
     nodes::{Template, TemplateId},
     runtime::{Runtime, RuntimeGuard},
     runtime::{Runtime, RuntimeGuard},
     scopes::ScopeId,
     scopes::ScopeId,
-    AttributeValue, ComponentFunction, Element, Event, Mutations, Task,
+    AttributeValue, ComponentFunction, Element, Event, Mutations,
 };
 };
-use futures_util::{pin_mut, StreamExt};
+use futures_util::StreamExt;
 use rustc_hash::{FxHashMap, FxHashSet};
 use rustc_hash::{FxHashMap, FxHashSet};
 use slab::Slab;
 use slab::Slab;
-use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
+use std::{any::Any, collections::BTreeSet, 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.
 ///
 ///
@@ -29,7 +29,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
 /// ```rust
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
 ///
 ///
-/// #[derive(Props, PartialEq)]
+/// #[derive(Props, PartialEq, Clone)]
 /// struct AppProps {
 /// struct AppProps {
 ///     title: String
 ///     title: String
 /// }
 /// }
@@ -37,7 +37,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
 /// fn app(cx: AppProps) -> Element {
 /// fn app(cx: AppProps) -> Element {
 ///     rsx!(
 ///     rsx!(
 ///         div {"hello, {cx.title}"}
 ///         div {"hello, {cx.title}"}
-///     ))
+///     )
 /// }
 /// }
 /// ```
 /// ```
 ///
 ///
@@ -47,7 +47,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
 /// # #![allow(unused)]
 /// # #![allow(unused)]
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
 ///
 ///
-/// # #[derive(Props, PartialEq)]
+/// # #[derive(Props, PartialEq, Clone)]
 /// # struct AppProps {
 /// # struct AppProps {
 /// #     title: String
 /// #     title: String
 /// # }
 /// # }
@@ -60,26 +60,26 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
 ///         NavBar { routes: ROUTES }
 ///         NavBar { routes: ROUTES }
 ///         Title { "{cx.title}" }
 ///         Title { "{cx.title}" }
 ///         Footer {}
 ///         Footer {}
-///     ))
+///     )
 /// }
 /// }
 ///
 ///
 /// #[component]
 /// #[component]
 /// fn NavBar( routes: &'static str) -> Element {
 /// fn NavBar( routes: &'static str) -> Element {
 ///     rsx! {
 ///     rsx! {
 ///         div { "Routes: {routes}" }
 ///         div { "Routes: {routes}" }
-///     })
+///     }
 /// }
 /// }
 ///
 ///
 /// #[component]
 /// #[component]
 /// fn Footer() -> Element {
 /// fn Footer() -> Element {
-///     rsx! { div { "Footer" } })
+///     rsx! { div { "Footer" } }
 /// }
 /// }
 ///
 ///
 /// #[component]
 /// #[component]
-/// fn Title<'a>( children: Element) -> Element {
+/// fn Title( children: Element) -> Element {
 ///     rsx! {
 ///     rsx! {
 ///         div { id: "title", {children} }
 ///         div { id: "title", {children} }
-///     })
+///     }
 /// }
 /// }
 /// ```
 /// ```
 ///
 ///
@@ -88,10 +88,10 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
 ///
 ///
 /// ```rust
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
-/// # fn app() -> Element { rsx! { div {} }) }
+/// # fn app() -> Element { rsx! { div {} } }
 ///
 ///
 /// let mut vdom = VirtualDom::new(app);
 /// let mut vdom = VirtualDom::new(app);
-/// let edits = vdom.rebuild();
+/// let edits = vdom.rebuild_to_vec();
 /// ```
 /// ```
 ///
 ///
 /// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
 /// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
@@ -130,7 +130,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
 /// fn app() -> Element {
 /// fn app() -> Element {
 ///     rsx! {
 ///     rsx! {
 ///         div { "Hello World" }
 ///         div { "Hello World" }
-///     })
+///     }
 /// }
 /// }
 ///
 ///
 /// let dom = VirtualDom::new(app);
 /// let dom = VirtualDom::new(app);
@@ -247,7 +247,7 @@ impl VirtualDom {
     /// }
     /// }
     ///
     ///
     /// fn Example(cx: SomeProps) -> Element  {
     /// fn Example(cx: SomeProps) -> Element  {
-    ///     rsx!{ div{ "hello {cx.name}" } })
+    ///     rsx!{ div { "hello {cx.name}" } }
     /// }
     /// }
     ///
     ///
     /// let dom = VirtualDom::new(Example);
     /// let dom = VirtualDom::new(Example);
@@ -291,7 +291,7 @@ impl VirtualDom {
     /// }
     /// }
     ///
     ///
     /// fn Example(cx: SomeProps) -> Element  {
     /// fn Example(cx: SomeProps) -> Element  {
-    ///     rsx!{ div{ "hello {cx.name}" } })
+    ///     rsx!{ div{ "hello {cx.name}" } }
     /// }
     /// }
     ///
     ///
     /// let dom = VirtualDom::new(Example);
     /// let dom = VirtualDom::new(Example);
@@ -321,7 +321,7 @@ impl VirtualDom {
         let root = dom.new_scope(Box::new(root), "app");
         let root = dom.new_scope(Box::new(root), "app");
 
 
         // 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.context()
+        root.state()
             .provide_context(Rc::new(ErrorBoundary::new_in_scope(ScopeId::ROOT)));
             .provide_context(Rc::new(ErrorBoundary::new_in_scope(ScopeId::ROOT)));
 
 
         // the root element is always given element ID 0 since it's the container for the entire tree
         // the root element is always given element ID 0 since it's the container for the entire tree
@@ -354,7 +354,7 @@ impl VirtualDom {
     ///
     ///
     /// This is useful for what is essentially dependency injection when building the app
     /// This is useful for what is essentially dependency injection when building the app
     pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
     pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
-        self.base_scope().context().provide_context(context);
+        self.base_scope().state().provide_context(context);
         self
         self
     }
     }
 
 
@@ -362,14 +362,14 @@ impl VirtualDom {
     ///
     ///
     /// This method is useful for when you want to provide a context in your app without knowing its type
     /// This method is useful for when you want to provide a context in your app without knowing its type
     pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
     pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
-        self.base_scope().context().provide_any_context(context);
+        self.base_scope().state().provide_any_context(context);
     }
     }
 
 
     /// Manually mark a scope as requiring a re-render
     /// Manually mark a scope as requiring a re-render
     ///
     ///
     /// Whenever the Runtime "works", it will re-render this scope
     /// Whenever the Runtime "works", it will re-render this scope
     pub fn mark_dirty(&mut self, id: ScopeId) {
     pub fn mark_dirty(&mut self, id: ScopeId) {
-        if let Some(context) = self.runtime.get_context(id) {
+        if let Some(context) = self.runtime.get_state(id) {
             let height = context.height();
             let height = context.height();
             tracing::trace!("Marking scope {:?} ({}) as dirty", id, context.name);
             tracing::trace!("Marking scope {:?} ({}) as dirty", id, context.name);
             self.dirty_scopes.insert(DirtyScope { height, id });
             self.dirty_scopes.insert(DirtyScope { height, id });
@@ -417,52 +417,50 @@ impl VirtualDom {
     ///
     ///
     /// ```rust, ignore
     /// ```rust, ignore
     /// let dom = VirtualDom::new(app);
     /// let dom = VirtualDom::new(app);
-    /// let sender = dom.get_scheduler_channel();
     /// ```
     /// ```
     pub async fn wait_for_work(&mut self) {
     pub async fn wait_for_work(&mut self) {
-        let mut some_msg = None;
+        // Ping tasks waiting on the flush table - they're waiting for sync stuff to be done before progressing
+        self.clear_flush_table();
 
 
         loop {
         loop {
-            // If a bunch of messages are ready in a sequence, try to pop them off synchronously
-            if let Some(msg) = some_msg.take() {
-                match msg {
-                    SchedulerMsg::Immediate(id) => self.mark_dirty(id),
-                    SchedulerMsg::TaskNotified(task) => self.queue_task_wakeup(task),
-                }
+            // Process all events - Scopes are marked dirty, etc
+            self.process_events();
+
+            // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures
+            if !self.dirty_scopes.is_empty() || !self.suspended_scopes.is_empty() {
+                return;
             }
             }
 
 
-            // If they're not ready, then we should wait for them to be ready
-            match self.rx.try_next() {
-                Ok(Some(val)) => some_msg = Some(val),
-                Ok(None) => return,
-                Err(_) => {
-                    // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures
-                    let has_dirty_scopes = !self.dirty_scopes.is_empty();
-
-                    if !has_dirty_scopes {
-                        // If we have no dirty scopes, then we should poll any tasks that have been notified
-                        while let Some(task) = self.runtime.take_queued_task() {
-                            self.handle_task_wakeup(task);
-                        }
-                    }
+            // Make sure we set the runtime since we're running user code
+            let _runtime = RuntimeGuard::new(self.runtime.clone());
 
 
-                    // If we have any dirty scopes, or finished fiber trees then we should exit
-                    if has_dirty_scopes || !self.suspended_scopes.is_empty() {
-                        return;
-                    }
+            match self.rx.next().await.expect("channel should never close") {
+                SchedulerMsg::Immediate(id) => self.mark_dirty(id),
+                SchedulerMsg::TaskNotified(id) => self.runtime.handle_task_wakeup(id),
+            };
+        }
+    }
 
 
-                    some_msg = self.rx.next().await
-                }
-            }
+    fn clear_flush_table(&mut self) {
+        // Make sure we set the runtime since we're running user code
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
+
+        // Manually flush tasks that called `flush().await`
+        // Tasks that might've been waiting for `flush` finally have a chance to run to their next await point
+        for task in self.runtime.flush_table.take() {
+            self.runtime.handle_task_wakeup(task);
         }
         }
     }
     }
 
 
     /// Process all events in the queue until there are no more left
     /// Process all events in the queue until there are no more left
     pub fn process_events(&mut self) {
     pub fn process_events(&mut self) {
+        let _runtime = RuntimeGuard::new(self.runtime.clone());
+
+        // Prevent a task from deadlocking the runtime by repeatedly queueing itself
         while let Ok(Some(msg)) = self.rx.try_next() {
         while let Ok(Some(msg)) = self.rx.try_next() {
             match msg {
             match msg {
                 SchedulerMsg::Immediate(id) => self.mark_dirty(id),
                 SchedulerMsg::Immediate(id) => self.mark_dirty(id),
-                SchedulerMsg::TaskNotified(task) => self.queue_task_wakeup(task),
+                SchedulerMsg::TaskNotified(task) => self.runtime.handle_task_wakeup(task),
             }
             }
         }
         }
     }
     }
@@ -481,7 +479,7 @@ impl VirtualDom {
                 if sync.template.get().name.rsplit_once(':').unwrap().0
                 if sync.template.get().name.rsplit_once(':').unwrap().0
                     == template.name.rsplit_once(':').unwrap().0
                     == template.name.rsplit_once(':').unwrap().0
                 {
                 {
-                    let context = scope.context();
+                    let context = scope.state();
                     let height = context.height;
                     let height = context.height;
                     self.dirty_scopes.insert(DirtyScope {
                     self.dirty_scopes.insert(DirtyScope {
                         height,
                         height,
@@ -542,18 +540,25 @@ impl VirtualDom {
     /// suspended subtrees.
     /// suspended subtrees.
     pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
     pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
         self.flush_templates(to);
         self.flush_templates(to);
-        // Build a waker that won't wake up since our deadline is already expired when it's polled
-        let waker = futures_util::task::noop_waker();
-        let mut cx = std::task::Context::from_waker(&waker);
-
-        // Now run render with deadline but dont even try to poll any async tasks
-        let fut = self.render_with_deadline(std::future::ready(()), to);
-        pin_mut!(fut);
-
-        // The root component is not allowed to be async
-        match fut.poll(&mut cx) {
-            std::task::Poll::Ready(mutations) => mutations,
-            std::task::Poll::Pending => panic!("render_immediate should never return pending"),
+
+        // Process any events that might be pending in the queue
+        self.process_events();
+
+        // Next, diff any dirty scopes
+        // We choose not to poll the deadline since we complete pretty quickly anyways
+        while let Some(dirty) = self.dirty_scopes.pop_first() {
+            // If the scope doesn't exist for whatever reason, then we should skip it
+            if !self.scopes.contains(dirty.id.0) {
+                continue;
+            }
+
+            {
+                let _runtime = RuntimeGuard::new(self.runtime.clone());
+                // Run the scope and get the mutations
+                let new_nodes = self.run_scope(dirty.id);
+
+                self.diff_scope(to, dirty.id, new_nodes);
+            }
         }
         }
     }
     }
 
 
@@ -567,72 +572,24 @@ impl VirtualDom {
     /// Render the virtual dom, waiting for all suspense to be finished
     /// 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
     /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
+    ///
+    /// Tasks waiting to be flushed are *cleared* here *without running them*
+    /// This behavior is subject to change, but configured this way so use_future/use_memo/use_future won't react on the server
     pub async fn wait_for_suspense(&mut self) {
     pub async fn wait_for_suspense(&mut self) {
         loop {
         loop {
             if self.suspended_scopes.is_empty() {
             if self.suspended_scopes.is_empty() {
                 return;
                 return;
             }
             }
 
 
+            // not sure if we should be doing this?
+            self.runtime.flush_table.borrow_mut().clear();
+
             self.wait_for_work().await;
             self.wait_for_work().await;
 
 
             self.render_immediate(&mut NoOpMutations);
             self.render_immediate(&mut NoOpMutations);
         }
         }
     }
     }
 
 
-    /// 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.
-    ///
-    /// If no suspense trees are present
-    pub async fn render_with_deadline(
-        &mut self,
-        deadline: impl Future<Output = ()>,
-        to: &mut impl WriteMutations,
-    ) {
-        self.flush_templates(to);
-        pin_mut!(deadline);
-
-        self.process_events();
-
-        loop {
-            // Next, diff any dirty scopes
-            // We choose not to poll the deadline since we complete pretty quickly anyways
-            while let Some(dirty) = self.dirty_scopes.pop_first() {
-                // If the scope doesn't exist for whatever reason, then we should skip it
-                if !self.scopes.contains(dirty.id.0) {
-                    continue;
-                }
-
-                {
-                    let _runtime = RuntimeGuard::new(self.runtime.clone());
-                    // Run the scope and get the mutations
-                    let new_nodes = self.run_scope(dirty.id);
-
-                    self.diff_scope(to, dirty.id, new_nodes);
-                }
-            }
-
-            // Wait until the deadline is ready or we have work if there's no work ready
-            let work = self.wait_for_work();
-            pin_mut!(work);
-
-            use futures_util::future::{select, Either};
-            if let Either::Left((_, _)) = select(&mut deadline, &mut work).await {
-                return;
-            }
-        }
-    }
-
-    /// [`Self::render_with_deadline`] to a vector of mutations for testing purposes
-    pub async fn render_with_deadline_to_vec(
-        &mut self,
-        deadline: impl Future<Output = ()>,
-    ) -> Mutations {
-        let mut mutations = Mutations::default();
-        self.render_with_deadline(deadline, &mut mutations).await;
-        mutations
-    }
-
     /// Get the current runtime
     /// Get the current runtime
     pub fn runtime(&self) -> Rc<Runtime> {
     pub fn runtime(&self) -> Rc<Runtime> {
         self.runtime.clone()
         self.runtime.clone()
@@ -645,20 +602,6 @@ impl VirtualDom {
         }
         }
     }
     }
 
 
-    /// Queue a task to be polled after all dirty scopes have been rendered
-    fn queue_task_wakeup(&mut self, id: Task) {
-        self.runtime.queued_tasks.borrow_mut().push_back(id);
-    }
-
-    /// Handle notifications by tasks inside the scheduler
-    ///
-    /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
-    /// queue
-    fn handle_task_wakeup(&mut self, id: Task) {
-        let _runtime = RuntimeGuard::new(self.runtime.clone());
-        self.runtime.handle_task_wakeup(id);
-    }
-
     /*
     /*
     ------------------------
     ------------------------
     The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
     The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
@@ -764,7 +707,7 @@ impl Drop for VirtualDom {
     fn drop(&mut self) {
     fn drop(&mut self) {
         // Drop all scopes in order of height
         // Drop all scopes in order of height
         let mut scopes = self.scopes.drain().collect::<Vec<_>>();
         let mut scopes = self.scopes.drain().collect::<Vec<_>>();
-        scopes.sort_by_key(|scope| scope.context().height);
+        scopes.sort_by_key(|scope| scope.state().height);
         for scope in scopes.into_iter().rev() {
         for scope in scopes.into_iter().rev() {
             drop(scope);
             drop(scope);
         }
         }

+ 101 - 11
packages/core/tests/task.rs

@@ -1,11 +1,22 @@
 //! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
 //! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
 
 
-#[cfg(not(miri))]
+use std::{sync::atomic::AtomicUsize, time::Duration};
+
+use dioxus::prelude::*;
+
+async fn run_vdom(app: fn() -> Element) {
+    let mut dom = VirtualDom::new(app);
+
+    dom.rebuild(&mut dioxus_core::NoOpMutations);
+
+    tokio::select! {
+        _ = dom.wait_for_work() => {}
+        _ = tokio::time::sleep(Duration::from_millis(500)) => {}
+    };
+}
+
 #[tokio::test]
 #[tokio::test]
 async fn it_works() {
 async fn it_works() {
-    use dioxus::prelude::*;
-    use std::{sync::atomic::AtomicUsize, time::Duration};
-
     static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
     static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
 
 
     fn app() -> Element {
     fn app() -> Element {
@@ -28,19 +39,98 @@ async fn it_works() {
         rsx!({ () })
         rsx!({ () })
     }
     }
 
 
+    run_vdom(app).await;
+
+    // By the time the tasks are finished, we should've accumulated ticks from two tasks
+    // Be warned that by setting the delay to too short, tokio might not schedule in the tasks
+    assert_eq!(
+        POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed),
+        135
+    );
+}
+
+/// Prove that yield_now doesn't cause a deadlock
+#[tokio::test]
+async fn yield_now_works() {
+    thread_local! {
+        static SEQUENCE: std::cell::RefCell<Vec<usize>> = std::cell::RefCell::new(Vec::new());
+    }
+
+    fn app() -> Element {
+        // these two tasks should yield to eachother
+        use_hook(|| {
+            spawn(async move {
+                for x in 0..10 {
+                    tokio::task::yield_now().await;
+                    SEQUENCE.with(|s| s.borrow_mut().push(1));
+                }
+            })
+        });
+
+        use_hook(|| {
+            spawn(async move {
+                for x in 0..10 {
+                    tokio::task::yield_now().await;
+                    SEQUENCE.with(|s| s.borrow_mut().push(2));
+                }
+            })
+        });
+
+        rsx!({ () })
+    }
+
+    run_vdom(app).await;
+
+    SEQUENCE.with(|s| assert_eq!(s.borrow().len(), 20));
+}
+
+/// Ensure that calling wait_for_flush waits for dioxus to finish its syncrhonous work
+#[tokio::test]
+async fn flushing() {
+    thread_local! {
+        static SEQUENCE: std::cell::RefCell<Vec<usize>> = std::cell::RefCell::new(Vec::new());
+    }
+
+    fn app() -> Element {
+        use_hook(|| {
+            spawn(async move {
+                for x in 0..10 {
+                    flush_sync().await;
+                    SEQUENCE.with(|s| s.borrow_mut().push(1));
+                }
+            })
+        });
+
+        use_hook(|| {
+            spawn(async move {
+                for x in 0..10 {
+                    flush_sync().await;
+                    SEQUENCE.with(|s| s.borrow_mut().push(2));
+                }
+            })
+        });
+
+        rsx!({ () })
+    }
+
     let mut dom = VirtualDom::new(app);
     let mut dom = VirtualDom::new(app);
 
 
     dom.rebuild(&mut dioxus_core::NoOpMutations);
     dom.rebuild(&mut dioxus_core::NoOpMutations);
 
 
+    let fut = async {
+        // Trigger the flush by waiting for work
+        for _ in 0..40 {
+            tokio::select! {
+                _ = dom.wait_for_work() => {}
+                _ = tokio::time::sleep(Duration::from_millis(1)) => {}
+            };
+        }
+    };
+
     tokio::select! {
     tokio::select! {
-        _ = dom.wait_for_work() => {}
+        _ = fut => {}
         _ = tokio::time::sleep(Duration::from_millis(500)) => {}
         _ = tokio::time::sleep(Duration::from_millis(500)) => {}
     };
     };
 
 
-    // By the time the tasks are finished, we should've accumulated ticks from two tasks
-    // Be warned that by setting the delay to too short, tokio might not schedule in the tasks
-    assert_eq!(
-        POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed),
-        135
-    );
+    SEQUENCE.with(|s| assert_eq!(s.borrow().len(), 20));
 }
 }

+ 3 - 3
packages/dioxus-tui/benches/update.rs

@@ -65,7 +65,7 @@ struct BoxProps {
     alpha: f32,
     alpha: f32,
 }
 }
 #[allow(non_snake_case)]
 #[allow(non_snake_case)]
-fn Box(cx: Scope<BoxProps>) -> Element {
+fn Box(cx: ScopeState<BoxProps>) -> Element {
     let count = use_signal(|| 0);
     let count = use_signal(|| 0);
 
 
     let x = cx.props.x * 2;
     let x = cx.props.x * 2;
@@ -94,7 +94,7 @@ struct GridProps {
     update_count: usize,
     update_count: usize,
 }
 }
 #[allow(non_snake_case)]
 #[allow(non_snake_case)]
-fn Grid(cx: Scope<GridProps>) -> Element {
+fn Grid(cx: ScopeState<GridProps>) -> Element {
     let size = cx.props.size;
     let size = cx.props.size;
     let count = use_signal(|| 0);
     let count = use_signal(|| 0);
     let counts = use_signal(|| vec![0; size * size]);
     let counts = use_signal(|| vec![0; size * size]);
@@ -151,7 +151,7 @@ fn Grid(cx: Scope<GridProps>) -> Element {
     }
     }
 }
 }
 
 
-fn app(cx: Scope<GridProps>) -> Element {
+fn app(cx: ScopeState<GridProps>) -> Element {
     rsx! {
     rsx! {
         div{
         div{
             width: "100%",
             width: "100%",

+ 9 - 6
packages/liveview/src/pool.rs

@@ -6,7 +6,7 @@ use crate::{
     LiveViewError,
     LiveViewError,
 };
 };
 use dioxus_core::prelude::*;
 use dioxus_core::prelude::*;
-use dioxus_html::{EventData, HtmlEvent, PlatformEventData};
+use dioxus_html::{select, EventData, HtmlEvent, PlatformEventData};
 use dioxus_interpreter_js::MutationState;
 use dioxus_interpreter_js::MutationState;
 use futures_util::{pin_mut, SinkExt, StreamExt};
 use futures_util::{pin_mut, SinkExt, StreamExt};
 use serde::Serialize;
 use serde::Serialize;
@@ -227,11 +227,14 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
             }
             }
         }
         }
 
 
-        vdom.render_with_deadline(
-            tokio::time::sleep(Duration::from_millis(10)),
-            &mut mutations,
-        )
-        .await;
+        // wait for suspense to resolve in a 10ms window
+        tokio::select! {
+            _ = tokio::time::sleep(Duration::from_millis(10)) => {}
+            _ = vdom.wait_for_suspense() => {}
+        }
+
+        // render the vdom
+        vdom.render_immediate(&mut mutations);
 
 
         if let Some(edits) = take_edits(&mut mutations) {
         if let Some(edits) = take_edits(&mut mutations) {
             ws.send(edits).await?;
             ws.send(edits).await?;

+ 1 - 1
packages/router/src/components/router.rs

@@ -151,7 +151,7 @@ where
 
 
 #[cfg(feature = "serde")]
 #[cfg(feature = "serde")]
 /// A component that renders the current route.
 /// A component that renders the current route.
-pub fn Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
+pub fn Router<R: Routable + Clone>(cx: ScopeState<RouterProps<R>>) -> Element
 where
 where
     <R as FromStr>::Err: std::fmt::Display,
     <R as FromStr>::Err: std::fmt::Display,
     R: serde::Serialize + serde::de::DeserializeOwned,
     R: serde::Serialize + serde::de::DeserializeOwned,