浏览代码

feat: context api overhaul

Jonathan Kelley 2 年之前
父节点
当前提交
a61daf220d
共有 44 个文件被更改,包括 789 次插入598 次删除
  1. 1 1
      packages/core/src/any_props.rs
  2. 5 1
      packages/core/src/arena.rs
  3. 3 3
      packages/core/src/bump_frame.rs
  4. 5 6
      packages/core/src/create.rs
  5. 3 3
      packages/core/src/diff.rs
  6. 0 197
      packages/core/src/factory.rs
  7. 11 12
      packages/core/src/fragment.rs
  8. 1 1
      packages/core/src/lazynodes.rs
  9. 4 5
      packages/core/src/lib.rs
  10. 93 19
      packages/core/src/mutations.rs
  11. 356 55
      packages/core/src/nodes.rs
  12. 11 11
      packages/core/src/scheduler/suspense.rs
  13. 4 0
      packages/core/src/scheduler/task.rs
  14. 14 7
      packages/core/src/scheduler/wait.rs
  15. 2 4
      packages/core/src/scope_arena.rs
  16. 66 37
      packages/core/src/scopes.rs
  17. 9 5
      packages/core/src/virtual_dom.rs
  18. 1 1
      packages/core/tests/attr_cleanup.rs
  19. 1 1
      packages/core/tests/boolattrs.rs
  20. 1 1
      packages/core/tests/borrowedstate.rs
  21. 1 1
      packages/core/tests/context_api.rs
  22. 69 89
      packages/core/tests/create_dom.rs
  23. 19 16
      packages/core/tests/create_element.rs
  24. 5 5
      packages/core/tests/create_fragments.rs
  25. 24 23
      packages/core/tests/create_lists.rs
  26. 4 4
      packages/core/tests/create_passthru.rs
  27. 1 1
      packages/core/tests/cycle.rs
  28. 1 1
      packages/core/tests/diff_component.rs
  29. 1 1
      packages/core/tests/diff_keyed_list.rs
  30. 5 5
      packages/core/tests/diff_unkeyed_list.rs
  31. 1 37
      packages/core/tests/kitchen_sink.rs
  32. 1 1
      packages/core/tests/lifecycle.rs
  33. 14 13
      packages/core/tests/suspense.rs
  34. 5 2
      packages/hooks/src/lib.rs
  35. 3 2
      packages/hooks/src/use_shared_state.rs
  36. 17 0
      packages/hooks/src/usecontext.rs
  37. 13 11
      packages/hooks/src/usecoroutine.rs
  38. 1 1
      packages/hooks/src/useeffect.rs
  39. 1 1
      packages/hooks/src/usefuture.rs
  40. 1 1
      packages/hooks/src/usestate.rs
  41. 1 1
      packages/html/src/events.rs
  42. 8 5
      packages/rsx/src/lib.rs
  43. 1 1
      packages/ssr/src/template.rs
  44. 1 6
      packages/web/src/dom.rs

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

@@ -1,8 +1,8 @@
 use std::marker::PhantomData;
 
 use crate::{
-    factory::{ComponentReturn, RenderReturn},
     innerlude::Scoped,
+    nodes::{ComponentReturn, RenderReturn},
     scopes::{Scope, ScopeState},
     Element,
 };

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

@@ -1,9 +1,13 @@
 use crate::{
-    factory::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
+    nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
     ScopeId,
 };
 use bumpalo::boxed::Box as BumpBox;
 
+/// An Element's unique identifier.
+///
+/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
+/// unmounted, then the `ElementId` will be reused for a new component.
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
 pub struct ElementId(pub usize);

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

@@ -1,4 +1,4 @@
-use crate::factory::RenderReturn;
+use crate::nodes::RenderReturn;
 use bumpalo::Bump;
 use std::cell::Cell;
 
@@ -8,7 +8,7 @@ pub(crate) struct BumpFrame {
 }
 
 impl BumpFrame {
-    pub fn new(capacity: usize) -> Self {
+    pub(crate) fn new(capacity: usize) -> Self {
         let bump = Bump::with_capacity(capacity);
         Self {
             bump,
@@ -17,7 +17,7 @@ impl BumpFrame {
     }
 
     /// Creates a new lifetime out of thin air
-    pub unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
+    pub(crate) unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
         let node = self.node.get();
 
         if node.is_null() {

+ 5 - 6
packages/core/src/create.rs

@@ -1,13 +1,12 @@
 use std::cell::Cell;
 
-use crate::factory::RenderReturn;
 use crate::innerlude::{VComponent, VText};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, TemplateNode};
 use crate::virtual_dom::VirtualDom;
-use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext};
+use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
 
 impl<'b> VirtualDom {
     /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
@@ -25,7 +24,7 @@ impl<'b> VirtualDom {
     pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize {
         // The best renderers will have templates prehydrated and registered
         // Just in case, let's create the template using instructions anyways
-        if !self.templates.contains_key(&template.template.id) {
+        if !self.templates.contains_key(&template.template.name) {
             self.register_template(template);
         }
 
@@ -75,7 +74,7 @@ impl<'b> VirtualDom {
 
                     template.root_ids[root_idx].set(this_id);
                     self.mutations.push(LoadTemplate {
-                        name: template.template.id,
+                        name: template.template.name,
                         index: root_idx,
                         id: this_id,
                     });
@@ -204,7 +203,7 @@ impl<'b> VirtualDom {
     fn register_template(&mut self, template: &'b VNode<'b>) {
         // First, make sure we mark the template as seen, regardless if we process it
         self.templates
-            .insert(template.template.id, template.template);
+            .insert(template.template.name, template.template);
 
         // If it's all dynamic nodes, then we don't need to register it
         // Quickly run through and see if it's all just dynamic nodes
@@ -342,7 +341,7 @@ impl<'b> VirtualDom {
 
         // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense
         let boundary = match self.scopes[scope.0].has_context::<SuspenseContext>() {
-            Some(boundary) => boundary,
+            Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) },
             _ => return created,
         };
 

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

@@ -2,9 +2,9 @@ use std::cell::Cell;
 
 use crate::{
     arena::ElementId,
-    factory::RenderReturn,
     innerlude::{DirtyScope, VComponent, VText},
     mutations::Mutation,
+    nodes::RenderReturn,
     nodes::{DynamicNode, VNode},
     scopes::ScopeId,
     virtual_dom::VirtualDom,
@@ -63,10 +63,10 @@ impl<'b> VirtualDom {
     fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
         println!(
             "diffing node {:#?},\n\n{:#?}",
-            left_template.template.id, right_template.template.id
+            left_template.template.name, right_template.template.name
         );
 
-        if left_template.template.id != right_template.template.id {
+        if left_template.template.name != right_template.template.name {
             return self.light_diff_templates(left_template, right_template);
         }
 

+ 0 - 197
packages/core/src/factory.rs

@@ -1,197 +0,0 @@
-use crate::{innerlude::DynamicNode, AttributeValue, Element, LazyNodes, ScopeState, VNode};
-use bumpalo::boxed::Box as BumpBox;
-use bumpalo::Bump;
-use std::fmt::Arguments;
-use std::future::Future;
-
-#[doc(hidden)]
-pub trait ComponentReturn<'a, A = ()> {
-    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
-}
-
-impl<'a> ComponentReturn<'a> for Element<'a> {
-    fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
-        RenderReturn::Sync(self)
-    }
-}
-
-#[doc(hidden)]
-pub struct AsyncMarker;
-impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
-where
-    F: Future<Output = Element<'a>> + 'a,
-{
-    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
-        let f: &mut dyn Future<Output = Element<'a>> = cx.bump().alloc(self);
-        RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
-    }
-}
-
-pub enum RenderReturn<'a> {
-    /// A currently-available element
-    Sync(Element<'a>),
-
-    /// An ongoing future that will resolve to a [`Element`]
-    Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
-}
-
-impl<'a> RenderReturn<'a> {
-    pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
-        unsafe { std::mem::transmute(self) }
-    }
-    pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
-        unsafe { std::mem::transmute(self) }
-    }
-}
-
-#[doc(hidden)]
-pub trait IntoDynNode<'a, A = ()> {
-    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
-}
-
-impl<'a> IntoDynNode<'a> for () {
-    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        DynamicNode::placeholder()
-    }
-}
-impl<'a> IntoDynNode<'a> for VNode<'a> {
-    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        DynamicNode::Fragment(_cx.bump().alloc([self]))
-    }
-}
-impl<'a> IntoDynNode<'a> for Element<'a> {
-    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        match self {
-            Ok(val) => val.into_vnode(_cx),
-            _ => DynamicNode::placeholder(),
-        }
-    }
-}
-
-impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
-    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        match self {
-            Some(val) => val.into_vnode(_cx),
-            None => DynamicNode::placeholder(),
-        }
-    }
-}
-
-impl<'a> IntoDynNode<'a> for &Element<'a> {
-    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        match self.as_ref() {
-            Ok(val) => val.clone().into_vnode(_cx),
-            _ => DynamicNode::placeholder(),
-        }
-    }
-}
-
-impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
-    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
-        DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
-    }
-}
-
-impl<'a> IntoDynNode<'_> for &'a str {
-    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
-        cx.text_node(format_args!("{}", self))
-    }
-}
-
-impl IntoDynNode<'_> for String {
-    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
-        cx.text_node(format_args!("{}", self))
-    }
-}
-
-impl<'b> IntoDynNode<'b> for Arguments<'_> {
-    fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
-        cx.text_node(self)
-    }
-}
-
-impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
-    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        DynamicNode::Fragment(_cx.bump().alloc([VNode {
-            parent: self.parent,
-            template: self.template,
-            root_ids: self.root_ids,
-            key: self.key,
-            dynamic_nodes: self.dynamic_nodes,
-            dynamic_attrs: self.dynamic_attrs,
-        }]))
-    }
-}
-
-pub trait IntoTemplate<'a> {
-    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
-}
-impl<'a> IntoTemplate<'a> for VNode<'a> {
-    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
-        self
-    }
-}
-impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
-    fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
-        self.call(cx)
-    }
-}
-
-// Note that we're using the E as a generic but this is never crafted anyways.
-#[doc(hidden)]
-pub struct FromNodeIterator;
-impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
-where
-    T: Iterator<Item = I>,
-    I: IntoTemplate<'a>,
-{
-    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
-        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
-
-        for node in self {
-            nodes.push(node.into_template(cx));
-        }
-
-        let children = nodes.into_bump_slice();
-
-        match children.len() {
-            0 => DynamicNode::placeholder(),
-            _ => DynamicNode::Fragment(children),
-        }
-    }
-}
-
-/// A value that can be converted into an attribute value
-pub trait IntoAttributeValue<'a> {
-    /// Convert into an attribute value
-    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
-}
-
-impl<'a> IntoAttributeValue<'a> for &'a str {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Text(self)
-    }
-}
-impl<'a> IntoAttributeValue<'a> for f32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Float(self)
-    }
-}
-impl<'a> IntoAttributeValue<'a> for i32 {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Int(self)
-    }
-}
-impl<'a> IntoAttributeValue<'a> for bool {
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Bool(self)
-    }
-}
-impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
-    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
-        use bumpalo::core_alloc::fmt::Write;
-        let mut str_buf = bumpalo::collections::String::new_in(bump);
-        str_buf.write_fmt(self).unwrap();
-        AttributeValue::Text(str_buf.into_bump_str())
-    }
-}

