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

wip: work on scheduler, async, coroutines, and merge scope into context

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

+ 1 - 1
examples/async.rs

@@ -17,7 +17,7 @@ pub static App: FC<()> = |(cx, _)| {
 
     let (async_count, dir) = (count.for_async(), *direction);
 
-    let (task, _) = use_task(cx, move || async move {
+    let (task, _) = use_coroutine(cx, move || async move {
         loop {
             TimeoutFuture::new(250).await;
             *async_count.get_mut() += dir;

+ 2 - 2
examples/coroutine.rs

@@ -32,13 +32,13 @@ static App: FC<()> = |(cx, props)| {
     let p2 = use_state(cx, || 0);
 
     let (mut p1_async, mut p2_async) = (p1.for_async(), p2.for_async());
-    let (p1_handle, _) = use_task(cx, || async move {
+    let (p1_handle, _) = use_coroutine(cx, || async move {
         loop {
             *p1_async.get_mut() += 1;
             async_std::task::sleep(std::time::Duration::from_millis(75)).await;
         }
     });
-    let (p2_handle, _) = use_task(cx, || async move {
+    let (p2_handle, _) = use_coroutine(cx, || async move {
         loop {
             *p2_async.get_mut() += 1;
             async_std::task::sleep(std::time::Duration::from_millis(100)).await;

+ 0 - 271
packages/core/src/context.rs

@@ -1,271 +0,0 @@
-//! Public APIs for managing component state, tasks, and lifecycles.
-//!
-//! This module is separate from `Scope` to narrow what exactly is exposed to user code.
-//!
-//! We unsafely implement `send` for the VirtualDom, but those guarantees can only be
-
-use bumpalo::Bump;
-
-use crate::{innerlude::*, lazynodes::LazyNodes};
-use std::{any::TypeId, ops::Deref, rc::Rc};
-
-/// 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 {
-///     name: String
-/// }
-///
-/// fn example(cx: Context<Props>) -> VNode {
-///     html! {
-///         <div> "Hello, {cx.name}" </div>
-///     }
-/// }
-/// ```
-pub struct Context<'src> {
-    pub scope: &'src ScopeInner,
-}
-
-impl<'src> Copy for Context<'src> {}
-impl<'src> Clone for Context<'src> {
-    fn clone(&self) -> Self {
-        Self { scope: self.scope }
-    }
-}
-
-// We currently deref to props, but it might make more sense to deref to Scope?
-// This allows for code that takes cx.xyz instead of cx.props.xyz
-impl<'a> Deref for Context<'a> {
-    type Target = &'a ScopeInner;
-    fn deref(&self) -> &Self::Target {
-        &self.scope
-    }
-}
-
-impl<'src> Context<'src> {
-    /// 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.scope.memoized_updater.clone()
-    }
-
-    pub fn needs_update(&self) {
-        (self.scope.memoized_updater)()
-    }
-
-    pub fn needs_update_any(&self, id: ScopeId) {
-        (self.scope.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.scope.shared.schedule_any_immediate.clone()
-    }
-
-    /// 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 scope_id(&self) -> ScopeId {
-        self.scope.our_arena_idx
-    }
-
-    pub fn bump(&self) -> &'src Bump {
-        let bump = &self.scope.frames.wip_frame().bump;
-        bump
-    }
-
-    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
-    ///
-    /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
-    ///
-    /// ## Example
-    ///
-    /// ```ignore
-    /// fn Component(cx: Context<()>) -> VNode {
-    ///     // Lazy assemble the VNode tree
-    ///     let lazy_tree = html! {<div> "Hello World" </div>};
-    ///
-    ///     // Actually build the tree and allocate it
-    ///     cx.render(lazy_tree)
-    /// }
-    ///```
-    pub fn render(self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<VNode<'src>> {
-        let bump = &self.scope.frames.wip_frame().bump;
-        let factory = NodeFactory { bump };
-        lazy_nodes.map(|f| f.call(factory))
-    }
-
-    /// `submit_task` will submit the future to be polled.
-    ///
-    /// This is useful when you have some async task that needs to be progressed.
-    ///
-    /// This method takes ownership over the task you've provided, and must return (). This means any work that needs to
-    /// happen must occur within the future or scheduled for after the future completes (through schedule_update )
-    ///
-    /// ## Explanation
-    /// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
-    ///
-    /// Tasks can't return anything, but they can be controlled with the returned handle
-    ///
-    /// Tasks will only run until the component renders again. Because `submit_task` is valid for the &'src lifetime, it
-    /// is considered "stable"
-    ///
-    ///
-    ///
-    pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        (self.scope.shared.submit_task)(task)
-    }
-
-    /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
-    ///
-    /// This is a "fundamental" operation and should only be called during initialization of a hook.
-    ///
-    /// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead.
-    ///
-    /// 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, props)|{
-    ///     cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {});
-    ///     rsx!(cx, Child {})
-    /// }
-    ///
-    /// static Child: FC<()> = |(cx, props)|{
-    ///     let state = cx.consume_state::<SharedState>();
-    ///     rsx!(cx, div { "hello {state.0}" })
-    /// }
-    /// ```
-    pub fn provide_state<T>(self, value: T)
-    where
-        T: 'static,
-    {
-        self.scope
-            .shared_contexts
-            .borrow_mut()
-            .insert(TypeId::of::<T>(), Rc::new(value))
-            .map(|f| f.downcast::<T>().ok())
-            .flatten();
-    }
-
-    /// Try to retrieve a SharedState with type T from the any parent Scope.
-    pub fn consume_state<T: 'static>(self) -> Option<Rc<T>> {
-        let getter = &self.scope.shared.get_shared_context;
-        let ty = TypeId::of::<T>();
-        let idx = self.scope.our_arena_idx;
-        getter(idx, ty).map(|f| f.downcast().unwrap())
-    }
-
-    /// Create a new subtree with this scope as the root of the subtree.
-    ///
-    /// 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.
-    ///
-    /// This method
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// static App: FC<()> = |(cx, props)| {
-    ///     todo!();
-    ///     rsx!(cx, div { "Subtree {id}"})
-    /// };
-    /// ```
-    pub fn create_subtree(self) -> Option<u32> {
-        self.scope.new_subtree()
-    }
-
-    /// 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
-    /// static App: FC<()> = |(cx, props)| {
-    ///     let id = cx.get_current_subtree();
-    ///     rsx!(cx, div { "Subtree {id}"})
-    /// };
-    /// ```
-    pub fn get_current_subtree(self) -> u32 {
-        self.scope.subtree()
-    }
-
-    /// Store a value between renders
-    ///
-    /// This is *the* foundational hook for all other hooks.
-    ///
-    /// - 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
-    ///
-    ///
-    /// # Example
-    ///
-    /// ```ignore
-    /// // use_ref is the simplest way of storing a value between renders
-    /// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
-    ///     use_hook(
-    ///         || Rc::new(RefCell::new(initial_value())),
-    ///         |state| state,
-    ///         |_| {},
-    ///     )
-    /// }
-    /// ```
-    pub fn use_hook<State, Output, Init, Run, Cleanup>(
-        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
-        if self.scope.hooks.at_end() {
-            self.scope.hooks.push_hook(
-                initializer(self.scope.hooks.len()),
-                Box::new(|raw| {
-                    //
-                    let s = raw.downcast::<State>().unwrap();
-                    cleanup(s);
-                }),
-            );
-        }
-
-        runner(self.scope.hooks.next::<State>().expect(HOOK_ERR_MSG))
-    }
-}
-
-const HOOK_ERR_MSG: &str = r###"
-Unable to retrieve the hook that was initialized at this index.
-Consult the `rules of hooks` to understand how to use hooks properly.
-
-You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
-Functions prefixed with "use" should never be called conditionally.
-"###;

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

@@ -0,0 +1,17 @@
+//! 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(),
+        }
+    }
+}

+ 91 - 112
packages/core/src/hooks.rs

@@ -30,7 +30,7 @@ use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
 ///     }
 /// };
 /// ```
-pub fn use_task<'src, Out, Fut, Init>(
+pub fn use_coroutine<'src, Out, Fut, Init>(
     cx: Context<'src>,
     task_initializer: Init,
 ) -> (&'src TaskHandle, &'src Option<Out>)
@@ -45,38 +45,40 @@ where
         value: Option<T>,
     }
 
-    // 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)
-        },
-        |_| {},
-    )
+    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.
@@ -95,9 +97,9 @@ pub fn use_suspense<'src, Out, Fut, Cb>(
     user_callback: Cb,
 ) -> Element<'src>
 where
-    Fut: Future<Output = Out> + 'static,
+    Fut: Future<Output = Out>,
     Out: 'static,
-    Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> Element<'a> + 'static,
+    Cb: FnMut(&Out) -> Element<'src> + 'src,
 {
     /*
     General strategy:
@@ -109,65 +111,60 @@ where
     - if it does, then we can render the node directly
     - if it doesn't, then we render a suspended node along with with the callback and task id
     */
-    cx.use_hook(
-        move |_| {
-            let value = Rc::new(RefCell::new(None));
-            let slot = value.clone();
-            let originator = cx.scope.our_arena_idx;
-
-            let handle = cx.submit_task(Box::pin(task_initializer().then(
-                move |output| async move {
-                    *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
-                    originator
-                },
-            )));
-
-            SuspenseHook { handle, value }
-        },
-        move |hook| match hook.value.borrow().as_ref() {
-            Some(value) => {
-                let out = value.downcast_ref::<Out>().unwrap();
-                let sus = SuspendedContext {
-                    inner: Context { scope: cx.scope },
-                };
-                user_callback(sus, out)
-            }
-            None => {
-                let value = hook.value.clone();
-
-                let id = hook.handle.our_id;
-
-                todo!()
-                // Some(LazyNodes::new(move |f| {
-                //     let bump = f.bump();
-
-                //     use bumpalo::boxed::Box as BumpBox;
-
-                //     let f: &mut dyn FnMut(SuspendedContext<'src>) -> Element<'src> =
-                //         bump.alloc(move |sus| {
-                //             let val = value.borrow();
-
-                //             let out = val
-                //                 .as_ref()
-                //                 .unwrap()
-                //                 .as_ref()
-                //                 .downcast_ref::<Out>()
-                //                 .unwrap();
-
-                //             user_callback(sus, out)
-                //         });
-                //     let callback = unsafe { BumpBox::from_raw(f) };
-
-                //     VNode::Suspended(bump.alloc(VSuspended {
-                //         dom_id: empty_cell(),
-                //         task_id: id,
-                //         callback: RefCell::new(Some(callback)),
-                //     }))
-                // }))
-            }
-        },
-        |_| {},
-    )
+    todo!()
+    // cx.use_hook(
+    // move |_| {
+    //     let value = Rc::new(RefCell::new(None));
+    //     let slot = value.clone();
+    //     let originator = cx.scope.our_arena_idx;
+
+    //     let handle = cx.submit_task(Box::pin(task_initializer().then(
+    //         move |output| async move {
+    //             *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
+    //             originator
+    //         },
+    //     )));
+
+    //     SuspenseHook { handle, value }
+    // },
+    // move |hook| {
+    //     // If the value exists, just run the callback to get the contents
+    //     // if the value doesn't exist, we want to render a suspended node with an associated callback
+    //     if let Some(value) = hook.value.borrow().as_ref() {
+    //         let out = value.downcast_ref::<Out>().unwrap();
+    //         user_callback(out)
+    //     } else {
+    //         let value = hook.value.clone();
+
+    //         let id = hook.handle.our_id;
+
+    //         let bump = cx.bump();
+
+    //         use bumpalo::boxed::Box as BumpBox;
+
+    //         let f: &mut dyn FnMut() -> Element<'src> = bump.alloc(move || {
+    //             let val = value.borrow();
+
+    //             let out = val
+    //                 .as_ref()
+    //                 .unwrap()
+    //                 .as_ref()
+    //                 .downcast_ref::<Out>()
+    //                 .unwrap();
+
+    //             user_callback(out)
+    //         });
+    //         let callback = unsafe { BumpBox::from_raw(f) };
+
+    //         Some(VNode::Suspended(bump.alloc(VSuspended {
+    //             dom_id: empty_cell(),
+    //             task_id: id,
+    //             callback: RefCell::new(Some(callback)),
+    //         })))
+    //     }
+    // },
+    // |_| {},
+    // )
 }
 
 pub(crate) struct SuspenseHook {
@@ -175,24 +172,6 @@ pub(crate) struct SuspenseHook {
     pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
 }
 
-pub struct SuspendedContext<'a> {
-    pub(crate) inner: Context<'a>,
-}
-
-impl<'src> SuspendedContext<'src> {
-    // // pub fn render(
-    // pub fn render(
-    //     // pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
-    //     self,
-    //     lazy_nodes: LazyNodes<'_>,
-    //     // lazy_nodes: LazyNodes<'src, '_>,
-    // ) -> Element<'src> {
-    //     let bump = &self.inner.scope.frames.wip_frame().bump;
-    //     todo!("suspense")
-    //     // Some(lazy_nodes.into_vnode(NodeFactory { bump }))
-    // }
-}
-
 #[derive(Clone, Copy)]
 pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
 

+ 24 - 25
packages/core/src/lib.rs

@@ -12,27 +12,27 @@ Navigating this crate:
 
 Some utilities
 */
-pub mod bumpframe;
-pub mod childiter;
-pub mod component;
-pub mod context;
-pub mod diff;
-pub mod diff_stack;
-pub mod events;
-pub mod heuristics;
-pub mod hooklist;
-pub mod hooks;
-pub mod lazynodes;
-pub mod mutations;
-pub mod nodes;
-pub mod resources;
-pub mod scheduler;
-pub mod scope;
-pub mod tasks;
-pub mod test_dom;
-pub mod threadsafe;
-pub mod util;
-pub mod virtual_dom;
+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;
+pub(crate) mod virtual_dom;
 
 #[cfg(feature = "debug_vdom")]
 pub mod debug_dom;
@@ -41,7 +41,6 @@ pub(crate) mod innerlude {
     pub(crate) use crate::bumpframe::*;
     pub(crate) use crate::childiter::*;
     pub use crate::component::*;
-    pub use crate::context::*;
     pub(crate) use crate::diff::*;
     pub use crate::diff_stack::*;
     pub use crate::events::*;
@@ -66,14 +65,14 @@ pub(crate) mod innerlude {
 
 pub use crate::innerlude::{
     Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
-    Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, SuspendedContext,
-    TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
+    Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, TaskHandle, TestDom,
+    ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
 };
 
 pub mod prelude {
     pub use crate::component::{fc_to_builder, Fragment, Properties, Scope};
-    pub use crate::context::Context;
     pub use crate::hooks::*;
+    pub use crate::innerlude::Context;
     pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC};
     pub use crate::nodes::VNode;
     pub use crate::VirtualDom;

+ 12 - 15
packages/core/src/nodes.rs

@@ -4,14 +4,12 @@
 //! cheap and *very* fast to construct - building a full tree should be quick.
 
 use crate::{
-    innerlude::{
-        empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner,
-        SuspendedContext,
-    },
+    innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner},
     lazynodes::LazyNodes,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
+    any::Any,
     cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
 };
@@ -307,10 +305,8 @@ pub struct Listener<'bump> {
     /// IE "click" - whatever the renderer needs to attach the listener by name.
     pub event: &'static str,
 
-    #[allow(clippy::type_complexity)]
     /// The actual callback that the user specified
-    pub(crate) callback:
-        RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn std::any::Any + Send>) + 'bump>>>,
+    pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
 }
 
 /// Virtual Components for custom user-defined components
