Explorar o código

Feat: major overhaul, wire up event system

Jonathan Kelley %!s(int64=4) %!d(string=hai) anos
pai
achega
8295ac4

+ 1 - 1
.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-    "rust-analyzer.inlayHints.enable": false
+    "rust-analyzer.inlayHints.enable": true
 }

+ 12 - 0
SOLVEDPROBLEMS.md

@@ -245,3 +245,15 @@ A few notes:
 ## Concurrency
 
 I don't even know yet
+
+
+## Execution Model
+
+
+
+
+
+
+
+
+

+ 3 - 0
docs/guides/01-ssr.md

@@ -0,0 +1,3 @@
+# The Server-Side-Rendering Guide
+
+This guide will help you build your first app that leverages server-side-rendering to display the user interface.

+ 3 - 0
docs/guides/02-wasm.md

@@ -0,0 +1,3 @@
+# The WASM Guide
+
+This guide will help you build your first app that leverages WASM in the browser to display the user interface.

+ 3 - 0
docs/guides/03-desktop.md

@@ -0,0 +1,3 @@
+# The Desktop Guide
+
+This guide will help you build your first app that leverages webview for desktop to display the user interface.

+ 1 - 21
examples/fc_macro.rs

@@ -55,28 +55,11 @@ impl<T: Properties> Comp for FC<T> {
     }
 }
 
-// impl<T: Properties, F: Fn(&Context<T>) -> VNode> Comp for F {
-//     type Props = T;
-
-//     fn render(&self, ctx: &mut Context<T>) -> VNode {
-//         let g = self(ctx);
-//         g
-//     }
-
-//     fn builder(&self) -> T {
-//         T::new()
-//     }
-// }
-
-impl Properties for () {
-    fn new() -> Self {
-        ()
-    }
-}
 #[allow(unused, non_upper_case_globals)]
 static MyComp: FC<()> = |ctx| {
     html! {
         <div>
+            <p> "hello world" </p>
         </div>
     }
 };
@@ -89,7 +72,4 @@ fn test() {
     let mut ctx = Context { props: &() };
     let f = MyComp.render(&mut ctx);
     let props = MyComp.builder();
-
-    // let f = my_comp.render(&mut ctx);
-    // let props = my_comp.builder();
 }

+ 1 - 1
packages/core/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-    "rust-analyzer.inlayHints.enable": false
+    "rust-analyzer.inlayHints.enable": true
 }

+ 1 - 0
packages/core/Cargo.toml

@@ -28,3 +28,4 @@ owning_ref = "0.4.1"
 typed-arena = "2.0.1"
 toolshed = "0.8.1"
 id-arena = "2.2.1"
+thiserror = "1.0.23"

+ 8 - 15
packages/core/examples/nested.rs

@@ -12,12 +12,14 @@ static Header: FC<()> = |ctx, props| {
 
     let handler1 = move || println!("Value is {}", inner.current());
 
-    ctx.view(html! {
-        <div>
-            <h1> "This is the header bar" </h1>
-            <h1> "Idnt it awesome" </h1>
-            <button onclick={move |_| handler1()}> "Click me" </button>
-        </div>
+    ctx.view(|bump| {
+        builder::div(bump)
+            .child(VNode::Component(VComponent::new(
+                Bottom,
+                //
+                (),
+            )))
+            .finish()
     })
 };
 
@@ -29,12 +31,3 @@ static Bottom: FC<()> = |ctx, props| {
         </div>
     })
 };
-
-static Example: FC<()> = |ctx, props| {
-    ctx.view(html! {
-        <div>
-            <h1> "BROSKI!" </h1>
-            <h1> "DRO!" </h1>
-        </div>
-    })
-};

+ 1 - 1
packages/core/examples/step.rs

@@ -11,7 +11,7 @@ fn main() -> Result<(), ()> {
     let p1 = Props { name: "bob".into() };
 
     let mut vdom = VirtualDom::new_with_props(Example, p1);
-    vdom.progress()?;
+    // vdom.progress()?;
 
     Ok(())
 }

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

