Explorar el Código

Feat: implememt nodes better

Jonathan Kelley hace 4 años
padre
commit
edbb33b

+ 20 - 13
CHANGELOG.md

@@ -1,4 +1,5 @@
 # Project: Live-View 🤲 🍨
+> Combine the server and client into a single file :) 
 
 
 # Project: Sanitization (TBD)
@@ -8,17 +9,10 @@
 # Project: Examples
 > Get *all* the examples
 - [ ] (Examples) Tide example with templating
-- [ ] (Examples) Tide example with templating
-- [ ] (Examples) Tide example with templating
-- [ ] (Examples) Tide example with templating
 
 # Project: State management 
 > Get some global state management installed with the hooks API
 
-# Project: Hooks + Context + Subscriptions (TBD)
-> Implement a light-weight string renderer with basic caching 
-- [ ] Implement context object
-
 
 # Project: Concurrency (TBD)
 > Ensure the concurrency model works well, play with lifetimes to check if it can be multithreaded + halted
@@ -31,13 +25,26 @@
 - [ ] (SSR) Implement stateful 3rd party string renderer
 - [ ] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}" in place of {format!("Text is {}",blah)}
 
+# Project: Hooks + Context + Subscriptions (TBD)
+> Implement the foundations for state management
+- [x] Implement context object
+- [ ] Implement use_state
+- [ ] Implement use_ref
+- [ ] Implement use_reducer
+- [ ] Implement use_context
+
+# Project: QOL 
+> Make it easier to write components
+- [ ] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed)
+- [ ] (Macro) Tweak component syntax to accept a new custom element 
+- [ ] (Macro) Allow components to specify their props as function args
+
 # Project: Initial VDOM support (TBD)
 > Get the initial VDom + Event System + Patching + Diffing + Component framework up and running
 - [x] (Core) Migrate virtual node into new VNode type
-- [ ] (Macro) Allow components to specify their props as function args
-- [ ] (Core) Arena allocate VNodes
-- [ ] (Core) Allow VNodes to borrow arena contents
-- [ ] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures)
-- [ ] (Macro) Tweak component syntax to accept a new custom element 
-- [ ] (Core) Introduce the VDOM and patch API for 3rd party renderers
+- [x] (Core) Arena allocate VNodes
+- [x] (Core) Allow VNodes to borrow arena contents
+- [x] (Core) Introduce the VDOM and patch API for 3rd party renderers
+- [ ] (Core) Implement lifecycle
+- [ ] (Core) Implement an event system 
 

+ 4 - 4
packages/core/README.md

@@ -6,7 +6,7 @@ This is the core crate for the Dioxus Virtual DOM. This README will focus on the
 Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:
 
 - React: hooks, concurrency, suspense
-- Dodrio: bump allocation, double buffering
+- Dodrio: bump allocation, double buffering, and source code for nodes + NodeBuilder
 - Percy: html! macro architecture, platform-agnostic edits
 - Yew: passion and inspiration ❤️
 
@@ -17,7 +17,7 @@ We have big goals for Dioxus. The final implementation must:
 - Be **fast**. Allocators are typically slow in WASM/Rust, so we should have a smart way of allocating.
 - Be extremely memory efficient. Servers should handle tens of thousands of simultaneous VDoms with no problem.
 - Be concurrent. Components should be able to pause rendering using a threading mechanism.
-- Support "broadcasting". Edit lists should be separate from the Renderer implementation.
+- Be "remote". Edit lists should be separate from the Renderer implementation.
 - Support SSR. VNodes should render to a string that can be served via a web server.
 - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
 - Be modular. Components and hooks should be work anywhere without worrying about target platform.
@@ -25,8 +25,8 @@ We have big goals for Dioxus. The final implementation must:
 ## Optimizations
 
 - Support a pluggable allocation strategy that makes VNode creation **very** fast
-- Support lazy DomTrees (ie DomTrees that are not actually created when the view fn is ran)
-- Support advanced diffing strategies
+- Support lazy DomTrees (ie DomTrees that are not actually created when the html! macro is used)
+- Support advanced diffing strategies (patience, Myers, etc)
 
 ## Design Quirks
 

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

