Просмотр исходного кода

wip: consolidation, simplification

Jonathan Kelley 3 лет назад
Родитель
Сommit
70b983d

+ 0 - 2
packages/core/src/bumpframe.rs

@@ -28,7 +28,6 @@ impl ActiveFrame {
             head_node: VNode::Fragment(VFragment {
                 key: None,
                 children: &[],
-                is_static: false,
             }),
             bump: b1,
 
@@ -39,7 +38,6 @@ impl ActiveFrame {
             head_node: VNode::Fragment(VFragment {
                 key: None,
                 children: &[],
-                is_static: false,
             }),
             bump: b2,
 

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

@@ -62,7 +62,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
                     VNode::Component(sc) => {
                         let scope = self
                             .scopes
-                            .get_scope(sc.associated_scope.get().unwrap())
+                            .get_scope(&sc.associated_scope.get().unwrap())
                             .unwrap();
 
                         // Simply swap the current node on the stack with the root of the component

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

@@ -5,10 +5,7 @@
 //! 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, Element, VAnchor, VFragment, VNode},
-    LazyNodes, ScopeChildren,
-};
+use crate::innerlude::{Context, Element, LazyNodes, ScopeChildren};
 /// A component is a wrapper around a Context and some Props that share a lifetime
 ///
 ///
@@ -51,7 +48,7 @@ pub struct FragmentBuilder<'a, const BUILT: bool> {
     children: Option<ScopeChildren<'a>>,
 }
 impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(mut self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
+    pub fn children(self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
         FragmentBuilder {
             children: Some(children),
         }
@@ -75,7 +72,7 @@ impl<'a> Properties for FragmentProps<'a> {
         FragmentBuilder { children: None }
     }
 
-    unsafe fn memoize(&self, other: &Self) -> bool {
+    unsafe fn memoize(&self, _other: &Self) -> bool {
         false
     }
 }

+ 0 - 17
packages/core/src/coroutines.rs

@@ -1,17 +0,0 @@
-//! Coroutines are just a "futures unordered" buffer for tasks that can be submitted through the use_coroutine hook.
-//!
-//! The idea here is to move *coroutine* support as a layer on top of *tasks*
-
-use futures_util::{stream::FuturesUnordered, Future};
-
-pub struct CoroutineScheduler {
-    futures: FuturesUnordered<Box<dyn Future<Output = ()>>>,
-}
-
-impl CoroutineScheduler {
-    pub fn new() -> Self {
-        CoroutineScheduler {
-            futures: FuturesUnordered::new(),
-        }
-    }
-}

+ 61 - 32
packages/core/src/diff.rs

@@ -169,7 +169,7 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     pub fn diff_scope(&mut self, id: ScopeId) {
-        if let Some(component) = self.vdom.get_scope_mut(id) {
+        if let Some(component) = self.vdom.get_scope_mut(&id) {
             let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
             self.stack.push(DiffInstruction::Diff { new, old });
             self.work(|| false);
@@ -189,7 +189,10 @@ impl<'bump> DiffMachine<'bump> {
                 DiffInstruction::Diff { old, new } => self.diff_node(old, new),
                 DiffInstruction::Create { node } => self.create_node(node),
                 DiffInstruction::Mount { and } => self.mount(and),
-                DiffInstruction::PrepareMove { node } => self.prepare_move_node(node),
+                DiffInstruction::PrepareMove { node } => {
+                    let num_on_stack = self.push_all_nodes(node);
+                    self.stack.add_child_count(num_on_stack);
+                }
                 DiffInstruction::PopScope => self.stack.pop_off_scope(),
             };
 
@@ -202,10 +205,29 @@ impl<'bump> DiffMachine<'bump> {
         true
     }
 
-    fn prepare_move_node(&mut self, node: &'bump VNode<'bump>) {
-        for el in RealChildIterator::new(node, self.vdom) {
-            self.mutations.push_root(el.mounted_id());
-            self.stack.add_child_count(1);
+    // recursively push all the nodes of a tree onto the stack and return how many are there
+    fn push_all_nodes(&mut self, node: &'bump VNode<'bump>) -> usize {
+        match node {
+            VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
+                self.mutations.push_root(node.mounted_id());
+                1
+            }
+
+            VNode::Fragment(_) | VNode::Component(_) => node
+                .children()
+                .iter()
+                .map(|child| self.push_all_nodes(child))
+                .sum(),
+
+            VNode::Element(el) => {
+                let mut num_on_stack = 0;
+                for child in el.children.iter() {
+                    num_on_stack += self.push_all_nodes(child);
+                }
+                self.mutations.push_root(el.dom_id.get().unwrap());
+
+                num_on_stack + 1
+            }
         }
     }
 
@@ -307,7 +329,7 @@ impl<'bump> DiffMachine<'bump> {
         self.stack.add_child_count(1);
 
         if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.vdom.get_scope(cur_scope_id).unwrap();
+            let scope = self.vdom.get_scope_mut(&cur_scope_id).unwrap();
 
             listeners.iter().for_each(|listener| {
                 self.attach_listener_to_scope(listener, scope);
@@ -332,18 +354,18 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
-        let caller = vcomponent.caller;
+        // let caller = vcomponent.caller;
 
         let parent_idx = self.stack.current_scope().unwrap();
 
         let shared = self.vdom.channel.clone();
 
         // Insert a new scope into our component list
-        let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
+        let parent_scope = self.vdom.get_scope(&parent_idx).unwrap();
 
         let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
             ScopeInner::new(
-                caller,
+                vcomponent,
                 new_idx,
                 Some(parent_idx),
                 parent_scope.height + 1,
@@ -356,17 +378,18 @@ impl<'bump> DiffMachine<'bump> {
         vcomponent.associated_scope.set(Some(new_idx));
 
         if !vcomponent.can_memoize {
-            let cur_scope = self.vdom.get_scope_mut(parent_idx).unwrap();
+            let cur_scope = self.vdom.get_scope_mut(&parent_idx).unwrap();
             let extended = vcomponent as *const VComponent;
             let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) };
-            cur_scope.borrowed_props.borrow_mut().push(extended);
+
+            cur_scope.items.get_mut().borrowed_props.push(extended);
         }
 
         // TODO:
         //  add noderefs to current noderef list Noderefs
         //  add effects to current effect list Effects
 
-        let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
+        let new_component = self.vdom.get_scope_mut(&new_idx).unwrap();
 
         log::debug!(
             "initializing component {:?} with height {:?}",
@@ -494,7 +517,7 @@ impl<'bump> DiffMachine<'bump> {
         //
         // TODO: take a more efficient path than this
         if let Some(cur_scope_id) = self.stack.current_scope() {
-            let scope = self.vdom.get_scope(cur_scope_id).unwrap();
+            let scope = self.vdom.get_scope_mut(&cur_scope_id).unwrap();
 
             if old.listeners.len() == new.listeners.len() {
                 for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
@@ -549,19 +572,21 @@ impl<'bump> DiffMachine<'bump> {
             new.associated_scope.set(Some(scope_addr));
 
             // make sure the component's caller function is up to date
-            let scope = self.vdom.get_scope_mut(scope_addr).unwrap();
-            scope.update_scope_dependencies(new.caller);
+            let scope = self.vdom.get_scope_mut(&scope_addr).unwrap();
+            scope.update_vcomp(new);
 
             // React doesn't automatically memoize, but we do.
-            let props_are_the_same = old.comparator.unwrap();
+            let props_are_the_same = todo!("reworking component memoization");
+            // let props_are_the_same = todo!("reworking component memoization");
+            // let props_are_the_same = old.comparator.unwrap();
 
-            if self.cfg.force_diff || !props_are_the_same(new) {
-                let succeeded = scope.run_scope(self.vdom);
+            // if self.cfg.force_diff || !props_are_the_same(new) {
+            //     let succeeded = scope.run_scope(self.vdom);
 
-                if succeeded {
-                    self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
-                }
-            }
+            //     if succeeded {
+            //         self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
+            //     }
+            // }
 
             self.stack.scope_stack.pop();
         } else {
@@ -1038,7 +1063,7 @@ impl<'bump> DiffMachine<'bump> {
                 }
                 VNode::Component(el) => {
                     let scope_id = el.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope(scope_id).unwrap();
+                    let scope = self.vdom.get_scope(&scope_id).unwrap();
                     search_node = Some(scope.root_node());
                 }
             }
@@ -1056,7 +1081,7 @@ impl<'bump> DiffMachine<'bump> {
                 }
                 VNode::Component(el) => {
                     let scope_id = el.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope(scope_id).unwrap();
+                    let scope = self.vdom.get_scope(&scope_id).unwrap();
                     search_node = Some(scope.root_node());
                 }
                 VNode::Text(t) => break t.dom_id.get(),
@@ -1131,12 +1156,12 @@ impl<'bump> DiffMachine<'bump> {
 
                 VNode::Component(c) => {
                     let scope_id = c.associated_scope.get().unwrap();
-                    let scope = self.vdom.get_scope_mut(scope_id).unwrap();
+                    let scope = self.vdom.get_scope_mut(&scope_id).unwrap();
                     let root = scope.root_node();
                     self.remove_nodes(Some(root), gen_muts);
 
                     log::debug!("Destroying scope {:?}", scope_id);
-                    let mut s = self.vdom.try_remove(scope_id).unwrap();
+                    let mut s = self.vdom.try_remove(&scope_id).unwrap();
                     s.hooks.clear_hooks();
                 }
             }
@@ -1161,23 +1186,27 @@ impl<'bump> DiffMachine<'bump> {
     }
 
     /// Adds a listener closure to a scope during diff.
-    fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &ScopeInner) {
-        let mut queue = scope.listeners.borrow_mut();
+    fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &mut ScopeInner) {
         let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
-        queue.push(long_listener as *const _)
+        scope
+            .items
+            .get_mut()
+            .listeners
+            .push(long_listener as *const _)
     }
 
     fn attach_suspended_node_to_scope(&mut self, suspended: &'bump VSuspended) {
         if let Some(scope) = self
             .stack
             .current_scope()
-            .and_then(|id| self.vdom.get_scope_mut(id))
+            .and_then(|id| self.vdom.get_scope_mut(&id))
         {
             // safety: this lifetime is managed by the logic on scope
             let extended: &VSuspended<'static> = unsafe { std::mem::transmute(suspended) };
             scope
+                .items
+                .get_mut()
                 .suspended_nodes
-                .borrow_mut()
                 .insert(suspended.task_id, extended as *const _);
         }
     }

+ 0 - 50
packages/core/src/heuristics.rs

@@ -1,50 +0,0 @@
-use std::collections::HashMap;
-
-use fxhash::FxHashMap;
-
-use crate::FC;
-
-/// Provides heuristics to the "SharedResources" object for improving allocation performance.
-///
-/// 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 initialization process.
-///
-///
-pub struct HeuristicsEngine {
-    heuristics: FxHashMap<FcSlot, Heuristic>,
-}
-
-pub type FcSlot = *const ();
-
-pub struct Heuristic {
-    hooks: u32,
-    bump_size: u64,
-}
-
-impl HeuristicsEngine {
-    pub(crate) fn new() -> Self {
-        Self {
-            heuristics: FxHashMap::default(),
-        }
-    }
-
-    fn recommend<T>(&mut self, fc: FC<T>, heuristic: Heuristic) {
-        let g = fc as FcSlot;
-        let e = self.heuristics.entry(g);
-    }
-
-    fn get_recommendation<T>(&mut self, fc: FC<T>) -> &Heuristic {
-        let id = fc as FcSlot;
-
-        self.heuristics.entry(id).or_insert(Heuristic {
-            bump_size: 100,
-            hooks: 10,
-        })
-    }
-}
-
-#[test]
-fn types_work() {
-    let engine = HeuristicsEngine::new();
-}

+ 19 - 14
packages/core/src/hooklist.rs

@@ -1,10 +1,9 @@
 use std::{
     any::Any,
-    cell::{Cell, RefCell, UnsafeCell},
+    cell::{Cell, RefCell},
 };
 
-type UnsafeInnerHookState = UnsafeCell<Box<dyn Any>>;
-type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
+use bumpalo::Bump;
 
 /// An abstraction over internally stored data using a hook-based memory layout.
 ///
@@ -15,7 +14,8 @@ type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
 /// Todo: this could use its very own bump arena, but that might be a tad overkill
 #[derive(Default)]
 pub(crate) struct HookList {
-    vals: RefCell<Vec<(UnsafeInnerHookState, HookCleanup)>>,
+    arena: Bump,
+    vals: RefCell<Vec<*mut dyn Any>>,
     idx: Cell<usize>,
 }
 
@@ -23,7 +23,7 @@ impl HookList {
     pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
         self.vals.borrow().get(self.idx.get()).and_then(|inn| {
             self.idx.set(self.idx.get() + 1);
-            let raw_box = unsafe { &mut *inn.0.get() };
+            let raw_box = unsafe { &mut **inn };
             raw_box.downcast_mut::<T>()
         })
     }
@@ -38,10 +38,9 @@ impl HookList {
         self.idx.set(0);
     }
 
-    pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: HookCleanup) {
-        self.vals
-            .borrow_mut()
-            .push((UnsafeCell::new(Box::new(new)), cleanup))
+    pub(crate) fn push_hook<T: 'static>(&self, new: T) {
+        let val = self.arena.alloc(new);
+        self.vals.borrow_mut().push(val)
     }
 
     pub(crate) fn len(&self) -> usize {
@@ -57,10 +56,16 @@ impl HookList {
     }
 
     pub fn clear_hooks(&mut self) {
-        log::debug!("clearing hooks...");
-        self.vals
-            .borrow_mut()
-            .drain(..)
-            .for_each(|(state, cleanup)| cleanup(state.into_inner()));
+        self.vals.borrow_mut().drain(..).for_each(|state| {
+            let as_mut = unsafe { &mut *state };
+            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
+            drop(boxed);
+        });
+    }
+
+    /// Get the ammount of memory a hooklist uses
+    /// Used in heuristics
+    pub fn get_hook_arena_size(&self) -> usize {
+        self.arena.allocated_bytes()
     }
 }

+ 72 - 72
packages/core/src/hooks.rs

@@ -13,73 +13,73 @@ use crate::innerlude::*;
 use futures_util::FutureExt;
 use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
 
-/// Awaits the given task, forcing the component to re-render when the value is ready.
-///
-/// Returns the handle to the task and the value (if it is ready, else None).
-///
-/// ```
-/// static Example: FC<()> = |(cx, props)| {
-///     let (task, value) = use_task(|| async {
-///         timer::sleep(Duration::from_secs(1)).await;
-///         "Hello World"
-///     });
-///
-///     match contents {
-///         Some(contents) => rsx!(cx, div { "{title}" }),
-///         None => rsx!(cx, div { "Loading..." }),
-///     }
-/// };
-/// ```
-pub fn use_coroutine<'src, Out, Fut, Init>(
-    cx: Context<'src>,
-    task_initializer: Init,
-) -> (&'src TaskHandle, &'src Option<Out>)
-where
-    Out: 'static,
-    Fut: Future<Output = Out> + 'static,
-    Init: FnOnce() -> Fut + 'src,
-{
-    struct TaskHook<T> {
-        handle: TaskHandle,
-        task_dump: Rc<RefCell<Option<T>>>,
-        value: Option<T>,
-    }
-
-    todo!()
-
-    // // whenever the task is complete, save it into th
-    // cx.use_hook(
-    //     move |_| {
-    //         let task_fut = task_initializer();
-
-    //         let task_dump = Rc::new(RefCell::new(None));
-
-    //         let slot = task_dump.clone();
-
-    //         let updater = cx.schedule_update_any();
-    //         let originator = cx.scope.our_arena_idx;
-
-    //         let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
-    //             *slot.as_ref().borrow_mut() = Some(output);
-    //             updater(originator);
-    //             originator
-    //         })));
-
-    //         TaskHook {
-    //             task_dump,
-    //             value: None,
-    //             handle,
-    //         }
-    //     },
-    //     |hook| {
-    //         if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
-    //             hook.value = Some(val);
-    //         }
-    //         (&hook.handle, &hook.value)
-    //     },
-    //     |_| {},
-    // )
-}
+// /// Awaits the given task, forcing the component to re-render when the value is ready.
+// ///
+// /// Returns the handle to the task and the value (if it is ready, else None).
+// ///
+// /// ```
+// /// static Example: FC<()> = |(cx, props)| {
+// ///     let (task, value) = use_task(|| async {
+// ///         timer::sleep(Duration::from_secs(1)).await;
+// ///         "Hello World"
+// ///     });
+// ///
+// ///     match contents {
+// ///         Some(contents) => rsx!(cx, div { "{title}" }),
+// ///         None => rsx!(cx, div { "Loading..." }),
+// ///     }
+// /// };
+// /// ```
+// pub fn use_coroutine<'src, Out, Fut, Init>(
+//     cx: Context<'src>,
+//     task_initializer: Init,
+// ) -> (&'src TaskHandle, &'src Option<Out>)
+// where
+//     Out: 'static,
+//     Fut: Future<Output = Out> + 'static,
+//     Init: FnOnce() -> Fut + 'src,
+// {
+//     struct TaskHook<T> {
+//         handle: TaskHandle,
+//         task_dump: Rc<RefCell<Option<T>>>,
+//         value: Option<T>,
+//     }
+
+//     todo!()
+
+//     // // whenever the task is complete, save it into th
+//     // cx.use_hook(
+//     //     move |_| {
+//     //         let task_fut = task_initializer();
+
+//     //         let task_dump = Rc::new(RefCell::new(None));
+
+//     //         let slot = task_dump.clone();
+
+//     //         let updater = cx.schedule_update_any();
+//     //         let originator = cx.scope.our_arena_idx;
+
+//     //         let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
+//     //             *slot.as_ref().borrow_mut() = Some(output);
+//     //             updater(originator);
+//     //             originator
+//     //         })));
+
+//     //         TaskHook {
+//     //             task_dump,
+//     //             value: None,
+//     //             handle,
+//     //         }
+//     //     },
+//     //     |hook| {
+//     //         if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
+//     //             hook.value = Some(val);
+//     //         }
+//     //         (&hook.handle, &hook.value)
+//     //     },
+//     //     |_| {},
+//     // )
+// }
 
 /// Asynchronously render new nodes once the given future has completed.
 ///
@@ -167,10 +167,10 @@ where
     // )
 }
 
-pub(crate) struct SuspenseHook {
-    pub handle: TaskHandle,
-    pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
-}
+// pub(crate) struct SuspenseHook {
+//     pub handle: TaskHandle,
+//     pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
+// }
 
 #[derive(Clone, Copy)]
 pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
@@ -183,5 +183,5 @@ impl<'a, T> Deref for NodeRef<'a, T> {
 }
 
 pub fn use_node_ref<T, P>(cx: Context) -> NodeRef<T> {
-    cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {})
+    cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f })
 }

+ 2 - 13
packages/core/src/lib.rs

@@ -13,22 +13,16 @@ Navigating this crate:
 Some utilities
 */
 pub(crate) mod bumpframe;
-pub(crate) mod childiter;
 pub(crate) mod component;
-pub(crate) mod coroutines;
 pub(crate) mod diff;
 pub(crate) mod diff_stack;
 pub(crate) mod events;
-pub(crate) mod heuristics;
 pub(crate) mod hooklist;
 pub(crate) mod hooks;
 pub(crate) mod lazynodes;
 pub(crate) mod mutations;
 pub(crate) mod nodes;
-pub(crate) mod resources;
-pub(crate) mod scheduler;
 pub(crate) mod scope;
-pub(crate) mod tasks;
 pub(crate) mod test_dom;
 pub(crate) mod threadsafe;
 pub(crate) mod util;
@@ -39,21 +33,16 @@ pub mod debug_dom;
 
 pub(crate) mod innerlude {
     pub(crate) use crate::bumpframe::*;
-    pub(crate) use crate::childiter::*;
     pub use crate::component::*;
     pub(crate) use crate::diff::*;
     pub use crate::diff_stack::*;
     pub use crate::events::*;
-    pub use crate::heuristics::*;
     pub(crate) use crate::hooklist::*;
     pub use crate::hooks::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;
-    pub(crate) use crate::resources::*;
-    pub use crate::scheduler::*;
     pub use crate::scope::*;
-    pub use crate::tasks::*;
     pub use crate::test_dom::*;
     pub use crate::threadsafe::*;
     pub use crate::util::*;
@@ -65,8 +54,8 @@ pub(crate) mod innerlude {
 
 pub use crate::innerlude::{
     Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
-    Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, TaskHandle, TestDom,
-    ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
+    Mutations, NodeFactory, Properties, ScopeChildren, ScopeId, TestDom, ThreadsafeVirtualDom,
+    UserEvent, VNode, VirtualDom, FC,
 };
 
 pub mod prelude {

+ 111 - 84
packages/core/src/nodes.rs

@@ -154,6 +154,14 @@ impl<'src> VNode<'src> {
         }
     }
 
+    pub fn children(&self) -> &[VNode<'src>] {
+        match &self {
+            VNode::Fragment(f) => &f.children,
+            VNode::Component(c) => &c.children,
+            _ => &[],
+        }
+    }
+
     // Create an "owned" version of the vnode.
     pub fn decouple(&self) -> VNode<'src> {
         match self {
@@ -165,7 +173,6 @@ impl<'src> VNode<'src> {
             VNode::Fragment(f) => VNode::Fragment(VFragment {
                 children: f.children,
                 key: f.key,
-                is_static: f.is_static,
             }),
         }
     }
@@ -209,8 +216,6 @@ pub struct VFragment<'src> {
     pub key: Option<&'src str>,
 
     pub children: &'src [VNode<'src>],
-
-    pub is_static: bool,
 }
 
 /// An element like a "div" with children, listeners, and attributes.
@@ -316,21 +321,21 @@ pub struct VComponent<'src> {
 
     pub associated_scope: Cell<Option<ScopeId>>,
 
-    pub is_static: bool,
-
     // Function pointer to the FC that was used to generate this component
     pub user_fc: *const (),
-
-    pub(crate) caller: BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>,
-
-    pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
-
-    pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
-
     pub(crate) can_memoize: bool,
 
     // Raw pointer into the bump arena for the props of the component
     pub(crate) raw_props: *const (),
+
+    // during the "teardown" process we'll take the caller out so it can be dropped properly
+    pub(crate) caller: Option<VCompCaller<'src>>,
+    pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
+}
+
+pub enum VCompCaller<'src> {
+    Borrowed(BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>),
+    Owned(Box<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>),
 }
 
 pub struct VSuspended<'a> {
@@ -473,7 +478,7 @@ impl<'a> NodeFactory<'a> {
         }
     }
 
-    pub fn component<P>(
+    pub fn component<P, P1>(
         &self,
         component: fn(Scope<'a, P>) -> Element<'a>,
         props: P,
@@ -482,85 +487,109 @@ impl<'a> NodeFactory<'a> {
     where
         P: Properties + 'a,
     {
+        /*
+        our strategy:
+        - unsafe hackery
+        - lol
+
+        - we don't want to hit the global allocator
+        - allocate into our bump arena
+        - if the props aren't static, then we convert them into a box which we pass off between renders
+        */
+
         let bump = self.bump();
-        let props = bump.alloc(props);
-        let raw_props = props as *mut P as *mut ();
+
+        // let p = BumpBox::new_in(x, a)
+
+        // the best place to allocate the props are the other component's arena
+        // the second best place is the global allocator
+
+        // // if the props are static
+        // let boxed = if P::IS_STATIC {
+        //     todo!()
+        // } else {
+        //     todo!()
+        // }
+
+        // let caller = Box::new(|f: &ScopeInner| -> Element {
+        //     //
+        //     component((f, &props))
+        // });
+
         let user_fc = component as *const ();
 
-        let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
-            move |other: &VComponent| {
-                if user_fc == other.user_fc {
-                    // Safety
-                    // - We guarantee that FC<P> is the same by function pointer
-                    // - Because FC<P> is the same, then P must be the same (even with generics)
-                    // - Non-static P are autoderived to memoize as false
-                    // - This comparator is only called on a corresponding set of bumpframes
-                    let props_memoized = unsafe {
-                        let real_other: &P = &*(other.raw_props as *const _ as *const P);
-                        props.memoize(real_other)
-                    };
-
-                    // It's only okay to memoize if there are no children and the props can be memoized
-                    // Implementing memoize is unsafe and done automatically with the props trait
-                    props_memoized
-                } else {
-                    false
-                }
-            }
-        });
-        let comparator = Some(unsafe { BumpBox::from_raw(comparator) });
-
-        let drop_props = {
-            // create a closure to drop the props
-            let mut has_dropped = false;
-
-            let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
-                move || unsafe {
-                    log::debug!("dropping props!");
-                    if !has_dropped {
-                        let real_other = raw_props as *mut _ as *mut P;
-                        let b = BumpBox::from_raw(real_other);
-                        std::mem::drop(b);
-
-                        has_dropped = true;
-                    } else {
-                        panic!("Drop props called twice - this is an internal failure of Dioxus");
-                    }
-                }
-            });
-
-            let drop_props = unsafe { BumpBox::from_raw(drop_props) };
-
-            RefCell::new(Some(drop_props))
-        };
+        // let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
+        //     move |other: &VComponent| {
+        //         if user_fc == other.user_fc {
+        //             // Safety
+        //             // - We guarantee that FC<P> is the same by function pointer
+        //             // - Because FC<P> is the same, then P must be the same (even with generics)
+        //             // - Non-static P are autoderived to memoize as false
+        //             // - This comparator is only called on a corresponding set of bumpframes
+        //             let props_memoized = unsafe {
+        //                 let real_other: &P = &*(other.raw_props as *const _ as *const P);
+        //                 props.memoize(real_other)
+        //             };
+
+        //             // It's only okay to memoize if there are no children and the props can be memoized
+        //             // Implementing memoize is unsafe and done automatically with the props trait
+        //             props_memoized
+        //         } else {
+        //             false
+        //         }
+        //     }
+        // });
+        // let comparator = Some(unsafe { BumpBox::from_raw(comparator) });
 
         let key = key.map(|f| self.raw_text(f).0);
 
-        let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
-            bump.alloc(move |scope: &ScopeInner| -> Element {
-                log::debug!("calling component renderr {:?}", scope.our_arena_idx);
-                let props: &'_ P = unsafe { &*(raw_props as *const P) };
+        let caller = match P::IS_STATIC {
+            true => {
+                // it just makes sense to box the props
+                let boxed_props: Box<P> = Box::new(props);
+                let props_we_know_are_static = todo!();
+                VCompCaller::Owned(Box::new(|f| {
+                    //
 
-                let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
-                let s: Scope<'a, P> = (scp, props);
+                    let p = todo!();
 
-                let res: Element = component(s);
-                unsafe { std::mem::transmute(res) }
-            });
+                    todo!()
+                }))
+            }
+            false => VCompCaller::Borrowed({
+                //
 
-        let caller = unsafe { BumpBox::from_raw(caller) };
+                todo!()
+                // let caller = bump.alloc()
+            }),
+        };
 
-        VNode::Component(bump.alloc(VComponent {
-            user_fc,
-            comparator,
-            raw_props,
-            caller,
-            is_static: P::IS_STATIC,
-            key,
-            can_memoize: P::IS_STATIC,
-            drop_props,
-            associated_scope: Cell::new(None),
-        }))
+        todo!()
+        // let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
+        //     bump.alloc(move |scope: &ScopeInner| -> Element {
+        //         log::debug!("calling component renderr {:?}", scope.our_arena_idx);
+        //         let props: &'_ P = unsafe { &*(raw_props as *const P) };
+
+        //         let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
+        //         let s: Scope<'a, P> = (scp, props);
+
+        //         let res: Element = component(s);
+        //         unsafe { std::mem::transmute(res) }
+        //     });
+
+        // let caller = unsafe { BumpBox::from_raw(caller) };
+
+        // VNode::Component(bump.alloc(VComponent {
+        //     user_fc,
+        //     comparator,
+        //     raw_props,
+        //     caller,
+        //     is_static: P::IS_STATIC,
+        //     key,
+        //     can_memoize: P::IS_STATIC,
+        //     drop_props,
+        //     associated_scope: Cell::new(None),
+        // }))
     }
 
     pub fn fragment_from_iter(
@@ -604,7 +633,6 @@ impl<'a> NodeFactory<'a> {
         VNode::Fragment(VFragment {
             children,
             key: None,
-            is_static: false,
         })
     }
 
@@ -684,7 +712,6 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
             Some(lazy) => lazy.call(cx),
             None => VNode::Fragment(VFragment {
                 children: &[],
-                is_static: false,
                 key: None,
             }),
         }

+ 3 - 3
packages/core/src/resources.rs

@@ -32,18 +32,18 @@ pub(crate) struct ResourcePool {
 
 impl ResourcePool {
     /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope(&self, idx: ScopeId) -> Option<&ScopeInner> {
+    pub fn get_scope(&self, idx: &ScopeId) -> Option<&ScopeInner> {
         let inner = unsafe { &*self.components.get() };
         inner.get(idx.0)
     }
 
     /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut ScopeInner> {
+    pub fn get_scope_mut(&self, idx: &ScopeId) -> Option<&mut ScopeInner> {
         let inner = unsafe { &mut *self.components.get() };
         inner.get_mut(idx.0)
     }
 
-    pub fn try_remove(&self, id: ScopeId) -> Option<ScopeInner> {
+    pub fn try_remove(&self, id: &ScopeId) -> Option<ScopeInner> {
         let inner = unsafe { &mut *self.components.get() };
         Some(inner.remove(id.0))
         // .try_remove(id.0)

+ 105 - 121
packages/core/src/scheduler.rs

@@ -68,14 +68,17 @@ do anything too arduous from onInput.
 
 For the rest, we defer to the rIC period and work down each queue from high to low.
 */
-use crate::heuristics::*;
+
 use crate::innerlude::*;
+use bumpalo::Bump;
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
 use fxhash::FxHashMap;
 use fxhash::FxHashSet;
 use indexmap::IndexSet;
 use slab::Slab;
+use std::pin::Pin;
+use std::task::Poll;
 use std::{
     any::{Any, TypeId},
     cell::{Cell, UnsafeCell},
@@ -85,11 +88,7 @@ use std::{
 
 #[derive(Clone)]
 pub(crate) struct EventChannel {
-    pub task_counter: Rc<Cell<u64>>,
-    pub cur_subtree: Rc<Cell<u32>>,
     pub sender: UnboundedSender<SchedulerMsg>,
-    pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
-    pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
     pub get_shared_context: GetSharedContext,
 }
 
@@ -101,16 +100,6 @@ pub enum SchedulerMsg {
 
     // setstate
     Immediate(ScopeId),
-
-    // tasks
-    Task(TaskMsg),
-}
-
-pub enum TaskMsg {
-    ToggleTask(u64),
-    PauseTask(u64),
-    ResumeTask(u64),
-    DropTask(u64),
 }
 
 /// The scheduler holds basically everything around "working"
@@ -126,16 +115,26 @@ pub enum TaskMsg {
 ///
 /// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
 ///
+/// There's a lot of raw pointers here...
+///
+/// Since we're building self-referential structures for each component, we need to make sure that the referencs stay stable
+/// The best way to do that is a bump allocator.
+///
+///
 ///
 pub(crate) struct Scheduler {
-    /// All mounted components are arena allocated to make additions, removals, and references easy to work with
-    /// 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 guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
-    pub pool: ResourcePool,
+    // /// All mounted components are arena allocated to make additions, removals, and references easy to work with
+    // /// 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 guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
+    // pub pool: ResourcePool,
+    //
+    pub component_arena: Bump,
+
+    pub free_components: VecDeque<*mut ScopeInner>,
 
-    pub heuristics: HeuristicsEngine,
+    pub heuristics: FxHashMap<FcSlot, Heuristic>,
 
     pub receiver: UnboundedReceiver<SchedulerMsg>,
 
@@ -145,104 +144,70 @@ pub(crate) struct Scheduler {
     // Every component that has futures that need to be polled
     pub pending_futures: FxHashSet<ScopeId>,
 
-    // In-flight futures
-    pub async_tasks: FuturesUnordered<FiberTask>,
-
     // // scheduler stuff
     // pub current_priority: EventPriority,
     pub ui_events: VecDeque<UserEvent>,
 
     pub pending_immediates: VecDeque<ScopeId>,
 
-    pub pending_tasks: VecDeque<UserEvent>,
-
     pub batched_events: VecDeque<UserEvent>,
 
     pub garbage_scopes: HashSet<ScopeId>,
 
     pub dirty_scopes: IndexSet<ScopeId>,
+
     pub saved_state: Option<SavedDiffWork<'static>>,
+
     pub in_progress: bool,
 }
 
+pub type FcSlot = *const ();
+
+pub struct Heuristic {
+    hook_arena_size: usize,
+    node_arena_size: usize,
+}
+
 impl Scheduler {
     pub(crate) fn new(
         sender: UnboundedSender<SchedulerMsg>,
         receiver: UnboundedReceiver<SchedulerMsg>,
+        component_capacity: usize,
+        element_capacity: usize,
     ) -> Self {
         /*
         Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
         Perhaps this should be configurable from some external config?
         */
-        let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
-        let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(2000)));
 
-        let heuristics = HeuristicsEngine::new();
-
-        let task_counter = Rc::new(Cell::new(0));
-        let cur_subtree = Rc::new(Cell::new(0));
+        // let components = Rc::new(UnsafeCell::new(Slab::with_capacity(component_capacity)));
+        let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(element_capacity)));
 
         let channel = EventChannel {
-            cur_subtree,
-            task_counter: task_counter.clone(),
             sender: sender.clone(),
-            schedule_any_immediate: {
-                let sender = sender.clone();
-                Rc::new(move |id| {
-                    //
-                    log::debug!("scheduling immediate! {:?}", id);
-                    sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()
-                })
-            },
-            // todo: we want to get the futures out of the scheduler message
-            // the scheduler message should be send/sync
-            submit_task: {
-                Rc::new(move |fiber_task| {
-                    let task_id = task_counter.get();
-                    task_counter.set(task_id + 1);
-
-                    todo!();
-                    // sender
-                    //     .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
-                    //         fiber_task, task_id,
-                    //     )))
-                    //     .unwrap();
-                    TaskHandle {
-                        our_id: task_id,
-                        sender: sender.clone(),
-                    }
-                })
-            },
             get_shared_context: {
-                let components = components.clone();
-                Rc::new(move |id, ty| {
-                    let components = unsafe { &*components.get() };
-                    let mut search: Option<&ScopeInner> = components.get(id.0);
-                    while let Some(inner) = search.take() {
-                        if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
-                            return Some(shared.clone());
-                        } else {
-                            search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
-                        }
-                    }
-                    None
-                })
+                todo!()
+                // let components = components.clone();
+                // Rc::new(move |id, ty| {
+                //     let components = unsafe { &*components.get() };
+                //     let mut search: Option<&ScopeInner> = components.get(id.0);
+                //     while let Some(inner) = search.take() {
+                //         if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
+                //             return Some(shared.clone());
+                //         } else {
+                //             search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
+                //         }
+                //     }
+                //     None
+                // })
             },
         };
 
-        let pool = ResourcePool {
-            components,
-            raw_elements,
-            channel,
-        };
-
-        let async_tasks = FuturesUnordered::new();
-
-        // push a task that would never resolve - prevents us from immediately aborting the scheduler
-        async_tasks.push(Box::pin(async {
-            std::future::pending::<()>().await;
-            ScopeId(0)
-        }) as FiberTask);
+        // let pool = ResourcePool {
+        //     components,
+        //     raw_elements,
+        //     channel,
+        // };
 
         let saved_state = SavedDiffWork {
             mutations: Mutations::new(),
@@ -251,22 +216,15 @@ impl Scheduler {
         };
 
         Self {
-            pool,
-
+            // pool,
             receiver,
 
-            async_tasks,
-
             pending_garbage: FxHashSet::default(),
 
-            heuristics,
-
             ui_events: VecDeque::new(),
 
             pending_immediates: VecDeque::new(),
 
-            pending_tasks: VecDeque::new(),
-
             batched_events: VecDeque::new(),
 
             garbage_scopes: HashSet::new(),
@@ -275,6 +233,8 @@ impl Scheduler {
             dirty_scopes: Default::default(),
             saved_state: Some(saved_state),
             in_progress: false,
+
+            heuristics: todo!(),
         }
     }
 
@@ -282,7 +242,7 @@ impl Scheduler {
     pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
         let (discrete, priority) = event_meta(&event);
 
-        if let Some(scope) = self.pool.get_scope_mut(event.scope) {
+        if let Some(scope) = self.get_scope_mut(&event.scope) {
             if let Some(element) = event.mounted_dom_id {
                 // TODO: bubble properly here
                 scope.call_listener(event, element);
@@ -308,7 +268,7 @@ impl Scheduler {
 
     fn prepare_work(&mut self) {
         // while let Some(trigger) = self.ui_events.pop_back() {
-        //     if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
+        //     if let Some(scope) = self.get_scope_mut(&trigger.scope) {}
         // }
     }
 
@@ -328,15 +288,9 @@ impl Scheduler {
     unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
         self.saved_state.take().unwrap().extend()
     }
-    pub fn handle_task(&mut self, evt: TaskMsg) {
-        //
-    }
 
     pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
         match msg {
-            //
-            SchedulerMsg::Task(msg) => todo!(),
-
             SchedulerMsg::Immediate(_) => todo!(),
 
             SchedulerMsg::UiEvent(event) => {
@@ -344,7 +298,7 @@ impl Scheduler {
 
                 let (discrete, priority) = event_meta(&event);
 
-                if let Some(scope) = self.pool.get_scope_mut(event.scope) {
+                if let Some(scope) = self.get_scope_mut(&event.scope) {
                     if let Some(element) = event.mounted_dom_id {
                         // TODO: bubble properly here
                         scope.call_listener(event, element);
@@ -374,19 +328,19 @@ impl Scheduler {
         let saved_state = unsafe { self.load_work() };
 
         // We have to split away some parts of ourself - current lane is borrowed mutably
-        let shared = self.pool.clone();
+        let shared = self.clone();
         let mut machine = unsafe { saved_state.promote(&shared) };
 
         let mut ran_scopes = FxHashSet::default();
 
         if machine.stack.is_empty() {
-            let shared = self.pool.clone();
+            let shared = self.clone();
 
             self.dirty_scopes
-                .retain(|id| shared.get_scope(*id).is_some());
+                .retain(|id| shared.get_scope(id).is_some());
             self.dirty_scopes.sort_by(|a, b| {
-                let h1 = shared.get_scope(*a).unwrap().height;
-                let h2 = shared.get_scope(*b).unwrap().height;
+                let h1 = shared.get_scope(a).unwrap().height;
+                let h2 = shared.get_scope(b).unwrap().height;
                 h1.cmp(&h2).reverse()
             });
 
@@ -396,8 +350,8 @@ impl Scheduler {
                     ran_scopes.insert(scopeid);
                     log::debug!("about to run scope {:?}", scopeid);
 
-                    if let Some(component) = self.pool.get_scope_mut(scopeid) {
-                        if component.run_scope(&self.pool) {
+                    if let Some(component) = self.get_scope_mut(&scopeid) {
+                        if component.run_scope(&self) {
                             let (old, new) =
                                 (component.frames.wip_head(), component.frames.fin_head());
                             // let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
@@ -494,7 +448,6 @@ impl Scheduler {
         while self.has_any_work() {
             while let Ok(Some(msg)) = self.receiver.try_next() {
                 match msg {
-                    SchedulerMsg::Task(t) => todo!(),
                     SchedulerMsg::Immediate(im) => {
                         self.dirty_scopes.insert(im);
                     }
@@ -506,7 +459,7 @@ impl Scheduler {
 
             // switch our priority, pop off any work
             while let Some(event) = self.ui_events.pop_front() {
-                if let Some(scope) = self.pool.get_scope_mut(event.scope) {
+                if let Some(scope) = self.get_scope_mut(&event.scope) {
                     if let Some(element) = event.mounted_dom_id {
                         log::info!("Calling listener {:?}, {:?}", event.scope, element);
 
@@ -519,7 +472,6 @@ impl Scheduler {
                                     self.dirty_scopes.insert(im);
                                 }
                                 SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
-                                SchedulerMsg::Task(_) => todo!(),
                             }
                         }
                     }
@@ -562,19 +514,19 @@ impl Scheduler {
     ///
     /// Typically used to kickstart the VirtualDOM after initialization.
     pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
-        let mut shared = self.pool.clone();
+        let mut shared = self.clone();
         let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
 
         // TODO: drain any in-flight work
         let cur_component = self
             .pool
-            .get_scope_mut(base_scope)
+            .get_scope_mut(&base_scope)
             .expect("The base scope should never be moved");
 
         log::debug!("rebuild {:?}", base_scope);
 
         // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
-        if cur_component.run_scope(&self.pool) {
+        if cur_component.run_scope(&self) {
             diff_machine
                 .stack
                 .create_node(cur_component.frames.fin_head(), MountType::Append);
@@ -596,13 +548,13 @@ impl Scheduler {
     pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
         let cur_component = self
             .pool
-            .get_scope_mut(base_scope)
+            .get_scope_mut(&base_scope)
             .expect("The base scope should never be moved");
 
         log::debug!("hard diff {:?}", base_scope);
 
-        if cur_component.run_scope(&self.pool) {
-            let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self.pool);
+        if cur_component.run_scope(&self) {
+            let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self);
             diff_machine.cfg.force_diff = true;
             diff_machine.diff_scope(base_scope);
             diff_machine.mutations
@@ -611,3 +563,35 @@ impl Scheduler {
         }
     }
 }
+
+impl Future for Scheduler {
+    type Output = ();
+
+    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
+        let mut all_pending = true;
+
+        for fut in self.pending_futures.iter() {
+            let scope = self
+                .pool
+                .get_scope_mut(&fut)
+                .expect("Scope should never be moved");
+
+            let items = scope.items.get_mut();
+            for task in items.tasks.iter_mut() {
+                let t = task.as_mut();
+                let g = unsafe { Pin::new_unchecked(t) };
+                match g.poll(cx) {
+                    Poll::Ready(r) => {
+                        all_pending = false;
+                    }
+                    Poll::Pending => {}
+                }
+            }
+        }
+
+        match all_pending {
+            true => Poll::Pending,
+            false => Poll::Ready(()),
+        }
+    }
+}

+ 201 - 227
packages/core/src/scope.rs

@@ -6,36 +6,27 @@ use std::{
     cell::{Cell, RefCell},
     collections::HashMap,
     future::Future,
-    pin::Pin,
     rc::Rc,
 };
 
-use crate::{innerlude::*, lazynodes::LazyNodes};
 use bumpalo::{boxed::Box as BumpBox, Bump};
-use std::ops::Deref;
 
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
 ///
 /// This lets components access props, schedule updates, integrate hooks, and expose shared state.
 ///
-/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
-/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
-/// exists for the `use_provide_state` hook to provide a shared state object.
-///
 /// For the most part, the only method you should be using regularly is `render`.
 ///
 /// ## Example
 ///
 /// ```ignore
-/// #[derive(Properties)]
-/// struct Props {
+/// #[derive(Props)]
+/// struct ExampleProps {
 ///     name: String
 /// }
 ///
-/// fn example(cx: Context<Props>) -> VNode {
-///     html! {
-///         <div> "Hello, {cx.name}" </div>
-///     }
+/// fn Example((cx, props): Scope<Props>) -> Element {
+///     cx.render(rsx!{ div {"Hello, {props.name}"} })
 /// }
 /// ```
 pub type Context<'a> = &'a ScopeInner;
@@ -59,7 +50,8 @@ pub struct ScopeInner {
 
     // Nodes
     pub(crate) frames: ActiveFrame,
-    pub(crate) caller: BumpBox<'static, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
+
+    pub(crate) vcomp: *const VComponent<'static>,
 
     /*
     we care about:
@@ -67,12 +59,7 @@ pub struct ScopeInner {
     - borrowed props (and how to drop them when the parent is dropped)
     - suspended nodes (and how to call their callback when their associated tasks are complete)
     */
-    pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
-    pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
-    pub(crate) suspended_nodes: RefCell<FxHashMap<u64, *const VSuspended<'static>>>,
-
-    pub(crate) tasks: RefCell<Vec<BumpBox<'static, dyn Future<Output = ()>>>>,
-    pub(crate) pending_effects: RefCell<Vec<BumpBox<'static, dyn FnMut()>>>,
+    pub(crate) items: RefCell<SelfReferentialItems<'static>>,
 
     // State
     pub(crate) hooks: HookList,
@@ -80,126 +67,17 @@ pub struct ScopeInner {
     // todo: move this into a centralized place - is more memory efficient
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
-    // whenever set_state is called, we fire off a message to the scheduler
-    // this closure _is_ the method called by schedule_update that marks this component as dirty
-    pub(crate) memoized_updater: Rc<dyn Fn()>,
-
     pub(crate) shared: EventChannel,
 }
 
-/// Public interface for Scopes.
-impl ScopeInner {
-    /// Get the root VNode for this Scope.
-    ///
-    /// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment.
-    ///
-    /// # Example
-    /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// if let VNode::VElement(node) = base.root_node() {
-    ///     assert_eq!(node.tag_name, "div");
-    /// }
-    /// ```
-    pub fn root_node(&self) -> &VNode {
-        self.frames.fin_head()
-    }
-
-    /// Get the subtree ID that this scope belongs to.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// assert_eq!(base.subtree(), 0);
-    /// ```
-    pub fn subtree(&self) -> u32 {
-        self.subtree.get()
-    }
-
-    pub(crate) fn new_subtree(&self) -> Option<u32> {
-        if self.is_subtree_root.get() {
-            None
-        } else {
-            let cur = self.shared.cur_subtree.get();
-            self.shared.cur_subtree.set(cur + 1);
-            Some(cur)
-        }
-    }
-
-    /// Get the height of this Scope - IE the number of scopes above it.
-    ///
-    /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// assert_eq!(base.height(), 0);
-    /// ```
-    pub fn height(&self) -> u32 {
-        self.height
-    }
-
-    /// Get the Parent of this Scope within this Dioxus VirtualDOM.
-    ///
-    /// This ID is not unique across Dioxus VirtualDOMs or across time. IDs will be reused when components are unmounted.
-    ///
-    /// The base component will not have a parent, and will return `None`.
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// assert_eq!(base.parent(), None);
-    /// ```
-    pub fn parent(&self) -> Option<ScopeId> {
-        self.parent_idx
-    }
-
-    /// Get the ID of this Scope within this Dioxus VirtualDOM.
-    ///
-    /// This ID is not unique across Dioxus VirtualDOMs or across time. IDs will be reused when components are unmounted.
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    /// let base = dom.base_scope();
-    ///
-    /// assert_eq!(base.scope_id(), 0);
-    /// ```
-    pub fn scope_id(&self) -> ScopeId {
-        self.our_arena_idx
-    }
+pub struct SelfReferentialItems<'a> {
+    pub(crate) listeners: Vec<*const Listener<'a>>,
+    pub(crate) borrowed_props: Vec<*const VComponent<'a>>,
+    pub(crate) suspended_nodes: FxHashMap<u64, *const VSuspended<'a>>,
+    pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
+    pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
 }
 
-// The type of closure that wraps calling components
-/// The type of task that gets sent to the task scheduler
-/// Submitting a fiber task returns a handle to that task, which can be used to wake up suspended nodes
-pub type FiberTask = Pin<Box<dyn Future<Output = ScopeId>>>;
-
-/// Private interface for Scopes.
 impl ScopeInner {
     // we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
     // we are going to break this lifetime by force in order to save it on ourselves.
@@ -209,48 +87,36 @@ impl ScopeInner {
     // Scopes cannot be made anywhere else except for this file
     // Therefore, their lifetimes are connected exclusively to the virtual dom
     pub(crate) fn new(
-        caller: BumpBox<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
+        vcomp: &VComponent,
         our_arena_idx: ScopeId,
         parent_idx: Option<ScopeId>,
         height: u32,
         subtree: u32,
         shared: EventChannel,
     ) -> Self {
-        let schedule_any_update = shared.schedule_any_immediate.clone();
-
-        let memoized_updater = Rc::new(move || schedule_any_update(our_arena_idx));
-
-        // wipe away the associated lifetime - we are going to manually manage the one-way lifetime graph
-        let caller = unsafe { std::mem::transmute(caller) };
+        let vcomp = unsafe { std::mem::transmute(vcomp as *const VComponent) };
 
         Self {
-            memoized_updater,
             shared,
-            caller,
             parent_idx,
             our_arena_idx,
             height,
             subtree: Cell::new(subtree),
             is_subtree_root: Cell::new(false),
-            tasks: Default::default(),
             frames: ActiveFrame::new(),
-            hooks: Default::default(),
+            vcomp,
 
-            pending_effects: Default::default(),
-            suspended_nodes: Default::default(),
+            hooks: Default::default(),
             shared_contexts: Default::default(),
-            listeners: Default::default(),
-            borrowed_props: Default::default(),
-        }
-    }
 
-    pub(crate) fn update_scope_dependencies(
-        &mut self,
-        caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
-    ) {
-        log::debug!("Updating scope dependencies {:?}", self.our_arena_idx);
-        let caller = caller as *const _;
-        self.caller = unsafe { std::mem::transmute(caller) };
+            items: RefCell::new(SelfReferentialItems {
+                listeners: Default::default(),
+                borrowed_props: Default::default(),
+                suspended_nodes: Default::default(),
+                tasks: Default::default(),
+                pending_effects: Default::default(),
+            }),
+        }
     }
 
     /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
@@ -269,8 +135,9 @@ impl ScopeInner {
         // 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 Reference)
         // right now, we don't drop
-        self.borrowed_props
+        self.items
             .get_mut()
+            .borrowed_props
             .drain(..)
             .map(|li| unsafe { &*li })
             .for_each(|comp| {
@@ -280,17 +147,19 @@ impl ScopeInner {
                     .get()
                     .expect("VComponents should be associated with a valid Scope");
 
-                if let Some(scope) = pool.get_scope_mut(scope_id) {
+                if let Some(scope) = pool.get_scope_mut(&scope_id) {
                     scope.ensure_drop_safety(pool);
 
-                    let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
-                    drop_props();
+                    todo!("drop the component's props");
+                    // let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
+                    // drop_props();
                 }
             });
 
         // Now that all the references are gone, we can safely drop our own references in our listeners.
-        self.listeners
+        self.items
             .get_mut()
+            .listeners
             .drain(..)
             .map(|li| unsafe { &*li })
             .for_each(|listener| drop(listener.callback.borrow_mut().take()));
@@ -298,7 +167,7 @@ impl ScopeInner {
 
     /// A safe wrapper around calling listeners
     pub(crate) fn call_listener(&mut self, event: UserEvent, element: ElementId) {
-        let listners = self.listeners.borrow_mut();
+        let listners = &mut self.items.get_mut().listeners;
 
         let raw_listener = listners.iter().find(|lis| {
             let search = unsafe { &***lis };
@@ -321,12 +190,10 @@ impl ScopeInner {
         }
     }
 
-    /*
-    General strategy here is to load up the appropriate suspended task and then run it.
-    Suspended nodes cannot be called repeatedly.
-    */
+    // General strategy here is to load up the appropriate suspended task and then run it.
+    // Suspended nodes cannot be called repeatedly.
     pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
-        let mut nodes = self.suspended_nodes.borrow_mut();
+        let mut nodes = &mut self.items.get_mut().suspended_nodes;
 
         if let Some(suspended) = nodes.remove(&task_id) {
             let sus: &'a VSuspended<'static> = unsafe { &*suspended };
@@ -338,19 +205,9 @@ impl ScopeInner {
 
     // run the list of effects
     pub(crate) fn run_effects(&mut self, pool: &ResourcePool) {
-        todo!()
-        // let mut effects = self.frames.effects.borrow_mut();
-        // let mut effects = effects.drain(..).collect::<Vec<_>>();
-
-        // for effect in effects {
-        //     let effect = unsafe { &*effect };
-        //     let effect = effect.as_ref();
-
-        //     let mut effect = effect.borrow_mut();
-        //     let mut effect = effect.as_mut();
-
-        //     effect.run(pool);
-        // }
+        for mut effect in self.items.get_mut().pending_effects.drain(..) {
+            effect();
+        }
     }
 
     /// Render this component.
@@ -371,18 +228,22 @@ impl ScopeInner {
         // - We've dropped all references to the wip bump frame
         unsafe { self.frames.reset_wip_frame() };
 
+        let items = self.items.get_mut();
+
         // just forget about our suspended nodes while we're at it
-        self.suspended_nodes.get_mut().clear();
+        items.suspended_nodes.clear();
 
         // guarantee that we haven't screwed up - there should be no latent references anywhere
-        debug_assert!(self.listeners.borrow().is_empty());
-        debug_assert!(self.suspended_nodes.borrow().is_empty());
-        debug_assert!(self.borrowed_props.borrow().is_empty());
+        debug_assert!(items.listeners.is_empty());
+        debug_assert!(items.suspended_nodes.is_empty());
+        debug_assert!(items.borrowed_props.is_empty());
 
         log::debug!("Borrowed stuff is successfully cleared");
 
-        // Cast the caller ptr from static to one with our own reference
-        let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = unsafe { &*self.caller };
+        // temporarily cast the vcomponent to the right lifetime
+        let vcomp = self.load_vcomp();
+
+        let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = todo!();
 
         // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
         if let Some(builder) = render(self) {
@@ -400,34 +261,160 @@ impl ScopeInner {
             false
         }
     }
+
+    pub(crate) fn new_subtree(&self) -> Option<u32> {
+        todo!()
+        // if self.is_subtree_root.get() {
+        //     None
+        // } else {
+        //     let cur = self.shared.cur_subtree.get();
+        //     self.shared.cur_subtree.set(cur + 1);
+        //     Some(cur)
+        // }
+    }
+
+    pub(crate) fn update_vcomp(&mut self, vcomp: &VComponent) {
+        let f: *const _ = vcomp;
+        self.vcomp = unsafe { std::mem::transmute(f) };
+    }
+
+    pub(crate) fn load_vcomp<'a>(&'a mut self) -> &'a VComponent<'a> {
+        unsafe { std::mem::transmute(&*self.vcomp) }
+    }
+
+    /// Get the root VNode for this Scope.
+    ///
+    /// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment.
+    ///
+    /// # Example
+    /// ```rust
+    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    ///
+    /// let base = dom.base_scope();
+    ///
+    /// if let VNode::VElement(node) = base.root_node() {
+    ///     assert_eq!(node.tag_name, "div");
+    /// }
+    /// ```
+    pub fn root_node(&self) -> &VNode {
+        self.frames.fin_head()
+    }
+
+    /// Get the subtree ID that this scope belongs to.
+    ///
+    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
+    /// the mutations to the correct window/portal/subtree.
+    ///
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    ///
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.subtree(), 0);
+    /// ```
+    pub fn subtree(&self) -> u32 {
+        self.subtree.get()
+    }
+
+    /// Get the height of this Scope - IE the number of scopes above it.
+    ///
+    /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    ///
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.height(), 0);
+    /// ```
+    pub fn height(&self) -> u32 {
+        self.height
+    }
+
+    /// Get the Parent of this Scope within this Dioxus VirtualDOM.
+    ///
+    /// This ID is not unique across Dioxus VirtualDOMs or across time. IDs will be reused when components are unmounted.
+    ///
+    /// The base component will not have a parent, and will return `None`.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    ///
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.parent(), None);
+    /// ```
+    pub fn parent(&self) -> Option<ScopeId> {
+        self.parent_idx
+    }
+
+    /// Get the ID of this Scope within this Dioxus VirtualDOM.
+    ///
+    /// This ID is not unique across Dioxus VirtualDOMs or across time. IDs will be reused when components are unmounted.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.scope_id(), 0);
+    /// ```
+    pub fn scope_id(&self) -> ScopeId {
+        self.our_arena_idx
+    }
+
     /// Create a subscription that schedules a future render for the reference component
     ///
     /// ## Notice: you should prefer using prepare_update and get_scope_id
     pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        self.memoized_updater.clone()
+        // pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
+        let chan = self.shared.sender.clone();
+        let id = self.scope_id();
+        Rc::new(move || {
+            chan.unbounded_send(SchedulerMsg::Immediate(id));
+        })
+    }
+
+    /// Schedule an update for any component given its ScopeId.
+    ///
+    /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
+    ///
+    /// This method should be used when you want to schedule an update for a component
+    pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
+        let chan = self.shared.sender.clone();
+        Rc::new(move |id| {
+            chan.unbounded_send(SchedulerMsg::Immediate(id));
+        })
     }
 
     /// Get the [`ScopeId`] of a mounted component.
     ///
     /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
     pub fn needs_update(&self) {
-        (self.memoized_updater)()
+        self.needs_update_any(self.scope_id())
     }
 
     /// Get the [`ScopeId`] of a mounted component.
     ///
     /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
     pub fn needs_update_any(&self, id: ScopeId) {
-        (self.shared.schedule_any_immediate)(id)
-    }
-
-    /// Schedule an update for any component given its ScopeId.
-    ///
-    /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
-    ///
-    /// This method should be used when you want to schedule an update for a component
-    pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
-        self.shared.schedule_any_immediate.clone()
+        self.shared
+            .sender
+            .unbounded_send(SchedulerMsg::Immediate(id))
+            .unwrap();
     }
 
     /// Get the [`ScopeId`] of a mounted component.
@@ -475,8 +462,9 @@ impl ScopeInner {
         // erase the 'src lifetime for self-referential storage
         let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
 
-        self.pending_effects.borrow_mut().push(self_ref_fut);
-        self.pending_effects.borrow().len() - 1
+        let mut items = self.items.borrow_mut();
+        items.pending_effects.push(self_ref_fut);
+        items.pending_effects.len() - 1
     }
 
     /// Pushes the future onto the poll queue to be polled
@@ -491,8 +479,9 @@ impl ScopeInner {
         // erase the 'src lifetime for self-referential storage
         let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
 
-        self.tasks.borrow_mut().push(self_ref_fut);
-        self.tasks.borrow().len() - 1
+        let mut items = self.items.borrow_mut();
+        items.tasks.push(self_ref_fut);
+        items.tasks.len() - 1
     }
 
     /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
@@ -580,8 +569,9 @@ impl ScopeInner {
     ///
     /// - Initializer: closure used to create the initial hook state
     /// - Runner: closure used to output a value every time the hook is used
-    /// - Cleanup: closure used to teardown the hook once the dom is cleaned up
     ///
+    /// To "cleanup" the hook, implement `Drop` on the stored hook value. Whenever the component is dropped, the hook
+    /// will be dropped as well.
     ///
     /// # Example
     ///
@@ -591,32 +581,16 @@ impl ScopeInner {
     ///     use_hook(
     ///         || Rc::new(RefCell::new(initial_value())),
     ///         |state| state,
-    ///         |_| {},
     ///     )
     /// }
     /// ```
-    pub fn use_hook<'src, State, Output, Init, Run, Cleanup>(
+    pub fn use_hook<'src, State: 'static, Output: 'src>(
         &'src self,
-        initializer: Init,
-        runner: Run,
-        cleanup: Cleanup,
-    ) -> Output
-    where
-        State: 'static,
-        Output: 'src,
-        Init: FnOnce(usize) -> State,
-        Run: FnOnce(&'src mut State) -> Output,
-        Cleanup: FnOnce(Box<State>) + 'static,
-    {
-        // If the idx is the same as the hook length, then we need to add the current hook
+        initializer: impl FnOnce(usize) -> State,
+        runner: impl FnOnce(&'src mut State) -> Output,
+    ) -> Output {
         if self.hooks.at_end() {
-            self.hooks.push_hook(
-                initializer(self.hooks.len()),
-                Box::new(|raw| {
-                    let s = raw.downcast::<State>().unwrap();
-                    cleanup(s);
-                }),
-            );
+            self.hooks.push_hook(initializer(self.hooks.len()));
         }
 
         runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))

+ 0 - 39
packages/core/src/tasks.rs

@@ -1,39 +0,0 @@
-use crate::innerlude::*;
-use futures_channel::mpsc::UnboundedSender;
-
-pub struct TaskHandle {
-    pub(crate) sender: UnboundedSender<SchedulerMsg>,
-    pub(crate) our_id: u64,
-}
-
-impl TaskHandle {
-    /// Toggles this coroutine off/on.
-    ///
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn toggle(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn resume(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn stop(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-
-    /// This method is not synchronous - your task will not stop immediately.
-    pub fn restart(&self) {
-        self.sender
-            .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
-            .unwrap()
-    }
-}

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

@@ -13,7 +13,7 @@ impl TestDom {
     pub fn new() -> TestDom {
         let bump = Bump::new();
         let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
-        let scheduler = Scheduler::new(sender, receiver);
+        let scheduler = Scheduler::new(sender, receiver, 10, 100);
         TestDom { bump, scheduler }
     }
 

+ 0 - 53
packages/core/src/util.rs

@@ -11,59 +11,6 @@ pub fn type_name_of<T>(_: T) -> &'static str {
     std::any::type_name::<T>()
 }
 
-use std::future::Future;
-use std::pin::Pin;
-use std::task::{Context, Poll};
-
-// use crate::task::{Context, Poll};
-
-/// Cooperatively gives up a timeslice to the task scheduler.
-///
-/// Calling this function will move the currently executing future to the back
-/// of the execution queue, making room for other futures to execute. This is
-/// especially useful after running CPU-intensive operations inside a future.
-///
-/// See also [`task::spawn_blocking`].
-///
-/// [`task::spawn_blocking`]: fn.spawn_blocking.html
-///
-/// # Examples
-///
-/// Basic usage:
-///
-/// ```
-/// # async_std::task::block_on(async {
-/// #
-/// use async_std::task;
-///
-/// task::yield_now().await;
-/// #
-/// # })
-/// ```
-#[inline]
-pub async fn yield_now() {
-    YieldNow(false).await
-}
-
-struct YieldNow(bool);
-
-impl Future for YieldNow {
-    type Output = ();
-
-    // The futures executor is implemented as a FIFO queue, so all this future
-    // does is re-schedule the future back to the end of the queue, giving room
-    // for other futures to progress.
-    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-        if !self.0 {
-            self.0 = true;
-            cx.waker().wake_by_ref();
-            Poll::Pending
-        } else {
-            Poll::Ready(())
-        }
-    }
-}
-
 /// A component's unique identifier.
 ///
 /// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is

+ 605 - 57
packages/core/src/virtual_dom.rs

@@ -19,10 +19,24 @@
 //! This module includes just the barebones for a complete VirtualDOM API.
 //! Additional functionality is defined in the respective files.
 
+use crate::innerlude::*;
+use bumpalo::Bump;
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
+use fxhash::FxHashMap;
+use fxhash::FxHashSet;
+use indexmap::IndexSet;
+use slab::Slab;
+use std::pin::Pin;
+use std::task::Poll;
+use std::{
+    any::{Any, TypeId},
+    cell::{Cell, UnsafeCell},
+    collections::{HashSet, VecDeque},
+    rc::Rc,
+};
 
 use crate::innerlude::*;
-use std::{any::Any, rc::Rc};
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
 ///
@@ -55,8 +69,6 @@ use std::{any::Any, rc::Rc};
 /// }
 /// ```
 pub struct VirtualDom {
-    scheduler: Scheduler,
-
     base_scope: ScopeId,
 
     root_fc: Box<dyn Any>,
@@ -65,6 +77,49 @@ pub struct VirtualDom {
 
     // we need to keep the allocation around, but we don't necessarily use it
     _root_caller: Box<dyn Any>,
+
+    // /// All mounted components are arena allocated to make additions, removals, and references easy to work with
+    // /// 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 guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
+    // pub pool: ResourcePool,
+    //
+    pub component_arena: Bump,
+
+    pub free_components: VecDeque<*mut ScopeInner>,
+
+    pub heuristics: FxHashMap<FcSlot, Heuristic>,
+
+    pub receiver: UnboundedReceiver<SchedulerMsg>,
+
+    // Garbage stored
+    pub pending_garbage: FxHashSet<ScopeId>,
+
+    // Every component that has futures that need to be polled
+    pub pending_futures: FxHashSet<ScopeId>,
+
+    pub ui_events: VecDeque<UserEvent>,
+
+    pub pending_immediates: VecDeque<ScopeId>,
+
+    pub batched_events: VecDeque<UserEvent>,
+
+    pub garbage_scopes: HashSet<ScopeId>,
+
+    pub dirty_scopes: IndexSet<ScopeId>,
+
+    pub saved_state: Option<SavedDiffWork<'static>>,
+
+    pub in_progress: bool,
+}
+
+pub enum SchedulerMsg {
+    // events from the host
+    UiEvent(UserEvent),
+
+    // setstate
+    Immediate(ScopeId),
 }
 
 impl VirtualDom {
@@ -127,7 +182,7 @@ impl VirtualDom {
         Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
     }
 
-    /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
+    /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the
     ///
     /// 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 retrieve its channel receiver.
@@ -137,33 +192,71 @@ impl VirtualDom {
         sender: UnboundedSender<SchedulerMsg>,
         receiver: UnboundedReceiver<SchedulerMsg>,
     ) -> Self {
-        let root_fc = Box::new(root);
-
-        let root_props: Rc<dyn Any> = Rc::new(root_props);
-
-        let props = root_props.clone();
-
-        let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
-            Box::new(move |scope: &ScopeInner| {
-                let props = props.downcast_ref::<P>().unwrap();
-                let node = root((scope, props));
-                // cast into the right lifetime
-                unsafe { std::mem::transmute(node) }
-            });
-        let caller = unsafe { bumpalo::boxed::Box::from_raw(root_caller.as_mut() as *mut _) };
-
-        let scheduler = Scheduler::new(sender, receiver);
-
-        let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
-            ScopeInner::new(caller, myidx, None, 0, 0, scheduler.pool.channel.clone())
-        });
+        let mut component_arena = Bump::new();
+
+        // let root_fc = Box::new(root);
+
+        // let root_props: Rc<dyn Any> = Rc::new(root_props);
+
+        // let props = root_props.clone();
+
+        // let mut root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
+        //     Box::new(move |scope: &ScopeInner| {
+        //         let props = props.downcast_ref::<P>().unwrap();
+        //         let node = root((scope, props));
+        //         // cast into the right lifetime
+        //         unsafe { std::mem::transmute(node) }
+        //     });
+
+        // let caller = unsafe { bumpalo::boxed::Box::from_raw(root_caller.as_mut() as *mut _) };
+
+        // // todo make the memory footprint congifurable
+        // let scheduler = Scheduler::new(sender, receiver, 100, 2000);
+
+        // let vcomp = VComponent {
+        //     key: todo!(),
+        //     associated_scope: todo!(),
+        //     user_fc: root as *const _,
+        //     can_memoize: todo!(),
+        //     raw_props: todo!(),
+        //     // drop_props: todo!(),
+        //     // caller,
+        //     comparator: todo!(),
+        //     caller: todo!(),
+        // };
+
+        // let boxed_comp = Box::new(vcomp);
+
+        // let base_scope = pool.insert_scope_with_key(|myidx| {
+        //     ScopeInner::new(
+        //         boxed_comp.as_ref(),
+        //         myidx,
+        //         None,
+        //         0,
+        //         0,
+        //         pool.channel.clone(),
+        //     )
+        // });
 
         Self {
-            _root_caller: Box::new(root_caller),
-            root_fc,
-            base_scope,
-            scheduler,
-            root_props,
+            scheduler: todo!(),
+            base_scope: todo!(),
+            root_fc: todo!(),
+            root_props: todo!(),
+            _root_caller: todo!(),
+            component_arena: todo!(),
+            free_components: todo!(),
+            heuristics: todo!(),
+            receiver,
+            pending_garbage: todo!(),
+            pending_futures: todo!(),
+            ui_events: todo!(),
+            pending_immediates: todo!(),
+            batched_events: todo!(),
+            garbage_scopes: todo!(),
+            dirty_scopes: todo!(),
+            saved_state: todo!(),
+            in_progress: todo!(),
         }
     }
 
@@ -172,12 +265,12 @@ impl VirtualDom {
     /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
     /// directly.
     pub fn base_scope(&self) -> &ScopeInner {
-        self.scheduler.pool.get_scope(self.base_scope).unwrap()
+        self.pool.get_scope(&self.base_scope).unwrap()
     }
 
     /// Get the [`Scope`] for a component given its [`ScopeId`]
     pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> {
-        self.scheduler.pool.get_scope(id)
+        self.pool.get_scope(&id)
     }
 
     /// Update the root props of this VirtualDOM.
@@ -202,10 +295,10 @@ impl VirtualDom {
     where
         P: 'static,
     {
-        let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
+        let root_scope = self.pool.get_scope_mut(&self.base_scope).unwrap();
 
         // Pre-emptively drop any downstream references of the old props
-        root_scope.ensure_drop_safety(&self.scheduler.pool);
+        root_scope.ensure_drop_safety(&self.pool);
 
         let mut root_props: Rc<dyn Any> = Rc::new(root_props);
 
@@ -221,8 +314,6 @@ impl VirtualDom {
                     std::mem::transmute(root((scope, props)))
                 });
 
-            root_scope.update_scope_dependencies(&root_caller);
-
             drop(root_props);
 
             Some(self.rebuild())
@@ -249,7 +340,7 @@ impl VirtualDom {
     /// apply_edits(edits);
     /// ```
     pub fn rebuild(&mut self) -> Mutations {
-        self.scheduler.rebuild(self.base_scope)
+        self.rebuild(self.base_scope)
     }
 
     /// Compute a manual diff of the VirtualDOM between states.
@@ -287,7 +378,7 @@ impl VirtualDom {
     /// let edits = dom.diff();
     /// ```
     pub fn diff(&mut self) -> Mutations {
-        self.scheduler.hard_diff(self.base_scope)
+        self.hard_diff(self.base_scope)
     }
 
     /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
@@ -295,8 +386,8 @@ impl VirtualDom {
     /// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
     /// return "None"
     pub fn run_immediate(&mut self) -> Option<Vec<Mutations>> {
-        if self.scheduler.has_any_work() {
-            Some(self.scheduler.work_sync())
+        if self.has_any_work() {
+            Some(self.work_sync())
         } else {
             None
         }
@@ -346,18 +437,18 @@ impl VirtualDom {
     ///
     /// Mutations are the only link between the RealDOM and the VirtualDOM.
     pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
-        self.scheduler.work_with_deadline(deadline)
+        self.work_with_deadline(deadline)
     }
 
     pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
-        self.scheduler.pool.channel.sender.clone()
+        self.pool.channel.sender.clone()
     }
 
     /// Waits for the scheduler to have work
     /// This lets us poll async tasks during idle periods without blocking the main thread.
     pub async fn wait_for_work(&mut self) {
         // todo: poll the events once even if there is work to do to prevent starvation
-        if self.scheduler.has_any_work() {
+        if self.has_any_work() {
             return;
         }
 
@@ -365,28 +456,485 @@ impl VirtualDom {
 
         // Wait for any new events if we have nothing to do
 
-        let tasks_fut = self.scheduler.async_tasks.next();
-        let scheduler_fut = self.scheduler.receiver.next();
+        // let tasks_fut = self.async_tasks.next();
+        // let scheduler_fut = self.receiver.next();
+
+        // use futures_util::future::{select, Either};
+        // match select(tasks_fut, scheduler_fut).await {
+        //     // poll the internal futures
+        //     Either::Left((_id, _)) => {
+        //         //
+        //     }
+
+        //     // wait for an external event
+        //     Either::Right((msg, _)) => match msg.unwrap() {
+        //         SchedulerMsg::Task(t) => {
+        //             self.handle_task(t);
+        //         }
+        //         SchedulerMsg::Immediate(im) => {
+        //             self.dirty_scopes.insert(im);
+        //         }
+        //         SchedulerMsg::UiEvent(evt) => {
+        //             self.ui_events.push_back(evt);
+        //         }
+        //     },
+        // }
+    }
+}
+
+pub type FcSlot = *const ();
+
+pub struct Heuristic {
+    hook_arena_size: usize,
+    node_arena_size: usize,
+}
+
+/*
+Welcome to Dioxus's cooperative, priority-based scheduler.
+
+I hope you enjoy your stay.
+
+Some essential reading:
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
+- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
+- https://github.com/WICG/is-input-pending
+- https://web.dev/rail/
+- https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
+
+# What's going on?
+
+Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
+snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
+point in being "fast" if you can't also be "responsive."
+
+As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
+priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
+The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
+different types of work simultaneously.
+
+# How does it work?
 
-        use futures_util::future::{select, Either};
-        match select(tasks_fut, scheduler_fut).await {
-            // poll the internal futures
-            Either::Left((_id, _)) => {
+Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
+React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
+React, and the UI still stays snappy!
+
+While it's straightforward to run code ASAP and be as "fast as possible", what's not  _not_ straightforward is how to do
+this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
+has time to paint and run animations. When the browser is finished, we can step in and continue our work.
+
+React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
+necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
+"commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
+our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
+so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
+
+React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
+now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
+before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
+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).
+
+# 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-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 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)
+- Low: set_state called asynchronously, and anything generated by suspense
+
+In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
+work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
+as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
+do anything too arduous from onInput.
+
+For the rest, we defer to the rIC period and work down each queue from high to low.
+*/
+
+/// The scheduler holds basically everything around "working"
+///
+/// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
+///
+/// In Dioxus, the scheduler provides 4 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
+/// out.
+///
+/// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
+/// pending DiffInstructions, Mutations, and their underlying Scope. It's okay for us to be self-referential with this data, provided we don't priority
+/// task shift to a higher priority task that needs mutable access to the same scopes.
+///
+/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
+///
+/// There's a lot of raw pointers here...
+///
+/// Since we're building self-referential structures for each component, we need to make sure that the referencs stay stable
+/// The best way to do that is a bump allocator.
+///
+///
+///
+impl VirtualDom {
+    // returns true if the event is discrete
+    pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
+        let (discrete, priority) = event_meta(&event);
+
+        if let Some(scope) = self.get_scope_mut(&event.scope) {
+            if let Some(element) = event.mounted_dom_id {
+                // TODO: bubble properly here
+                scope.call_listener(event, element);
+
+                while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
+                    //
+                    //     self.add_dirty_scope(dirty_scope, trigger.priority)
+                }
+            }
+        }
+
+        // use EventPriority::*;
+
+        // match priority {
+        //     Immediate => todo!(),
+        //     High => todo!(),
+        //     Medium => todo!(),
+        //     Low => todo!(),
+        // }
+
+        discrete
+    }
+
+    fn prepare_work(&mut self) {
+        // while let Some(trigger) = self.ui_events.pop_back() {
+        //     if let Some(scope) = self.get_scope_mut(&trigger.scope) {}
+        // }
+    }
+
+    // nothing to do, no events on channels, no work
+    pub fn has_any_work(&self) -> bool {
+        !(self.dirty_scopes.is_empty() && self.ui_events.is_empty())
+    }
+
+    /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
+    fn balance_lanes(&mut self) {}
+
+    fn save_work(&mut self, lane: SavedDiffWork) {
+        let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) };
+        self.saved_state = Some(saved);
+    }
+
+    unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
+        self.saved_state.take().unwrap().extend()
+    }
+
+    pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
+        match msg {
+            SchedulerMsg::Immediate(_) => todo!(),
+
+            SchedulerMsg::UiEvent(event) => {
                 //
+
+                let (discrete, priority) = event_meta(&event);
+
+                if let Some(scope) = self.get_scope_mut(&event.scope) {
+                    if let Some(element) = event.mounted_dom_id {
+                        // TODO: bubble properly here
+                        scope.call_listener(event, element);
+
+                        while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
+                            //
+                            //     self.add_dirty_scope(dirty_scope, trigger.priority)
+                        }
+                    }
+                }
+
+                discrete;
             }
+        }
+    }
 
-            // wait for an external event
-            Either::Right((msg, _)) => match msg.unwrap() {
-                SchedulerMsg::Task(t) => {
-                    self.scheduler.handle_task(t);
+    /// Load the current lane, and work on it, periodically checking in if the deadline has been reached.
+    ///
+    /// Returns true if the lane is finished before the deadline could be met.
+    pub fn work_on_current_lane(
+        &mut self,
+        deadline_reached: impl FnMut() -> bool,
+        mutations: &mut Vec<Mutations>,
+    ) -> bool {
+        // Work through the current subtree, and commit the results when it finishes
+        // When the deadline expires, give back the work
+        let saved_state = unsafe { self.load_work() };
+
+        // We have to split away some parts of ourself - current lane is borrowed mutably
+        let shared = self.clone();
+        let mut machine = unsafe { saved_state.promote(&shared) };
+
+        let mut ran_scopes = FxHashSet::default();
+
+        if machine.stack.is_empty() {
+            let shared = self.clone();
+
+            self.dirty_scopes
+                .retain(|id| shared.get_scope(id).is_some());
+            self.dirty_scopes.sort_by(|a, b| {
+                let h1 = shared.get_scope(a).unwrap().height;
+                let h2 = shared.get_scope(b).unwrap().height;
+                h1.cmp(&h2).reverse()
+            });
+
+            if let Some(scopeid) = self.dirty_scopes.pop() {
+                log::info!("handling dirty scope {:?}", scopeid);
+                if !ran_scopes.contains(&scopeid) {
+                    ran_scopes.insert(scopeid);
+                    log::debug!("about to run scope {:?}", scopeid);
+
+                    if let Some(component) = self.get_scope_mut(&scopeid) {
+                        if component.run_scope(&self) {
+                            let (old, new) =
+                                (component.frames.wip_head(), component.frames.fin_head());
+                            // let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
+                            machine.stack.scope_stack.push(scopeid);
+                            machine.stack.push(DiffInstruction::Diff { new, old });
+                        }
+                    }
                 }
-                SchedulerMsg::Immediate(im) => {
-                    self.scheduler.dirty_scopes.insert(im);
+            }
+        }
+
+        let work_completed = machine.work(deadline_reached);
+
+        // log::debug!("raw edits {:?}", machine.mutations.edits);
+
+        let mut machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) };
+        // let mut saved = machine.save();
+
+        if work_completed {
+            for node in machine.seen_scopes.drain() {
+                // self.dirty_scopes.clear();
+                // self.ui_events.clear();
+                self.dirty_scopes.remove(&node);
+                // self.dirty_scopes.remove(&node);
+            }
+
+            let mut new_mutations = Mutations::new();
+
+            for edit in machine.mutations.edits.drain(..) {
+                new_mutations.edits.push(edit);
+            }
+
+            // for edit in saved.edits.drain(..) {
+            //     new_mutations.edits.push(edit);
+            // }
+
+            // std::mem::swap(&mut new_mutations, &mut saved.mutations);
+
+            mutations.push(new_mutations);
+
+            // log::debug!("saved edits {:?}", mutations);
+
+            let mut saved = machine.save();
+            self.save_work(saved);
+            true
+
+            // self.save_work(saved);
+            // false
+        } else {
+            false
+        }
+    }
+
+    /// The primary workhorse of the VirtualDOM.
+    ///
+    /// Uses some fairly complex logic to schedule what work should be produced.
+    ///
+    /// Returns a list of successful mutations.
+    pub fn work_with_deadline<'a>(
+        &'a mut self,
+        mut deadline: impl FnMut() -> bool,
+    ) -> Vec<Mutations<'a>> {
+        /*
+        Strategy:
+        - 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 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.
+            'static components have a leg up here since their work can be re-used among multiple scopes.
+            "High priority" is only for blocking! Should only be used on "clicks"
+
+        - If there are no pending discrete events, then check for continuous events. These can be completely batched
+
+        - we batch completely until we run into a discrete event
+        - all continuous events are batched together
+        - so D C C C C C would be two separate events - D and C. IE onclick and onscroll
+        - D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
+
+        - !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
+            - we need to keep track of element parents to be able to traverse properly
+
+
+        Open questions:
+        - what if we get two clicks from the component during the same slice?
+            - should we batch?
+            - react says no - they are continuous
+            - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
+        */
+        let mut committed_mutations = Vec::<Mutations<'static>>::new();
+
+        while self.has_any_work() {
+            while let Ok(Some(msg)) = self.receiver.try_next() {
+                match msg {
+                    SchedulerMsg::Immediate(im) => {
+                        self.dirty_scopes.insert(im);
+                    }
+                    SchedulerMsg::UiEvent(evt) => {
+                        self.ui_events.push_back(evt);
+                    }
                 }
-                SchedulerMsg::UiEvent(evt) => {
-                    self.scheduler.ui_events.push_back(evt);
+            }
+
+            // switch our priority, pop off any work
+            while let Some(event) = self.ui_events.pop_front() {
+                if let Some(scope) = self.get_scope_mut(&event.scope) {
+                    if let Some(element) = event.mounted_dom_id {
+                        log::info!("Calling listener {:?}, {:?}", event.scope, element);
+
+                        // TODO: bubble properly here
+                        scope.call_listener(event, element);
+
+                        while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
+                            match dirty_scope {
+                                SchedulerMsg::Immediate(im) => {
+                                    self.dirty_scopes.insert(im);
+                                }
+                                SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
+                            }
+                        }
+                    }
                 }
-            },
+            }
+
+            let work_complete = self.work_on_current_lane(&mut deadline, &mut committed_mutations);
+
+            if !work_complete {
+                return committed_mutations;
+            }
+        }
+
+        committed_mutations
+    }
+
+    /// Work the scheduler down, not polling any ongoing tasks.
+    ///
+    /// Will use the standard priority-based scheduling, batching, etc, but just won't interact with the async reactor.
+    pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
+        let mut committed_mutations = Vec::new();
+
+        while let Ok(Some(msg)) = self.receiver.try_next() {
+            self.handle_channel_msg(msg);
+        }
+
+        if !self.has_any_work() {
+            return committed_mutations;
+        }
+
+        while self.has_any_work() {
+            self.prepare_work();
+            self.work_on_current_lane(|| false, &mut committed_mutations);
+        }
+
+        committed_mutations
+    }
+
+    /// Restart the entire VirtualDOM from scratch, wiping away any old state and components.
+    ///
+    /// Typically used to kickstart the VirtualDOM after initialization.
+    pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
+        let mut shared = self.clone();
+        let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
+
+        // TODO: drain any in-flight work
+        let cur_component = self
+            .pool
+            .get_scope_mut(&base_scope)
+            .expect("The base scope should never be moved");
+
+        log::debug!("rebuild {:?}", base_scope);
+
+        // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
+        if cur_component.run_scope(&self) {
+            diff_machine
+                .stack
+                .create_node(cur_component.frames.fin_head(), MountType::Append);
+
+            diff_machine.stack.scope_stack.push(base_scope);
+
+            diff_machine.work(|| false);
+        } else {
+            // todo: should this be a hard error?
+            log::warn!(
+                "Component failed to run successfully during rebuild.
+                This does not result in a failed rebuild, but indicates a logic failure within your app."
+            );
+        }
+
+        unsafe { std::mem::transmute(diff_machine.mutations) }
+    }
+
+    pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
+        let cur_component = self
+            .pool
+            .get_scope_mut(&base_scope)
+            .expect("The base scope should never be moved");
+
+        log::debug!("hard diff {:?}", base_scope);
+
+        if cur_component.run_scope(&self) {
+            let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self);
+            diff_machine.cfg.force_diff = true;
+            diff_machine.diff_scope(base_scope);
+            diff_machine.mutations
+        } else {
+            Mutations::new()
+        }
+    }
+}
+
+impl Future for VirtualDom {
+    type Output = ();
+
+    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
+        let mut all_pending = true;
+
+        for fut in self.pending_futures.iter() {
+            let scope = self
+                .pool
+                .get_scope_mut(&fut)
+                .expect("Scope should never be moved");
+
+            let items = scope.items.get_mut();
+            for task in items.tasks.iter_mut() {
+                let t = task.as_mut();
+                let g = unsafe { Pin::new_unchecked(t) };
+                match g.poll(cx) {
+                    Poll::Ready(r) => {
+                        all_pending = false;
+                    }
+                    Poll::Pending => {}
+                }
+            }
+        }
+
+        match all_pending {
+            true => Poll::Pending,
+            false => Poll::Ready(()),
         }
     }
 }