From c9d95dd1dc35bde6f395dd95a951a0260ecfbb8a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 7 Feb 2021 17:38:17 -0500 Subject: [PATCH] Feat: move out scope into its own file --- packages/core/architecture.md | 23 ++ packages/core/examples/dummy.rs | 95 +----- packages/core/examples/macrosrc.rs | 31 +- packages/core/examples/sketch.rs | 4 +- packages/core/examples/step.rs | 14 +- packages/core/{src => old}/old.rs | 0 packages/core/{src => old}/virtual_node.rs | 0 packages/core/src/component.rs | 58 ++++ packages/core/src/context.rs | 0 packages/core/src/debug_renderer.rs | 35 ++ packages/core/src/events.rs | 26 ++ packages/core/src/lib.rs | 56 +++- packages/core/src/nodes.rs | 2 +- packages/core/src/scope.rs | 225 +++++++++++++ packages/core/src/virtual_dom.rs | 360 ++------------------- 15 files changed, 447 insertions(+), 482 deletions(-) create mode 100644 packages/core/architecture.md rename packages/core/{src => old}/old.rs (100%) rename packages/core/{src => old}/virtual_node.rs (100%) create mode 100644 packages/core/src/component.rs create mode 100644 packages/core/src/context.rs create mode 100644 packages/core/src/debug_renderer.rs create mode 100644 packages/core/src/events.rs create mode 100644 packages/core/src/scope.rs diff --git a/packages/core/architecture.md b/packages/core/architecture.md new file mode 100644 index 000000000..9124fe788 --- /dev/null +++ b/packages/core/architecture.md @@ -0,0 +1,23 @@ +# This module includes all life-cycle related mechanics, including the virtual DOM, scopes, properties, and lifecycles. +--- +The VirtualDom is designed as so: + +VDOM contains: +- An arena of component scopes. + - A scope contains + - lifecycle data + - hook data +- Event queue + - An event + +A VDOM is +- constructed from anything that implements "component" + +A "Component" is anything (normally functions) that can be ran with a context to produce VNodes +- Must implement properties-builder trait which produces a properties builder + +A Context +- Is a consumable struct + - Made of references to properties + - Holds a reference (lockable) to the underlying scope + - Is partially thread-safe diff --git a/packages/core/examples/dummy.rs b/packages/core/examples/dummy.rs index 930cc9f8b..43550b569 100644 --- a/packages/core/examples/dummy.rs +++ b/packages/core/examples/dummy.rs @@ -1,102 +1,9 @@ #![allow(unused, non_upper_case_globals)] use bumpalo::Bump; +use dioxus_core::nodebuilder::*; use dioxus_core::prelude::VNode; use dioxus_core::prelude::*; -use dioxus_core::{nodebuilder::*, virtual_dom::Properties}; use once_cell::sync::{Lazy, OnceCell}; use std::{collections::HashMap, future::Future, marker::PhantomData}; fn main() {} - -// struct VC) -> VNode> { -// f: F, -// _a: std::marker::PhantomData<(P, F)>, // cell: OnceCell, -// // init: Cell> -// } -// impl VC { -// const fn new(init: F) -> VC { -// Self { -// _a: std::marker::PhantomData {}, -// f: init, -// } -// } -// fn builder() -> P { -// // P::new() -// } -// } - -// // Build a new functional component -// static SomeComp: VC<()> = VC::new(|ctx| { -// // This is a component, apparently -// // still not useful because we can't have bounds - -// ctx.view(html! { -//
- -//
-// }) -// }); - -/* - - - - - - - - - - - -*/ -static BILL: Lazy) -> String> = Lazy::new(|| { - // - |c| "BLAH".to_string() -}); - -// struct FUNC T> {} - -struct SomeBuilder {} - -// struct DummyRenderer { -// alloc: Bump, -// } - -// impl DummyRenderer { -// // "Renders" a domtree by logging its children and outputs -// fn render() {} - -// // Takes a domtree, an initial value, a new value, and produces the diff list -// fn produce_diffs() {} -// } - -// struct Props<'a> { -// name: &'a str, -// } - -// /// This component does "xyz things" -// /// This is sample documentation -// static Component: FC = |ctx| { -// // This block mimics that output of the html! macro - -// DomTree::new(move |bump| { -// // parse into RSX structures -// // regurgetate as rust types - -// //
"Child 1" "Child 2"
-// div(bump) -// .attr("class", "edit") -// .child(text("Child 1")) -// .child(text("Child 2")) -// .finish() -// }) -// }; - -// /* -// source -// |> c1 -> VNode -// |> c2 -> VNode -// |> c3 -> VNode -// |> c4 -> VNode -// */ diff --git a/packages/core/examples/macrosrc.rs b/packages/core/examples/macrosrc.rs index 54665ecf1..223989617 100644 --- a/packages/core/examples/macrosrc.rs +++ b/packages/core/examples/macrosrc.rs @@ -1,7 +1,7 @@ #![allow(unused, non_upper_case_globals, non_snake_case)] use bumpalo::Bump; +use dioxus_core::nodebuilder::*; use dioxus_core::prelude::*; -use dioxus_core::{nodebuilder::*, virtual_dom::Properties}; use std::{collections::HashMap, future::Future, marker::PhantomData}; fn main() { @@ -9,7 +9,7 @@ fn main() { component, Props { blah: false, - text: "blah", + text: "blah".into(), }, ); @@ -45,11 +45,12 @@ fn main() { // ~~~ Text shared between components via props can be done with lifetimes! ~~~ // Super duper efficient :) -struct Props<'src> { +struct Props { blah: bool, - text: &'src str, + text: String, + // text: &'src str, } -impl<'src> Properties for Props<'src> { +impl Properties for Props { fn new() -> Self { todo!() } @@ -88,7 +89,7 @@ fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> { div(bump) .attr("class", "edit") .child(text("Hello")) - .child(text(ctx.props.text)) + .child(text(ctx.props.text.as_ref())) .finish() }) } @@ -99,23 +100,7 @@ fn EffcComp(ctx: &Context, name: &str) -> VNode { // However, both of these are "lazy" - they need to be evaluated (aka, "viewed") // We can "view" them with Context for ultimate speed while inside components // use "phase" style allocation; - /* - nodes... - text... - attrs... -
// node0 -
// node1 - {// support some expression} // node 2 -
- let node0; - let node1; - let node2 = evaluate{}.into(); - let g= |bump| {1}; - g(bump).into() - */ - - // should we automatically view the output or leave it? ctx.view(html! {
// your template goes here @@ -129,6 +114,8 @@ fn FullySuspended<'a>(ctx: &'a Context) -> VNode<'a> { let i: i32 = 0; // full suspended works great with just returning VNodes! + // Feel free to capture the html! macro directly + // Anything returned here is automatically viewed let tex = match i { 1 => html! {
}, 2 => html! {
}, diff --git a/packages/core/examples/sketch.rs b/packages/core/examples/sketch.rs index 5f09da37b..e7a66e97d 100644 --- a/packages/core/examples/sketch.rs +++ b/packages/core/examples/sketch.rs @@ -1,8 +1,8 @@ use bumpalo::Bump; use dioxus_core as dioxus; use dioxus_core::{ - prelude::{html, Context, VElement, VNode, FC}, - virtual_dom::{Properties, Scope}, + prelude::{html, Context, Properties, VElement, VNode, FC}, + scope::Scope, }; use std::{ any::Any, diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index 36b94153b..551374f53 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -5,17 +5,15 @@ //! render it again //! consume the diffs and write that to a renderer -use dioxus_core::{ - prelude::*, - virtual_dom::{Properties, Scope}, -}; +use dioxus_core::{prelude::*, scope::Scope}; -fn main() { - let mut scope = Scope::new(Example); - let ctx = scope.create_context::(); +fn main() -> Result<(), ()> { let p1 = Props { name: "bob".into() }; - let p2 = Props { name: "bob".into() }; + let mut vdom = VirtualDom::new_with_props(Example, p1); + vdom.progress()?; + + Ok(()) } struct Props { diff --git a/packages/core/src/old.rs b/packages/core/old/old.rs similarity index 100% rename from packages/core/src/old.rs rename to packages/core/old/old.rs diff --git a/packages/core/src/virtual_node.rs b/packages/core/old/virtual_node.rs similarity index 100% rename from packages/core/src/virtual_node.rs rename to packages/core/old/virtual_node.rs diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs new file mode 100644 index 000000000..6f423ff59 --- /dev/null +++ b/packages/core/src/component.rs @@ -0,0 +1,58 @@ +use crate::inner::*; +use crate::prelude::bumpalo::Bump; + +/// The `Component` trait refers to any struct or funciton that can be used as a component +/// We automatically implement Component for FC +pub trait Component { + type Props: Properties; + fn builder(&'static self) -> Self::Props; +} + +// Auto implement component for a FC +// Calling the FC is the same as "rendering" it +impl Component for FC

{ + type Props = P; + + fn builder(&self) -> Self::Props { + todo!() + } +} + +/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields. +/// Components take a "properties" object +pub trait Properties: 'static { + fn new() -> Self; +} + +// Auto implement for no-prop components +impl Properties for () { + fn new() -> Self { + () + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_static_fn<'a, P: Properties>(b: &'a Bump, r: FC

) -> VNode<'a> { + todo!() + } + + fn test_component(ctx: Context<()>) -> VNode { + ctx.view(html! {

}) + } + + fn test_component2(ctx: Context<()>) -> VNode { + ctx.view(|bump: &Bump| VNode::text("blah")) + } + + #[test] + fn ensure_types_work() { + let bump = Bump::new(); + + // Happiness! The VNodes are now allocated onto the bump vdom + let _ = test_static_fn(&bump, test_component); + let _ = test_static_fn(&bump, test_component2); + } +} diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs new file mode 100644 index 000000000..fe6177c25 --- /dev/null +++ b/packages/core/src/debug_renderer.rs @@ -0,0 +1,35 @@ +//! Debug virtual doms! +//! This renderer comes built in with dioxus core and shows how to implement a basic renderer. +//! +//! Renderers don't actually need to own the virtual dom (it's up to the implementer). + +use crate::prelude::{Properties, VirtualDom}; + +pub struct DebugRenderer<'a, P: Properties> { + vdom: &'a mut VirtualDom

