浏览代码

feat: simple tests passing

Jonathan Kelley 2 年之前
父节点
当前提交
4a31b29703

+ 1 - 1
Cargo.toml

@@ -19,7 +19,7 @@ members = [
     "packages/rsx",
     "packages/native-core",
     "packages/native-core-macro",
-    "packages/edit-stream",
+    # "packages/edit-stream",
     "docs/guide",
 ]
 

+ 1 - 2
packages/core-macro/src/lib.rs

@@ -11,8 +11,7 @@ use dioxus_rsx as rsx;
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
     use rsx::*;
-    let item = parse_macro_input!(input as IfmtInput);
-    format_args_f_impl(item)
+    format_args_f_impl(parse_macro_input!(input as IfmtInput))
         .unwrap_or_else(|err| err.to_compile_error())
         .into()
 }

+ 0 - 4
packages/core/src.old/scopes.rs

@@ -424,7 +424,6 @@ impl<P> Clone for Scope<'_, P> {
 }
 
 impl<'a, P> std::ops::Deref for Scope<'a, P> {
-    // rust will auto deref again to the original 'a lifetime at the call site
     type Target = &'a ScopeState;
     fn deref(&self) -> &Self::Target {
         &self.scope
@@ -621,9 +620,6 @@ impl ScopeState {
         Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
     }
 
-    /// Get the [`ScopeId`] of a mounted component.
-    ///
-    /// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
     pub fn needs_update(&self) {
         self.needs_update_any(self.scope_id());
     }

+ 20 - 13
packages/core/src/any_props.rs

@@ -1,7 +1,9 @@
 use std::cell::Cell;
 
+use futures_util::Future;
+
 use crate::{
-    component::Component,
+    component::{Component, ComponentFn, Dummy, IntoComponent},
     element::Element,
     scopes::{Scope, ScopeState},
 };
@@ -12,21 +14,18 @@ pub trait AnyProps {
     unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
 }
 
-pub(crate) struct VComponentProps<P> {
-    pub render_fn: Component<P>,
+pub(crate) struct VComponentProps<P, F: Future<Output = ()> = Dummy> {
+    pub render_fn: ComponentFn<P, F>,
     pub memo: unsafe fn(&P, &P) -> bool,
-    pub props: Scope<P>,
+    pub props: *const P,
 }
 
 impl VComponentProps<()> {
     pub fn new_empty(render_fn: Component<()>) -> Self {
         Self {
-            render_fn,
+            render_fn: render_fn.into_component(),
             memo: <() as PartialEq>::eq,
-            props: Scope {
-                props: (),
-                state: Cell::new(std::ptr::null_mut()),
-            },
+            props: std::ptr::null_mut(),
         }
     }
 }
@@ -35,10 +34,10 @@ impl<P> VComponentProps<P> {
     pub(crate) fn new(
         render_fn: Component<P>,
         memo: unsafe fn(&P, &P) -> bool,
-        props: Scope<P>,
+        props: *const P,
     ) -> Self {
         Self {
-            render_fn,
+            render_fn: render_fn.into_component(),
             memo,
             props,
         }
@@ -62,10 +61,18 @@ impl<P> AnyProps for VComponentProps<P> {
 
     fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
         // Make sure the scope ptr is not null
-        self.props.state.set(scope);
+        // self.props.state.set(scope);
+
+        let scope = Scope {
+            props: unsafe { &*self.props },
+            scope,
+        };
 
         // Call the render function directly
         // todo: implement async
-        (self.render_fn)(unsafe { std::mem::transmute(&self.props) })
+        match self.render_fn {
+            ComponentFn::Sync(f) => f(scope),
+            ComponentFn::Async(_) => todo!(),
+        }
     }
 }

+ 20 - 18
packages/core/src/arena.rs

@@ -1,30 +1,32 @@
 use std::num::NonZeroUsize;
 
+use crate::{
+    nodes::{Template, VNode},
+    virtualdom::VirtualDom,
+};
+
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
 pub struct ElementId(pub usize);
 
-// pub struct ElementId(pub NonZeroUsize);
+pub struct ElementPath {
+    pub template: *mut VNode<'static>,
+    pub element: usize,
+}
 
-// impl Default for ElementId {
-//     fn default() -> Self {
-//         Self(NonZeroUsize::new(1).unwrap())
-//     }
-// }
+impl VirtualDom {
+    pub fn next_element(&mut self, template: &VNode) -> ElementId {
+        let entry = self.elements.vacant_entry();
+        let id = entry.key();
 
-pub struct ElementArena {
-    counter: usize,
-}
+        entry.insert(ElementPath {
+            template: template as *const _ as *mut _,
+            element: id,
+        });
 
-impl Default for ElementArena {
-    fn default() -> Self {
-        Self { counter: 1 }
+        ElementId(id)
     }
-}
 
-impl ElementArena {
-    pub fn next(&mut self) -> ElementId {
-        let id = self.counter;
-        self.counter += 1;
-        ElementId(id)
+    pub fn cleanup_element(&mut self, id: ElementId) {
+        self.elements.remove(id.0);
     }
 }

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

@@ -2,11 +2,11 @@ use std::cell::Cell;
 
 use bumpalo::Bump;
 
-use crate::nodes::VTemplate;
+use crate::nodes::VNode;
 
 pub struct BumpFrame {
     pub bump: Bump,
-    pub node: Cell<*const VTemplate<'static>>,
+    pub node: Cell<*const VNode<'static>>,
 }
 impl BumpFrame {
     pub fn new(capacity: usize) -> Self {

+ 39 - 1
packages/core/src/component.rs

@@ -2,6 +2,44 @@
 //     fn into_component_type(self) -> ComponentType;
 // }
 
+use futures_util::Future;
+
 use crate::{element::Element, scopes::Scope};
 
-pub type Component<T = ()> = fn(&Scope<T>) -> Element;
+pub type Component<T = ()> = fn(Scope<T>) -> Element;
+
+pub enum ComponentFn<T, F: Future<Output = ()> = Dummy> {
+    Sync(fn(Scope<T>) -> Element),
+    Async(fn(Scope<T>) -> F),
+}
+
+pub trait IntoComponent<T, A = ()> {
+    fn into_component(self) -> ComponentFn<T>;
+}
+
+impl<T> IntoComponent<T> for fn(Scope<T>) -> Element {
+    fn into_component(self) -> ComponentFn<T> {
+        ComponentFn::Sync(self)
+    }
+}
+
+pub struct AsyncMarker;
+impl<'a, T, F: Future<Output = Element<'a>>> IntoComponent<T, AsyncMarker>
+    for fn(&'a Scope<T>) -> F
+{
+    fn into_component(self) -> ComponentFn<T> {
+        todo!()
+    }
+}
+
+pub struct Dummy;
+impl Future for Dummy {
+    type Output = ();
+
+    fn poll(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Self::Output> {
+        unreachable!()
+    }
+}

+ 99 - 41
packages/core/src/create.rs

@@ -1,33 +1,30 @@
-use crate::VirtualDom;
-
-use crate::any_props::VComponentProps;
-use crate::arena::ElementArena;
-use crate::component::Component;
 use crate::mutations::Mutation;
-use crate::nodes::{
-    AttributeLocation, DynamicNode, DynamicNodeKind, Template, TemplateId, TemplateNode,
-};
-use crate::scopes::Scope;
-use crate::{
-    any_props::AnyProps,
-    arena::ElementId,
-    bump_frame::BumpFrame,
-    nodes::VTemplate,
-    scopes::{ComponentPtr, ScopeId, ScopeState},
-};
-use slab::Slab;
+use crate::mutations::Mutation::*;
+use crate::nodes::VNode;
+use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
+use crate::virtualdom::VirtualDom;
 
 impl VirtualDom {
     /// Create this template and write its mutations
     pub fn create<'a>(
         &mut self,
         mutations: &mut Vec<Mutation<'a>>,
-        template: &'a VTemplate<'a>,
+        template: &'a VNode<'a>,
     ) -> usize {
-        // The best renderers will have tempaltes prehydrated
+        // 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) {
-            self.create_static_template(mutations, template.template);
+            for node in template.template.roots {
+                self.create_static_node(mutations, template, node);
+            }
+
+            mutations.push(SaveTemplate {
+                name: template.template.id,
+                m: template.template.roots.len(),
+            });
+
+            self.templates
+                .insert(template.template.id, template.template.clone());
         }
 
         // Walk the roots backwards, creating nodes and assigning IDs
@@ -38,28 +35,37 @@ impl VirtualDom {
         let mut on_stack = 0;
         for (root_idx, root) in template.template.roots.iter().enumerate() {
             on_stack += match root {
+                TemplateNode::Element { .. } | TemplateNode::Text(_) => {
+                    mutations.push(LoadTemplate {
+                        name: template.template.id,
+                        index: root_idx,
+                    });
+
+                    1
+                }
+
                 TemplateNode::Dynamic(id) => {
                     self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id])
                 }
-                TemplateNode::DynamicText { .. }
-                | TemplateNode::Element { .. }
-                | TemplateNode::Text(_) => 1,
+
+                TemplateNode::DynamicText { .. } => 1,
             };
 
             // we're on top of a node that has a dynamic attribute for a descendant
             // Set that attribute now before the stack gets in a weird state
             while let Some(loc) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
                 // Attach all the elementIDs to the nodes with dynamic content
-                let id = self.elements.next();
-                mutations.push(Mutation::AssignId {
+                let id = self.next_element(template);
+
+                mutations.push(AssignId {
                     path: &loc.path[1..],
                     id,
                 });
 
                 loc.mounted_element.set(id);
 
-                for attr in loc.attrs {
-                    mutations.push(Mutation::SetAttribute {
+                for attr in loc.attrs.iter() {
+                    mutations.push(SetAttribute {
                         name: attr.name,
                         value: attr.value,
                         id,
@@ -67,31 +73,71 @@ impl VirtualDom {
                 }
             }
 
-            // We're on top of a node that has a dynamic child for a descndent
-            // Might as well set it now while we can
+            // We're on top of a node that has a dynamic child for a descendant
             while let Some(node) = dynamic_nodes.next_if(|f| f.path[0] == root_idx as u8) {
-                self.create_dynamic_node(mutations, template, node);
+                let m = self.create_dynamic_node(mutations, template, node);
+                mutations.push(ReplacePlaceholder {
+                    m,
+                    path: &node.path[1..],
+                });
             }
         }
 
         on_stack
     }
 
-    fn create_static_template(&mut self, mutations: &mut Vec<Mutation>, template: &Template) {
-        todo!("load template")
+    fn create_static_node<'a>(
+        &mut self,
+        mutations: &mut Vec<Mutation<'a>>,
+        template: &'a VNode<'a>,
+        node: &'a TemplateNode<'static>,
+    ) {
+        match *node {
+            TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder),
+            TemplateNode::Text(value) => mutations.push(CreateText { value }),
+            TemplateNode::DynamicText { .. } => mutations.push(CreateText {
+                value: "placeholder",
+            }),
+            TemplateNode::Element {
+                attrs,
+                children,
+                namespace,
+                tag,
+            } => {
+                let id = self.next_element(template);
+
+                mutations.push(CreateElement {
+                    name: tag,
+                    namespace,
+                    id,
+                });
+
+                mutations.extend(attrs.into_iter().map(|attr| SetAttribute {
+                    name: attr.name,
+                    value: attr.value,
+                    id,
+                }));
+
+                children
+                    .into_iter()
+                    .for_each(|child| self.create_static_node(mutations, template, child));
+
+                mutations.push(AppendChildren { m: children.len() })
+            }
+        }
     }
 
     fn create_dynamic_node<'a>(
         &mut self,
         mutations: &mut Vec<Mutation<'a>>,
-        template: &VTemplate<'a>,
+        template: &'a VNode<'a>,
         node: &'a DynamicNode<'a>,
     ) -> usize {
         match &node.kind {
             DynamicNodeKind::Text { id, value } => {
-                let new_id = self.elements.next();
+                let new_id = self.next_element(template);
                 id.set(new_id);
-                mutations.push(Mutation::HydrateText {
+                mutations.push(HydrateText {
                     id: new_id,
                     path: &node.path[1..],
                     value,
@@ -99,16 +145,28 @@ impl VirtualDom {
 
                 1
             }
-            DynamicNodeKind::Component { props, fn_ptr, .. } => {
-                let id = self.new_scope(*fn_ptr, None, ElementId(0), *props);
+
+            DynamicNodeKind::Component { props, .. } => {
+                let id = self.new_scope(*props);
 
                 let template = self.run_scope(id);
 
-                todo!("create component has bad data");
+                // shut up about lifetimes please, I know what I'm doing
+                let template: &VNode = unsafe { std::mem::transmute(template) };
+
+                self.scope_stack.push(id);
+                let created = self.create(mutations, template);
+                self.scope_stack.pop();
+
+                created
+            }
+
+            DynamicNodeKind::Fragment { children } => {
+                //
+                children
+                    .iter()
+                    .fold(0, |acc, child| acc + self.create(mutations, child))
             }
-            DynamicNodeKind::Fragment { children } => children
-                .iter()
-                .fold(0, |acc, child| acc + self.create(mutations, child)),
         }
     }
 }

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

@@ -1,7 +1,7 @@
-use crate::VirtualDom;
+use crate::virtualdom::VirtualDom;
 
 use crate::any_props::VComponentProps;
-use crate::arena::ElementArena;
+
 use crate::component::Component;
 use crate::mutations::Mutation;
 use crate::nodes::{DynamicNode, Template, TemplateId};
@@ -10,9 +10,18 @@ use crate::{
     any_props::AnyProps,
     arena::ElementId,
     bump_frame::BumpFrame,
-    nodes::VTemplate,
-    scopes::{ComponentPtr, ScopeId, ScopeState},
+    nodes::VNode,
+    scopes::{ScopeId, ScopeState},
 };
 use slab::Slab;
 
-impl VirtualDom {}
+pub struct DirtyScope {
+    height: usize,
+    id: ScopeId,
+}
+
+impl VirtualDom {
+    fn diff_scope<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>, scope: ScopeId) {
+        let scope_state = &mut self.scopes[scope.0];
+    }
+}

+ 2 - 2
packages/core/src/element.rs

@@ -1,3 +1,3 @@
-use crate::nodes::{Template, VTemplate};
+use crate::nodes::{Template, VNode};
 
-pub type Element<'a> = Option<VTemplate<'a>>;
+pub type Element<'a> = Option<VNode<'a>>;

+ 159 - 0
packages/core/src/events.rs

@@ -0,0 +1,159 @@
+use std::{any::Any, cell::Cell};
+
+use crate::{arena::ElementId, nodes::Listener, scopes::ScopeId, virtualdom::VirtualDom};
+
+/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
+///
+/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
+/// where each listener is checked and fired if the event name matches.
+///
+/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
+/// attempting to downcast the event data.
+///
+/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
+/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
+///
+/// # Example
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+///     render!(div {
+///         onclick: move |_| println!("Clicked!")
+///     })
+/// }
+///
+/// let mut dom = VirtualDom::new(App);
+/// let mut scheduler = dom.get_scheduler_channel();
+/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
+///     UserEvent {
+///         scope_id: None,
+///         priority: EventPriority::Medium,
+///         name: "click",
+///         element: Some(ElementId(0)),
+///         data: Arc::new(ClickEvent { .. })
+///     }
+/// )).unwrap();
+/// ```
+#[derive(Debug)]
+pub struct UiEvent<T> {
+    /// The priority of the event to be scheduled around ongoing work
+    pub priority: EventPriority,
+
+    /// The optional real node associated with the trigger
+    pub element: ElementId,
+
+    /// The event type IE "onclick" or "onmouseover"
+    pub name: &'static str,
+    pub bubble: Cell<bool>,
+    pub event: T,
+}
+
+/// Priority of Event Triggers.
+///
+/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
+/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers
+/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
+///
+/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
+///
+/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
+/// we keep it simple, and just use a 3-tier priority system.
+///
+/// - `NoPriority` = 0
+/// - `LowPriority` = 1
+/// - `NormalPriority` = 2
+/// - `UserBlocking` = 3
+/// - `HighPriority` = 4
+/// - `ImmediatePriority` = 5
+///
+/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
+/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
+/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
+pub enum EventPriority {
+    /// Work that must be completed during the EventHandler phase.
+    ///
+    /// Currently this is reserved for controlled inputs.
+    Immediate = 3,
+
+    /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
+    ///
+    /// This is typically reserved for things like user interaction.
+    ///
+    /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
+    High = 2,
+
+    /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
+    /// than "High Priority" events and will take precedence over low priority events.
+    ///
+    /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
+    ///
+    /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
+    Medium = 1,
+
+    /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
+    /// advanced to the front of the work queue until completed.
+    ///
+    /// The primary user of Low Priority work is the asynchronous work system (Suspense).
+    ///
+    /// This is considered "idle" work or "background" work.
+    Low = 0,
+}
+
+impl VirtualDom {
+    /// Returns None if no element could be found
+    pub fn handle_event<T: 'static>(&mut self, event: &UiEvent<T>) -> Option<()> {
+        let path = self.elements.get(event.element.0)?;
+
+        let location = unsafe { &mut *path.template }
+            .dynamic_attrs
+            .iter_mut()
+            .position(|attr| attr.mounted_element.get() == event.element)?;
+
+        let mut index = Some((path.template, location));
+
+        let mut listeners = Vec::<&mut Listener>::new();
+
+        while let Some((raw_parent, dyn_index)) = index {
+            let parent = unsafe { &mut *raw_parent };
+            let path = parent.dynamic_nodes[dyn_index].path;
+
+            listeners.extend(
+                parent
+                    .dynamic_attrs
+                    .iter_mut()
+                    .filter(|attr| is_path_ascendant(attr.path, path))
+                    .map(|f| f.listeners.iter_mut().filter(|f| f.name == event.name))
+                    .flatten(),
+            );
+
+            index = parent.parent;
+        }
+
+        for listener in listeners {
+            (listener.callback)(&event.event);
+        }
+
+        Some(())
+    }
+}
+
+// ensures a strict descendant relationship
+// returns false if the paths are equal
+fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
+    if small.len() >= big.len() {
+        return false;
+    }
+
+    small == &big[..small.len()]
+}
+
+#[test]
+fn matches_slice() {
+    let left = &[1, 2, 3];
+    let right = &[1, 2, 3, 4, 5];
+    assert!(is_path_ascendant(left, right));
+    assert!(!is_path_ascendant(right, left));
+    assert!(!is_path_ascendant(left, left));
+
+    assert!(is_path_ascendant(&[1, 2], &[1, 2, 3, 4, 5]));
+}

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

@@ -0,0 +1,46 @@
+use std::fmt::Arguments;
+
+use crate::{innerlude::DynamicNode, LazyNodes, ScopeState, VNode};
+
+impl ScopeState {
+    /// Create some text that's allocated along with the other vnodes
+    ///
+    pub fn text(&self, args: Arguments) -> DynamicNode {
+        // let (text, _is_static) = self.raw_text(args);
+
+        // VNode::Text(self.bump.alloc(VText {
+        //     text,
+        //     id: Default::default(),
+        // }))
+
+        todo!()
+    }
+
+    pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>(
+        &'a self,
+        it: impl IntoIterator<Item = F>,
+    ) -> DynamicNode {
+        let mut bump_vec = bumpalo::vec![in self.bump();];
+
+        for item in it {
+            bump_vec.push(item.into_dynamic_node(self));
+        }
+
+        DynamicNode {
+            path: &[0, 0],
+            kind: crate::innerlude::DynamicNodeKind::Fragment {
+                children: bump_vec.into_bump_slice(),
+            },
+        }
+    }
+}
+
+pub trait IntoVnode<'a, A = ()> {
+    fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
+}
+
+impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
+    fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
+        self.call(cx)
+    }
+}

+ 65 - 0
packages/core/src/future_container.rs

@@ -0,0 +1,65 @@
+use futures_channel::mpsc::UnboundedSender;
+use futures_util::Future;
+use std::{cell::RefCell, rc::Rc};
+
+use crate::innerlude::ScopeId;
+/// The type of message that can be sent to the scheduler.
+///
+/// These messages control how the scheduler will process updates to the UI.
+#[derive(Debug)]
+pub enum SchedulerMsg {
+    /// Events from athe Renderer
+    Event,
+
+    /// Immediate updates from Components that mark them as dirty
+    Immediate(ScopeId),
+
+    /// Mark all components as dirty and update them
+    DirtyAll,
+
+    /// New tasks from components that should be polled when the next poll is ready
+    NewTask(ScopeId),
+}
+
+// todo extract this so spawning doesn't require refcell and rc doesnt need to be tracked
+#[derive(Clone)]
+pub struct FutureQueue {
+    pub sender: UnboundedSender<SchedulerMsg>,
+    pub queue: Rc<RefCell<Vec<Box<dyn Future<Output = ()>>>>>,
+}
+
+impl FutureQueue {
+    pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
+        Self {
+            sender,
+            queue: Default::default(),
+        }
+    }
+
+    pub fn spawn(&self, id: ScopeId, fut: impl Future<Output = ()> + 'static) -> TaskId {
+        self.sender
+            .unbounded_send(SchedulerMsg::NewTask(id))
+            .unwrap();
+        self.queue.borrow_mut().push(Box::new(fut));
+
+        todo!()
+    }
+
+    pub fn remove(&self, id: TaskId) {
+        todo!()
+    }
+}
+
+/// A task's unique identifier.
+///
+/// `TaskId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`TaskID`]s 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 {
+    /// The global ID of the task
+    pub id: usize,
+
+    /// The original scope that this task was scheduled in
+    pub scope: ScopeId,
+}

+ 30 - 0
packages/core/src/garbage.rs

@@ -0,0 +1,30 @@
+use crate::{
+    nodes::{DynamicNodeKind, VNode},
+    scopes::ScopeId,
+    virtualdom::VirtualDom,
+};
+
+impl VirtualDom {
+    pub fn drop_scope(&mut self, id: ScopeId) {
+        let scope = self.scopes.get(id.0).unwrap();
+
+        let root = scope.root_node();
+        let root = unsafe { std::mem::transmute(root) };
+
+        self.drop_template(root);
+    }
+
+    pub fn drop_template<'a>(&'a mut self, template: &'a VNode<'a>) {
+        for node in template.dynamic_nodes.iter() {
+            match &node.kind {
+                DynamicNodeKind::Text { id, .. } => {}
+
+                DynamicNodeKind::Component { .. } => {
+                    todo!()
+                }
+
+                DynamicNodeKind::Fragment { children } => {}
+            }
+        }
+    }
+}

+ 324 - 0
packages/core/src/lazynodes.rs

@@ -0,0 +1,324 @@
+//! Support for storing lazy-nodes on the stack
+//!
+//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
+//! to `rsx!` more efficient.
+//!
+//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures.
+//!
+//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
+//! we build a tiny alloactor in the stack and allocate the closure into that.
+//!
+//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
+//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
+
+use crate::{innerlude::VNode, ScopeState};
+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`].
+///
+///
+/// ```rust, ignore
+/// LazyNodes::new(|f| f.element("div", [], [], [] None))
+/// ```
+pub struct LazyNodes<'a, 'b> {
+    inner: StackNodeStorage<'a, 'b>,
+}
+
+pub type NodeFactory<'a> = &'a ScopeState;
+
+type StackHeapSize = [usize; 16];
+
+enum StackNodeStorage<'a, 'b> {
+    Stack(LazyStack),
+    Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
+}
+
+impl<'a, 'b> LazyNodes<'a, 'b> {
+    /// Create a new [`LazyNodes`] closure, optimistically placing it onto the stack.
+    ///
+    /// If the closure cannot fit into the stack allocation (16 bytes), then it
+    /// is placed on the heap. Most closures will fit into the stack, and is
+    /// the most optimal way to use the creation function.
+    pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
+        // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
+        let mut slot = Some(val);
+
+        let val = move |fac: Option<NodeFactory<'a>>| {
+            fac.map(
+                slot.take()
+                    .expect("LazyNodes closure to be called only once"),
+            )
+        };
+
+        // miri does not know how to work with mucking directly into bytes
+        // just use a heap allocated type when miri is running
+        if cfg!(miri) {
+            Self {
+                inner: StackNodeStorage::Heap(Box::new(val)),
+            }
+        } else {
+            unsafe { LazyNodes::new_inner(val) }
+        }
+    }
+
+    /// Create a new [`LazyNodes`] closure, but force it onto the heap.
+    pub fn new_boxed<F>(inner: F) -> Self
+    where
+        F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
+    {
+        // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
+        let mut slot = Some(inner);
+
+        Self {
+            inner: StackNodeStorage::Heap(Box::new(move |fac: Option<NodeFactory<'a>>| {
+                fac.map(
+                    slot.take()
+                        .expect("LazyNodes closure to be called only once"),
+                )
+            })),
+        }
+    }
+
+    unsafe fn new_inner<F>(val: F) -> Self
+    where
+        F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
+    {
+        let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
+
+        assert_eq!(
+            ptr as *const u8, &val as *const _ as *const u8,
+            "MISUSE: Closure returned different pointer"
+        );
+        assert_eq!(
+            std::mem::size_of_val(&*ptr),
+            std::mem::size_of::<F>(),
+            "MISUSE: Closure returned a subset pointer"
+        );
+
+        let words = ptr_as_slice(&mut ptr);
+        assert!(
+            words[0] == &val as *const _ as usize,
+            "BUG: Pointer layout is not (data_ptr, info...)"
+        );
+
+        // - Ensure that Self is aligned same as data requires
+        assert!(
+            std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
+            "TODO: Enforce alignment >{} (requires {})",
+            std::mem::align_of::<Self>(),
+            std::mem::align_of::<F>()
+        );
+
+        let info = &words[1..];
+        let data = words[0] as *mut ();
+        let size = mem::size_of::<F>();
+
+        let stored_size = info.len() * mem::size_of::<usize>() + size;
+        let max_size = mem::size_of::<StackHeapSize>();
+
+        if stored_size > max_size {
+            Self {
+                inner: StackNodeStorage::Heap(Box::new(val)),
+            }
+        } else {
+            let mut buf: StackHeapSize = StackHeapSize::default();
+
+            assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
+
+            // Place pointer information at the end of the region
+            // - Allows the data to be at the start for alignment purposes
+            {
+                let info_ofs = buf.as_ref().len() - info.len();
+                let info_dst = &mut buf.as_mut()[info_ofs..];
+                for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
+                    *d = *v;
+                }
+            }
+
+            let src_ptr = data as *const u8;
+            let dataptr = buf.as_mut_ptr().cast::<u8>();
+
+            for i in 0..size {
+                *dataptr.add(i) = *src_ptr.add(i);
+            }
+
+            std::mem::forget(val);
+
+            Self {
+                inner: StackNodeStorage::Stack(LazyStack {
+                    _align: [],
+                    buf,
+                    dropped: false,
+                }),
+            }
+        }
+    }
+
+    /// Call the closure with the given factory to produce real [`VNode`].
+    ///
+    /// ```rust, ignore
+    /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
+    ///
+    /// let fac = NodeFactory::new(&cx);
+    ///
+    /// let node = f.call(cac);
+    /// ```
+    #[must_use]
+    pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
+        match self.inner {
+            StackNodeStorage::Heap(mut lazy) => {
+                lazy(Some(f)).expect("Closure should not be called twice")
+            }
+            StackNodeStorage::Stack(mut stack) => stack.call(f),
+        }
+    }
+}
+
+struct LazyStack {
+    _align: [u64; 0],
+    buf: StackHeapSize,
+    dropped: bool,
+}
+
+impl LazyStack {
+    fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
+        let LazyStack { buf, .. } = self;
+        let data = buf.as_ref();
+
+        let info_size =
+            mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
+                / mem::size_of::<usize>()
+                - 1;
+
+        let info_ofs = data.len() - info_size;
+
+        let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
+            unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
+
+        self.dropped = true;
+
+        let clos = unsafe { &mut *g };
+        clos(Some(f)).unwrap()
+    }
+}
+impl Drop for LazyStack {
+    fn drop(&mut self) {
+        if !self.dropped {
+            let LazyStack { buf, .. } = self;
+            let data = buf.as_ref();
+
+            let info_size = mem::size_of::<
+                *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
+            >() / mem::size_of::<usize>()
+                - 1;
+
+            let info_ofs = data.len() - info_size;
+
+            let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
+                unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
+
+            self.dropped = true;
+
+            let clos = unsafe { &mut *g };
+            clos(None);
+        }
+    }
+}
+
+/// Obtain mutable access to a pointer's words
+fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
+    assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
+    let words = mem::size_of::<T>() / mem::size_of::<usize>();
+    // SAFE: Points to valid memory (a raw pointer)
+    unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
+}
+
+/// Re-construct a fat pointer
+unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
+    let mut rv = mem::MaybeUninit::<*mut T>::uninit();
+    {
+        let s = ptr_as_slice(&mut rv);
+        s[0] = data_ptr;
+        s[1..].copy_from_slice(meta_vals);
+    }
+    let rv = rv.assume_init();
+    assert_eq!(rv as *const (), data_ptr as *const ());
+    rv
+}
+
+fn round_to_words(len: usize) -> usize {
+    (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
+}
+
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use crate::innerlude::{Element, Scope, VirtualDom};
+
+//     #[test]
+//     fn it_works() {
+//         fn app(cx: Scope<()>) -> Element {
+//             cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
+//         }
+
+//         let mut dom = VirtualDom::new(app);
+//         dom.rebuild();
+
+//         let g = dom.base_scope().root_node();
+//         dbg!(g);
+//     }
+
+//     #[test]
+//     fn it_drops() {
+//         use std::rc::Rc;
+
+//         struct AppProps {
+//             inner: Rc<i32>,
+//         }
+
+//         fn app(cx: Scope<AppProps>) -> Element {
+//             struct DropInner {
+//                 id: i32,
+//             }
+//             impl Drop for DropInner {
+//                 fn drop(&mut self) {
+//                     eprintln!("dropping inner");
+//                 }
+//             }
+
+//             let caller = {
+//                 let it = (0..10).map(|i| {
+//                     let val = cx.props.inner.clone();
+//                     LazyNodes::new(move |f| {
+//                         eprintln!("hell closure");
+//                         let inner = DropInner { id: i };
+//                         f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
+//                     })
+//                 });
+
+//                 LazyNodes::new(|f| {
+//                     eprintln!("main closure");
+//                     f.fragment_from_iter(it)
+//                 })
+//             };
+
+//             cx.render(caller)
+//         }
+
+//         let inner = Rc::new(0);
+//         let mut dom = VirtualDom::new_with_props(
+//             app,
+//             AppProps {
+//                 inner: inner.clone(),
+//             },
+//         );
+//         dom.rebuild();
+
+//         drop(dom);
+
+//         assert_eq!(Rc::strong_count(&inner), 1);
+//     }
+// }

+ 125 - 54
packages/core/src/lib.rs

@@ -1,20 +1,3 @@
-use std::collections::HashMap;
-
-use crate::{
-    any_props::AnyProps,
-    arena::ElementId,
-    bump_frame::BumpFrame,
-    nodes::VTemplate,
-    scopes::{ComponentPtr, ScopeId, ScopeState},
-};
-use any_props::VComponentProps;
-use arena::ElementArena;
-use component::Component;
-use mutations::Mutation;
-use nodes::{DynamicNode, Template, TemplateId};
-use scopes::Scope;
-use slab::Slab;
-
 mod any_props;
 mod arena;
 mod bump_frame;
@@ -22,54 +5,142 @@ mod component;
 mod create;
 mod diff;
 mod element;
+mod events;
+mod factory;
+mod future_container;
+mod garbage;
+mod lazynodes;
 mod mutations;
 mod nodes;
 mod scope_arena;
 mod scopes;
+mod virtualdom;
 
-pub struct VirtualDom {
-    templates: HashMap<TemplateId, Template>,
-    elements: ElementArena,
-    scopes: Slab<ScopeState>,
-    scope_stack: Vec<ScopeId>,
-}
+pub(crate) mod innerlude {
+    pub use crate::element::Element;
+    pub use crate::events::*;
+    pub use crate::future_container::*;
+    pub use crate::mutations::*;
+    pub use crate::nodes::*;
+    // pub use crate::properties::*;
+    pub use crate::lazynodes::*;
+    pub use crate::scopes::*;
+    pub use crate::virtualdom::*;
 
-impl VirtualDom {
-    pub fn new(app: Component) -> Self {
-        let mut res = Self {
-            templates: Default::default(),
-            scopes: Slab::default(),
-            elements: ElementArena::default(),
-            scope_stack: Vec::new(),
-        };
+    // /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
+    // ///
+    // /// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
+    // pub type Element<'a> = Option<VNodea<'a>>;
 
-        res.new_scope(
-            app as _,
-            None,
-            ElementId(0),
-            Box::new(VComponentProps::new_empty(app)),
-        );
+    /// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
+    ///
+    /// Components can be used in other components with two syntax options:
+    /// - lowercase as a function call with named arguments (rust style)
+    /// - uppercase as an element (react style)
+    ///
+    /// ## Rust-Style
+    ///
+    /// ```rust, ignore
+    /// fn example(cx: Scope<Props>) -> Element {
+    ///     // ...
+    /// }
+    ///
+    /// rsx!(
+    ///     example()
+    /// )
+    /// ```
+    /// ## React-Style
+    /// ```rust, ignore
+    /// fn Example(cx: Scope<Props>) -> Element {
+    ///     // ...
+    /// }
+    ///
+    /// rsx!(
+    ///     Example {}
+    /// )
+    /// ```
+    pub type Component<P = ()> = fn(Scope<P>) -> Element;
 
-        res
-    }
+    /// A list of attributes
+    pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
+}
 
-    fn root_scope(&self) -> &ScopeState {
-        todo!()
-    }
+pub use crate::innerlude::{
+    // AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
+    Attribute,
+    AttributeValue,
+    Element,
+    EventPriority,
+    LazyNodes,
+    Listener,
+    NodeFactory,
+    Scope,
+    ScopeId,
+    ScopeState,
+    TaskId,
+    Template,
+    TemplateAttribute,
+    TemplateNode,
+    UiEvent,
+    VNode,
+    VirtualDom,
+};
+// EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
+// SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
+// UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
 
-    /// Render the virtualdom, waiting for all suspended nodes to complete before moving on
-    ///
-    /// Forces a full render of the virtualdom from scratch.
-    ///
-    /// Use other methods to update the virtualdom incrementally.
-    pub fn render_all<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
-        let root = self.root_scope();
+/// The purpose of this module is to alleviate imports of many common types
+///
+/// This includes types like [`Scope`], [`Element`], and [`Component`].
+pub mod prelude {
+    pub use crate::innerlude::{
+        Attribute, Element, EventPriority, LazyNodes, Listener, NodeFactory, Scope, ScopeId,
+        ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
+    };
+}
 
-        let root_template = root.current_arena();
+pub mod exports {
+    //! Important dependencies that are used by the rest of the library
+    //! Feel free to just add the dependencies in your own Crates.toml
+    pub use bumpalo;
+    pub use futures_channel;
+}
 
-        let root_node: &'a VTemplate = unsafe { &*root_template.node.get() };
-        let root_node: &'a VTemplate<'a> = unsafe { std::mem::transmute(root_node) };
+#[macro_export]
+/// A helper macro for using hooks in async environements.
+///
+/// # Usage
+///
+///
+/// ```ignore
+/// let (data) = use_ref(&cx, || {});
+///
+/// let handle_thing = move |_| {
+///     to_owned![data]
+///     cx.spawn(async move {
+///         // do stuff
+///     });
+/// }
+/// ```
+macro_rules! to_owned {
+    ($($es:ident),+) => {$(
+        #[allow(unused_mut)]
+        let mut $es = $es.to_owned();
+    )*}
+}
 
-        self.create(mutations, root_node);
-    }
+/// get the code location of the code that called this function
+#[macro_export]
+macro_rules! get_line_num {
+    () => {
+        concat!(
+            file!(),
+            ":",
+            line!(),
+            ":",
+            column!(),
+            ":",
+            env!("CARGO_MANIFEST_DIR")
+        )
+    };
 }

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

@@ -17,6 +17,11 @@ pub enum Mutation<'a> {
         index: usize,
     },
 
+    SaveTemplate {
+        name: &'static str,
+        m: usize,
+    },
+
     HydrateText {
         path: &'static [u8],
         value: &'a str,
@@ -30,7 +35,7 @@ pub enum Mutation<'a> {
 
     ReplacePlaceholder {
         path: &'static [u8],
-        id: ElementId,
+        m: usize,
     },
 
     AssignId {
@@ -42,4 +47,20 @@ pub enum Mutation<'a> {
     Replace {
         id: ElementId,
     },
+
+    CreateElement {
+        name: &'a str,
+        namespace: Option<&'a str>,
+        id: ElementId,
+    },
+
+    CreateText {
+        value: &'a str,
+    },
+
+    CreatePlaceholder,
+
+    AppendChildren {
+        m: usize,
+    },
 }

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

@@ -1,4 +1,4 @@
-use crate::{any_props::AnyProps, arena::ElementId, scopes::ComponentPtr};
+use crate::{any_props::AnyProps, arena::ElementId};
 use std::{
     any::{Any, TypeId},
     cell::Cell,
@@ -8,11 +8,14 @@ use std::{
 pub type TemplateId = &'static str;
 
 /// A reference to a template along with any context needed to hydrate it
-pub struct VTemplate<'a> {
+pub struct VNode<'a> {
     // The ID assigned for the root of this template
     pub node_id: Cell<ElementId>,
 
-    pub template: &'static Template,
+    // When rendered, this template will be linked to its parent
+    pub parent: Option<(*mut VNode<'static>, usize)>,
+
+    pub template: Template,
 
     pub root_ids: &'a [Cell<ElementId>],
 
@@ -25,7 +28,6 @@ pub struct VTemplate<'a> {
 #[derive(Debug, Clone, Copy)]
 pub struct Template {
     pub id: &'static str,
-
     pub roots: &'static [TemplateNode<'static>],
 }
 
@@ -54,8 +56,7 @@ pub enum DynamicNodeKind<'a> {
     // IE in caps or with underscores
     Component {
         name: &'static str,
-        fn_ptr: ComponentPtr,
-        props: Box<dyn AnyProps>,
+        props: *mut dyn AnyProps,
     },
 
     // Comes in with string interpolation or from format_args, include_str, etc
@@ -66,7 +67,7 @@ pub enum DynamicNodeKind<'a> {
 
     // Anything that's coming in as an iterator
     Fragment {
-        children: &'a [VTemplate<'a>],
+        children: &'a [VNode<'a>],
     },
 }
 
@@ -80,8 +81,8 @@ pub struct TemplateAttribute<'a> {
 
 pub struct AttributeLocation<'a> {
     pub mounted_element: Cell<ElementId>,
-    pub attrs: &'a [Attribute<'a>],
-    pub listeners: &'a [Listener<'a>],
+    pub attrs: &'a mut [Attribute<'a>],
+    pub listeners: &'a mut [Listener<'a>],
     pub path: &'static [u8],
 }
 
