Browse Source

wip: pre any props

Jonathan Kelley 2 years ago
parent
commit
493591400f

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

@@ -13,10 +13,7 @@ pub trait AnyProps<'a> {
     unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
 }
 
-pub(crate) struct VComponentProps<'a, P, A, F = Element<'a>>
-where
-    F: ComponentReturn<'a, A>,
-{
+pub(crate) struct VComponentProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> {
     pub render_fn: fn(Scope<'a, P>) -> F,
     pub memo: unsafe fn(&P, &P) -> bool,
     pub props: P,

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

@@ -1,13 +1,12 @@
 use std::cell::Cell;
-
-use bumpalo::Bump;
-
 use crate::factory::RenderReturn;
+use bumpalo::Bump;
 
 pub struct BumpFrame {
     pub bump: Bump,
     pub node: Cell<*mut RenderReturn<'static>>,
 }
+
 impl BumpFrame {
     pub fn new(capacity: usize) -> Self {
         let bump = Bump::with_capacity(capacity);
@@ -22,6 +21,7 @@ impl BumpFrame {
         self.node.set(std::ptr::null_mut());
     }
 
+    /// Creates a new lifetime out of thin air
     pub unsafe fn load_node<'b>(&self) -> &'b RenderReturn<'b> {
         unsafe { std::mem::transmute(&*self.node.get()) }
     }

+ 15 - 18
packages/core/src/create.rs

@@ -1,5 +1,5 @@
 use crate::factory::RenderReturn;
-use crate::innerlude::{Mutations, SuspenseContext};
+use crate::innerlude::{Mutations, SuspenseContext, VText};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
@@ -70,9 +70,11 @@ impl VirtualDom {
                                 &template.dynamic_nodes[*id],
                                 *id,
                             ),
-                        DynamicNode::Text {
-                            id: slot, value, ..
-                        } => {
+                        DynamicNode::Text(VText {
+                            id: slot,
+                            value,
+                            inner,
+                        }) => {
                             let id = self.next_element(template, template.template.node_paths[*id]);
                             slot.set(id);
                             mutations.push(CreateTextNode { value, id });
@@ -237,7 +239,7 @@ impl VirtualDom {
         idx: usize,
     ) -> usize {
         match &node {
-            DynamicNode::Text { id, value, .. } => {
+            DynamicNode::Text(VText { id, value, inner }) => {
                 let new_id = self.next_element(template, template.template.node_paths[idx]);
                 id.set(new_id);
                 mutations.push(HydrateText {
@@ -248,17 +250,12 @@ impl VirtualDom {
                 0
             }
 
-            DynamicNode::Component {
-                props,
-                placeholder,
-                scope: scope_slot,
-                ..
-            } => {
+            DynamicNode::Component(component) => {
                 let scope = self
-                    .new_scope(unsafe { std::mem::transmute(props.get()) })
+                    .new_scope(unsafe { std::mem::transmute(component.props.get()) })
                     .id;
 
-                scope_slot.set(Some(scope));
+                component.scope.set(Some(scope));
 
                 let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
 
@@ -269,7 +266,7 @@ impl VirtualDom {
 
                     RenderReturn::Async(_) => {
                         let new_id = self.next_element(template, template.template.node_paths[idx]);
-                        placeholder.set(Some(new_id));
+                        component.placeholder.set(Some(new_id));
                         self.scopes[scope.0].placeholder.set(Some(new_id));
 
                         mutations.push(AssignId {
@@ -306,7 +303,7 @@ impl VirtualDom {
                                 // Since this is a boundary, use it as a placeholder
                                 let new_id =
                                     self.next_element(template, template.template.node_paths[idx]);
-                                placeholder.set(Some(new_id));
+                                component.placeholder.set(Some(new_id));
                                 self.scopes[scope.0].placeholder.set(Some(new_id));
                                 mutations.push(AssignId {
                                     id: new_id,
@@ -342,9 +339,9 @@ impl VirtualDom {
                 }
             }
 
-            DynamicNode::Fragment { nodes, .. } => {
-                //
-                nodes
+            DynamicNode::Fragment(frag) => {
+                // Todo: if there's no children create a placeholder instead ?
+                frag.nodes
                     .iter()
                     .fold(0, |acc, child| acc + self.create(mutations, child))
             }

+ 185 - 165
packages/core/src/diff.rs

@@ -1,7 +1,7 @@
 use std::any::Any;
 
 use crate::factory::RenderReturn;
-use crate::innerlude::Mutations;
+use crate::innerlude::{Mutations, VComponent, VFragment, VText};
 use crate::virtual_dom::VirtualDom;
 use crate::{Attribute, AttributeValue, TemplateNode};
 
@@ -42,16 +42,12 @@ impl<'b> VirtualDom {
     pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
         let scope_state = &mut self.scopes[scope.0];
 
-        let cur_arena = scope_state.previous_frame();
-        let prev_arena = scope_state.current_frame();
-        // let cur_arena = scope_state.current_frame();
-        // let prev_arena = scope_state.previous_frame();
-
-        // relax the borrow checker
-        let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
-        let prev_arena: &BumpFrame = unsafe { std::mem::transmute(prev_arena) };
+        // Load the old and new bump arenas
+        let cur_arena = scope_state.current_frame();
+        let prev_arena = scope_state.previous_frame();
 
         // Make sure the nodes arent null (they've been set properly)
+        // This is a rough check to make sure we're not entering any UB
         assert_ne!(
             cur_arena.node.get(),
             std::ptr::null_mut(),
@@ -64,11 +60,11 @@ impl<'b> VirtualDom {
         );
 
         self.scope_stack.push(scope);
-
-        let left = unsafe { prev_arena.load_node() };
-        let right = unsafe { cur_arena.load_node() };
-
-        self.diff_maybe_node(mutations, left, right);
+        unsafe {
+            let cur_arena = cur_arena.load_node();
+            let prev_arena = prev_arena.load_node();
+            self.diff_maybe_node(mutations, prev_arena, cur_arena);
+        }
         self.scope_stack.pop();
     }
 
@@ -109,10 +105,9 @@ impl<'b> VirtualDom {
         left_template: &'b VNode<'b>,
         right_template: &'b VNode<'b>,
     ) {
-        println!("diffing {:?} and {:?}", left_template, right_template);
-
         if left_template.template.id != right_template.template.id {
             // do a light diff of the roots nodes.
+            todo!("lightly diff roots and replace");
             return;
         }
 
@@ -122,23 +117,21 @@ impl<'b> VirtualDom {
             .zip(right_template.dynamic_attrs.iter())
             .enumerate()
         {
-            debug_assert!(left_attr.name == right_attr.name);
-            debug_assert!(left_attr.value == right_attr.value);
-
             // Move over the ID from the old to the new
             right_attr
                 .mounted_element
                 .set(left_attr.mounted_element.get());
 
             if left_attr.value != right_attr.value {
-                println!("DIFF ATTR: {:?} -> {:?}", left_attr, right_attr);
-                let value = "todo!()";
-                muts.push(Mutation::SetAttribute {
-                    id: left_attr.mounted_element.get(),
-                    name: left_attr.name,
-                    value,
-                    ns: right_attr.namespace,
-                });
+                // todo: add more types of attribute values
+                if let AttributeValue::Text(text) = right_attr.value {
+                    muts.push(Mutation::SetAttribute {
+                        id: left_attr.mounted_element.get(),
+                        name: left_attr.name,
+                        value: text,
+                        ns: right_attr.namespace,
+                    });
+                }
             }
         }
 
@@ -148,102 +141,188 @@ impl<'b> VirtualDom {
             .zip(right_template.dynamic_nodes.iter())
             .enumerate()
         {
-            #[rustfmt::skip]
-            match (left_node, right_node) {
-                (DynamicNode::Component { props: lprops, .. }, DynamicNode::Component {  static_props: is_static , props: rprops, .. }) => {
-                    let left_props = unsafe { &mut *lprops.get()};
-                    let right_props = unsafe { &mut *rprops.get()};
+            use DynamicNode::*;
 
-                    // Ensure these two props are of the same component type
-                    match left_props.as_ptr() == right_props.as_ptr()  {
-                        true => {
-                            //
-
-                            if *is_static {
-                                let props_are_same = unsafe { left_props.memoize(right_props)  };
-
-                                if props_are_same{
-                                    //
-                                } else {
-                                    //
-                                }
-                            } else {
+            match (left_node, right_node) {
+                (Component(left), Component(right)) => self.diff_vcomponent(muts, left, right),
+                (Text(left), Text(right)) => self.diff_vtext(muts, left, right),
+                (Fragment(left), Fragment(right)) => self.diff_vfragment(muts, left, right),
 
-                            }
+                (Placeholder(_), Placeholder(_)) => todo!(),
 
-                        },
-                        false => todo!(),
-                    }
-                    //
-                },
+                // Make sure to drop all the fragment children properly
+                (DynamicNode::Fragment { .. }, right) => todo!(),
 
+                (Text(left), right) => {
+                    todo!()
+                    // let m = self.create_dynamic_node(muts, right_template, right, idx);
+                    // muts.push(Mutation::ReplaceWith { id: lid.get(), m });
+                }
+                (Placeholder(_), _) => todo!(),
                 // Make sure to drop the component properly
-                (DynamicNode::Component { .. }, right) => {
+                (Component { .. }, right) => {
                     // remove all the component roots except for the first
                     // replace the first with the new node
                     let m = self.create_dynamic_node(muts, right_template, right, idx);
                     todo!()
-                },
-
-                (DynamicNode::Text { id: lid, value: lvalue, .. }, DynamicNode::Text { id: rid, value: rvalue, .. }) => {
-                    rid.set(lid.get());
-                    if lvalue != rvalue {
-                        muts.push(Mutation::SetText {
-                            id: lid.get(),
-                            value: rvalue,
-                        });
+                }
+            };
+        }
+    }
+
+    fn diff_vcomponent(
+        &mut self,
+        muts: &mut Mutations<'b>,
+        left: &'b VComponent<'b>,
+        right: &'b VComponent<'b>,
+    ) {
+        let left_props = unsafe { &mut *left.props.get() };
+        let right_props = unsafe { &mut *right.props.get() };
+
+        // Ensure these two props are of the same component type
+        match left_props.as_ptr() == right_props.as_ptr() {
+            true => {
+                //
+                if left.static_props {
+                    let props_are_same = unsafe { left_props.memoize(right_props) };
+
+                    if props_are_same {
+                        //
+                    } else {
+                        //
                     }
-                },
+                } else {
+                }
+            }
+            false => todo!(),
+        }
+    }
 
-                (DynamicNode::Text { id: lid, .. }, right) => {
-                    let m = self.create_dynamic_node(muts, right_template, right, idx);
-                    muts.push(Mutation::ReplaceWith { id: lid.get(), m });
+    /// Lightly diff the two templates, checking only their roots.
+    ///
+    /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
+    /// behavior where the component state is preserved when the component is re-rendered.
+    ///
+    /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
+    ///
+    /// We then pass the new template through "create" which should be smart enough to skip roots.
+    ///
+    /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
+    /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
+    ///
+    /// This is mostly implemented to help solve the issue where the same component is rendered under two different
+    /// conditions:
+    ///
+    /// ```rust
+    /// if enabled {
+    ///     rsx!{ Component { enabled_sign: "abc" } }
+    /// } else {
+    ///     rsx!{ Component { enabled_sign: "xyz" } }
+    /// }
+    /// ```
+    ///
+    /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
+    /// then you should be passing in separate props instead.
+    ///
+    /// ```
+    ///
+    /// let props = if enabled {
+    ///     ComponentProps { enabled_sign: "abc" }
+    /// } else {
+    ///     ComponentProps { enabled_sign: "xyz" }
+    /// };
+    ///
+    /// rsx! {
+    ///     Component { ..props }
+    /// }
+    /// ```
+    fn light_diff_tempaltes(
+        &mut self,
+        muts: &mut Mutations<'b>,
+        left: &'b VNode<'b>,
+        right: &'b VNode<'b>,
+    ) -> bool {
+        if left.template.roots.len() != right.template.roots.len() {
+            return false;
+        }
+
+        let mut components = vec![];
+
+        // run through the components, ensuring they're the same
+        for (l, r) in left.template.roots.iter().zip(right.template.roots.iter()) {
+            match (l, r) {
+                (TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => {
+                    components.push(match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
+                        (DynamicNode::Component(l), DynamicNode::Component(r)) => {
+                            let l_props = unsafe { &*l.props.get() };
+                            let r_props = unsafe { &*r.props.get() };
+
+                            (l, r)
+                        }
+                        _ => return false,
+                    })
                 }
+                _ => return false,
+            }
+        }
 
-                (DynamicNode::Placeholder(_), DynamicNode::Placeholder(_)) => todo!(),
-                (DynamicNode::Placeholder(_), _) => todo!(),
-
-
-                (DynamicNode::Fragment { nodes: lnodes, ..}, DynamicNode::Fragment { nodes: rnodes, ..}) => {
-
-
-                    // match (old, new) {
-                    //     ([], []) => rp.set(lp.get()),
-                    //     ([], _) => {
-                    //         //
-                    //         todo!()
-                    //     },
-                    //     (_, []) => {
-                    //         todo!()
-                    //     },
-                    //     _ => {
-                    //         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(muts, old, new);
-                    //         } else {
-                    //             self.diff_non_keyed_children(muts, old, new);
-                    //         }
-                    //     }
-                    // }
-                },
+        // run through the components, diffing them
+        for (l, r) in components {
+            self.diff_vcomponent(muts, l, r);
+        }
 
-                // Make sure to drop all the fragment children properly
-                (DynamicNode::Fragment { .. }, right) => todo!(),
-            };
+        true
+    }
+
+    fn diff_vtext(&mut self, muts: &mut Mutations<'b>, left: &'b VText<'b>, right: &'b VText<'b>) {
+        right.id.set(left.id.get());
+        if left.value != right.value {
+            muts.push(Mutation::SetText {
+                id: left.id.get(),
+                value: right.value,
+            });
         }
     }
 
+    fn diff_vfragment(
+        &mut self,
+        muts: &mut Mutations<'b>,
+        left: &'b VFragment<'b>,
+        right: &'b VFragment<'b>,
+    ) {
+        // match (left.nodes, right.nodes) {
+        //     ([], []) => rp.set(lp.get()),
+        //     ([], _) => {
+        //         //
+        //         todo!()
+        //     }
+        //     (_, []) => {
+        //         todo!()
+        //     }
+        //     _ => {
+        //         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(muts, old, new);
+        //         } else {
+        //             self.diff_non_keyed_children(muts, old, new);
+        //         }
+        //     }
+        // }
+
+        todo!()
+    }
+
     // Diff children that are not keyed.
     //
     // The parent must be on the top of the change list stack when entering this
@@ -614,62 +693,3 @@ impl<'b> VirtualDom {
         //
     }
 }
-
-// /// Lightly diff the two templates and apply their edits to the dom
-// fn light_diff_template_roots(
-//     &'a mut self,
-//     mutations: &mut Vec<Mutation<'a>>,
-//     left: &VNode,
-//     right: &VNode,
-// ) {
-//     match right.template.roots.len().cmp(&left.template.roots.len()) {
-//         std::cmp::Ordering::Less => {
-//             // remove the old nodes at the end
-//         }
-//         std::cmp::Ordering::Greater => {
-//             // add the extra nodes.
-//         }
-//         std::cmp::Ordering::Equal => {}
-//     }
-
-//     for (left_node, right_node) in left.template.roots.iter().zip(right.template.roots.iter()) {
-//         if let (TemplateNode::Dynamic(lidx), TemplateNode::Dynamic(ridx)) =
-//             (left_node, right_node)
-//         {
-//             let left_node = &left.dynamic_nodes[*lidx];
-//             let right_node = &right.dynamic_nodes[*ridx];
-
-//             // match (left_node, right_node) {
-//             //     (
-//             //         DynamicNode::Component {
-//             //             name,
-//             //             can_memoize,
-//             //             props,
-//             //         },
-//             //         DynamicNode::Component {
-//             //             name,
-//             //             can_memoize,
-//             //             props,
-//             //         },
-//             //     ) => todo!(),
-//             //     (
-//             //         DynamicNode::Component {
-//             //             name,
-//             //             can_memoize,
-//             //             props,
-//             //         },
-//             //         DynamicNode::Fragment { children },
-//             //     ) => todo!(),
-//             //     (
-//             //         DynamicNode::Fragment { children },
-//             //         DynamicNode::Component {
-//             //             name,
-//             //             can_memoize,
-//             //             props,
-//             //         },
-//             //     ) => todo!(),
-//             //     _ => {}
-//             // }
-//         }
-//     }
-// }

+ 9 - 9
packages/core/src/factory.rs

@@ -10,7 +10,7 @@ use std::future::Future;
 use crate::{
     any_props::{AnyProps, VComponentProps},
     arena::ElementId,
-    innerlude::{DynamicNode, EventHandler},
+    innerlude::{DynamicNode, EventHandler, VComponent, VFragment, VText},
     Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode,
 };
 
@@ -18,11 +18,11 @@ impl ScopeState {
     /// Create some text that's allocated along with the other vnodes
     pub fn text<'a>(&'a self, args: Arguments) -> DynamicNode<'a> {
         let (text, _) = self.raw_text(args);
-        DynamicNode::Text {
+        DynamicNode::Text(VText {
             id: Cell::new(ElementId(0)),
             value: text,
             inner: false,
-        }
+        })
     }
 
     pub fn raw_text_inline<'a>(&'a self, args: Arguments) -> &'a str {
@@ -87,13 +87,13 @@ impl ScopeState {
         //     self.scope.items.borrow_mut().borrowed_props.push(vcomp);
         // }
 
-        DynamicNode::Component {
+        DynamicNode::Component(VComponent {
             name: fn_name,
             static_props: P::IS_STATIC,
             props: Cell::new(detached_dyn),
             placeholder: Cell::new(None),
             scope: Cell::new(None),
-        }
+        })
     }
 
     /// Create a new [`EventHandler`] from an [`FnMut`]
@@ -176,10 +176,10 @@ impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for &Option<T> {
 
 impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
     fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
-        DynamicNode::Fragment {
+        DynamicNode::Fragment(VFragment {
             nodes: cx.bump().alloc([self.call(cx)]),
             inner: false,
-        }
+        })
     }
 }
 
@@ -248,10 +248,10 @@ where
 
         match children.len() {
             0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
-            _ => DynamicNode::Fragment {
+            _ => DynamicNode::Fragment(VFragment {
                 inner: false,
                 nodes: children,
-            },
+            }),
         }
     }
 }

+ 104 - 0
packages/core/src/fragment.rs

@@ -0,0 +1,104 @@
+use crate::innerlude::*;
+
+/// Create inline fragments using Component syntax.
+///
+/// ## Details
+///
+/// Fragments capture a series of children without rendering extra nodes.
+///
+/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
+/// a key is needed to identify each item.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// rsx!{
+///     Fragment { key: "abc" }
+/// }
+/// ```
+///
+/// ## Usage
+///
+/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
+///
+/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+///
+/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+#[allow(non_upper_case_globals, non_snake_case)]
+pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
+    let children = cx.props.0.as_ref()?;
+    Some(VNode {
+        node_id: children.node_id.clone(),
+        key: children.key.clone(),
+        parent: children.parent.clone(),
+        template: children.template,
+        root_ids: children.root_ids,
+        dynamic_nodes: children.dynamic_nodes,
+        dynamic_attrs: children.dynamic_attrs,
+    })
+}
+
+pub struct FragmentProps<'a>(Element<'a>);
+pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
+impl<'a> FragmentBuilder<'a, false> {
+    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
+        FragmentBuilder(children)
+    }
+}
+impl<'a, const A: bool> FragmentBuilder<'a, A> {
+    pub fn build(self) -> FragmentProps<'a> {
+        FragmentProps(self.0)
+    }
+}
+
+/// Access the children elements passed into the component
+///
+/// This enables patterns where a component is passed children from its parent.
+///
+/// ## Details
+///
+/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
+/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
+/// on the props that takes Context.
+///
+/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
+/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
+/// props are valid for the static lifetime.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+///     cx.render(rsx!{
+///         CustomCard {
+///             h1 {}
+///             p {}
+///         }
+///     })
+/// }
+///
+/// #[derive(PartialEq, Props)]
+/// struct CardProps {
+///     children: Element
+/// }
+///
+/// fn CustomCard(cx: Scope<CardProps>) -> Element {
+///     cx.render(rsx!{
+///         div {
+///             h1 {"Title card"}
+///             {cx.props.children}
+///         }
+///     })
+/// }
+/// ```
+impl<'a> Properties for FragmentProps<'a> {
+    type Builder = FragmentBuilder<'a, false>;
+    const IS_STATIC: bool = false;
+    fn builder() -> Self::Builder {
+        FragmentBuilder(None)
+    }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        false
+    }
+}

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

@@ -17,18 +17,18 @@ impl<'b> VirtualDom {
         template: &'b VNode<'b>,
         gen_roots: bool,
     ) {
-        for node in template.dynamic_nodes.iter() {
-            match node {
-                DynamicNode::Text { id, .. } => {}
+        // for node in template.dynamic_nodes.iter() {
+        //     match node {
+        //         DynamicNode::Text { id, .. } => {}
 
-                DynamicNode::Component { .. } => {
-                    todo!()
-                }
+        //         DynamicNode::Component { .. } => {
+        //             todo!()
+        //         }
 
-                DynamicNode::Fragment { inner, nodes } => {}
-                DynamicNode::Placeholder(_) => todo!(),
-                _ => todo!(),
-            }
-        }
+        //         DynamicNode::Fragment { inner, nodes } => {}
+        //         DynamicNode::Placeholder(_) => todo!(),
+        //         _ => todo!(),
+        //     }
+        // }
     }
 }

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

@@ -5,6 +5,7 @@ mod create;
 mod diff;
 mod events;
 mod factory;
+mod fragment;
 mod garbage;
 mod lazynodes;
 mod mutations;
@@ -18,6 +19,7 @@ mod virtual_dom;
 pub(crate) mod innerlude {
     pub use crate::arena::*;
     pub use crate::events::*;
+    pub use crate::fragment::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;

+ 53 - 44
packages/core/src/nodes.rs

@@ -3,28 +3,38 @@ use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
     hash::Hasher,
-    rc::Rc,
 };
 
 pub type TemplateId = &'static str;
 
 /// A reference to a template along with any context needed to hydrate it
+///
+/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
+/// static parts of the template.
 #[derive(Debug)]
 pub struct VNode<'a> {
     // The ID assigned for the root of this template
     pub node_id: Cell<ElementId>,
 
+    /// The key given to the root of this template.
+    ///
+    /// In fragments, this is the key of the first child. In other cases, it is the key of the root.
     pub key: Option<&'a str>,
 
-    // When rendered, this template will be linked to its parent manually
+    /// When rendered, this template will be linked to its parent manually
     pub parent: Option<ElementId>,
 
+    /// The static nodes and static descriptor of the template
     pub template: Template<'static>,
 
+    /// The IDs for the roots of this template - to be used when moving the template around and removing it from
+    /// the actual Dom
     pub root_ids: &'a [Cell<ElementId>],
 
+    /// The dynamic parts of the template
     pub dynamic_nodes: &'a [DynamicNode<'a>],
 
+    /// The dynamic parts of the template
     pub dynamic_attrs: &'a [Attribute<'a>],
 }
 
@@ -109,25 +119,40 @@ pub enum TemplateNode<'a> {
 
 #[derive(Debug)]
 pub enum DynamicNode<'a> {
-    Component {
-        name: &'static str,
-        static_props: bool,
-        props: Cell<*mut dyn AnyProps<'a>>,
-        placeholder: Cell<Option<ElementId>>,
-        scope: Cell<Option<ScopeId>>,
-    },
-    Text {
-        id: Cell<ElementId>,
-        value: &'a str,
-        inner: bool,
-    },
-    Fragment {
-        nodes: &'a [VNode<'a>],
-        inner: bool,
-    },
+    Component(VComponent<'a>),
+    Text(VText<'a>),
+    Fragment(VFragment<'a>),
     Placeholder(Cell<ElementId>),
 }
 
+impl<'a> DynamicNode<'a> {
+    pub fn is_component(&self) -> bool {
+        matches!(self, DynamicNode::Component(_))
+    }
+}
+
+#[derive(Debug)]
+pub struct VComponent<'a> {
+    pub name: &'static str,
+    pub static_props: bool,
+    pub props: Cell<*mut dyn AnyProps<'a>>,
+    pub placeholder: Cell<Option<ElementId>>,
+    pub scope: Cell<Option<ScopeId>>,
+}
+
+#[derive(Debug)]
+pub struct VText<'a> {
+    pub id: Cell<ElementId>,
+    pub value: &'a str,
+    pub inner: bool,
+}
+
+#[derive(Debug)]
+pub struct VFragment<'a> {
+    pub nodes: &'a [VNode<'a>],
+    pub inner: bool,
+}
+
 #[derive(Debug)]
 pub enum TemplateAttribute<'a> {
     Static {
@@ -163,32 +188,16 @@ impl<'a> AttributeValue<'a> {
         cx: &'a ScopeState,
         mut f: impl FnMut(UiEvent<T>) + 'a,
     ) -> AttributeValue<'a> {
-        println!("creating listener with type od id {:?}", TypeId::of::<T>());
-
-        let f = cx.bump().alloc(move |a: UiEvent<dyn Any>| {
-            println!(
-                "incoming: {:?}, desired {:?}",
-                a.data.type_id(),
-                std::any::TypeId::of::<T>()
-            );
-
-            let data = a.data.downcast::<T>().unwrap();
-
-            // println!("casting to type {:?}", TypeId::of::<T>());
-
-            f(UiEvent {
-                bubbles: a.bubbles,
-                data,
-            })
-
-            // println!("listener called");
-            // if let Some(event) = a.downcast::<T>() {
-            //     println!("downcast success");
-            //     f(event)
-            // }
-        }) as &mut dyn FnMut(UiEvent<dyn Any>);
-
-        AttributeValue::Listener(RefCell::new(f))
+        AttributeValue::Listener(RefCell::new(cx.bump().alloc(
+            move |event: UiEvent<dyn Any>| {
+                if let Ok(data) = event.data.downcast::<T>() {
+                    f(UiEvent {
+                        bubbles: event.bubbles,
+                        data,
+                    })
+                }
+            },
+        )))
     }
 }
 

