Browse Source

wip: rewrite core to be template focused

Jonathan Kelley 2 years ago
parent
commit
23603aaaf5
34 changed files with 3162 additions and 2441 deletions
  1. 0 0
      packages/core/src.old/arbitrary_value.rs
  2. 1246 0
      packages/core/src.old/diff.rs
  3. 0 0
      packages/core/src.old/events.rs
  4. 0 0
      packages/core/src.old/lazynodes.rs
  5. 131 0
      packages/core/src.old/lib.rs
  6. 98 0
      packages/core/src.old/mutations.rs
  7. 0 0
      packages/core/src.old/nodes.old.rs
  8. 0 0
      packages/core/src.old/nodes/arbitrary_value.rs
  9. 0 0
      packages/core/src.old/nodes/component.rs
  10. 0 0
      packages/core/src.old/nodes/element.rs
  11. 0 0
      packages/core/src.old/nodes/factory.rs
  12. 0 0
      packages/core/src.old/nodes/fragment.rs
  13. 0 0
      packages/core/src.old/nodes/mod.old.rs
  14. 88 0
      packages/core/src.old/nodes/mod.rs
  15. 0 0
      packages/core/src.old/nodes/placeholder.rs
  16. 0 0
      packages/core/src.old/nodes/suspense.rs
  17. 0 0
      packages/core/src.old/nodes/template.rs
  18. 0 0
      packages/core/src.old/nodes/text.rs
  19. 0 0
      packages/core/src.old/properties.rs
  20. 1012 0
      packages/core/src.old/scopes.rs
  21. 0 0
      packages/core/src.old/util.rs
  22. 0 0
      packages/core/src.old/virtual_dom.rs
  23. 71 0
      packages/core/src/any_props.rs
  24. 30 0
      packages/core/src/arena.rs
  25. 24 0
      packages/core/src/bump_frame.rs
  26. 7 0
      packages/core/src/component.rs
  27. 114 0
      packages/core/src/create.rs
  28. 15 1243
      packages/core/src/diff.rs
  29. 3 0
      packages/core/src/element.rs
  30. 63 119
      packages/core/src/lib.rs
  31. 40 93
      packages/core/src/mutations.rs
  32. 129 0
      packages/core/src/nodes.rs
  33. 62 0
      packages/core/src/scope_arena.rs
  34. 29 986
      packages/core/src/scopes.rs

+ 0 - 0
packages/core/src/arbitrary_value.rs → packages/core/src.old/arbitrary_value.rs


+ 1246 - 0
packages/core/src.old/diff.rs

