Bläddra i källkod

wip: remove scopechildren in favor of elements directly

Jonathan Kelley 3 år sedan
förälder
incheckning
574d7fd

+ 62 - 34
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 FragmentBuilder<'a, const BUILT: bool> {
-    children: Option<ScopeChildren<'a>>,
-}
-impl<'a> FragmentBuilder<'a, false> {
-    pub fn children(self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
-        FragmentBuilder {
-            children: Some(children),
-        }
+pub struct FragmentProps(Element);
+pub struct FragmentBuilder<const BUILT: bool>(Element);
+impl FragmentBuilder<false> {
+    pub fn children(self, children: Option<NodeLink>) -> FragmentBuilder<true> {
+        FragmentBuilder(children)
     }
 }
-
-impl<'a, const A: bool> FragmentBuilder<'a, A> {
-    pub fn build(self) -> FragmentProps<'a> {
-        FragmentProps {
-            children: self.children.unwrap_or_default(),
-        }
+impl<const A: bool> FragmentBuilder<A> {
+    pub fn build(self) -> FragmentProps {
+        FragmentProps(self.0)
     }
 }
 
-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<false>;
     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) {}
 }
 

+ 1 - 1
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;
 

+ 3 - 6
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;
 }

+ 62 - 126
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.
+    ///
+    /// Linked VNodes can only be made through the [`Context::render`] method
+    ///
+    /// Typically, linked nodes are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into
+    /// an `rsx!` call.
     ///
-    /// Is essentially a "pointer" to a "rendered" node in a particular scope
+    /// # Example
+    /// ```rust
+    /// let mut vdom = VirtualDom::new();
     ///
-    /// Used in portals
+    /// 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<Option<BumpBox<'a, dyn FnMut() -> 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<NodeLink> {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        todo!()
-        // cx.fragment_from_iter(None as Option<VNode>)
-    }
-}
-// Conveniently, we also support "None"
-impl IntoVNode<'_> for NodeLink {
-    fn into_vnode(self, cx: NodeFactory) -> VNode {
-        todo!()
-        // cx.fragment_from_iter(None as Option<VNode>)
-    }
-}
-
 impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
         self.unwrap_or_else(|| cx.fragment_from_iter(None as Option<VNode>))
@@ -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<VNode<'a>>,
-}
-
-impl Default for ScopeChildren<'_> {
-    fn default() -> Self {
-        Self { root: None }
+impl IntoVNode<'_> for Option<NodeLink> {
+    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<VNode<'a>>) -> Self {
-        Self { root }
+impl IntoVNode<'_> for &Option<NodeLink> {
+    fn into_vnode(self, cx: NodeFactory) -> VNode {
+        todo!()
     }
 }
 
-impl IntoIterator for &ScopeChildren<'_> {
-    type Item = Self;
-
-    type IntoIter = std::iter::Once<Self>;
-
-    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<VNode>),
-        }
+// Conveniently, we also support "None"
+impl IntoVNode<'_> for &NodeLink {
+    fn into_vnode(self, cx: NodeFactory) -> VNode {
+        todo!()
     }
 }

+ 11 - 18
packages/core/src/scope.rs

@@ -315,10 +315,7 @@ impl Scope {
     ///     rsx!(cx, div { "hello {state.0}" })
     /// }
     /// ```
-    pub fn provide_state<T>(&self, value: T)
-    where
-        T: 'static,
-    {
+    pub fn provide_state<T: 'static>(&self, value: T) {
         self.shared_contexts
             .borrow_mut()
             .insert(TypeId::of::<T>(), 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<u32> {
-        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<u32> {
-        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 {

+ 13 - 6
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.