+ 11 - 12
packages/core/src/fragment.rs

@@ -26,17 +26,16 @@ use crate::innerlude::*;
 ///
 /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
 #[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>(_cx: Scope<'a, FragmentProps<'a>>) -> Element {
-    // let children = cx.props.0.as_ref().unwrap();
-    todo!()
-    // Ok(VNode {
-    //     key: children.key.clone(),
-    //     parent: children.parent.clone(),
-    //     template: children.template,
-    //     root_ids: children.root_ids,
-    //     dynamic_nodes: children.dynamic_nodes,
-    //     dynamic_attrs: children.dynamic_attrs,
-    // })
+pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
+    let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?;
+    Ok(VNode {
+        key: children.key.clone(),
+        parent: children.parent.clone(),
+        template: children.template,
+        root_ids: children.root_ids,
+        dynamic_nodes: children.dynamic_nodes,
+        dynamic_attrs: children.dynamic_attrs,
+    })
 }
 
 pub struct FragmentProps<'a>(Element<'a>);
@@ -96,7 +95,7 @@ impl<'a> Properties for FragmentProps<'a> {
     type Builder = FragmentBuilder<'a, false>;
     const IS_STATIC: bool = false;
     fn builder() -> Self::Builder {
-        todo!()
+        FragmentBuilder(VNode::empty())
     }
     unsafe fn memoize(&self, _other: &Self) -> bool {
         false

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

@@ -17,7 +17,7 @@ use std::mem;
 /// A concrete type provider for closures that build [`VNode`] structures.
 ///
 /// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
-/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
+/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
 ///
 ///
 /// ```rust, ignore

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

@@ -9,7 +9,6 @@ mod diff;
 mod dirty_scope;
 mod error_boundary;
 mod events;
-mod factory;
 mod fragment;
 mod lazynodes;
 mod mutations;
@@ -25,10 +24,10 @@ pub(crate) mod innerlude {
     pub use crate::dirty_scope::*;
     pub use crate::error_boundary::*;
     pub use crate::events::*;
-    pub use crate::factory::RenderReturn;
     pub use crate::fragment::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
+    pub use crate::nodes::RenderReturn;
     pub use crate::nodes::*;
     pub use crate::properties::*;
     pub use crate::scheduler::*;
@@ -72,9 +71,9 @@ pub(crate) mod innerlude {
 
 pub use crate::innerlude::{
     fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
-    Fragment, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState,
-    Scoped, SuspenseBoundary, SuspenseContext, TaskId, Template, TemplateAttribute, TemplateNode,
-    VComponent, VNode, VText, VirtualDom,
+    Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope,
+    ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute,
+    TemplateNode, VComponent, VNode, VText, VirtualDom,
 };
 
 /// The purpose of this module is to alleviate imports of many common types

+ 93 - 19
packages/core/src/mutations.rs

@@ -4,9 +4,13 @@ use crate::{arena::ElementId, ScopeId, Template};
 
 /// A container for all the relevant steps to modify the Real DOM
 ///
-/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
-/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
-/// applied the edits.
+/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the
+/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes
+/// in the form of a [`Mutation`].
+///
+/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.
+///
+/// Templates, however, apply to all subtrees, not just target subtree.
 ///
 /// Mutations are the only link between the RealDOM and the VirtualDOM.
 #[derive(Debug, Default)]
@@ -32,17 +36,13 @@ impl<'a> Mutations<'a> {
     ///
     /// Used really only for testing
     pub fn santize(mut self) -> Self {
-        todo!()
-        // self.templates
-        //     .iter_mut()
-        //     .chain(self.dom_edits.iter_mut())
-        //     .for_each(|edit| match edit {
-        //         Mutation::LoadTemplate { name, .. } => *name = "template",
-        //         Mutation::SaveTemplate { name, .. } => *name = "template",
-        //         _ => {}
-        //     });
-
-        // self
+        for edit in self.edits.iter_mut() {
+            if let Mutation::LoadTemplate { name, .. } = edit {
+                *name = "template"
+            }
+        }
+
+        self
     }
 
     /// Push a new mutation into the dom_edits list
@@ -51,10 +51,10 @@ impl<'a> Mutations<'a> {
     }
 }
 
-/*
-each subtree has its own numbering scheme
-*/
-
+/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
+/// of the Dioxus VirtualDom.
+///
+/// These edits can be serialized and sent over the network or through any interface
 #[cfg_attr(
     feature = "serialize",
     derive(serde::Serialize, serde::Deserialize),
@@ -62,37 +62,111 @@ each subtree has its own numbering scheme
 )]
 #[derive(Debug, PartialEq, Eq)]
 pub enum Mutation<'a> {
+    /// Add these m children to the target element
+    AppendChildren {
+        /// The ID of the element being mounted to
+        id: ElementId,
+
+        /// The number of nodes on the stack
+        m: usize,
+    },
+
+    /// Assign the element at the given path the target ElementId.
+    ///
+    /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
+    /// element, hence the use of a single byte.
+    ///
+    ///
     AssignId {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
         path: &'static [u8],
+
+        /// The ID we're assigning to this element/placeholder.
+        ///
+        /// This will be used later to modify the element or replace it with another element.
         id: ElementId,
     },
 
+    /// Create an placeholder int he DOM that we will use later.
+    ///
+    /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
     CreatePlaceholder {
+        /// The ID we're assigning to this element/placeholder.
+        ///
+        /// This will be used later to modify the element or replace it with another element.
         id: ElementId,
     },
+
+    /// Create a node specifically for text with the given value
     CreateTextNode {
+        /// The text content of this text node
         value: &'a str,
+
+        /// The ID we're assigning to this specific text nodes
+        ///
+        /// This will be used later to modify the element or replace it with another element.
         id: ElementId,
     },
+
+    /// Hydrate an existing text node at the given path with the given text.
+    ///
+    /// Assign this text node the given ID since we will likely need to modify this text at a later point
     HydrateText {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
         path: &'static [u8],
+
+        /// The value of the textnode that we want to set the placeholder with
         value: &'a str,
+
+        /// The ID we're assigning to this specific text nodes
+        ///
+        /// This will be used later to modify the element or replace it with another element.
         id: ElementId,
     },
+
+    /// Load and clone an existing node from a template saved under that specific name
+    ///
+    /// Dioxus guarantees that the renderer will have already been provided the template.
+    /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
     LoadTemplate {
+        /// The "name" of the template. When paired with `rsx!`, this is autogenerated
         name: &'static str,
+
+        /// Which root are we loading from the template?
+        ///
+        /// The template is stored as a list of nodes. This index represents the position of that root
         index: usize,
+
+        /// The ID we're assigning to this element being loaded from the template
+        ///
+        /// This will be used later to move the element around in lists
         id: ElementId,
     },
 
-    // Take the current element and replace it with the element with the given id.
+    /// Replace the target element (given by its ID) with the topmost m nodes on the stack
     ReplaceWith {
+        /// The ID of the node we're going to replace with
         id: ElementId,
+
+        /// The number of nodes on the stack to use to replace
         m: usize,
     },
 
+    /// Replace an existing element in the template at the given path with the m nodes on the stack
     ReplacePlaceholder {
+        /// The path of the child of the topmost node on the stack
+        ///
+        /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+        /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
         path: &'static [u8],
+
+        /// The number of nodes on the stack to use to replace
         m: usize,
     },
 

+ 356 - 55
packages/core/src/nodes.rs

@@ -1,12 +1,32 @@
-use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState};
+use crate::{
+    any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
+};
 use bumpalo::boxed::Box as BumpBox;
+use bumpalo::Bump;
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
+    fmt::Arguments,
+    future::Future,
 };
 
 pub type TemplateId = &'static str;
 