@@ -118,12 +119,12 @@ where
 
 pub struct Listener<'a> {
     pub name: &'static str,
-    pub callback: &'a dyn Fn(),
+    pub callback: &'a mut dyn FnMut(&dyn Any),
 }
 
 #[test]
 fn what_are_the_sizes() {
-    dbg!(std::mem::size_of::<VTemplate>());
+    dbg!(std::mem::size_of::<VNode>());
     dbg!(std::mem::size_of::<Template>());
     dbg!(std::mem::size_of::<TemplateNode>());
 }

+ 31 - 20
packages/core/src/scope_arena.rs

@@ -1,50 +1,61 @@
-use slab::Slab;
-
 use crate::{
     any_props::AnyProps,
     arena::ElementId,
     bump_frame::BumpFrame,
-    nodes::VTemplate,
-    scopes::{ComponentPtr, ScopeId, ScopeState},
-    VirtualDom,
+    nodes::VNode,
+    scopes::{ScopeId, ScopeState},
+    virtualdom::VirtualDom,
 };
 
 impl VirtualDom {
-    pub fn new_scope(
-        &mut self,
-        fn_ptr: ComponentPtr,
-        parent: Option<*mut ScopeState>,
-        container: ElementId,
-        props: Box<dyn AnyProps>,
-    ) -> ScopeId {
+    pub fn new_scope(&mut self, props: *mut dyn AnyProps) -> ScopeId {
+        let parent = self.acquire_current_scope_raw();
+        let container = self.acquire_current_container();
         let entry = self.scopes.vacant_entry();
-        let our_arena_idx = entry.key();
         let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
+        let id = ScopeId(entry.key());
 
         entry.insert(ScopeState {
             parent,
             container,
-            our_arena_idx,
+            id,
             height,
-            fn_ptr,
             props,
+            tasks: self.pending_futures.clone(),
             node_arena_1: BumpFrame::new(50),
             node_arena_2: BumpFrame::new(50),
             render_cnt: Default::default(),
             hook_arena: Default::default(),
             hook_vals: Default::default(),
             hook_idx: Default::default(),
+            shared_contexts: Default::default(),
         });
 
-        our_arena_idx
+        id
+    }
+
+    pub fn acquire_current_container(&self) -> ElementId {
+        self.element_stack
+            .last()
+            .copied()
+            .expect("Always have a container")
     }
 
-    pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VTemplate<'a> {
-        let scope = &mut self.scopes[id];
+    fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
+        self.scope_stack
+            .last()
+            .copied()
+            .and_then(|id| self.scopes.get_mut(id.0).map(|f| f as *mut ScopeState))
+    }
+
+    pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VNode<'a> {
+        let scope = &mut self.scopes[id.0];
         scope.hook_idx.set(0);
 
-        let res = scope.props.render(scope).unwrap();
-        let res: VTemplate<'static> = unsafe { std::mem::transmute(res) };
+        let props = unsafe { &mut *scope.props };
+
+        let res = props.render(scope).unwrap();
+        let res: VNode<'static> = unsafe { std::mem::transmute(res) };
 
         let frame = match scope.render_cnt % 2 {
             0 => &mut scope.node_arena_1,

+ 353 - 16
packages/core/src/scopes.rs

@@ -1,13 +1,50 @@
 use std::{
-    any::Any,
+    any::{Any, TypeId},
     cell::{Cell, RefCell},
+    collections::HashMap,
+    sync::Arc,
 };
 
 use bumpalo::Bump;
+use futures_channel::mpsc::UnboundedSender;
+use futures_util::Future;
 
-use crate::{any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, nodes::VTemplate};
+use crate::{
+    any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, future_container::FutureQueue,
+    innerlude::SchedulerMsg, lazynodes::LazyNodes, nodes::VNode, TaskId,
+};
+
+pub struct Scope<'a, T = ()> {
+    pub scope: &'a ScopeState,
+    pub props: &'a T,
+}
 
-pub type ScopeId = usize;
+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> {
+    type Target = &'a ScopeState;
+
+    fn deref(&self) -> &Self::Target {
+        &self.scope
+    }
+}
+
+/// 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.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
+pub struct ScopeId(pub usize);
 
 pub struct ScopeState {
     pub render_cnt: usize,
@@ -17,16 +54,19 @@ pub struct ScopeState {
 
     pub parent: Option<*mut ScopeState>,
     pub container: ElementId,
-    pub our_arena_idx: ScopeId,
+    pub id: ScopeId,
 
     pub height: u32,
-    pub fn_ptr: ComponentPtr,
 
     pub hook_arena: Bump,
     pub hook_vals: RefCell<Vec<*mut dyn Any>>,
     pub hook_idx: Cell<usize>,
 
-    pub props: Box<dyn AnyProps>,
+    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
+
+    pub tasks: FutureQueue,
+
+    pub props: *mut dyn AnyProps,
 }
 
 impl ScopeState {
@@ -37,19 +77,316 @@ impl ScopeState {
             _ => unreachable!(),
         }
     }
-}
 
-pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
+    pub fn bump(&self) -> &Bump {
+        &self.current_arena().bump
+    }
+
+    pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
+        let r = unsafe { &*self.current_arena().node.get() };
+        unsafe { std::mem::transmute(r) }
+    }
 