, +} + +impl<'a, P: Properties> DebugRenderer<'a, P> { + pub fn new(vdom: &'a mut VirtualDom

) -> Self { + Self { vdom } + } + + pub async fn run(&mut self) -> Result<(), ()> { + Ok(()) + } + + pub fn log_dom(&self) {} +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::prelude::*; + + #[test] + fn ensure_creation() -> Result<(), ()> { + let mut dom = VirtualDom::new(|ctx| ctx.view(html! {

"hello world"
})); + dom.progress()?; + Ok(()) + } +} diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs new file mode 100644 index 000000000..f3348ef6c --- /dev/null +++ b/packages/core/src/events.rs @@ -0,0 +1,26 @@ +//! Virtual Events +//! This module provides a wrapping of platform-specific events with a list of events easier to work with. +//! +//! 3rd party renderers are responsible for forming this virtual events from events +//! +//! The goal here is to provide a consistent event interface across all renderer types + +pub enum VirtualEvent { + ClipboardEvent, + CompositionEvent, + KeyboardEvent, + FocusEvent, + FormEvent, + GenericEvent, + MouseEvent, + PointerEvent, + SelectionEvent, + TouchEvent, + UIEvent, + WheelEvent, + MediaEvent, + ImageEvent, + AnimationEvent, + TransitionEvent, + OtherEvent, +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 03f4b8035..699febaa7 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -65,8 +65,13 @@ //! - dioxus-liveview (SSR + StringRenderer) //! +pub mod component; +pub mod context; +pub mod debug_renderer; +pub mod events; pub mod nodebuilder; pub mod nodes; +pub mod scope; pub mod validation; pub mod virtual_dom; @@ -74,33 +79,52 @@ pub mod builder { pub use super::nodebuilder::*; } -/// Re-export common types for ease of development use. -/// Essential when working with the html! macro -pub mod prelude { +// types used internally that are important +pub(crate) mod inner { + pub use crate::component::{Component, Properties}; use crate::nodes; - pub use crate::virtual_dom::{Context, VirtualDom}; + pub use crate::scope::{Context, Hook, Scope}; + pub use crate::virtual_dom::VirtualDom; pub use nodes::*; - // pub use nodes::iterables::IterableNodes; + // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; - // pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate pub type VirtualNode<'a> = VNode<'a>; - // Re-export from the macro crate - // pub use dodrio_derive::html; - - pub use bumpalo; - // pub use dioxus_html_macro::html; - // Re-export the FC macro + pub use crate as dioxus; + pub use crate::nodebuilder as builder; + pub use dioxus_core_macro::fc; + pub use dioxus_html_2::html; +} + +/// Re-export common types for ease of development use. +/// Essential when working with the html! macro +pub mod prelude { + pub use crate::component::{Component, Properties}; + use crate::nodes; + pub use crate::scope::Context; + pub use crate::virtual_dom::VirtualDom; + pub use nodes::*; + + // pub use nodes::iterables::IterableNodes; + /// This type alias is an internal way of abstracting over the static functions that represent components. + pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; + + // TODO @Jon, fix this + // hack the VNode type until VirtualNode is fixed in the macro crate + pub type VirtualNode<'a> = VNode<'a>; + + // expose our bumpalo type + pub use bumpalo; + + // Re-export the FC macro + pub use crate as dioxus; + pub use crate::nodebuilder as builder; pub use dioxus_core_macro::fc; pub use dioxus_html_2::html; - - pub use crate as dioxus; - - pub use crate::nodebuilder as builder; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9c16fd537..1e7c95f8a 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -330,7 +330,7 @@ mod vtext { /// Virtual Components for custom user-defined components /// Only supports the functional syntax mod vcomponent { - use crate::virtual_dom::Properties; + use crate::prelude::Properties; use std::{any::TypeId, fmt, future::Future}; use super::VNode; diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs new file mode 100644 index 000000000..5b5aa0d16 --- /dev/null +++ b/packages/core/src/scope.rs @@ -0,0 +1,225 @@ +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, +}; + +/// 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. +pub struct Scope { + arena: typed_arena::Arena, + hooks: RefCell>, + props_type: TypeId, + caller: *const i32, +} + +impl Scope { + // create a new scope from a function + pub fn new(f: FC) -> Self { + // Capture the props type + let props_type = TypeId::of::(); + let arena = typed_arena::Arena::new(); + let hooks = RefCell::new(Vec::new()); + + let caller = f as *const i32; + + Self { + arena, + hooks, + props_type, + caller, + } + } + + pub fn create_context(&mut self) -> Context { + Context { + _p: PhantomData {}, + arena: &self.arena, + hooks: &self.hooks, + idx: 0.into(), + props: T::new(), + } + } + + /// Create a new context and run the component with references from the Virtual Dom + /// This function downcasts the function pointer based on the stored props_type + fn run(&self, f: FC) {} + + fn call(&mut self, val: T) { + if self.props_type == TypeId::of::() { + /* + SAFETY ALERT + + This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html + We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called, + we transmute the function back using the props as reference. + + This is safe because we check that the generic type matches before casting. + */ + let caller = unsafe { std::mem::transmute::<*const i32, FC>(self.caller) }; + let ctx = self.create_context::(); + // TODO: do something with these nodes + let nodes = caller(ctx); + } else { + panic!("Do not try to use `call` on Scopes with the wrong props type") + } + } +} + +/// 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, +} + +impl Hook { + fn new(state: Box) -> Self { + Self { state } + } +} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 8fff4ff45..bfe28c4fb 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -21,34 +21,9 @@ static Component: FC = |ctx| { ctx.view(html! {
"hello world"
}) } ``` - - -This module includes all life-cycle related mechanics, including the virtual dom, scopes, properties, and lifecycles. ---- -The VirtualDom is designed as so: - -VDOM contains: - - An arena of component scopes. - - A scope contains - - lifecycle data - - hook data - - Event queue - - An event - -A VDOM is - - constructed from anything that implements "component" - -A "Component" is anything (normally functions) that can be ran with a context to produce VNodes - - Must implement properties-builder trait which produces a properties builder - -A Context - - Is a consumable struct - - Made of references to properties - - Holds a reference (lockable) to the underlying scope - - Is partially threadsafe */ +use crate::inner::*; use crate::nodes::VNode; -use crate::prelude::*; use any::Any; use bumpalo::Bump; use generational_arena::{Arena, Index}; @@ -67,15 +42,12 @@ pub struct VirtualDom { /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena. components: Arena, + /// The index of the root component. base_scope: Index, /// Components generate lifecycle events event_queue: Vec, - buffers: [Bump; 2], - - selected_buf: u8, - root_props: P, } @@ -98,12 +70,10 @@ impl VirtualDom

{ /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// to toss out the entire tree. pub fn new_with_props(root: FC

, root_props: P) -> Self { - // 1. Create the buffers - // 2. Create the component arena - // 3. Create the base scope (can never be removed) - // 4. Create the lifecycle queue - // 5. Create the event queue - let buffers = [Bump::new(), Bump::new()]; + // 1. Create the component arena + // 2. Create the base scope (can never be removed) + // 3. Create the lifecycle queue + // 4. Create the event queue // Arena allocate all the components // This should make it *really* easy to store references in events and such @@ -112,16 +82,17 @@ impl VirtualDom

{ // Create a reference to the component in the arena let base_scope = components.insert(Scope::new(root)); + // Create a new mount event with no root container + let first_event = LifecycleEvent::mount(base_scope, None, 0); + // Create an event queue with a mount for the base scope - let event_queue = vec![]; + let event_queue = vec![first_event]; Self { components, base_scope, event_queue, - buffers, root_props, - selected_buf: 0, } } @@ -133,9 +104,15 @@ impl VirtualDom

{ match event_type { // Component needs to be mounted to the virtual dom - LifecycleType::Mount {} => { + LifecycleType::Mount { to, under } => { // todo! run the FC with the bump allocator // Run it with its properties + if let Some(other) = to { + // mount to another component + let p = (); + } else { + // mount to the root + } } // The parent for this component generated new props and the component needs update @@ -150,10 +127,6 @@ impl VirtualDom

{ let f = self.components.remove(index); } - // Component was moved around in the DomTree - // Doesn't generate any event but interesting to keep track of - LifecycleType::Moved {} => {} - // Component was messaged via the internal subscription service LifecycleType::Messaged => {} } @@ -183,309 +156,18 @@ pub struct LifecycleEvent { pub event_type: LifecycleType, } impl LifecycleEvent { - fn mount(index: Index) -> Self { + fn mount(which: Index, to: Option, under: usize) -> Self { Self { - index, - event_type: LifecycleType::Mount, + index: which, + event_type: LifecycleType::Mount { to, under }, } } } /// The internal lifecycle event system is managed by these pub enum LifecycleType { - Mount, + Mount { to: Option, under: usize }, PropsChanged, Mounted, Removed, - Moved, Messaged, } - -/// The `Component` trait refers to any struct or funciton that can be used as a component -/// We automatically implement Component for FC -pub trait Component { - type Props: Properties; - fn builder(&'static self) -> Self::Props; -} - -// Auto implement component for a FC -// Calling the FC is the same as "rendering" it -impl Component for FC

{ - type Props = P; - - fn builder(&self) -> Self::Props { - todo!() - } -} - -/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields. -/// Components take a "properties" object -pub trait Properties { - fn new() -> Self; -} - -// Auto implement for no-prop components -impl Properties for () { - fn new() -> Self { - () - } -} - -// ============================================ -// Compile Tests for FC/Component/Properties -// ============================================ -#[cfg(test)] -mod fc_test { - use super::*; - use crate::prelude::*; - - // Make sure this function builds properly. - fn test_static_fn<'a, P: Properties>(b: &'a Bump, r: FC

) -> VNode<'a> { - todo!() - // let p = P::new(); // new props - // let c = Context { props: &p }; // new context with props - // let g = r(&c); // calling function with context - // g - } - - fn test_component(ctx: Context<()>) -> VNode { - // todo: helper should be part of html! macro - todo!() - // ctx.view(|bump| html! {bump,

}) - } - - fn test_component2(ctx: Context<()>) -> VNode { - ctx.view(|bump: &Bump| VNode::text("blah")) - } - // fn test_component2<'a>(ctx: &'a Context<()>) -> VNode<'a> { - // ctx.view(|bump: &Bump| VNode::text("blah")) - // } - - #[test] - fn ensure_types_work() { - // TODO: Get the whole casting thing to work properly. - // For whatever reason, FC is not auto-implemented, depsite it being a static type - let b = Bump::new(); - - // Happiness! The VNodes are now allocated onto the bump vdom - let nodes0 = test_static_fn(&b, test_component); - - let nodes1 = test_static_fn(&b, test_component2); - } -} - -/// 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. -pub struct Scope { - arena: typed_arena::Arena, - hooks: RefCell>, - props_type: TypeId, - caller: *const (), -} - -impl Scope { - // create a new scope from a function - pub fn new(f: FC) -> Self { - // Capture the props type - let props_type = TypeId::of::(); - let arena = typed_arena::Arena::new(); - let hooks = RefCell::new(Vec::new()); - - let caller = f as *const (); - - Self { - arena, - hooks, - props_type, - caller, - } - } - - pub fn create_context(&mut self) -> Context { - Context { - _p: PhantomData {}, - arena: &self.arena, - hooks: &self.hooks, - idx: 0.into(), - props: T::new(), - } - } - - /// Create a new context and run the component with references from the Virtual Dom - /// This function downcasts the function pointer based on the stored props_type - fn run(&self, f: FC) {} - - fn call(&mut self, val: T) { - if self.props_type == TypeId::of::() { - /* - SAFETY ALERT - - This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html - We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called, - we transmute the function back using the props as reference. - - This is safe because we check that the generic type matches before casting. - */ - let caller = unsafe { std::mem::transmute::<*const (), FC>(self.caller) }; - let ctx = self.create_context::(); - let nodes = caller(ctx); - } else { - panic!("Do not try to use `call` on Scopes with the wrong props type") - } - } -} - -/// 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, -} - -impl Hook { - fn new(state: Box) -> Self { - Self { state } - } -} - -/// A CallbackEvent wraps any event returned from the renderer's event system. -pub struct CallbackEvent {} - -pub struct EventListener {}