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

+ 1 - 1
packages/core/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-  "rust-analyzer.inlayHints.enable": true
+  "rust-analyzer.inlayHints.enable": false
 }

+ 6 - 43
packages/core/src/context.rs

@@ -96,13 +96,11 @@ impl<'src, P> Context<'src, P> {
     /// ## Notice: you should prefer using prepare_update and get_scope_id
     ///
     pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
-        let cb = self.scope.vdom.schedule_update();
-        let id = self.get_scope_id();
-        Rc::new(move || cb(id))
+        self.scope.memoized_updater.clone()
     }
 
     pub fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
-        self.scope.vdom.schedule_update()
+        self.scope.shared.schedule_any_immediate.clone()
     }
 
     pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
@@ -160,7 +158,7 @@ impl<'src, P> Context<'src, P> {
     ///
     ///
     pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        self.scope.vdom.submit_task(task)
+        (self.scope.shared.submit_task)(task)
     }
 
     /// Add a state globally accessible to child components via tree walking
@@ -174,46 +172,11 @@ impl<'src, P> Context<'src, P> {
             });
     }
 
-    /// Walk the tree to find a shared state with the TypeId of the generic type
-    ///
     pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
-        let mut scope = Some(self.scope);
-        let mut parent = None;
-
+        let getter = &self.scope.shared.get_shared_context;
         let ty = TypeId::of::<T>();
-        while let Some(inner) = scope {
-            log::debug!(
-                "Searching {:#?} for valid shared_context",
-                inner.our_arena_idx
-            );
-            let shared_ctx = {
-                let shared_contexts = inner.shared_contexts.borrow();
-
-                log::debug!(
-                    "This component has {} shared contexts",
-                    shared_contexts.len()
-                );
-                shared_contexts.get(&ty).map(|f| f.clone())
-            };
-
-            if let Some(shared_cx) = shared_ctx {
-                log::debug!("found matching cx");
-                let rc = shared_cx
-                    .clone()
-                    .downcast::<T>()
-                    .expect("Should not fail, already validated the type from the hashmap");
-                parent = Some(rc);
-                break;
-            } else {
-                match inner.parent_idx {
-                    Some(parent_id) => {
-                        scope = unsafe { inner.vdom.get_scope(parent_id) };
-                    }
-                    None => break,
-                }
-            }
-        }
-        parent
+        let idx = self.scope.our_arena_idx;
+        getter(idx, ty).map(|f| f.downcast().unwrap())
     }
 
     /// Store a value between renders

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

@@ -88,7 +88,7 @@
 //! More info on how to improve this diffing algorithm:
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
 
-use crate::{arena::Scheduler, innerlude::*};
+use crate::{innerlude::*, scheduler::Scheduler};
 use fxhash::{FxHashMap, FxHashSet};
 use DomEdit::*;
 
@@ -110,9 +110,7 @@ pub struct DiffMachine<'bump> {
     pub mutations: &'bump mut Mutations<'bump>,
 
     pub stack: DiffStack<'bump>,
-
     pub diffed: FxHashSet<ScopeId>,
-
     pub seen_scopes: FxHashSet<ScopeId>,
 }
 
@@ -310,6 +308,7 @@ impl<'bump> DiffMachine<'bump> {
 
         let parent_idx = self.stack.current_scope().unwrap();
 
+        let shared = self.vdom.channel.clone();
         // Insert a new scope into our component list
         let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
             let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
@@ -320,8 +319,7 @@ impl<'bump> DiffMachine<'bump> {
                 Some(parent_idx),
                 height,
                 ScopeChildren(vcomponent.children),
-                // self.vdom.clone(),
-                vcomponent.name,
+                shared,
             )
         });
 

+ 8 - 154
packages/core/src/events.rs