+ 0 - 96
packages/core/src/properties.rs

@@ -1,101 +1,5 @@
 use crate::innerlude::*;
 
-pub struct FragmentProps<'a>(Element<'a>);
-pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
-impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
-        FragmentBuilder(children)
-    }
-}
-impl<'a, const A: bool> FragmentBuilder<'a, A> {
-    pub fn build(self) -> FragmentProps<'a> {
-        FragmentProps(self.0)
-    }
-}
-
-/// Access the children elements passed into the component
-///
-/// This enables patterns where a component is passed children from its parent.
-///
-/// ## Details
-///
-/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
-/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
-/// on the props that takes Context.
-///
-/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
-/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
-/// props are valid for the static lifetime.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-///     cx.render(rsx!{
-///         CustomCard {
-///             h1 {}
-///             p {}
-///         }
-///     })
-/// }
-///
-/// #[derive(PartialEq, Props)]
-/// struct CardProps {
-///     children: Element
-/// }
-///
-/// fn CustomCard(cx: Scope<CardProps>) -> Element {
-///     cx.render(rsx!{
-///         div {
-///             h1 {"Title card"}
-///             {cx.props.children}
-///         }
-///     })
-/// }
-/// ```
-impl<'a> Properties for FragmentProps<'a> {
-    type Builder = FragmentBuilder<'a, false>;
-    const IS_STATIC: bool = false;
-    fn builder() -> Self::Builder {
-        FragmentBuilder(None)
-    }
-    unsafe fn memoize(&self, _other: &Self) -> bool {
-        false
-    }
-}
-
-/// Create inline fragments using Component syntax.
-///
-/// ## Details
-///
-/// Fragments capture a series of children without rendering extra nodes.
-///
-/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
-/// a key is needed to identify each item.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// rsx!{
-///     Fragment { key: "abc" }
-/// }
-/// ```
-///
-/// ## Usage
-///
-/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
-/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
-///
-/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
-///
-/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
-#[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
-    // let i = cx.props.0.as_ref().map(|f| f.decouple());
-    // cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
-    todo!("Fragment")
-}
-
 /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
 /// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
 /// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.