@@ -0,0 +1,1246 @@
+#![warn(clippy::pedantic)]
+#![allow(clippy::cast_possible_truncation)]
+
+//! This module contains the stateful [`DiffState`] and all methods to diff [`VNode`]s, their properties, and their children.
+//!
+//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
+//! of mutations for the renderer to apply.
+//!
+//! ## Notice:
+//!
+//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
+//! Components, Fragments, Suspense, `SubTree` memoization, incremental diffing, cancellation, pausing, priority
+//! scheduling, and additional batching operations.
+//!
+//! ## Implementation Details:
+//!
+//! ### IDs for elements
+//! --------------------
+//! All nodes are addressed by their IDs.
+//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
+//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
+//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive
+//! garbage collection as the [`crate::VirtualDom`] replaces old nodes.
+//!
+//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
+//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly
+//! and brick the user's page.
+//!
+//! ### Fragment Support
+//! --------------------
+//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
+//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
+//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`crate::innerlude::NodeFactory`] - it is
+//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
+//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
+//! the platform.
+//!
+//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
+//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
+//!
+//! ## Subtree Memoization
+//! -----------------------
+//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can
+//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
+//! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
+//! ```rust, ignore
+//! rsx!( div { class: "hello world", "this node is entirely static" } )
+//! ```
+//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to
+//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
+//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
+//! a way of short-circuiting the most expensive checks.
+//!
+//! ## Bloom Filter and Heuristics
+//! ------------------------------
+//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
+//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a
+//! bump arena after first render, the number of hooks, and the number of nodes in the tree.
+//!
+//! ## Garbage Collection
+//! ---------------------
+//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage
+//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then
+//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient.
+//!
+//! The IDs used by the key/map are just an index into a Vec. This means that Dioxus will drive the key allocation strategy
+//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
+//! for the client. As new nodes are created, old nodes will be over-written.
+//!
+//! ## Further Reading and Thoughts
+//! ----------------------------
+//! There are more ways of increasing diff performance here that are currently not implemented.
+//! - Strong memoization of subtrees.
+//! - Guided diffing.
+//! - Certain web-dom-specific optimizations.
+//!
+//! More info on how to improve this diffing algorithm:
+//!  - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
+
+use crate::{
+    innerlude::{
+        AnyProps, ElementId, Renderer, ScopeArena, ScopeId, TemplateNode, VComponent, VElement,
+        VFragment, VNode, VTemplate, VText,
+    },
+    AttributeValue,
+};
+use fxhash::{FxHashMap, FxHashSet};
+use smallvec::{smallvec, SmallVec};
+
+pub(crate) struct DiffState<'a, 'bump, R: Renderer<'bump>> {
+    pub(crate) scopes: &'bump ScopeArena,
+    pub(crate) mutations: &'a mut R,
+    pub(crate) force_diff: bool,
+    pub(crate) element_stack: SmallVec<[ElementId; 10]>,
+    pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
+}
+
+impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
+    pub fn new(scopes: &'b ScopeArena, renderer: &'a mut R) -> Self {
+        Self {
+            scopes,
+            mutations: renderer,
+            force_diff: false,
+            element_stack: smallvec![],
+            scope_stack: smallvec![],
+        }
+    }
+
+    pub fn diff_scope(&mut self, scopeid: ScopeId) {
+        let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
+        let scope = self.scopes.get_scope(scopeid).unwrap();
+
+        self.scope_stack.push(scopeid);
+        self.element_stack.push(scope.container);
+        self.diff_node(old, new);
+        self.element_stack.pop();
+        self.scope_stack.pop();
+
+        self.mutations.mark_dirty_scope(scopeid);
+    }
+
+    pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
+        use VNode::{Component, Element, Fragment, Template, Text};
+
+        // Same node by ref, no need to diff.
+        if std::ptr::eq(old_node, new_node) {
+            return;
+        }
+
+        match (old_node, new_node) {
+            (Text(old), Text(new)) => self.diff_text(old, new, old_node, new_node),
+            (Element(old), Element(new)) => self.diff_element(old, new, old_node, new_node),
+            (Component(old), Component(new)) => self.diff_component(old_node, new_node, *old, *new),
+            (Fragment(old), Fragment(new)) => self.diff_fragment(old, new),
+            (Template(old), Template(new)) => self.diff_templates(old, new, old_node, new_node),
+
+            (
+                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
+                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
+            ) => self.replace_node(old_node, new_node),
+        }
+    }
+
+    pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
+        match node {
+            VNode::Text(vtext) => self.create_text(vtext, node),
+            VNode::Element(element) => self.create_element(element, node),
+            VNode::Fragment(frag) => self.create_fragment(frag),
+            VNode::Component(component) => self.create_component_node(*component),
+            VNode::Template(template) => self.create_template_node(template, node),
+        }
+    }
+
+    fn create_text(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
+        let real_id = self.scopes.reserve_node(node);
+        text.id.set(Some(real_id));
+        self.mutations.create_text_node(text.text, real_id);
+        1
+    }
+
+    fn create_element(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
+        let VElement {
+            tag: tag_name,
+            listeners,
+            attributes,
+            children,
+            namespace,
+            id: dom_id,
+            parent: parent_id,
+            ..
+        } = &element;
+
+        parent_id.set(self.element_stack.last().copied());
+
+        let real_id = self.scopes.reserve_node(node);
+
+        dom_id.set(Some(real_id));
+
+        self.element_stack.push(real_id);
+        {
+            self.mutations.create_element(tag_name, *namespace, real_id);
+
+            let cur_scope_id = self.current_scope();
+
+            for listener in listeners.iter() {
+                listener.mounted_node.set(real_id);
+                self.mutations.new_event_listener(listener, cur_scope_id);
+            }
+
+            for attr in attributes.iter() {
+                self.mutations
+                    .set_attribute(attr.name, attr.value, attr.namespace, real_id);
+            }
+
+            if !children.is_empty() {
+                self.create_and_append_children(children);
+            }
+        }
+        self.element_stack.pop();
+
+        1
+    }
+
+    fn create_fragment(&mut self, frag: &'b VFragment<'b>) -> usize {
+        self.create_children(frag.children)
+    }
+
+    fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize {
+        let parent_idx = self.current_scope();
+
+        // the component might already exist - if it does, we need to reuse it
+        // this makes figure out when to drop the component more complicated
+        let new_idx = if let Some(idx) = vcomponent.scope.get() {
+            assert!(self.scopes.get_scope(idx).is_some());
+            idx
+        } else {
+            // Insert a new scope into our component list
+            let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap();
+            let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
+            self.scopes.new_with_key(
+                vcomponent.user_fc,
+                props,
+                Some(parent_idx),
+                self.element_stack.last().copied().unwrap(),
+                0,
+            )
+        };
+
+        // Actually initialize the caller's slot with the right address
+        vcomponent.scope.set(Some(new_idx));
+
+        log::trace!(
+            "created component \"{}\", id: {:?} parent {:?}",
+            vcomponent.fn_name,
+            new_idx,
+            parent_idx,
+        );
+
+        // if vcomponent.can_memoize {
+        //     // todo: implement promotion logic. save us from boxing props that we don't need
+        // } else {
+        //     // track this component internally so we know the right drop order
+        // }
+
+        self.enter_scope(new_idx);
+
+        let created = {
+            // Run the scope for one iteration to initialize it
+            self.scopes.run_scope(new_idx);
+            self.mutations.mark_dirty_scope(new_idx);
+
+            // Take the node that was just generated from running the component
+            let nextnode = self.scopes.fin_head(new_idx);
+            self.create_node(nextnode)
+        };
+
+        self.leave_scope();
+
+        created
+    }
+
+    pub(crate) fn diff_text(
+        &mut self,
+        old: &'b VText<'b>,
+        new: &'b VText<'b>,
+        _old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+    ) {
+        // if the node is comming back not assigned, that means it was borrowed but removed
+        let root = match old.id.get() {
+            Some(id) => id,
+            None => self.scopes.reserve_node(new_node),
+        };
+
+        if old.text != new.text {
+            self.mutations.set_text(new.text, root);
+        }
+
+        self.scopes.update_node(new_node, root);
+
+        new.id.set(Some(root));
+    }
+
+    fn diff_templates(
+        &mut self,
+        old: &'b VTemplate<'b>,
+        new: &'b VTemplate<'b>,
+        old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+    ) {
+        if old.template.id != new.template.id {
+            return self.replace_node(old_node, new_node);
+        }
+
+        // if they're the same, just diff the dynamic nodes directly
+        for (left, right) in old.dynamic_nodes.iter().zip(new.dynamic_nodes.iter()) {
+            self.diff_node(&left.node, &right.node);
+        }
+
+        for (left, right) in old.dynamic_attrs.iter().zip(new.dynamic_attrs.iter()) {
+            let id = left.mounted_element.get();
+            right.mounted_element.set(id);
+
+            for (left, right) in right.attrs.iter().zip(left.attrs.iter()) {
+                if right.value != left.value || right.volatile {
+                    self.mutations
+                        .set_attribute(right.name, right.value, right.namespace, id);
+                }
+            }
+
+            // There's not really any diffing that needs to happen for listeners
+            for listener in right.listeners {
+                listener.mounted_node.set(id);
+            }
+        }
+    }
+
+    fn create_static_template_nodes(&mut self, node: &'b TemplateNode, id: ElementId) {
+        match *node {
+            TemplateNode::Element {
+                tag,
+                attrs,
+                children,
+                namespace,
+            } => {
+                self.mutations.create_element(tag, namespace, id);
+                for attr in attrs {
+                    self.mutations.set_attribute(
+                        attr.name,
+                        AttributeValue::Text(attr.value),
+                        attr.namespace,
+                        id,
+                    );
+                }
+                for child in children.iter() {
+                    self.create_static_template_nodes(child, id);
+                }
+                self.mutations.append_children(children.len() as u32);
+            }
+            TemplateNode::Text(text) => self.mutations.create_text_node(text, id),
+            TemplateNode::Dynamic(_) => self.mutations.create_placeholder(id),
+        }
+    }
+
+    /// Create the template from scratch using instructions, cache it, and then use the instructions to build it
+    ///
+    /// This would be way easier if the ID could just be unique *after* cloning
+    ///
+    /// If we traversed the template
+    fn create_template_node(&mut self, template: &'b VTemplate<'b>, node: &'b VNode<'b>) -> usize {
+        // Reserve a single node for all the template nodes to reuse
+        template.node_id.set(self.scopes.reserve_node(node));
+
+        // Save the template if it doesn't exist
+        // todo: use &mut cache instead of borrowed cache
+        let mut templates = self.scopes.template_cache.borrow_mut();
+        if !templates.contains(&template.template) {
+            template
+                .template
+                .roots
+                .into_iter()
+                .for_each(|node| self.create_static_template_nodes(node, template.node_id.get()));
+
+            self.mutations
+                .save(template.template.id, template.template.roots.len() as u32);
+
+            templates.insert(template.template);
+        }
+
+        // Walk the roots backwards, creating nodes and assigning IDs
+        let mut dynamic_attrs = template.dynamic_attrs.iter().peekable();
+        let mut dynamic_nodes = template.dynamic_nodes.iter().peekable();
+
+        let mut on_stack = 0;
+        for (root_idx, root) in template.template.roots.iter().enumerate() {
+            on_stack += match root {
+                TemplateNode::Dynamic(id) => self.create_node(&template.dynamic_nodes[*id].node),
+                TemplateNode::Element { .. } | TemplateNode::Text(_) => 1,
+            };
+
+            // we're on top of a node that has a dynamic attribute for a descndent
+            // Set that attribute now before the stack gets in a weird state
+            // Roots may not be more than 255 nodes long, enforced by the macro
+            while let Some(loc) = dynamic_attrs.next_if(|attr| attr.pathway[0] == root_idx as u8) {
+                // Attach all the elementIDs to the nodes with dynamic content
+                let id = self.scopes.reserve_node(node);
+                self.mutations.assign_id(&loc.pathway[1..], id);
+                loc.mounted_element.set(id);
+
+                for attr in loc.attrs {
+                    self.mutations
+                        .set_attribute(attr.name, attr.value, attr.namespace, id);
+                }
+
+                for listener in loc.listeners {
+                    listener.mounted_node.set(id);
+                }
+            }
+
+            while let Some(dyn_node) = dynamic_nodes.next_if(|f| f.pathway[0] == root_idx as u8) {
+                // we're on top of a node that has a dynamic node for a descndent
+                // Set that node now
+                // Roots may not be more than 255 nodes long, enforced by the macro
+                if dyn_node.pathway[0] == root_idx as u8 {
+                    let created = self.create_node(&dyn_node.node);
+
+                    self.mutations
+                        .replace_descendant(&dyn_node.pathway[1..], created as u32);
+                }
+            }
+        }
+
+        on_stack
+    }
+
+    fn diff_element(
+        &mut self,
+        old: &'b VElement<'b>,
+        new: &'b VElement<'b>,
+        old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+    ) {
+        // if the node is comming back not assigned, that means it was borrowed but removed
+        let root = match old.id.get() {
+            Some(id) => id,
+            None => self.scopes.reserve_node(new_node),
+        };
+
+        // If the element type is completely different, the element needs to be re-rendered completely
+        // This is an optimization React makes due to how users structure their code
+        //
+        // This case is rather rare (typically only in non-keyed lists)
+        if new.tag != old.tag || new.namespace != old.namespace {
+            self.replace_node(old_node, new_node);
+            return;
+        }
+
+        self.scopes.update_node(new_node, root);
+
+        new.id.set(Some(root));
+        new.parent.set(old.parent.get());
+
+        // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
+        // element to modify its attributes.
+        // it would result in fewer instructions if we just set the id directly.
+        // it would also clean up this code some, but that's not very important anyways
+
+        // Diff Attributes
+        //
+        // It's extraordinarily rare to have the number/order of attributes change
+        // In these cases, we just completely erase the old set and make a new set
+        //
+        // TODO: take a more efficient path than this
+        if old.attributes.len() == new.attributes.len() {
+            for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
+                if old_attr.value != new_attr.value || new_attr.volatile {
+                    self.mutations.set_attribute(
+                        new_attr.name,
+                        new_attr.value,
+                        new_attr.namespace,
+                        root,
+                    );
+                }
+            }
+        } else {
+            for attribute in old.attributes {
+                self.mutations.remove_attribute(attribute, root);
+            }
+            for attribute in new.attributes {
+                self.mutations.set_attribute(
+                    attribute.name,
+                    attribute.value,
+                    attribute.namespace,
+                    root,
+                );
+            }
+        }
+
+        // Diff listeners
+        //
+        // It's extraordinarily rare to have the number/order of listeners change
+        // In the cases where the listeners change, we completely wipe the data attributes and add new ones
+        //
+        // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
+        //
+        // TODO: take a more efficient path than this
+        let cur_scope_id = self.current_scope();
+
+        if old.listeners.len() == new.listeners.len() {
+            for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
+                new_l.mounted_node.set(old_l.mounted_node.get());
+                if old_l.event != new_l.event {
+                    self.mutations.remove_event_listener(old_l.event, root);
+                    self.mutations.new_event_listener(new_l, cur_scope_id);
+                }
+            }
+        } else {
+            for listener in old.listeners {
+                self.mutations.remove_event_listener(listener.event, root);
+            }
+            for listener in new.listeners {
+                listener.mounted_node.set(root);
+                self.mutations.new_event_listener(listener, cur_scope_id);
+            }
+        }
+
+        match (old.children.len(), new.children.len()) {
+            (0, 0) => {}
+            (0, _) => {
+                self.mutations.push_root(root);
+                let created = self.create_children(new.children);
+                self.mutations.append_children(created as u32);
+                self.mutations.pop_root();
+            }
+            (_, _) => self.diff_children(old.children, new.children),
+        };
+    }
+
+    fn diff_component(
+        &mut self,
+        old_node: &'b VNode<'b>,
+        new_node: &'b VNode<'b>,
+        old: &'b VComponent<'b>,
+        new: &'b VComponent<'b>,
+    ) {
+        let scope_addr = old
+            .scope
+            .get()
+            .expect("existing component nodes should have a scope");
+
+        // Make sure we're dealing with the same component (by function pointer)
+        if old.user_fc == new.user_fc {
+            self.enter_scope(scope_addr);
+            {
+                // Make sure the new component vnode is referencing the right scope id
+                new.scope.set(Some(scope_addr));
+
+                // make sure the component's caller function is up to date
+                let scope = self
+                    .scopes
+                    .get_scope(scope_addr)
+                    .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
+
+                // take the new props out regardless
+                // when memoizing, push to the existing scope if memoization happens
+                let new_props = new
+                    .props
+                    .borrow_mut()
+                    .take()
+                    .expect("new component props should exist");
+
+                let should_diff = {
+                    if old.can_memoize {
+                        // safety: we trust the implementation of "memoize"
+                        let props_are_the_same = unsafe {
+                            let new_ref = new_props.as_ref();
+                            scope.props.borrow().as_ref().unwrap().memoize(new_ref)
+                        };
+                        !props_are_the_same || self.force_diff
+                    } else {
+                        true
+                    }
+                };
+
+                if should_diff {
+                    let _old_props = scope
+                        .props
+                        .replace(unsafe { std::mem::transmute(Some(new_props)) });
+
+                    // this should auto drop the previous props
+                    self.scopes.run_scope(scope_addr);
+                    self.mutations.mark_dirty_scope(scope_addr);
+
+                    self.diff_node(
+                        self.scopes.wip_head(scope_addr),
+                        self.scopes.fin_head(scope_addr),
+                    );
+                } else {
+                    // memoization has taken place
+                    drop(new_props);
+                };
+            }
+            self.leave_scope();
+        } else {
+            self.replace_node(old_node, new_node);
+        }
+    }
+
+    fn diff_fragment(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
+        todo!()
+        // // This is the case where options or direct vnodes might be used.
+        // // In this case, it's faster to just skip ahead to their diff
+        // if old.children.len() == 1 && new.children.len() == 1 {
+        //     if !std::ptr::eq(old, new) {
+        //         self.diff_node(&old.children[0], &new.children[0]);
+        //     }
+        //     return;
+        // }
+
+        // debug_assert!(!old.children.is_empty());
+        // debug_assert!(!new.children.is_empty());
+
+        // self.diff_children(old.children, new.children);
+    }
+
+    // Diff the given set of old and new children.
+    //
+    // The parent must be on top of the change list stack when this function is
+    // entered:
+    //
+    //     [... parent]
+    //
+    // the change list stack is in the same state when this function returns.
+    //
+    // If old no anchors are provided, then it's assumed that we can freely append to the parent.
+    //
+    // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
+    //
+    // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
+    // to an element, and appending makes sense.
+    fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        if std::ptr::eq(old, new) {
+            return;
+        }
+
+        // Remember, fragments can never be empty (they always have a single child)
+        match (old, new) {
+            ([], []) => {}
+            ([], _) => self.create_and_append_children(new),
+            (_, []) => self.remove_nodes(old, true),
+            _ => {
+                let new_is_keyed = new[0].key().is_some();
+                let old_is_keyed = old[0].key().is_some();
+
+                debug_assert!(
+                    new.iter().all(|n| n.key().is_some() == new_is_keyed),
+                    "all siblings must be keyed or all siblings must be non-keyed"
+                );
+                debug_assert!(
+                    old.iter().all(|o| o.key().is_some() == old_is_keyed),
+                    "all siblings must be keyed or all siblings must be non-keyed"
+                );
+
+                if new_is_keyed && old_is_keyed {
+                    self.diff_keyed_children(old, new);
+                } else {
+                    self.diff_non_keyed_children(old, new);
+                }
+            }
+        }
+    }
+
+    // Diff children that are not keyed.
+    //
+    // The parent must be on the top of the change list stack when entering this
+    // function:
+    //
+    //     [... parent]
+    //
+    // the change list stack is in the same state when this function returns.
+    fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        use std::cmp::Ordering;
+
+        // Handled these cases in `diff_children` before calling this function.
+        debug_assert!(!new.is_empty());
+        debug_assert!(!old.is_empty());
+
+        match old.len().cmp(&new.len()) {
+            Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
+            Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
+            Ordering::Equal => {}
+        }
+
+        for (new, old) in new.iter().zip(old.iter()) {
+            self.diff_node(old, new);
+        }
+    }
+
+    // Diffing "keyed" children.
+    //
+    // With keyed children, we care about whether we delete, move, or create nodes
+    // versus mutate existing nodes in place. Presumably there is some sort of CSS
+    // transition animation that makes the virtual DOM diffing algorithm
+    // observable. By specifying keys for nodes, we know which virtual DOM nodes
+    // must reuse (or not reuse) the same physical DOM nodes.
+    //
+    // This is loosely based on Inferno's keyed patching implementation. However, we
+    // have to modify the algorithm since we are compiling the diff down into change
+    // list instructions that will be executed later, rather than applying the
+    // changes to the DOM directly as we compare virtual DOMs.
+    //
+    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
+    //
+    // The stack is empty upon entry.
+    fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        if cfg!(debug_assertions) {
+            let mut keys = fxhash::FxHashSet::default();
+            let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
+                keys.clear();
+                for child in children {
+                    let key = child.key();
+                    debug_assert!(
+                        key.is_some(),
+                        "if any sibling is keyed, all siblings must be keyed"
+                    );
+                    keys.insert(key);
+                }
+                debug_assert_eq!(
+                    children.len(),
+                    keys.len(),
+                    "keyed siblings must each have a unique key"
+                );
+            };
+            assert_unique_keys(old);
+            assert_unique_keys(new);
+        }
+
+        // First up, we diff all the nodes with the same key at the beginning of the
+        // children.
+        //
+        // `shared_prefix_count` is the count of how many nodes at the start of
+        // `new` and `old` share the same keys.
+        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
+            Some(count) => count,
+            None => return,
+        };
+
+        // Ok, we now hopefully have a smaller range of children in the middle
+        // within which to re-order nodes with the same keys, remove old nodes with
+        // now-unused keys, and create new nodes with fresh keys.
+
+        let old_middle = &old[left_offset..(old.len() - right_offset)];
+        let new_middle = &new[left_offset..(new.len() - right_offset)];
+
+        debug_assert!(
+            !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
+            "keyed children must have the same number of children"
+        );
+
+        if new_middle.is_empty() {
+            // remove the old elements
+            self.remove_nodes(old_middle, true);
+        } else if old_middle.is_empty() {
+            // there were no old elements, so just create the new elements
+            // we need to find the right "foothold" though - we shouldn't use the "append" at all
+            if left_offset == 0 {
+                // insert at the beginning of the old list
+                let foothold = &old[old.len() - right_offset];
+                self.create_and_insert_before(new_middle, foothold);
+            } else if right_offset == 0 {
+                // insert at the end  the old list
+                let foothold = old.last().unwrap();
+                self.create_and_insert_after(new_middle, foothold);
+            } else {
+                // inserting in the middle
+                let foothold = &old[left_offset - 1];
+                self.create_and_insert_after(new_middle, foothold);
+            }
+        } else {
+            self.diff_keyed_middle(old_middle, new_middle);
+        }
+    }
+
+    /// Diff both ends of the children that share keys.
+    ///
+    /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
+    ///
+    /// If there is no offset, then this function returns None and the diffing is complete.
+    fn diff_keyed_ends(
+        &mut self,
+        old: &'b [VNode<'b>],
+        new: &'b [VNode<'b>],
+    ) -> Option<(usize, usize)> {
+        let mut left_offset = 0;
+
+        for (old, new) in old.iter().zip(new.iter()) {
+            // abort early if we finally run into nodes with different keys
+            if old.key() != new.key() {
+                break;
+            }
+            self.diff_node(old, new);
+            left_offset += 1;
+        }
+
+        // If that was all of the old children, then create and append the remaining
+        // new children and we're finished.
+        if left_offset == old.len() {
+            self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
+            return None;
+        }
+
+        // And if that was all of the new children, then remove all of the remaining
+        // old children and we're finished.
+        if left_offset == new.len() {
+            self.remove_nodes(&old[left_offset..], true);
+            return None;
+        }
+
+        // if the shared prefix is less than either length, then we need to walk backwards
+        let mut right_offset = 0;
+        for (old, new) in old.iter().rev().zip(new.iter().rev()) {
+            // abort early if we finally run into nodes with different keys
+            if old.key() != new.key() {
+                break;
+            }
+            self.diff_node(old, new);
+            right_offset += 1;
+        }
+
+        Some((left_offset, right_offset))
+    }
+
+    // The most-general, expensive code path for keyed children diffing.
+    //
+    // We find the longest subsequence within `old` of children that are relatively
+    // ordered the same way in `new` (via finding a longest-increasing-subsequence
+    // of the old child's index within `new`). The children that are elements of
+    // this subsequence will remain in place, minimizing the number of DOM moves we
+    // will have to do.
+    //
+    // Upon entry to this function, the change list stack must be empty.
+    //
+    // This function will load the appropriate nodes onto the stack and do diffing in place.
+    //
+    // Upon exit from this function, it will be restored to that same self.
+    #[allow(clippy::too_many_lines)]
+    fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+        /*
+        1. Map the old keys into a numerical ordering based on indices.
+        2. Create a map of old key to its index
+        3. Map each new key to the old key, carrying over the old index.
+            - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
+            - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
+
+        now, we should have a list of integers that indicates where in the old list the new items map to.
+
+        4. Compute the LIS of this list
+            - this indicates the longest list of new children that won't need to be moved.
+
+        5. Identify which nodes need to be removed
+        6. Identify which nodes will need to be diffed
+
+        7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.
+            - if the item already existed, just move it to the right place.
+
+        8. Finally, generate instructions to remove any old children.
+        9. Generate instructions to finally diff children that are the same between both
+        */
+
+        // 0. Debug sanity checks
+        // Should have already diffed the shared-key prefixes and suffixes.
+        debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
+        debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
+
+        // 1. Map the old keys into a numerical ordering based on indices.
+        // 2. Create a map of old key to its index
+        // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
+        let old_key_to_old_index = old
+            .iter()
+            .enumerate()
+            .map(|(i, o)| (o.key().unwrap(), i))
+            .collect::<FxHashMap<_, _>>();
+
+        let mut shared_keys = FxHashSet::default();
+
+        // 3. Map each new key to the old key, carrying over the old index.
+        let new_index_to_old_index = new
+            .iter()
+            .map(|node| {
+                let key = node.key().unwrap();
+                if let Some(&index) = old_key_to_old_index.get(&key) {
+                    shared_keys.insert(key);
+                    index
+                } else {
+                    u32::MAX as usize
+                }
+            })
+            .collect::<Vec<_>>();
+
+        // If none of the old keys are reused by the new children, then we remove all the remaining old children and
+        // create the new children afresh.
+        if shared_keys.is_empty() {
+            if let Some(first_old) = old.get(0) {
+                self.remove_nodes(&old[1..], true);
+                let nodes_created = self.create_children(new);
+                self.replace_inner(first_old, nodes_created);
+            } else {
+                // I think this is wrong - why are we appending?
+                // only valid of the if there are no trailing elements
+                self.create_and_append_children(new);
+            }
+            return;
+        }
+
+        // remove any old children that are not shared
+        // todo: make this an iterator
+        for child in old {
+            let key = child.key().unwrap();
+            if !shared_keys.contains(&key) {
+                self.remove_nodes([child], true);
+            }
+        }
+
+        // 4. Compute the LIS of this list
+        let mut lis_sequence = Vec::default();
+        lis_sequence.reserve(new_index_to_old_index.len());
+
+        let mut predecessors = vec![0; new_index_to_old_index.len()];
+        let mut starts = vec![0; new_index_to_old_index.len()];
+
+        longest_increasing_subsequence::lis_with(
+            &new_index_to_old_index,
+            &mut lis_sequence,
+            |a, b| a < b,
+            &mut predecessors,
+            &mut starts,
+        );
+
+        // the lis comes out backwards, I think. can't quite tell.
+        lis_sequence.sort_unstable();
+
+        // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
+        if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
+            lis_sequence.pop();
+        }
+
+        for idx in &lis_sequence {
+            self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
+        }
+
+        let mut nodes_created = 0;
+
+        // add mount instruction for the first items not covered by the lis
+        let last = *lis_sequence.last().unwrap();
+        if last < (new.len() - 1) {
+            for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
+                let new_idx = idx + last + 1;
+                let old_index = new_index_to_old_index[new_idx];
+                if old_index == u32::MAX as usize {
+                    nodes_created += self.create_node(new_node);
+                } else {
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
+                }
+            }
+
+            self.mutations.insert_after(
+                self.find_last_element(&new[last]).unwrap(),
+                nodes_created as u32,
+            );
+            nodes_created = 0;
+        }
+
+        // for each spacing, generate a mount instruction
+        let mut lis_iter = lis_sequence.iter().rev();
+        let mut last = *lis_iter.next().unwrap();
+        for next in lis_iter {
+            if last - next > 1 {
+                for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
+                    let new_idx = idx + next + 1;
+                    let old_index = new_index_to_old_index[new_idx];
+                    if old_index == u32::MAX as usize {
+                        nodes_created += self.create_node(new_node);
+                    } else {
+                        self.diff_node(&old[old_index], new_node);
+                        nodes_created += self.push_all_real_nodes(new_node);
+                    }
+                }
+
+                self.mutations.insert_before(
+                    self.find_first_element(&new[last]).unwrap(),
+                    nodes_created as u32,
+                );
+
+                nodes_created = 0;
+            }
+            last = *next;
+        }
+
+        // add mount instruction for the last items not covered by the lis
+        let first_lis = *lis_sequence.first().unwrap();
+        if first_lis > 0 {
+            for (idx, new_node) in new[..first_lis].iter().enumerate() {
+                let old_index = new_index_to_old_index[idx];
+                if old_index == u32::MAX as usize {
+                    nodes_created += self.create_node(new_node);
+                } else {
+                    self.diff_node(&old[old_index], new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
+                }
+            }
+
+            self.mutations.insert_before(
+                self.find_first_element(&new[first_lis]).unwrap(),
+                nodes_created as u32,
+            );
+        }
+    }
+
+    fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) {
+        let nodes_created = self.create_node(new);
+        self.replace_inner(old, nodes_created);
+    }
+
+    fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) {
+        match old {
+            VNode::Element(el) => {
+                let id = old
+                    .try_mounted_id()
+                    .unwrap_or_else(|| panic!("broke on {:?}", old));
+
+                self.mutations.replace_with(id, nodes_created as u32);
+                self.remove_nodes(el.children, false);
+                self.scopes.collect_garbage(id);
+            }
+            VNode::Text(_) => {
+                let id = old
+                    .try_mounted_id()
+                    .unwrap_or_else(|| panic!("broke on {:?}", old));
+
+                self.mutations.replace_with(id, nodes_created as u32);
+                self.scopes.collect_garbage(id);
+            }
+            VNode::Fragment(f) => {
+                self.replace_inner(&f.children[0], nodes_created);
+                self.remove_nodes(f.children.iter().skip(1), true);
+            }
+            VNode::Component(c) => {
+                let scope_id = c.scope.get().unwrap();
+                let node = self.scopes.fin_head(scope_id);
+
+                self.enter_scope(scope_id);
+                {
+                    self.replace_inner(node, nodes_created);
+                    let scope = self.scopes.get_scope(scope_id).unwrap();
+                    c.scope.set(None);
+                    let props = scope.props.take().unwrap();
+                    c.props.borrow_mut().replace(props);
+                    self.scopes.try_remove(scope_id);
+                }
+
+                self.leave_scope();
+            }
+            VNode::Template(c) => {
+                todo!()
+                // // let ids = c.root_keys.as_slice_of_cells();
+
+                // self.mutations
+                //     .replace_with(ids[0].get(), nodes_created as u32);
+                // self.scopes.collect_garbage(ids[0].get());
+
+                // for id in ids.iter().skip(1) {
+                //     self.mutations.remove(id.get());
+                //     self.scopes.collect_garbage(id.get());
+                // }
+            }
+        }
+    }
+
+    pub fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>, gen_muts: bool) {
+        for node in nodes {
+            match node {
+                VNode::Text(t) => {
+                    // this check exists because our null node will be removed but does not have an ID
+                    if let Some(id) = t.id.get() {
+                        self.scopes.collect_garbage(id);
+                        t.id.set(None);
+
+                        if gen_muts {
+                            self.mutations.remove(id);
+                        }
+                    }
+                }
+                // VNode::Placeholder(a) => {
+                //     let id = a.id.get().unwrap();
+                //     self.scopes.collect_garbage(id);
+                //     a.id.set(None);
+
+                //     if gen_muts {
+                //         self.mutations.remove(id);
+                //     }
+                // }
+                VNode::Element(e) => {
+                    let id = e.id.get().unwrap();
+
+                    if gen_muts {
+                        self.mutations.remove(id);
+                    }
+
+                    self.scopes.collect_garbage(id);
+                    e.id.set(None);
+
+                    self.remove_nodes(e.children, false);
+                }
+
+                VNode::Fragment(f) => {
+                    self.remove_nodes(f.children, gen_muts);
+                }
+                VNode::Component(c) => {
+                    self.enter_scope(c.scope.get().unwrap());
+                    {
+                        let scope_id = c.scope.get().unwrap();
+                        let root = self.scopes.root_node(scope_id);
+                        self.remove_nodes([root], gen_muts);
+
+                        let scope = self.scopes.get_scope(scope_id).unwrap();
+                        c.scope.set(None);
+
+                        let props = scope.props.take().unwrap();
+                        c.props.borrow_mut().replace(props);
+                        self.scopes.try_remove(scope_id);
+                    }
+                    self.leave_scope();
+                }
+
+                VNode::Template(c) => {}
+            }
+        }
+    }
+
+    fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+        let mut created = 0;
+        for node in nodes {
+            created += self.create_node(node);
+        }
+        created
+    }
+
+    fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) {
+        let created = self.create_children(nodes);
+        self.mutations.append_children(created as u32);
+    }
+
+    fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) {
+        let created = self.create_children(nodes);
+        let last = self.find_last_element(after).unwrap();
+        self.mutations.insert_after(last, created as u32);
+    }
+
+    fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) {
+        let created = self.create_children(nodes);
+        let first = self.find_first_element(before).unwrap();
+        self.mutations.insert_before(first, created as u32);
+    }
+
+    fn current_scope(&self) -> ScopeId {
+        self.scope_stack.last().copied().expect("no current scope")
+    }
+
+    fn enter_scope(&mut self, scope: ScopeId) {
+        self.scope_stack.push(scope);
+    }
+
+    fn leave_scope(&mut self) {
+        self.scope_stack.pop();
+    }
+
+    fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
+        let mut search_node = Some(vnode);
+        loop {
+            match &search_node.take().unwrap() {
+                VNode::Text(t) => break t.id.get(),
+                VNode::Element(t) => break t.id.get(),
+                VNode::Fragment(frag) => search_node = frag.children.last(),
+                VNode::Component(el) => {
+                    let scope_id = el.scope.get().unwrap();
+                    search_node = Some(self.scopes.root_node(scope_id));
+                }
+                VNode::Template(template) => match &template.template.roots[0] {
+                    TemplateNode::Text(_) | TemplateNode::Element { .. } => {
+                        break Some(template.root_ids.last().unwrap().get());
+                    }
+                    TemplateNode::Dynamic(el) => {
+                        search_node = Some(&template.dynamic_nodes[*el].node)
+                    }
+                },
+            }
+        }
+    }
+
+    fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
+        let mut search_node = Some(vnode);
+        loop {
+            match &search_node.take().expect("search node to have an ID") {
+                VNode::Text(t) => break t.id.get(),
+                VNode::Element(t) => break t.id.get(),
+                VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
+                VNode::Component(el) => {
+                    let scope = el.scope.get().expect("element to have a scope assigned");
+                    search_node = Some(self.scopes.root_node(scope));
+                }
+                VNode::Template(template) => match &template.template.roots[0] {
+                    TemplateNode::Text(_) | TemplateNode::Element { .. } => {
+                        break Some(template.root_ids[0].get());
+                    }
+                    TemplateNode::Dynamic(el) => {
+                        search_node = Some(&template.dynamic_nodes[*el].node)
+                    }
+                },
+            }
+        }
+    }
+
+    // recursively push all the nodes of a tree onto the stack and return how many are there
+    fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+        match node {
+            VNode::Text(_) | VNode::Element(_) => {
+                self.mutations.push_root(node.mounted_id());
+                1
+            }
+
+            VNode::Fragment(frag) => {
+                let mut added = 0;
+                for child in frag.children {
+                    added += self.push_all_real_nodes(child);
+                }
+                added
+            }
+            VNode::Component(c) => {
+                let scope_id = c.scope.get().unwrap();
+                let root = self.scopes.root_node(scope_id);
+                self.push_all_real_nodes(root)
+            }
+
+            VNode::Template(template) => {
+                let mut added = 0;
+                for (idx, root) in template.template.roots.iter().enumerate() {
+                    match root {
+                        TemplateNode::Text(_) | TemplateNode::Element { .. } => {
+                            self.mutations.push_root(template.root_ids[idx].get());
+                            added += 1;
+                        }
+                        TemplateNode::Dynamic(did) => {
+                            added += self.push_all_real_nodes(&template.dynamic_nodes[*did].node);
+                        }
+                    }
+                }
+                added
+            }
+        }
+    }
+
+    pub(crate) fn diff_placeholder(&self, old_node: &VNode, new_node: &VNode) {
+        todo!()
+    }
+}

