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 {}