-pub struct Scope<T = ()> {
-    pub props: T,
-    pub state: Cell<*const ScopeState>,
-}
+    /// Get the height of this Scope - IE the number of scopes above it.
+    ///
+    /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// let mut dom = VirtualDom::new(|cx|  cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    ///
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.height(), 0);
+    /// ```
+    pub fn height(&self) -> u32 {
+        self.height
+    }
 
-impl<T> std::ops::Deref for Scope<T> {
-    type Target = ScopeState;
+    /// Get the Parent of this [`Scope`] within this Dioxus [`VirtualDom`].
+    ///
+    /// This ID is not unique across Dioxus [`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`.
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// let mut dom = VirtualDom::new(|cx|  cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    ///
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.parent(), None);
+    /// ```
+    pub fn parent(&self) -> Option<ScopeId> {
+        // safety: the pointer to our parent is *always* valid thanks to the bump arena
+        self.parent.map(|p| unsafe { &*p }.id)
+    }
 
-    fn deref(&self) -> &Self::Target {
-        unsafe { &*self.state.get() }
+    /// Get the ID of this Scope within this Dioxus [`VirtualDom`].
+    ///
+    /// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// let mut dom = VirtualDom::new(|cx|  cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.scope_id(), 0);
+    /// ```
+    pub fn scope_id(&self) -> ScopeId {
+        self.id
+    }
+
+    /// Get a handle to the raw update scheduler channel
+    pub fn scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
+        self.tasks.sender.clone()
+    }
+
+    /// Create a subscription that schedules a future render for the reference component
+    ///
+    /// ## Notice: you should prefer using [`schedule_update_any`] and [`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))))
+    }
+
+    /// Schedule an update for any component given its [`ScopeId`].
+    ///
+    /// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
+    ///
+    /// This method should be used when you want to schedule an update for a component
+    pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
+        let chan = self.tasks.sender.clone();
+        Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
+    }
+
+    pub fn needs_update(&self) {
+        self.needs_update_any(self.scope_id());
+    }
+
+    /// Get the [`ScopeId`] of a mounted component.
+    ///
+    /// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
+    pub fn needs_update_any(&self, id: ScopeId) {
+        self.tasks
+            .sender
+            .unbounded_send(SchedulerMsg::Immediate(id))
+            .expect("Scheduler to exist if scope exists");
+    }
+
+    /// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
+    ///
+    /// This is a "fundamental" operation and should only be called during initialization of a hook.
+    ///
+    /// For a hook that provides the same functionality, use `use_provide_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.
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// struct SharedState(&'static str);
+    ///
+    /// static App: Component = |cx| {
+    ///     cx.use_hook(|| cx.provide_context(SharedState("world")));
+    ///     render!(Child {})
+    /// }
+    ///
+    /// static Child: Component = |cx| {
+    ///     let state = cx.consume_state::<SharedState>();
+    ///     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
+    }
+
+    /// Provide a context for the root component from anywhere in your app.
+    ///
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// struct SharedState(&'static str);
+    ///
+    /// static App: Component = |cx| {
+    ///     cx.use_hook(|| cx.provide_root_context(SharedState("world")));
+    ///     render!(Child {})
+    /// }
+    ///
+    /// static Child: Component = |cx| {
+    ///     let state = cx.consume_state::<SharedState>();
+    ///     render!(div { "hello {state.0}" })
+    /// }
+    /// ```
+    pub fn provide_root_context<T: 'static + Clone>(&self, value: T) -> T {
+        // if we *are* the root component, then we can just provide the context directly
+        if self.scope_id() == ScopeId(0) {
+            self.shared_contexts
+                .borrow_mut()
+                .insert(TypeId::of::<T>(), Box::new(value.clone()))
+                .and_then(|f| f.downcast::<T>().ok());
+            return value;
+        }
+
+        let mut search_parent = self.parent;
+
+        while let Some(parent) = search_parent.take() {
+            let parent = unsafe { &*parent };
+
+            if parent.scope_id() == ScopeId(0) {
+                let exists = parent
+                    .shared_contexts
+                    .borrow_mut()
+                    .insert(TypeId::of::<T>(), Box::new(value.clone()));
+
+                if exists.is_some() {
+                    log::warn!("Context already provided to parent scope - replacing it");
+                }
+                return value;
+            }
+
+            search_parent = parent.parent;
+        }
+
+        unreachable!("all apps have a root scope")
+    }
+
+    /// Try to retrieve a shared state with type T from the any parent Scope.
+    pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
+        if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
+            Some(
+                (*shared
+                    .downcast_ref::<T>()
+                    .expect("Context of type T should exist"))
+                .clone(),
+            )
+        } else {
+            let mut search_parent = self.parent;
+
+            while let Some(parent_ptr) = search_parent {
+                // 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(),
+                    );
+                }
+                search_parent = parent.parent;
+            }
+            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 {
+        // wake up the scheduler if it is sleeping
+        self.tasks
+            .sender
+            .unbounded_send(SchedulerMsg::NewTask(self.id))
+            .expect("Scheduler should exist");
+
+        self.tasks.spawn(self.id, fut)
+    }
+
+    /// Spawns the future but does not return the [`TaskId`]
+    pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
+        self.push_future(fut);
+    }
+
+    /// Spawn a future that Dioxus will never clean up
+    ///
+    /// This is good for tasks that need to be run after the component has been dropped.
+    pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
+        // wake up the scheduler if it is sleeping
+        self.tasks
+            .sender
+            .unbounded_send(SchedulerMsg::NewTask(self.id))
+            .expect("Scheduler should exist");
+
+        // The root scope will never be unmounted so we can just add the task at the top of the app
+        self.tasks.spawn(ScopeId(0), fut)
+    }
+
+    /// Informs the scheduler that this task is no longer needed and should be removed
+    /// on next poll.
+    pub fn remove_future(&self, id: TaskId) {
+        self.tasks.remove(id);
+    }
+
+    /// Take a lazy [`VNode`] structure and actually build it with the context of the Vdoms efficient [`VNode`] allocator.
+    ///
+    /// ## Example
+    ///
+    /// ```ignore
+    /// fn Component(cx: Scope<Props>) -> Element {
+    ///     // Lazy assemble the VNode tree
+    ///     let lazy_nodes = rsx!("hello world");
+    ///
+    ///     // Actually build the tree and allocate it
+    ///     cx.render(lazy_tree)
+    /// }
+    ///```
+    pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
+        Some(rsx.call(self))
+    }
+
+    /// 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`.
+    ///
+    /// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use dioxus_core::ScopeState;
+    ///
+    /// // prints a greeting on the initial render
+    /// pub fn use_hello_world(cx: &ScopeState) {
+    ///     cx.use_hook(|| println!("Hello, world!"));
+    /// }
+    /// ```
+    #[allow(clippy::mut_from_ref)]
+    pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
+        let mut vals = self.hook_vals.borrow_mut();
+
+        let hook_len = vals.len();
+        let cur_idx = self.hook_idx.get();
+
+        if cur_idx >= hook_len {
+            vals.push(self.hook_arena.alloc(initializer()));
+        }
+
+        vals
+            .get(cur_idx)
+            .and_then(|inn| {
+                self.hook_idx.set(cur_idx + 1);
+                let raw_box = unsafe { &mut **inn };
+                raw_box.downcast_mut::<State>()
+            })
+            .expect(
+                r###"
+                Unable to retrieve the hook that was initialized at this index.
+                Consult the `rules of hooks` to understand how to use hooks properly.
+
+                You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
+                Functions prefixed with "use" should never be called conditionally.
+                "###,
+            )
     }
 }