+ 0 - 0
packages/core/src/events.rs → packages/core/src.old/events.rs


+ 0 - 0
packages/core/src/lazynodes.rs → packages/core/src.old/lazynodes.rs


+ 131 - 0
packages/core/src.old/lib.rs

@@ -0,0 +1,131 @@
+#![allow(non_snake_case)]
+#![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
+
+pub(crate) mod diff;
+pub(crate) mod events;
+pub(crate) mod lazynodes;
+pub(crate) mod mutations;
+pub(crate) mod nodes;
+pub(crate) mod properties;
+pub(crate) mod scopes;
+pub(crate) mod virtual_dom;
+
+pub(crate) mod innerlude {
+    pub use crate::events::*;
+    pub use crate::lazynodes::*;
+    pub use crate::mutations::*;
+    pub use crate::nodes::*;
+    pub use crate::properties::*;
+    pub use crate::scopes::*;
+    pub use crate::virtual_dom::*;
+
+    /// 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<VNode<'a>>;
+
+    /// 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;
+
+    /// A list of attributes
+    pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
+}
+
+pub use crate::innerlude::{
+    AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
+    EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
+    SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
+    UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
+};
+
+/// 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::{
+        fc_to_builder, Attributes, Component, Element, EventHandler, LazyNodes, NodeFactory,
+        Properties, Scope, ScopeId, ScopeState, Template, VNode, VirtualDom,
+    };
+}
+
+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;
+}
+
+/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
+pub(crate) mod unsafe_utils {
+    use crate::VNode;
+
+    pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
+        std::mem::transmute(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();
+    )*}
+}
+
+/// 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")
+        )
+    };
+}

+ 98 - 0
packages/core/src.old/mutations.rs

@@ -0,0 +1,98 @@
+//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
+//!
+//! This module contains an internal API to generate these instructions.
+//!
+//! Beware that changing code in this module will break compatibility with
+//! interpreters for these types of DomEdits.
+
+use crate::innerlude::*;
+
+/// A renderer for Dioxus to modify during diffing
+///
+/// The renderer should implement a Stack Machine. IE each call to the below methods are modifications to the renderer's
+/// internal stack for creating and modifying nodes.
+///
+/// Dioxus guarantees that the stack is always in a valid state.
+pub trait Renderer<'a> {
+    /// Load this element onto the stack
+    fn push_root(&mut self, root: ElementId);
+    /// Pop the topmost element from the stack
+    fn pop_root(&mut self);
+    /// Replace the given element with the next m elements on the stack
+    fn replace_with(&mut self, root: ElementId, m: u32);
+
+    /// Insert the next m elements on the stack after the given element
+    fn insert_after(&mut self, root: ElementId, n: u32);
+    /// Insert the next m elements on the stack before the given element
+    fn insert_before(&mut self, root: ElementId, n: u32);
+    /// Append the next n elements on the stack to the n+1 element on the stack
+    fn append_children(&mut self, n: u32);
+
+    /// Create a new element with the given text and ElementId
+    fn create_text_node(&mut self, text: &'a str, root: ElementId);
+    /// Create an element with the given tag name, optional namespace, and ElementId
+    /// Note that namespaces do not cascade down the tree, so the renderer must handle this if it implements namespaces
+    fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId);
+    /// Create a hidden element to be used later for replacement.
+    /// Used in suspense, lists, and other places where we need to hide a node before it is ready to be shown.
+    /// This is up to the renderer to implement, but it should not be visible to the user.
+    fn create_placeholder(&mut self, id: ElementId);
+
+    /// Remove the targeted node from the DOM
+    fn remove(&mut self, root: ElementId);
+    /// Remove an attribute from an existing element
+    fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
+    /// Remove all the children of the given element
+    fn remove_children(&mut self, root: ElementId);
+
+    /// Attach a new listener to the dom
+    fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
+    /// Remove an existing listener from the dom
+    fn remove_event_listener(&mut self, event: &'static str, root: ElementId);
+
+    /// Set the text content of a node
+    fn set_text(&mut self, text: &'a str, root: ElementId);
+    /// Set an attribute on an element
+    fn set_attribute(
+        &mut self,
+        name: &'static str,
+        value: AttributeValue<'a>,
+        namespace: Option<&'a str>,
+        root: ElementId,
+    );
+
+    /// General statistics for doing things that extend outside of the renderer
+    fn mark_dirty_scope(&mut self, scope: ScopeId);
+
+    /// Save the current n nodes to the ID to be loaded later
+    fn save(&mut self, id: &'static str, num: u32);
+    /// Loads a set of saved nodes from the ID into a scratch space
+    fn load(&mut self, id: &'static str, index: u32);
+    /// Assign the element on the stack's descendent the given ID
+    fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
+    /// Replace the given element of the topmost element with the next m elements on the stack
+    /// Is essentially a combination of assign_id and replace_with
+    fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
+}
+
+/*
+div {
+    div {
+        div {
+            div {}
+        }
+    }
+}
+
+push_child(0)
+push_child(1)
+push_child(3)
+push_child(4)
+pop
+pop
+
+clone_node(0)
+set_node(el, [1,2,3,4])
+set_attribute("class", "foo")
+append_child(1)
+*/

+ 0 - 0
packages/core/src/nodes.old.rs → packages/core/src.old/nodes.old.rs


+ 0 - 0
packages/core/src/nodes/arbitrary_value.rs → packages/core/src.old/nodes/arbitrary_value.rs


+ 0 - 0
packages/core/src/nodes/component.rs → packages/core/src.old/nodes/component.rs


+ 0 - 0
packages/core/src/nodes/element.rs → packages/core/src.old/nodes/element.rs


+ 0 - 0
packages/core/src/nodes/factory.rs → packages/core/src.old/nodes/factory.rs


+ 0 - 0
packages/core/src/nodes/fragment.rs → packages/core/src.old/nodes/fragment.rs


+ 0 - 0
packages/core/src/nodes/mod.rs → packages/core/src.old/nodes/mod.old.rs


+ 88 - 0
packages/core/src.old/nodes/mod.rs

@@ -0,0 +1,88 @@
+use std::{cell::Cell, num::NonZeroUsize};
+
+/// A reference to a template along with any context needed to hydrate it
+pub struct VTemplate<'a> {
+    // The ID assigned for the root of this template
+    pub node_id: Cell<ElementId>,
+
+    pub template: &'static Template,
+
+    /// All the dynamic nodes for a template
+    pub dynamic_nodes: &'a [DynamicNode<'a>],
+
+    pub dynamic_attrs: &'a [AttributeLocation<'a>],
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Template {
+    pub id: &'static str,
+
+    pub root: TemplateNode<'static>,
+
+    // todo: locations of dynamic nodes
+    pub node_pathways: &'static [&'static [u8]],
+
+    // todo: locations of dynamic nodes
+    pub attr_pathways: &'static [&'static [u8]],
+}
+
+/// A weird-ish variant of VNodes with way more limited types
+#[derive(Debug, Clone, Copy)]
+pub enum TemplateNode<'a> {
+    /// A simple element
+    Element {
+        tag: &'a str,
+        namespace: Option<&'a str>,
+        attrs: &'a [TemplateAttribute<'a>],
+        children: &'a [TemplateNode<'a>],
+    },
+    Text(&'a str),
+    Dynamic(usize),
+    DynamicText(usize),
+}
+
+pub enum DynamicNode<'a> {
+    // Anything declared in component form
+    // IE in caps or with underscores
+    Component {
+        name: &'static str,
+    },
+
+    // Comes in with string interpolation or from format_args, include_str, etc
+    Text {
+        id: Cell<ElementId>,
+        value: &'static str,
+    },
+
+    // Anything that's coming in as an iterator
+    Fragment {
+        children: &'a [VTemplate<'a>],
+    },
+}
+
+#[derive(Debug)]
+pub struct TemplateAttribute<'a> {
+    pub name: &'static str,
+    pub value: &'a str,
+    pub namespace: Option<&'static str>,
+    pub volatile: bool,
+}
+
+pub struct AttributeLocation<'a> {
+    pub mounted_element: Cell<ElementId>,
+    pub attrs: &'a [Attribute<'a>],
+}
+
+#[derive(Debug)]
+pub struct Attribute<'a> {
+    pub name: &'static str,
+    pub value: &'a str,
+    pub namespace: Option<&'static str>,
+}
+
+#[test]
+fn what_are_the_sizes() {
+    dbg!(std::mem::size_of::<VTemplate>());
+    dbg!(std::mem::size_of::<Template>());
+    dbg!(std::mem::size_of::<TemplateNode>());
+}

+ 0 - 0
packages/core/src/nodes/placeholder.rs → packages/core/src.old/nodes/placeholder.rs


+ 0 - 0
packages/core/src/nodes/suspense.rs → packages/core/src.old/nodes/suspense.rs


+ 0 - 0
packages/core/src/nodes/template.rs → packages/core/src.old/nodes/template.rs


+ 0 - 0
packages/core/src/nodes/text.rs → packages/core/src.old/nodes/text.rs


+ 0 - 0
packages/core/src/properties.rs → packages/core/src.old/properties.rs


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