@@ -138,7 +138,7 @@ fn test_use_state(ctx: Context<()>) -> VNode {
     // Those vnodes are then tossed out and new ones are installed, meaning and old references (potentially bad)
     // are removed and UB is prevented from /affecting/ the program
     {
-        VNode::Element(VElement::new("button"))
+        VNode::text("blah")
     }
 }
 

+ 144 - 0
packages/core/src/context.rs

@@ -0,0 +1,144 @@
+use crate::nodes::VNode;
+use crate::prelude::*;
+use crate::scope::Hook;
+use bumpalo::Bump;
+use std::{
+    any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
+};
+
+/// Components in Dioxus use the "Context" object to interact with their lifecycle.
+/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
+///
+/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
+///
+/// ```ignore
+/// #[derive(Properties)]
+/// struct Props {
+///     name: String
+///
+/// }
+///
+/// fn example(ctx: &Context<Props>) -> VNode {
+///     html! {
+///         <div> "Hello, {ctx.props.name}" </div>
+///     }
+/// }
+/// ```
+// todo: force lifetime of source into T as a valid lifetime too
+// it's definitely possible, just needs some more messing around
+pub struct Context<'src, T> {
+    /// Direct access to the properties used to create this component.
+    pub props: T,
+    pub idx: AtomicUsize,
+
+    // Borrowed from scope
+    pub(crate) arena: &'src typed_arena::Arena<Hook>,
+    pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
+
+    // holder for the src lifetime
+    // todo @jon remove this
+    pub _p: std::marker::PhantomData<&'src ()>,
+}
+
+impl<'a, T> Context<'a, T> {
+    /// Access the children elements passed into the component
+    pub fn children(&self) -> Vec<VNode> {
+        todo!("Children API not yet implemented for component Context")
+    }
+
+    /// Access a parent context
+    pub fn parent_context<C>(&self) -> C {
+        todo!("Context API is not ready yet")
+    }
+
+    /// Create a subscription that schedules a future render for the reference component
+    pub fn subscribe(&self) -> impl FnOnce() -> () {
+        todo!("Subscription API is not ready yet");
+        || {}
+    }
+
+    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
+    ///
+    /// ```ignore
+    /// fn Component(ctx: Context<Props>) -> VNode {
+    ///     // Lazy assemble the VNode tree
+    ///     let lazy_tree = html! {<div>"Hello World"</div>};
+    ///     
+    ///     // Actually build the tree and allocate it
+    ///     ctx.view(lazy_tree)
+    /// }
+    ///```
+    pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
+        todo!()
+    }
+
+    /// Create a suspended component from a future.
+    ///
+    /// When the future completes, the component will be renderered
+    pub fn suspend(
+        &self,
+        fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
+    ) -> VNode<'a> {
+        todo!()
+    }
+
+    /// use_hook provides a way to store data between renders for functional components.
+    pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
+        &'comp self,
+        // The closure that builds the hook state
+        initializer: impl FnOnce() -> InternalHookState,
+        // The closure that takes the hookstate and returns some value
+        runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
+        // The closure that cleans up whatever mess is left when the component gets torn down
+        // TODO: add this to the "clean up" group for when the component is dropped
+        cleanup: impl FnOnce(InternalHookState),
+    ) -> Output {
+        let raw_hook = {
+            let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
+
+            // Mutate hook list if necessary
+            let mut hooks = self.hooks.borrow_mut();
+
+            // Initialize the hook by allocating it in the typed arena.
+            // We get a reference from the arena which is owned by the component scope
+            // This is valid because "Context" is only valid while the scope is borrowed
+            if idx >= hooks.len() {
+                let new_state = initializer();
+                let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
+                let hook = self.arena.alloc(Hook::new(boxed_state));
+
+                // Push the raw pointer instead of the &mut
+                // A "poor man's OwningRef"
+                hooks.push(hook);
+            }
+            self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
+
+            *hooks.get(idx).unwrap()
+        };
+
+        /*
+        ** UNSAFETY ALERT **
+        Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
+
+        However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
+        into the arena. During the first call of the function, we need to add the mutable reference given to us by
+        the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
+        when the component itself is deallocated.
+
+        This is okay because:
+        - The lifetime of the component arena is tied to the lifetime of these raw hooks
+        - Usage of the raw hooks is tied behind the Vec refcell
+        - Output is static, meaning it can't take a reference to the data
+        - We don't expose the raw hook pointer outside of the scope of use_hook
+        - The reference is tied to context, meaning it can only be used while ctx is around to free it
+        */
+        let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
+
+        let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
+
+        // todo: set up an updater with the subscription API
+        let updater = ();
+
+        runner(internal_state, updater)
+    }
+}

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