@@ -23,7 +23,9 @@ impl<P: Properties> Component for FC<P> {
 
 /// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields.
 /// Components take a "properties" object
-pub trait Properties {
+pub trait Properties // where
+//     Self: Sized,
+{
     fn call(&self, ptr: *const ()) {}
 }
 

+ 8 - 10
packages/core/src/context.rs

@@ -3,7 +3,8 @@ use crate::{inner::Scope, nodes::VNode};
 use bumpalo::Bump;
 use hooks::Hook;
 use std::{
-    any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
+    any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
+    sync::atomic::AtomicUsize,
 };
 
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
@@ -27,15 +28,17 @@ use std::{
 // todo: force lifetime of source into T as a valid lifetime too
 // it's definitely possible, just needs some more messing around
 pub struct Context<'src> {
+    pub idx: AtomicUsize,
+
     pub(crate) scope: &'src Scope,
     /// Direct access to the properties used to create this component.
     // pub props: &'src PropType,
-    pub idx: AtomicUsize,
 
     // Borrowed from scope
     pub(crate) arena: &'src typed_arena::Arena<Hook>,
     pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
-    pub(crate) components: &'src generational_arena::Arena<Scope>,
+    pub(crate) bump: &'src Bump,
+    // pub(crate) components: &'src generational_arena::Arena<Scope>,
 
     // holder for the src lifetime
     // todo @jon remove this
@@ -49,11 +52,6 @@ impl<'a> Context<'a> {
         todo!("Children API not yet implemented for component Context")
     }
 
-    /// Access a parent context
-    pub fn parent_context<C>(&self) -> C {
-        todo!("Context API is not ready yet")
-    }
-
     /// Create a subscription that schedules a future render for the reference component
     pub fn schedule_update(&self) -> impl Fn() -> () {
         todo!("Subscription API is not ready yet");
@@ -75,8 +73,8 @@ impl<'a> Context<'a> {
     ///     ctx.view(lazy_tree)
     /// }
     ///```
-    pub fn view(self, v: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
-        todo!()
+    pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
+        lazy_nodes(self.bump.borrow())
     }
 
     pub fn callback(&self, f: impl Fn(()) + 'static) {}

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

@@ -33,7 +33,7 @@ mod tests {
             ctx.view(html! { <div>"hello world" </div> })
         });
 
-        dom.progress()?;
+        // dom.progress()?;
         Ok(())
     }
 }

+ 1147 - 0
packages/core/src/diff.rs

@@ -0,0 +1,1147 @@
+use crate::prelude::VNode;
+
+// use crate::{
+//     cached_set::{CacheId, CachedSet},
+//     change_list::ChangeListBuilder,
+//     events::EventsRegistry,
+//     node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
+// };
+// use fxhash::{FxHashMap, FxHashSet};
+// use std::cmp::Ordering;
+// use std::u32;
+// use wasm_bindgen::UnwrapThrowExt;
+
+// Diff the `old` node with the `new` node. Emits instructions to modify a
+// physical DOM node that reflects `old` into something that reflects `new`.
+//
+// Upon entry to this function, the physical DOM node must be on the top of the
+// change list stack:
+//
+//     [... node]
+//
+// The change list stack is in the same state when this function exits.
+pub(crate) fn diff(
+    // cached_set: &CachedSet,
+    // change_list: &mut ChangeListBuilder,
+    // registry: &mut EventsRegistry,
+    old: &VNode,
+    new: &VNode,
+    // cached_roots: &mut FxHashSet<CacheId>,
+) {
+    todo!()
+    // match (&new.kind, &old.kind) {
+    //     (
+    //         &NodeKind::Text(TextNode { text: new_text }),
+    //         &NodeKind::Text(TextNode { text: old_text }),
+    //     ) => {
+    //         if new_text != old_text {
+    //             change_list.commit_traversal();
+    //             change_list.set_text(new_text);
+    //         }
+    //     }
+
+    //     (&NodeKind::Text(_), &NodeKind::Element(_)) => {
+    //         change_list.commit_traversal();
+    //         create(cached_set, change_list, registry, new, cached_roots);
+    //         registry.remove_subtree(&old);
+    //         change_list.replace_with();
+    //     }
+
+    //     (&NodeKind::Element(_), &NodeKind::Text(_)) => {
+    //         change_list.commit_traversal();
+    //         create(cached_set, change_list, registry, new, cached_roots);
+    //         // Note: text nodes cannot have event listeners, so we don't need to
+    //         // remove the old node's listeners from our registry her.
+    //         change_list.replace_with();
+    //     }
+
+    //     (
+    //         &NodeKind::Element(ElementNode {
+    //             key: _,
+    //             tag_name: new_tag_name,
+    //             listeners: new_listeners,
+    //             attributes: new_attributes,
+    //             children: new_children,
+    //             namespace: new_namespace,
+    //         }),
+    //         &NodeKind::Element(ElementNode {
+    //             key: _,
+    //             tag_name: old_tag_name,
+    //             listeners: old_listeners,
+    //             attributes: old_attributes,
+    //             children: old_children,
+    //             namespace: old_namespace,
+    //         }),
+    //     ) => {
+    //         if new_tag_name != old_tag_name || new_namespace != old_namespace {
+    //             change_list.commit_traversal();
+    //             create(cached_set, change_list, registry, new, cached_roots);
+    //             registry.remove_subtree(&old);
+    //             change_list.replace_with();
+    //             return;
+    //         }
+    //         diff_listeners(change_list, registry, old_listeners, new_listeners);
+    //         diff_attributes(
+    //             change_list,
+    //             old_attributes,
+    //             new_attributes,
+    //             new_namespace.is_some(),
+    //         );
+    //         diff_children(
+    //             cached_set,
+    //             change_list,
+    //             registry,
+    //             old_children,
+    //             new_children,
+    //             cached_roots,
+    //         );
+    //     }
+
+    //     // Both the new and old nodes are cached.
+    //     (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
+    //         cached_roots.insert(new.id);
+
+    //         if new.id == old.id {
+    //             // This is the same cached node, so nothing has changed!
+    //             return;
+    //         }
+
+    //         let (new, new_template) = cached_set.get(new.id);
+    //         let (old, old_template) = cached_set.get(old.id);
+    //         if new_template == old_template {
+    //             // If they are both using the same template, then just diff the
+    //             // subtrees.
+    //             diff(cached_set, change_list, registry, old, new, cached_roots);
+    //         } else {
+    //             // Otherwise, they are probably different enough that
+    //             // re-constructing the subtree from scratch should be faster.
+    //             // This doubly holds true if we have a new template.
+    //             change_list.commit_traversal();
+    //             create_and_replace(
+    //                 cached_set,
+    //                 change_list,
+    //                 registry,
+    //                 new_template,
+    //                 old,
+    //                 new,
+    //                 cached_roots,
+    //             );
+    //         }
+    //     }
+
+    //     // New cached node when the old node was not cached. In this scenario,
+    //     // we assume that they are pretty different, and it isn't worth diffing
+    //     // the subtrees, so we just create the new cached node afresh.
+    //     (&NodeKind::Cached(ref c), _) => {
+    //         change_list.commit_traversal();
+    //         cached_roots.insert(c.id);
+    //         let (new, new_template) = cached_set.get(c.id);
+    //         create_and_replace(
+    //             cached_set,
+    //             change_list,
+    //             registry,
+    //             new_template,
+    //             old,
+    //             new,
+    //             cached_roots,
+    //         );
+    //     }
+
+    //     // Old cached node and new non-cached node. Again, assume that they are
+    //     // probably pretty different and create the new non-cached node afresh.
+    //     (_, &NodeKind::Cached(_)) => {
+    //         change_list.commit_traversal();
+    //         create(cached_set, change_list, registry, new, cached_roots);
+    //         registry.remove_subtree(&old);
+    //         change_list.replace_with();
+    //     }
+    // }
+}
+
+#[cfg(predicate)]
+mod todo_diff {
+
+    // Diff event listeners between `old` and `new`.
+    //
+    // The listeners' node must be on top of the change list stack:
+    //
+    //     [... node]
+    //
+    // The change list stack is left unchanged.
+    fn diff_listeners(
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Listener],
+        new: &[Listener],
+    ) {
+        if !old.is_empty() || !new.is_empty() {
+            change_list.commit_traversal();
+        }
+
+        'outer1: for new_l in new {
+            unsafe {
+                // Safety relies on removing `new_l` from the registry manually at
+                // the end of its lifetime. This happens below in the `'outer2`
+                // loop, and elsewhere in diffing when removing old dom trees.
+                registry.add(new_l);
+            }
+
+            for old_l in old {
+                if new_l.event == old_l.event {
+                    change_list.update_event_listener(new_l);
+                    continue 'outer1;
+                }
+            }
+
+            change_list.new_event_listener(new_l);
+        }
+
+        'outer2: for old_l in old {
+            registry.remove(old_l);
+
+            for new_l in new {
+                if new_l.event == old_l.event {
+                    continue 'outer2;
+                }
+            }
+            change_list.remove_event_listener(old_l.event);
+        }
+    }
+
+    // Diff a node's attributes.
+    //
+    // The attributes' node must be on top of the change list stack:
+    //
+    //     [... node]
+    //
+    // The change list stack is left unchanged.
+    fn diff_attributes(
+        change_list: &mut ChangeListBuilder,
+        old: &[Attribute],
+        new: &[Attribute],
+        is_namespaced: bool,
+    ) {
+        // Do O(n^2) passes to add/update and remove attributes, since
+        // there are almost always very few attributes.
+        'outer: for new_attr in new {
+            if new_attr.is_volatile() {
+                change_list.commit_traversal();
+                change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
+            } else {
+                for old_attr in old {
+                    if old_attr.name == new_attr.name {
+                        if old_attr.value != new_attr.value {
+                            change_list.commit_traversal();
+                            change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
+                        }
+                        continue 'outer;
+                    }
+                }
+
+                change_list.commit_traversal();
+                change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
+            }
+        }
+
+        'outer2: for old_attr in old {
+            for new_attr in new {
+                if old_attr.name == new_attr.name {
+                    continue 'outer2;
+                }
+            }
+
+            change_list.commit_traversal();
+            change_list.remove_attribute(old_attr.name);
+        }
+    }
+
+    // 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.
+    fn diff_children(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+        new: &[Node],
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) {
+        if new.is_empty() {
+            if !old.is_empty() {
+                change_list.commit_traversal();
+                remove_all_children(change_list, registry, old);
+            }
+            return;
+        }
+
+        if new.len() == 1 {
+            match (old.first(), &new[0]) {
+                (
+                    Some(&Node {
+                        kind: NodeKind::Text(TextNode { text: old_text }),
+                    }),
+                    &Node {
+                        kind: NodeKind::Text(TextNode { text: new_text }),
+                    },
+                ) if old_text == new_text => {
+                    // Don't take this fast path...
+                }
+                (
+                    _,
+                    &Node {
+                        kind: NodeKind::Text(TextNode { text }),
+                    },
+                ) => {
+                    change_list.commit_traversal();
+                    change_list.set_text(text);
+                    for o in old {
+                        registry.remove_subtree(o);
+                    }
+                    return;
+                }
+                (_, _) => {}
+            }
+        }
+
+        if old.is_empty() {
+            if !new.is_empty() {
+                change_list.commit_traversal();
+                create_and_append_children(cached_set, change_list, registry, new, cached_roots);
+            }
+            return;
+        }
+
+        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 {
+            let t = change_list.next_temporary();
+            diff_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
+            change_list.set_next_temporary(t);
+        } else {
+            diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
+        }
+    }
+
+    // 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
+    //
+    // When entering this function, the parent must be on top of the change list
+    // stack:
+    //
+    //     [... parent]
+    //
+    // Upon exiting, the change list stack is in the same state.
+    fn diff_keyed_children(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+        new: &[Node],
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) {
+        if cfg!(debug_assertions) {
+            let mut keys = FxHashSet::default();
+            let mut assert_unique_keys = |children: &[Node]| {
+                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 shared_prefix_count =
+            match diff_keyed_prefix(cached_set, change_list, registry, old, new, cached_roots) {
+                KeyedPrefixResult::Finished => return,
+                KeyedPrefixResult::MoreWorkToDo(count) => count,
+            };
+
+        // Next, we find out how many of the nodes at the end of the children have
+        // the same key. We do _not_ diff them yet, since we want to emit the change
+        // list instructions such that they can be applied in a single pass over the
+        // DOM. Instead, we just save this information for later.
+        //
+        // `shared_suffix_count` is the count of how many nodes at the end of `new`
+        // and `old` share the same keys.
+        let shared_suffix_count = old[shared_prefix_count..]
+            .iter()
+            .rev()
+            .zip(new[shared_prefix_count..].iter().rev())
+            .take_while(|&(old, new)| old.key() == new.key())
+            .count();
+
+        let old_shared_suffix_start = old.len() - shared_suffix_count;
+        let new_shared_suffix_start = new.len() - shared_suffix_count;
+
+        // 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.
+        diff_keyed_middle(
+            cached_set,
+            change_list,
+            registry,
+            &old[shared_prefix_count..old_shared_suffix_start],
+            &new[shared_prefix_count..new_shared_suffix_start],
+            cached_roots,
+            shared_prefix_count,
+            shared_suffix_count,
+            old_shared_suffix_start,
+        );
+
+        // Finally, diff the nodes at the end of `old` and `new` that share keys.
+        let old_suffix = &old[old_shared_suffix_start..];
+        let new_suffix = &new[new_shared_suffix_start..];
+        debug_assert_eq!(old_suffix.len(), new_suffix.len());
+        if !old_suffix.is_empty() {
+            diff_keyed_suffix(
+                cached_set,
+                change_list,
+                registry,
+                old_suffix,
+                new_suffix,
+                cached_roots,
+                new_shared_suffix_start,
+            );
+        }
+    }
+
+    enum KeyedPrefixResult {
+        // Fast path: we finished diffing all the children just by looking at the
+        // prefix of shared keys!
+        Finished,
+        // There is more diffing work to do. Here is a count of how many children at
+        // the beginning of `new` and `old` we already processed.
+        MoreWorkToDo(usize),
+    }
+
+    // Diff the prefix of children in `new` and `old` that share the same keys in
+    // the same order.
+    //
+    // Upon entry of this function, the change list stack must be:
+    //
+    //     [... parent]
+    //
+    // Upon exit, the change list stack is the same.
+    fn diff_keyed_prefix(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+        new: &[Node],
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) -> KeyedPrefixResult {
+        change_list.go_down();
+        let mut shared_prefix_count = 0;
+
+        for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
+            if old.key() != new.key() {
+                break;
+            }
+
+            change_list.go_to_sibling(i);
+            diff(cached_set, change_list, registry, old, new, cached_roots);
+            shared_prefix_count += 1;
+        }
+
+        // If that was all of the old children, then create and append the remaining
+        // new children and we're finished.
+        if shared_prefix_count == old.len() {
+            change_list.go_up();
+            change_list.commit_traversal();
+            create_and_append_children(
+                cached_set,
+                change_list,
+                registry,
+                &new[shared_prefix_count..],
+                cached_roots,
+            );
+            return KeyedPrefixResult::Finished;
+        }
+
+        // And if that was all of the new children, then remove all of the remaining
+        // old children and we're finished.
+        if shared_prefix_count == new.len() {
+            change_list.go_to_sibling(shared_prefix_count);
+            change_list.commit_traversal();
+            remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
+            return KeyedPrefixResult::Finished;
+        }
+
+        change_list.go_up();
+        KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
+    }
+
+    // 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:
+    //
+    //     [... parent]
+    //
+    // Upon exit from this function, it will be restored to that same state.
+    fn diff_keyed_middle(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+        mut new: &[Node],
+        cached_roots: &mut FxHashSet<CacheId>,
+        shared_prefix_count: usize,
+        shared_suffix_count: usize,
+        old_shared_suffix_start: usize,
+    ) {
+        // Should have already diffed the shared-key prefixes and suffixes.
+        debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
+        debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
+
+        // The algorithm below relies upon using `u32::MAX` as a sentinel
+        // value, so if we have that many new nodes, it won't work. This
+        // check is a bit academic (hence only enabled in debug), since
+        // wasm32 doesn't have enough address space to hold that many nodes
+        // in memory.
+        debug_assert!(new.len() < u32::MAX as usize);
+
+        // Map from each `old` node's key to its index within `old`.
+        let mut old_key_to_old_index = FxHashMap::default();
+        old_key_to_old_index.reserve(old.len());
+        old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
+
+        // The set of shared keys between `new` and `old`.
+        let mut shared_keys = FxHashSet::default();
+        // Map from each index in `new` to the index of the node in `old` that
+        // has the same key.
+        let mut new_index_to_old_index = Vec::with_capacity(new.len());
+        new_index_to_old_index.extend(new.iter().map(|n| {
+            let key = n.key();
+            if let Some(&i) = old_key_to_old_index.get(&key) {
+                shared_keys.insert(key);
+                i
+            } else {
+                u32::MAX as usize
+            }
+        }));
+
+        // 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_suffix_count == 0 && shared_keys.is_empty() {
+            if shared_prefix_count == 0 {
+                change_list.commit_traversal();
+                remove_all_children(change_list, registry, old);
+            } else {
+                change_list.go_down_to_child(shared_prefix_count);
+                change_list.commit_traversal();
+                remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
+            }
+            create_and_append_children(cached_set, change_list, registry, new, cached_roots);
+            return;
+        }
+
+        // Save each of the old children whose keys are reused in the new
+        // children.
+        let mut old_index_to_temp = vec![u32::MAX; old.len()];
+        let mut start = 0;
+        loop {
+            let end = (start..old.len())
+                .find(|&i| {
+                    let key = old[i].key();
+                    !shared_keys.contains(&key)
+                })
+                .unwrap_or(old.len());
+
+            if end - start > 0 {
+                change_list.commit_traversal();
+                let mut t = change_list.save_children_to_temporaries(
+                    shared_prefix_count + start,
+                    shared_prefix_count + end,
+                );
+                for i in start..end {
+                    old_index_to_temp[i] = t;
+                    t += 1;
+                }
+            }
+
+            debug_assert!(end <= old.len());
+            if end == old.len() {
+                break;
+            } else {
+                start = end + 1;
+            }
+        }
+
+        // Remove any old children whose keys were not reused in the new
+        // children. Remove from the end first so that we don't mess up indices.
+        let mut removed_count = 0;
+        for (i, old_child) in old.iter().enumerate().rev() {
+            if !shared_keys.contains(&old_child.key()) {
+                registry.remove_subtree(old_child);
+                change_list.commit_traversal();
+                change_list.remove_child(i + shared_prefix_count);
+                removed_count += 1;
+            }
+        }
+
+        // If there aren't any more new children, then we are done!
+        if new.is_empty() {
+            return;
+        }
+
+        // The longest increasing subsequence within `new_index_to_old_index`. This
+        // is the longest sequence on DOM nodes in `old` that are relatively ordered
+        // correctly within `new`. We will leave these nodes in place in the DOM,
+        // and only move nodes that are not part of the LIS. This results in the
+        // maximum number of DOM nodes left in place, AKA the minimum number of DOM
+        // nodes moved.
+        let mut new_index_is_in_lis = FxHashSet::default();
+        new_index_is_in_lis.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 new_index_is_in_lis,
+            |a, b| a < b,
+            &mut predecessors,
+            &mut starts,
+        );
+
+        // Now we will iterate from the end of the new children back to the
+        // beginning, diffing old children we are reusing and if they aren't in the
+        // LIS moving them to their new destination, or creating new children. Note
+        // that iterating in reverse order lets us use `Node.prototype.insertBefore`
+        // to move/insert children.
+        //
+        // But first, we ensure that we have a child on the change list stack that
+        // we can `insertBefore`. We handle this once before looping over `new`
+        // children, so that we don't have to keep checking on every loop iteration.
+        if shared_suffix_count > 0 {
+            // There is a shared suffix after these middle children. We will be
+            // inserting before that shared suffix, so add the first child of that
+            // shared suffix to the change list stack.
+            //
+            // [... parent]
+            change_list.go_down_to_child(old_shared_suffix_start - removed_count);
+        // [... parent first_child_of_shared_suffix]
+        } else {
+            // There is no shared suffix coming after these middle children.
+            // Therefore we have to process the last child in `new` and move it to
+            // the end of the parent's children if it isn't already there.
+            let last_index = new.len() - 1;
+            let last = new.last().unwrap_throw();
+            new = &new[..new.len() - 1];
+            if shared_keys.contains(&last.key()) {
+                let old_index = new_index_to_old_index[last_index];
+                let temp = old_index_to_temp[old_index];
+                // [... parent]
+                change_list.go_down_to_temp_child(temp);
+                // [... parent last]
+                diff(
+                    cached_set,
+                    change_list,
+                    registry,
+                    &old[old_index],
+                    last,
+                    cached_roots,
+                );
+                if new_index_is_in_lis.contains(&last_index) {
+                    // Don't move it, since it is already where it needs to be.
+                } else {
+                    change_list.commit_traversal();
+                    // [... parent last]
+                    change_list.append_child();
+                    // [... parent]
+                    change_list.go_down_to_temp_child(temp);
+                    // [... parent last]
+                }
+            } else {
+                change_list.commit_traversal();
+                // [... parent]
+                create(cached_set, change_list, registry, last, cached_roots);
+                // [... parent last]
+                change_list.append_child();
+                // [... parent]
+                change_list.go_down_to_reverse_child(0);
+                // [... parent last]
+            }
+        }
+
+        for (new_index, new_child) in new.iter().enumerate().rev() {
+            let old_index = new_index_to_old_index[new_index];
+            if old_index == u32::MAX as usize {
+                debug_assert!(!shared_keys.contains(&new_child.key()));
+                change_list.commit_traversal();
+                // [... parent successor]
+                create(cached_set, change_list, registry, new_child, cached_roots);
+                // [... parent successor new_child]
+                change_list.insert_before();
+            // [... parent new_child]
+            } else {
+                debug_assert!(shared_keys.contains(&new_child.key()));
+                let temp = old_index_to_temp[old_index];
+                debug_assert_ne!(temp, u32::MAX);
+
+                if new_index_is_in_lis.contains(&new_index) {
+                    // [... parent successor]
+                    change_list.go_to_temp_sibling(temp);
+                // [... parent new_child]
+                } else {
+                    change_list.commit_traversal();
+                    // [... parent successor]
+                    change_list.push_temporary(temp);
+                    // [... parent successor new_child]
+                    change_list.insert_before();
+                    // [... parent new_child]
+                }
+
+                diff(
+                    cached_set,
+                    change_list,
+                    registry,
+                    &old[old_index],
+                    new_child,
+                    cached_roots,
+                );
+            }
+        }
+
+        // [... parent child]
+        change_list.go_up();
+        // [... parent]
+    }
+
+    // Diff the suffix of keyed children that share the same keys in the same order.
+    //
+    // The parent must be on the change list stack when we enter this function:
+    //
+    //     [... parent]
+    //
+    // When this function exits, the change list stack remains the same.
+    fn diff_keyed_suffix(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+        new: &[Node],
+        cached_roots: &mut FxHashSet<CacheId>,
+        new_shared_suffix_start: usize,
+    ) {
+        debug_assert_eq!(old.len(), new.len());
+        debug_assert!(!old.is_empty());
+
+        // [... parent]
+        change_list.go_down();
+        // [... parent new_child]
+
+        for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
+            change_list.go_to_sibling(new_shared_suffix_start + i);
+            diff(
+                cached_set,
+                change_list,
+                registry,
+                old_child,
+                new_child,
+                cached_roots,
+            );
+        }
+
+        // [... parent]
+        change_list.go_up();
+    }
+
+    // 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(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+        new: &[Node],
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) {
+        // Handled these cases in `diff_children` before calling this function.
+        debug_assert!(!new.is_empty());
+        debug_assert!(!old.is_empty());
+
+        //     [... parent]
+        change_list.go_down();
+        //     [... parent child]
+
+        for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
+            // [... parent prev_child]
+            change_list.go_to_sibling(i);
+            // [... parent this_child]
+            diff(
+                cached_set,
+                change_list,
+                registry,
+                old_child,
+                new_child,
+                cached_roots,
+            );
+        }
+
+        match old.len().cmp(&new.len()) {
+            Ordering::Greater => {
+                // [... parent prev_child]
+                change_list.go_to_sibling(new.len());
+                // [... parent first_child_to_remove]
+                change_list.commit_traversal();
+                remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
+                // [... parent]
+            }
+            Ordering::Less => {
+                // [... parent last_child]
+                change_list.go_up();
+                // [... parent]
+                change_list.commit_traversal();
+                create_and_append_children(
+                    cached_set,
+                    change_list,
+                    registry,
+                    &new[old.len()..],
+                    cached_roots,
+                );
+            }
+            Ordering::Equal => {
+                // [... parent child]
+                change_list.go_up();
+                // [... parent]
+            }
+        }
+    }
+
+    // Create the given children and append them to the parent node.
+    //
+    // The parent node must currently be on top of the change list stack:
+    //
+    //     [... parent]
+    //
+    // When this function returns, the change list stack is in the same state.
+    fn create_and_append_children(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        new: &[Node],
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) {
+        debug_assert!(change_list.traversal_is_committed());
+        for child in new {
+            create(cached_set, change_list, registry, child, cached_roots);
+            change_list.append_child();
+        }
+    }
+
+    // Remove all of a node's children.
+    //
+    // The change list stack must have this shape upon entry to this function:
+    //
+    //     [... parent]
+    //
+    // When this function returns, the change list stack is in the same state.
+    fn remove_all_children(
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+    ) {
+        debug_assert!(change_list.traversal_is_committed());
+        for child in old {
+            registry.remove_subtree(child);
+        }
+        // Fast way to remove all children: set the node's textContent to an empty
+        // string.
+        change_list.set_text("");
+    }
+
+    // Remove the current child and all of its following siblings.
+    //
+    // The change list stack must have this shape upon entry to this function:
+    //
+    //     [... parent child]
+    //
+    // After the function returns, the child is no longer on the change list stack:
+    //
+    //     [... parent]
+    fn remove_self_and_next_siblings(
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        old: &[Node],
+    ) {
+        debug_assert!(change_list.traversal_is_committed());
+        for child in old {
+            registry.remove_subtree(child);
+        }
+        change_list.remove_self_and_next_siblings();
+    }
+
+    // Emit instructions to create the given virtual node.
+    //
+    // The change list stack may have any shape upon entering this function:
+    //
+    //     [...]
+    //
+    // When this function returns, the new node is on top of the change list stack:
+    //
+    //     [... node]
+    fn create(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        node: &Node,
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) {
+        debug_assert!(change_list.traversal_is_committed());
+        match node.kind {
+            NodeKind::Text(TextNode { text }) => {
+                change_list.create_text_node(text);
+            }
+            NodeKind::Element(&ElementNode {
+                key: _,
+                tag_name,
+                listeners,
+                attributes,
+                children,
+                namespace,
+            }) => {
+                if let Some(namespace) = namespace {
+                    change_list.create_element_ns(tag_name, namespace);
+                } else {
+                    change_list.create_element(tag_name);
+                }
+
+                for l in listeners {
+                    unsafe {
+                        registry.add(l);
+                    }
+                    change_list.new_event_listener(l);
+                }
+
+                for attr in attributes {
+                    change_list.set_attribute(&attr.name, &attr.value, namespace.is_some());
+                }
+
+                // Fast path: if there is a single text child, it is faster to
+                // create-and-append the text node all at once via setting the
+                // parent's `textContent` in a single change list instruction than
+                // to emit three instructions to (1) create a text node, (2) set its
+                // text content, and finally (3) append the text node to this
+                // parent.
+                if children.len() == 1 {
+                    if let Node {
+                        kind: NodeKind::Text(TextNode { text }),
+                    } = children[0]
+                    {
+                        change_list.set_text(text);
+                        return;
+                    }
+                }
+
+                for child in children {
+                    create(cached_set, change_list, registry, child, cached_roots);
+                    change_list.append_child();
+                }
+            }
+            NodeKind::Cached(ref c) => {
+                cached_roots.insert(c.id);
+                let (node, template) = cached_set.get(c.id);
+                if let Some(template) = template {
+                    create_with_template(
+                        cached_set,
+                        change_list,
+                        registry,
+                        template,
+                        node,
+                        cached_roots,
+                    );
+                } else {
+                    create(cached_set, change_list, registry, node, cached_roots);
+                }
+            }
+        }
+    }
+
+    // Get or create the template.
+    //
+    // Upon entering this function the change list stack may be in any shape:
+    //
+    //     [...]
+    //
+    // When this function returns, it leaves a freshly cloned copy of the template
+    // on the top of the change list stack:
+    //
+    //     [... template]
+    #[inline]
+    fn get_or_create_template<'a>(
+        cached_set: &'a CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        cached_roots: &mut FxHashSet<CacheId>,
+        template_id: CacheId,
+    ) -> (&'a Node<'a>, bool) {
+        let (template, template_template) = cached_set.get(template_id);
+        debug_assert!(
+            template_template.is_none(),
+            "templates should not be templated themselves"
+        );
+
+        // If we haven't already created and saved the physical DOM subtree for this
+        // template, do that now.
+        if change_list.has_template(template_id) {
+            // Clone the template and push it onto the stack.
+            //
+            // [...]
+            change_list.push_template(template_id);
+            // [... template]
+
+            (template, true)
+        } else {
+            // [...]
+            create(cached_set, change_list, registry, template, cached_roots);
+            // [... template]
+            change_list.save_template(template_id);
+            // [... template]
+
+            (template, false)
+        }
+    }
+
+    fn create_and_replace(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        new_template: Option<CacheId>,
+        old: &Node,
+        new: &Node,
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) {
+        debug_assert!(change_list.traversal_is_committed());
+
+        if let Some(template_id) = new_template {
+            let (template, needs_listeners) = get_or_create_template(
+                cached_set,
+                change_list,
+                registry,
+                cached_roots,
+                template_id,
+            );
+            change_list.replace_with();
+
+            let mut old_forcing = None;
+            if needs_listeners {
+                old_forcing = Some(change_list.push_force_new_listeners());
+            }
+
+            diff(
+                cached_set,
+                change_list,
+                registry,
+                template,
+                new,
+                cached_roots,
+            );
+
+            if let Some(old) = old_forcing {
+                change_list.pop_force_new_listeners(old);
+            }
+
+            change_list.commit_traversal();
+        } else {
+            create(cached_set, change_list, registry, new, cached_roots);
+            change_list.replace_with();
+        }
+        registry.remove_subtree(old);
+    }
+
+    fn create_with_template(
+        cached_set: &CachedSet,
+        change_list: &mut ChangeListBuilder,
+        registry: &mut EventsRegistry,
+        template_id: CacheId,
+        node: &Node,
+        cached_roots: &mut FxHashSet<CacheId>,
+    ) {
+        debug_assert!(change_list.traversal_is_committed());
+
+        // [...]
+        let (template, needs_listeners) =
+            get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
+        // [... template]
+
+        // Now diff the node with its template.
+        //
+        // We must force adding new listeners instead of updating existing ones,
+        // since listeners don't get cloned in `cloneNode`.
+        let mut old_forcing = None;
+        if needs_listeners {
+            old_forcing = Some(change_list.push_force_new_listeners());
+        }
+
+        diff(
+            cached_set,
+            change_list,
+            registry,
+            template,
+            node,
+            cached_roots,
+        );
+
+        if let Some(old) = old_forcing {
+            change_list.pop_force_new_listeners(old);
+        }
+
+        // Make sure that we come back up to the level we were at originally.
+        change_list.commit_traversal();
+    }
+}