@@ -0,0 +1,1012 @@
+use crate::{innerlude::*, unsafe_utils::extend_vnode};
+use bumpalo::Bump;
+use futures_channel::mpsc::UnboundedSender;
+use fxhash::FxHashMap;
+use slab::Slab;
+use std::{
+    any::{Any, TypeId},
+    borrow::Borrow,
+    cell::{Cell, RefCell},
+    collections::{HashMap, HashSet},
+    future::Future,
+    pin::Pin,
+    rc::Rc,
+    sync::Arc,
+};
+
+/// for traceability, we use the raw fn pointer to identify the function
+/// we also get the component name, but that's not necessarily unique in the app
+pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
+
+pub(crate) struct Heuristic {
+    hook_arena_size: usize,
+    node_arena_size: usize,
+}
+
+// a slab-like arena with stable references even when new scopes are allocated
+// uses a bump arena as a backing
+//
+// has an internal heuristics engine to pre-allocate arenas to the right size
+pub(crate) struct ScopeArena {
+    pub scope_gen: Cell<usize>,
+    pub bump: Bump,
+    pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
+    pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
+    pub free_scopes: RefCell<Vec<*mut ScopeState>>,
+    pub nodes: RefCell<Slab<*const VNode<'static>>>,
+    pub tasks: Rc<TaskQueue>,
+    pub template_cache: RefCell<HashSet<Template<'static>>>,
+}
+
+impl ScopeArena {
+    pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
+        let bump = Bump::new();
+
+        // allocate a container for the root element
+        // this will *never* show up in the diffing process
+        // todo: figure out why this is necessary. i forgot. whoops.
+        let el = bump.alloc(VElement {
+            tag: "root",
+            namespace: None,
+            key: None,
+            id: Cell::new(Some(ElementId(0))),
+            parent: Default::default(),
+            listeners: &[],
+            attributes: &[],
+            children: &[],
+        });
+
+        let node = bump.alloc(VNode::Element(el));
+        let mut nodes = Slab::new();
+        let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
+
+        debug_assert_eq!(root_id, 0);
+
+        Self {
+            scope_gen: Cell::new(0),
+            bump,
+            scopes: RefCell::new(FxHashMap::default()),
+            heuristics: RefCell::new(FxHashMap::default()),
+            free_scopes: RefCell::new(Vec::new()),
+            nodes: RefCell::new(nodes),
+            tasks: Rc::new(TaskQueue {
+                tasks: RefCell::new(FxHashMap::default()),
+                task_map: RefCell::new(FxHashMap::default()),
+                gen: Cell::new(0),
+                sender,
+            }),
+            template_cache: RefCell::new(HashSet::new()),
+        }
+    }
+
+    /// Safety:
+    /// - Obtaining a mutable reference to any Scope is unsafe
+    /// - Scopes use interior mutability when sharing data into components
+    pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
+        unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
+    }
+
+    pub(crate) fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
+        self.scopes.borrow().get(&id).copied()
+    }
+
+    pub(crate) fn new_with_key(
+        &self,
+        fc_ptr: ComponentPtr,
+        vcomp: Box<dyn AnyProps>,
+        parent_scope: Option<ScopeId>,
+        container: ElementId,
+        subtree: u32,
+    ) -> ScopeId {
+        // Increment the ScopeId system. ScopeIDs are never reused
+        let new_scope_id = ScopeId(self.scope_gen.get());
+        self.scope_gen.set(self.scope_gen.get() + 1);
+
+        // Get the height of the scope
+        let height = parent_scope
+            .and_then(|id| self.get_scope(id).map(|scope| scope.height + 1))
+            .unwrap_or_default();
+
+        let parent_scope = parent_scope.and_then(|f| self.get_scope_raw(f));
+
+        /*
+        This scopearena aggressively reuses old scopes when possible.
+        We try to minimize the new allocations for props/arenas.
+
+        However, this will probably lead to some sort of fragmentation.
+        I'm not exactly sure how to improve this today.
+        */
+        if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
+            // reuse the old scope
+            let scope = unsafe { &mut *old_scope };
+
+            scope.container = container;
+            scope.our_arena_idx = new_scope_id;
+            scope.parent_scope = parent_scope;
+            scope.height = height;
+            scope.fnptr = fc_ptr;
+            scope.props.get_mut().replace(vcomp);
+            scope.subtree.set(subtree);
+            scope.frames[0].reset();
+            scope.frames[1].reset();
+            scope.shared_contexts.get_mut().clear();
+            scope.items.get_mut().listeners.clear();
+            scope.items.get_mut().borrowed_props.clear();
+            scope.hook_idx.set(0);
+            scope.hook_vals.get_mut().clear();
+
+            let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
+            debug_assert!(any_item.is_none());
+        } else {
+            // else create a new scope
+            let (node_capacity, hook_capacity) = self
+                .heuristics
+                .borrow()
+                .get(&fc_ptr)
+                .map(|h| (h.node_arena_size, h.hook_arena_size))
+                .unwrap_or_default();
+
+            self.scopes.borrow_mut().insert(
+                new_scope_id,
+                self.bump.alloc(ScopeState {
+                    container,
+                    our_arena_idx: new_scope_id,
+                    parent_scope,
+                    height,
+                    fnptr: fc_ptr,
+                    props: RefCell::new(Some(vcomp)),
+                    frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
+
+                    // todo: subtrees
+                    subtree: Cell::new(0),
+                    is_subtree_root: Cell::default(),
+
+                    generation: 0.into(),
+
+                    tasks: self.tasks.clone(),
+                    shared_contexts: RefCell::default(),
+
+                    items: RefCell::new(SelfReferentialItems {
+                        listeners: Vec::default(),
+                        borrowed_props: Vec::default(),
+                    }),
+
+                    hook_arena: Bump::new(),
+                    hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
+                    hook_idx: Cell::default(),
+                }),
+            );
+        }
+
+        new_scope_id
+    }
+
+    // Removes a scope and its descendents from the arena
+    pub fn try_remove(&self, id: ScopeId) {
+        self.ensure_drop_safety(id);
+
+        // Dispose of any ongoing tasks
+        let mut tasks = self.tasks.tasks.borrow_mut();
+        let mut task_map = self.tasks.task_map.borrow_mut();
+        if let Some(cur_tasks) = task_map.remove(&id) {
+            for task in cur_tasks {
+                tasks.remove(&task);
+            }
+        }
+
+        // Safety:
+        // - ensure_drop_safety ensures that no references to this scope are in use
+        // - this raw pointer is removed from the map
+        let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
+        scope.reset();
+
+        self.free_scopes.borrow_mut().push(scope);
+    }
+
+    pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
+        let mut els = self.nodes.borrow_mut();
+        let entry = els.vacant_entry();
+        let key = entry.key();
+        let id = ElementId(key);
+        let node = unsafe { extend_vnode(node) };
+        entry.insert(node as *const _);
+        id
+    }
+
+    pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
+        let node = unsafe { extend_vnode(node) };
+        *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
+    }
+
+    pub fn collect_garbage(&self, id: ElementId) {
+        self.nodes.borrow_mut().remove(id.0);
+    }
+
+    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
+    /// causing UB in our tree.
+    ///
+    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
+    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
+    /// any possible references to the data in the hook list.
+    ///
+    /// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
+    /// all listeners and borrowed props so we can clear them here.
+    ///
+    /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
+    /// be dropped.
+    pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
+        if let Some(scope) = self.get_scope(scope_id) {
+            let mut items = scope.items.borrow_mut();
+
+            // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
+            // run the hooks (which hold an &mut Reference)
+            // recursively call ensure_drop_safety on all children
+            items.borrowed_props.drain(..).for_each(|comp| {
+                if let Some(scope_id) = comp.scope.get() {
+                    self.ensure_drop_safety(scope_id);
+                }
+                drop(comp.props.take());
+            });
+
+            // Now that all the references are gone, we can safely drop our own references in our listeners.
+            items
+                .listeners
+                .drain(..)
+                .for_each(|listener| drop(listener.callback.borrow_mut().take()));
+        }
+    }
+
+    pub(crate) fn run_scope(&self, id: ScopeId) {
+        // 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
+        self.ensure_drop_safety(id);
+
+        // todo: we *know* that this is aliased by the contents of the scope itself
+        let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
+
+        log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);
+
+        // Safety:
+        // - We dropped the listeners, so no more &mut T can be used while these are held
+        // - All children nodes that rely on &mut T are replaced with a new reference
+        scope.hook_idx.set(0);
+
+        {
+            // Safety:
+            // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
+            unsafe { scope.reset_wip_frame() };
+
+            let items = scope.items.borrow();
+
+            // guarantee that we haven't screwed up - there should be no latent references anywhere
+            debug_assert!(items.listeners.is_empty());
+            debug_assert!(items.borrowed_props.is_empty());
+        }
+
+        /*
+        If the component returns None, then we fill in a placeholder node. This will wipe what was there.
+        An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
+
+        Instead, we just treat the `None` as a shortcut to placeholder.
+        If the developer wants to prevent a scope from updating, they should control its memoization instead.
+
+        Also, the way we implement hooks allows us to cut rendering short before the next hook is recalled.
+        I'm not sure if React lets you abort the component early, but we let you do that.
+        */
+
+        let props = scope.props.borrow();
+        let render = props.as_ref().unwrap();
+        if let Some(node) = render.render(scope) {
+            let frame = scope.wip_frame();
+            let node = frame.bump.alloc(node);
+            frame.node.set(unsafe { extend_vnode(node) });
+        } else {
+            let frame = scope.wip_frame();
+            let node = frame.bump.alloc(VNode::Text(frame.bump.alloc(VText {
+                id: Cell::default(),
+                text: "asd",
+            })));
+            frame.node.set(unsafe { extend_vnode(node) });
+        }
+
+        // make the "wip frame" contents the "finished frame"
+        // any future dipping into completed nodes after "render" will go through "fin head"
+        scope.cycle_frame();
+    }
+
+    pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) {
+        let nodes = self.nodes.borrow();
+        let mut cur_el = Some(element);
+
+        let state = Rc::new(BubbleState::new());
+
+        while let Some(id) = cur_el.take() {
+            if let Some(el) = nodes.get(id.0) {
+                let real_el = unsafe { &**el };
+
+                if let VNode::Element(real_el) = real_el {
+                    for listener in real_el.listeners.borrow().iter() {
+                        if listener.event == event.name {
+                            if state.canceled.get() {
+                                // stop bubbling if canceled
+                                return;
+                            }
+
+                            let mut cb = listener.callback.borrow_mut();
+                            if let Some(cb) = cb.as_mut() {
+                                // todo: arcs are pretty heavy to clone
+                                // we really want to convert arc to rc
+                                // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
+                                // we could convert arc to rc internally or something
+                                (cb)(AnyEvent {
+                                    bubble_state: state.clone(),
+                                    data: event.data.clone(),
+                                });
+                            }
+
+                            if !event.bubbles {
+                                return;
+                            }
+                        }
+                    }
+
+                    cur_el = real_el.parent.get();
+                }
+            }
+        }
+    }
+
+    // The head of the bumpframe is the first linked NodeLink
+    pub fn wip_head(&self, id: ScopeId) -> &VNode {
+        let scope = self.get_scope(id).unwrap();
+        let frame = scope.wip_frame();
+        let node = unsafe { &*frame.node.get() };
+        unsafe { extend_vnode(node) }
+    }
+
+    // The head of the bumpframe is the first linked NodeLink
+    pub fn fin_head(&self, id: ScopeId) -> &VNode {
+        let scope = self.get_scope(id).unwrap();
+        let frame = scope.fin_frame();
+        let node = unsafe { &*frame.node.get() };
+        unsafe { extend_vnode(node) }
+    }
+
+    pub fn root_node(&self, id: ScopeId) -> &VNode {
+        self.fin_head(id)
+    }
+
+    // this is totally okay since all our nodes are always in a valid state
+    pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
+        self.nodes
+            .borrow()
+            .get(id.0)
+            .copied()
+            .map(|ptr| unsafe { extend_vnode(&*ptr) })
+    }
+}
+
+/// Components in Dioxus use the "Context" object to interact with their lifecycle.
+///
+/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
+///
+/// For the most part, the only method you should be using regularly is `render`.
+///
+/// ## Example
+///
+/// ```ignore
+/// #[derive(Props)]
+/// struct ExampleProps {
+///     name: String
+/// }
+///
+/// fn Example(cx: Scope<ExampleProps>) -> Element {
+///     cx.render(rsx!{ div {"Hello, {cx.props.name}"} })
+/// }
+/// ```
+pub struct Scope<'a, P = ()> {
+    /// The internal ScopeState for this component
+    pub scope: &'a ScopeState,
+
+    /// The props for this component
+    pub props: &'a P,
+}
+
+impl<P> Copy for Scope<'_, P> {}
+impl<P> Clone for Scope<'_, P> {
+    fn clone(&self) -> Self {
+        Self {
+            scope: self.scope,
+            props: self.props,
+        }
+    }
+}
+
+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
+    }
+}
+
+/// 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);
+
+/// 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,
+}
+
+/// Every component in Dioxus is represented by a `ScopeState`.
+///
+/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
+///
+/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
+/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
+///
+/// We expose the `Scope` type so downstream users can traverse the Dioxus [`VirtualDom`] for whatever
+/// use case they might have.
+pub struct ScopeState {
+    pub(crate) parent_scope: Option<*mut ScopeState>,
+    pub(crate) container: ElementId,
+    pub(crate) our_arena_idx: ScopeId,
+    pub(crate) height: u32,
+    pub(crate) fnptr: ComponentPtr,
+
+    // todo: subtrees
+    pub(crate) is_subtree_root: Cell<bool>,
+    pub(crate) subtree: Cell<u32>,
+    pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
+
+    // nodes, items
+    pub(crate) frames: [BumpFrame; 2],
+    pub(crate) generation: Cell<u32>,
+    pub(crate) items: RefCell<SelfReferentialItems<'static>>,
+
+    // hooks
+    pub(crate) hook_arena: Bump,
+    pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
+    pub(crate) hook_idx: Cell<usize>,
+
+    // shared state -> todo: move this out of scopestate
+    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
+    pub(crate) tasks: Rc<TaskQueue>,
+}
+
+pub struct SelfReferentialItems<'a> {
+    pub(crate) listeners: Vec<&'a Listener<'a>>,
+    pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
+}
+
+// Public methods exposed to libraries and components
+impl ScopeState {
+    /// Get the subtree ID that this scope belongs to.
+    ///
+    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
+    /// the mutations to the correct window/portal/subtree.
+    ///
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
+    /// dom.rebuild();
+    ///
+    /// let base = dom.base_scope();
+    ///
+    /// assert_eq!(base.subtree(), 0);
+    /// ```
+    ///
+    /// todo: enable
+    pub(crate) fn _subtree(&self) -> u32 {
+        self.subtree.get()
+    }
+
+    /// Create a new subtree with this scope as the root of the subtree.
+    ///
+    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
+    /// the mutations to the correct window/portal/subtree.
+    ///
+    /// This method
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// fn App(cx: Scope) -> Element {
+    ///     render!(div { "Subtree {id}"})
+    /// };
+    /// ```
+    ///
+    /// todo: enable subtree
+    pub(crate) fn _create_subtree(&self) -> Option<u32> {
+        if self.is_subtree_root.get() {
+            None
+        } else {
+            todo!()
+        }
+    }
+
+    /// 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
+    }
+
+    /// 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_scope.map(|p| unsafe { &*p }.our_arena_idx)
+    }
+
+    /// 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.our_arena_idx
+    }
+
+    /// 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))))
+    }
+
+    /// 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());
+    }
+
+    /// 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");
+    }
+
+    /// Get the Root Node of this scope
+    pub fn root_node(&self) -> &VNode {
+        let node = unsafe { &*self.fin_frame().node.get() };
+        unsafe { std::mem::transmute(node) }
+    }
+
+    /// 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_scope;
+
+        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_scope;
+        }
+
+        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_scope;
+
+            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_scope;
+            }
+            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.our_arena_idx))
+            .expect("Scheduler should exist");
+
+        self.tasks.spawn(self.our_arena_idx, 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.our_arena_idx))
+            .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(NodeFactory {
+            scope: self,
+            bump: &self.wip_frame().bump,
+        }))
+    }
+
+    /// 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.
+                "###,
+            )
+    }
+
+    /// The "work in progress frame" represents the frame that is currently being worked on.
+    pub(crate) fn wip_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 {
+            0 => &self.frames[0],
+            _ => &self.frames[1],
+        }
+    }
+
+    /// Mutable access to the "work in progress frame" - used to clear it
+    pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
+        match self.generation.get() & 1 {
+            0 => &mut self.frames[0],
+            _ => &mut self.frames[1],
+        }
+    }
+
+    /// Access to the frame where finalized nodes existed
+    pub(crate) fn fin_frame(&self) -> &BumpFrame {
+        match self.generation.get() & 1 {
+            1 => &self.frames[0],
+            _ => &self.frames[1],
+        }
+    }
+
+    /// Reset this component's frame
+    ///
+    /// # Safety:
+    ///
+    /// This method breaks every reference of every [`VNode`] in the current frame.
+    ///
+    /// Calling reset itself is not usually a big deal, but we consider it important
+    /// due to the complex safety guarantees we need to uphold.
+    pub(crate) unsafe fn reset_wip_frame(&mut self) {
+        self.wip_frame_mut().bump.reset();
+    }
+
+    /// Cycle to the next generation
+    pub(crate) fn cycle_frame(&self) {
+        self.generation.set(self.generation.get() + 1);
+    }
+
+    // todo: disable bookkeeping on drop (unncessary)
+    pub(crate) fn reset(&mut self) {
+        // first: book keaping
+        self.hook_idx.set(0);
+        self.parent_scope = None;
+        self.generation.set(0);
+        self.is_subtree_root.set(false);
+        self.subtree.set(0);
+
+        // next: shared context data
+        self.shared_contexts.get_mut().clear();
+
+        // next: reset the node data
+        let SelfReferentialItems {
+            borrowed_props,
+            listeners,
+        } = self.items.get_mut();
+        borrowed_props.clear();
+        listeners.clear();
+        self.frames[0].reset();
+        self.frames[1].reset();
+
+        // Free up the hook values
+        self.hook_vals.get_mut().drain(..).for_each(|state| {
+            let as_mut = unsafe { &mut *state };
+            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
+            drop(boxed);
+        });
+
+        // Finally, clear the hook arena
+        self.hook_arena.reset();
+    }
+}
+
+pub(crate) struct BumpFrame {
+    pub bump: Bump,
+    pub node: Cell<*const VNode<'static>>,
+}
+impl BumpFrame {
+    pub(crate) fn new(capacity: usize) -> Self {
+        let bump = Bump::with_capacity(capacity);
+        let node = bump.alloc(VText {
+            text: "placeholdertext",
+            id: Cell::default(),
+        });
+        let node = bump.alloc(VNode::Text(unsafe {
+            &*(node as *mut VText as *const VText)
+        }));
+        let nodes = Cell::new(node as *const _);
+        Self { bump, node: nodes }
+    }
+
+    pub(crate) fn reset(&mut self) {
+        self.bump.reset();
+        let node = self.bump.alloc(VText {
+            text: "placeholdertext",
+            id: Cell::default(),
+        });
+        let node = self.bump.alloc(VNode::Text(unsafe {
+            &*(node as *mut VText as *const VText)
+        }));
+        self.node.set(node as *const _);
+    }
+}
+
+pub(crate) struct TaskQueue {
+    pub(crate) tasks: RefCell<FxHashMap<TaskId, InnerTask>>,
+    pub(crate) task_map: RefCell<FxHashMap<ScopeId, HashSet<TaskId>>>,
+    gen: Cell<usize>,
+    sender: UnboundedSender<SchedulerMsg>,
+}
+
+pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
+impl TaskQueue {
+    fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
+        let pinned = Box::pin(task);
+        let id = self.gen.get();
+        self.gen.set(id + 1);
+        let tid = TaskId { id, scope };
+
+        self.tasks.borrow_mut().insert(tid, pinned);
+
+        // also add to the task map
+        // when the component is unmounted we know to remove it from the map
+        self.task_map
+            .borrow_mut()
+            .entry(scope)
+            .or_default()
+            .insert(tid);
+
+        tid
+    }
+
+    fn remove(&self, id: TaskId) {
+        if let Ok(mut tasks) = self.tasks.try_borrow_mut() {
+            tasks.remove(&id);
+            if let Some(task_map) = self.task_map.borrow_mut().get_mut(&id.scope) {
+                task_map.remove(&id);
+            }
+        }
+        // the task map is still around, but it'll be removed when the scope is unmounted
+    }
+
+    pub(crate) fn has_tasks(&self) -> bool {
+        !self.tasks.borrow().is_empty()
+    }
+}
+
+#[test]
+fn sizeof() {
+    dbg!(std::mem::size_of::<ScopeState>());
+}

+ 0 - 0
packages/core/src/util.rs → packages/core/src.old/util.rs


+ 0 - 0
packages/core/src/virtual_dom.rs → packages/core/src.old/virtual_dom.rs


+ 71 - 0
packages/core/src/any_props.rs

@@ -0,0 +1,71 @@
+use std::cell::Cell;
+
+use crate::{
+    component::Component,
+    element::Element,
+    scopes::{Scope, ScopeState},
+};
+
+pub trait AnyProps {
+    fn as_ptr(&self) -> *const ();
+    fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
+    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
+}
+
+pub(crate) struct VComponentProps<P> {
+    pub render_fn: Component<P>,
+    pub memo: unsafe fn(&P, &P) -> bool,
+    pub props: Scope<P>,
+}
+
+impl VComponentProps<()> {
+    pub fn new_empty(render_fn: Component<()>) -> Self {
+        Self {
+            render_fn,
+            memo: <() as PartialEq>::eq,
+            props: Scope {
+                props: (),
+                state: Cell::new(std::ptr::null_mut()),
+            },
+        }
+    }
+}
+
+impl<P> VComponentProps<P> {
+    pub(crate) fn new(
+        render_fn: Component<P>,
+        memo: unsafe fn(&P, &P) -> bool,
+        props: Scope<P>,
+    ) -> Self {
+        Self {
+            render_fn,
+            memo,
+            props,
+        }
+    }
+}
+
+impl<P> AnyProps for VComponentProps<P> {
+    fn as_ptr(&self) -> *const () {
+        &self.props as *const _ as *const ()
+    }
+
+    // Safety:
+    // this will downcast the other ptr as our swallowed type!
+    // you *must* make this check *before* calling this method
+    // if your functions are not the same, then you will downcast a pointer into a different type (UB)
+    unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
+        let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
+        let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
+        (self.memo)(real_us, real_other)
+    }
+
+    fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
+        // Make sure the scope ptr is not null
+        self.props.state.set(scope);
+
+        // Call the render function directly
+        // todo: implement async
+        (self.render_fn)(unsafe { std::mem::transmute(&self.props) })
+    }
+}

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

@@ -0,0 +1,30 @@
+use std::num::NonZeroUsize;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+pub struct ElementId(pub usize);
+
+// pub struct ElementId(pub NonZeroUsize);
+
+// impl Default for ElementId {
+//     fn default() -> Self {
+//         Self(NonZeroUsize::new(1).unwrap())
+//     }
+// }
+
+pub struct ElementArena {
+    counter: usize,
+}
+
+impl Default for ElementArena {
+    fn default() -> Self {
+        Self { counter: 1 }
+    }
+}
+
+impl ElementArena {
+    pub fn next(&mut self) -> ElementId {
+        let id = self.counter;
+        self.counter += 1;
+        ElementId(id)
+    }
+}

+ 24 - 0
packages/core/src/bump_frame.rs

@@ -0,0 +1,24 @@
+use std::cell::Cell;
+
+use bumpalo::Bump;
+
+use crate::nodes::VTemplate;
+
+pub struct BumpFrame {
+    pub bump: Bump,
+    pub node: Cell<*const VTemplate<'static>>,
+}
+impl BumpFrame {
+    pub fn new(capacity: usize) -> Self {
+        let bump = Bump::with_capacity(capacity);
+        Self {
+            bump,
+            node: Cell::new(std::ptr::null()),
+        }
+    }
+
+    pub fn reset(&mut self) {
+        self.bump.reset();
+        self.node.set(std::ptr::null());
+    }
+}

+ 7 - 0
packages/core/src/component.rs

@@ -0,0 +1,7 @@
+// pub trait IntoComponentType<T> {
+//     fn into_component_type(self) -> ComponentType;
+// }
+
+use crate::{element::Element, scopes::Scope};
+
+pub type Component<T = ()> = fn(&Scope<T>) -> Element;

+ 114 - 0
packages/core/src/create.rs

