ソースを参照

polish: change in cx to cx

Jonathan Kelley 3 年 前
コミット
9971ff2

+ 4 - 4
docs/main-concepts/03-rsx.md

@@ -113,9 +113,9 @@ pub static Example: FC<()> = |cx| {
                 // rsx! is lazy, and the underlying closures cannot have the same type
                 // Rendering produces the VNode type
                 {match rand::gen_range::<i32>(1..3) {
-                    1 => rsx!(in cx, h1 { "big" })
-                    2 => rsx!(in cx, h2 { "medium" })
-                    _ => rsx!(in cx, h3 { "small" })
+                    1 => rsx!(cx, h1 { "big" })
+                    2 => rsx!(cx, h2 { "medium" })
+                    _ => rsx!(cx, h3 { "small" })
                 }}
 
                 // Optionals
@@ -128,7 +128,7 @@ pub static Example: FC<()> = |cx| {
                 // Duplicating nodes
                 // Clones the nodes by reference, so they are literally identical
                 {{
-                    let node = rsx!(in cx, h1{ "TopNode" });
+                    let node = rsx!(cx, h1{ "TopNode" });
                     (0..10).map(|_| node.clone())
                 }}
 

+ 2 - 2
docs/main-concepts/12-signals.md

@@ -98,7 +98,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa
 const TITLE: Atom<String> = || "".to_string();
 const Provider: FC<()> = |cx| {
     let title = use_signal(&cx, &TITLE);
-    rsx!(in cx, input { value: title })
+    rsx!(cx, input { value: title })
 };
 ```
 
@@ -108,7 +108,7 @@ If we use the `TITLE` atom in another component, we can cause updates to flow be
 const Receiver: FC<()> = |cx| {
     let title = use_signal(&cx, &TITLE);
     log::info!("This will only be called once!");
-    rsx!(in cx,
+    rsx!(cx,
         div {
             h1 { "{title}" }
             div {}

+ 5 - 5
examples/reference/antipatterns.rs

@@ -34,11 +34,11 @@ struct NoKeysProps {
 }
 static AntipatternNoKeys: FC<NoKeysProps> = |cx| {
     // WRONG: Make sure to add keys!
-    rsx!(in cx, ul {
+    rsx!(cx, ul {
         {cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
     });
     // RIGHT: Like this:
-    rsx!(in cx, ul {
+    rsx!(cx, ul {
         {cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
     })
 };
@@ -56,7 +56,7 @@ static AntipatternNoKeys: FC<NoKeysProps> = |cx| {
 /// an API for registering shared state without the ContextProvider pattern.
 static AntipatternNestedFragments: FC<()> = |cx| {
     // Try to avoid heavily nesting fragments
-    rsx!(in cx,
+    rsx!(cx,
         Fragment {
             Fragment {
                 Fragment {
@@ -125,9 +125,9 @@ static AntipatternMisusedHooks: FC<MisuedHooksProps> = |cx| {
         // do not place a hook in the conditional!
         // prefer to move it out of the conditional
         let (state, set_state) = use_state(cx, || "hello world").classic();
-        rsx!(in cx, div { "{state}" })
+        rsx!(cx, div { "{state}" })
     } else {
-        rsx!(in cx, div { "Not rendering state" })
+        rsx!(cx, div { "Not rendering state" })
     }
 };
 

+ 9 - 9
examples/reference/conditional_rendering.rs

@@ -34,7 +34,7 @@ pub static Example0: FC<MyProps> = |cx| {
 // which will do essentially the same thing as `cx.render`.
 //
 // In short:
-// `rsx!(in cx, ...)` is shorthand for `cx.render(rsx!(...))`
+// `rsx!(cx, ...)` is shorthand for `cx.render(rsx!(...))`
 #[derive(PartialEq, Props)]
 pub struct MyProps1 {
     should_show: bool,
@@ -45,21 +45,21 @@ pub static Example1: FC<MyProps1> = |cx| {
             // With matching
             {match cx.should_show {
                 true => cx.render(rsx!(div {"it is true!"})),
-                false => rsx!(in cx, div {"it is false!"}),
+                false => rsx!(cx, div {"it is false!"}),
             }}
 
             // or with just regular conditions
             {if cx.should_show {
-                rsx!(in cx, div {"it is true!"})
+                rsx!(cx, div {"it is true!"})
             } else {
-                rsx!(in cx, div {"it is false!"})
+                rsx!(cx, div {"it is false!"})
             }}
 
             // or with optional chaining
             {
                 cx.should_show
-                .then(|| rsx!(in cx, div {"it is false!"}))
-                .unwrap_or_else(|| rsx!(in cx, div {"it is false!"}))
+                .then(|| rsx!(cx, div {"it is false!"}))
+                .unwrap_or_else(|| rsx!(cx, div {"it is false!"}))
             }
         }
     })
@@ -81,9 +81,9 @@ pub static Example2: FC<MyProps2> = |cx| {
     cx.render(rsx! {
         div {
             {match cx.color {
-                Color::Green => rsx!(in cx, div {"it is Green!"}),
-                Color::Yellow => rsx!(in cx, div {"it is Yellow!"}),
-                Color::Red => rsx!(in cx, div {"it is Red!"}),
+                Color::Green => rsx!(cx, div {"it is Green!"}),
+                Color::Yellow => rsx!(cx, div {"it is Yellow!"}),
+                Color::Red => rsx!(cx, div {"it is Red!"}),
             }}
         }
     })

+ 2 - 2
examples/reference/listener.rs

@@ -34,7 +34,7 @@ static ButtonList: FC<()> = |cx| {
 /// This shows how listeners may be without a visible change in the display.
 /// Check the console.
 static NonUpdatingEvents: FC<()> = |cx| {
-    rsx!(in cx, div {
+    rsx!(cx, div {
         button {
             onclick: move |_| log::info!("Did not cause any updates!")
             "Click me to log!"
@@ -43,7 +43,7 @@ static NonUpdatingEvents: FC<()> = |cx| {
 };
 
 static DisablePropogation: FC<()> = |cx| {
-    rsx!(in cx,
+    rsx!(cx,
         div {
             onclick: move |_| log::info!("event propogated to the div!")
             button {

+ 2 - 2
examples/reference/suspense.rs

@@ -19,8 +19,8 @@ pub static Example: FC<()> = |cx| {
         cx,
         || surf::get(ENDPOINT).recv_json::<DogApi>(),
         |cx, res| match res {
-            Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
-            Err(_) => rsx!(in cx, div { "No doggos for you :(" }),
+            Ok(res) => rsx!(cx, img { src: "{res.message}" }),
+            Err(_) => rsx!(cx, div { "No doggos for you :(" }),
         },
     );
 

+ 3 - 3
examples/rsx_usage.rs

@@ -106,7 +106,7 @@ pub static Example: FC<()> = |cx| {
             // To fix this, call "render" method or use the "in" syntax to produce VNodes.
             // There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros)
             {match true {
-                true => rsx!(in cx, h1 {"Top text"}),
+                true => rsx!(cx, h1 {"Top text"}),
                 false => cx.render(rsx!( h1 {"Bottom text"}))
             }}
 
@@ -117,9 +117,9 @@ pub static Example: FC<()> = |cx| {
 
             // True conditions need to be rendered (same reasons as matching)
             {if true {
-                rsx!(in cx, h1 {"Top text"})
+                rsx!(cx, h1 {"Top text"})
             } else {
-                rsx!(in cx, h1 {"Bottom text"})
+                rsx!(cx, h1 {"Bottom text"})
             }}
 
             // returning "None" is a bit noisy... but rare in practice

+ 2 - 2
examples/showcase.rs

@@ -15,8 +15,8 @@ static App: FC<()> = |cx| {
     let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
 
     let body = match selection {
-        Some(id) => rsx!(in cx, ReferenceItem { selected: *id }),
-        None => rsx!(in cx, div { "Select an concept to explore" }),
+        Some(id) => rsx!(cx, ReferenceItem { selected: *id }),
+        None => rsx!(cx, div { "Select an concept to explore" }),
     };
 
     cx.render(rsx! {

+ 3 - 3
packages/core-macro/src/lib.rs

@@ -87,7 +87,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 ///             // To fix this, call "render" method or use the "in" syntax to produce VNodes.
 ///             // There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros)
 ///             {match true {
-///                 true => rsx!(in cx, h1 {"Top text"}),
+///                 true => rsx!(cx, h1 {"Top text"}),
 ///                 false => cx.render(rsx!( h1 {"Bottom text"}))
 ///             }}
 ///
@@ -98,9 +98,9 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 ///
 ///             // True conditions need to be rendered (same reasons as matching)
 ///             {if true {
-///                 rsx!(in cx, h1 {"Top text"})
+///                 rsx!(cx, h1 {"Top text"})
 ///             } else {
-///                 rsx!(in cx, h1 {"Bottom text"})
+///                 rsx!(cx, h1 {"Bottom text"})
 ///             }}
 ///
 ///             // returning "None" is a bit noisy... but rare in practice

+ 1 - 2
packages/core-macro/src/rsx/body.rs

@@ -39,8 +39,7 @@ impl Parse for RsxBody<AS_HTML> {
 }
 
 fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
-    let res = if input.peek(Token![in]) && input.peek2(Ident) && input.peek3(Token![,]) {
-        let _ = input.parse::<Token![in]>()?;
+    let res = if input.peek(Ident) && input.peek2(Token![,]) {
         let name = input.parse::<Ident>()?;
         input.parse::<Token![,]>()?;
         Some(name)

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

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

+ 72 - 1
packages/core/src/context.rs

@@ -162,13 +162,84 @@ impl<'src, P> Context<'src, P> {
             });
     }
 
-    pub fn consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
+    pub fn try_consume_shared_state<T: 'static>(self) -> Option<Rc<T>> {
         let getter = &self.scope.shared.get_shared_context;
         let ty = TypeId::of::<T>();
         let idx = self.scope.our_arena_idx;
         getter(idx, ty).map(|f| f.downcast().expect("TypeID already validated"))
     }
 
+    /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
+    ///
+    /// This is a hook, so it should not be called conditionally!
+    ///
+    /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
+    /// so don't put it in a conditional.
+    ///
+    /// When the component is dropped, so is the context. Be aware of this behavior when consuming
+    /// the context via Rc/Weak.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// struct SharedState(&'static str);
+    ///
+    /// static App: FC<()> = |cx| {
+    ///     cx.provide_state(|| SharedState("world"));
+    ///     rsx!(cx, Child {})
+    /// }
+    ///
+    /// static Child: FC<()> = |cx| {
+    ///     let state = cx.consume_state::<SharedState>();
+    ///     rsx!(cx, div { "hello {state.0}" })
+    /// }
+    /// ```
+    pub fn provide_state<T, F>(self, init: F) -> &'src Rc<T>
+    where
+        T: 'static,
+        F: FnOnce() -> T,
+    {
+        //
+        let ty = TypeId::of::<T>();
+        let contains_key = self.scope.shared_contexts.borrow().contains_key(&ty);
+
+        let is_initialized = self.use_hook(
+            |_| false,
+            |s| {
+                let i = s.clone();
+                *s = true;
+                i
+            },
+            |_| {},
+        );
+
+        match (is_initialized, contains_key) {
+            // Do nothing, already initialized and already exists
+            (true, true) => {}
+
+            // Needs to be initialized
+            (false, false) => self.add_shared_state(init()),
+
+            _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
+        };
+
+        self.consume_state().unwrap()
+    }
+
+    /// Uses a context, storing the cached value around
+    ///
+    /// If a context is not found on the first search, then this call will be  "dud", always returning "None" even if a
+    /// context was added later. This allows using another hook as a fallback
+    ///
+    pub fn consume_state<T: 'static>(self) -> Option<&'src Rc<T>> {
+        struct UseContextHook<C>(Option<Rc<C>>);
+        self.use_hook(
+            move |_| UseContextHook(self.try_consume_shared_state::<T>()),
+            move |hook| hook.0.as_ref(),
+            |_| {},
+        )
+    }
+
     /// Store a value between renders
     ///
     /// This is *the* foundational hook for all other hooks.

+ 0 - 63
packages/core/src/hooks.rs

@@ -18,69 +18,6 @@ use std::{
     rc::Rc,
 };
 
-/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
-///
-/// This is a hook, so it may not be called conditionally!
-///
-/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
-/// so don't put it in a conditional.
-///
-/// When the component is dropped, so is the context. Be aware of this behavior when consuming
-/// the context via Rc/Weak.
-///
-///
-///
-pub fn use_provide_state<'src, Pr, T, F>(cx: Context<'src, Pr>, init: F) -> &'src Rc<T>
-where
-    T: 'static,
-    F: FnOnce() -> T,
-{
-    let ty = TypeId::of::<T>();
-    let contains_key = cx.scope.shared_contexts.borrow().contains_key(&ty);
-
-    let is_initialized = cx.use_hook(
-        |_| false,
-        |s| {
-            let i = s.clone();
-            *s = true;
-            i
-        },
-        |_| {},
-    );
-
-    match (is_initialized, contains_key) {
-        // Do nothing, already initialized and already exists
-        (true, true) => {}
-
-        // Needs to be initialized
-        (false, false) => cx.add_shared_state(init()),
-
-        _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
-    };
-
-    use_consume_state::<T, _>(cx)
-}
-
-/// There are hooks going on here!
-pub fn use_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> &'src Rc<T> {
-    use_try_consume_state::<T, _>(cx).unwrap()
-}
-
-/// Uses a context, storing the cached value around
-///
-/// If a context is not found on the first search, then this call will be  "dud", always returning "None" even if a
-/// context was added later. This allows using another hook as a fallback
-///
-pub fn use_try_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> Option<&'src Rc<T>> {
-    struct UseContextHook<C>(Option<Rc<C>>);
-
-    cx.use_hook(
-        move |_| UseContextHook(cx.consume_shared_state::<T>()),
-        move |hook| hook.0.as_ref(),
-        |_| {},
-    )
-}
-
 /// Awaits the given task, forcing the component to re-render when the value is ready.
 ///
 ///