+ 44 - 0
packages/core/src/error.rs

@@ -0,0 +1,44 @@
+use thiserror::Error as ThisError;
+
+pub type Result<T, E = Error> = std::result::Result<T, E>;
+
+#[derive(ThisError, Debug)]
+pub enum Error {
+    #[error("No event to progress")]
+    NoEvent,
+
+    #[error("Out of compute credits")]
+    OutOfCredits,
+
+    /// Used when errors need to propogate but are too unique to be typed
+    #[error("{0}")]
+    Unique(String),
+
+    #[error("GraphQL error: {0}")]
+    GraphQL(String),
+
+    // TODO(haze): Remove, or make a better error. This is pretty much useless
+    #[error("GraphQL response mismatch. Got {found} but expected {expected}")]
+    GraphQLMisMatch {
+        expected: &'static str,
+        found: String,
+    },
+
+    #[error("Difference detected in SystemTime! {0}")]
+    SystemTime(#[from] std::time::SystemTimeError),
+
+    #[error("Failed to parse Integer")]
+    ParseInt(#[from] std::num::ParseIntError),
+
+    #[error("")]
+    MissingAuthentication,
+
+    #[error("Failed to create experiment run: {0}")]
+    FailedToCreateExperimentRun(String),
+
+    #[error("Could not find shared behavior with ID {0}")]
+    MissingSharedBehavior(String),
+
+    #[error("I/O Error: {0}")]
+    IO(#[from] std::io::Error),
+}

+ 6 - 4
packages/core/src/hooks.rs

@@ -1,14 +1,16 @@
 //! Useful, foundational hooks that 3rd parties can implement.
 //! Currently implemented:
-//! - use_ref
-//! - use_state
+//! - [x] use_ref
+//! - [x] use_state
+//! - [ ] use_reducer
+//! - [ ] use_effect
 
 pub use use_ref_def::use_ref;
 pub use use_state_def::use_state;
 
 mod use_state_def {
     use crate::inner::*;
-    use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc};
+    use std::{cell::RefCell, ops::DerefMut, rc::Rc};
 
     struct UseState<T: 'static> {
         new_val: Rc<RefCell<Option<T>>>,
@@ -79,7 +81,7 @@ mod use_state_def {
 
 mod use_ref_def {
     use crate::inner::*;
-    use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc};
+    use std::{cell::RefCell, ops::DerefMut};
 
     pub struct UseRef<T: 'static> {
         _current: RefCell<T>,

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

@@ -68,6 +68,8 @@
 pub mod component;
 pub mod context;
 pub mod debug_renderer;
+pub mod diff;
+pub mod error;
 pub mod events;
 pub mod hooks;
 pub mod nodebuilder;
@@ -85,6 +87,7 @@ pub(crate) mod inner {
     pub use crate::component::{Component, Properties};
     use crate::context::hooks::Hook;
     pub use crate::context::Context;
+    pub use crate::error::{Error, Result};
     use crate::nodes;
     pub use crate::scope::Scope;
     pub use crate::virtual_dom::VirtualDom;

+ 19 - 34
packages/core/src/nodes.rs

@@ -232,56 +232,41 @@ mod vtext {
 
     impl<'a> VText<'a> {
         // / Create an new `VText` instance with the specified text.
-        // pub fn new<S>(text: S) -> Self
+        pub fn new(text: &'a str) -> Self
+// pub fn new<S>(text: S) -> Self
         // where
-        //     S: Into<String>,
-        // {
-        //     VText { text: text.into() }
-        // }
+        //     S: Into<str>,
+        {
+            VText { text: text.into() }
+        }
     }
 }
 
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 mod vcomponent {
-    use crate::prelude::Properties;
+    use crate::inner::{Properties, FC};
     use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
 
     use super::VNode;
 
     pub struct VComponent<'src> {
         _p: PhantomData<&'src ()>,
-        runner: Box<dyn Properties + 'src>,
+        props: Box<dyn Properties>,
+        // props: Box<dyn Properties + 'src>,
         caller: *const (),
-        props_type: TypeId,
     }
 
-    // impl<'src> PartialEq for CallerSource<'src> {
-    //     fn eq(&self, other: &Self) -> bool {
-    //         todo!()
-    //     }
-    // }
-    // caller: Box<dyn Fn
-    // should we box the caller?
-    // probably, so we can call it again
-    //
-    // props_id: TypeId,
-    // callerIDs are unsafely coerced to function pointers
-    // This is okay because #1, we store the props_id and verify and 2# the html! macro rejects components not made this way
-    //
-    // Manually constructing the VComponent is not possible from 3rd party crates
-
     impl<'a> VComponent<'a> {
-        // /// Construct a VComponent directly from a function component
-        // /// This should be *very* fast - we store the function pointer and props type ID. It should also be small on the stack
-        // pub fn from_fn<P: Properties>(f: FC<P>, props: P) -> Self {
-        //     // // Props needs to be static
-        //     // let props_id = std::any::TypeId::of::<P>();
-
-        //     // // Cast the caller down
-
-        //     // Self { props_id }
-        //     Self {}
-        // }
+        pub fn new<P: Properties + 'static>(caller: FC<P>, props: P) -> Self {
+            let caller = caller as *const ();
+            let props = Box::new(props);
+
+            Self {
+                _p: PhantomData {},
+                props,
+                caller,
+            }
+        }
     }
 }

+ 56 - 39
packages/core/src/scope.rs

@@ -1,6 +1,6 @@
-use crate::context::hooks::Hook;
 use crate::inner::*;
 use crate::nodes::VNode;
+use crate::{context::hooks::Hook, diff::diff};
 use bumpalo::Bump;
 use generational_arena::Index;
 use std::{
@@ -15,22 +15,35 @@ use std::{
 /// 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.
 pub struct Scope {
+    // TODO @Jon
     // These hooks are actually references into the hook arena
     // These two could be combined with "OwningRef" to remove unsafe usage
-    // TODO @Jon
+    // could also use ourborous
     hooks: RefCell<Vec<*mut Hook>>,
     hook_arena: typed_arena::Arena<Hook>,
 
     // Map to the parent
     parent: Option<Index>,
 
+    // todo, do better with the active frame stuff
+    frames: [Bump; 2],
+
+    // somehow build this vnode with a lifetime tied to self
+    cur_node: *mut VNode<'static>,
+
+    active_frame: u8,
+
+    // IE Which listeners need to be woken up?
+    listeners: Vec<Box<dyn Fn()>>,
+
+    //
     props_type: TypeId,
     caller: *const i32,
 }
 
 impl Scope {
     // create a new scope from a function
-    pub fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self {
+    pub(crate) fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self {
         // Capture the props type
         let props_type = TypeId::of::<T>();
         let hook_arena = typed_arena::Arena::new();
@@ -38,56 +51,60 @@ impl Scope {
 
         let caller = f as *const i32;
 
+        let frames = [Bump::new(), Bump::new()];
+
+        let listeners = Vec::new();
+
+        let active_frame = 1;
+
+        let new = frames[0].alloc(VNode::Text(VText::new("")));
+        let cur_node = new as *mut _;
+
         Self {
             hook_arena,
             hooks,
             props_type,
             caller,
+            active_frame,
+            listeners,
             parent,
+            frames,
+            cur_node,
         }
     }
 
-    pub fn create_context<'a, T: Properties>(
-        &'a mut self,
-        components: &'a generational_arena::Arena<Scope>,
-        props: &'a T,
-    ) -> Context {
-        
-        //
-        Context {
+    /// Create a new context and run the component with references from the Virtual Dom
+    /// This function downcasts the function pointer based on the stored props_type
+    ///
+    /// Props is ?Sized because we borrow the props
+    pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
+        let bump = &self.frames[0];
+
+        let ctx = Context {
             scope: &*self,
             _p: PhantomData {},
             arena: &self.hook_arena,
             hooks: &self.hooks,
             idx: 0.into(),
-            components,
-        }
-    }
+            bump,
+        };
 
-    /// Create a new context and run the component with references from the Virtual Dom
-    /// This function downcasts the function pointer based on the stored props_type
-    fn run<T: 'static>(&self, f: FC<T>) {}
-}
+        /*
+        SAFETY ALERT
+
+        This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
+        We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
+        we transmute the function back using the props as reference.
 
-mod bad_unsafety {
-    // todo
-    // fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
-    //     if self.props_type == TypeId::of::<T>() {
-    //         /*
-    //         SAFETY ALERT
-
-    //         This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
-    //         We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
-    //         we transmute the function back using the props as reference.
-
-    //         This is safe because we check that the generic type matches before casting.
-    //         */
-    //         let caller = unsafe { std::mem::transmute::<*const i32, FC<T>>(self.caller) };
-    //     // let ctx = self.create_context::<T>();
-    //     // // TODO: do something with these nodes
-    //     // let nodes = caller(ctx);
-    //     } else {
-    //         panic!("Do not try to use `call` on Scopes with the wrong props type")
-    //     }
-    // }
+        we could do a better check to make sure that the TypeID is correct
+        --
+        This is safe because we check that the generic type matches before casting.
+        */
+        let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
+        let new_nodes = caller(ctx, props);
+        let old_nodes: &mut VNode<'static> = unsafe { &mut *self.cur_node };
+
+        // perform the diff, dumping into the change list
+        crate::diff::diff(old_nodes, &new_nodes);
+    }
 }

+ 93 - 56
packages/core/src/virtual_dom.rs

@@ -40,7 +40,7 @@ use std::{
 pub struct VirtualDom<P: Properties> {
     /// All mounted components are arena allocated to make additions, removals, and references easy to work with
     /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
-    components: Arena<Scope>,
+    pub(crate) components: Arena<Scope>,
 
     /// The index of the root component.
     base_scope: Index,
@@ -48,7 +48,8 @@ pub struct VirtualDom<P: Properties> {
     /// Components generate lifecycle events
     event_queue: Vec<LifecycleEvent>,
 
-    root_props: P,
+    // mark the root props with P, even though they're held by the root component
+    _p: PhantomData<P>,
 }
 
 /// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
@@ -83,7 +84,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
         let base_scope = components.insert(Scope::new(root, None));
 
         // Create a new mount event with no root container
-        let first_event = LifecycleEvent::mount(base_scope, None, 0);
+        let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
 
         // Create an event queue with a mount for the base scope
         let event_queue = vec![first_event];
@@ -92,83 +93,119 @@ impl<P: Properties + 'static> VirtualDom<P> {
             components,
             base_scope,
             event_queue,
-            root_props,
+            _p: PhantomData {},
         }
     }
 
-    /// Pop an event off the even queue and process it
-    pub fn progress(&mut self) -> Result<(), ()> {
-        let LifecycleEvent { index, event_type } = self.event_queue.pop().ok_or(())?;
-
-        let scope = self.components.get(index).ok_or(())?;
-
-        match event_type {
-            // Component needs to be mounted to the virtual dom
-            LifecycleType::Mount { to, under } => {
-                // todo! run the FC with the bump allocator
-                // Run it with its properties
-                if let Some(other) = to {
-                    // mount to another component
-                    let p = ();
-                } else {
-                    // mount to the root
-                }
-            }
+    /// Pop an event off the event queue and process it
+    pub fn progress(&mut self) -> Result<()> {
+        let event = self.event_queue.pop().ok_or(Error::NoEvent)?;
 
-            // The parent for this component generated new props and the component needs update
-            LifecycleType::PropsChanged {} => {}
+        process_event(self, event)
+    }
 
-            // Component was successfully mounted to the dom
-            LifecycleType::Mounted {} => {}
+    /// Update the root props, causing a full event cycle
+    pub fn update_props(&mut self, new_props: P) {}
+}
 
-            // Component was removed from the DOM
-            // Run any destructors and cleanup for the hooks and the dump the component
-            LifecycleType::Removed {} => {
-                let f = self.components.remove(index);
+/// Using mutable access to the Virtual Dom, progress a given lifecycle event
+///
+///
+///
+///
+///
+///
+fn process_event<P: Properties>(
+    dom: &mut VirtualDom<P>,
+    LifecycleEvent { index, event_type }: LifecycleEvent,
+) -> Result<()> {
+    let scope = dom.components.get(index).ok_or(Error::NoEvent)?;
+
+    match event_type {
+        // Component needs to be mounted to the virtual dom
+        LifecycleType::Mount { to, under, props } => {
+            if let Some(other) = to {
+                // mount to another component
+            } else {
+                // mount to the root
             }
 
-            // Component was messaged via the internal subscription service
-            LifecycleType::Messaged => {}
+            let g = props.as_ref();
+            scope.run(g);
+            // scope.run(runner, props, dom);
         }
 
-        Ok(())
-    }
+        // The parent for this component generated new props and the component needs update
+        LifecycleType::PropsChanged { props } => {
+            //
+        }
 
-    /// Update the root props, causing a full event cycle
-    pub fn update_props(&mut self, new_props: P) {}
+        // Component was successfully mounted to the dom
+        LifecycleType::Mounted {} => {
+            //
+        }
 
-    /// Run through every event in the event queue until the events are empty.
-    /// Function is asynchronous to allow for async components to finish their work.
-    pub async fn progess_completely() {}
+        // Component was removed from the DOM
+        // Run any destructors and cleanup for the hooks and the dump the component
+        LifecycleType::Removed {} => {
+            let f = dom.components.remove(index);
+        }
 
-    /// Create a new context object for a given component and scope
-    fn new_context<T: Properties>(&self) -> Context {
-        // fn new_context<T: Properties>(&self) -> Context<T> {
-        todo!()
+        // Component was messaged via the internal subscription service
+        LifecycleType::Messaged => {
+            //
+        }
+
+        // Event from renderer was fired with a given listener ID
+        //
+        LifecycleType::Callback { listener_id } => {}
     }
 
-    /// Stop writing to the current buffer and start writing to the new one.
-    /// This should be done inbetween CallbackEvent handling, but not between lifecycle events.
-    pub fn swap_buffers(&mut self) {}
+    Ok(())
 }
 
 pub struct LifecycleEvent {
     pub index: Index,
     pub event_type: LifecycleType,
 }
-impl LifecycleEvent {
-    fn mount(which: Index, to: Option<Index>, under: usize) -> Self {
-        Self {
-            index: which,
-            event_type: LifecycleType::Mount { to, under },
-        }
-    }
-}
+
 /// The internal lifecycle event system is managed by these
+/// Right now, we box the properties and but them in the enum
+/// Later, we could directly call the chain of children without boxing
+/// We could try to reuse the boxes somehow
 pub enum LifecycleType {
-    Mount { to: Option<Index>, under: usize },
-    PropsChanged,
+    Mount {
+        to: Option<Index>,
+        under: usize,
+        props: Box<dyn Properties>,
+    },
+    PropsChanged {
+        props: Box<dyn Properties>,
+    },
     Mounted,
     Removed,
     Messaged,
+    Callback {
+        listener_id: i32,
+    },
+}
+
+impl LifecycleEvent {
+    // helper method for shortcutting to the enum type
+    // probably not necessary
+    fn mount<P: Properties + 'static>(
+        which: Index,
+        to: Option<Index>,
+        under: usize,
+        props: P,
+    ) -> Self {
+        Self {
+            index: which,
+            event_type: LifecycleType::Mount {
+                to,
+                under,
+                props: Box::new(props),
+            },
+        }
+    }
 }