@@ -0,0 +1,114 @@
+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;
+
+impl VirtualDom {
+    /// Create this template and write its mutations
+    pub fn create<'a>(
+        &mut self,
+        mutations: &mut Vec<Mutation<'a>>,
+        template: &'a VTemplate<'a>,
+    ) -> usize {
+        // The best renderers will have tempaltes 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);
+        }
+
+        // Walk the roots backwards, creating nodes and assigning IDs
+        // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
+        let mut dynamic_attrs = template.dynamic_attrs.iter().peekable();
+        let mut dynamic_nodes = template.dynamic_nodes.iter().peekable();
+
+        let mut on_stack = 0;
+        for (root_idx, root) in template.template.roots.iter().enumerate() {
+            on_stack += match root {
+                TemplateNode::Dynamic(id) => {
+                    self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id])
+                }
+                TemplateNode::DynamicText { .. }
+                | TemplateNode::Element { .. }
+                | TemplateNode::Text(_) => 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 {
+                    path: &loc.path[1..],
+                    id,
+                });
+
+                loc.mounted_element.set(id);
+
+                for attr in loc.attrs {
+                    mutations.push(Mutation::SetAttribute {
+                        name: attr.name,
+                        value: attr.value,
+                        id,
+                    });
+                }
+            }
+
+            // We're on top of a node that has a dynamic child for a descndent
+            // Might as well set it now while we can
+            while let Some(node) = dynamic_nodes.next_if(|f| f.path[0] == root_idx as u8) {
+                self.create_dynamic_node(mutations, template, node);
+            }
+        }
+
+        on_stack
+    }
+
+    fn create_static_template(&mut self, mutations: &mut Vec<Mutation>, template: &Template) {
+        todo!("load template")
+    }
+
+    fn create_dynamic_node<'a>(
+        &mut self,
+        mutations: &mut Vec<Mutation<'a>>,
+        template: &VTemplate<'a>,
+        node: &'a DynamicNode<'a>,
+    ) -> usize {
+        match &node.kind {
+            DynamicNodeKind::Text { id, value } => {
+                let new_id = self.elements.next();
+                id.set(new_id);
+                mutations.push(Mutation::HydrateText {
+                    id: new_id,
+                    path: &node.path[1..],
+                    value,
+                });
+
+                1
+            }
+            DynamicNodeKind::Component { props, fn_ptr, .. } => {
+                let id = self.new_scope(*fn_ptr, None, ElementId(0), *props);
+
+                let template = self.run_scope(id);
+
+                todo!("create component has bad data");
+            }
+            DynamicNodeKind::Fragment { children } => children
+                .iter()
+                .fold(0, |acc, child| acc + self.create(mutations, child)),
+        }
+    }
+}

+ 15 - 1243
packages/core/src/diff.rs

@@ -1,1246 +1,18 @@
-#![warn(clippy::pedantic)]
-#![allow(clippy::cast_possible_truncation)]
-
-//! This module contains the stateful [`DiffState`] and all methods to diff [`VNode`]s, their properties, and their children.
-//!
-//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
-//! of mutations for the renderer to apply.
-//!
-//! ## Notice:
-//!
-//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
-//! Components, Fragments, Suspense, `SubTree` memoization, incremental diffing, cancellation, pausing, priority
-//! scheduling, and additional batching operations.
-//!
-//! ## Implementation Details:
-//!
-//! ### IDs for elements
-//! --------------------
-//! All nodes are addressed by their IDs.
-//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
-//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
-//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive
-//! garbage collection as the [`crate::VirtualDom`] replaces old nodes.
-//!
-//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
-//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly
-//! and brick the user's page.
-//!
-//! ### Fragment Support
-//! --------------------
-//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
-//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
-//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`crate::innerlude::NodeFactory`] - it is
-//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
-//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
-//! the platform.
-//!
-//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
-//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
-//!
-//! ## Subtree Memoization
-//! -----------------------
-//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can
-//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
-//! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
-//! ```rust, ignore
-//! rsx!( div { class: "hello world", "this node is entirely static" } )
-//! ```
-//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to
-//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
-//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
-//! a way of short-circuiting the most expensive checks.
-//!
-//! ## Bloom Filter and Heuristics
-//! ------------------------------
-//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
-//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a
-//! bump arena after first render, the number of hooks, and the number of nodes in the tree.
-//!
-//! ## Garbage Collection
-//! ---------------------
-//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage
-//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then
-//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient.
-//!
-//! The IDs used by the key/map are just an index into a Vec. This means that Dioxus will drive the key allocation strategy
-//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
-//! for the client. As new nodes are created, old nodes will be over-written.
-//!
-//! ## Further Reading and Thoughts
-//! ----------------------------
-//! There are more ways of increasing diff performance here that are currently not implemented.
-//! - Strong memoization of subtrees.
-//! - Guided diffing.
-//! - Certain web-dom-specific optimizations.
-//!
-//! More info on how to improve this diffing algorithm:
-//!  - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
-
+use crate::VirtualDom;
+
+use crate::any_props::VComponentProps;
+use crate::arena::ElementArena;
+use crate::component::Component;
+use crate::mutations::Mutation;
+use crate::nodes::{DynamicNode, Template, TemplateId};
+use crate::scopes::Scope;
 use crate::{
-    innerlude::{
-        AnyProps, ElementId, Renderer, ScopeArena, ScopeId, TemplateNode, VComponent, VElement,
-        VFragment, VNode, VTemplate, VText,
-    },
-    AttributeValue,
+    any_props::AnyProps,
+    arena::ElementId,
+    bump_frame::BumpFrame,
+    nodes::VTemplate,
+    scopes::{ComponentPtr, ScopeId, ScopeState},
 };