+/// The actual state of the component's most recent computation
+///
+/// Because Dioxus accepts components in the form of `async fn(Scope) -> Result<VNode>`, we need to support both
+/// sync and async versions.
+///
+/// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor
+/// you might need to handle the case where there's no node immediately ready.
+pub enum RenderReturn<'a> {
+    /// A currently-available element
+    Sync(Element<'a>),
+
+    /// An ongoing future that will resolve to a [`Element`]
+    Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
+}
+
 /// A reference to a template along with any context needed to hydrate it
 ///
 /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
@@ -36,6 +56,7 @@ pub struct VNode<'a> {
 }
 
 impl<'a> VNode<'a> {
+    /// Create a template with no nodes that will be skipped over during diffing
     pub fn empty() -> Element<'a> {
         Ok(VNode {
             key: None,
@@ -44,7 +65,7 @@ impl<'a> VNode<'a> {
             dynamic_nodes: &[],
             dynamic_attrs: &[],
             template: Template {
-                id: "dioxus-empty",
+                name: "dioxus-empty",
                 roots: &[],
                 node_paths: &[],
                 attr_paths: &[],
@@ -52,6 +73,9 @@ impl<'a> VNode<'a> {
         })
     }
 
+    /// Load a dynamic root at the given index
+    ///
+    /// Returns [`None`] if the root is actually a static node (Element/Text)
     pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
         match &self.template.roots[idx] {
             TemplateNode::Element { .. } | TemplateNode::Text(_) => None,
@@ -62,52 +86,139 @@ impl<'a> VNode<'a> {
     }
 }
 
+/// A static layout of a UI tree that describes a set of dynamic and static nodes.
+///
+/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
+/// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use
+/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
+///
+/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
+/// ways, with the suggested approach being the unique code location (file, line, col, etc).
 #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
 pub struct Template<'a> {
-    pub id: &'a str,
+    /// The name of the template. This must be unique across your entire program for template diffing to work properly
+    ///
+    /// If two templates have the same name, it's likely that Dioxus will panic when diffing.
+    pub name: &'a str,
+
+    /// The list of template nodes that make up the template
+    ///
+    /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
     pub roots: &'a [TemplateNode<'a>],
+
+    /// The paths of each node relative to the root of the template.
+    ///
+    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+    /// topmost element, not the `roots` field.
     pub node_paths: &'a [&'a [u8]],
+
+    /// The paths of each dynamic attribute relative to the root of the template
+    ///
+    /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+    /// topmost element, not the `roots` field.
     pub attr_paths: &'a [&'a [u8]],
 }
 
-#[derive(Debug, Clone, Copy)]
+/// A statically known node in a layout.
+///
+/// This can be created at compile time, saving the VirtualDom time when diffing the tree
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
 pub enum TemplateNode<'a> {
+    /// An statically known element in the dom.
+    ///
+    /// In HTML this would be something like `<div id="123"> </div>`
     Element {
+        /// The name of the element
+        ///
+        /// IE for a div, it would be the string "div"
         tag: &'a str,
+
+        /// The namespace of the element
+        ///
+        /// In HTML, this would be a valid URI that defines a namespace for all elements below it
+        /// SVG is an example of this namespace
         namespace: Option<&'a str>,
+
+        /// A list of possibly dynamic attribues for this element
+        ///
+        /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
         attrs: &'a [TemplateAttribute<'a>],
+
+        /// A list of template nodes that define another set of template nodes
         children: &'a [TemplateNode<'a>],
-        inner_opt: bool,
     },
+
+    /// This template node is just a piece of static text
     Text(&'a str),
+
+    /// This template node is unknown, and needs to be created at runtime.
     Dynamic(usize),
+
+    /// This template node is known to be some text, but needs to be created at runtime
+    ///
+    /// This is separate from the pure Dynamic variant for various optimizations
     DynamicText(usize),
 }
 
+/// A node created at runtime
+///
+/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
 #[derive(Debug)]
 pub enum DynamicNode<'a> {
+    /// A component node
+    ///
+    /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
+    /// assigned scope are dynamic.
+    ///
+    /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
+    /// the render function at runtime
     Component(VComponent<'a>),
+
+    /// A text node
     Text(VText<'a>),
+
+    /// A placeholder
+    ///
+    /// Used by suspense when a node isn't ready and by fragments that don't render anything
+    ///
+    /// In code, this is just an ElementId whose initial value is set to 0 upon creation
     Placeholder(Cell<ElementId>),
+
+    /// A list of VNodes.
+    ///
+    /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
+    /// or iterators.
     Fragment(&'a [VNode<'a>]),
 }
 
-impl<'a> DynamicNode<'a> {
-    pub fn is_component(&self) -> bool {
-        matches!(self, DynamicNode::Component(_))
-    }
-    pub fn placeholder() -> Self {
+impl Default for DynamicNode<'_> {
+    fn default() -> Self {
         Self::Placeholder(Default::default())
     }
 }
 
+/// An instance of a child component
 pub struct VComponent<'a> {
+    /// The name of this component
     pub name: &'static str,
+
+    /// Are the props valid for the 'static lifetime?
+    ///
+    /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it.
+    ///
+    /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement
     pub static_props: bool,
+
+    /// The assigned Scope for this component
     pub scope: Cell<Option<ScopeId>>,
+
+    /// The function pointer of the component, known at compile time
+    ///
+    /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
     pub render_fn: *const (),
+
     pub(crate) props: Cell<Option<Box<dyn AnyProps<'a> + 'a>>>,
 }
 
@@ -121,65 +232,92 @@ impl<'a> std::fmt::Debug for VComponent<'a> {
     }
 }
 
+/// An instance of some text, mounted to the DOM
 #[derive(Debug)]
 pub struct VText<'a> {
-    pub id: Cell<ElementId>,
+    /// The actual text itself
     pub value: &'a str,
+
+    /// The ID of this node in the real DOM
+    pub id: Cell<ElementId>,
 }
 
-#[derive(Debug)]
+/// An attribute of the TemplateNode, created at compile time
+#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 pub enum TemplateAttribute<'a> {
+    /// This attribute is entirely known at compile time, enabling
     Static {
+        /// The name of this attribute.
+        ///
+        /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
         name: &'a str,
+
+        /// The value of this attribute, known at compile time
+        ///
+        /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
         value: &'a str,
+
+        /// The namespace of this attribute. Does not exist in the HTML spec
         namespace: Option<&'a str>,
-        volatile: bool,
     },
+
+    /// The attribute in this position is actually determined dynamically at runtime
+    ///
+    /// This is the index into the dynamic_attributes field on the container VNode
     Dynamic(usize),
 }
 
+/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
 #[derive(Debug)]
 pub struct Attribute<'a> {
+    /// The name of the attribute.
     pub name: &'a str,
+
+    /// The value of the attribute
     pub value: AttributeValue<'a>,
+
+    /// The namespace of the attribute.
+    ///
+    /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
     pub namespace: Option<&'static str>,
+
+    /// The element in the DOM that this attribute belongs to
     pub mounted_element: Cell<ElementId>,
+
+    /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
     pub volatile: bool,
 }
 
+/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
+///
+/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
+/// variant.
 pub enum AttributeValue<'a> {
+    /// Text attribute
     Text(&'a str),
-    Float(f32),
-    Int(i32),
+
+    /// A float
+    Float(f64),
+
+    /// Signed integer
+    Int(i64),
+
+    /// Boolean
     Bool(bool),
+
+    /// A listener, like "onclick"
     Listener(RefCell<Option<ListenerCb<'a>>>),
+
+    /// An arbitrary value that implements PartialEq and is static
     Any(BumpBox<'a, dyn AnyValue>),
+
+    /// A "none" value, resulting in the removal of an attribute from the dom
     None,
 }
 
 type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
 
-impl<'a> AttributeValue<'a> {
-    pub fn new_listener<T: 'static>(
-        cx: &'a ScopeState,
-        mut callback: impl FnMut(Event<T>) + 'a,
-    ) -> AttributeValue<'a> {
-        let boxed: BumpBox<'a, dyn FnMut(_) + 'a> = unsafe {
-            BumpBox::from_raw(cx.bump().alloc(move |event: Event<dyn Any>| {
-                if let Ok(data) = event.data.downcast::<T>() {
-                    callback(Event {
-                        propogates: event.propogates,
-                        data,
-                    })
-                }
-            }))
-        };
-
-        AttributeValue::Listener(RefCell::new(Some(boxed)))
-    }
-}
-
 impl<'a> std::fmt::Debug for AttributeValue<'a> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