+ 227 - 186
packages/core/src/scheduler.rs

@@ -151,6 +151,8 @@ pub(crate) struct Scheduler {
 
     pub pending_tasks: VecDeque<UserEvent>,
 
+    pub batched_events: VecDeque<UserEvent>,
+
     pub garbage_scopes: HashSet<ScopeId>,
 
     pub lanes: [PriorityLane; 4],
@@ -214,7 +216,7 @@ impl Scheduler {
             channel,
         };
 
-        let mut async_tasks = FuturesUnordered::new();
+        let async_tasks = FuturesUnordered::new();
 
         Self {
             pool,
@@ -233,6 +235,8 @@ impl Scheduler {
 
             pending_tasks: VecDeque::new(),
 
+            batched_events: VecDeque::new(),
+
             garbage_scopes: HashSet::new(),
 
             current_priority: EventPriority::Low,
@@ -249,103 +253,52 @@ impl Scheduler {
 
     pub fn manually_poll_events(&mut self) {
         while let Ok(Some(msg)) = self.receiver.try_next() {
-            self.handle_channel_msg(msg);
+            match msg {
+                SchedulerMsg::UiEvent(event) => self.ui_events.push_front(event),
+                SchedulerMsg::Immediate(im) => self.pending_immediates.push_front(im),
+
+                // task stuff
+                SchedulerMsg::SubmitTask(_, _) => todo!(),
+                SchedulerMsg::ToggleTask(_) => todo!(),
+                SchedulerMsg::PauseTask(_) => todo!(),
+                SchedulerMsg::ResumeTask(_) => todo!(),
+                SchedulerMsg::DropTask(_) => todo!(),
+            }
         }
     }
 
-    // Converts UI events into dirty scopes with various priorities
-    pub fn consume_pending_events(&mut self) {
-        // consume all events that are "continuous" to be batched
-        // if we run into a discrete event, then bail early
-
-        while let Some(trigger) = self.ui_events.pop_back() {
-            if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {
-                if let Some(element) = trigger.mounted_dom_id {
-                    let priority = match trigger.name {
-                        // clipboard
-                        "copy" | "cut" | "paste" => EventPriority::Medium,
-
-                        // Composition
-                        "compositionend" | "compositionstart" | "compositionupdate" => {
-                            EventPriority::Low
-                        }
-
-                        // Keyboard
-                        "keydown" | "keypress" | "keyup" => EventPriority::Low,
-
-                        // Focus
-                        "focus" | "blur" => EventPriority::Low,
-
-                        // Form
-                        "change" | "input" | "invalid" | "reset" | "submit" => EventPriority::Low,
-
-                        // Mouse
-                        "click" | "contextmenu" | "doubleclick" | "drag" | "dragend"
-                        | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
-                        | "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove"
-                        | "mouseout" | "mouseover" | "mouseup" => EventPriority::Low,
-
-                        "mousemove" => EventPriority::Medium,
-
-                        // Pointer
-                        "pointerdown" | "pointermove" | "pointerup" | "pointercancel"
-                        | "gotpointercapture" | "lostpointercapture" | "pointerenter"
-                        | "pointerleave" | "pointerover" | "pointerout" => EventPriority::Low,
-
-                        // Selection
-                        "select" | "touchcancel" | "touchend" => EventPriority::Low,
-
-                        // Touch
-                        "touchmove" | "touchstart" => EventPriority::Low,
+    // returns true if the event is discrete
+    pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
+        let (discrete, priority) = event_meta(&event);
 
-                        // Wheel
-                        "scroll" | "wheel" => EventPriority::Low,
+        if let Some(scope) = self.pool.get_scope_mut(event.scope) {
+            if let Some(element) = event.mounted_dom_id {
+                // TODO: bubble properly here
+                scope.call_listener(event.event, element);
 
-                        // Media
-                        "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
-                        | "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata"
-                        | "loadstart" | "pause" | "play" | "playing" | "progress"
-                        | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
-                        | "timeupdate" | "volumechange" | "waiting" => EventPriority::Low,
-
-                        // Animation
-                        "animationstart" | "animationend" | "animationiteration" => {
-                            EventPriority::Low
-                        }
-
-                        // Transition
-                        "transitionend" => EventPriority::Low,
-
-                        // Toggle
-                        "toggle" => EventPriority::Low,
-
-                        _ => EventPriority::Low,
-                    };
-
-                    scope.call_listener(trigger.event, element);
-                    // let receiver = self.immediate_receiver.clone();
-                    // let mut receiver = receiver.borrow_mut();
-
-                    // // Drain the immediates into the dirty scopes, setting the appropiate priorities
-                    // while let Ok(Some(dirty_scope)) = receiver.try_next() {
+                while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
+                    //
                     //     self.add_dirty_scope(dirty_scope, trigger.priority)
-                    // }
                 }
             }
         }
-    }
 
-    // nothing to do, no events on channels, no work
-    pub fn has_any_work(&self) -> bool {
-        let pending_lanes = self.lanes.iter().find(|f| f.has_work()).is_some();
-        pending_lanes || self.has_pending_events()
-    }
+        use EventPriority::*;
 
-    pub fn has_pending_events(&self) -> bool {
-        self.ui_events.len() > 0
+        match priority {
+            Immediate => todo!(),
+            High => todo!(),
+            Medium => todo!(),
+            Low => todo!(),
+        }
+
+        discrete
     }
 
-    fn shift_priorities(&mut self) {
+    fn prepare_work(&mut self) {
+        // consume all events that are "continuous" to be batched
+        // if we run into a discrete event, then bail early
+
         self.current_priority = match (
             self.lanes[0].has_work(),
             self.lanes[1].has_work(),
@@ -355,8 +308,26 @@ impl Scheduler {
             (true, _, _, _) => EventPriority::Immediate,
             (false, true, _, _) => EventPriority::High,
             (false, false, true, _) => EventPriority::Medium,
-            (false, false, false, _) => EventPriority::Low,
+            (false, false, false, true) => EventPriority::Low,
+            (false, false, false, false) => {
+                // no work to do, process events
+                EventPriority::Low
+            }
         };
+
+        while let Some(trigger) = self.ui_events.pop_back() {
+            if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
+        }
+    }
+
+    // nothing to do, no events on channels, no work
+    pub fn has_any_work(&self) -> bool {
+        let pending_lanes = self.lanes.iter().find(|f| f.has_work()).is_some();
+        pending_lanes || self.has_pending_events()
+    }
+
+    pub fn has_pending_events(&self) -> bool {
+        self.ui_events.len() > 0
     }
 
     /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
@@ -385,26 +356,97 @@ impl Scheduler {
         }
     }
 
-    /// 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();
+    pub fn current_lane(&mut self) -> &mut PriorityLane {
+        match self.current_priority {
+            EventPriority::Immediate => &mut self.lanes[0],
+            EventPriority::High => &mut self.lanes[1],
+            EventPriority::Medium => &mut self.lanes[2],
+            EventPriority::Low => &mut self.lanes[3],
+        }
+    }
 
-        self.manually_poll_events();
+    pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
+        match msg {
+            SchedulerMsg::Immediate(_) => todo!(),
 
-        if !self.has_any_work() {
-            return committed_mutations;
+            SchedulerMsg::UiEvent(event) => {
+                //
+
+                self.handle_ui_event(event);
+            }
+
+            //
+            SchedulerMsg::SubmitTask(_, _) => todo!(),
+            SchedulerMsg::ToggleTask(_) => todo!(),
+            SchedulerMsg::PauseTask(_) => todo!(),
+            SchedulerMsg::ResumeTask(_) => todo!(),
+            SchedulerMsg::DropTask(_) => todo!(),
         }
+    }
 
-        self.consume_pending_events();
+    fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
+        todo!()
+        // match priority {
+        //     EventPriority::High => self.high_priorty.dirty_scopes.insert(scope),
+        //     EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope),
+        //     EventPriority::Low => self.low_priority.dirty_scopes.insert(scope),
+        // };
+    }
 
-        while self.has_any_work() {
-            self.shift_priorities();
-            self.work_on_current_lane(|| false, &mut committed_mutations);
+    async fn wait_for_any_work(&mut self) {}
+
+    /// 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 = self.load_work();
+
+        // We have to split away some parts of ourself - current lane is borrowed mutably
+        let mut shared = self.pool.clone();
+        let mut machine = unsafe { saved_state.promote(&mut shared) };
+
+        if machine.stack.is_empty() {
+            let shared = self.pool.clone();
+
+            self.current_lane().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)
+            });
+
+            if let Some(scope) = self.current_lane().dirty_scopes.pop() {
+                let component = self.pool.get_scope(scope).unwrap();
+                let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
+                machine.stack.push(DiffInstruction::Diff { new, old });
+            }
         }
 
-        committed_mutations
+        let deadline_expired = machine.work(deadline_reached);
+
+        let machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) };
+        let mut saved = machine.save();
+
+        if deadline_expired {
+            self.save_work(saved);
+            false
+        } else {
+            for node in saved.seen_scopes.drain() {
+                self.current_lane().dirty_scopes.remove(&node);
+            }
+
+            let mut new_mutations = Mutations::new();
+            std::mem::swap(&mut new_mutations, &mut saved.mutations);
+
+            mutations.push(new_mutations);
+            self.save_work(saved);
+            true
+        }
     }
 
     /// The primary workhorse of the VirtualDOM.
@@ -449,24 +491,25 @@ impl Scheduler {
         pin_mut!(deadline);
 
         loop {
-            // Internalize any pending work since the last time we ran
+            // Move work out of the scheduler message receiver and into dedicated message lanes
             self.manually_poll_events();
 
             // Wait for any new events if we have nothing to do
             // todo: poll the events once even if there is work to do to prevent starvation
             if !self.has_any_work() {
                 futures_util::select! {
-                    msg = self.async_tasks.next() => {}
-                    msg = self.receiver.next() => self.handle_channel_msg(msg.unwrap()),
+                    // todo: find a solution for the exhausted queue problem
+                    // msg = self.async_tasks.next() => {}
+                    msg = self.receiver.next() => {
+                        log::debug!("received work in scheduler");
+                        self.handle_channel_msg(msg.unwrap())
+                    },
                     _ = (&mut deadline).fuse() => return committed_mutations,
                 }
             }
 
-            // Create work from the pending event queue
-            self.consume_pending_events();
-
-            // shift to the correct lane
-            self.shift_priorities();
+            // switch our priority, pop off any work
+            self.prepare_work();
 
             let mut deadline_reached = || (&mut deadline).now_or_never().is_some();
 
@@ -481,94 +524,30 @@ impl Scheduler {
         committed_mutations
     }
 
-    /// Load the current lane, and work on it, periodically checking in if the deadline has been reached.
+    /// Work the scheduler down, not polling any ongoing tasks.
     ///
-    /// 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 = self.load_work();
-
-        // We have to split away some parts of ourself - current lane is borrowed mutably
-        let mut shared = self.pool.clone();
-        let mut machine = unsafe { saved_state.promote(&mut shared) };
-
-        if machine.stack.is_empty() {
-            let shared = self.pool.clone();
-
-            self.current_lane().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)
-            });
-
-            if let Some(scope) = self.current_lane().dirty_scopes.pop() {
-                let component = self.pool.get_scope(scope).unwrap();
-                let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
-                machine.stack.push(DiffInstruction::Diff { new, old });
-            }
-        }
-
-        let deadline_expired = machine.work(deadline_reached);
-
-        let machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) };
-        let mut saved = machine.save();
-
-        if deadline_expired {
-            self.save_work(saved);
-            false
-        } else {
-            for node in saved.seen_scopes.drain() {
-                self.current_lane().dirty_scopes.remove(&node);
-            }
-
-            let mut new_mutations = Mutations::new();
-            std::mem::swap(&mut new_mutations, &mut saved.mutations);
+    /// 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();
 
-            mutations.push(new_mutations);
-            self.save_work(saved);
-            true
-        }
-    }
+        self.manually_poll_events();
 