-use fxhash::{FxHashMap, FxHashSet};
-use smallvec::{smallvec, SmallVec};
-
-pub(crate) struct DiffState<'a, 'bump, R: Renderer<'bump>> {
-    pub(crate) scopes: &'bump ScopeArena,
-    pub(crate) mutations: &'a mut R,
-    pub(crate) force_diff: bool,
-    pub(crate) element_stack: SmallVec<[ElementId; 10]>,
-    pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
-}
-
-impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
-    pub fn new(scopes: &'b ScopeArena, renderer: &'a mut R) -> Self {
-        Self {
-            scopes,
-            mutations: renderer,
-            force_diff: false,
-            element_stack: smallvec![],
-            scope_stack: smallvec![],
-        }
-    }
-
-    pub fn diff_scope(&mut self, scopeid: ScopeId) {
-        let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
-        let scope = self.scopes.get_scope(scopeid).unwrap();
-
-        self.scope_stack.push(scopeid);
-        self.element_stack.push(scope.container);
-        self.diff_node(old, new);
-        self.element_stack.pop();
-        self.scope_stack.pop();
-
-        self.mutations.mark_dirty_scope(scopeid);
-    }
-
-    pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
-        use VNode::{Component, Element, Fragment, Template, Text};
-
-        // Same node by ref, no need to diff.
-        if std::ptr::eq(old_node, new_node) {
-            return;
-        }
-
-        match (old_node, new_node) {
-            (Text(old), Text(new)) => self.diff_text(old, new, old_node, new_node),
-            (Element(old), Element(new)) => self.diff_element(old, new, old_node, new_node),
-            (Component(old), Component(new)) => self.diff_component(old_node, new_node, *old, *new),
-            (Fragment(old), Fragment(new)) => self.diff_fragment(old, new),
-            (Template(old), Template(new)) => self.diff_templates(old, new, old_node, new_node),
-
-            (
-                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
-                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
-            ) => self.replace_node(old_node, new_node),
-        }
-    }
-
-    pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
-        match node {
-            VNode::Text(vtext) => self.create_text(vtext, node),
-            VNode::Element(element) => self.create_element(element, node),
-            VNode::Fragment(frag) => self.create_fragment(frag),
-            VNode::Component(component) => self.create_component_node(*component),
-            VNode::Template(template) => self.create_template_node(template, node),
-        }
-    }
-
-    fn create_text(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
-        let real_id = self.scopes.reserve_node(node);
-        text.id.set(Some(real_id));
-        self.mutations.create_text_node(text.text, real_id);
-        1
-    }
-
-    fn create_element(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
-        let VElement {
-            tag: tag_name,
-            listeners,
-            attributes,
-            children,
-            namespace,
-            id: dom_id,
-            parent: parent_id,
-            ..
-        } = &element;
-
-        parent_id.set(self.element_stack.last().copied());
-
-        let real_id = self.scopes.reserve_node(node);
-
-        dom_id.set(Some(real_id));
-
-        self.element_stack.push(real_id);
-        {
-            self.mutations.create_element(tag_name, *namespace, real_id);
-
-            let cur_scope_id = self.current_scope();
-
-            for listener in listeners.iter() {
-                listener.mounted_node.set(real_id);
-                self.mutations.new_event_listener(listener, cur_scope_id);
-            }
-
-            for attr in attributes.iter() {
-                self.mutations
-                    .set_attribute(attr.name, attr.value, attr.namespace, real_id);
-            }
-
-            if !children.is_empty() {
-                self.create_and_append_children(children);
-            }
-        }
-        self.element_stack.pop();
-
-        1
-    }
-
-    fn create_fragment(&mut self, frag: &'b VFragment<'b>) -> usize {
-        self.create_children(frag.children)
-    }
-
-    fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize {
-        let parent_idx = self.current_scope();
-
-        // the component might already exist - if it does, we need to reuse it
-        // this makes figure out when to drop the component more complicated
-        let new_idx = if let Some(idx) = vcomponent.scope.get() {
-            assert!(self.scopes.get_scope(idx).is_some());
-            idx
-        } else {
-            // Insert a new scope into our component list
-            let props: Box<dyn AnyProps + 'b> = vcomponent.props.borrow_mut().take().unwrap();
-            let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
-            self.scopes.new_with_key(
-                vcomponent.user_fc,
-                props,
-                Some(parent_idx),
-                self.element_stack.last().copied().unwrap(),
-                0,
-            )
-        };
-
-        // Actually initialize the caller's slot with the right address
-        vcomponent.scope.set(Some(new_idx));
-
-        log::trace!(
-            "created component \"{}\", id: {:?} parent {:?}",
-            vcomponent.fn_name,
-            new_idx,
-            parent_idx,
-        );
-
-        // if vcomponent.can_memoize {
-        //     // todo: implement promotion logic. save us from boxing props that we don't need
-        // } else {
-        //     // track this component internally so we know the right drop order
-        // }
-
-        self.enter_scope(new_idx);
-
-        let created = {
-            // Run the scope for one iteration to initialize it
-            self.scopes.run_scope(new_idx);
-            self.mutations.mark_dirty_scope(new_idx);
-
-            // Take the node that was just generated from running the component
-            let nextnode = self.scopes.fin_head(new_idx);
-            self.create_node(nextnode)
-        };
-
-        self.leave_scope();
-
-        created
-    }
-
-    pub(crate) fn diff_text(
-        &mut self,
-        old: &'b VText<'b>,
-        new: &'b VText<'b>,
-        _old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        // if the node is comming back not assigned, that means it was borrowed but removed
-        let root = match old.id.get() {
-            Some(id) => id,
-            None => self.scopes.reserve_node(new_node),
-        };
-
-        if old.text != new.text {
-            self.mutations.set_text(new.text, root);
-        }
-
-        self.scopes.update_node(new_node, root);
-
-        new.id.set(Some(root));
-    }
-
-    fn diff_templates(
-        &mut self,
-        old: &'b VTemplate<'b>,
-        new: &'b VTemplate<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        if old.template.id != new.template.id {
-            return self.replace_node(old_node, new_node);
-        }
-
-        // if they're the same, just diff the dynamic nodes directly
-        for (left, right) in old.dynamic_nodes.iter().zip(new.dynamic_nodes.iter()) {
-            self.diff_node(&left.node, &right.node);
-        }
-
-        for (left, right) in old.dynamic_attrs.iter().zip(new.dynamic_attrs.iter()) {
-            let id = left.mounted_element.get();
-            right.mounted_element.set(id);
-
-            for (left, right) in right.attrs.iter().zip(left.attrs.iter()) {
-                if right.value != left.value || right.volatile {
-                    self.mutations
-                        .set_attribute(right.name, right.value, right.namespace, id);
-                }
-            }
-
-            // There's not really any diffing that needs to happen for listeners
-            for listener in right.listeners {
-                listener.mounted_node.set(id);
-            }
-        }
-    }
-
-    fn create_static_template_nodes(&mut self, node: &'b TemplateNode, id: ElementId) {
-        match *node {
-            TemplateNode::Element {
-                tag,
-                attrs,
-                children,
-                namespace,
-            } => {
-                self.mutations.create_element(tag, namespace, id);
-                for attr in attrs {
-                    self.mutations.set_attribute(
-                        attr.name,
-                        AttributeValue::Text(attr.value),
-                        attr.namespace,
-                        id,
-                    );
-                }
-                for child in children.iter() {
-                    self.create_static_template_nodes(child, id);
-                }
-                self.mutations.append_children(children.len() as u32);
-            }
-            TemplateNode::Text(text) => self.mutations.create_text_node(text, id),
-            TemplateNode::Dynamic(_) => self.mutations.create_placeholder(id),
-        }
-    }
-
-    /// Create the template from scratch using instructions, cache it, and then use the instructions to build it
-    ///
-    /// This would be way easier if the ID could just be unique *after* cloning
-    ///
-    /// If we traversed the template
-    fn create_template_node(&mut self, template: &'b VTemplate<'b>, node: &'b VNode<'b>) -> usize {
-        // Reserve a single node for all the template nodes to reuse
-        template.node_id.set(self.scopes.reserve_node(node));
-
-        // Save the template if it doesn't exist
-        // todo: use &mut cache instead of borrowed cache
-        let mut templates = self.scopes.template_cache.borrow_mut();
-        if !templates.contains(&template.template) {
-            template
-                .template
-                .roots
-                .into_iter()
-                .for_each(|node| self.create_static_template_nodes(node, template.node_id.get()));
-
-            self.mutations
-                .save(template.template.id, template.template.roots.len() as u32);
-
-            templates.insert(template.template);
-        }
-
-        // Walk the roots backwards, creating nodes and assigning IDs
-        let mut dynamic_attrs = template.dynamic_attrs.iter().peekable();
-        let mut dynamic_nodes = template.dynamic_nodes.iter().peekable();
-
-        let mut on_stack = 0;
-        for (root_idx, root) in template.template.roots.iter().enumerate() {
-            on_stack += match root {
-                TemplateNode::Dynamic(id) => self.create_node(&template.dynamic_nodes[*id].node),
-                TemplateNode::Element { .. } | TemplateNode::Text(_) => 1,
-            };
-
-            // we're on top of a node that has a dynamic attribute for a descndent
-            // Set that attribute now before the stack gets in a weird state
-            // Roots may not be more than 255 nodes long, enforced by the macro
-            while let Some(loc) = dynamic_attrs.next_if(|attr| attr.pathway[0] == root_idx as u8) {
-                // Attach all the elementIDs to the nodes with dynamic content
-                let id = self.scopes.reserve_node(node);
-                self.mutations.assign_id(&loc.pathway[1..], id);
-                loc.mounted_element.set(id);
-
-                for attr in loc.attrs {
-                    self.mutations
-                        .set_attribute(attr.name, attr.value, attr.namespace, id);
-                }
-
-                for listener in loc.listeners {
-                    listener.mounted_node.set(id);
-                }
-            }
-
-            while let Some(dyn_node) = dynamic_nodes.next_if(|f| f.pathway[0] == root_idx as u8) {
-                // we're on top of a node that has a dynamic node for a descndent
-                // Set that node now
-                // Roots may not be more than 255 nodes long, enforced by the macro
-                if dyn_node.pathway[0] == root_idx as u8 {
-                    let created = self.create_node(&dyn_node.node);
-
-                    self.mutations
-                        .replace_descendant(&dyn_node.pathway[1..], created as u32);
-                }
-            }
-        }
-
-        on_stack
-    }
-
-    fn diff_element(
-        &mut self,
-        old: &'b VElement<'b>,
-        new: &'b VElement<'b>,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-    ) {
-        // if the node is comming back not assigned, that means it was borrowed but removed
-        let root = match old.id.get() {
-            Some(id) => id,
-            None => self.scopes.reserve_node(new_node),
-        };
-
-        // If the element type is completely different, the element needs to be re-rendered completely
-        // This is an optimization React makes due to how users structure their code
-        //
-        // This case is rather rare (typically only in non-keyed lists)
-        if new.tag != old.tag || new.namespace != old.namespace {
-            self.replace_node(old_node, new_node);
-            return;
-        }
-
-        self.scopes.update_node(new_node, root);
-
-        new.id.set(Some(root));
-        new.parent.set(old.parent.get());
-
-        // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
-        // element to modify its attributes.
-        // it would result in fewer instructions if we just set the id directly.
-        // it would also clean up this code some, but that's not very important anyways
-
-        // Diff Attributes
-        //
-        // It's extraordinarily rare to have the number/order of attributes change
-        // In these cases, we just completely erase the old set and make a new set
-        //
-        // TODO: take a more efficient path than this
-        if old.attributes.len() == new.attributes.len() {
-            for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
-                if old_attr.value != new_attr.value || new_attr.volatile {
-                    self.mutations.set_attribute(
-                        new_attr.name,
-                        new_attr.value,
-                        new_attr.namespace,
-                        root,
-                    );
-                }
-            }
-        } else {
-            for attribute in old.attributes {
-                self.mutations.remove_attribute(attribute, root);
-            }
-            for attribute in new.attributes {
-                self.mutations.set_attribute(
-                    attribute.name,
-                    attribute.value,
-                    attribute.namespace,
-                    root,
-                );
-            }
-        }
-
-        // Diff listeners
-        //
-        // It's extraordinarily rare to have the number/order of listeners change
-        // In the cases where the listeners change, we completely wipe the data attributes and add new ones
-        //
-        // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
-        //
-        // TODO: take a more efficient path than this
-        let cur_scope_id = self.current_scope();
-
-        if old.listeners.len() == new.listeners.len() {
-            for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
-                new_l.mounted_node.set(old_l.mounted_node.get());
-                if old_l.event != new_l.event {
-                    self.mutations.remove_event_listener(old_l.event, root);
-                    self.mutations.new_event_listener(new_l, cur_scope_id);
-                }
-            }
-        } else {
-            for listener in old.listeners {
-                self.mutations.remove_event_listener(listener.event, root);
-            }
-            for listener in new.listeners {
-                listener.mounted_node.set(root);
-                self.mutations.new_event_listener(listener, cur_scope_id);
-            }
-        }
-
-        match (old.children.len(), new.children.len()) {
-            (0, 0) => {}
-            (0, _) => {
-                self.mutations.push_root(root);
-                let created = self.create_children(new.children);
-                self.mutations.append_children(created as u32);
-                self.mutations.pop_root();
-            }
-            (_, _) => self.diff_children(old.children, new.children),
-        };
-    }
-
-    fn diff_component(
-        &mut self,
-        old_node: &'b VNode<'b>,
-        new_node: &'b VNode<'b>,
-        old: &'b VComponent<'b>,
-        new: &'b VComponent<'b>,
-    ) {
-        let scope_addr = old
-            .scope
-            .get()
-            .expect("existing component nodes should have a scope");
-
-        // Make sure we're dealing with the same component (by function pointer)
-        if old.user_fc == new.user_fc {
-            self.enter_scope(scope_addr);
-            {
-                // Make sure the new component vnode is referencing the right scope id
-                new.scope.set(Some(scope_addr));
-
-                // make sure the component's caller function is up to date
-                let scope = self
-                    .scopes
-                    .get_scope(scope_addr)
-                    .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
-
-                // take the new props out regardless
-                // when memoizing, push to the existing scope if memoization happens
-                let new_props = new
-                    .props
-                    .borrow_mut()
-                    .take()
-                    .expect("new component props should exist");
-
-                let should_diff = {
-                    if old.can_memoize {
-                        // safety: we trust the implementation of "memoize"
-                        let props_are_the_same = unsafe {
-                            let new_ref = new_props.as_ref();
-                            scope.props.borrow().as_ref().unwrap().memoize(new_ref)
-                        };
-                        !props_are_the_same || self.force_diff
-                    } else {
-                        true
-                    }
-                };
-
-                if should_diff {
-                    let _old_props = scope
-                        .props
-                        .replace(unsafe { std::mem::transmute(Some(new_props)) });
-
-                    // this should auto drop the previous props
-                    self.scopes.run_scope(scope_addr);
-                    self.mutations.mark_dirty_scope(scope_addr);
-
-                    self.diff_node(
-                        self.scopes.wip_head(scope_addr),
-                        self.scopes.fin_head(scope_addr),
-                    );
-                } else {
-                    // memoization has taken place
-                    drop(new_props);
-                };
-            }
-            self.leave_scope();
-        } else {
-            self.replace_node(old_node, new_node);
-        }
-    }
-
-    fn diff_fragment(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
-        todo!()
-        // // This is the case where options or direct vnodes might be used.
-        // // In this case, it's faster to just skip ahead to their diff
-        // if old.children.len() == 1 && new.children.len() == 1 {
-        //     if !std::ptr::eq(old, new) {
-        //         self.diff_node(&old.children[0], &new.children[0]);
-        //     }
-        //     return;
-        // }
-
-        // debug_assert!(!old.children.is_empty());
-        // debug_assert!(!new.children.is_empty());
-
-        // self.diff_children(old.children, new.children);
-    }
-
-    // Diff the given set of old and new children.
-    //
-    // The parent must be on top of the change list stack when this function is
-    // entered:
-    //
-    //     [... parent]
-    //
-    // the change list stack is in the same state when this function returns.
-    //
-    // If old no anchors are provided, then it's assumed that we can freely append to the parent.
-    //
-    // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
-    //
-    // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
-    // to an element, and appending makes sense.
-    fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
-        // Remember, fragments can never be empty (they always have a single child)
-        match (old, new) {
-            ([], []) => {}
-            ([], _) => self.create_and_append_children(new),
-            (_, []) => self.remove_nodes(old, true),
-            _ => {
-                let new_is_keyed = new[0].key().is_some();
-                let old_is_keyed = old[0].key().is_some();
-
-                debug_assert!(
-                    new.iter().all(|n| n.key().is_some() == new_is_keyed),
-                    "all siblings must be keyed or all siblings must be non-keyed"
-                );
-                debug_assert!(
-                    old.iter().all(|o| o.key().is_some() == old_is_keyed),
-                    "all siblings must be keyed or all siblings must be non-keyed"
-                );
-
-                if new_is_keyed && old_is_keyed {
-                    self.diff_keyed_children(old, new);
-                } else {
-                    self.diff_non_keyed_children(old, new);
-                }
-            }
-        }
-    }
-
-    // Diff children that are not keyed.
-    //
-    // The parent must be on the top of the change list stack when entering this
-    // function:
-    //
-    //     [... parent]
-    //
-    // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
-        use std::cmp::Ordering;
-
-        // Handled these cases in `diff_children` before calling this function.
-        debug_assert!(!new.is_empty());
-        debug_assert!(!old.is_empty());
-
-        match old.len().cmp(&new.len()) {
-            Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
-            Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
-            Ordering::Equal => {}
-        }
-
-        for (new, old) in new.iter().zip(old.iter()) {
-            self.diff_node(old, new);
-        }
-    }
-
-    // Diffing "keyed" children.
-    //
-    // With keyed children, we care about whether we delete, move, or create nodes
-    // versus mutate existing nodes in place. Presumably there is some sort of CSS
-    // transition animation that makes the virtual DOM diffing algorithm
-    // observable. By specifying keys for nodes, we know which virtual DOM nodes
-    // must reuse (or not reuse) the same physical DOM nodes.
-    //
-    // This is loosely based on Inferno's keyed patching implementation. However, we
-    // have to modify the algorithm since we are compiling the diff down into change
-    // list instructions that will be executed later, rather than applying the
-    // changes to the DOM directly as we compare virtual DOMs.
-    //
-    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
-    //
-    // The stack is empty upon entry.
-    fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
-        if cfg!(debug_assertions) {
-            let mut keys = fxhash::FxHashSet::default();
-            let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
-                keys.clear();
-                for child in children {
-                    let key = child.key();
-                    debug_assert!(
-                        key.is_some(),
-                        "if any sibling is keyed, all siblings must be keyed"
-                    );
-                    keys.insert(key);
-                }
-                debug_assert_eq!(
-                    children.len(),
-                    keys.len(),
-                    "keyed siblings must each have a unique key"
-                );
-            };
-            assert_unique_keys(old);
-            assert_unique_keys(new);
-        }
-
-        // First up, we diff all the nodes with the same key at the beginning of the
-        // children.
-        //
-        // `shared_prefix_count` is the count of how many nodes at the start of
-        // `new` and `old` share the same keys.
-        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
-            Some(count) => count,
-            None => return,
-        };
-
-        // Ok, we now hopefully have a smaller range of children in the middle
-        // within which to re-order nodes with the same keys, remove old nodes with
-        // now-unused keys, and create new nodes with fresh keys.
-
-        let old_middle = &old[left_offset..(old.len() - right_offset)];
-        let new_middle = &new[left_offset..(new.len() - right_offset)];
-
-        debug_assert!(
-            !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
-            "keyed children must have the same number of children"
-        );
-
-        if new_middle.is_empty() {
-            // remove the old elements
-            self.remove_nodes(old_middle, true);
-        } else if old_middle.is_empty() {
-            // there were no old elements, so just create the new elements
-            // we need to find the right "foothold" though - we shouldn't use the "append" at all
-            if left_offset == 0 {
-                // insert at the beginning of the old list
-                let foothold = &old[old.len() - right_offset];
-                self.create_and_insert_before(new_middle, foothold);
-            } else if right_offset == 0 {
-                // insert at the end  the old list
-                let foothold = old.last().unwrap();
-                self.create_and_insert_after(new_middle, foothold);
-            } else {
-                // inserting in the middle
-                let foothold = &old[left_offset - 1];
-                self.create_and_insert_after(new_middle, foothold);
-            }
-        } else {
-            self.diff_keyed_middle(old_middle, new_middle);
-        }
-    }
-
-    /// Diff both ends of the children that share keys.
-    ///
-    /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
-    ///
-    /// If there is no offset, then this function returns None and the diffing is complete.
-    fn diff_keyed_ends(
-        &mut self,
-        old: &'b [VNode<'b>],
-        new: &'b [VNode<'b>],
-    ) -> Option<(usize, usize)> {
-        let mut left_offset = 0;
-
-        for (old, new) in old.iter().zip(new.iter()) {
-            // abort early if we finally run into nodes with different keys
-            if old.key() != new.key() {
-                break;
-            }
-            self.diff_node(old, new);
-            left_offset += 1;
-        }
-
-        // If that was all of the old children, then create and append the remaining
-        // new children and we're finished.
-        if left_offset == old.len() {
-            self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
-            return None;
-        }
-
-        // And if that was all of the new children, then remove all of the remaining
-        // old children and we're finished.
-        if left_offset == new.len() {
-            self.remove_nodes(&old[left_offset..], true);
-            return None;
-        }
-
-        // if the shared prefix is less than either length, then we need to walk backwards
-        let mut right_offset = 0;
-        for (old, new) in old.iter().rev().zip(new.iter().rev()) {
-            // abort early if we finally run into nodes with different keys
-            if old.key() != new.key() {
-                break;
-            }
-            self.diff_node(old, new);
-            right_offset += 1;
-        }
-
-        Some((left_offset, right_offset))
-    }
-
-    // The most-general, expensive code path for keyed children diffing.
-    //
-    // We find the longest subsequence within `old` of children that are relatively
-    // ordered the same way in `new` (via finding a longest-increasing-subsequence
-    // of the old child's index within `new`). The children that are elements of
-    // this subsequence will remain in place, minimizing the number of DOM moves we
-    // will have to do.
-    //
-    // Upon entry to this function, the change list stack must be empty.
-    //
-    // This function will load the appropriate nodes onto the stack and do diffing in place.
-    //
-    // Upon exit from this function, it will be restored to that same self.
-    #[allow(clippy::too_many_lines)]
-    fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
-        /*
-        1. Map the old keys into a numerical ordering based on indices.
-        2. Create a map of old key to its index
-        3. Map each new key to the old key, carrying over the old index.
-            - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
-            - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
-
-        now, we should have a list of integers that indicates where in the old list the new items map to.
-
-        4. Compute the LIS of this list
-            - this indicates the longest list of new children that won't need to be moved.
-
-        5. Identify which nodes need to be removed
-        6. Identify which nodes will need to be diffed
-
-        7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.
-            - if the item already existed, just move it to the right place.
-
-        8. Finally, generate instructions to remove any old children.
-        9. Generate instructions to finally diff children that are the same between both
-        */
-
-        // 0. Debug sanity checks
-        // Should have already diffed the shared-key prefixes and suffixes.
-        debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
-        debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
-
-        // 1. Map the old keys into a numerical ordering based on indices.
-        // 2. Create a map of old key to its index
-        // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
-        let old_key_to_old_index = old
-            .iter()
-            .enumerate()
-            .map(|(i, o)| (o.key().unwrap(), i))
-            .collect::<FxHashMap<_, _>>();
-
-        let mut shared_keys = FxHashSet::default();
-
-        // 3. Map each new key to the old key, carrying over the old index.
-        let new_index_to_old_index = new
-            .iter()
-            .map(|node| {
-                let key = node.key().unwrap();
-                if let Some(&index) = old_key_to_old_index.get(&key) {
-                    shared_keys.insert(key);
-                    index
-                } else {
-                    u32::MAX as usize
-                }
-            })
-            .collect::<Vec<_>>();
-
-        // If none of the old keys are reused by the new children, then we remove all the remaining old children and
-        // create the new children afresh.
-        if shared_keys.is_empty() {
-            if let Some(first_old) = old.get(0) {
-                self.remove_nodes(&old[1..], true);
-                let nodes_created = self.create_children(new);
-                self.replace_inner(first_old, nodes_created);
-            } else {
-                // I think this is wrong - why are we appending?
-                // only valid of the if there are no trailing elements
-                self.create_and_append_children(new);
-            }
-            return;
-        }
-
-        // remove any old children that are not shared
-        // todo: make this an iterator
-        for child in old {
-            let key = child.key().unwrap();
-            if !shared_keys.contains(&key) {
-                self.remove_nodes([child], true);
-            }
-        }
-
-        // 4. Compute the LIS of this list
-        let mut lis_sequence = Vec::default();
-        lis_sequence.reserve(new_index_to_old_index.len());
-
-        let mut predecessors = vec![0; new_index_to_old_index.len()];
-        let mut starts = vec![0; new_index_to_old_index.len()];
-
-        longest_increasing_subsequence::lis_with(
-            &new_index_to_old_index,
-            &mut lis_sequence,
-            |a, b| a < b,
-            &mut predecessors,
-            &mut starts,
-        );
-
-        // the lis comes out backwards, I think. can't quite tell.
-        lis_sequence.sort_unstable();
-
-        // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
-        if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
-            lis_sequence.pop();
-        }
-
-        for idx in &lis_sequence {
-            self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
-        }
-
-        let mut nodes_created = 0;
-
-        // add mount instruction for the first items not covered by the lis
-        let last = *lis_sequence.last().unwrap();
-        if last < (new.len() - 1) {
-            for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
-                let new_idx = idx + last + 1;
-                let old_index = new_index_to_old_index[new_idx];
-                if old_index == u32::MAX as usize {
-                    nodes_created += self.create_node(new_node);
-                } else {
-                    self.diff_node(&old[old_index], new_node);
-                    nodes_created += self.push_all_real_nodes(new_node);
-                }
-            }
-
-            self.mutations.insert_after(
-                self.find_last_element(&new[last]).unwrap(),
-                nodes_created as u32,
-            );
-            nodes_created = 0;
-        }
-
-        // for each spacing, generate a mount instruction
-        let mut lis_iter = lis_sequence.iter().rev();
-        let mut last = *lis_iter.next().unwrap();
-        for next in lis_iter {
-            if last - next > 1 {
-                for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
-                    let new_idx = idx + next + 1;
-                    let old_index = new_index_to_old_index[new_idx];
-                    if old_index == u32::MAX as usize {
-                        nodes_created += self.create_node(new_node);
-                    } else {
-                        self.diff_node(&old[old_index], new_node);
-                        nodes_created += self.push_all_real_nodes(new_node);
-                    }
-                }
-
-                self.mutations.insert_before(
-                    self.find_first_element(&new[last]).unwrap(),
-                    nodes_created as u32,
-                );
-
-                nodes_created = 0;
-            }
-            last = *next;
-        }
-
-        // add mount instruction for the last items not covered by the lis
-        let first_lis = *lis_sequence.first().unwrap();
-        if first_lis > 0 {
-            for (idx, new_node) in new[..first_lis].iter().enumerate() {
-                let old_index = new_index_to_old_index[idx];
-                if old_index == u32::MAX as usize {
-                    nodes_created += self.create_node(new_node);
-                } else {
-                    self.diff_node(&old[old_index], new_node);
-                    nodes_created += self.push_all_real_nodes(new_node);
-                }
-            }
-
-            self.mutations.insert_before(
-                self.find_first_element(&new[first_lis]).unwrap(),
-                nodes_created as u32,
-            );
-        }
-    }
-
-    fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) {
-        let nodes_created = self.create_node(new);
-        self.replace_inner(old, nodes_created);
-    }
-
-    fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) {
-        match old {
-            VNode::Element(el) => {
-                let id = old
-                    .try_mounted_id()
-                    .unwrap_or_else(|| panic!("broke on {:?}", old));
-
-                self.mutations.replace_with(id, nodes_created as u32);
-                self.remove_nodes(el.children, false);
-                self.scopes.collect_garbage(id);
-            }
-            VNode::Text(_) => {
-                let id = old
-                    .try_mounted_id()
-                    .unwrap_or_else(|| panic!("broke on {:?}", old));
-
-                self.mutations.replace_with(id, nodes_created as u32);
-                self.scopes.collect_garbage(id);
-            }
-            VNode::Fragment(f) => {
-                self.replace_inner(&f.children[0], nodes_created);
-                self.remove_nodes(f.children.iter().skip(1), true);
-            }
-            VNode::Component(c) => {
-                let scope_id = c.scope.get().unwrap();
-                let node = self.scopes.fin_head(scope_id);
-
-                self.enter_scope(scope_id);
-                {
-                    self.replace_inner(node, nodes_created);
-                    let scope = self.scopes.get_scope(scope_id).unwrap();
-                    c.scope.set(None);
-                    let props = scope.props.take().unwrap();
-                    c.props.borrow_mut().replace(props);
-                    self.scopes.try_remove(scope_id);
-                }
-
-                self.leave_scope();
-            }
-            VNode::Template(c) => {
-                todo!()
-                // // let ids = c.root_keys.as_slice_of_cells();
-
-                // self.mutations
-                //     .replace_with(ids[0].get(), nodes_created as u32);
-                // self.scopes.collect_garbage(ids[0].get());
-
-                // for id in ids.iter().skip(1) {
-                //     self.mutations.remove(id.get());
-                //     self.scopes.collect_garbage(id.get());
-                // }
-            }
-        }
-    }
-
-    pub fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>, gen_muts: bool) {
-        for node in nodes {
-            match node {
-                VNode::Text(t) => {
-                    // this check exists because our null node will be removed but does not have an ID
-                    if let Some(id) = t.id.get() {
-                        self.scopes.collect_garbage(id);
-                        t.id.set(None);
-
-                        if gen_muts {
-                            self.mutations.remove(id);
-                        }
-                    }
-                }
-                // VNode::Placeholder(a) => {
-                //     let id = a.id.get().unwrap();
-                //     self.scopes.collect_garbage(id);
-                //     a.id.set(None);
-
-                //     if gen_muts {
-                //         self.mutations.remove(id);
-                //     }
-                // }
-                VNode::Element(e) => {
-                    let id = e.id.get().unwrap();
-
-                    if gen_muts {
-                        self.mutations.remove(id);
-                    }
-
-                    self.scopes.collect_garbage(id);
-                    e.id.set(None);
-
-                    self.remove_nodes(e.children, false);
-                }
-
-                VNode::Fragment(f) => {
-                    self.remove_nodes(f.children, gen_muts);
-                }
-                VNode::Component(c) => {
-                    self.enter_scope(c.scope.get().unwrap());
-                    {
-                        let scope_id = c.scope.get().unwrap();
-                        let root = self.scopes.root_node(scope_id);
-                        self.remove_nodes([root], gen_muts);
-
-                        let scope = self.scopes.get_scope(scope_id).unwrap();
-                        c.scope.set(None);
-
-                        let props = scope.props.take().unwrap();
-                        c.props.borrow_mut().replace(props);
-                        self.scopes.try_remove(scope_id);
-                    }
-                    self.leave_scope();
-                }
-
-                VNode::Template(c) => {}
-            }
-        }
-    }
-
-    fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
-        let mut created = 0;
-        for node in nodes {
-            created += self.create_node(node);
-        }
-        created
-    }
-
-    fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) {
-        let created = self.create_children(nodes);
-        self.mutations.append_children(created as u32);
-    }
-
-    fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) {
-        let created = self.create_children(nodes);
-        let last = self.find_last_element(after).unwrap();
-        self.mutations.insert_after(last, created as u32);
-    }
-
-    fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) {
-        let created = self.create_children(nodes);
-        let first = self.find_first_element(before).unwrap();
-        self.mutations.insert_before(first, created as u32);
-    }
-
-    fn current_scope(&self) -> ScopeId {
-        self.scope_stack.last().copied().expect("no current scope")
-    }
-
-    fn enter_scope(&mut self, scope: ScopeId) {
-        self.scope_stack.push(scope);
-    }
-
-    fn leave_scope(&mut self) {
-        self.scope_stack.pop();
-    }
-
-    fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-        loop {
-            match &search_node.take().unwrap() {
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Fragment(frag) => search_node = frag.children.last(),
-                VNode::Component(el) => {
-                    let scope_id = el.scope.get().unwrap();
-                    search_node = Some(self.scopes.root_node(scope_id));
-                }
-                VNode::Template(template) => match &template.template.roots[0] {
-                    TemplateNode::Text(_) | TemplateNode::Element { .. } => {
-                        break Some(template.root_ids.last().unwrap().get());
-                    }
-                    TemplateNode::Dynamic(el) => {
-                        search_node = Some(&template.dynamic_nodes[*el].node)
-                    }
-                },
-            }
-        }
-    }
-
-    fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option<ElementId> {
-        let mut search_node = Some(vnode);
-        loop {
-            match &search_node.take().expect("search node to have an ID") {
-                VNode::Text(t) => break t.id.get(),
-                VNode::Element(t) => break t.id.get(),
-                VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
-                VNode::Component(el) => {
-                    let scope = el.scope.get().expect("element to have a scope assigned");
-                    search_node = Some(self.scopes.root_node(scope));
-                }
-                VNode::Template(template) => match &template.template.roots[0] {
-                    TemplateNode::Text(_) | TemplateNode::Element { .. } => {
-                        break Some(template.root_ids[0].get());
-                    }
-                    TemplateNode::Dynamic(el) => {
-                        search_node = Some(&template.dynamic_nodes[*el].node)
-                    }
-                },
-            }
-        }
-    }
-
-    // recursively push all the nodes of a tree onto the stack and return how many are there
-    fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
-        match node {
-            VNode::Text(_) | VNode::Element(_) => {
-                self.mutations.push_root(node.mounted_id());
-                1
-            }
-
-            VNode::Fragment(frag) => {
-                let mut added = 0;
-                for child in frag.children {
-                    added += self.push_all_real_nodes(child);
-                }
-                added
-            }
-            VNode::Component(c) => {
-                let scope_id = c.scope.get().unwrap();
-                let root = self.scopes.root_node(scope_id);
-                self.push_all_real_nodes(root)
-            }
-
-            VNode::Template(template) => {
-                let mut added = 0;
-                for (idx, root) in template.template.roots.iter().enumerate() {
-                    match root {
-                        TemplateNode::Text(_) | TemplateNode::Element { .. } => {
-                            self.mutations.push_root(template.root_ids[idx].get());
-                            added += 1;
-                        }
-                        TemplateNode::Dynamic(did) => {
-                            added += self.push_all_real_nodes(&template.dynamic_nodes[*did].node);
-                        }
-                    }
-                }
-                added
-            }
-        }
-    }
+use slab::Slab;
 