@@ -82,8 +82,9 @@ pub mod builder {
 // types used internally that are important
 pub(crate) mod inner {
     pub use crate::component::{Component, Properties};
+    pub use crate::context::Context;
     use crate::nodes;
-    pub use crate::scope::{Context, Hook, Scope};
+    pub use crate::scope::{Hook, Scope};
     pub use crate::virtual_dom::VirtualDom;
     pub use nodes::*;
 
@@ -106,8 +107,8 @@ pub(crate) mod inner {
 /// Essential when working with the html! macro
 pub mod prelude {
     pub use crate::component::{Component, Properties};
+    pub use crate::context::Context;
     use crate::nodes;
-    pub use crate::scope::Context;
     pub use crate::virtual_dom::VirtualDom;
     pub use nodes::*;
 

+ 74 - 72
packages/core/src/nodebuilder.rs

@@ -1,7 +1,10 @@
-//! Helpers for building virtual DOM nodes.
+//! Helpers for building virtual DOM VNodes.
+
+use crate::{
+    nodes::{Attribute, Listener, NodeKey, VNode},
+    prelude::VElement,
+};
 
-use crate::nodes::{Attribute, Listener, NodeKey, VNode};
-type Node<'g> = VNode<'g>;
 use bumpalo::Bump;
 
 /// A virtual DOM element builder.
@@ -14,7 +17,7 @@ pub struct ElementBuilder<'a, Listeners, Attributes, Children>
 where
     Listeners: 'a + AsRef<[Listener<'a>]>,
     Attributes: 'a + AsRef<[Attribute<'a>]>,
-    Children: 'a + AsRef<[Node<'a>]>,
+    Children: 'a + AsRef<[VNode<'a>]>,
 {
     bump: &'a Bump,
     key: NodeKey,
@@ -30,7 +33,7 @@ impl<'a>
         'a,
         bumpalo::collections::Vec<'a, Listener<'a>>,
         bumpalo::collections::Vec<'a, Attribute<'a>>,
-        bumpalo::collections::Vec<'a, Node<'a>>,
+        bumpalo::collections::Vec<'a, VNode<'a>>,
     >
 {
     /// Create a new `ElementBuilder` for an element with the given tag name.
@@ -77,7 +80,7 @@ impl<'a, Listeners, Attributes, Children> ElementBuilder<'a, Listeners, Attribut
 where
     Listeners: 'a + AsRef<[Listener<'a>]>,
     Attributes: 'a + AsRef<[Attribute<'a>]>,
-    Children: 'a + AsRef<[Node<'a>]>,
+    Children: 'a + AsRef<[VNode<'a>]>,
 {
     /// Set the listeners for this element.
     ///
@@ -187,7 +190,7 @@ where
     #[inline]
     pub fn children<C>(self, children: C) -> ElementBuilder<'a, Listeners, Attributes, C>
     where
-        C: 'a + AsRef<[Node<'a>]>,
+        C: 'a + AsRef<[VNode<'a>]>,
     {
         ElementBuilder {
             bump: self.bump,
@@ -230,7 +233,7 @@ where
     /// Set this element's key.
     ///
     /// When diffing sets of siblings, if an old sibling and new sibling share a
-    /// key, then they will always reuse the same physical DOM node. This is
+    /// key, then they will always reuse the same physical DOM VNode. This is
     /// important when using CSS animations, web components, third party JS, or
     /// anything else that makes the diffing implementation observable.
     ///
@@ -244,7 +247,7 @@ where
     ///
     /// Keys must be unique among siblings.
     ///
-    /// All sibling nodes must be keyed, or they must all not be keyed. You may
+    /// All sibling VNodes must be keyed, or they must all not be keyed. You may
     /// not mix keyed and unkeyed siblings.
     ///
     /// # Example
@@ -266,25 +269,25 @@ where
         self
     }
 
-    /// Create the virtual DOM node described by this builder.
+    /// Create the virtual DOM VNode described by this builder.
     ///
     /// # Example
     ///
     /// ```no_run
-    /// use dodrio::{builder::*, bumpalo::Bump, Node};
+    /// use dodrio::{builder::*, bumpalo::Bump, VNode};
     ///
     /// let b = Bump::new();
     ///
     /// // Start with a builder...
     /// let builder: ElementBuilder<_, _, _> = div(&b);
     ///
-    /// // ...and finish it to create a virtual DOM node!
-    /// let my_div: Node = builder.finish();
+    /// // ...and finish it to create a virtual DOM VNode!
+    /// let my_div: VNode = builder.finish();
     /// ```
     #[inline]
-    pub fn finish(self) -> Node<'a> {
+    pub fn finish(self) -> VNode<'a> {
         let children: &'a Children = self.bump.alloc(self.children);
-        let children: &'a [Node<'a>] = children.as_ref();
+        let children: &'a [VNode<'a>] = children.as_ref();
 
         let listeners: &'a Listeners = self.bump.alloc(self.listeners);
         let listeners: &'a [Listener<'a>] = listeners.as_ref();