-    pub fn current_lane(&mut self) -> &mut PriorityLane {
-        match self.current_priority {
-            EventPriority::Immediate => &mut self.lanes[0],
-            EventPriority::High => &mut self.lanes[1],
-            EventPriority::Medium => &mut self.lanes[2],
-            EventPriority::Low => &mut self.lanes[3],
+        if !self.has_any_work() {
+            return committed_mutations;
         }
-    }
-
-    pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
-        match msg {
-            SchedulerMsg::Immediate(_) => todo!(),
-            SchedulerMsg::UiEvent(_) => todo!(),
 
-            //
-            SchedulerMsg::SubmitTask(_, _) => todo!(),
-            SchedulerMsg::ToggleTask(_) => todo!(),
-            SchedulerMsg::PauseTask(_) => todo!(),
-            SchedulerMsg::ResumeTask(_) => todo!(),
-            SchedulerMsg::DropTask(_) => todo!(),
+        while self.has_any_work() {
+            self.prepare_work();
+            self.work_on_current_lane(|| false, &mut committed_mutations);
         }
-    }
 
-    fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
-        todo!()
-        // match priority {
-        //     EventPriority::High => self.high_priorty.dirty_scopes.insert(scope),
-        //     EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope),
-        //     EventPriority::Low => self.low_priority.dirty_scopes.insert(scope),
-        // };
+        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.pool.clone();
         let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
 
@@ -615,6 +594,8 @@ impl Scheduler {
     }
 }
 