-    pub(crate) fn diff_placeholder(&self, old_node: &VNode, new_node: &VNode) {
-        todo!()
-    }
-}
+impl VirtualDom {}

+ 3 - 0
packages/core/src/element.rs

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

+ 63 - 119
packages/core/src/lib.rs

@@ -1,131 +1,75 @@
-#![allow(non_snake_case)]
-#![doc = include_str!("../README.md")]
-#![warn(missing_docs)]
+use std::collections::HashMap;
 
-pub(crate) mod diff;
-pub(crate) mod events;
-pub(crate) mod lazynodes;
-pub(crate) mod mutations;
-pub(crate) mod nodes;
-pub(crate) mod properties;
-pub(crate) mod scopes;
-pub(crate) mod virtual_dom;
-
-pub(crate) mod innerlude {
-    pub use crate::events::*;
-    pub use crate::lazynodes::*;
-    pub use crate::mutations::*;
-    pub use crate::nodes::*;
-    pub use crate::properties::*;
-    pub use crate::scopes::*;
-    pub use crate::virtual_dom::*;
+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;
 
-    /// 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<VNode<'a>>;
+mod any_props;
+mod arena;
+mod bump_frame;
+mod component;
+mod create;
+mod diff;
+mod element;
+mod mutations;
+mod nodes;
+mod scope_arena;
+mod scopes;
 
-    /// 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;
-
-    /// A list of attributes
-    pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
+pub struct VirtualDom {
+    templates: HashMap<TemplateId, Template>,
+    elements: ElementArena,
+    scopes: Slab<ScopeState>,
+    scope_stack: Vec<ScopeId>,
 }
 
-pub use crate::innerlude::{
-    AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
-    EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
-    SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
-    UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, 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(),
+        };
 
-/// 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::{
-        fc_to_builder, Attributes, Component, Element, EventHandler, LazyNodes, NodeFactory,
-        Properties, Scope, ScopeId, ScopeState, Template, VNode, VirtualDom,
-    };
-}
+        res.new_scope(
+            app as _,
+            None,
+            ElementId(0),
+            Box::new(VComponentProps::new_empty(app)),
+        );
 
-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;
-}
-
-/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
-pub(crate) mod unsafe_utils {
-    use crate::VNode;
+        res
+    }
 
-    pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
-        std::mem::transmute(node)
+    fn root_scope(&self) -> &ScopeState {
+        todo!()
     }
-}
 
-#[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();
-    )*}
-}
+    /// 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();
+
+        let root_template = root.current_arena();
 
-/// 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")
-        )
-    };
+        let root_node: &'a VTemplate = unsafe { &*root_template.node.get() };
+        let root_node: &'a VTemplate<'a> = unsafe { std::mem::transmute(root_node) };
+
+        self.create(mutations, root_node);
+    }
 }

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

@@ -1,98 +1,45 @@
-//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
-//!
-//! This module contains an internal API to generate these instructions.
-//!
-//! Beware that changing code in this module will break compatibility with
-//! interpreters for these types of DomEdits.
+use crate::arena::ElementId;
 
-use crate::innerlude::*;
-
-/// A renderer for Dioxus to modify during diffing
-///
-/// The renderer should implement a Stack Machine. IE each call to the below methods are modifications to the renderer's
-/// internal stack for creating and modifying nodes.
-///
-/// Dioxus guarantees that the stack is always in a valid state.
-pub trait Renderer<'a> {
-    /// Load this element onto the stack
-    fn push_root(&mut self, root: ElementId);
-    /// Pop the topmost element from the stack
-    fn pop_root(&mut self);
-    /// Replace the given element with the next m elements on the stack
-    fn replace_with(&mut self, root: ElementId, m: u32);
-
-    /// Insert the next m elements on the stack after the given element
-    fn insert_after(&mut self, root: ElementId, n: u32);
-    /// Insert the next m elements on the stack before the given element
-    fn insert_before(&mut self, root: ElementId, n: u32);
-    /// Append the next n elements on the stack to the n+1 element on the stack
-    fn append_children(&mut self, n: u32);
-
-    /// Create a new element with the given text and ElementId
-    fn create_text_node(&mut self, text: &'a str, root: ElementId);
-    /// Create an element with the given tag name, optional namespace, and ElementId
-    /// Note that namespaces do not cascade down the tree, so the renderer must handle this if it implements namespaces
-    fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId);
-    /// Create a hidden element to be used later for replacement.
-    /// Used in suspense, lists, and other places where we need to hide a node before it is ready to be shown.
-    /// This is up to the renderer to implement, but it should not be visible to the user.
-    fn create_placeholder(&mut self, id: ElementId);
-
-    /// Remove the targeted node from the DOM
-    fn remove(&mut self, root: ElementId);
-    /// Remove an attribute from an existing element
-    fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
-    /// Remove all the children of the given element
-    fn remove_children(&mut self, root: ElementId);
-
-    /// Attach a new listener to the dom
-    fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
-    /// Remove an existing listener from the dom
-    fn remove_event_listener(&mut self, event: &'static str, root: ElementId);
+pub struct Renderer<'a> {
+    mutations: Vec<Mutation<'a>>,
+}
 
-    /// Set the text content of a node
-    fn set_text(&mut self, text: &'a str, root: ElementId);
-    /// Set an attribute on an element
-    fn set_attribute(
-        &mut self,
+#[derive(Debug)]
+pub enum Mutation<'a> {
+    SetAttribute {
         name: &'static str,
-        value: AttributeValue<'a>,
-        namespace: Option<&'a str>,
-        root: ElementId,
-    );
+        value: &'a str,
+        id: ElementId,
+    },
 
-    /// General statistics for doing things that extend outside of the renderer
-    fn mark_dirty_scope(&mut self, scope: ScopeId);
-
-    /// Save the current n nodes to the ID to be loaded later
-    fn save(&mut self, id: &'static str, num: u32);
-    /// Loads a set of saved nodes from the ID into a scratch space
-    fn load(&mut self, id: &'static str, index: u32);
-    /// Assign the element on the stack's descendent the given ID
-    fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
-    /// Replace the given element of the topmost element with the next m elements on the stack
-    /// Is essentially a combination of assign_id and replace_with
-    fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
-}
-
-/*
-div {
-    div {
-        div {
-            div {}
-        }
-    }
+    LoadTemplate {
+        name: &'static str,
+        index: usize,
+    },
+
+    HydrateText {
+        path: &'static [u8],
+        value: &'a str,
+        id: ElementId,
+    },
+
+    SetText {
+        value: &'a str,
+        id: ElementId,
+    },
+
+    ReplacePlaceholder {
+        path: &'static [u8],
+        id: ElementId,
+    },
+
+    AssignId {
+        path: &'static [u8],
+        id: ElementId,
+    },
+
+    // Take the current element and replace it with the element with the given id.
+    Replace {
+        id: ElementId,
+    },
 }
-
-push_child(0)
-push_child(1)
-push_child(3)
-push_child(4)
-pop
-pop
-
-clone_node(0)
-set_node(el, [1,2,3,4])
-set_attribute("class", "foo")
-append_child(1)
-*/

+ 129 - 0
packages/core/src/nodes.rs

@@ -0,0 +1,129 @@
+use crate::{any_props::AnyProps, arena::ElementId, scopes::ComponentPtr};
+use std::{
+    any::{Any, TypeId},
+    cell::Cell,
+    num::NonZeroUsize,
+};
+
+pub type TemplateId = &'static str;
+
+/// A reference to a template along with any context needed to hydrate it
+pub struct VTemplate<'a> {
+    // The ID assigned for the root of this template
+    pub node_id: Cell<ElementId>,
+
+    pub template: &'static Template,
+
+    pub root_ids: &'a [Cell<ElementId>],
+
+    /// All the dynamic nodes for a template
+    pub dynamic_nodes: &'a mut [DynamicNode<'a>],
+
+    pub dynamic_attrs: &'a mut [AttributeLocation<'a>],
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Template {
+    pub id: &'static str,
+
+    pub roots: &'static [TemplateNode<'static>],
+}
+
+/// A weird-ish variant of VNodes with way more limited types
+#[derive(Debug, Clone, Copy)]
+pub enum TemplateNode<'a> {
+    /// A simple element
+    Element {
+        tag: &'a str,
+        namespace: Option<&'a str>,
+        attrs: &'a [TemplateAttribute<'a>],
+        children: &'a [TemplateNode<'a>],
+    },
+    Text(&'a str),
+    Dynamic(usize),
+    DynamicText(usize),
+}
+
+pub struct DynamicNode<'a> {
+    pub path: &'static [u8],
+    pub kind: DynamicNodeKind<'a>,
+}
+
+pub enum DynamicNodeKind<'a> {
+    // Anything declared in component form
+    // IE in caps or with underscores
+    Component {
+        name: &'static str,
+        fn_ptr: ComponentPtr,
+        props: Box<dyn AnyProps>,
+    },
+
+    // Comes in with string interpolation or from format_args, include_str, etc
+    Text {
+        id: Cell<ElementId>,
+        value: &'static str,
+    },
+
+    // Anything that's coming in as an iterator
+    Fragment {
+        children: &'a [VTemplate<'a>],
+    },
+}
+
+#[derive(Debug)]
+pub struct TemplateAttribute<'a> {
+    pub name: &'static str,
+    pub value: &'a str,
+    pub namespace: Option<&'static str>,
+    pub volatile: bool,
+}
+
+pub struct AttributeLocation<'a> {
+    pub mounted_element: Cell<ElementId>,
+    pub attrs: &'a [Attribute<'a>],
+    pub listeners: &'a [Listener<'a>],
+    pub path: &'static [u8],
+}
+
+#[derive(Debug)]
+pub struct Attribute<'a> {
+    pub name: &'static str,
+    pub value: &'a str,
+    pub namespace: Option<&'static str>,
+}
+
+pub enum AttributeValue<'a> {
+    Text(&'a str),
+    Float(f32),
+    Int(i32),
+    Bool(bool),
+    Any(&'a dyn AnyValue),
+}
+
+pub trait AnyValue {
+    fn any_cmp(&self, other: &dyn Any) -> bool;
+}
+impl<T> AnyValue for T
+where
+    T: PartialEq + Any,
+{
+    fn any_cmp(&self, other: &dyn Any) -> bool {
+        if self.type_id() != other.type_id() {
+            return false;
+        }
+
+        self == unsafe { &*(other as *const _ as *const T) }
+    }
+}
+
+pub struct Listener<'a> {
+    pub name: &'static str,
+    pub callback: &'a dyn Fn(),
+}
+
+#[test]
+fn what_are_the_sizes() {
+    dbg!(std::mem::size_of::<VTemplate>());
+    dbg!(std::mem::size_of::<Template>());
+    dbg!(std::mem::size_of::<TemplateNode>());
+}

+ 62 - 0
packages/core/src/scope_arena.rs

@@ -0,0 +1,62 @@
+use slab::Slab;
+
+use crate::{
+    any_props::AnyProps,
+    arena::ElementId,
+    bump_frame::BumpFrame,
+    nodes::VTemplate,
+    scopes::{ComponentPtr, ScopeId, ScopeState},
+    VirtualDom,
+};
+
+impl VirtualDom {
+    pub fn new_scope(
+        &mut self,
+        fn_ptr: ComponentPtr,
+        parent: Option<*mut ScopeState>,
+        container: ElementId,
+        props: Box<dyn AnyProps>,
+    ) -> ScopeId {
+        let entry = self.scopes.vacant_entry();
+        let our_arena_idx = entry.key();
+        let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
+
+        entry.insert(ScopeState {
+            parent,
+            container,
+            our_arena_idx,
+            height,
+            fn_ptr,
+            props,
+            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(),
+        });
+
+        our_arena_idx
+    }
+
+    pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VTemplate<'a> {
+        let scope = &mut self.scopes[id];
+        scope.hook_idx.set(0);
+
+        let res = scope.props.render(scope).unwrap();
+        let res: VTemplate<'static> = unsafe { std::mem::transmute(res) };
+
+        let frame = match scope.render_cnt % 2 {
+            0 => &mut scope.node_arena_1,
+            1 => &mut scope.node_arena_2,
+            _ => unreachable!(),
+        };
+
+        // set the head of the bump frame
+        let alloced = frame.bump.alloc(res);
+        frame.node.set(alloced);
+
+        // rebind the lifetime now that its stored internally
+        unsafe { std::mem::transmute(alloced) }
+    }
+}

+ 29 - 986
packages/core/src/scopes.rs

@@ -1,1012 +1,55 @@
-use crate::{innerlude::*, unsafe_utils::extend_vnode};
-use bumpalo::Bump;
-use futures_channel::mpsc::UnboundedSender;
-use fxhash::FxHashMap;
-use slab::Slab;
 use std::{
-    any::{Any, TypeId},
-    borrow::Borrow,
+    any::Any,
     cell::{Cell, RefCell},
-    collections::{HashMap, HashSet},
-    future::Future,
-    pin::Pin,
-    rc::Rc,
-    sync::Arc,
 };
 