@@ -292,63 +295,62 @@ where
         let attributes: &'a Attributes = self.bump.alloc(self.attributes);
         let attributes: &'a [Attribute<'a>] = attributes.as_ref();
 
-        todo!()
-        // Node::element(
-        //     self.bump,
-        //     self.key,
-        //     self.tag_name,
-        //     listeners,
-        //     attributes,
-        //     children,
-        //     self.namespace,
-        // )
+        VNode::element(
+            self.bump,
+            self.key,
+            self.tag_name,
+            listeners,
+            attributes,
+            children,
+            self.namespace,
+        )
     }
 }
 
-// impl<'a, Attributes, Children>
-//     ElementBuilder<'a, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
-// where
-//     Attributes: 'a + AsRef<[Attribute<'a>]>,
-//     Children: 'a + AsRef<[Node<'a>]>,
-// {
-//     /// Add a new event listener to this element.
-//     ///
-//     /// The `event` string specifies which event will be listened for. The
-//     /// `callback` function is the function that will be invoked if the
-//     /// specified event occurs.
-//     ///
-//     /// # Example
-//     ///
-//     /// ```no_run
-//     /// use dodrio::{builder::*, bumpalo::Bump};
-//     ///
-//     /// let b = Bump::new();
-//     ///
-//     /// // A button that does something when clicked!
-//     /// let my_button = button(&b)
-//     ///     .on("click", |root, vdom, event| {
-//     ///         // ...
-//     ///     })
-//     ///     .finish();
-//     /// ```
-//     #[inline]
-//     pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
-//     where
-//         F: 'static + Fn(&mut dyn RootRender, VdomWeak, web_sys::Event),
-//     {
-//         self.listeners.push(Listener {
-//             event,
-//             callback: self.bump.alloc(callback),
-//         });
-//         self
-//     }
-// }
+impl<'a, Attributes, Children>
+    ElementBuilder<'a, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
+where
+    Attributes: 'a + AsRef<[Attribute<'a>]>,
+    Children: 'a + AsRef<[VNode<'a>]>,
+{
+    /// Add a new event listener to this element.
+    ///
+    /// The `event` string specifies which event will be listened for. The
+    /// `callback` function is the function that will be invoked if the
+    /// specified event occurs.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// use dodrio::{builder::*, bumpalo::Bump};
+    ///
+    /// let b = Bump::new();
+    ///
+    /// // A button that does something when clicked!
+    /// let my_button = button(&b)
+    ///     .on("click", |root, vdom, event| {
+    ///         // ...
+    ///     })
+    ///     .finish();
+    /// ```
+    #[inline]
+    pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
+    where
+        F: 'static + Fn(),
+    {
+        self.listeners.push(Listener {
+            event,
+            callback: self.bump.alloc(callback),
+        });
+        self
+    }
+}
 
 impl<'a, Listeners, Children>
     ElementBuilder<'a, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
 where
     Listeners: 'a + AsRef<[Listener<'a>]>,