@@ -208,20 +346,7 @@ impl<'a> PartialEq for AttributeValue<'a> {
     }
 }
 
-impl<'a> AttributeValue<'a> {
-    pub fn matches_type(&self, other: &'a AttributeValue<'a>) -> bool {
-        matches!(
-            (self, other),
-            (Self::Text(_), Self::Text(_))
-                | (Self::Float(_), Self::Float(_))
-                | (Self::Int(_), Self::Int(_))
-                | (Self::Bool(_), Self::Bool(_))
-                | (Self::Listener(_), Self::Listener(_))
-                | (Self::Any(_), Self::Any(_))
-        )
-    }
-}
-
+#[doc(hidden)]
 pub trait AnyValue {
     fn any_cmp(&self, other: &dyn AnyValue) -> bool;
     fn our_typeid(&self) -> TypeId;
@@ -241,9 +366,185 @@ impl<T: PartialEq + Any> AnyValue for T {
     }
 }
 
-#[test]
-fn what_are_the_sizes() {
-    dbg!(std::mem::size_of::<VNode>());
-    dbg!(std::mem::size_of::<Template>());
-    dbg!(std::mem::size_of::<TemplateNode>());
+#[doc(hidden)]
+pub trait ComponentReturn<'a, A = ()> {
+    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
+}
+
+impl<'a> ComponentReturn<'a> for Element<'a> {
+    fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
+        RenderReturn::Sync(self)
+    }
+}
+
+#[doc(hidden)]
+pub struct AsyncMarker;
+impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
+where
+    F: Future<Output = Element<'a>> + 'a,
+{
+    fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
+        let f: &mut dyn Future<Output = Element<'a>> = cx.bump().alloc(self);
+        RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
+    }
+}
+
+impl<'a> RenderReturn<'a> {
+    pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
+        unsafe { std::mem::transmute(self) }
+    }
+    pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
+        unsafe { std::mem::transmute(self) }
+    }
+}
+
+/// A trait that allows various items to be converted into a dynamic node for the rsx macro
+pub trait IntoDynNode<'a, A = ()> {
+    /// Consume this item along with a scopestate and produce a DynamicNode
+    ///
+    /// You can use the bump alloactor of the scopestate to creat the dynamic node
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
+}
+
+impl<'a> IntoDynNode<'a> for () {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::default()
+    }
+}
+impl<'a> IntoDynNode<'a> for VNode<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(_cx.bump().alloc([self]))
+    }
+}
+impl<'a> IntoDynNode<'a> for Element<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self {
+            Ok(val) => val.into_vnode(_cx),
+            _ => DynamicNode::default(),
+        }
+    }
+}
+
+impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self {
+            Some(val) => val.into_vnode(_cx),
+            None => DynamicNode::default(),
+        }
+    }
+}
+
+impl<'a> IntoDynNode<'a> for &Element<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        match self.as_ref() {
+            Ok(val) => val.clone().into_vnode(_cx),
+            _ => DynamicNode::default(),
+        }
+    }
+}
+
+impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
+    }
+}
+
+impl<'a> IntoDynNode<'_> for &'a str {
+    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+        cx.text_node(format_args!("{}", self))
+    }
+}
+
+impl IntoDynNode<'_> for String {
+    fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+        cx.text_node(format_args!("{}", self))
+    }
+}
+
+impl<'b> IntoDynNode<'b> for Arguments<'_> {
+    fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
+        cx.text_node(self)
+    }
+}
+
+impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
+    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+        DynamicNode::Fragment(_cx.bump().alloc([VNode {
+            parent: self.parent,
+            template: self.template,
+            root_ids: self.root_ids,
+            key: self.key,
+            dynamic_nodes: self.dynamic_nodes,
+            dynamic_attrs: self.dynamic_attrs,
+        }]))
+    }
+}
+
+pub trait IntoTemplate<'a> {
+    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
+}
+impl<'a> IntoTemplate<'a> for VNode<'a> {
+    fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+        self
+    }
+}
+impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
+    fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
+        self.call(cx)
+    }
+}
+
+// Note that we're using the E as a generic but this is never crafted anyways.
+#[doc(hidden)]
+pub struct FromNodeIterator;
+impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
+where
+    T: Iterator<Item = I>,
+    I: IntoTemplate<'a>,
+{
+    fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+        let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
+
+        nodes.extend(self.into_iter().map(|node| node.into_template(cx)));
+
+        match nodes.into_bump_slice() {
+            children if children.is_empty() => DynamicNode::default(),
+            children => DynamicNode::Fragment(children),
+        }
+    }
+}
+
+/// A value that can be converted into an attribute value
+pub trait IntoAttributeValue<'a> {
+    /// Convert into an attribute value
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
+}
+
+impl<'a> IntoAttributeValue<'a> for &'a str {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Text(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for f64 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Float(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for i64 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Int(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for bool {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Bool(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
+        use bumpalo::core_alloc::fmt::Write;
+        let mut str_buf = bumpalo::collections::String::new_in(bump);
+        str_buf.write_fmt(self).unwrap();
+        AttributeValue::Text(str_buf.into_bump_str())
+    }
 }

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

@@ -8,13 +8,12 @@ use std::{
     rc::Rc,
 };
 
+/// An ID representing an ongoing suspended component
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-pub struct SuspenseId(pub usize);
+pub(crate) struct SuspenseId(pub usize);
 