+ 91 - 0
packages/core/src/virtualdom.rs

@@ -0,0 +1,91 @@
+use crate::any_props::VComponentProps;
+use crate::arena::ElementPath;
+use crate::component::{Component, IntoComponent};
+use crate::diff::DirtyScope;
+use crate::future_container::FutureQueue;
+use crate::innerlude::SchedulerMsg;
+use crate::mutations::Mutation;
+use crate::nodes::{Template, TemplateId};
+use crate::{
+    arena::ElementId,
+    scopes::{ScopeId, ScopeState},
+};
+use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use slab::Slab;
+use std::collections::{BTreeSet, HashMap};
+
+pub struct VirtualDom {
+    pub(crate) templates: HashMap<TemplateId, Template>,
+    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) pending_futures: FutureQueue,
+    pub(crate) sender: UnboundedSender<SchedulerMsg>,
+    pub(crate) receiver: UnboundedReceiver<SchedulerMsg>,
+}
+
+impl VirtualDom {
+    pub fn new(app: Component<()>) -> Self {
+        let (sender, receiver) = futures_channel::mpsc::unbounded();
+
+        let mut res = Self {
+            templates: Default::default(),
+            scopes: Slab::default(),
+            elements: Default::default(),
+            scope_stack: Vec::new(),
+            element_stack: vec![ElementId(0)],
+            dirty_scopes: BTreeSet::new(),
+            pending_futures: FutureQueue::new(sender.clone()),
+            receiver,
+            sender,
+        };
+
+        let props = Box::into_raw(Box::new(VComponentProps::new_empty(app)));
+
+        let root = res.new_scope(props);
+
+        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>>) {
+        // let root = self.scopes.get(0).unwrap();
+
+        let root_node = unsafe { std::mem::transmute(self.run_scope(ScopeId(0))) };
+
+        // let root_node = unsafe { std::mem::transmute(root.root_node()) };
+
+        self.scope_stack.push(ScopeId(0));
+        self.create(mutations, root_node);
+        self.scope_stack.pop();
+    }
+
+    /// 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>>,
+    ) {
+        todo!()
+    }
+
+    // Whenever the future is canceled, the VirtualDom will be
+    pub async fn render<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
+        //
+    }
+
+    /// Wait for futures internal to the virtualdom
+    ///
+    /// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
+    pub async fn wait_for_work(&mut self) {}
+}
+
+impl Drop for VirtualDom {
+    fn drop(&mut self) {
+        // self.drop_scope(ScopeId(0));
+    }
+}