@@ -17,169 +17,34 @@ use crate::{
 #[derive(Debug)]
 pub struct EventTrigger {
     /// The originator of the event trigger
-    pub originator: ScopeId,
+    pub scope: ScopeId,
 
     /// The optional real node associated with the trigger
-    pub real_node_id: Option<ElementId>,
+    pub mounted_dom_id: Option<ElementId>,
 
     /// The type of event
     pub event: VirtualEvent,
 
-    /// The priority of the event
-    pub priority: EventPriority,
-}
-
-#[derive(PartialEq, Eq, Clone, Copy)]
-pub struct EventKey {
-    /// The originator of the event trigger
-    pub originator: ScopeId,
-    /// The priority of the event
-    pub priority: EventPriority,
-    /// The height of the scope (used for ordering)
-    pub height: u32,
-    // TODO: add the time that the event was queued
-}
-
-impl PartialOrd for EventKey {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        todo!()
-    }
-}
-impl Ord for EventKey {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        todo!()
-    }
-}
-
-/// Priority of Event Triggers.
-///
-/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
-/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
-/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
-///
-/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
-///
-/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
-/// we keep it simple, and just use a 3-tier priority system.
-///
-/// - NoPriority = 0
-/// - LowPriority = 1
-/// - NormalPriority = 2
-/// - UserBlocking = 3
-/// - HighPriority = 4
-/// - ImmediatePriority = 5
-///
-/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
-/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
-/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
-pub enum EventPriority {
-    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
-    ///
-    /// This is typically reserved for things like user interaction.
-    ///
-    /// React calls these "discrete" events, but with an extra category of "user-blocking".
-    High = 2,
-
-    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
-    /// than "High Priority" events and will take presedence over low priority events.
-    ///
-    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
-    ///
-    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
-    Medium = 1,
-
-    /// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
-    /// advanced to the front of the work queue until completed.
-    ///
-    /// The primary user of Low Priority work is the asynchronous work system (suspense).
-    ///
-    /// This is considered "idle" work or "background" work.
-    Low = 0,
-}
-
-impl EventTrigger {
-    pub fn new(
-        event: VirtualEvent,
-        scope: ScopeId,
-        mounted_dom_id: Option<ElementId>,
-        priority: EventPriority,
-    ) -> Self {
-        Self {
-            priority,
-            originator: scope,
-            real_node_id: mounted_dom_id,
-            event,
-        }
-    }
+    pub discrete: bool,
 }
 
 pub enum VirtualEvent {
-    // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
-    //
-    // Async events don't necessarily propagate into a scope being ran. It's up to the event itself
-    // to force an update for itself.
-    //
-    // Most async events should have a low priority.
-    //
-    // This type exists for the task/concurrency system to signal that a task is ready.
-    // However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
-    // own re-run.
-    AsyncEvent {
-        should_rerender: bool,
-    },
-
-    // Suspense events are a type of async event generated when suspended nodes are ready to be processed.
-    //
-    // they have the lowest priority
-    SuspenseEvent {
-        hook_idx: usize,
-        domnode: Rc<Cell<Option<ElementId>>>,
-    },
-
-    // image event has conflicting method types
-    // ImageEvent(event_data::ImageEvent),
-
-    // Real events
+    KeyboardEvent(on::KeyboardEvent),
+    TouchEvent(on::TouchEvent),
+    MouseEvent(on::MouseEvent),
     ClipboardEvent(on::ClipboardEvent),
     CompositionEvent(on::CompositionEvent),
-    KeyboardEvent(on::KeyboardEvent),
     FocusEvent(on::FocusEvent),
     FormEvent(on::FormEvent),
     SelectionEvent(on::SelectionEvent),
-    TouchEvent(on::TouchEvent),
     UIEvent(on::UIEvent),
     WheelEvent(on::WheelEvent),
     MediaEvent(on::MediaEvent),
     AnimationEvent(on::AnimationEvent),
     TransitionEvent(on::TransitionEvent),
     ToggleEvent(on::ToggleEvent),
-    MouseEvent(on::MouseEvent),
     PointerEvent(on::PointerEvent),
-}
-
-impl VirtualEvent {
-    pub fn is_input_event(&self) -> bool {
-        match self {
-            VirtualEvent::ClipboardEvent(_)
-            | VirtualEvent::CompositionEvent(_)
-            | VirtualEvent::KeyboardEvent(_)
-            | VirtualEvent::FocusEvent(_)
-            | VirtualEvent::FormEvent(_)
-            | VirtualEvent::SelectionEvent(_)
-            | VirtualEvent::TouchEvent(_)
-            | VirtualEvent::UIEvent(_)
-            | VirtualEvent::WheelEvent(_)
-            | VirtualEvent::MediaEvent(_)
-            | VirtualEvent::AnimationEvent(_)
-            | VirtualEvent::TransitionEvent(_)
-            | VirtualEvent::ToggleEvent(_)
-            | VirtualEvent::MouseEvent(_)
-            | VirtualEvent::PointerEvent(_) => true,
-
-            VirtualEvent::AsyncEvent { .. } | VirtualEvent::SuspenseEvent { .. } => false,
-        }
-    }
+    // ImageEvent(event_data::ImageEvent),
 }
 
 impl std::fmt::Debug for VirtualEvent {
@@ -200,8 +65,6 @@ impl std::fmt::Debug for VirtualEvent {
             VirtualEvent::ToggleEvent(_) => "ToggleEvent",
             VirtualEvent::MouseEvent(_) => "MouseEvent",
             VirtualEvent::PointerEvent(_) => "PointerEvent",
-            VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
-            VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
         };
 
         f.debug_struct("VirtualEvent").field("type", &name).finish()
@@ -215,8 +78,7 @@ pub mod on {
     //! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling
     //! Arc allocation through "get_mut"
     //!
-    //!
-    //!
+    //! React recently dropped support for re-using event allocation and just passes the real event along.
 
     #![allow(unused)]
     use bumpalo::boxed::Box as BumpBox;
@@ -285,14 +147,6 @@ pub mod on {
     }
 
     // The Dioxus Synthetic event system
-    //
-    //
-    //
-    //
-    //
-    //
-    //
-    //
     event_directory! {
         ClipboardEventInner(ClipboardEvent): [
             /// Called when "copy"

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

@@ -130,9 +130,9 @@ where
                     event: VirtualEvent::AsyncEvent {
                         should_rerender: false,
                     },
-                    originator,
+                    scope: originator,
                     priority: EventPriority::Low,
-                    real_node_id: None,
+                    mounted_dom_id: None,
                 }
             })));
 