-pub type SuspenseContext = Rc<SuspenseBoundary>;
-
-/// Essentially a fiber in React
-pub struct SuspenseBoundary {
+/// A boundary in the VirtualDom that captures all suspended components below it
+pub struct SuspenseContext {
     pub(crate) id: ScopeId,
     pub(crate) waiting_on: RefCell<HashSet<SuspenseId>>,
     pub(crate) mutations: RefCell<Mutations<'static>>,
@@ -22,7 +21,8 @@ pub struct SuspenseBoundary {
     pub(crate) created_on_stack: Cell<usize>,
 }
 
-impl SuspenseBoundary {
+impl SuspenseContext {
+    /// Create a new boundary for suspense
     pub fn new(id: ScopeId) -> Self {
         Self {
             id,
@@ -35,11 +35,11 @@ impl SuspenseBoundary {
 }
 
 pub(crate) struct SuspenseLeaf {
-    pub id: SuspenseId,
-    pub scope_id: ScopeId,
-    pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
-    pub notified: Cell<bool>,
-    pub task: *mut dyn Future<Output = Element<'static>>,
+    pub(crate) id: SuspenseId,
+    pub(crate) scope_id: ScopeId,
+    pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+    pub(crate) notified: Cell<bool>,
+    pub(crate) task: *mut dyn Future<Output = Element<'static>>,
 }
 
 impl RcWake for SuspenseLeaf {

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

@@ -4,6 +4,10 @@ use std::cell::RefCell;
 use std::future::Future;
 use std::{pin::Pin, rc::Rc};
 
+/// A task's unique identifier.
+///
+/// `TaskId` is a `usize` that is unique across the entire VirtualDOM and across time. TaskIDs will never be reused
+/// once a Task has been completed.
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct TaskId(pub usize);

+ 14 - 7
packages/core/src/scheduler/wait.rs

@@ -2,9 +2,9 @@ use futures_util::FutureExt;
 use std::task::{Context, Poll};
 
 use crate::{
-    factory::RenderReturn,
     innerlude::{Mutation, Mutations, SuspenseContext},
-    TaskId, VNode, VirtualDom,
+    nodes::RenderReturn,
+    ScopeId, TaskId, VNode, VirtualDom,
 };
 
 use super::{waker::RcWake, SuspenseId};
@@ -14,7 +14,7 @@ impl VirtualDom {
     ///
     /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
     /// queue
-    pub fn handle_task_wakeup(&mut self, id: TaskId) {
+    pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
         let mut tasks = self.scheduler.tasks.borrow_mut();
         let task = &tasks[id.0];
 
@@ -31,7 +31,15 @@ impl VirtualDom {
         }
     }
 
-    pub fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
+    pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> &'a SuspenseContext {
+        let ct = self.scopes[id.0]
+            .consume_context::<SuspenseContext>()
+            .unwrap();
+
+        unsafe { &*(ct as *const SuspenseContext) }
+    }
+
+    pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
         println!("suspense notified");
 
         let leaf = self
@@ -56,9 +64,8 @@ impl VirtualDom {
         // we should attach them to that component and then render its children
         // continue rendering the tree until we hit yet another suspended component
         if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
-            let fiber = &self.scopes[leaf.scope_id.0]
-                .consume_context::<SuspenseContext>()
-                .unwrap();
+            // safety: we're not going to modify the suspense context but we don't want to make a clone of it
+            let fiber = self.acquire_suspense_boundary(leaf.scope_id);
 
             println!("ready pool");
 

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

@@ -1,9 +1,9 @@
 use crate::{
     any_props::AnyProps,
     bump_frame::BumpFrame,
-    factory::RenderReturn,
     innerlude::DirtyScope,
     innerlude::{SuspenseId, SuspenseLeaf},
+    nodes::RenderReturn,
     scheduler::RcWake,
     scopes::{ScopeId, ScopeState},
     virtual_dom::VirtualDom,
@@ -18,7 +18,7 @@ use std::{
 };
 
 impl VirtualDom {
-    pub(super) fn new_scope(&mut self, props: Box<dyn AnyProps<'static>>) -> &mut ScopeState {
+    pub(super) fn new_scope(&mut self, props: Box<dyn AnyProps<'static>>) -> &ScopeState {
         let parent = self.acquire_current_scope_raw();
         let entry = self.scopes.vacant_entry();
         let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
@@ -86,8 +86,6 @@ impl VirtualDom {
     }
 
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
-        println!("Running scope {:?}", scope_id);
-
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners

+ 66 - 37
packages/core/src/scopes.rs

@@ -3,11 +3,11 @@ use crate::{
     any_props::VProps,
     arena::ElementId,
     bump_frame::BumpFrame,
-    factory::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
     innerlude::{DynamicNode, EventHandler, VComponent, VText},
     innerlude::{Scheduler, SchedulerMsg},
     lazynodes::LazyNodes,
-    Attribute, Element, Properties, TaskId,
+    nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
+    Attribute, AttributeValue, Element, Event, Properties, TaskId,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
 use std::{
@@ -23,7 +23,7 @@ use std::{
 /// A wrapper around the [`Scoped`] object that contains a reference to the [`ScopeState`] and properties for a given
 /// component.
 ///
-/// The [`Scope`] is your handle to the [`VirtualDom`] and the component state. Every component is given its own
+/// The [`Scope`] is your handle to the [`crate::VirtualDom`] and the component state. Every component is given its own
 /// [`ScopeState`] and merged with its properties to create a [`Scoped`].
 ///
 /// The [`Scope`] handle specifically exists to provide a stable reference to these items for the lifetime of the
@@ -56,8 +56,9 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
 
 /// A component's unique identifier.
 ///
-/// `ScopeId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`ScopeID`]s will never be reused
-/// once a component has been unmounted.
+/// `ScopeId` is a `usize` that acts a key for the internal slab of Scopes. This means that the key is not unqiue across
+/// time. We do try and guarantee that between calls to `wait_for_work`, no ScopeIds will be recycled in order to give
+/// time for any logic that relies on these IDs to properly update.
 #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
 pub struct ScopeId(pub usize);
@@ -177,9 +178,9 @@ impl<'src> ScopeState {
         self.height
     }
 
-    /// Get the Parent of this [`Scope`] within this Dioxus [`VirtualDom`].
+    /// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
     ///
-    /// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
+    /// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
     ///
     /// The base component will not have a parent, and will return `None`.
     ///
@@ -198,9 +199,9 @@ impl<'src> ScopeState {
         self.parent.map(|p| unsafe { &*p }.id)
     }
 
-    /// Get the ID of this Scope within this Dioxus [`VirtualDom`].
+    /// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
     ///
-    /// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
+    /// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
     ///
     /// # Example
     ///
@@ -217,7 +218,7 @@ impl<'src> ScopeState {
 
     /// Create a subscription that schedules a future render for the reference component
     ///
-    /// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
+    /// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
     pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
         let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
         Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
@@ -240,7 +241,7 @@ impl<'src> ScopeState {
 
     /// 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.
+    /// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
     pub fn needs_update_any(&self, id: ScopeId) {
         self.tasks
             .sender
@@ -249,20 +250,17 @@ impl<'src> ScopeState {
     }
 
     /// Return any context of type T if it exists on this scope
-    pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
-        self.shared_contexts
-            .borrow()
-            .get(&TypeId::of::<T>())
-            .and_then(|shared| shared.downcast_ref::<T>())
-            .cloned()
+    pub fn has_context<T: 'static>(&self) -> Option<&T> {
+        let contextex = self.shared_contexts.borrow();
+        let val = contextex.get(&TypeId::of::<T>())?;
+        let as_concrete = val.downcast_ref::<T>()? as *const T;
+        Some(unsafe { &*as_concrete })
     }
 
     /// Try to retrieve a shared state with type `T` from any parent scope.
     ///
-    /// The state will be cloned and returned, if it exists.
-    ///
-    /// We recommend wrapping the state in an `Rc` or `Arc` to avoid deep cloning.
-    pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
+    /// To release the borrow, use `cloned` if the context is clone.
+    pub fn consume_context<T: 'static>(&self) -> Option<&T> {
         if let Some(this_ctx) = self.has_context() {
             return Some(this_ctx);
         }
@@ -272,26 +270,24 @@ impl<'src> ScopeState {
             // safety: all parent pointers are valid thanks to the bump arena
             let parent = unsafe { &*parent_ptr };
             if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
-                return Some(
-                    shared
-                        .downcast_ref::<T>()
-                        .expect("Context of type T should exist")
-                        .clone(),
-                );
+                let as_concrete = shared.downcast_ref::<T>()? as *const T;
+                return Some(unsafe { &*as_concrete });
             }
             search_parent = parent.parent;
         }
         None
     }
 
-    /// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
+    /// Expose state to children further down the [`crate::VirtualDom`] Tree. Does not require `clone` on the context,
+    /// though we do recommend it.
     ///
     /// 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_context` and `use_consume_context` instead.
     ///
-    /// When the component is dropped, so is the context. Be aware of this behavior when consuming
-    /// the context via Rc/Weak.
+    /// If a state is provided that already exists, the new value will not be inserted. Instead, this method will
+    /// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular
+    /// behavior might change in the future.
     ///
     /// # Example
     ///
@@ -308,12 +304,20 @@ impl<'src> ScopeState {
     ///     render!(div { "hello {state.0}" })
     /// }
     /// ```
-    pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
-        self.shared_contexts
-            .borrow_mut()
-            .insert(TypeId::of::<T>(), Box::new(value.clone()))
-            .and_then(|f| f.downcast::<T>().ok());
-        value
+    pub fn provide_context<T: 'static>(&self, value: T) -> &T {
+        let mut contexts = self.shared_contexts.borrow_mut();
+
+        let any = match contexts.get(&TypeId::of::<T>()) {
+            Some(item) => item.downcast_ref::<T>().unwrap() as *const T,
+            None => {
+                let boxed = Box::new(value);
+                let boxed_ptr = boxed.as_ref() as *const T;
+                contexts.insert(TypeId::of::<T>(), boxed);
+                boxed_ptr
+            }
+        };
+
+        unsafe { &*any }
     }
 
     /// Pushes the future onto the poll queue to be polled after the component renders.
@@ -349,7 +353,7 @@ impl<'src> ScopeState {
         self.tasks.remove(id);
     }
 
-    /// Take a lazy [`VNode`] structure and actually build it with the context of the efficient [`Bump`] allocator.
+    /// Take a lazy [`crate::VNode`] structure and actually build it with the context of the efficient [`bumpalo::Bump`] allocator.
     ///
     /// ## Example
     ///
@@ -457,6 +461,31 @@ impl<'src> ScopeState {
         EventHandler { callback }
     }
 
+    /// Create a new [`AttributeValue`] with the listener variant from a callback
+    ///
+    /// The callback must be confined to the lifetime of the ScopeState
+    pub fn listener<T: 'static>(
+        &'src self,
+        mut callback: impl FnMut(Event<T>) + 'src,
+    ) -> AttributeValue<'src> {
+        // safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
+        // This is the suggested way to build a bumpbox
+        //
+        // In theory, we could just use regular boxes
+        let boxed: BumpBox<'src, dyn FnMut(_) + 'src> = unsafe {
+            BumpBox::from_raw(self.bump().alloc(move |event: Event<dyn Any>| {
+                if let Ok(data) = event.data.downcast::<T>() {
+                    callback(Event {
+                        propogates: event.propogates,
+                        data,
+                    })
+                }
+            }))
+        };
+
+        AttributeValue::Listener(RefCell::new(Some(boxed)))
+    }
+
     /// Store a value between renders. The foundational hook for all other hooks.
     ///
     /// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.

+ 9 - 5
packages/core/src/virtual_dom.rs

@@ -5,11 +5,11 @@
 use crate::{
     any_props::VProps,
     arena::{ElementId, ElementRef},
-    factory::RenderReturn,
     innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
     mutations::Mutation,
+    nodes::RenderReturn,
     nodes::{Template, TemplateId},
-    scheduler::{SuspenseBoundary, SuspenseId},
+    scheduler::SuspenseId,
     scopes::{ScopeId, ScopeState},
     AttributeValue, Element, Event, Scope, SuspenseContext,
 };
@@ -249,10 +249,10 @@ impl VirtualDom {
         // This could be unexpected, so we might rethink this behavior later
         //
         // We *could* just panic if the suspense boundary is not found
-        root.provide_context(Rc::new(SuspenseBoundary::new(ScopeId(0))));
+        root.provide_context(SuspenseContext::new(ScopeId(0)));
 
         // Unlike react, we provide a default error boundary that just renders the error as a string
-        root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
+        root.provide_context(ErrorBoundary::new(ScopeId(0)));
 
         // the root element is always given element ID 0 since it's the container for the entire tree
         dom.elements.insert(ElementRef::null());
@@ -489,7 +489,11 @@ impl VirtualDom {
         match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
             // Rebuilding implies we append the created elements to the root
             RenderReturn::Sync(Ok(node)) => {
-                let _m = self.create_scope(ScopeId(0), node);
+                let m = self.create_scope(ScopeId(0), node);
+                self.mutations.edits.push(Mutation::AppendChildren {
+                    id: ElementId(0),
+                    m,
+                });
             }
             // If an error occurs, we should try to render the default error component and context where the error occured
             RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),

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

@@ -26,7 +26,7 @@ fn attrs_cycle() {
         dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            AppendChildren { m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
         ]
     );
 

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

@@ -9,7 +9,7 @@ fn bool_test() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },
-            AppendChildren { m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
         ]
     )
 }

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

@@ -16,7 +16,7 @@ fn test_borrowed_state() {
             HydrateText { path: &[0,], value: "Hello w1!", id: ElementId(4,) },
             ReplacePlaceholder { path: &[1,], m: 1 },
             ReplacePlaceholder { path: &[0,], m: 1 },
-            AppendChildren { m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
         ]
     )
 }

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

@@ -23,7 +23,7 @@ fn state_shares() {
         dom.rebuild().santize().edits,
         [
             CreateTextNode { value: "Value is 0", id: ElementId(1,) },
-            AppendChildren { m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
         ]
     );
 

+ 69 - 89
packages/core/tests/create_dom.rs

@@ -22,25 +22,13 @@ fn test_original_diff() {
     });
 
     let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.templates,
-        [
-            // create template
-            CreateElement { name: "div" },
-            CreateElement { name: "div" },
-            CreateStaticText { value: "Hello, world!" },
-            AppendChildren { m: 1 },
-            AppendChildren { m: 1 },
-            SaveTemplate { name: "template", m: 1 },
-        ]
-    );
 
     assert_eq!(
         edits.edits,
         [
             // add to root
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            AppendChildren { m: 1 }
+            AppendChildren { m: 1, id: ElementId(0) }
         ]
     )
 }