+ 0 - 114
packages/core/tests/interpreter.rs

@@ -1,114 +0,0 @@
-use dioxus_core::{prelude::*, TemplateNode, VTemplate, VText};
-
-// #[test]
-// fn simple_static() {
-//     fn app(cx: Scope) -> Element {
-//         static MyTemplate: TemplateDef = TemplateDef {
-//             id: "my-template",
-//             static_nodes: &[TemplateNode::Element {
-//                 attributes: &[],
-//                 nodes: &[TemplateNode::StaticText("Hello, world!")],
-//                 tag: "div",
-//             }],
-//             dynamic_nodes: &[],
-//         };
-
-//         Some(VNode::Template(NodeFactory::new(&cx).bump().alloc(
-//             VTemplate {
-//                 def: &MyTemplate,
-//                 dynamic_nodes: &[],
-//                 rendered_nodes: &[],
-//             },
-//         )))
-//     }
-
-//     let mut dom = VirtualDom::new(app);
-//     let edits = dom.rebuild();
-//     dbg!(edits);
-// }
-
-// #[test]
-// fn mixed_dynamic() {
-//     fn app(cx: Scope) -> Element {
-//         static MyTemplate: TemplateDef = TemplateDef {
-//             id: "my-template",
-//             static_nodes: &[TemplateNode::Element {
-//                 tag: "div",
-//                 attributes: &[],
-//                 nodes: &[
-//                     TemplateNode::StaticText("Hello, world!"),
-//                     TemplateNode::DynamicText,
-//                 ],
-//             }],
-//             dynamic_nodes: &[],
-//         };
-
-//         let val = cx.use_hook(|| 0);
-//         *val += 1;
-
-//         let fact = NodeFactory::new(&cx);
-
-//         Some(VNode::Template(fact.bump().alloc(VTemplateRef {
-//             def: &MyTemplate,
-//             dynamic_nodes: fact.bump().alloc([fact.text(format_args!("{val}"))]),
-//         })))
-//     }
-
-//     let mut dom = VirtualDom::new(app);
-//     let edits = dom.rebuild();
-//     dbg!(edits);
-
-//     let edits = dom.hard_diff(ScopeId(0));
-//     dbg!(edits);
-
-//     let edits = dom.hard_diff(ScopeId(0));
-//     dbg!(edits);
-
-//     let edits = dom.hard_diff(ScopeId(0));
-//     dbg!(edits);
-// }
-
-// #[test]
-// fn mixes() {
-//     fn app(cx: Scope) -> Element {
-//         static MyTemplate: TemplateDef = TemplateDef {
-//             id: "my-template",
-//             static_nodes: &[TemplateNode::Element {
-//                 tag: "div",
-//                 attributes: &[],
-//                 nodes: &[
-//                     TemplateNode::StaticText("Hello, world!"),
-//                     TemplateNode::DynamicText,
-//                 ],
-//             }],
-//             dynamic_nodes: &[],
-//         };
-
-//         let val = cx.use_hook(|| 1);
-//         *val += 1;
-
-//         let fact = NodeFactory::new(&cx);
-
-//         if *val % 2 == 0 {
-//             Some(VNode::Template(fact.bump().alloc(VTemplateRef {
-//                 def: &MyTemplate,
-//                 dynamic_nodes: fact.bump().alloc([fact.text(format_args!("{val}"))]),
-//             })))
-//         } else {
-//             Some(fact.text(format_args!("Hello, world! {val}")))
-//         }
-//     }
-
-//     let mut dom = VirtualDom::new(app);
-//     let edits = dom.rebuild();
-//     dbg!(edits);
-
-//     let edits = dom.hard_diff(ScopeId(0));
-//     dbg!(edits);
-
-//     let edits = dom.hard_diff(ScopeId(0));
-//     dbg!(edits);
-
-//     let edits = dom.hard_diff(ScopeId(0));
-//     dbg!(edits);
-// }