-/// for traceability, we use the raw fn pointer to identify the function
-/// we also get the component name, but that's not necessarily unique in the app
-pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
-
-pub(crate) struct Heuristic {
-    hook_arena_size: usize,
-    node_arena_size: usize,
-}
-
-// a slab-like arena with stable references even when new scopes are allocated
-// uses a bump arena as a backing
-//
-// has an internal heuristics engine to pre-allocate arenas to the right size
-pub(crate) struct ScopeArena {
-    pub scope_gen: Cell<usize>,
-    pub bump: Bump,
-    pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
-    pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
-    pub free_scopes: RefCell<Vec<*mut ScopeState>>,
-    pub nodes: RefCell<Slab<*const VNode<'static>>>,
-    pub tasks: Rc<TaskQueue>,
-    pub template_cache: RefCell<HashSet<Template<'static>>>,
-}
-
-impl ScopeArena {
-    pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
-        let bump = Bump::new();
-
-        // allocate a container for the root element
-        // this will *never* show up in the diffing process
-        // todo: figure out why this is necessary. i forgot. whoops.
-        let el = bump.alloc(VElement {
-            tag: "root",
-            namespace: None,
-            key: None,
-            id: Cell::new(Some(ElementId(0))),
-            parent: Default::default(),
-            listeners: &[],
-            attributes: &[],
-            children: &[],
-        });
-
-        let node = bump.alloc(VNode::Element(el));
-        let mut nodes = Slab::new();
-        let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
-
-        debug_assert_eq!(root_id, 0);
-
-        Self {
-            scope_gen: Cell::new(0),
-            bump,
-            scopes: RefCell::new(FxHashMap::default()),
-            heuristics: RefCell::new(FxHashMap::default()),
-            free_scopes: RefCell::new(Vec::new()),
-            nodes: RefCell::new(nodes),
-            tasks: Rc::new(TaskQueue {
-                tasks: RefCell::new(FxHashMap::default()),
-                task_map: RefCell::new(FxHashMap::default()),
-                gen: Cell::new(0),
-                sender,
-            }),
-            template_cache: RefCell::new(HashSet::new()),
-        }
-    }
-
-    /// Safety:
-    /// - Obtaining a mutable reference to any Scope is unsafe
-    /// - Scopes use interior mutability when sharing data into components
-    pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
-        unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
-    }
-
-    pub(crate) fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
-        self.scopes.borrow().get(&id).copied()
-    }
-
-    pub(crate) fn new_with_key(
-        &self,
-        fc_ptr: ComponentPtr,
-        vcomp: Box<dyn AnyProps>,
-        parent_scope: Option<ScopeId>,
-        container: ElementId,
-        subtree: u32,
-    ) -> ScopeId {
-        // Increment the ScopeId system. ScopeIDs are never reused
-        let new_scope_id = ScopeId(self.scope_gen.get());
-        self.scope_gen.set(self.scope_gen.get() + 1);
-
-        // Get the height of the scope
-        let height = parent_scope
-            .and_then(|id| self.get_scope(id).map(|scope| scope.height + 1))
-            .unwrap_or_default();
-
-        let parent_scope = parent_scope.and_then(|f| self.get_scope_raw(f));
-
-        /*
-        This scopearena aggressively reuses old scopes when possible.
-        We try to minimize the new allocations for props/arenas.
-
-        However, this will probably lead to some sort of fragmentation.
-        I'm not exactly sure how to improve this today.
-        */
-        if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
-            // reuse the old scope
-            let scope = unsafe { &mut *old_scope };
-
-            scope.container = container;
-            scope.our_arena_idx = new_scope_id;
-            scope.parent_scope = parent_scope;
-            scope.height = height;
-            scope.fnptr = fc_ptr;
-            scope.props.get_mut().replace(vcomp);
-            scope.subtree.set(subtree);
-            scope.frames[0].reset();
-            scope.frames[1].reset();
-            scope.shared_contexts.get_mut().clear();
-            scope.items.get_mut().listeners.clear();
-            scope.items.get_mut().borrowed_props.clear();
-            scope.hook_idx.set(0);
-            scope.hook_vals.get_mut().clear();
-
-            let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
-            debug_assert!(any_item.is_none());
-        } else {
-            // else create a new scope
-            let (node_capacity, hook_capacity) = self
-                .heuristics
-                .borrow()
-                .get(&fc_ptr)
-                .map(|h| (h.node_arena_size, h.hook_arena_size))
-                .unwrap_or_default();
-
-            self.scopes.borrow_mut().insert(
-                new_scope_id,
-                self.bump.alloc(ScopeState {
-                    container,
-                    our_arena_idx: new_scope_id,
-                    parent_scope,
-                    height,
-                    fnptr: fc_ptr,
-                    props: RefCell::new(Some(vcomp)),
-                    frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
-
-                    // todo: subtrees
-                    subtree: Cell::new(0),
-                    is_subtree_root: Cell::default(),
-
-                    generation: 0.into(),
-
-                    tasks: self.tasks.clone(),
-                    shared_contexts: RefCell::default(),
-
-                    items: RefCell::new(SelfReferentialItems {
-                        listeners: Vec::default(),
-                        borrowed_props: Vec::default(),
-                    }),
-
-                    hook_arena: Bump::new(),
-                    hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
-                    hook_idx: Cell::default(),
-                }),
-            );
-        }
-
-        new_scope_id
-    }
-
-    // Removes a scope and its descendents from the arena
-    pub fn try_remove(&self, id: ScopeId) {
-        self.ensure_drop_safety(id);
-
-        // Dispose of any ongoing tasks
-        let mut tasks = self.tasks.tasks.borrow_mut();
-        let mut task_map = self.tasks.task_map.borrow_mut();
-        if let Some(cur_tasks) = task_map.remove(&id) {
-            for task in cur_tasks {
-                tasks.remove(&task);
-            }
-        }
-
-        // Safety:
-        // - ensure_drop_safety ensures that no references to this scope are in use
-        // - this raw pointer is removed from the map
-        let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
-        scope.reset();
-
-        self.free_scopes.borrow_mut().push(scope);
-    }
-
-    pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
-        let mut els = self.nodes.borrow_mut();
-        let entry = els.vacant_entry();
-        let key = entry.key();
-        let id = ElementId(key);
-        let node = unsafe { extend_vnode(node) };
-        entry.insert(node as *const _);
-        id
-    }
-
-    pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
-        let node = unsafe { extend_vnode(node) };
-        *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
-    }
-
-    pub fn collect_garbage(&self, id: ElementId) {
-        self.nodes.borrow_mut().remove(id.0);
-    }
-
-    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
-    /// causing UB in our tree.
-    ///
-    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
-    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
-    /// any possible references to the data in the hook list.
-    ///
-    /// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
-    /// all listeners and borrowed props so we can clear them here.
-    ///
-    /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
-    /// be dropped.
-    pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
-        if let Some(scope) = self.get_scope(scope_id) {
-            let mut items = scope.items.borrow_mut();
-
-            // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
-            // run the hooks (which hold an &mut Reference)
-            // recursively call ensure_drop_safety on all children
-            items.borrowed_props.drain(..).for_each(|comp| {
-                if let Some(scope_id) = comp.scope.get() {
-                    self.ensure_drop_safety(scope_id);
-                }
-                drop(comp.props.take());
-            });
-
-            // Now that all the references are gone, we can safely drop our own references in our listeners.
-            items
-                .listeners
-                .drain(..)
-                .for_each(|listener| drop(listener.callback.borrow_mut().take()));
-        }
-    }
-
-    pub(crate) fn run_scope(&self, id: ScopeId) {
-        // 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
-        self.ensure_drop_safety(id);
-
-        // todo: we *know* that this is aliased by the contents of the scope itself
-        let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
-
-        log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);
-
-        // Safety:
-        // - We dropped the listeners, so no more &mut T can be used while these are held
-        // - All children nodes that rely on &mut T are replaced with a new reference
-        scope.hook_idx.set(0);
-
-        {
-            // Safety:
-            // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
-            unsafe { scope.reset_wip_frame() };
-
-            let items = scope.items.borrow();
-
-            // guarantee that we haven't screwed up - there should be no latent references anywhere
-            debug_assert!(items.listeners.is_empty());
-            debug_assert!(items.borrowed_props.is_empty());
-        }
-
-        /*
-        If the component returns None, then we fill in a placeholder node. This will wipe what was there.
-        An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
-
-        Instead, we just treat the `None` as a shortcut to placeholder.
-        If the developer wants to prevent a scope from updating, they should control its memoization instead.
-
-        Also, the way we implement hooks allows us to cut rendering short before the next hook is recalled.
-        I'm not sure if React lets you abort the component early, but we let you do that.
-        */
-
-        let props = scope.props.borrow();
-        let render = props.as_ref().unwrap();
-        if let Some(node) = render.render(scope) {
-            let frame = scope.wip_frame();
-            let node = frame.bump.alloc(node);
-            frame.node.set(unsafe { extend_vnode(node) });
-        } else {
-            let frame = scope.wip_frame();
-            let node = frame.bump.alloc(VNode::Text(frame.bump.alloc(VText {
-                id: Cell::default(),
-                text: "asd",
-            })));
-            frame.node.set(unsafe { extend_vnode(node) });
-        }
-
-        // make the "wip frame" contents the "finished frame"
-        // any future dipping into completed nodes after "render" will go through "fin head"
-        scope.cycle_frame();
-    }
-
-    pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) {
-        let nodes = self.nodes.borrow();
-        let mut cur_el = Some(element);
-
-        let state = Rc::new(BubbleState::new());
-
-        while let Some(id) = cur_el.take() {
-            if let Some(el) = nodes.get(id.0) {
-                let real_el = unsafe { &**el };
-
-                if let VNode::Element(real_el) = real_el {
-                    for listener in real_el.listeners.borrow().iter() {
-                        if listener.event == event.name {
-                            if state.canceled.get() {
-                                // stop bubbling if canceled
-                                return;
-                            }
-
-                            let mut cb = listener.callback.borrow_mut();
-                            if let Some(cb) = cb.as_mut() {
-                                // todo: arcs are pretty heavy to clone
-                                // we really want to convert arc to rc
-                                // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
-                                // we could convert arc to rc internally or something
-                                (cb)(AnyEvent {
-                                    bubble_state: state.clone(),
-                                    data: event.data.clone(),
-                                });
-                            }
-
-                            if !event.bubbles {
-                                return;
-                            }
-                        }
-                    }
-
-                    cur_el = real_el.parent.get();
-                }
-            }
-        }
-    }
-
-    // The head of the bumpframe is the first linked NodeLink
-    pub fn wip_head(&self, id: ScopeId) -> &VNode {
-        let scope = self.get_scope(id).unwrap();
-        let frame = scope.wip_frame();
-        let node = unsafe { &*frame.node.get() };
-        unsafe { extend_vnode(node) }
-    }
-
-    // The head of the bumpframe is the first linked NodeLink
-    pub fn fin_head(&self, id: ScopeId) -> &VNode {
-        let scope = self.get_scope(id).unwrap();
-        let frame = scope.fin_frame();
-        let node = unsafe { &*frame.node.get() };
-        unsafe { extend_vnode(node) }
-    }
-
-    pub fn root_node(&self, id: ScopeId) -> &VNode {
-        self.fin_head(id)
-    }
-
-    // this is totally okay since all our nodes are always in a valid state
-    pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
-        self.nodes
-            .borrow()
-            .get(id.0)
-            .copied()
-            .map(|ptr| unsafe { extend_vnode(&*ptr) })
-    }
-}
-
-/// Components in Dioxus use the "Context" object to interact with their lifecycle.
-///
-/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
-///
-/// For the most part, the only method you should be using regularly is `render`.
-///
-/// ## Example
-///
-/// ```ignore
-/// #[derive(Props)]
-/// struct ExampleProps {
-///     name: String
-/// }
-///
-/// fn Example(cx: Scope<ExampleProps>) -> Element {
-///     cx.render(rsx!{ div {"Hello, {cx.props.name}"} })
-/// }
-/// ```
-pub struct Scope<'a, P = ()> {
-    /// The internal ScopeState for this component
-    pub scope: &'a ScopeState,
-
-    /// The props for this component
-    pub props: &'a P,
-}
-
-impl<P> Copy for Scope<'_, P> {}
-impl<P> Clone for Scope<'_, P> {
-    fn clone(&self) -> Self {
-        Self {
-            scope: self.scope,
-            props: self.props,
-        }
-    }
-}
-
-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
-    }
-}
-
-/// 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);
+use bumpalo::Bump;
 
-/// 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,
+use crate::{any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, nodes::VTemplate};
 
-    /// The original scope that this task was scheduled in
-    pub scope: ScopeId,
-}
+pub type ScopeId = usize;
 
-/// Every component in Dioxus is represented by a `ScopeState`.
-///
-/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
-///
-/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
-/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
-///
-/// We expose the `Scope` type so downstream users can traverse the Dioxus [`VirtualDom`] for whatever
-/// use case they might have.
 pub struct ScopeState {
-    pub(crate) parent_scope: Option<*mut ScopeState>,
-    pub(crate) container: ElementId,
-    pub(crate) our_arena_idx: ScopeId,
-    pub(crate) height: u32,
-    pub(crate) fnptr: ComponentPtr,
+    pub render_cnt: usize,
 
-    // todo: subtrees
-    pub(crate) is_subtree_root: Cell<bool>,
-    pub(crate) subtree: Cell<u32>,
-    pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
+    pub node_arena_1: BumpFrame,
+    pub node_arena_2: BumpFrame,
 
-    // nodes, items
-    pub(crate) frames: [BumpFrame; 2],
-    pub(crate) generation: Cell<u32>,
-    pub(crate) items: RefCell<SelfReferentialItems<'static>>,
+    pub parent: Option<*mut ScopeState>,
+    pub container: ElementId,
+    pub our_arena_idx: ScopeId,
 
-    // hooks
-    pub(crate) hook_arena: Bump,
-    pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
-    pub(crate) hook_idx: Cell<usize>,
+    pub height: u32,
+    pub fn_ptr: ComponentPtr,
 
-    // shared state -> todo: move this out of scopestate
-    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
-    pub(crate) tasks: Rc<TaskQueue>,
-}
+    pub hook_arena: Bump,
+    pub hook_vals: RefCell<Vec<*mut dyn Any>>,
+    pub hook_idx: Cell<usize>,
 
-pub struct SelfReferentialItems<'a> {
-    pub(crate) listeners: Vec<&'a Listener<'a>>,
-    pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
+    pub props: Box<dyn AnyProps>,
 }
 
-// Public methods exposed to libraries and components
 impl ScopeState {
-    /// Get the subtree ID that this scope belongs to.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
-    /// dom.rebuild();
-    ///
-    /// let base = dom.base_scope();
-    ///
-    /// assert_eq!(base.subtree(), 0);
-    /// ```
-    ///
-    /// todo: enable
-    pub(crate) fn _subtree(&self) -> u32 {
-        self.subtree.get()
-    }
-
-    /// Create a new subtree with this scope as the root of the subtree.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    /// This method
-    ///
-    /// # Example
-    ///
-    /// ```rust, ignore
-    /// fn App(cx: Scope) -> Element {
-    ///     render!(div { "Subtree {id}"})
-    /// };
-    /// ```
-    ///
-    /// todo: enable subtree
-    pub(crate) fn _create_subtree(&self) -> Option<u32> {
-        if self.is_subtree_root.get() {
-            None
-        } else {
-            todo!()
-        }
-    }
-
-    /// 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
-    }
-
-    /// 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_scope.map(|p| unsafe { &*p }.our_arena_idx)
-    }
-
-    /// 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.our_arena_idx
-    }
-
-    /// 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))))
-    }
-
-    /// 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());
-    }
-
-    /// 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");
-    }
-
-    /// Get the Root Node of this scope
-    pub fn root_node(&self) -> &VNode {
-        let node = unsafe { &*self.fin_frame().node.get() };
-        unsafe { std::mem::transmute(node) }
-    }
-
-    /// 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_scope;
-
-        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_scope;
-        }
-
-        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_scope;
-
-            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_scope;
-            }
-            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.our_arena_idx))
-            .expect("Scheduler should exist");
-
-        self.tasks.spawn(self.our_arena_idx, 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.our_arena_idx))
-            .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(NodeFactory {
-            scope: self,
-            bump: &self.wip_frame().bump,
-        }))
-    }
-
-    /// 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.
-                "###,
-            )
-    }
-
-    /// The "work in progress frame" represents the frame that is currently being worked on.
-    pub(crate) fn wip_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 {
-            0 => &self.frames[0],
-            _ => &self.frames[1],
-        }
-    }
-
-    /// Mutable access to the "work in progress frame" - used to clear it
-    pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
-        match self.generation.get() & 1 {
-            0 => &mut self.frames[0],
-            _ => &mut self.frames[1],
+    pub fn current_arena(&self) -> &BumpFrame {
+        match self.render_cnt % 2 {
+            0 => &self.node_arena_1,
+            1 => &self.node_arena_2,
+            _ => unreachable!(),
         }
     }
-
-    /// Access to the frame where finalized nodes existed
-    pub(crate) fn fin_frame(&self) -> &BumpFrame {
-        match self.generation.get() & 1 {
-            1 => &self.frames[0],
-            _ => &self.frames[1],
-        }
-    }
-
-    /// Reset this component's frame
-    ///
-    /// # Safety:
-    ///
-    /// This method breaks every reference of every [`VNode`] in the current frame.
-    ///
-    /// Calling reset itself is not usually a big deal, but we consider it important
-    /// due to the complex safety guarantees we need to uphold.
-    pub(crate) unsafe fn reset_wip_frame(&mut self) {
-        self.wip_frame_mut().bump.reset();
-    }
-
-    /// Cycle to the next generation
-    pub(crate) fn cycle_frame(&self) {
-        self.generation.set(self.generation.get() + 1);
-    }
-
-    // todo: disable bookkeeping on drop (unncessary)
-    pub(crate) fn reset(&mut self) {
-        // first: book keaping
-        self.hook_idx.set(0);
-        self.parent_scope = None;
-        self.generation.set(0);
-        self.is_subtree_root.set(false);
-        self.subtree.set(0);
-
-        // next: shared context data
-        self.shared_contexts.get_mut().clear();
-
-        // next: reset the node data
-        let SelfReferentialItems {
-            borrowed_props,
-            listeners,
-        } = self.items.get_mut();
-        borrowed_props.clear();
-        listeners.clear();
-        self.frames[0].reset();
-        self.frames[1].reset();
-
-        // Free up the hook values
-        self.hook_vals.get_mut().drain(..).for_each(|state| {
-            let as_mut = unsafe { &mut *state };
-            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
-            drop(boxed);
-        });
-
-        // Finally, clear the hook arena
-        self.hook_arena.reset();
-    }
-}
-
-pub(crate) struct BumpFrame {
-    pub bump: Bump,
-    pub node: Cell<*const VNode<'static>>,
 }
-impl BumpFrame {
-    pub(crate) fn new(capacity: usize) -> Self {
-        let bump = Bump::with_capacity(capacity);
-        let node = bump.alloc(VText {
-            text: "placeholdertext",
-            id: Cell::default(),
-        });
-        let node = bump.alloc(VNode::Text(unsafe {
-            &*(node as *mut VText as *const VText)
-        }));
-        let nodes = Cell::new(node as *const _);
-        Self { bump, node: nodes }
-    }
 
-    pub(crate) fn reset(&mut self) {
-        self.bump.reset();
-        let node = self.bump.alloc(VText {
-            text: "placeholdertext",
-            id: Cell::default(),
-        });
-        let node = self.bump.alloc(VNode::Text(unsafe {
-            &*(node as *mut VText as *const VText)
-        }));
-        self.node.set(node as *const _);
-    }
-}
+pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
 
-pub(crate) struct TaskQueue {
-    pub(crate) tasks: RefCell<FxHashMap<TaskId, InnerTask>>,
-    pub(crate) task_map: RefCell<FxHashMap<ScopeId, HashSet<TaskId>>>,
-    gen: Cell<usize>,
-    sender: UnboundedSender<SchedulerMsg>,
+pub struct Scope<T = ()> {
+    pub props: T,
+    pub state: Cell<*const ScopeState>,
 }
 
-pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
-impl TaskQueue {
-    fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
-        let pinned = Box::pin(task);
-        let id = self.gen.get();
-        self.gen.set(id + 1);
-        let tid = TaskId { id, scope };
-
-        self.tasks.borrow_mut().insert(tid, pinned);
-
-        // also add to the task map
-        // when the component is unmounted we know to remove it from the map
-        self.task_map
-            .borrow_mut()
-            .entry(scope)
-            .or_default()
-            .insert(tid);
+impl<T> std::ops::Deref for Scope<T> {
+    type Target = ScopeState;
 
-        tid
-    }
-
-    fn remove(&self, id: TaskId) {
-        if let Ok(mut tasks) = self.tasks.try_borrow_mut() {
-            tasks.remove(&id);
-            if let Some(task_map) = self.task_map.borrow_mut().get_mut(&id.scope) {
-                task_map.remove(&id);
-            }
-        }
-        // the task map is still around, but it'll be removed when the scope is unmounted
-    }
-
-    pub(crate) fn has_tasks(&self) -> bool {
-        !self.tasks.borrow().is_empty()
+    fn deref(&self) -> &Self::Target {
+        unsafe { &*self.state.get() }
     }
 }
-
-#[test]
-fn sizeof() {
-    dbg!(std::mem::size_of::<ScopeState>());
-}