@@ -65,28 +53,31 @@ fn create() {
         })
     });
 
-    let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.templates,
-        [
-            // create template
-            CreateElement { name: "div" },
-            CreateElement { name: "div" },
-            CreateStaticText { value: "Hello, world!" },
-            CreateElement { name: "div" },
-            CreateElement { name: "div" },
-            CreateStaticPlaceholder {},
-            AppendChildren { m: 1 },
-            AppendChildren { m: 1 },
-            AppendChildren { m: 2 },
-            AppendChildren { m: 1 },
-            SaveTemplate { name: "template", m: 1 },
-            // The fragment child template
-            CreateStaticText { value: "hello" },
-            CreateStaticText { value: "world" },
-            SaveTemplate { name: "template", m: 2 },
-        ]
-    );
+    let _edits = dom.rebuild().santize();
+
+    // todo: we don't test template mutations anymore since the templates are passed along
+
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create template
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello, world!" },
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateStaticPlaceholder {},
+    //         AppendChildren { m: 1 },
+    //         AppendChildren { m: 1 },
+    //         AppendChildren { m: 2 },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 1 },
+    //         // The fragment child template
+    //         CreateStaticText { value: "hello" },
+    //         CreateStaticText { value: "world" },
+    //         SaveTemplate { name: "template", m: 2 },
+    //     ]
+    // );
 }
 
 #[test]
@@ -97,17 +88,19 @@ fn create_list() {
         })
     });
 
-    let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.templates,
-        [
-            // create template
-            CreateElement { name: "div" },
-            CreateStaticText { value: "hello" },
-            AppendChildren { m: 1 },
-            SaveTemplate { name: "template", m: 1 }
-        ]
-    );
+    let _edits = dom.rebuild().santize();
+
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create template
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "hello" },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 1 }
+    //     ]
+    // );
 }
 
 #[test]
@@ -122,18 +115,20 @@ fn create_simple() {
     });
 
     let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.templates,
-        [
-            // create template
-            CreateElement { name: "div" },
-            CreateElement { name: "div" },
-            CreateElement { name: "div" },
-            CreateElement { name: "div" },
-            // add to root
-            SaveTemplate { name: "template", m: 4 }
-        ]
-    );
+
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create template
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "div" },
+    //         // add to root
+    //         SaveTemplate { name: "template", m: 4 }
+    //     ]
+    // );
 }
 #[test]
 fn create_components() {
@@ -158,26 +153,9 @@ fn create_components() {
         })
     }
 
-    let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.templates,
-        [
-            // The "child" template
-            CreateElement { name: "h1" },
-            CreateElement { name: "div" },
-            CreateStaticPlaceholder {},
-            AppendChildren { m: 1 },
-            CreateElement { name: "p" },
-            SaveTemplate { name: "template", m: 3 },
-            // Sub template for component children
-            CreateStaticText { value: "abc1" },
-            SaveTemplate { name: "template", m: 1 },
-            CreateStaticText { value: "abc2" },
-            SaveTemplate { name: "template", m: 1 },
-            CreateStaticText { value: "abc3" },
-            SaveTemplate { name: "template", m: 1 }
-        ]
-    );
+    let _edits = dom.rebuild().santize();
+
+    // todo: test this
 }
 
 #[test]
@@ -195,23 +173,25 @@ fn anchors() {
 
     // note that the template under "false" doesn't show up since it's not loaded
     let edits = dom.rebuild().santize();
-    assert_eq!(
-        edits.templates,
-        [
-            // create each template
-            CreateElement { name: "div" },
-            CreateStaticText { value: "hello" },
-            AppendChildren { m: 1 },
-            SaveTemplate { m: 1, name: "template" },
-        ]
-    );
+
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // create each template
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "hello" },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { m: 1, name: "template" },
+    //     ]
+    // );
 
     assert_eq!(
         edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             CreatePlaceholder { id: ElementId(2) },
-            AppendChildren { m: 2 }
+            AppendChildren { m: 2, id: ElementId(0) }
         ]
     )
 }

+ 19 - 16
packages/core/tests/create_element.rs

@@ -1,4 +1,4 @@
-use dioxus::core::Mutation::*;
+// use dioxus::core::Mutation::*;
 use dioxus::prelude::*;
 
 #[test]
@@ -11,19 +11,22 @@ fn multiroot() {
         })
     });
 
-    assert_eq!(
-        dom.rebuild().santize().templates,
-        [
-            CreateElement { name: "div" },
-            CreateStaticText { value: "Hello a" },
-            AppendChildren { m: 1 },
-            CreateElement { name: "div" },
-            CreateStaticText { value: "Hello b" },
-            AppendChildren { m: 1 },
-            CreateElement { name: "div" },
-            CreateStaticText { value: "Hello c" },
-            AppendChildren { m: 1 },
-            SaveTemplate { name: "template", m: 3 }
-        ]
-    )
+    // note: we dont test template edits anymore
+    let _templates = dom.rebuild().santize().templates;
+
+    // assert_eq!(
+    //     dom.rebuild().santize().templates,
+    //     [
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello a" },
+    //         AppendChildren { m: 1 },
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello b" },
+    //         AppendChildren { m: 1 },
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Hello c" },
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 3 }
+    //     ]
+    // )
 }

+ 5 - 5
packages/core/tests/create_fragments.rs

@@ -17,7 +17,7 @@ fn empty_fragment_creates_nothing() {
         edits.edits,
         [
             CreatePlaceholder { id: ElementId(1) },
-            AppendChildren { m: 1 }
+            AppendChildren { id: ElementId(0), m: 1 }
         ]
     );
 }
@@ -33,7 +33,7 @@ fn root_fragments_work() {
 
     assert_eq!(
         vdom.rebuild().edits.last().unwrap(),
-        &AppendChildren { m: 2 }
+        &AppendChildren { id: ElementId(0), m: 2 }
     );
 }
 
@@ -60,7 +60,7 @@ fn fragments_nested() {
 
     assert_eq!(
         vdom.rebuild().edits.last().unwrap(),
-        &AppendChildren { m: 8 }
+        &AppendChildren { id: ElementId(0), m: 8 }
     );
 }
 
@@ -85,7 +85,7 @@ fn fragments_across_components() {
 
     assert_eq!(
         VirtualDom::new(app).rebuild().edits.last().unwrap(),
-        &AppendChildren { m: 8 }
+        &AppendChildren { id: ElementId(0), m: 8 }
     );
 }
 
@@ -99,6 +99,6 @@ fn list_fragments() {
     }
     assert_eq!(
         VirtualDom::new(app).rebuild().edits.last().unwrap(),
-        &AppendChildren { m: 7 }
+        &AppendChildren { id: ElementId(0), m: 7 }
     );
 }

+ 24 - 23
packages/core/tests/create_lists.rs

