From 574d7fdb9e72a5254130589f13f9efd31e3d2870 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 9 Nov 2021 14:36:26 -0500 Subject: [PATCH] wip: remove scopechildren in favor of elements directly --- packages/core/src/component.rs | 98 +++++++++++------ packages/core/src/diff.rs | 2 +- packages/core/src/lib.rs | 9 +- packages/core/src/nodes.rs | 188 +++++++++++--------------------- packages/core/src/scope.rs | 29 ++--- packages/core/src/scopearena.rs | 19 +++- 6 files changed, 153 insertions(+), 192 deletions(-) diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 8a10c2083..f01e11bf7 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -5,40 +5,67 @@ //! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder //! that ensures compile-time required and optional fields on cx. -use crate::innerlude::{Context, Element, LazyNodes, ScopeChildren}; +use crate::innerlude::{Context, Element, LazyNodes, NodeLink}; -pub struct FragmentProps<'a> { - children: ScopeChildren<'a>, +pub struct FragmentProps(Element); +pub struct FragmentBuilder(Element); +impl FragmentBuilder { + pub fn children(self, children: Option) -> FragmentBuilder { + FragmentBuilder(children) + } } - -pub struct FragmentBuilder<'a, const BUILT: bool> { - children: Option>, -} -impl<'a> FragmentBuilder<'a, false> { - pub fn children(self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> { - FragmentBuilder { - children: Some(children), - } +impl FragmentBuilder { + pub fn build(self) -> FragmentProps { + FragmentProps(self.0) } } -impl<'a, const A: bool> FragmentBuilder<'a, A> { - pub fn build(self) -> FragmentProps<'a> { - FragmentProps { - children: self.children.unwrap_or_default(), - } - } -} - -impl<'a> Properties for FragmentProps<'a> { - type Builder = FragmentBuilder<'a, false>; - +/// 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 +/// fn App(cx: Context, props: &()) -> Element { +/// cx.render(rsx!{ +/// CustomCard { +/// h1 {} +/// p {} +/// } +/// }) +/// } +/// +/// #[derive(PartialEq, Props)] +/// struct CardProps { +/// children: Element +/// } +/// +/// fn CustomCard(cx: Context, props: &CardProps) -> Element { +/// cx.render(rsx!{ +/// div { +/// h1 {"Title card"} +/// {props.children} +/// } +/// }) +/// } +/// ``` +impl Properties for FragmentProps { + type Builder = FragmentBuilder; const IS_STATIC: bool = false; - fn builder() -> Self::Builder { - FragmentBuilder { children: None } + FragmentBuilder(None) } - unsafe fn memoize(&self, _other: &Self) -> bool { false } @@ -46,9 +73,14 @@ impl<'a> Properties for FragmentProps<'a> { /// Create inline fragments using Component syntax. /// +/// ## Details +/// /// Fragments capture a series of children without rendering extra nodes. /// -/// # Example +/// 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 /// rsx!{ @@ -56,20 +88,17 @@ impl<'a> Properties for FragmentProps<'a> { /// } /// ``` /// -/// # Details +/// ## Usage /// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. -/// Try to avoid nesting fragments if you can. There is no protection against infinitely nested fragments. +/// 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: Context<'a>, props: &'a FragmentProps<'a>) -> Element { - cx.render(Some(LazyNodes::new(|f| { - f.fragment_from_iter(&props.children) - }))) +pub fn Fragment(cx: Context, props: &FragmentProps) -> Element { + cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(&props.0)))) } /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus @@ -131,7 +160,6 @@ impl Properties for () { // that the macros use to anonymously complete prop construction. pub struct EmptyBuilder; impl EmptyBuilder { - #[inline] pub fn build(self) {} } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 1771c1972..7a3bd4b48 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -442,7 +442,7 @@ impl<'bump> DiffState<'bump> { let height = parent_scope.height + 1; let subtree = parent_scope.subtree.get(); - let parent_scope = unsafe { self.scopes.get_scope_mut(&parent_idx) }; + let parent_scope = unsafe { self.scopes.get_scope_raw(&parent_idx) }; let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) }; let fc_ptr = vcomponent.user_fc; diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 6d540bdfd..a4ea08399 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -37,17 +37,14 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode, - LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, - ScopeChildren, ScopeId, UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, - FC, + LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, + UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, FC, }; pub mod prelude { pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::innerlude::Context; - pub use crate::innerlude::{ - DioxusElement, Element, LazyNodes, NodeFactory, Scope, ScopeChildren, FC, - }; + pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, Scope, FC}; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index bb6a4e9e8..77bf03179 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -14,26 +14,11 @@ use std::{ fmt::{Arguments, Debug, Formatter}, }; -/// A cached node is a "pointer" to a "rendered" node in a particular scope -/// -/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it -/// -/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key -/// is essentially a unique key to guarantee safe usage of the Node. -/// -/// todo: remove "clone" -#[derive(Clone, Debug)] -pub struct NodeLink { - pub(crate) link_idx: usize, - pub(crate) gen_id: u32, - pub(crate) scope_id: ScopeId, -} - /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. /// /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: +/// /// - the [`rsx`] macro -/// - the [`html`] macro /// - the [`NodeFactory`] API pub enum VNode<'src> { /// Text VNodes simply bump-allocated (or static) string slices @@ -41,7 +26,8 @@ pub enum VNode<'src> { /// # Example /// /// ``` - /// let node = cx.render(rsx!{ "hello" }).unwrap(); + /// let mut vdom = VirtualDom::new(); + /// let node = vdom.render_vnode(rsx!( "hello" )); /// /// if let VNode::Text(vtext) = node { /// assert_eq!(vtext.text, "hello"); @@ -56,7 +42,9 @@ pub enum VNode<'src> { /// # Example /// /// ```rust - /// let node = cx.render(rsx!{ + /// let mut vdom = VirtualDom::new(); + /// + /// let node = vdom.render_vnode(rsx!{ /// div { /// key: "a", /// onclick: |e| log::info!("clicked"), @@ -64,7 +52,8 @@ pub enum VNode<'src> { /// style: { background_color: "red" } /// "hello" /// } - /// }).unwrap(); + /// }); + /// /// if let VNode::Element(velement) = node { /// assert_eq!(velement.tag_name, "div"); /// assert_eq!(velement.namespace, None); @@ -93,13 +82,13 @@ pub enum VNode<'src> { /// # Example /// /// ```rust - /// fn Example(cx: Context<()>) -> DomTree { + /// fn Example(cx: Context, props: &()) -> Element { /// todo!() /// } /// - /// let node = cx.render(rsx!{ - /// Example {} - /// }).unwrap(); + /// let mut vdom = VirtualDom::new(); + /// + /// let node = vdom.render_vnode(rsx!( Example {} )); /// /// if let VNode::Component(vcomp) = node { /// assert_eq!(vcomp.user_fc, Example as *const ()); @@ -109,13 +98,11 @@ pub enum VNode<'src> { /// Suspended VNodes represent chunks of the UI tree that are not yet ready to be displayed. /// - /// These nodes currently can only be constructed via the [`use_suspense`] hook. - /// /// # Example /// /// ```rust - /// rsx!{ - /// } + /// + /// /// ``` Suspended(&'src VSuspended<'src>), @@ -126,7 +113,10 @@ pub enum VNode<'src> { /// # Example /// /// ```rust - /// let node = cx.render(rsx! ( Fragment {} )).unwrap(); + /// let mut vdom = VirtualDom::new(); + /// + /// let node = vdom.render_vnode(rsx!( Fragment {} )); + /// /// if let VNode::Fragment(frag) = node { /// let root = &frag.children[0]; /// assert_eq!(root, VNode::Anchor); @@ -134,11 +124,20 @@ pub enum VNode<'src> { /// ``` Anchor(&'src VAnchor), - /// A type of node that links this node to another scope or render cycle + /// A VNode that is actually a pointer to some nodes rather than the nodes directly. Useful when rendering portals + /// or eliding lifetimes on VNodes through runtime checks. /// - /// Is essentially a "pointer" to a "rendered" node in a particular scope + /// Linked VNodes can only be made through the [`Context::render`] method /// - /// Used in portals + /// Typically, linked nodes are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into + /// an `rsx!` call. + /// + /// # Example + /// ```rust + /// let mut vdom = VirtualDom::new(); + /// + /// let node: NodeLink = vdom.render_vnode(rsx!( "hello" )); + /// ``` Linked(NodeLink), } @@ -273,7 +272,6 @@ pub struct VText<'src> { /// A list of VNodes with no single root. pub struct VFragment<'src> { pub key: Option<&'src str>, - pub children: &'src [VNode<'src>], } @@ -408,6 +406,24 @@ pub struct VSuspended<'a> { pub callback: RefCell Element + 'a>>>, } +/// A cached node is a "pointer" to a "rendered" node in a particular scope +/// +/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it +/// +/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key +/// is essentially a unique key to guarantee safe usage of the Node. +/// +/// Linked VNodes can only be made through the [`Context::render`] method +/// +/// Typically, NodeLinks are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into +/// an `rsx!` call. +#[derive(Debug)] +pub struct NodeLink { + pub(crate) link_idx: usize, + pub(crate) gen_id: u32, + pub(crate) scope_id: ScopeId, +} + /// This struct provides an ergonomic API to quickly build VNodes. /// /// NodeFactory is used to build VNodes in the component's memory space. @@ -549,20 +565,6 @@ impl<'a> NodeFactory<'a> { where P: Properties + 'a, { - /* - our strategy: - - unsafe hackery - - lol - - - we don't want to hit the global allocator - - allocate into our bump arena - - if the props aren't static, then we convert them into a box which we pass off between renders - */ - - // let bump = self.bump(); - - // // later, we'll do a hard allocation - // let raw_ptr = bump.alloc(props); let bump = self.bump(); let props = bump.alloc(props); let bump_props = props as *mut P as *mut (); @@ -758,21 +760,6 @@ impl IntoVNode<'_> for Option<()> { } } -// Conveniently, we also support "None" -impl IntoVNode<'_> for Option { - fn into_vnode(self, cx: NodeFactory) -> VNode { - todo!() - // cx.fragment_from_iter(None as Option) - } -} -// Conveniently, we also support "None" -impl IntoVNode<'_> for NodeLink { - fn into_vnode(self, cx: NodeFactory) -> VNode { - todo!() - // cx.fragment_from_iter(None as Option) - } -} - impl<'a> IntoVNode<'a> for Option> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { self.unwrap_or_else(|| cx.fragment_from_iter(None as Option)) @@ -809,79 +796,28 @@ impl IntoVNode<'_> for Arguments<'_> { } } -/// 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 -/// const App: FC<()> = |cx, props|{ -/// cx.render(rsx!{ -/// CustomCard { -/// h1 {} -/// p {} -/// } -/// }) -/// } -/// -/// const CustomCard: FC<()> = |cx, props|{ -/// cx.render(rsx!{ -/// div { -/// h1 {"Title card"} -/// {props.children} -/// } -/// }) -/// } -/// ``` -/// -/// ## Notes: -/// -/// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime. -pub struct ScopeChildren<'a> { - root: Option>, -} - -impl Default for ScopeChildren<'_> { - fn default() -> Self { - Self { root: None } +impl IntoVNode<'_> for Option { + fn into_vnode(self, cx: NodeFactory) -> VNode { + todo!() } } -impl<'a> ScopeChildren<'a> { - pub fn new(root: VNode<'a>) -> Self { - Self { root: Some(root) } - } - pub fn new_option(root: Option>) -> Self { - Self { root } +impl IntoVNode<'_> for &Option { + fn into_vnode(self, cx: NodeFactory) -> VNode { + todo!() } } -impl IntoIterator for &ScopeChildren<'_> { - type Item = Self; - - type IntoIter = std::iter::Once; - - fn into_iter(self) -> Self::IntoIter { - std::iter::once(self) +// Conveniently, we also support "None" +impl IntoVNode<'_> for NodeLink { + fn into_vnode(self, cx: NodeFactory) -> VNode { + todo!() } } -impl<'a> IntoVNode<'a> for &ScopeChildren<'a> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - match &self.root { - Some(n) => n.decouple(), - None => cx.fragment_from_iter(None as Option), - } +// Conveniently, we also support "None" +impl IntoVNode<'_> for &NodeLink { + fn into_vnode(self, cx: NodeFactory) -> VNode { + todo!() } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 30ae15e7b..b2072dc84 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -315,10 +315,7 @@ impl Scope { /// rsx!(cx, div { "hello {state.0}" }) /// } /// ``` - pub fn provide_state(&self, value: T) - where - T: 'static, - { + pub fn provide_state(&self, value: T) { self.shared_contexts .borrow_mut() .insert(TypeId::of::(), Rc::new(value)) @@ -354,13 +351,20 @@ impl Scope { /// # Example /// /// ```rust - /// static App: FC<()> = |cx, props| { + /// fn App(cx: Context, props: &()) -> Element { /// todo!(); /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` pub fn create_subtree(&self) -> Option { - self.new_subtree() + if self.is_subtree_root.get() { + None + } else { + todo!() + // let cur = self.subtree().get(); + // self.shared.cur_subtree.set(cur + 1); + // Some(cur) + } } /// Get the subtree ID that this scope belongs to. @@ -371,7 +375,7 @@ impl Scope { /// # Example /// /// ```rust - /// static App: FC<()> = |cx, props| { + /// fn App(cx: Context, props: &()) -> Element { /// let id = cx.get_current_subtree(); /// rsx!(cx, div { "Subtree {id}"}) /// }; @@ -492,17 +496,6 @@ impl Scope { effect(); } } - - pub(crate) fn new_subtree(&self) -> Option { - todo!() - // if self.is_subtree_root.get() { - // None - // } else { - // let cur = self.shared.cur_subtree.get(); - // self.shared.cur_subtree.set(cur + 1); - // Some(cur) - // } - } } pub struct BumpFrame { diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index f873e14bb..911da1d0a 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -38,12 +38,17 @@ impl ScopeArena { } pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> { - unsafe { Some(&*self.scopes.borrow()[id.0]) } + unsafe { self.scopes.borrow().get(id.0).map(|f| &**f) } } // this is unsafe - pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<*mut Scope> { - Some(self.scopes.borrow()[id.0]) + pub unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> { + self.scopes.borrow().get(id.0).map(|f| *f) + } + // this is unsafe + + pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> { + self.scopes.borrow().get(id.0).map(|s| &mut **s) } pub fn new_with_key( @@ -182,9 +187,11 @@ impl ScopeArena { } pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { - let scope = self - .get_scope(id) - .expect("The base scope should never be moved"); + let scope = unsafe { + &mut *self + .get_scope_mut(id) + .expect("The base scope should never be moved") + }; // Cycle to the next frame and then reset it // This breaks any latent references, invalidating every pointer referencing into it.