@@ -209,9 +209,9 @@ where
                 *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
                 EventTrigger {
                     event: VirtualEvent::SuspenseEvent { hook_idx, domnode },
-                    originator,
+                    scope: originator,
                     priority: EventPriority::Low,
-                    real_node_id: None,
+                    mounted_dom_id: None,
                 }
             })));
 

+ 5 - 4
packages/core/src/lib.rs

@@ -28,7 +28,6 @@ pub mod prelude {
 
 // types used internally that are important
 pub(crate) mod innerlude {
-    pub use crate::arena::*;
     pub use crate::bumpframe::*;
     pub use crate::childiter::*;
     pub use crate::component::*;
@@ -43,6 +42,7 @@ pub(crate) mod innerlude {
     pub use crate::hooks::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;
+    pub use crate::scheduler::*;
     // pub use crate::scheduler::*;
     pub use crate::scope::*;
     pub use crate::util::*;
@@ -56,11 +56,12 @@ pub(crate) mod innerlude {
 }
 
 pub mod exports {
-    // export important things here
+    //! Important dependencies that are used by the rest of the library
+
+    // the foundation of this library
     pub use bumpalo;
 }
 
-pub mod arena;
 pub mod bumpframe;
 pub mod childiter;
 pub mod component;
@@ -75,7 +76,7 @@ pub mod hooklist;
 pub mod hooks;
 pub mod mutations;
 pub mod nodes;
-// pub mod scheduler;
+pub mod scheduler;
 pub mod scope;
 pub mod signals;
 pub mod util;

+ 215 - 125
packages/core/src/arena.rs → packages/core/src/scheduler.rs

@@ -1,4 +1,4 @@
-use std::cell::{RefCell, RefMut};
+use std::cell::{Cell, RefCell, RefMut};
 use std::fmt::Display;
 use std::{cell::UnsafeCell, rc::Rc};
 
@@ -23,25 +23,68 @@ use futures_util::Future;
 use futures_util::FutureExt;
 use futures_util::StreamExt;
 
-type UiReceiver = UnboundedReceiver<EventTrigger>;
-type UiSender = UnboundedSender<EventTrigger>;
+#[derive(Clone)]
+pub struct EventChannel {
+    pub task_counter: Rc<Cell<u64>>,
+    pub sender: UnboundedSender<SchedulerMsg>,
+    pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
+    pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
+    pub get_shared_context: Rc<dyn Fn(ScopeId, TypeId) -> Option<Rc<dyn Any>>>,
+}
 
-type TaskReceiver = UnboundedReceiver<ScopeId>;
-type TaskSender = UnboundedSender<ScopeId>;
+pub enum SchedulerMsg {
+    Immediate(ScopeId),
+    UiEvent(),
+    SubmitTask(u64),
+    ToggleTask(u64),
+    PauseTask(u64),
+    ResumeTask(u64),
+    DropTask(u64),
+}
 
-/// These are resources shared among all the components and the virtualdom itself
+/// 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 3 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.
+///
+///
 pub struct Scheduler {
-    pub components: UnsafeCell<Slab<Scope>>,
+    /*
+    This *has* to be an UnsafeCell.
+
+    Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to
+    its bumpframe conents immutably.
+
+    However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly
+    simple to audit.
+
+    Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!)
+    */
+    pub components: Rc<UnsafeCell<Slab<Scope>>>,
+
+    /*
+    Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy
+    that slab uses. The slab essentially just provides keys for ElementIDs that we can re-use in a Vec on the client.
 
-    pub(crate) heuristics: HeuristicsEngine,
+    This just happened to be the simplest and most efficient way to implement a deterministic keyed map with slot reuse.
 
-    // Used by "set_state" and co - is its own queue
-    pub immediate_sender: TaskSender,
-    pub immediate_receiver: TaskReceiver,
+    In the future, we could actually store a pointer to the VNode instead of nil to provide O(1) lookup for VNodes...
+    */
+    pub raw_elements: Slab<()>,
+
+    pub heuristics: HeuristicsEngine,
+
+    pub channel: EventChannel,
 
-    /// Triggered by event listeners
-    pub ui_event_sender: UiSender,
-    pub ui_event_receiver: UiReceiver,
+    pub receiver: UnboundedReceiver<SchedulerMsg>,
 
     // Garbage stored
     pub pending_garbage: FxHashSet<ScopeId>,
@@ -49,12 +92,6 @@ pub struct Scheduler {
     // In-flight futures
     pub async_tasks: FuturesUnordered<FiberTask>,
 
-    /// We use a SlotSet to keep track of the keys that are currently being used.
-    /// However, we don't store any specific data since the "mirror"
-    pub raw_elements: Slab<()>,
-
-    pub task_setter: Rc<dyn Fn(ScopeId)>,
-
     // scheduler stuff
     pub current_priority: EventPriority,
 
@@ -66,75 +103,78 @@ pub struct Scheduler {
 
     pub garbage_scopes: HashSet<ScopeId>,
 
-    pub high_priorty: PriortySystem,
-    pub medium_priority: PriortySystem,
-    pub low_priority: PriortySystem,
+    pub fibers: [PriortySystem; 3],
 }
 
 impl Scheduler {
     pub fn new() -> Self {
         // preallocate 2000 elements and 20 scopes to avoid dynamic allocation
-        let components: UnsafeCell<Slab<Scope>> = UnsafeCell::new(Slab::with_capacity(100));
+        let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
 
         // elements are super cheap - the value takes no space
         let raw_elements = Slab::with_capacity(2000);
 
-        let (ui_sender, ui_receiver) = futures_channel::mpsc::unbounded();
-        let (immediate_sender, immediate_receiver) = futures_channel::mpsc::unbounded();
-
         let heuristics = HeuristicsEngine::new();
 
-        // we allocate this task setter once to save us from having to allocate later
-        let task_setter = {
-            let queue = immediate_sender.clone();
-            let components = components.clone();
-            Rc::new(move |idx: ScopeId| {
-                let comps = unsafe { &*components.get() };
-
-                if let Some(scope) = comps.get(idx.0) {
-                    // todo!("implement immediates again")
-                    //
-
-                    // queue
-                    // .unbounded_send(EventTrigger::new(
-                    //     V
-                    //     idx,
-                    //     None,
-                    //     EventPriority::High,
-                    // ))
-                    // .expect("The event queu receiver should *never* be dropped");
-                }
-            }) as Rc<dyn Fn(ScopeId)>
+        let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
+        let task_counter = Rc::new(Cell::new(0));
+        let channel = EventChannel {
+            task_counter,
+            sender: sender.clone(),
+            schedule_any_immediate: {
+                Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap())
+            },
+            submit_task: Rc::new(|_| {
+                //
+                todo!()
+                // TaskHandle {}
+            }),
+            get_shared_context: {
+                let components = components.clone();
+                Rc::new(|id, ty| {
+                    let components = unsafe { &*components.get() };
+                    let mut search: Option<&Scope> = 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
+                })
+            },
         };
 
         Self {
+            channel,
+            receiver,
+
             components,
             async_tasks: FuturesUnordered::new(),
 
-            ui_event_receiver: ui_receiver,
-            ui_event_sender: ui_sender,
-
-            immediate_receiver: immediate_receiver,
-            immediate_sender: immediate_sender,
-
             pending_garbage: FxHashSet::default(),
 
-            heuristics: heuristics,
-            raw_elements: raw_elements,
-            task_setter,
+            heuristics,
+            raw_elements,
 
             // a storage for our receiver to dump into
             pending_events: VecDeque::new(),
+
             pending_immediates: VecDeque::new(),
+
             pending_tasks: VecDeque::new(),
 
             garbage_scopes: HashSet::new(),
 
             current_priority: EventPriority::Low,
 
-            high_priorty: PriortySystem::new(),
-            medium_priority: PriortySystem::new(),
-            low_priority: PriortySystem::new(),
+            // a dedicated fiber for each priority
+            fibers: [
+                PriortySystem::new(),
+                PriortySystem::new(),
+                PriortySystem::new(),
+            ],
         }
     }
 
@@ -176,12 +216,12 @@ impl Scheduler {
     }
 
     pub fn reserve_node(&self) -> ElementId {
-        ElementId(self.raw_elements.borrow_mut().insert(()))
+        ElementId(self.raw_elements.insert(()))
     }
 
     /// return the id, freeing the space of the original node
     pub fn collect_garbage(&self, id: ElementId) {
-        self.raw_elements.borrow_mut().remove(id.0);
+        self.raw_elements.remove(id.0);
     }
 
     pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
@@ -192,24 +232,12 @@ impl Scheduler {
         id
     }
 
-    pub fn schedule_update(&self) -> Rc<dyn Fn(ScopeId)> {
-        self.task_setter.clone()
-    }
-
-    pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        self.async_tasks.borrow_mut().push(task);
-        TaskHandle {}
-    }
-
     pub fn make_trigger_key(&self, trigger: &EventTrigger) -> EventKey {
-        let height = self
-            .get_scope(trigger.originator)
-            .map(|f| f.height)
-            .unwrap();
+        let height = self.get_scope(trigger.scope).map(|f| f.height).unwrap();
 
         EventKey {
             height,
-            originator: trigger.originator,
+            originator: trigger.scope,
             priority: trigger.priority,
         }
     }
@@ -356,8 +384,8 @@ impl Scheduler {
                 | VirtualEvent::ToggleEvent(_)
                 | VirtualEvent::MouseEvent(_)
                 | VirtualEvent::PointerEvent(_) => {
-                    if let Some(scope) = self.get_scope_mut(trigger.originator) {
-                        if let Some(element) = trigger.real_node_id {
+                    if let Some(scope) = self.get_scope_mut(trigger.scope) {
+                        if let Some(element) = trigger.mounted_dom_id {
                             scope.call_listener(trigger.event, element)?;
 
                             let receiver = self.immediate_receiver.clone();
@@ -406,10 +434,10 @@ impl Scheduler {
 
     /// If a the fiber finishes its works (IE needs to be committed) the scheduler will drop the dirty scope
     pub async fn work_with_deadline<'a>(
-        &mut self,
-        mutations: &mut Mutations<'_>,
+        &'a mut self,
+        mutations: &mut Mutations<'a>,
         deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
-    ) -> FiberResult {
+    ) -> bool {
         // check if we need to elevate priority
         self.current_priority = match (
             self.high_priorty.has_work(),
@@ -421,7 +449,7 @@ impl Scheduler {
             (false, false, _) => EventPriority::Low,
         };
 
-        let mut machine = DiffMachine::new(mutations, ScopeId(0), &self);
+        // let mut machine = DiffMachine::new(mutations, ScopeId(0), &self);
 
         let dirty_root = {
             let dirty_roots = match self.current_priority {
@@ -433,7 +461,7 @@ impl Scheduler {
             let mut dirty_root = {
                 let root = dirty_roots.iter().next();
                 if root.is_none() {
-                    return FiberResult::Done;
+                    return true;
                 }
                 root.unwrap()
             };
@@ -449,17 +477,12 @@ impl Scheduler {
             dirty_root
         };
 
-        match {
-            let fut = machine.diff_scope(*dirty_root).fuse();
-            pin_mut!(fut);
+        let fut = machine.diff_scope(*dirty_root).fuse();
+        pin_mut!(fut);
 
-            match futures_util::future::select(deadline, fut).await {
-                futures_util::future::Either::Left((deadline, work_fut)) => true,
-                futures_util::future::Either::Right((_, deadline_fut)) => false,
-            }
-        } {
-            true => FiberResult::Done,
-            false => FiberResult::Interrupted,
+        match futures_util::future::select(deadline, fut).await {
+            futures_util::future::Either::Left((deadline, work_fut)) => true,
+            futures_util::future::Either::Right((_, deadline_fut)) => false,
         }
     }
 
@@ -468,45 +491,31 @@ impl Scheduler {
     // does not return the trigger, but caches it in the scheduler
     pub async fn wait_for_any_trigger(
         &mut self,
-        mut deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
+        deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
     ) -> bool {
-        use futures_util::select;
-
-        let _immediates = self.immediate_receiver.clone();
-        let mut immediates = _immediates.borrow_mut();
+        use futures_util::future::{select, Either};
 
-        let mut _ui_events = self.ui_event_receiver.clone();
-        let mut ui_events = _ui_events.borrow_mut();
-
-        let mut _tasks = self.async_tasks.clone();
-        let mut tasks = _tasks.borrow_mut();
-
-        // set_state called
-        select! {
-            dirty_scope = immediates.next() => {
-                if let Some(scope) = dirty_scope {
-                    self.add_dirty_scope(scope, EventPriority::Low);
+        let event_fut = async {
+            match select(self.receiver.next(), self.async_tasks.next()).await {
+                Either::Left((msg, _other)) => {
+                    self.handle_channel_msg(msg.unwrap());
                 }
-            }
-
-            ui_event = ui_events.next() => {
-                if let Some(event) = ui_event {
-                    self.pending_events.push_back(event);
-                }
-            }
-
-            async_task = tasks.next() => {
-                if let Some(event) = async_task {
-                    self.pending_events.push_back(event);
+                Either::Right((task, _other)) => {
+                    // do nothing, async task will likely generate a set of scheduler messages
                 }
             }
+        };
 
-            _ = deadline => {
-                return true;
-            }
+        pin_mut!(event_fut);
 
+        match select(event_fut, deadline).await {
+            Either::Left((msg, _other)) => false,
+            Either::Right((deadline, _)) => true,
         }
-        false
+    }
+
+    pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
+        //
     }
 
     pub fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
@@ -518,12 +527,24 @@ impl Scheduler {
     }
 }
 
-pub struct TaskHandle {}
+pub struct TaskHandle {
+    channel: EventChannel,
+    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) {}
+
+    /// This method is not synchronous - your task will not stop immediately.
     pub fn start(&self) {}
+
+    /// This method is not synchronous - your task will not stop immediately.
     pub fn stop(&self) {}
+
+    /// This method is not synchronous - your task will not stop immediately.
     pub fn restart(&self) {}
 }
 
@@ -567,3 +588,72 @@ impl ElementId {
         self.0 as u64
     }
 }
+
+// // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
+// //
+// // Async events don't necessarily propagate into a scope being ran. It's up to the event itself
+// // to force an update for itself.
+// //
+// // Most async events should have a low priority.
+// //
+// // This type exists for the task/concurrency system to signal that a task is ready.
+// // However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
+// // own re-run.
+// AsyncEvent {
+//     should_rerender: bool,
+// },
+
+// // Suspense events are a type of async event generated when suspended nodes are ready to be processed.
+// //
+// // they have the lowest priority
+// SuspenseEvent {
+//     hook_idx: usize,
+//     domnode: Rc<Cell<Option<ElementId>>>,
+// },
+
+/// Priority of Event Triggers.
+///
+/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
+/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
+/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
+///
+/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
+///
+/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
+/// we keep it simple, and just use a 3-tier priority system.
+///
+/// - NoPriority = 0
+/// - LowPriority = 1
+/// - NormalPriority = 2
+/// - UserBlocking = 3
+/// - HighPriority = 4
+/// - ImmediatePriority = 5
+///
+/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
+/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
+/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
+pub enum EventPriority {
+    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
+    ///
+    /// This is typically reserved for things like user interaction.
+    ///
+    /// React calls these "discrete" events, but with an extra category of "user-blocking".
+    High = 2,
+
+    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
+    /// than "High Priority" events and will take presedence over low priority events.
+    ///
+    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
+    ///
+    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
+    Medium = 1,
+
+    /// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
+    /// advanced to the front of the work queue until completed.
+    ///
+    /// The primary user of Low Priority work is the asynchronous work system (suspense).
+    ///
+    /// This is considered "idle" work or "background" work.
+    Low = 0,
+}

+ 9 - 15
packages/core/src/scope.rs

@@ -1,5 +1,6 @@
 use crate::innerlude::*;
 use bumpalo::boxed::Box as BumpBox;
+use futures_channel::mpsc::UnboundedSender;
 use fxhash::FxHashSet;
 use std::{
     any::{Any, TypeId},
@@ -43,10 +44,9 @@ pub struct Scope {
     pub(crate) hooks: HookList,
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
-    // meta
-    pub(crate) function_name: &'static str,
-    // // A reference to the resources shared by all the comonents
-    // pub(crate) vdom: SharedResources,
+    pub(crate) memoized_updater: Rc<dyn Fn() + 'static>,
+
+    pub(crate) shared: EventChannel,
 }
 
 // The type of closure that wraps calling components
@@ -74,28 +74,22 @@ impl Scope {
 
         child_nodes: ScopeChildren,
 
-        // vdom: SharedResources,
-        function_name: &'static str,
+        shared: EventChannel,
     ) -> Self {
         let child_nodes = unsafe { child_nodes.extend_lifetime() };
 
-        // // insert ourself as a descendent of the parent
-        // // when the parent is removed, this map will be traversed, and we will also be cleaned up.
-        // if let Some(parent) = &parent {
-        //     let parent = unsafe { vdom.get_scope(*parent) }.unwrap();
-        //     parent.descendents.borrow_mut().insert(arena_idx);
-        // }
+        let up = shared.schedule_any_immediate.clone();
+        let memoized_updater = Rc::new(move || up(arena_idx));
 
         Self {
-            function_name,
+            memoized_updater,
+            shared,
             child_nodes,
             caller,
             parent_idx: parent,
             our_arena_idx: arena_idx,
             height,
-            // vdom,
             frames: ActiveFrame::new(),
-
             hooks: Default::default(),
             shared_contexts: Default::default(),
             listeners: Default::default(),

+ 25 - 25
packages/core/src/virtual_dom.rs

@@ -105,18 +105,17 @@ impl VirtualDom {
         let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
         let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
 
-        let link = components.clone();
+        let update_sender = components.immediate_sender.clone();
 
         let base_scope = components.insert_scope_with_key(move |myidx| {
             let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
             let name = type_name_of(root);
-            Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), link, name)
+            Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), update_sender)
         });
 
         Self {
             base_scope,
             _root_props: root_props,
-            scheduler: Scheduler::new(components.clone()),
             scheduler: components,
             _root_prop_type: TypeId::of::<P>(),
         }
@@ -152,28 +151,29 @@ impl VirtualDom {
     /// the diff and creating nodes can be expensive, so we provide this method to avoid blocking the main thread. This
     /// method can be useful when needing to perform some crucial periodic tasks.
     pub async fn rebuild_async<'s>(&'s mut self) -> Mutations<'s> {
-        let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.scheduler);
-
-        let cur_component = self
-            .scheduler
-            .get_scope_mut(self.base_scope)
-            .expect("The base scope should never be moved");
-
-        // // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
-        if cur_component.run_scope().is_ok() {
-            diff_machine
-                .stack
-                .create_node(cur_component.frames.fin_head(), MountType::Append);
-            diff_machine.work().await;
-        } else {
-            // todo: should this be a hard error?
-            log::warn!(
-                "Component failed to run succesfully during rebuild.
-                This does not result in a failed rebuild, but indicates a logic failure within your app."
-            );
-        }
-
-        diff_machine.mutations
+        todo!()
+        // let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.scheduler);
+
+        // let cur_component = self
+        //     .scheduler
+        //     .get_scope_mut(self.base_scope)
+        //     .expect("The base scope should never be moved");
+
+        // // // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
+        // if cur_component.run_scope().is_ok() {
+        //     diff_machine
+        //         .stack
+        //         .create_node(cur_component.frames.fin_head(), MountType::Append);
+        //     diff_machine.work().await;
+        // } else {
+        //     // todo: should this be a hard error?
+        //     log::warn!(
+        //         "Component failed to run succesfully during rebuild.
+        //         This does not result in a failed rebuild, but indicates a logic failure within your app."
+        //     );
+        // }
+
+        // diff_machine.mutations
     }
 
     pub fn diff_sync<'s>(&'s mut self) -> Mutations<'s> {

+ 1 - 1
packages/core/tests/create_iterative.rs

@@ -1,7 +1,7 @@
 //! tests to prove that the iterative implementation works
 
 use anyhow::{Context, Result};
-use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, DomEdit, Mutations};
+use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, DomEdit, Mutations};
 mod test_logging;
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;

+ 1 - 1
packages/core/tests/diffing.rs

@@ -3,7 +3,7 @@
 use bumpalo::Bump;
 
 use dioxus::{
-    arena::Scheduler, diff::DiffMachine, prelude::*, DiffInstruction, DomEdit, MountType,
+    diff::DiffMachine, prelude::*, scheduler::Scheduler, DiffInstruction, DomEdit, MountType,
 };
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;

+ 1 - 1
packages/core/tests/eventsystem.rs

@@ -1,7 +1,7 @@
 use bumpalo::Bump;
 
 use anyhow::{Context, Result};
-use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, DomEdit};
+use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, DomEdit};
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;
 

+ 1 - 1
packages/core/tests/hooks.rs

@@ -1,5 +1,5 @@
 use anyhow::{Context, Result};
-use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, DomEdit};
+use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, DomEdit};
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;
 

+ 1 - 1
packages/core/tests/work_sync.rs

@@ -3,7 +3,7 @@
 //! This means you can actually call it synchronously if you want.
 
 use anyhow::{Context, Result};
-use dioxus::{arena::Scheduler, diff::DiffMachine, prelude::*, scope::Scope};
+use dioxus::{diff::DiffMachine, prelude::*, scheduler::Scheduler, scope::Scope};
 use dioxus_core as dioxus;
 use dioxus_html as dioxus_elements;
 use futures_util::FutureExt;