Răsfoiți Sursa

feat: split apart template mutations

Jonathan Kelley 2 ani în urmă
părinte
comite
fc9fe6e560

+ 3 - 2
packages/core/src/any_props.rs

@@ -4,6 +4,7 @@ use futures_util::Future;
 
 use crate::{
     factory::{ComponentReturn, RenderReturn},
+    innerlude::Scoped,
     scopes::{Scope, ScopeState},
     Element,
 };
@@ -69,10 +70,10 @@ impl<'a, P, A, F: ComponentReturn<'a, A>> AnyProps<'a> for VComponentProps<'a, P
         // Make sure the scope ptr is not null
         // self.props.state.set(scope);
 
-        let scope = Scope {
+        let scope = cx.bump().alloc(Scoped {
             props: unsafe { &*self.props },
             scope: cx,
-        };
+        });
 
         // Call the render function directly
         (self.render_fn)(scope).as_return(cx)

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

@@ -1,4 +1,4 @@
-use crate::{nodes::VNode, virtualdom::VirtualDom};
+use crate::{nodes::VNode, virtual_dom::VirtualDom};
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
 pub struct ElementId(pub usize);

+ 45 - 31
packages/core/src/create.rs

@@ -1,31 +1,28 @@
 use std::pin::Pin;
 
 use crate::factory::{FiberLeaf, RenderReturn};