@@ -27,28 +27,29 @@ fn list_renders() {
 
     let edits = dom.rebuild().santize();
 
-    assert_eq!(
-        edits.templates,
-        [
-            // Create the outer div
-            CreateElement { name: "div" },
-            // todo: since this is the only child, we should just use
-            // append when modify the values (IE no need for a placeholder)
-            CreateStaticPlaceholder,
-            AppendChildren { m: 1 },
-            SaveTemplate { name: "template", m: 1 },
-            // Create the inner template div
-            CreateElement { name: "div" },
-            CreateElement { name: "h1" },
-            CreateStaticText { value: "hello world! " },
-            AppendChildren { m: 1 },
-            CreateElement { name: "p" },
-            CreateTextPlaceholder,
-            AppendChildren { m: 1 },
-            AppendChildren { m: 2 },
-            SaveTemplate { name: "template", m: 1 }
-        ],
-    );
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     edits.templates,
+    //     [
+    //         // Create the outer div
+    //         CreateElement { name: "div" },
+    //         // todo: since this is the only child, we should just use
+    //         // append when modify the values (IE no need for a placeholder)
+    //         CreateStaticPlaceholder,
+    //         AppendChildren { m: 1 },
+    //         SaveTemplate { name: "template", m: 1 },
+    //         // Create the inner template div
+    //         CreateElement { name: "div" },
+    //         CreateElement { name: "h1" },
+    //         CreateStaticText { value: "hello world! " },
+    //         AppendChildren { m: 1 },
+    //         CreateElement { name: "p" },
+    //         CreateTextPlaceholder,
+    //         AppendChildren { m: 1 },
+    //         AppendChildren { m: 2 },
+    //         SaveTemplate { name: "template", m: 1 }
+    //     ],
+    // );
 
     assert_eq!(
         edits.edits,
@@ -65,7 +66,7 @@ fn list_renders() {
             // Replace the 0th childn on the div with the 3 templates on the stack
             ReplacePlaceholder { m: 3, path: &[0] },
             // Append the container div to the dom
-            AppendChildren { m: 1 }
+            AppendChildren { m: 1, id: ElementId(0) }
         ],
     )
 }

+ 4 - 4
packages/core/tests/create_passthru.rs

@@ -29,7 +29,7 @@ fn nested_passthru_creates() {
         edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
-            AppendChildren { m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
         ]
     )
 }
@@ -74,7 +74,7 @@ fn nested_passthru_creates_add() {
             LoadTemplate { name: "template", index: 0, id: ElementId(3) },
             // load div that contains 4
             LoadTemplate { name: "template", index: 1, id: ElementId(4) },
-            AppendChildren { m: 4 },
+            AppendChildren { id: ElementId(0), m: 4 },
         ]
     );
 }
@@ -92,7 +92,7 @@ fn dynamic_node_as_root() {
     let edits = dom.rebuild().santize();
 
     // Since the roots were all dynamic, they should not cause any template muations
-    assert_eq!(edits.templates, []);
+    assert!(edits.templates.is_empty());
 
     // The root node is text, so we just create it on the spot
     assert_eq!(
@@ -100,7 +100,7 @@ fn dynamic_node_as_root() {
         [
             CreateTextNode { value: "123", id: ElementId(1) },
             CreateTextNode { value: "456", id: ElementId(2) },
-            AppendChildren { m: 2 }
+            AppendChildren { id: ElementId(0), m: 2 }
         ]
     )
 }

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

@@ -17,7 +17,7 @@ fn cycling_elements() {
         edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
-            AppendChildren { m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
         ]
     );
 

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

@@ -69,7 +69,7 @@ fn component_swap() {
             LoadTemplate { name: "template", index: 0, id: ElementId(4) },
             ReplacePlaceholder { path: &[1], m: 3 },
             LoadTemplate { name: "template", index: 0, id: ElementId(5) },
-            AppendChildren { m: 2 }
+            AppendChildren { m: 2, id: ElementId(0) }
         ]
     );
 

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

@@ -33,7 +33,7 @@ fn keyed_diffing_out_of_order() {
             LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
             LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
             LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
-            AppendChildren { m: 10 },
+            AppendChildren { m: 10, id: ElementId(0) },
         ]
     );
 

+ 5 - 5
packages/core/tests/diff_unkeyed_list.rs

@@ -21,7 +21,7 @@ fn list_creates_one_by_one() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             AssignId { path: &[0], id: ElementId(2,) },
-            AppendChildren { m: 1 },
+            AppendChildren { id: ElementId(0), m: 1 },
         ]
     );
 
@@ -100,7 +100,7 @@ fn removes_one_by_one() {
             // replace the placeholder in the template with the 3 templates on the stack
             ReplacePlaceholder { m: 3, path: &[0] },
             // Mount the div
-            AppendChildren { m: 1 }
+            AppendChildren { id: ElementId(0), m: 1 }
         ]
     );
 
@@ -165,7 +165,7 @@ fn list_shrink_multiroot() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             AssignId { path: &[0,], id: ElementId(2,) },
-            AppendChildren { m: 1 }
+            AppendChildren { id: ElementId(0), m: 1 }
         ]
     );
 
@@ -244,7 +244,7 @@ fn removes_one_by_one_multiroot() {
             //
             ReplacePlaceholder { path: &[0], m: 6 },
             //
-            AppendChildren { m: 1 }
+            AppendChildren { id: ElementId(0), m: 1 }
         ]
     );
 
@@ -320,7 +320,7 @@ fn remove_many() {
         edits.edits,
         [
             CreatePlaceholder { id: ElementId(1,) },
-            AppendChildren { m: 1 },
+            AppendChildren { id: ElementId(0), m: 1 },
         ]
     );
 

+ 1 - 37
packages/core/tests/kitchen_sink.rs

@@ -29,42 +29,6 @@ fn dual_stream() {
     let edits = dom.rebuild().santize();
 
     use Mutation::*;
-    assert_eq!(
-        edits.templates,
-        [
-            CreateElement { name: "div" },
-            SetStaticAttribute { name: "class", value: "asd", ns: None },
-            CreateElement { name: "div" },
-            CreateTextPlaceholder,
-            AppendChildren { m: 1 },
-            CreateElement { name: "div" },
-            CreateElement { name: "h1" },
-            CreateStaticText { value: "var" },
-            AppendChildren { m: 1 },
-            CreateElement { name: "p" },
-            CreateStaticText { value: "you're great!" },
-            AppendChildren { m: 1 },
-            CreateElement { name: "div" },
-            SetStaticAttribute { name: "background-color", value: "red", ns: Some("style") },
-            CreateElement { name: "h1" },
-            CreateStaticText { value: "var" },
-            AppendChildren { m: 1 },
-            CreateElement { name: "div" },
-            CreateElement { name: "b" },
-            CreateStaticText { value: "asd" },
-            AppendChildren { m: 1 },
-            CreateStaticText { value: "not great" },
-            AppendChildren { m: 2 },
-            AppendChildren { m: 2 },
-            CreateElement { name: "p" },
-            CreateStaticText { value: "you're great!" },
-            AppendChildren { m: 1 },
-            AppendChildren { m: 4 },
-            AppendChildren { m: 2 },
-            SaveTemplate { name: "template", m: 1 }
-        ],
-    );
-
     assert_eq!(
         edits.edits,
         [
@@ -72,7 +36,7 @@ fn dual_stream() {
             SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
             NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) },
             HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
-            AppendChildren { m: 1 }
+            AppendChildren { id: ElementId(0), m: 1 }
         ],
     );
 }

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

@@ -32,7 +32,7 @@ fn manual_diffing() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(3) },
             HydrateText { path: &[0], value: "goodbye", id: ElementId(4) },
-            AppendChildren { m: 1 }
+            AppendChildren { m: 1, id: ElementId(0) }
         ]
     );
 }

+ 14 - 13
packages/core/tests/suspense.rs

@@ -1,5 +1,5 @@
 use dioxus::core::ElementId;
-use dioxus::core::{Mutation::*, SuspenseBoundary};
+use dioxus::core::{Mutation::*, SuspenseContext};
 use dioxus::prelude::*;
 use dioxus_core::SuspenseContext;
 use std::future::IntoFuture;
@@ -12,16 +12,17 @@ async fn it_works() {
     let mutations = dom.rebuild().santize();
 
     // We should at least get the top-level template in before pausing for the children
-    assert_eq!(
-        mutations.templates,
-        [
-            CreateElement { name: "div" },
-            CreateStaticText { value: "Waiting for child..." },
-            CreateStaticPlaceholder,
-            AppendChildren { m: 2 },
-            SaveTemplate { name: "template", m: 1 }
-        ]
-    );
+    // note: we dont test template edits anymore
+    // assert_eq!(
+    //     mutations.templates,
+    //     [
+    //         CreateElement { name: "div" },
+    //         CreateStaticText { value: "Waiting for child..." },
+    //         CreateStaticPlaceholder,
+    //         AppendChildren { m: 2 },
+    //         SaveTemplate { name: "template", m: 1 }
+    //     ]
+    // );
 
     // And we should load it in and assign the placeholder properly
     assert_eq!(
@@ -31,7 +32,7 @@ async fn it_works() {
             // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
             // can we even?
             AssignId { path: &[1], id: ElementId(3) },
-            AppendChildren { m: 1 },
+            AppendChildren { m: 1, id: ElementId(0) },
         ]
     );
 
@@ -50,7 +51,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn suspense_boundary(cx: Scope) -> Element {
-    cx.use_hook(|| cx.provide_context(Rc::new(SuspenseBoundary::new(cx.scope_id()))));
+    cx.use_hook(|| cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))));
 
     // Ensure the right types are found
     cx.has_context::<SuspenseContext>().unwrap();

+ 5 - 2
packages/hooks/src/lib.rs

@@ -21,14 +21,17 @@ macro_rules! to_owned {
     )*}
 }
 