+impl Scheduler {}
+
 pub(crate) struct PriorityLane {
     pub dirty_scopes: IndexSet<ScopeId>,
     pub saved_state: Option<SavedDiffWork<'static>>,
@@ -631,7 +612,7 @@ impl PriorityLane {
     }
 
     fn has_work(&self) -> bool {
-        todo!()
+        self.dirty_scopes.len() > 0 || self.in_progress == true
     }
 
     fn work(&mut self) {
@@ -824,3 +805,63 @@ impl ResourcePool {
 
     pub fn borrow_bumpframe(&self) {}
 }
+
+fn event_meta(event: &UserEvent) -> (bool, EventPriority) {
+    use EventPriority::*;
+
+    match event.name {
+        // clipboard
+        "copy" | "cut" | "paste" => (true, Medium),
+
+        // Composition
+        "compositionend" | "compositionstart" | "compositionupdate" => (true, Low),
+
+        // Keyboard
+        "keydown" | "keypress" | "keyup" => (true, High),
+
+        // Focus
+        "focus" | "blur" => (true, Low),
+
+        // Form
+        "change" | "input" | "invalid" | "reset" | "submit" => (true, Medium),
+
+        // Mouse
+        "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
+        | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
+        | "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High),
+
+        "mousemove" => (false, Medium),
+
+        // Pointer
+        "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
+        | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
+            (true, Medium)
+        }
+
+        // Selection
+        "select" | "touchcancel" | "touchend" => (true, Medium),
+
+        // Touch
+        "touchmove" | "touchstart" => (true, Medium),
+
+        // Wheel
+        "scroll" | "wheel" => (false, Medium),
+
+        // Media
+        "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
+        | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
+        | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
+        | "timeupdate" | "volumechange" | "waiting" => (true, Medium),
+
+        // Animation
+        "animationstart" | "animationend" | "animationiteration" => (true, Medium),
+
+        // Transition
+        "transitionend" => (true, Medium),
+
+        // Toggle
+        "toggle" => (true, Medium),
+
+        _ => (true, Low),
+    }
+}

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

@@ -361,7 +361,7 @@ impl<'a> ScopeRenderer<'a> {
                 write_indent(f, il);
                 write!(f, "\"{}\"\n", text.text)?
             }
-            VNode::Anchor(anchor) => {
+            VNode::Anchor(_anchor) => {
                 write_indent(f, il);
                 write!(f, "Anchor {{}}\n")?;
             }

+ 7 - 3
packages/core/src/virtual_dom.rs

@@ -314,7 +314,7 @@ impl VirtualDom {
     /// # Example
     ///
     /// ```no_run
-    /// static App: FC<()> = |cx| rsx!(in cx, div {"hello"} );
+    /// static App: FC<()> = |cx| rsx!(cx, div {"hello"} );
     /// let mut dom = VirtualDom::new(App);
     /// loop {
     ///     let deadline = TimeoutFuture::from_ms(16);
@@ -345,13 +345,17 @@ impl VirtualDom {
     /// This lets us poll async tasks during idle periods without blocking the main thread.
     pub async fn wait_for_work(&mut self) {
         if self.scheduler.has_any_work() {
+            log::debug!("No need to wait for work, we already have some");
             return;
         }
 
+        log::debug!("No active work.... waiting for some...");
         use futures_util::StreamExt;
         futures_util::select! {
-            // hmm - will this resolve to none if there are no async tasks?
-            _ = self.scheduler.async_tasks.next() => {}
+            // // hmm - will this resolve to none if there are no async tasks?
+            // _ = self.scheduler.async_tasks.next() => {
+            //     log::debug!("async task completed!");
+            // }
             msg = self.scheduler.receiver.next() => self.scheduler.handle_channel_msg(msg.unwrap()),
         }
     }

+ 41 - 0
packages/core/tests/sharedstate.rs

@@ -0,0 +1,41 @@
+use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+use DomEdit::*;
+
+mod test_logging;
+
+fn new_dom() -> TestDom {
+    const IS_LOGGING_ENABLED: bool = false;
+    test_logging::set_up_logging(IS_LOGGING_ENABLED);
+    TestDom::new()
+}
+
+#[test]
+fn shared_state_test() {
+    struct MySharedState(&'static str);
+
+    static App: FC<()> = |cx| {
+        cx.provide_state(|| MySharedState("world!"));
+        rsx!(cx, Child {})
+    };
+
+    static Child: FC<()> = |cx| {
+        let shared = cx.consume_state::<MySharedState>()?;
+        rsx!(cx, "Hello, {shared.0}")
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let Mutations { edits, .. } = dom.rebuild();
+
+    assert_eq!(
+        edits,
+        [
+            CreateTextNode {
+                id: 0,
+                text: "Hello, world!"
+            },
+            AppendChildren { many: 1 },
+        ]
+    );
+}

+ 7 - 2
packages/web/examples/async_web.rs

@@ -35,8 +35,13 @@ static App: FC<()> = |cx| {
         cx,
         || surf::get(ENDPOINT).recv_json::<DogApi>(),
         |cx, res| match res {
-            Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
-            Err(_err) => rsx!(in cx, div { "No doggos for you :(" }),
+            Ok(res) => rsx!(
+                cx,
+                img {
+                    src: "{res.message}"
+                }
+            ),
+            Err(_err) => rsx!(cx, div { "No doggos for you :(" }),
         },
     );
 

+ 5 - 1
packages/web/examples/basic.rs

@@ -27,7 +27,11 @@ static APP: FC<()> = |cx| {
 
     cx.render(rsx! {
         div {
-            button { onclick: move |_| count += 1, "add"  }
+            button {
+                onclick: move |_| count += 1,
+                "Click to add."
+                "Current count: {count}"
+            }
             ul {
                 {(0..*count).map(|f| rsx!{
                     li { "a - {f}" }

+ 3 - 5
packages/web/src/dom.rs

@@ -93,7 +93,7 @@ impl WebsysDom {
 
     pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) {
         for edit in edits.drain(..) {
-            log::info!("Handling edit: {:#?}", edit);
+            // log::info!("Handling edit: {:#?}", edit);
             match edit {
                 DomEdit::PushRoot { id: root } => self.push(root),
                 DomEdit::PopRoot => self.pop(),
@@ -530,6 +530,8 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
 
     let typ = event.type_();
 
+    log::debug!("Event type is {:?}", typ);
+
     // let attrs = target.attributes();
     // for x in 0..attrs.length() {
     //     let attr = attrs.item(x).unwrap();
@@ -569,10 +571,6 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
     })
 }
 
-pub fn prepare_websys_dom() -> Element {
-    load_document().get_element_by_id("dioxusroot").unwrap()
-}
-
 pub fn load_document() -> Document {
     web_sys::window()
         .expect("should have access to the Window")

+ 11 - 5
packages/web/src/lib.rs

@@ -117,10 +117,12 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
 
     intern_cached_strings();
 
-    let hydrating = cfg.hydrate;
+    let should_hydrate = cfg.hydrate;
 
     let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
+
     let tasks = dom.get_event_sender();
+
     let sender_callback = Rc::new(move |event| tasks.unbounded_send(event).unwrap());
 
     let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
@@ -129,8 +131,10 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
 
     // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
     // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
-    if !hydrating {
+    if !should_hydrate {
         websys_dom.process_edits(&mut mutations.edits);
+    } else {
+        // websys dom processed the config and hydrated the dom already
     }
 
     let work_loop = ric_raf::RafLoop::new();
@@ -144,12 +148,14 @@ pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T,
         let deadline = work_loop.wait_for_idle_time().await;
 
         // run the virtualdom work phase until the frame deadline is reached
-        let mut mutations = dom.run_with_deadline(deadline).await;
+        let mutations = dom.run_with_deadline(deadline).await;
 
         // wait for the animation frame to fire so we can apply our changes
         work_loop.wait_for_raf().await;
 
-        // actually apply our changes during the animation frame
-        websys_dom.process_edits(&mut mutations[0].edits);
+        for mut edit in mutations {
+            // actually apply our changes during the animation frame
+            websys_dom.process_edits(&mut edit.edits);
+        }
     }
 }