-use crate::innerlude::SuspenseContext;
+use crate::innerlude::{Renderer, SuspenseContext};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, TemplateNode};
-use crate::virtualdom::VirtualDom;
+use crate::virtual_dom::VirtualDom;
 use crate::{AttributeValue, Element, ElementId, TemplateAttribute};
 use bumpalo::boxed::Box as BumpBox;
 use futures_util::Future;
 
 impl VirtualDom {
     /// Create this template and write its mutations
-    pub fn create<'a>(
-        &mut self,
-        mutations: &mut Vec<Mutation<'a>>,
-        template: &'a VNode<'a>,
-    ) -> usize {
+    pub fn create<'a>(&mut self, mutations: &mut Renderer<'a>, template: &'a VNode<'a>) -> usize {
         // The best renderers will have templates prehydrated
         // Just in case, let's create the template using instructions anyways
         if !self.templates.contains_key(&template.template.id) {
             for node in template.template.roots {
+                let mutations = &mut mutations.template_mutations;
                 self.create_static_node(mutations, template, node);
             }
 
-            mutations.push(SaveTemplate {
+            mutations.template_mutations.push(SaveTemplate {
                 name: template.template.id,
                 m: template.template.roots.len(),
             });
@@ -162,7 +159,7 @@ impl VirtualDom {
 
     pub fn create_dynamic_node<'a>(
         &mut self,
-        mutations: &mut Vec<Mutation<'a>>,
+        mutations: &mut Renderer<'a>,
         template: &'a VNode<'a>,
         node: &'a DynamicNode<'a>,
         idx: usize,
@@ -183,24 +180,14 @@ impl VirtualDom {
             DynamicNode::Component {
                 props, placeholder, ..
             } => {
-                println!("creaitng component");
                 let id = self.new_scope(unsafe { std::mem::transmute(props.get()) });
-
                 let render_ret = self.run_scope(id);
-
                 let render_ret: &mut RenderReturn = unsafe { std::mem::transmute(render_ret) };
 
-                match render_ret {
-                    RenderReturn::Sync(Some(template)) => {
-                        self.scope_stack.push(id);
-                        let created = self.create(mutations, template);
-                        self.scope_stack.pop();
-                        created
-                    }
+                // if boundary or subtree, start working on a new stack of mutations
 
-                    // whenever the future is polled later, we'll revisit it
-                    // For now, just set the placeholder
-                    RenderReturn::Sync(None) => {
+                match render_ret {
+                    RenderReturn::Sync(None) | RenderReturn::Async(_) => {
                         let new_id = self.next_element(template);
                         placeholder.set(Some(new_id));
                         self.scopes[id.0].placeholder.set(Some(new_id));
@@ -211,15 +198,42 @@ impl VirtualDom {
                         0
                     }
 
-                    RenderReturn::Async(fut) => {
-                        let new_id = self.next_element(template);
-                        placeholder.set(Some(new_id));
-                        self.scopes[id.0].placeholder.set(Some(new_id));
-                        mutations.push(AssignId {
-                            id: new_id,
-                            path: &template.template.node_paths[idx][1..],
-                        });
-                        0
+                    RenderReturn::Sync(Some(template)) => {
+                        let mutations_to_this_point = mutations.len();
+
+                        self.scope_stack.push(id);
+                        let mut created = self.create(mutations, template);
+                        self.scope_stack.pop();
+
+                        if !self.waiting_on.is_empty() {
+                            if let Some(boundary) =
+                                self.scopes[id.0].has_context::<SuspenseContext>()
+                            {
+                                let mut boundary_mut = boundary.borrow_mut();
+                                let split_off = mutations.split_off(mutations_to_this_point);
+
+                                let split_off = unsafe { std::mem::transmute(split_off) };
+
+                                println!("SPLIT OFF: {:#?}", split_off);
+
+                                boundary_mut.mutations.mutations = split_off;
+                                boundary_mut.waiting_on.extend(self.waiting_on.drain(..));
+
+                                // Since this is a boundary, use it as a placeholder
+                                let new_id = self.next_element(template);
+                                placeholder.set(Some(new_id));
+                                self.scopes[id.0].placeholder.set(Some(new_id));
+                                mutations.push(AssignId {
+                                    id: new_id,
+                                    path: &template.template.node_paths[idx][1..],
+                                });
+                                created = 0;
+                            }
+                        }
+
+                        // handle any waiting on futures accumulated by async calls down the tree
+                        // if this is a boundary, we split off the tree
+                        created
                     }
                 }
             }

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

@@ -1,6 +1,7 @@
 use std::any::Any;
 
-use crate::virtualdom::VirtualDom;
+use crate::innerlude::Renderer;
+use crate::virtual_dom::VirtualDom;
 use crate::{Attribute, AttributeValue, TemplateNode};
 
 use crate::any_props::VComponentProps;
@@ -25,13 +26,13 @@ pub struct DirtyScope {
 }
 
 impl<'b> VirtualDom {
-    pub fn diff_scope(&mut self, mutations: &mut Vec<Mutation<'b>>, scope: ScopeId) {
+    pub fn diff_scope(&mut self, mutations: &mut Renderer<'b>, scope: ScopeId) {
         let scope_state = &mut self.scopes[scope.0];
     }
 
     pub fn diff_node(
         &mut self,
-        muts: &mut Vec<Mutation<'b>>,
+        muts: &mut Renderer<'b>,
         left_template: &'b VNode<'b>,
         right_template: &'b VNode<'b>,
     ) {
@@ -176,7 +177,7 @@ impl<'b> VirtualDom {
     // the change list stack is in the same state when this function returns.
     fn diff_non_keyed_children(
         &mut self,
-        muts: &mut Vec<Mutation<'b>>,
+        muts: &mut Renderer<'b>,
         old: &'b [VNode<'b>],
         new: &'b [VNode<'b>],
     ) {
@@ -216,7 +217,7 @@ impl<'b> VirtualDom {
     // The stack is empty upon entry.
     fn diff_keyed_children(
         &mut self,
-        muts: &mut Vec<Mutation<'b>>,
+        muts: &mut Renderer<'b>,
         old: &'b [VNode<'b>],
         new: &'b [VNode<'b>],
     ) {
@@ -295,7 +296,7 @@ impl<'b> VirtualDom {
     // /// If there is no offset, then this function returns None and the diffing is complete.
     // fn diff_keyed_ends(
     //     &mut self,
-    //     muts: &mut Vec<Mutation<'b>>,
+    //     muts: &mut Renderer<'b>,
     //     old: &'b [VNode<'b>],
     //     new: &'b [VNode<'b>],
     // ) -> Option<(usize, usize)> {
@@ -354,7 +355,7 @@ impl<'b> VirtualDom {
     // #[allow(clippy::too_many_lines)]
     // fn diff_keyed_middle(
     //     &mut self,
-    //     muts: &mut Vec<Mutation<'b>>,
+    //     muts: &mut Renderer<'b>,
     //     old: &'b [VNode<'b>],
     //     new: &'b [VNode<'b>],
     // ) {
@@ -532,7 +533,7 @@ impl<'b> VirtualDom {
 
     /// Remove these nodes from the dom
     /// Wont generate mutations for the inner nodes
-    fn remove_nodes(&mut self, muts: &mut Vec<Mutation<'b>>, nodes: &'b [VNode<'b>]) {
+    fn remove_nodes(&mut self, muts: &mut Renderer<'b>, nodes: &'b [VNode<'b>]) {
         //
     }
 }

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

@@ -1,4 +1,4 @@
-use crate::{arena::ElementId, virtualdom::VirtualDom, Attribute, AttributeValue};
+use crate::{arena::ElementId, virtual_dom::VirtualDom, Attribute, AttributeValue};
 use std::cell::Cell;
 
 /// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.

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

@@ -1,4 +1,4 @@
-use crate::{nodes::VNode, scopes::ScopeId, virtualdom::VirtualDom, DynamicNode};
+use crate::{nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, DynamicNode};
 
 impl VirtualDom {
     pub fn drop_scope(&mut self, id: ScopeId) {

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

@@ -14,7 +14,7 @@ mod properties;
 mod scheduler;
 mod scope_arena;
 mod scopes;
-mod virtualdom;
+mod virtual_dom;
 
 pub(crate) mod innerlude {
     pub use crate::arena::*;
@@ -25,7 +25,7 @@ pub(crate) mod innerlude {
     pub use crate::properties::*;
     pub use crate::scheduler::*;
     pub use crate::scopes::*;
-    pub use crate::virtualdom::*;
+    pub use crate::virtual_dom::*;
 
     /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
     ///
@@ -83,6 +83,9 @@ pub use crate::innerlude::{
     Scope,
     ScopeId,
     ScopeState,
+    Scoped,
+    SuspenseBoundary,
+    SuspenseContext,
     TaskId,
     Template,
     TemplateAttribute,
@@ -98,8 +101,8 @@ pub use crate::innerlude::{
 pub mod prelude {
     pub use crate::innerlude::{
         fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory,
-        Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
-        UiEvent, VNode, VirtualDom,
+        Properties, Scope, ScopeId, ScopeState, Scoped, SuspenseBoundary, SuspenseContext, TaskId,
+        Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
     };
 }
 
@@ -132,3 +135,24 @@ macro_rules! to_owned {
         let mut $es = $es.to_owned();
     )*}
 }
+
+/// A helper macro for values into callbacks for async environements.
+///
+///
+macro_rules! callback {
+    () => {};
+}
+
+/// Convert a hook into a hook with an implicit dependency list by analyzing the closure.
+///
+/// ```
+/// // Convert hooks with annoying dependencies into...
+///
+/// let val = use_effect(cx, (val,) |(val,)| println!("thing {val}"))
+///
+/// // a simple closure
+/// let val = use_effect!(cx, |val| async { println!("thing {val}")) });
+/// ```
+macro_rules! make_dep_fn {
+    () => {};
+}

+ 73 - 1
packages/core/src/mutations.rs

@@ -1,9 +1,81 @@
 use crate::arena::ElementId;
 
+#[derive(Debug)]
 pub struct Renderer<'a> {
-    mutations: Vec<Mutation<'a>>,
+    pub subtree: usize,
+    pub mutations: Vec<Mutation<'a>>,
+    pub template_mutations: Vec<Mutation<'a>>,
+    // mutations: Vec<Mutations<'a>>,
+}
+
+impl<'a> Renderer<'a> {
+    pub fn new(subtree: usize) -> Self {
+        Self {
+            subtree,
+            mutations: Vec::new(),
+            template_mutations: Vec::new(),
+        }
+    }
+}
+
+impl<'a> std::ops::Deref for Renderer<'a> {
+    type Target = Vec<Mutation<'a>>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.mutations
+    }
+}
+
+impl std::ops::DerefMut for Renderer<'_> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.mutations
+    }
 }
 
+// impl<'a> Renderer<'a> {
+//     pub fn new(subtree: usize) -> Self {
+//         Self {
+//             mutations: vec![Mutations {
+//                 subtree,
+//                 mutations: Vec::new(),
+//             }],
+//         }
+//     }
+// }
+// impl<'a> Renderer<'a> {
+//     pub fn push(&mut self, mutation: Mutation<'a>) {
+//         self.mutations.last_mut().unwrap().mutations.push(mutation)
+//     }
+
+//     pub fn extend(&mut self, mutations: impl IntoIterator<Item = Mutation<'a>>) {
+//         self.mutations
+//             .last_mut()
+//             .unwrap()
+//             .mutations
+//             .extend(mutations)
+//     }
+
+//     pub fn len(&self) -> usize {
+//         self.mutations.last().unwrap().mutations.len()
+//     }
+
+//     pub fn split_off(&mut self, idx: usize) -> Renderer<'a> {
+//         let mut mutations = self.mutations.split_off(idx);
+//         let subtree = mutations.pop().unwrap().subtree;
+//         Renderer { mutations }
+//     }
+// }
+
+// #[derive(Debug)]
+// pub struct Mutations<'a> {
+//     subtree: usize,
+//     mutations: Vec<Mutation<'a>>,
+// }
+
+/*
+each subtree has its own numbering scheme
+*/
+
 #[derive(Debug)]
 pub enum Mutation<'a> {
     SetAttribute {

+ 14 - 11
packages/core/src/scheduler/suspense.rs

@@ -6,7 +6,10 @@ use std::{
 };
 
 use super::{waker::RcWake, SchedulerMsg};
-use crate::{innerlude::Mutation, Element, ScopeId};
+use crate::{
+    innerlude::{Mutation, Renderer},
+    Element, ScopeId,
+};
 use futures_task::Waker;
 use futures_util::Future;
 
@@ -14,27 +17,27 @@ use futures_util::Future;
 pub struct SuspenseId(pub usize);
 
 pub type SuspenseContext = Rc<RefCell<SuspenseBoundary>>;
+
 /// Essentially a fiber in React
 pub struct SuspenseBoundary {
     pub id: ScopeId,
     pub waiting_on: HashSet<SuspenseId>,
-    pub mutations: Vec<Mutation<'static>>,
+    pub mutations: Renderer<'static>,
 }
 
 impl SuspenseBoundary {
-    pub fn new(id: ScopeId) -> Self {
-        Self {
+    pub fn new(id: ScopeId) -> Rc<RefCell<Self>> {
+        Rc::new(RefCell::new(Self {
             id,
             waiting_on: Default::default(),
-            mutations: Default::default(),
-        }
+            mutations: Renderer::new(0),
+        }))
     }
 }
 
 pub struct SuspenseLeaf {
     pub id: SuspenseId,
     pub scope_id: ScopeId,
-    pub boundary: ScopeId,
     pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
     pub notified: Cell<bool>,
 
@@ -43,10 +46,10 @@ pub struct SuspenseLeaf {
 
 impl RcWake for SuspenseLeaf {
     fn wake_by_ref(arc_self: &Rc<Self>) {
-        if arc_self.notified.get() {
-            return;
-        }
-        arc_self.notified.set(true);
+        // if arc_self.notified.get() {
+        //     return;
+        // }
+        // arc_self.notified.set(true);
         _ = arc_self
             .tx
             .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id));

+ 20 - 4
packages/core/src/scheduler/wait.rs

@@ -3,7 +3,7 @@ use futures_util::{FutureExt, StreamExt};
 
 use crate::{
     factory::RenderReturn,
-    innerlude::{Mutation, SuspenseContext},
+    innerlude::{Mutation, Renderer, SuspenseContext},
     VNode, VirtualDom,
 };
 
@@ -38,6 +38,8 @@ impl VirtualDom {
                 }
 
                 SchedulerMsg::SuspenseNotified(id) => {
+                    println!("suspense notified");
+
                     let leaf = self
                         .scheduler
                         .handle
@@ -63,11 +65,19 @@ impl VirtualDom {
                     // continue rendering the tree until we hit yet another suspended component
                     if let futures_task::Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx)
                     {
-                        let boundary = &self.scopes[leaf.boundary.0]
+                        let boundary = &self.scopes[leaf.scope_id.0]
                             .consume_context::<SuspenseContext>()
                             .unwrap();
 
+                        println!("ready pool");
+
                         let mut fiber = boundary.borrow_mut();
+
+                        println!(
+                            "Existing mutations {:?}, scope {:?}",
+                            fiber.mutations, fiber.id
+                        );
+
                         let scope = &mut self.scopes[scope_id.0];
                         let arena = scope.current_arena();
 
@@ -77,18 +87,24 @@ impl VirtualDom {
                         if let RenderReturn::Sync(Some(template)) = ret {
                             let mutations = &mut fiber.mutations;
                             let template: &VNode = unsafe { std::mem::transmute(template) };
-                            let mutations: &mut Vec<Mutation> =
+                            let mutations: &mut Renderer =
                                 unsafe { std::mem::transmute(mutations) };
 
                             self.scope_stack.push(scope_id);
                             self.create(mutations, template);
                             self.scope_stack.pop();
 
-                            println!("{:?}", mutations);
+                            println!("{:#?}", mutations);
+                        } else {
+                            println!("nodes arent right");
                         }
+                    } else {
+                        println!("not ready");
                     }
                 }
             }
+
+            // now proces any events. If we end up running a component and it generates mutations, then we should run those mutations
         }
     }
 }

+ 4 - 3
packages/core/src/scope_arena.rs

@@ -14,7 +14,7 @@ use crate::{
     factory::RenderReturn,
     innerlude::{SuspenseId, SuspenseLeaf},
     scopes::{ScopeId, ScopeState},
-    virtualdom::VirtualDom,
+    virtual_dom::VirtualDom,
 };
 
 impl VirtualDom {
@@ -77,13 +77,13 @@ impl VirtualDom {
             let mut leaves = self.scheduler.handle.leaves.borrow_mut();
             let entry = leaves.vacant_entry();
             let key = entry.key();
+            let suspense_id = SuspenseId(key);
 
             let leaf = Rc::new(SuspenseLeaf {
                 scope_id,
                 task: task.as_mut(),
-                id: SuspenseId(key),
+                id: suspense_id,
                 tx: self.scheduler.handle.sender.clone(),
-                boundary: ScopeId(0),
                 notified: false.into(),
             });
 
@@ -112,6 +112,7 @@ impl VirtualDom {
                     // Insert the future into fiber leaves and break
                     _ => {
                         entry.insert(leaf);
+                        self.waiting_on.push(suspense_id);
                         break;
                     }
                 };

+ 17 - 13
packages/core/src/scopes.rs

@@ -19,23 +19,14 @@ use crate::{
     TaskId,
 };
 
-pub struct Scope<'a, T = ()> {
+pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
+
+pub struct Scoped<'a, T = ()> {
     pub scope: &'a ScopeState,
     pub props: &'a T,
 }
 
-impl<T> Copy for Scope<'_, T> {}
-
-impl<T> Clone for Scope<'_, T> {
-    fn clone(&self) -> Self {
-        Self {
-            props: self.props,
-            scope: self.scope,
-        }
-    }
-}
-
-impl<'a, T> std::ops::Deref for Scope<'a, T> {
+impl<'a, T> std::ops::Deref for Scoped<'a, T> {
     type Target = &'a ScopeState;
 
     fn deref(&self) -> &Self::Target {
@@ -298,6 +289,19 @@ impl ScopeState {
         }
     }
 
+    /// Return any context of type T if it exists on this scope
+    pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
+        match self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
+            Some(shared) => Some(
+                (*shared
+                    .downcast_ref::<T>()
+                    .expect("Context of type T should exist"))
+                .clone(),
+            ),
+            None => None,
+        }
+    }
+
     /// Pushes the future onto the poll queue to be polled after the component renders.
     pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
         self.tasks.spawn(self.id, fut)

+ 26 - 19
packages/core/src/virtualdom.rs → packages/core/src/virtual_dom.rs

@@ -3,7 +3,7 @@ use crate::arena::ElementPath;
 use crate::component::Component;
 use crate::diff::DirtyScope;
 use crate::factory::RenderReturn;
-use crate::innerlude::{Scheduler, SchedulerMsg};
+use crate::innerlude::{Renderer, Scheduler, SchedulerMsg};
 use crate::mutations::Mutation;
 use crate::nodes::{Template, TemplateId};
 
@@ -13,20 +13,22 @@ use crate::{
 };
 use crate::{scheduler, Element, Scope};
 use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
-use scheduler::{SuspenseBoundary, SuspenseContext};
+use futures_util::Future;
+use scheduler::{SuspenseBoundary, SuspenseContext, SuspenseId};
 use slab::Slab;
-use std::cell::RefCell;
 use std::collections::{BTreeSet, HashMap};
-use std::rc::Rc;
 
 pub struct VirtualDom {
     pub(crate) templates: HashMap<TemplateId, Template<'static>>,
     pub(crate) elements: Slab<ElementPath>,
     pub(crate) scopes: Slab<ScopeState>,
-    pub(crate) scope_stack: Vec<ScopeId>,
     pub(crate) element_stack: Vec<ElementId>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) scheduler: Scheduler,
+
+    // While diffing we need some sort of way of breaking off a stream of suspended mutations.
+    pub(crate) scope_stack: Vec<ScopeId>,
+    pub(crate) waiting_on: Vec<SuspenseId>,
 }
 
 impl VirtualDom {
@@ -40,6 +42,7 @@ impl VirtualDom {
             scope_stack: Vec::new(),
             element_stack: vec![ElementId(0)],
             dirty_scopes: BTreeSet::new(),
+            waiting_on: Vec::new(),
             scheduler,
         };
 
@@ -49,21 +52,25 @@ impl VirtualDom {
         let root = res.new_scope(props);
 
         // the root component is always a suspense boundary for any async children
-        res.scopes[root.0].provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(root))));
-
+        res.scopes[root.0].provide_context(SuspenseBoundary::new(root));
         assert_eq!(root, ScopeId(0));
 
         res
     }
 
     /// Render the virtualdom, without processing any suspense.
-    pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
+    ///
+    /// This does register futures with wakers, but does not process any of them.
+    pub fn rebuild<'a>(&'a mut self) -> Renderer<'a> {
+        let mut mutations = Renderer::new(0);
         let root_node: &RenderReturn = self.run_scope(ScopeId(0));
         let root_node: &RenderReturn = unsafe { std::mem::transmute(root_node) };
+
+        let mut created = 0;
         match root_node {
             RenderReturn::Sync(Some(node)) => {
                 self.scope_stack.push(ScopeId(0));
-                self.create(mutations, node);
+                created = self.create(&mut mutations, node);
                 self.scope_stack.pop();
             }
             RenderReturn::Sync(None) => {
@@ -71,22 +78,22 @@ impl VirtualDom {
             }
             RenderReturn::Async(_) => unreachable!(),
         }
+
+        mutations.push(Mutation::AppendChildren { m: created });
+
+        mutations
     }
 
     /// Render what you can given the timeline and then move on
-    pub async fn render_with_deadline<'a>(
-        &'a mut self,
-        future: impl std::future::Future<Output = ()>,
-        mutations: &mut Vec<Mutation<'a>>,
-    ) {
+    ///
+    /// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
+    pub async fn render_with_deadline(
+        &mut self,
+        deadline: impl Future<Output = ()>,
+    ) -> Vec<Mutation> {
         todo!()
     }
 
-    // Whenever the future is canceled, the VirtualDom will be
-    pub async fn render<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
-        //
-    }
-
     pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
         self.scopes.get(id.0)
     }

+ 1 - 2
packages/core/tests/simple_syntax.rs

@@ -21,6 +21,5 @@ async fn ChildAsync(cx: Scope<'_>) -> Element {
 fn it_works() {
     let mut dom = VirtualDom::new(app);
 
-    let mut mutations = vec![];
-    dom.rebuild(&mut mutations);
+    let mut mutations = dom.rebuild();
 }

+ 23 - 9
packages/core/tests/suspend.rs

@@ -1,13 +1,11 @@
-use std::{cell::Cell, ptr::null_mut, time::Duration};
-
 use dioxus_core::*;
+use std::{cell::RefCell, rc::Rc, time::Duration};
 
 #[tokio::test]
 async fn it_works() {
     let mut dom = VirtualDom::new(app);
 
-    let mut mutations = vec![];
-    dom.rebuild(&mut mutations);
+    let mutations = dom.rebuild();
 
     println!("mutations: {:?}", mutations);
 
@@ -15,8 +13,23 @@ async fn it_works() {
 }
 
 fn app(cx: Scope) -> Element {
-    let dy = cx.component(async_child, (), "async_child");
-    VNode::single_component(&cx, dy, "app")
+    println!("running root app");
+
+    VNode::single_component(
+        cx,
+        cx.component(suspense_boundary, (), "suspense_boundary"),
+        "app",
+    )
+}
+
+fn suspense_boundary(cx: Scope) -> Element {
+    println!("running boundary");
+
+    let _ = cx.use_hook(|| {
+        cx.provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(cx.scope_id()))))
+    });
+
+    VNode::single_component(cx, cx.component(async_child, (), "async_child"), "app")
 }
 
 async fn async_child(cx: Scope<'_>) -> Element {
@@ -34,8 +47,9 @@ async fn async_child(cx: Scope<'_>) -> Element {
 
     println!("Future awaited and complete");
 
-    let dy = cx.component(async_child, (), "async_child");
-    VNode::single_component(&cx, dy, "app")
+    VNode::single_component(cx, cx.component(async_text, (), "async_text"), "app")
+}
 
-    // VNode::single_text(&cx, &[TemplateNode::Text("it works!")], "beauty")
+async fn async_text(cx: Scope<'_>) -> Element {
+    VNode::single_text(&cx, &[TemplateNode::Text("it works!")], "beauty")
 }

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

@@ -6,8 +6,7 @@ use dioxus_core::*;
 async fn it_works() {
     let mut dom = VirtualDom::new(app);
 
-    let mut mutations = vec![];
-    dom.rebuild(&mut mutations);
+    let mutations = dom.rebuild();
 
     println!("mutations: {:?}", mutations);
 

+ 1 - 0
packages/dioxus/Cargo.toml

@@ -32,6 +32,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
 criterion = "0.3.5"
 thiserror = "1.0.30"
 env_logger = "0.9.0"
+tokio = { version = "1.21.2", features = ["full"] }
 # dioxus-edit-stream = { path = "../edit-stream" }
 
 [[bench]]

+ 50 - 26
packages/dioxus/tests/rsx_syntax.rs

@@ -1,3 +1,5 @@
+use std::future::IntoFuture;
+
 use dioxus::prelude::*;
 
 fn basic_syntax_is_a_template(cx: Scope) -> Element {
@@ -23,41 +25,63 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
     })
 }
 
-fn basic_template(cx: Scope) -> Element {
-    cx.render(rsx! {
-        div {
-            basic_child { }
-            async_child { }
-        }
-    })
+#[inline_props]
+fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
+    cx.use_hook(|| cx.provide_context(SuspenseBoundary::new(cx.scope_id())));
+    cx.render(rsx! { children })
 }
 
 fn basic_child(cx: Scope) -> Element {
-    todo!()
+    cx.render(rsx! {
+        div { "basic child 1" }
+    })
 }
 
 async fn async_child(cx: Scope<'_>) -> Element {
-    todo!()
-}
+    let username = use_future!(cx, || async {
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+        "async child 1"
+    });
 
-#[test]
-fn basic_prints() {
-    let mut dom = VirtualDom::new(basic_template);
+    let age = use_future!(cx, || async {
+        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
+        println!("long future completed");
+        1234
+    });
 
-    let mut edits = Vec::new();
-    dom.rebuild(&mut edits);
-    dbg!(edits);
-
-    let mut edits = Vec::new();
-    dom.rebuild(&mut edits);
+    let (_user, _age) = use_future!(cx, || async {
+        tokio::join!(
+            tokio::time::sleep(std::time::Duration::from_secs(1)),
+            tokio::time::sleep(std::time::Duration::from_secs(2))
+        );
+        ("async child 1", 1234)
+    })
+    .await;
 
-    dbg!(edits);
-    // let renderer = dioxus_edit_stream::Mutations::default();
-    //
-    // dbg!(renderer.edits);
+    let (username, age) = tokio::join!(username.into_future(), age.into_future());
 
-    // takes_it(basic_child);
+    cx.render(rsx!(
+        div { "Hello! {username}, you are {age}, {_user} {_age}" }
+    ))
 }
 
-// fn takes_it(f: fn(Scope) -> Element) {}
-// fn takes_it(f: fn(Scope) -> Element) {}
+#[tokio::test]
+async fn basic_prints() {
+    let mut dom = VirtualDom::new(|cx| {
+        cx.render(rsx! {
+            div {
+                h1 { "var" }
+                suspense_boundary {
+                    basic_child { }
+                    async_child { }
+                }
+            }
+        })
+    });
+
+    dbg!(dom.rebuild());
+
+    dom.wait_for_work().await;
+
+    dbg!(dom.rebuild());
+}

+ 104 - 36
packages/hooks/src/usefuture.rs

@@ -1,6 +1,12 @@
 #![allow(missing_docs)]
 use dioxus_core::{ScopeState, TaskId};
-use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    future::{Future, IntoFuture},
+    rc::Rc,
+    sync::Arc,
+};
 
 /// A future that resolves to a value.
 ///
@@ -28,16 +34,13 @@ where
     let state = cx.use_hook(move || UseFuture {
         update: cx.schedule_update(),
         needs_regen: Cell::new(true),
-        slot: Rc::new(Cell::new(None)),
-        value: None,
+        values: Default::default(),
         task: Cell::new(None),
         dependencies: Vec::new(),
+        waker: Default::default(),
     });
 
-    if let Some(value) = state.slot.take() {
-        state.value = Some(value);
-        state.task.set(None);
-    }
+    *state.waker.borrow_mut() = None;
 
     if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
         // We don't need regen anymore
@@ -47,8 +50,9 @@ where
         let fut = future(dependencies.out());
 
         // Clone in our cells
-        let slot = state.slot.clone();
+        let values = state.values.clone();
         let schedule_update = state.update.clone();
+        let waker = state.waker.clone();
 
         // Cancel the current future
         if let Some(current) = state.task.take() {
@@ -57,8 +61,16 @@ where
 
         state.task.set(Some(cx.push_future(async move {
             let res = fut.await;
-            slot.set(Some(res));
-            schedule_update();
+            values.borrow_mut().push(Box::leak(Box::new(res)));
+
+            // if there's a waker, we dont re-render the component. Instead we just progress that future
+            match waker.borrow().as_ref() {
+                Some(waker) => waker.wake_by_ref(),
+                None => {
+                    println!("scheduling update");
+                    // schedule_update()
+                }
+            }
         })));
     }
 
@@ -74,10 +86,10 @@ pub enum FutureState<'a, T> {
 pub struct UseFuture<T> {
     update: Arc<dyn Fn()>,
     needs_regen: Cell<bool>,
-    value: Option<T>,
-    slot: Rc<Cell<Option<T>>>,
     task: Cell<Option<TaskId>>,
     dependencies: Vec<Box<dyn Any>>,
+    waker: Rc<RefCell<Option<std::task::Waker>>>,
+    values: Rc<RefCell<Vec<*mut T>>>,
 }
 
 pub enum UseFutureState<'a, T> {
@@ -105,22 +117,25 @@ impl<T> UseFuture<T> {
 
     // clears the value in the future slot without starting the future over
     pub fn clear(&self) -> Option<T> {
-        (self.update)();
-        self.slot.replace(None)
+        todo!()
+        // (self.update)();
+        // self.slot.replace(None)
     }
 
     // Manually set the value in the future slot without starting the future over
     pub fn set(&self, new_value: T) {
-        self.slot.set(Some(new_value));
-        self.needs_regen.set(true);
-        (self.update)();
+        // self.slot.set(Some(new_value));
+        // self.needs_regen.set(true);
+        // (self.update)();
+        todo!()
     }
 
     /// Return any value, even old values if the future has not yet resolved.
     ///
     /// If the future has never completed, the returned value will be `None`.
     pub fn value(&self) -> Option<&T> {
-        self.value.as_ref()
+        // self.value.as_ref()
+        todo!()
     }
 
     /// Get the ID of the future in Dioxus' internal scheduler
@@ -130,18 +145,49 @@ impl<T> UseFuture<T> {
 
     /// Get the current stateof the future.
     pub fn state(&self) -> UseFutureState<T> {
-        match (&self.task.get(), &self.value) {
-            // If we have a task and an existing value, we're reloading
-            (Some(_), Some(val)) => UseFutureState::Reloading(val),
+        todo!()
+        // match (&self.task.get(), &self.value) {
+        //     // If we have a task and an existing value, we're reloading
+        //     (Some(_), Some(val)) => UseFutureState::Reloading(val),
+
+        //     // no task, but value - we're done
+        //     (None, Some(val)) => UseFutureState::Complete(val),
+
+        //     // no task, no value - something's wrong? return pending
+        //     (None, None) => UseFutureState::Pending,
+
+        //     // Task, no value - we're still pending
+        //     (Some(_), None) => UseFutureState::Pending,
+        // }
+    }
+}
+
+impl<'a, T> IntoFuture for &'a UseFuture<T> {
+    type Output = &'a T;
+    type IntoFuture = UseFutureAwait<'a, T>;
+    fn into_future(self) -> Self::IntoFuture {
+        UseFutureAwait { hook: self }
+    }
+}
 
-            // no task, but value - we're done
-            (None, Some(val)) => UseFutureState::Complete(val),
+pub struct UseFutureAwait<'a, T> {
+    hook: &'a UseFuture<T>,
+}
 
-            // no task, no value - something's wrong? return pending
-            (None, None) => UseFutureState::Pending,
+impl<'a, T> Future for UseFutureAwait<'a, T> {
+    type Output = &'a T;
 
-            // Task, no value - we're still pending
-            (Some(_), None) => UseFutureState::Pending,
+    fn poll(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Self::Output> {
+        println!("polling future");
+        match self.hook.values.borrow_mut().last().cloned() {
+            Some(value) => std::task::Poll::Ready(unsafe { &*value }),
+            None => {
+                self.hook.waker.replace(Some(cx.waker().clone()));
+                std::task::Poll::Pending
+            }
         }
     }
 }
@@ -239,6 +285,18 @@ impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,);
 impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g,);
 impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,);
 
+#[macro_export]
+macro_rules! use_future {
+    ($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) };
+    ($cx:ident, | $($args:tt),* | $($rest:tt)*) => {
+        use_future(
+            $cx,
+            ($($args),*),
+            |($($args),*)| $($rest)*
+        )
+    };
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -256,22 +314,32 @@ mod tests {
             e: i32,
         }
 
-        fn app(cx: Scope<MyProps>) -> Element {
+        async fn app(cx: Scope<'_, MyProps>) -> Element {
             // should only ever run once
-            let fut = use_future(&cx, (), |_| async move {
-                //
-            });
+            let fut = use_future(cx, (), |_| async move {});
 
             // runs when a is changed
-            let fut = use_future(&cx, (&cx.props.a,), |(a,)| async move {
-                //
-            });
+            let fut = use_future(cx, (&cx.props.a,), |(a,)| async move {});
 
             // runs when a or b is changed
-            let fut = use_future(&cx, (&cx.props.a, &cx.props.b), |(a, b)| async move {
-                //
+            let fut = use_future(cx, (&cx.props.a, &cx.props.b), |(a, b)| async move { 123 });
+
+            let a = use_future!(cx, || async move {
+                // do the thing!
             });
 
+            let b = &123;
+            let c = &123;
+
+            let a = use_future!(cx, |b, c| async move {
+                let a = b + c;
+                let blah = "asd";
+            });
+
+            let g2 = a.await;
+
+            let g = fut.await;
+
             None
         }
     }

+ 3 - 1
packages/hooks/src/usestate.rs

@@ -472,6 +472,8 @@ fn api_makes_sense() {
             }
         });
 
-        cx.render(LazyNodes::new(|f| f.static_text("asd")))
+        // cx.render(LazyNodes::new(|f| f.static_text("asd")))
+
+        todo!()
     }
 }

+ 0 - 41
packages/hooks/src/usesuspense.rs

@@ -1,41 +0,0 @@
-use std::{cell::Cell, future::Future, rc::Rc};
-
-use dioxus_core::{Element, ScopeState, TaskId};
-
-pub fn use_suspense<R: 'static, F: Future<Output = R> + 'static>(
-    cx: &ScopeState,
-    create_future: impl FnOnce() -> F,
-    render: impl FnOnce(&R) -> Element,
-) -> Element {
-    let sus = cx.use_hook(|| {
-        let fut = create_future();
-
-        let wip_value: Rc<Cell<Option<R>>> = Default::default();
-
-        let wip = wip_value.clone();
-        let new_fut = async move {
-            let val = fut.await;
-            wip.set(Some(val));
-        };
-
-        let task = cx.push_future(new_fut);
-        SuspenseInner {
-            _task: task,
-            value: None,
-            _wip_value: wip_value,
-        }
-    });
-
-    if let Some(value) = sus.value.as_ref() {
-        render(value)
-    } else {
-        // generate a placeholder node if the future isnt ready
-        None
-    }
-}
-
-struct SuspenseInner<R> {
-    _task: TaskId,
-    _wip_value: Rc<Cell<Option<R>>>,
-    value: Option<R>,
-}