-    Children: 'a + AsRef<[Node<'a>]>,
+    Children: 'a + AsRef<[VNode<'a>]>,
 {
     /// Add a new attribute to this element.
     ///
@@ -404,7 +406,7 @@ where
 }
 
 impl<'a, Listeners, Attributes>
-    ElementBuilder<'a, Listeners, Attributes, bumpalo::collections::Vec<'a, Node<'a>>>
+    ElementBuilder<'a, Listeners, Attributes, bumpalo::collections::Vec<'a, VNode<'a>>>
 where
     Listeners: 'a + AsRef<[Listener<'a>]>,
     Attributes: 'a + AsRef<[Attribute<'a>]>,
@@ -425,7 +427,7 @@ where
     ///     .finish();
     /// ```
     #[inline]
-    pub fn child(mut self, child: Node<'a>) -> Self {
+    pub fn child(mut self, child: VNode<'a>) -> Self {
         self.children.push(child);
         self
     }
@@ -445,7 +447,7 @@ macro_rules! builder_constructors {
                 'a,
                 bumpalo::collections::Vec<'a, Listener<'a>>,
                 bumpalo::collections::Vec<'a, Attribute<'a>>,
-                bumpalo::collections::Vec<'a, Node<'a>>,
+                bumpalo::collections::Vec<'a, VNode<'a>>,
             >
             where
                 B: Into<&'a Bump>
@@ -467,7 +469,7 @@ macro_rules! builder_constructors {
                 'a,
                 bumpalo::collections::Vec<'a, Listener<'a>>,
                 bumpalo::collections::Vec<'a, Attribute<'a>>,
-                bumpalo::collections::Vec<'a, Node<'a>>,
+                bumpalo::collections::Vec<'a, VNode<'a>>,
             > {
                 let builder = ElementBuilder::new(bump, stringify!($name));
                 builder.namespace(Some($namespace))
@@ -1018,9 +1020,9 @@ builder_constructors! {
     image <> "http://www.w3.org/2000/svg";
 }
 
-/// Construct a text node.
+/// Construct a text VNode.
 ///
-/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
+/// This is `dodrio`'s virtual DOM equivalent of `document.createTextVNode`.
 ///
 /// # Example
 ///
@@ -1030,8 +1032,8 @@ builder_constructors! {
 /// let my_text = text("hello, dodrio!");
 /// ```
 #[inline]
-pub fn text<'a>(contents: &'a str) -> Node<'a> {
-    Node::text(contents)
+pub fn text<'a>(contents: &'a str) -> VNode<'a> {
+    VNode::text(contents)
 }
 
 /// Construct an attribute for an element.

+ 50 - 136
packages/core/src/nodes.rs

@@ -22,7 +22,7 @@ mod vnode {
 
     pub enum VNode<'src> {
         /// An element node (node type `ELEMENT_NODE`).
-        Element(VElement<'src>),
+        Element(&'src VElement<'src>),
 
         /// A text node (node type `TEXT_NODE`).
         ///
@@ -40,147 +40,54 @@ mod vnode {
         Component(VComponent),
     }
 
-    impl<'src> VNode<'src> {
-        /// Create a new virtual element node with a given tag.
+    impl<'a> VNode<'a> {
+        /// Low-level constructor for making a new `Node` of type element with given
+        /// parts.
         ///
-        /// These get patched into the DOM using `document.createElement`
-        ///
-        /// ```ignore
-        /// let div = VNode::element("div");
-        /// ```
-        pub fn element(tag: &'static str) -> Self {
-            VNode::Element(VElement::new(tag))
+        /// This is primarily intended for JSX and templating proc-macros to compile
+        /// down into. If you are building nodes by-hand, prefer using the
+        /// `dodrio::builder::*` APIs.
+        #[inline]
+        pub fn element(
+            bump: &'a Bump,
+            key: NodeKey,
+            tag_name: &'a str,
+            listeners: &'a [Listener<'a>],
+            attributes: &'a [Attribute<'a>],
+            children: &'a [VNode<'a>],
+            namespace: Option<&'a str>,
+        ) -> VNode<'a> {
+            let element = bump.alloc_with(|| VElement {
+                key,
+                tag_name,
+                listeners,
+                attributes,
+                children,
+                namespace,
+            });
+            VNode::Element(element)
         }
 
         /// Construct a new text node with the given text.
         #[inline]
-        pub(crate) fn text(text: &'src str) -> VNode<'src> {
+        pub fn text(text: &'a str) -> VNode<'a> {
             VNode::Text(VText { text })
         }
-        // /// Create a new virtual text node with the given text.
-        // ///
-        // /// These get patched into the DOM using `document.createTextNode`
-        // ///
-        // /// ```ignore
-        // /// let div = VNode::text("div");
-        // /// ```
-        // pub fn text<S>(text: S) -> Self
-        // where
-        //     S: Into<String>,
-        // {
-        //     /*
-        //     TODO
-
-        //     This is an opportunity to be extremely efficient when allocating/creating strings
-        //     To assemble a formatted string, we can, using the macro, borrow all the contents without allocating.
-
-        //     String contents are therefore bump allocated automatically
-
-        //     html!{
-        //         <>"Hello {world}"</>
-        //     }
-
-        //     Should be
-
-        //     ```
-        //     let mut root = VNode::text(["Hello", world]);
-
-        //     ```
-
-        //     */
-        //     VNode::Text(VText::new(text.into()))
-        // }
-
-        // /// Return a [`VElement`] reference, if this is an [`Element`] variant.
-        // ///
-        // /// [`VElement`]: struct.VElement.html
-        // /// [`Element`]: enum.VNode.html#variant.Element
-        // pub fn as_velement_ref(&self) -> Option<&VElement> {
-        //     match self {
-        //         VNode::Element(ref element_node) => Some(element_node),
-        //         _ => None,
-        //     }
-        // }
 
-        // /// Return a mutable [`VElement`] reference, if this is an [`Element`] variant.
-        // ///
-        // /// [`VElement`]: struct.VElement.html
-        // /// [`Element`]: enum.VNode.html#variant.Element
-        // pub fn as_velement_mut(&mut self) -> Option<&mut VElement> {
-        //     match self {
-        //         VNode::Element(ref mut element_node) => Some(element_node),
-        //         _ => None,
-        //     }
-        // }
-
-        // /// Return a [`VText`] reference, if this is an [`Text`] variant.
-        // ///
-        // /// [`VText`]: struct.VText.html
-        // /// [`Text`]: enum.VNode.html#variant.Text
-        // pub fn as_vtext_ref(&self) -> Option<&VText> {
-        //     match self {
-        //         VNode::Text(ref text_node) => Some(text_node),
-        //         _ => None,
-        //     }
-        // }
-
-        // /// Return a mutable [`VText`] reference, if this is an [`Text`] variant.
-        // ///
-        // /// [`VText`]: struct.VText.html
-        // /// [`Text`]: enum.VNode.html#variant.Text
-        // pub fn as_vtext_mut(&mut self) -> Option<&mut VText> {
-        //     match self {
-        //         VNode::Text(ref mut text_node) => Some(text_node),
-        //         _ => None,
-        //     }
-        // }
-
-        // /// Used by html-macro to insert space before text that is inside of a block that came after
-        // /// an open tag.
-        // ///
-        // /// html! { <div> {world}</div> }
-        // ///
-        // /// So that we end up with <div> world</div> when we're finished parsing.
-        // pub fn insert_space_before_text(&mut self) {
-        //     match self {
-        //         VNode::Text(text_node) => {
-        //             text_node.text = " ".to_string() + &text_node.text;
-        //         }
-        //         _ => {}
-        //     }
-        // }
-
-        // /// Used by html-macro to insert space after braced text if we know that the next block is
-        // /// another block or a closing tag.
-        // ///
-        // /// html! { <div>{Hello} {world}</div> } -> <div>Hello world</div>
-        // /// html! { <div>{Hello} </div> } -> <div>Hello </div>
-        // ///
-        // /// So that we end up with <div>Hello world</div> when we're finished parsing.
-        // pub fn insert_space_after_text(&mut self) {
-        //     match self {
-        //         VNode::Text(text_node) => {
-        //             text_node.text += " ";
-        //         }
-        //         _ => {}
-        //     }
-        // }
+        #[inline]
+        pub(crate) fn key(&self) -> NodeKey {
+            match &self {
+                VNode::Text(_) => NodeKey::NONE,
+                VNode::Element(e) => e.key,
+                VNode::Suspended => {
+                    todo!()
+                }
+                VNode::Component(_) => {
+                    todo!()
+                }
+            }
+        }
     }
-
-    // -----------------------------------------------
-    //  Convert from DOM elements to the primary enum
-    // -----------------------------------------------
-    // impl From<VText> for VNode {
-    //     fn from(other: VText) -> Self {
-    //         VNode::Text(other)
-    //     }
-    // }
-
-    // impl From<VElement> for VNode {
-    //     fn from(other: VElement) -> Self {
-    //         VNode::Element(other)
-    //     }
-    // }
 }
 
 mod velement {
@@ -188,11 +95,18 @@ mod velement {
     use std::collections::HashMap;
 
     pub struct VElement<'a> {
-        /// The HTML tag, such as "div"
-        pub tag: &'a str,
-
+        /// Elements have a tag name, zero or more attributes, and zero or more
+        pub key: NodeKey,
         pub tag_name: &'a str,
+        pub listeners: &'a [Listener<'a>],
         pub attributes: &'a [Attribute<'a>],
+        pub children: &'a [VNode<'a>],
+        pub namespace: Option<&'a str>,
+        // The HTML tag, such as "div"
+        // pub tag: &'a str,
+
+        // pub tag_name: &'a str,
+        // pub attributes: &'a [Attribute<'a>],
         // todo: hook up listeners
         // pub listeners: &'a [Listener<'a>],
         // / HTML attributes such as id, class, style, etc

+ 17 - 158
packages/core/src/scope.rs

@@ -1,22 +1,23 @@
 use crate::nodes::VNode;
 use crate::prelude::*;
-use any::Any;
 use bumpalo::Bump;
-use generational_arena::{Arena, Index};
 use std::{
-    any::{self, TypeId},
-    cell::{RefCell, UnsafeCell},
-    future::Future,
-    marker::PhantomData,
-    sync::atomic::AtomicUsize,
+    any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
 };
 
-/// The Scope that wraps a functional component
-/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components
-/// The actualy contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
+/// Every component in Dioxus is represented by a `Scope`.
+///
+/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
+///
+/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
+/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
 pub struct Scope {
-    arena: typed_arena::Arena<Hook>,
+    // These hooks are actually references into the hook arena
+    // These two could be combined with "OwningRef" to remove unsafe usage
+    // TODO @Jon
     hooks: RefCell<Vec<*mut Hook>>,
+    hook_arena: typed_arena::Arena<Hook>,
+
     props_type: TypeId,
     caller: *const i32,
 }
@@ -32,7 +33,7 @@ impl Scope {
         let caller = f as *const i32;
 
         Self {
-            arena,
+            hook_arena: arena,
             hooks,
             props_type,
             caller,
@@ -42,7 +43,7 @@ impl Scope {
     pub fn create_context<T: Properties>(&mut self) -> Context<T> {
         Context {
             _p: PhantomData {},
-            arena: &self.arena,
+            arena: &self.hook_arena,
             hooks: &self.hooks,
             idx: 0.into(),
             props: T::new(),
@@ -74,152 +75,10 @@ impl Scope {
     }
 }
 
-/// Components in Dioxus use the "Context" object to interact with their lifecycle.
-/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
-///
-/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
-///
-/// ```ignore
-/// #[derive(Properties)]
-/// struct Props {
-///     name: String
-///
-/// }
-///
-/// fn example(ctx: &Context<Props>) -> VNode {
-///     html! {
-///         <div> "Hello, {ctx.props.name}" </div>
-///     }
-/// }
-/// ```
-// todo: force lifetime of source into T as a valid lifetime too
-// it's definitely possible, just needs some more messing around
-pub struct Context<'src, T> {
-    /// Direct access to the properties used to create this component.
-    pub props: T,
-    pub idx: AtomicUsize,
-
-    // Borrowed from scope
-    arena: &'src typed_arena::Arena<Hook>,
-    hooks: &'src RefCell<Vec<*mut Hook>>,
-
-    // holder for the src lifetime
-    // todo @jon remove this
-    pub _p: std::marker::PhantomData<&'src ()>,
-}
-
-impl<'a, T> Context<'a, T> {
-    /// Access the children elements passed into the component
-    pub fn children(&self) -> Vec<VNode> {
-        todo!("Children API not yet implemented for component Context")
-    }
-
-    /// Access a parent context
-    pub fn parent_context<C>(&self) -> C {
-        todo!("Context API is not ready yet")
-    }
-
-    /// Create a subscription that schedules a future render for the reference component
-    pub fn subscribe(&self) -> impl FnOnce() -> () {
-        todo!("Subscription API is not ready yet");
-        || {}
-    }
-
-    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
-    ///
-    /// ```ignore
-    /// fn Component(ctx: Context<Props>) -> VNode {
-    ///     // Lazy assemble the VNode tree
-    ///     let lazy_tree = html! {<div>"Hello World"</div>};
-    ///     
-    ///     // Actually build the tree and allocate it
-    ///     ctx.view(lazy_tree)
-    /// }
-    ///```
-    pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
-        todo!()
-    }
-
-    /// Create a suspended component from a future.
-    ///
-    /// When the future completes, the component will be renderered
-    pub fn suspend(
-        &self,
-        fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
-    ) -> VNode<'a> {
-        todo!()
-    }
-
-    /// use_hook provides a way to store data between renders for functional components.
-    pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
-        &'comp self,
-        // The closure that builds the hook state
-        initializer: impl FnOnce() -> InternalHookState,
-        // The closure that takes the hookstate and returns some value
-        runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
-        // The closure that cleans up whatever mess is left when the component gets torn down
-        // TODO: add this to the "clean up" group for when the component is dropped
-        cleanup: impl FnOnce(InternalHookState),
-    ) -> Output {
-        let raw_hook = {
-            let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
-
-            // Mutate hook list if necessary
-            let mut hooks = self.hooks.borrow_mut();
-
-            // Initialize the hook by allocating it in the typed arena.
-            // We get a reference from the arena which is owned by the component scope
-            // This is valid because "Context" is only valid while the scope is borrowed
-            if idx >= hooks.len() {
-                let new_state = initializer();
-                let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
-                let hook = self.arena.alloc(Hook::new(boxed_state));
-
-                // Push the raw pointer instead of the &mut
-                // A "poor man's OwningRef"
-                hooks.push(hook);
-            }
-            self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
-
-            *hooks.get(idx).unwrap()
-        };
-
-        /*
-        ** UNSAFETY ALERT **
-        Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
-
-        However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
-        into the arena. During the first call of the function, we need to add the mutable reference given to us by
-        the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
-        when the component itself is deallocated.
-
-        This is okay because:
-        - The lifetime of the component arena is tied to the lifetime of these raw hooks
-        - Usage of the raw hooks is tied behind the Vec refcell
-        - Output is static, meaning it can't take a reference to the data
-        - We don't expose the raw hook pointer outside of the scope of use_hook
-        - The reference is tied to context, meaning it can only be used while ctx is around to free it
-        */
-        let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
-
-        let internal_state = borrowed_hook
-            .state
-            .downcast_mut::<InternalHookState>()
-            .unwrap();
-
-        // todo: set up an updater with the subscription API
-        let updater = ();
-
-        runner(internal_state, updater)
-    }
-}
-
-pub struct Hook {
-    state: Box<dyn std::any::Any>,
-}
+pub struct Hook(pub Box<dyn std::any::Any>);
 
 impl Hook {
-    fn new(state: Box<dyn std::any::Any>) -> Self {
-        Self { state }
+    pub fn new(state: Box<dyn std::any::Any>) -> Self {
+        Self(state)
     }
 }

+ 4 - 1
packages/hooks/src/lib.rs

@@ -1,6 +1,9 @@
+// mod hooks;
+// pub use hooks::use_context;
+
 pub mod prelude {
     use dioxus_core::prelude::Context;
-    pub fn use_state<T, G>(ctx: &mut Context<G>, init: impl Fn() -> T) -> (T, impl Fn(T)) {
+    pub fn use_state<T, G>(ctx: &Context<G>, init: impl Fn() -> T) -> (T, impl Fn(T)) {
         let g = init();
         (g, |_| {})
     }