@@ -325,9 +321,9 @@ pub struct VComponent<'src> {
     // Function pointer to the FC that was used to generate this component
     pub user_fc: *const (),
 
-    pub(crate) caller: &'src dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
+    pub(crate) caller: BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>,
 
-    pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
+    pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
 
     pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
 
@@ -342,7 +338,7 @@ pub struct VSuspended<'a> {
     pub dom_id: Cell<Option<ElementId>>,
 
     #[allow(clippy::type_complexity)]
-    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> Element<'a>>>>,
+    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element<'a> + 'a>>>,
 }
 
 /// This struct provides an ergonomic API to quickly build VNodes.
@@ -491,7 +487,7 @@ impl<'a> NodeFactory<'a> {
         let raw_props = props as *mut P as *mut ();
         let user_fc = component as *const ();
 
-        let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
+        let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
             move |other: &VComponent| {
                 if user_fc == other.user_fc {
                     // Safety
@@ -504,8 +500,6 @@ impl<'a> NodeFactory<'a> {
                         props.memoize(real_other)
                     };
 
-                    log::debug!("comparing props...");
-
                     // 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
@@ -513,7 +507,8 @@ impl<'a> NodeFactory<'a> {
                     false
                 }
             }
-        }));
+        });
+        let comparator = Some(unsafe { BumpBox::from_raw(comparator) });
 
         let drop_props = {
             // create a closure to drop the props
@@ -547,12 +542,14 @@ impl<'a> NodeFactory<'a> {
                 let props: &'_ P = unsafe { &*(raw_props as *const P) };
 
                 let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
-                let s: Scope<'a, P> = (Context { scope: scp }, props);
+                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,

+ 4 - 0
packages/core/src/scheduler.rs

@@ -142,6 +142,9 @@ pub(crate) struct Scheduler {
     // Garbage stored
     pub pending_garbage: FxHashSet<ScopeId>,
 
+    // Every component that has futures that need to be polled
+    pub pending_futures: FxHashSet<ScopeId>,
+
     // In-flight futures
     pub async_tasks: FuturesUnordered<FiberTask>,
 
@@ -268,6 +271,7 @@ impl Scheduler {
 
             garbage_scopes: HashSet::new(),
 
+            pending_futures: Default::default(),
             dirty_scopes: Default::default(),
             saved_state: Some(saved_state),
             in_progress: false,

+ 270 - 13
packages/core/src/scope.rs

@@ -1,4 +1,5 @@
 use crate::innerlude::*;
+
 use fxhash::FxHashMap;
 use std::{
     any::{Any, TypeId},
@@ -9,6 +10,36 @@ use std::{
     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 {
+///     name: String
+/// }
+///
+/// fn example(cx: Context<Props>) -> VNode {
+///     html! {
+///         <div> "Hello, {cx.name}" </div>
+///     }
+/// }
+/// ```
+pub type Context<'a> = &'a ScopeInner;
+
 /// Every component in Dioxus is represented by a `Scope`.
 ///
 /// Scopes contain the state for hooks, the component's props, and other lifecycle information.
@@ -28,7 +59,7 @@ pub struct ScopeInner {
 
     // Nodes
     pub(crate) frames: ActiveFrame,
-    pub(crate) caller: *const dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
+    pub(crate) caller: BumpBox<'static, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
 
     /*
     we care about:
@@ -40,6 +71,9 @@ pub struct ScopeInner {
     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()>>>,
+
     // State
     pub(crate) hooks: HookList,
 
@@ -175,7 +209,7 @@ 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: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
+        caller: BumpBox<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
         our_arena_idx: ScopeId,
         parent_idx: Option<ScopeId>,
         height: u32,
@@ -186,8 +220,6 @@ impl ScopeInner {
 
         let memoized_updater = Rc::new(move || schedule_any_update(our_arena_idx));
 
-        let caller = caller as *const _;
-
         // wipe away the associated lifetime - we are going to manually manage the one-way lifetime graph
         let caller = unsafe { std::mem::transmute(caller) };
 
@@ -200,9 +232,11 @@ impl ScopeInner {
             height,
             subtree: Cell::new(subtree),
             is_subtree_root: Cell::new(false),
-
+            tasks: Default::default(),
             frames: ActiveFrame::new(),
             hooks: Default::default(),
+
+            pending_effects: Default::default(),
             suspended_nodes: Default::default(),
             shared_contexts: Default::default(),
             listeners: Default::default(),
@@ -297,14 +331,8 @@ impl ScopeInner {
         if let Some(suspended) = nodes.remove(&task_id) {
             let sus: &'a VSuspended<'static> = unsafe { &*suspended };
             let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
-
-            let cx: SuspendedContext<'a> = SuspendedContext {
-                inner: Context { scope: self },
-            };
-
-            let mut cb = sus.callback.borrow_mut().take().unwrap();
-
-            let new_node: Element<'a> = (cb)(cx);
+            let mut boxed = sus.callback.borrow_mut().take().unwrap();
+            let new_node: Element<'a> = boxed();
         }
     }
 
@@ -372,4 +400,233 @@ impl ScopeInner {
             false
         }
     }
+    /// 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()
+    }
+
+    /// 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)()
+    }
+
+    /// 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()
+    }
+
+    /// 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 bump(&self) -> &Bump {
+        let bump = &self.frames.wip_frame().bump;
+        bump
+    }
+
+    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
+    ///
+    /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
+    ///
+    /// ## Example
+    ///
+    /// ```ignore
+    /// fn Component(cx: Context<()>) -> VNode {
+    ///     // Lazy assemble the VNode tree
+    ///     let lazy_tree = html! {<div> "Hello World" </div>};
+    ///
+    ///     // Actually build the tree and allocate it
+    ///     cx.render(lazy_tree)
+    /// }
+    ///```
+    pub fn render<'src>(
+        &'src self,
+        lazy_nodes: Option<LazyNodes<'src, '_>>,
+    ) -> Option<VNode<'src>> {
+        let bump = &self.frames.wip_frame().bump;
+        let factory = NodeFactory { bump };
+        lazy_nodes.map(|f| f.call(factory))
+    }
+
+    /// Push an effect to be ran after the component has been successfully mounted to the dom
+    /// Returns the effect's position in the stack
+    pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
+        // this is some tricker to get around not being able to actually call fnonces
+        let mut slot = Some(effect);
+        let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
+
+        // wrap it in a type that will actually drop the contents
+        let boxed_fut = unsafe { BumpBox::from_raw(fut) };
+
+        // 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
+    }
+
+    /// Pushes the future onto the poll queue to be polled
+    /// The future is forcibly dropped if the component is not ready by the next render
+    pub fn push_task<'src>(&'src self, fut: impl Future<Output = ()> + 'src) -> usize {
+        // allocate the future
+        let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
+
+        // wrap it in a type that will actually drop the contents
+        let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
+
+        // 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
+    }
+
+    /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
+    ///
+    /// This is a "fundamental" operation and should only be called during initialization of a hook.
+    ///
+    /// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead.
+    ///
+    /// 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, props)|{
+    ///     cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {});
+    ///     rsx!(cx, Child {})
+    /// }
+    ///
+    /// static Child: FC<()> = |(cx, props)|{
+    ///     let state = cx.consume_state::<SharedState>();
+    ///     rsx!(cx, div { "hello {state.0}" })
+    /// }
+    /// ```
+    pub fn provide_state<T>(self, value: T)
+    where
+        T: 'static,
+    {
+        self.shared_contexts
+            .borrow_mut()
+            .insert(TypeId::of::<T>(), Rc::new(value))
+            .map(|f| f.downcast::<T>().ok())
+            .flatten();
+    }
+
+    /// Try to retrieve a SharedState with type T from the any parent Scope.
+    pub fn consume_state<T: 'static>(self) -> Option<Rc<T>> {
+        let getter = &self.shared.get_shared_context;
+        let ty = TypeId::of::<T>();
+        let idx = self.our_arena_idx;
+        getter(idx, ty).map(|f| f.downcast().unwrap())
+    }
+
+    /// Create a new subtree with this scope as the root of the subtree.
+    ///
+    /// 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.
+    ///
+    /// This method
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// static App: FC<()> = |(cx, props)| {
+    ///     todo!();
+    ///     rsx!(cx, div { "Subtree {id}"})
+    /// };
+    /// ```
+    pub fn create_subtree(self) -> Option<u32> {
+        self.new_subtree()
+    }
+
+    /// 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
+    /// static App: FC<()> = |(cx, props)| {
+    ///     let id = cx.get_current_subtree();
+    ///     rsx!(cx, div { "Subtree {id}"})
+    /// };
+    /// ```
+    pub fn get_current_subtree(self) -> u32 {
+        self.subtree()
+    }
+
+    /// Store a value between renders
+    ///
+    /// This is *the* foundational hook for all other hooks.
+    ///
+    /// - 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
+    ///
+    ///
+    /// # Example
+    ///
+    /// ```ignore
+    /// // use_ref is the simplest way of storing a value between renders
+    /// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
+    ///     use_hook(
+    ///         || Rc::new(RefCell::new(initial_value())),
+    ///         |state| state,
+    ///         |_| {},
+    ///     )
+    /// }
+    /// ```
+    pub fn use_hook<'src, State, Output, Init, Run, Cleanup>(
+        &'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
+        if self.hooks.at_end() {
+            self.hooks.push_hook(
+                initializer(self.hooks.len()),
+                Box::new(|raw| {
+                    let s = raw.downcast::<State>().unwrap();
+                    cleanup(s);
+                }),
+            );
+        }
+
+        runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
+    }
 }
+
+const HOOK_ERR_MSG: &str = r###"
+Unable to retrieve the hook that was initialized at this index.
+Consult the `rules of hooks` to understand how to use hooks properly.
+
+You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
+Functions prefixed with "use" should never be called conditionally.
+"###;

+ 12 - 17
packages/core/src/virtual_dom.rs

@@ -64,7 +64,7 @@ pub struct VirtualDom {
     root_props: Rc<dyn Any>,
 
     // we need to keep the allocation around, but we don't necessarily use it
-    _root_caller: Rc<dyn Any>,
+    _root_caller: Box<dyn Any>,
 }
 
 impl VirtualDom {
@@ -143,28 +143,23 @@ impl VirtualDom {
 
         let props = root_props.clone();
 
-        let root_caller: Rc<dyn Fn(&ScopeInner) -> Element> = Rc::new(move |scope: &ScopeInner| {
-            let props = props.downcast_ref::<P>().unwrap();
-            let node = root((Context { scope }, props));
-            // cast into the right lifetime
-            unsafe { std::mem::transmute(node) }
-        });
+        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(
-                root_caller.as_ref(),
-                myidx,
-                None,
-                0,
-                0,
-                scheduler.pool.channel.clone(),
-            )
+            ScopeInner::new(caller, myidx, None, 0, 0, scheduler.pool.channel.clone())
         });
 
         Self {
-            _root_caller: Rc::new(root_caller),
+            _root_caller: Box::new(root_caller),
             root_fc,
             base_scope,
             scheduler,
@@ -223,7 +218,7 @@ impl VirtualDom {
             let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
                 Box::new(move |scope: &ScopeInner| unsafe {
                     let props: &'_ P = &*(props_ptr as *const P);
-                    std::mem::transmute(root((Context { scope }, props)))
+                    std::mem::transmute(root((scope, props)))
                 });
 
             root_scope.update_scope_dependencies(&root_caller);

+ 1 - 0
packages/core/tests/task.rs

@@ -0,0 +1 @@
+fn main() {}