From edbb33b2ee3692cac155b9e206cd7c2d6382eb9d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 7 Feb 2021 19:14:04 -0500 Subject: [PATCH] Feat: implememt nodes better --- CHANGELOG.md | 33 +++--- packages/core/README.md | 8 +- packages/core/examples/sketch.rs | 2 +- packages/core/src/context.rs | 144 ++++++++++++++++++++++++ packages/core/src/lib.rs | 5 +- packages/core/src/nodebuilder.rs | 146 ++++++++++++------------ packages/core/src/nodes.rs | 186 +++++++++---------------------- packages/core/src/scope.rs | 175 +++-------------------------- packages/hooks/src/lib.rs | 5 +- 9 files changed, 317 insertions(+), 387 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b39b0acdd..cc883686f 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/packages/core/README.md b/packages/core/README.md index 90b9e7e2d..45077ee82 100644 --- a/packages/core/README.md +++ b/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 diff --git a/packages/core/examples/sketch.rs b/packages/core/examples/sketch.rs index e7a66e97d..2c10180a1 100644 --- a/packages/core/examples/sketch.rs +++ b/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") } } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index e69de29bb..7434874f9 100644 --- a/packages/core/src/context.rs +++ b/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) -> VNode { +/// html! { +///
"Hello, {ctx.props.name}"
+/// } +/// } +/// ``` +// 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, + pub(crate) hooks: &'src RefCell>, + + // 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 { + todo!("Children API not yet implemented for component Context") + } + + /// Access a parent context + pub fn parent_context(&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) -> VNode { + /// // Lazy assemble the VNode tree + /// let lazy_tree = html! {
"Hello World"
}; + /// + /// // 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 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 = 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::().unwrap(); + + // todo: set up an updater with the subscription API + let updater = (); + + runner(internal_state, updater) + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 699febaa7..5d024a15f 100644 --- a/packages/core/src/lib.rs +++ b/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::*; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index a152da27e..0a4641075 100644 --- a/packages/core/src/nodebuilder.rs +++ b/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(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(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(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. diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 1e7c95f8a..a6d2451d6 100644 --- a/packages/core/src/nodes.rs +++ b/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(text: S) -> Self - // where - // S: Into, - // { - // /* - // 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! {
{world}
} - // /// - // /// So that we end up with
world
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! {
{Hello} {world}
} ->
Hello world
- // /// html! {
{Hello}
} ->
Hello
- // /// - // /// So that we end up with
Hello world
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 for VNode { - // fn from(other: VText) -> Self { - // VNode::Text(other) - // } - // } - - // impl From 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 diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 5b5aa0d16..fee30c9d0 100644 --- a/packages/core/src/scope.rs +++ b/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, + // These hooks are actually references into the hook arena + // These two could be combined with "OwningRef" to remove unsafe usage + // TODO @Jon hooks: RefCell>, + hook_arena: typed_arena::Arena, + 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(&mut self) -> Context { 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) -> VNode { -/// html! { -///
"Hello, {ctx.props.name}"
-/// } -/// } -/// ``` -// 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, - hooks: &'src RefCell>, - - // 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 { - todo!("Children API not yet implemented for component Context") - } - - /// Access a parent context - pub fn parent_context(&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) -> VNode { - /// // Lazy assemble the VNode tree - /// let lazy_tree = html! {
"Hello World"
}; - /// - /// // 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 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 = 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::() - .unwrap(); - - // todo: set up an updater with the subscription API - let updater = (); - - runner(internal_state, updater) - } -} - -pub struct Hook { - state: Box, -} +pub struct Hook(pub Box); impl Hook { - fn new(state: Box) -> Self { - Self { state } + pub fn new(state: Box) -> Self { + Self(state) } } diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 980ce68e6..e38630898 100644 --- a/packages/hooks/src/lib.rs +++ b/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(ctx: &mut Context, init: impl Fn() -> T) -> (T, impl Fn(T)) { + pub fn use_state(ctx: &Context, init: impl Fn() -> T) -> (T, impl Fn(T)) { let g = init(); (g, |_| {}) }