+ 26 - 0
packages/core/tests/simple_syntax.rs

@@ -0,0 +1,26 @@
+use dioxus_core::prelude::*;
+
+fn app(cx: Scope) -> Element {
+    todo!();
+    // render! {
+    //      Suspend {
+    //          delay: Duration::from_millis(100),
+    //          fallback: rsx! { "Loading..." },
+    //          ChildAsync {}
+    //          ChildAsync {}
+    //          ChildAsync {}
+    //      }
+    // }
+}
+
+async fn ChildAsync(cx: Scope<'_>) -> Element {
+    todo!()
+}
+
+#[test]
+fn it_works() {
+    let mut dom = VirtualDom::new(app);
+
+    let mut mutations = vec![];
+    dom.rebuild(&mut mutations);
+}

+ 1 - 1
packages/dioxus/Cargo.toml

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

+ 18 - 10
packages/dioxus/tests/rsx_syntax.rs

@@ -1,20 +1,20 @@
 use dioxus::prelude::*;
-use dioxus_core::{Attribute, TemplateAttribute};
-use dioxus_edit_stream::*;
 
 fn basic_syntax_is_a_template(cx: Scope) -> Element {
     let asd = 123;
     let var = 123;
 
     cx.render(rsx! {
-        div { class: "asd", class: "{asd}",
+        div {
+            class: "asd",
+            class: "{asd}",
             onclick: move |_| {},
+
             div { "{var}" }
             div {
                 h1 { "var" }
                 p { "you're great!" }
-                div {
-                    background_color: "red",
+                div { background_color: "red",
                     h1 { "var" }
                     div {
                         b { "asd" }
@@ -29,16 +29,24 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
 
 fn basic_template(cx: Scope) -> Element {
     cx.render(rsx! {
-        div {"hi!"}
+        div {
+            (0..2).map(|i| rsx! {
+                div { "asd" }
+            })
+        }
     })
 }
 
 #[test]
 fn basic_prints() {
-    let dom = VirtualDom::new(basic_template);
+    let mut dom = VirtualDom::new(basic_template);
+
+    let mut edits = Vec::new();
 
-    let renderer = dioxus_edit_stream::Mutations::default();
-    dom.rebuild(&mut renderer);
+    dom.rebuild(&mut edits);
 
-    dbg!(renderer.edits);
+    dbg!(edits);
+    // let renderer = dioxus_edit_stream::Mutations::default();
+    //
+    // dbg!(renderer.edits);
 }

+ 18 - 17
packages/html/src/events.rs

@@ -40,28 +40,29 @@ pub mod on {
                         // mut callback: impl FnMut(UiEvent<$data>) + 'a,
                     ) -> Listener<'a>
                     {
-                        let bump = &factory.bump();
+                        // let bump = &factory.bump();
 
 
-                        use dioxus_core::{AnyEvent};
-                        // we can't allocate unsized in bumpalo's box, so we need to craft the box manually
-                        // safety: this is essentially the same as calling Box::new() but manually
-                        // The box is attached to the lifetime of the bumpalo allocator
-                        let cb: &mut dyn FnMut(AnyEvent) = bump.alloc(move |evt: AnyEvent| {
-                            let event = evt.downcast::<$data>().unwrap();
-                            callback(event)
-                        });
+                        // // we can't allocate unsized in bumpalo's box, so we need to craft the box manually
+                        // // safety: this is essentially the same as calling Box::new() but manually
+                        // // The box is attached to the lifetime of the bumpalo allocator
+                        // let cb: &mut dyn FnMut(AnyEvent) = bump.alloc(move |evt: AnyEvent| {
+                        //     let event = evt.downcast::<$data>().unwrap();
+                        //     callback(event)
+                        // });
 
-                        let callback: BumpBox<dyn FnMut(AnyEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
+                        // let callback: BumpBox<dyn FnMut(AnyEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
 
-                        // ie oncopy
-                        let event_name = stringify!($name);
+                        // // ie oncopy
+                        // let event_name = stringify!($name);
 
-                        // ie copy
-                        let shortname: &'static str = &event_name[2..];
+                        // // ie copy
+                        // let shortname: &'static str = &event_name[2..];
 
-                        let handler = bump.alloc(std::cell::RefCell::new(Some(callback)));
-                        factory.listener(shortname, handler)
+                        // let handler = bump.alloc(std::cell::RefCell::new(Some(callback)));
+                        // factory.listener(shortname, handler)
+
+                        todo!()
                     }
                 )*
             )*
@@ -1423,7 +1424,7 @@ impl KeyCode {
     }
 }
 
-pub(crate) fn _event_meta(event: &UserEvent) -> (bool, EventPriority) {
+pub(crate) fn _event_meta<T>(event: &UiEvent<T>) -> (bool, EventPriority) {
     use EventPriority::*;
 
     match event.name {

+ 43 - 36
packages/rsx/src/lib.rs

@@ -17,10 +17,6 @@ mod errors;
 // mod attributes;
 mod component;
 mod element;
-// #[cfg(any(feature = "hot-reload", debug_assertions))]
-// mod elements;
-// #[cfg(any(feature = "hot-reload", debug_assertions))]
-// mod error;
 mod ifmt;
 mod node;
 mod template;
@@ -173,23 +169,40 @@ impl ToTokens for CallBody {
         let listener_printer = context.dynamic_listeners.iter();
 
         out_tokens.append_all(quote! {
-            LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
-                __cx.template_ref(
-                    ::dioxus::core::Template {
-                        id: ::dioxus::core::get_line_num!(),
-                        roots: &[ #roots ]
-                    },
-                    __cx.bump().alloc([
-                       #( #node_printer ),*
-                    ]),
-                    __cx.bump().alloc([
-                       #( #attr_printer ),*
-                    ]),
-                    __cx.bump().alloc([
-                       #( #listener_printer ),*
-                    ]),
-                    None
-                )
+            // LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
+            //     __cx.template_ref(
+            //         ::dioxus::core::Template {
+            //             id: ::dioxus::core::get_line_num!(),
+            //             roots: &[ #roots ]
+            //         },
+            //         __cx.bump().alloc([
+            //            #( #node_printer ),*
+            //         ]),
+            //         __cx.bump().alloc([
+            //            #( #attr_printer ),*
+            //         ]),
+            //         __cx.bump().alloc([
+            //            #( #listener_printer ),*
+            //         ]),
+            //         None
+            //     )
+            // })
+
+
+            ::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
+                static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
+                    id: ::dioxus::core::get_line_num!(),
+                    roots: &[ #roots ]
+                };
+
+                ::dioxus::core::VNode {
+                    node_id: Default::default(),
+                    parent: None,
+                    template: TEMPLATE,
+                    root_ids: __cx.bump().alloc([]),
+                    dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
+                    dynamic_attrs: __cx.bump().alloc([]),
+                }
             })
         })
     }
@@ -221,22 +234,16 @@ impl CallBody {
                 roots: &[]
             };
 
-            LazyNodes::new(TEMPLATE, move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
-                todo!()
+            LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
+                ::dioxus::core::VNode {
+                    node_id: Default::default(),
+                    parent: None,
+                    template: &TEMPLATE,
+                    root_ids: __cx.bump().alloc([]),
+                    dynamic_nodes: __cx.bump().alloc([]),
+                    dynamic_attrs: __cx.bump().alloc([]),
+                }
             })
         })
-        // let template = TemplateBuilder::from_roots(self.roots.clone());
-        // let inner = if let Some(template) = template {
-        //     quote! { #template }
-        // } else {
-        //     let children = &self.roots;
-        //     if children.len() == 1 {
-        //         let inner = &self.roots[0];
-        //         quote! { #inner }
-        //     } else {
-        //         quote! { __cx.fragment_root([ #(#children),* ]) }
-        //     }
-        // };
-        // out_tokens.append_all(inner);
     }
 }