+ 6 - 23
packages/core/src/scope_arena.rs

@@ -1,6 +1,5 @@
 use crate::{
     any_props::AnyProps,
-    arena::ElementId,
     bump_frame::BumpFrame,
     factory::RenderReturn,
     innerlude::{SuspenseId, SuspenseLeaf},
@@ -19,18 +18,16 @@ use std::{
 impl VirtualDom {
     pub(super) fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
         let parent = self.acquire_current_scope_raw();
-        let container = self.acquire_current_container();
         let entry = self.scopes.vacant_entry();
         let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
         let id = ScopeId(entry.key());
 
         entry.insert(ScopeState {
             parent,
-            container,
             id,
             height,
             props,
-            placeholder: None.into(),
+            placeholder: Default::default(),
             node_arena_1: BumpFrame::new(50),
             node_arena_2: BumpFrame::new(50),
             spawned_tasks: Default::default(),
@@ -43,13 +40,6 @@ impl VirtualDom {
         })
     }
 
-    fn acquire_current_container(&self) -> ElementId {
-        self.element_stack
-            .last()
-            .copied()
-            .expect("Always have a container")
-    }
-
     fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
         self.scope_stack
             .last()
@@ -65,15 +55,12 @@ impl VirtualDom {
     }
 
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
-        println!("run_scope: {:?}", scope_id);
-
         let mut new_nodes = unsafe {
             let scope = &mut self.scopes[scope_id.0];
-            println!("run_scope: scope: {:?}", scope.render_cnt.get());
             scope.hook_idx.set(0);
 
             // safety: due to how we traverse the tree, we know that the scope is not currently aliased
-            let props: &mut dyn AnyProps = mem::transmute(&mut *scope.props);
+            let props: &dyn AnyProps = mem::transmute(&*scope.props);
             props.render(scope).extend_lifetime()
         };
 
@@ -126,16 +113,12 @@ impl VirtualDom {
             }
         };
 
-        /*
-        todo: use proper mutability here
-
-        right now we're aliasing the scope, which is not allowed
-        */
+        let scope = &self.scopes[scope_id.0];
 
-        let scope = &mut self.scopes[scope_id.0];
-        let frame = scope.current_frame();
+        // We write on top of the previous frame and then make it the current by pushing the generation forward
+        let frame = scope.previous_frame();
 
-        // set the head of the bump frame
+        // set the new head of the bump frame
         let alloced = frame.bump.alloc(new_nodes);
         frame.node.set(alloced);
 

+ 6 - 2
packages/core/src/scopes.rs

@@ -27,6 +27,11 @@ use std::{
 /// component render.
 pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
 
+// This ScopedType exists because we want to limit the amount of monomorphization that occurs when making inner
+// state type generic over props. When the state is generic, it causes every method to be monomorphized for every
+// instance of Scope<T> in the codebase.
+//
+//
 /// A wrapper around a component's [`ScopeState`] and properties. The [`ScopeState`] provides the majority of methods
 /// for the VirtualDom and component state.
 pub struct Scoped<'a, T = ()> {
@@ -64,7 +69,6 @@ pub struct ScopeState {
     pub(crate) node_arena_2: BumpFrame,
 
     pub(crate) parent: Option<*mut ScopeState>,
-    pub(crate) container: ElementId,
     pub(crate) id: ScopeId,
 
     pub(crate) height: u32,
@@ -78,7 +82,7 @@ pub struct ScopeState {
     pub(crate) tasks: Rc<Scheduler>,
     pub(crate) spawned_tasks: HashSet<TaskId>,
 
-    pub(crate) props: *mut dyn AnyProps<'static>,
+    pub(crate) props: *const dyn AnyProps<'static>,
     pub(crate) placeholder: Cell<Option<ElementId>>,
 }
 

+ 0 - 2
packages/core/src/virtual_dom.rs

@@ -151,7 +151,6 @@ use std::{
 pub struct VirtualDom {
     pub(crate) templates: HashMap<TemplateId, Template<'static>>,
     pub(crate) scopes: Slab<ScopeState>,
-    pub(crate) element_stack: Vec<ElementId>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) scheduler: Rc<Scheduler>,
 
@@ -236,7 +235,6 @@ impl VirtualDom {
             scopes: Slab::default(),
             elements: Default::default(),
             scope_stack: Vec::new(),
-            element_stack: vec![ElementId(0)],
             dirty_scopes: BTreeSet::new(),
             collected_leaves: Vec::new(),
             finished_fibers: Vec::new(),

+ 1 - 1
packages/fermi/src/hooks/atom_root.rs

@@ -6,6 +6,6 @@ use std::rc::Rc;
 pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
     cx.use_hook(|| match cx.consume_context::<Rc<AtomRoot>>() {
         Some(root) => root,
-        None => cx.provide_root_context(Rc::new(AtomRoot::new(cx.schedule_update_any()))),
+        None => panic!("No atom root found in context. Did you forget place an AtomRoot component at the top of your app?"),
     })
 }