From 493591400fbcdfe4fe2e4031fc2ee10031815c08 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 19 Nov 2022 17:07:29 -0800 Subject: [PATCH] wip: pre any props --- packages/core/src/any_props.rs | 5 +- packages/core/src/bump_frame.rs | 6 +- packages/core/src/create.rs | 33 ++- packages/core/src/diff.rs | 356 ++++++++++++++------------ packages/core/src/factory.rs | 18 +- packages/core/src/fragment.rs | 104 ++++++++ packages/core/src/garbage.rs | 22 +- packages/core/src/lib.rs | 2 + packages/core/src/nodes.rs | 97 +++---- packages/core/src/properties.rs | 96 ------- packages/core/src/scope_arena.rs | 29 +-- packages/core/src/scopes.rs | 8 +- packages/core/src/virtual_dom.rs | 2 - packages/fermi/src/hooks/atom_root.rs | 2 +- 14 files changed, 399 insertions(+), 381 deletions(-) create mode 100644 packages/core/src/fragment.rs diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index 05e2de912..9b143a034 100644 --- a/packages/core/src/any_props.rs +++ b/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, diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index f80cda4ae..8b2650c22 100644 --- a/packages/core/src/bump_frame.rs +++ b/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()) } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index f5574522b..fbd52b26c 100644 --- a/packages/core/src/create.rs +++ b/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)) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index f12fd8bbf..84610ad09 100644 --- a/packages/core/src/diff.rs +++ b/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] + use DynamicNode::*; + 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()}; + (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), - // Ensure these two props are of the same component type - match left_props.as_ptr() == right_props.as_ptr() { - true => { - // + (Placeholder(_), Placeholder(_)) => todo!(), - if *is_static { - let props_are_same = unsafe { left_props.memoize(right_props) }; - - if props_are_same{ - // - } else { - // - } - } else { - - } - - }, - 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, - }); - } - }, - - (DynamicNode::Text { id: lid, .. }, right) => { - let m = self.create_dynamic_node(muts, right_template, right, idx); - muts.push(Mutation::ReplaceWith { id: lid.get(), m }); } - - (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); - // } - // } - // } - }, - - // Make sure to drop all the fragment children properly - (DynamicNode::Fragment { .. }, right) => todo!(), }; } } + 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!(), + } + } + + /// 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, + } + } + + // run through the components, diffing them + for (l, r) in components { + self.diff_vcomponent(muts, l, r); + } + + 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>, -// 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!(), -// // _ => {} -// // } -// } -// } -// } diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 717f73a3c..57b0d0f55 100644 --- a/packages/core/src/factory.rs +++ b/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 { 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, - }, + }), } } } diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs new file mode 100644 index 000000000..19f0b1190 --- /dev/null +++ b/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) -> 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 + } +} diff --git a/packages/core/src/garbage.rs b/packages/core/src/garbage.rs index 5f8c422f0..056b14481 100644 --- a/packages/core/src/garbage.rs +++ b/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!(), + // } + // } } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 30196d0d3..1a78ed22b 100644 --- a/packages/core/src/lib.rs +++ b/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::*; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 843fec939..07a388e9f 100644 --- a/packages/core/src/nodes.rs +++ b/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, + /// 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, + /// 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], + /// 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>, - scope: Cell>, - }, - Text { - id: Cell, - value: &'a str, - inner: bool, - }, - Fragment { - nodes: &'a [VNode<'a>], - inner: bool, - }, + Component(VComponent<'a>), + Text(VText<'a>), + Fragment(VFragment<'a>), Placeholder(Cell), } +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>, + pub scope: Cell>, +} + +#[derive(Debug)] +pub struct VText<'a> { + pub id: Cell, + 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) + 'a, ) -> AttributeValue<'a> { - println!("creating listener with type od id {:?}", TypeId::of::()); - - let f = cx.bump().alloc(move |a: UiEvent| { - println!( - "incoming: {:?}, desired {:?}", - a.data.type_id(), - std::any::TypeId::of::() - ); - - let data = a.data.downcast::().unwrap(); - - // println!("casting to type {:?}", TypeId::of::()); - - f(UiEvent { - bubbles: a.bubbles, - data, - }) - - // println!("listener called"); - // if let Some(event) = a.downcast::() { - // println!("downcast success"); - // f(event) - // } - }) as &mut dyn FnMut(UiEvent); - - AttributeValue::Listener(RefCell::new(f)) + AttributeValue::Listener(RefCell::new(cx.bump().alloc( + move |event: UiEvent| { + if let Ok(data) = event.data.downcast::() { + f(UiEvent { + bubbles: event.bubbles, + data, + }) + } + }, + ))) } } diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 43afff79c..f8be8a0dd 100644 --- a/packages/core/src/properties.rs +++ b/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) -> 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. diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 0aa535e10..42823760a 100644 --- a/packages/core/src/scope_arena.rs +++ b/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 + let scope = &self.scopes[scope_id.0]; - right now we're aliasing the scope, which is not allowed - */ + // We write on top of the previous frame and then make it the current by pushing the generation forward + let frame = scope.previous_frame(); - let scope = &mut self.scopes[scope_id.0]; - let frame = scope.current_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); diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 3291db9e0..981780c3a 100644 --- a/packages/core/src/scopes.rs +++ b/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 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, pub(crate) spawned_tasks: HashSet, - pub(crate) props: *mut dyn AnyProps<'static>, + pub(crate) props: *const dyn AnyProps<'static>, pub(crate) placeholder: Cell>, } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index fa1036e2d..34f541973 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -151,7 +151,6 @@ use std::{ pub struct VirtualDom { pub(crate) templates: HashMap>, pub(crate) scopes: Slab, - pub(crate) element_stack: Vec, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Rc, @@ -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(), diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs index f9eb07bc9..267b71161 100644 --- a/packages/fermi/src/hooks/atom_root.rs +++ b/packages/fermi/src/hooks/atom_root.rs @@ -6,6 +6,6 @@ use std::rc::Rc; pub fn use_atom_root(cx: &ScopeState) -> &Rc { cx.use_hook(|| match cx.consume_context::>() { 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?"), }) }