+mod usecontext;
+pub use usecontext::*;
+
 mod usestate;
 pub use usestate::{use_state, UseState};
 
 mod useref;
 pub use useref::*;
 
-mod use_shared_state;
-pub use use_shared_state::*;
+// mod use_shared_state;
+// pub use use_shared_state::*;
 
 mod usecoroutine;
 pub use usecoroutine::*;

+ 3 - 2
packages/hooks/src/use_shared_state.rs

@@ -63,7 +63,7 @@ impl<T> ProvidedStateInner<T> {
 pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<UseSharedState<T>> {
     let state = cx.use_hook(|| {
         let scope_id = cx.scope_id();
-        let root = cx.consume_context::<ProvidedState<T>>();
+        let root = cx.consume_context::<ProvidedState<T>>().cloned();
 
         if let Some(root) = root.as_ref() {
             root.borrow_mut().consumers.insert(scope_id);
@@ -179,6 +179,7 @@ pub fn use_context_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T)
             notify_any: cx.schedule_update_any(),
             consumers: HashSet::new(),
         }));
-        cx.provide_context(state)
+
+        cx.provide_context(state);
     });
 }

+ 17 - 0
packages/hooks/src/usecontext.rs

@@ -0,0 +1,17 @@
+use dioxus_core::ScopeState;
+
+/// Consume some context in the tree
+pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<&T> {
+    match *cx.use_hook(|| cx.consume_context::<T>().map(|t| t as *const T)) {
+        Some(res) => Some(unsafe { &*res }),
+        None => None,
+    }
+}
+
+/// Provide some context via the tree and return a reference to it
+///
+/// Once the context has been provided, it is immutable. Mutations should be done via interior mutability.
+pub fn use_context_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {
+    let ptr = *cx.use_hook(|| cx.provide_context(f()) as *const T);
+    unsafe { &*ptr }
+}

+ 13 - 11
packages/hooks/src/usecoroutine.rs

@@ -2,6 +2,8 @@ use dioxus_core::{ScopeState, TaskId};
 pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use std::future::Future;
 
+use crate::{use_context, use_context_provider};
+
 /// Maintain a handle over a future that can be paused, resumed, and canceled.
 ///
 /// This is an upgraded form of [`use_future`] with an integrated channel system.
@@ -59,33 +61,33 @@ use std::future::Future;
 ///     }
 /// })
 /// ```
-pub fn use_coroutine<M, G, F>(cx: &ScopeState, init: G) -> &CoroutineHandle<M>
+pub fn use_coroutine<M, G, F>(cx: &ScopeState, init: G) -> &Coroutine<M>
 where
     M: 'static,
     G: FnOnce(UnboundedReceiver<M>) -> F,
     F: Future<Output = ()> + 'static,
 {
-    cx.use_hook(|| {
+    use_context_provider(cx, || {
         let (tx, rx) = futures_channel::mpsc::unbounded();
         let task = cx.push_future(init(rx));
-        cx.provide_context(CoroutineHandle { tx, task })
+        Coroutine { tx, task }
     })
 }
 
 /// Get a handle to a coroutine higher in the tree
 ///
 /// See the docs for [`use_coroutine`] for more details.
-pub fn use_coroutine_handle<M: 'static>(cx: &ScopeState) -> Option<&CoroutineHandle<M>> {
-    cx.use_hook(|| cx.consume_context::<CoroutineHandle<M>>())
-        .as_ref()
+pub fn use_coroutine_handle<M: 'static>(cx: &ScopeState) -> Option<&Coroutine<M>> {
+    use_context::<Coroutine<M>>(cx)
 }
 
-pub struct CoroutineHandle<T> {
+pub struct Coroutine<T> {
     tx: UnboundedSender<T>,
     task: TaskId,
 }
 
-impl<T> Clone for CoroutineHandle<T> {
+// for use in futures
+impl<T> Clone for Coroutine<T> {
     fn clone(&self) -> Self {
         Self {
             tx: self.tx.clone(),
@@ -94,7 +96,7 @@ impl<T> Clone for CoroutineHandle<T> {
     }
 }
 
-impl<T> CoroutineHandle<T> {
+impl<T> Coroutine<T> {
     /// Get the ID of this coroutine
     #[must_use]
     pub fn task_id(&self) -> TaskId {
@@ -112,8 +114,8 @@ mod tests {
     #![allow(unused)]
 
     use super::*;
-    use dioxus_core::exports::futures_channel::mpsc::unbounded;
     use dioxus_core::prelude::*;
+    use futures_channel::mpsc::unbounded;
     use futures_util::StreamExt;
 
     fn app(cx: Scope, name: String) -> Element {
@@ -127,7 +129,7 @@ mod tests {
 
         let task3 = use_coroutine(&cx, |rx| complex_task(rx, 10));
 
-        None
+        todo!()
     }
 
     async fn view_task(mut rx: UnboundedReceiver<i32>) {

+ 1 - 1
packages/hooks/src/useeffect.rs

@@ -86,7 +86,7 @@ mod tests {
                 //
             });
 
-            None
+            todo!()
         }
     }
 }

+ 1 - 1
packages/hooks/src/usefuture.rs

@@ -343,7 +343,7 @@ mod tests {
 
             let g = fut.await;
 
-            None
+            todo!()
         }
     }
 }

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

@@ -466,7 +466,7 @@ fn api_makes_sense() {
         }
 
         cx.spawn({
-            dioxus_core::to_owned![val];
+            to_owned![val];
             async move {
                 val.modify(|f| f + 1);
             }

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

@@ -14,7 +14,7 @@ macro_rules! impl_event {
             pub fn $name<'a>(_cx: &'a ::dioxus_core::ScopeState, _f: impl FnMut(::dioxus_core::Event<$data>) + 'a) -> ::dioxus_core::Attribute<'a> {
                 ::dioxus_core::Attribute {
                     name: stringify!($name),
-                    value: ::dioxus_core::AttributeValue::new_listener(_cx, _f),
+                    value: _cx.listener(_f),
                     namespace: None,
                     mounted_element: Default::default(),
                     volatile: false,

+ 8 - 5
packages/rsx/src/lib.rs

@@ -130,7 +130,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
 
         out_tokens.append_all(quote! {
             static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
-                id: concat!(
+                name: concat!(
                     file!(),
                     ":",
                     line!(),
@@ -187,8 +187,10 @@ impl<'a> DynamicContext<'a> {
                             ::dioxus::core::TemplateAttribute::Static {
                                 name: dioxus_elements::#el_name::#name.0,
                                 namespace: dioxus_elements::#el_name::#name.1,
-                                volatile: dioxus_elements::#el_name::#name.2,
                                 value: #value,
+
+                                // todo: we don't diff these so we never apply the volatile flag
+                                // volatile: dioxus_elements::#el_name::#name.2,
                             }
                         })
                     }
@@ -199,8 +201,10 @@ impl<'a> DynamicContext<'a> {
                             ::dioxus::core::TemplateAttribute::Static {
                                 name: dioxus_elements::#el_name::#name.0,
                                 namespace: dioxus_elements::#el_name::#name.1,
-                                volatile: dioxus_elements::#el_name::#name.2,
                                 value: #value,
+
+                                // todo: we don't diff these so we never apply the volatile flag
+                                // volatile: dioxus_elements::#el_name::#name.2,
                             }
                         })
                     }
@@ -226,7 +230,7 @@ impl<'a> DynamicContext<'a> {
                     out
                 });
 
-                let opt = el.children.len() == 1;
+                let _opt = el.children.len() == 1;
                 let children = quote! { #(#children),* };
 
                 quote! {
@@ -235,7 +239,6 @@ impl<'a> DynamicContext<'a> {
                         namespace: dioxus_elements::#el_name::NAME_SPACE,
                         attrs: &[ #attrs ],
                         children: &[ #children ],
-                        inner_opt: #opt,
                     }
                 }
             }

+ 1 - 1
packages/ssr/src/template.rs

@@ -35,7 +35,7 @@ impl SsrRender {
     ) -> std::fmt::Result {
         let entry = self
             .template_cache
-            .entry(template.template.id)
+            .entry(template.template.name)
             .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
             .clone();
 

+ 1 - 6
packages/web/src/dom.rs

@@ -59,7 +59,7 @@ impl WebsysDom {
                 roots.push(self.create_template_node(root))
             }
 
-            self.interpreter.SaveTemplate(roots, template.id);
+            self.interpreter.SaveTemplate(roots, template.name);
         }
     }
 
@@ -77,13 +77,11 @@ impl WebsysDom {
                     Some(ns) => self.document.create_element_ns(Some(ns), tag).unwrap(),
                     None => self.document.create_element(tag).unwrap(),
                 };
-
                 for attr in *attrs {
                     if let TemplateAttribute::Static {
                         name,
                         value,
                         namespace,
-                        volatile,
                     } = attr
                     {
                         match namespace {
@@ -98,14 +96,11 @@ impl WebsysDom {
                         }
                     }
                 }
-
                 for child in *children {
                     el.append_child(&self.create_template_node(child));
                 }
-
                 el.dyn_into().unwrap()
             }
-
             Text(t) => self.document.create_text_node(t).dyn_into().unwrap(),
             DynamicText(_) => self.document.create_text_node("p").dyn_into().unwrap(),
             Dynamic(_) => {