mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-12-03 17:39:11 +00:00
Feat: move out scope into its own file
This commit is contained in:
parent
e939373616
commit
c9d95dd1dc
15 changed files with 447 additions and 482 deletions
23
packages/core/architecture.md
Normal file
23
packages/core/architecture.md
Normal file
|
@ -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
|
|
@ -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<P, F = fn(Context<P>) -> VNode> {
|
||||
// f: F,
|
||||
// _a: std::marker::PhantomData<(P, F)>, // cell: OnceCell<T>,
|
||||
// // init: Cell<Option<F>>
|
||||
// }
|
||||
// impl<P, F> VC<P, F> {
|
||||
// const fn new(init: F) -> VC<P, F> {
|
||||
// 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! {
|
||||
// <div>
|
||||
|
||||
// </div>
|
||||
// })
|
||||
// });
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
static BILL: Lazy<fn(Context<()>) -> String> = Lazy::new(|| {
|
||||
//
|
||||
|c| "BLAH".to_string()
|
||||
});
|
||||
|
||||
// struct FUNC<F = fn() -> 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<Props> = |ctx| {
|
||||
// // This block mimics that output of the html! macro
|
||||
|
||||
// DomTree::new(move |bump| {
|
||||
// // parse into RSX structures
|
||||
// // regurgetate as rust types
|
||||
|
||||
// // <div> "Child 1" "Child 2"</div>
|
||||
// div(bump)
|
||||
// .attr("class", "edit")
|
||||
// .child(text("Child 1"))
|
||||
// .child(text("Child 2"))
|
||||
// .finish()
|
||||
// })
|
||||
// };
|
||||
|
||||
// /*
|
||||
// source
|
||||
// |> c1 -> VNode
|
||||
// |> c2 -> VNode
|
||||
// |> c3 -> VNode
|
||||
// |> c4 -> VNode
|
||||
// */
|
||||
|
|
|
@ -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...
|
||||
<div> // node0
|
||||
<div> </div> // node1
|
||||
{// support some expression} // node 2
|
||||
</div>
|
||||
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! {
|
||||
<div>
|
||||
// your template goes here
|
||||
|
@ -129,6 +114,8 @@ fn FullySuspended<'a>(ctx: &'a Context<Props>) -> 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! { <div> </div> },
|
||||
2 => html! { <div> </div> },
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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::<Props>();
|
||||
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 {
|
||||
|
|
58
packages/core/src/component.rs
Normal file
58
packages/core/src/component.rs
Normal file
|
@ -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<T>
|
||||
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<P: Properties> Component for FC<P> {
|
||||
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<P>) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn test_component(ctx: Context<()>) -> VNode {
|
||||
ctx.view(html! {<div> </div> })
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
0
packages/core/src/context.rs
Normal file
0
packages/core/src/context.rs
Normal file
35
packages/core/src/debug_renderer.rs
Normal file
35
packages/core/src/debug_renderer.rs
Normal file
|
@ -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<P>,
|
||||
}
|
||||
|
||||
impl<'a, P: Properties> DebugRenderer<'a, P> {
|
||||
pub fn new(vdom: &'a mut VirtualDom<P>) -> 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! { <div>"hello world" </div> }));
|
||||
dom.progress()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
26
packages/core/src/events.rs
Normal file
26
packages/core/src/events.rs
Normal file
|
@ -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,
|
||||
}
|
|
@ -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<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
||||
// pub type FC<P> = 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<P> = 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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
225
packages/core/src/scope.rs
Normal file
225
packages/core/src/scope.rs
Normal file
|
@ -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<Hook>,
|
||||
hooks: RefCell<Vec<*mut Hook>>,
|
||||
props_type: TypeId,
|
||||
caller: *const i32,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
// create a new scope from a function
|
||||
pub fn new<T: 'static>(f: FC<T>) -> Self {
|
||||
// Capture the props type
|
||||
let props_type = TypeId::of::<T>();
|
||||
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<T: Properties>(&mut self) -> Context<T> {
|
||||
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<T: 'static>(&self, f: FC<T>) {}
|
||||
|
||||
fn call<T: Properties + 'static>(&mut self, val: T) {
|
||||
if self.props_type == TypeId::of::<T>() {
|
||||
/*
|
||||
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<T>>(self.caller) };
|
||||
let ctx = self.create_context::<T>();
|
||||
// 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<Props>) -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {ctx.props.name}" </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
// todo: force lifetime of source into T as a valid lifetime too
|
||||
// it's definitely possible, just needs some more messing around
|
||||
pub struct Context<'src, T> {
|
||||
/// Direct access to the properties used to create this component.
|
||||
pub props: T,
|
||||
pub idx: AtomicUsize,
|
||||
|
||||
// Borrowed from scope
|
||||
arena: &'src typed_arena::Arena<Hook>,
|
||||
hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
|
||||
// holder for the src lifetime
|
||||
// todo @jon remove this
|
||||
pub _p: std::marker::PhantomData<&'src ()>,
|
||||
}
|
||||
|
||||
impl<'a, T> Context<'a, T> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> Vec<VNode> {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
/// Access a parent context
|
||||
pub fn parent_context<C>(&self) -> C {
|
||||
todo!("Context API is not ready yet")
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn subscribe(&self) -> impl FnOnce() -> () {
|
||||
todo!("Subscription API is not ready yet");
|
||||
|| {}
|
||||
}
|
||||
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(ctx: Context<Props>) -> VNode {
|
||||
/// // Lazy assemble the VNode tree
|
||||
/// let lazy_tree = html! {<div>"Hello World"</div>};
|
||||
///
|
||||
/// // Actually build the tree and allocate it
|
||||
/// ctx.view(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Create a suspended component from a future.
|
||||
///
|
||||
/// When the future completes, the component will be renderered
|
||||
pub fn suspend(
|
||||
&self,
|
||||
fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// use_hook provides a way to store data between renders for functional components.
|
||||
pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
|
||||
&'comp self,
|
||||
// The closure that builds the hook state
|
||||
initializer: impl FnOnce() -> InternalHookState,
|
||||
// The closure that takes the hookstate and returns some value
|
||||
runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
|
||||
// The closure that cleans up whatever mess is left when the component gets torn down
|
||||
// TODO: add this to the "clean up" group for when the component is dropped
|
||||
cleanup: impl FnOnce(InternalHookState),
|
||||
) -> Output {
|
||||
let raw_hook = {
|
||||
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
// Mutate hook list if necessary
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
|
||||
// Initialize the hook by allocating it in the typed arena.
|
||||
// We get a reference from the arena which is owned by the component scope
|
||||
// This is valid because "Context" is only valid while the scope is borrowed
|
||||
if idx >= hooks.len() {
|
||||
let new_state = initializer();
|
||||
let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
|
||||
let hook = self.arena.alloc(Hook::new(boxed_state));
|
||||
|
||||
// Push the raw pointer instead of the &mut
|
||||
// A "poor man's OwningRef"
|
||||
hooks.push(hook);
|
||||
}
|
||||
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
*hooks.get(idx).unwrap()
|
||||
};
|
||||
|
||||
/*
|
||||
** UNSAFETY ALERT **
|
||||
Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
|
||||
|
||||
However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
|
||||
into the arena. During the first call of the function, we need to add the mutable reference given to us by
|
||||
the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
|
||||
when the component itself is deallocated.
|
||||
|
||||
This is okay because:
|
||||
- The lifetime of the component arena is tied to the lifetime of these raw hooks
|
||||
- Usage of the raw hooks is tied behind the Vec refcell
|
||||
- Output is static, meaning it can't take a reference to the data
|
||||
- We don't expose the raw hook pointer outside of the scope of use_hook
|
||||
- The reference is tied to context, meaning it can only be used while ctx is around to free it
|
||||
*/
|
||||
let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
||||
|
||||
let internal_state = borrowed_hook
|
||||
.state
|
||||
.downcast_mut::<InternalHookState>()
|
||||
.unwrap();
|
||||
|
||||
// todo: set up an updater with the subscription API
|
||||
let updater = ();
|
||||
|
||||
runner(internal_state, updater)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Hook {
|
||||
state: Box<dyn std::any::Any>,
|
||||
}
|
||||
|
||||
impl Hook {
|
||||
fn new(state: Box<dyn std::any::Any>) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
}
|
|
@ -21,34 +21,9 @@ static Component: FC = |ctx| {
|
|||
ctx.view(html! {<div> "hello world" </div>})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
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<P: Properties> {
|
|||
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||
components: Arena<Scope>,
|
||||
|
||||
/// The index of the root component.
|
||||
base_scope: Index,
|
||||
|
||||
/// Components generate lifecycle events
|
||||
event_queue: Vec<LifecycleEvent>,
|
||||
|
||||
buffers: [Bump; 2],
|
||||
|
||||
selected_buf: u8,
|
||||
|
||||
root_props: P,
|
||||
}
|
||||
|
||||
|
@ -98,12 +70,10 @@ impl<P: Properties + 'static> VirtualDom<P> {
|
|||
/// 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<P>, 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<P: Properties + 'static> VirtualDom<P> {
|
|||
// 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<P: Properties + 'static> VirtualDom<P> {
|
|||
|
||||
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<P: Properties + 'static> VirtualDom<P> {
|
|||
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<Index>, 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<Index>, 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<T>
|
||||
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<P: Properties> Component for FC<P> {
|
||||
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<P>) -> 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, <div> </div> })
|
||||
}
|
||||
|
||||
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<Hook>,
|
||||
hooks: RefCell<Vec<*mut Hook>>,
|
||||
props_type: TypeId,
|
||||
caller: *const (),
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
// create a new scope from a function
|
||||
pub fn new<T: 'static>(f: FC<T>) -> Self {
|
||||
// Capture the props type
|
||||
let props_type = TypeId::of::<T>();
|
||||
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<T: Properties>(&mut self) -> Context<T> {
|
||||
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<T: 'static>(&self, f: FC<T>) {}
|
||||
|
||||
fn call<T: Properties + 'static>(&mut self, val: T) {
|
||||
if self.props_type == TypeId::of::<T>() {
|
||||
/*
|
||||
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<T>>(self.caller) };
|
||||
let ctx = self.create_context::<T>();
|
||||
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<Props>) -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {ctx.props.name}" </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
// todo: force lifetime of source into T as a valid lifetime too
|
||||
// it's definitely possible, just needs some more messing around
|
||||
pub struct Context<'src, T> {
|
||||
/// Direct access to the properties used to create this component.
|
||||
pub props: T,
|
||||
pub idx: AtomicUsize,
|
||||
|
||||
// Borrowed from scope
|
||||
arena: &'src typed_arena::Arena<Hook>,
|
||||
hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
|
||||
// holder for the src lifetime
|
||||
// todo @jon remove this
|
||||
pub _p: std::marker::PhantomData<&'src ()>,
|
||||
}
|
||||
|
||||
impl<'a, T> Context<'a, T> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> Vec<VNode> {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
/// Access a parent context
|
||||
pub fn parent_context<C>(&self) -> C {
|
||||
todo!("Context API is not ready yet")
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn subscribe(&self) -> impl FnOnce() -> () {
|
||||
todo!("Subscription API is not ready yet");
|
||||
|| {}
|
||||
}
|
||||
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(ctx: Context<Props>) -> VNode {
|
||||
/// // Lazy assemble the VNode tree
|
||||
/// let lazy_tree = html! {<div>"Hello World"</div>};
|
||||
///
|
||||
/// // Actually build the tree and allocate it
|
||||
/// ctx.view(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Create a suspended component from a future.
|
||||
///
|
||||
/// When the future completes, the component will be renderered
|
||||
pub fn suspend(
|
||||
&self,
|
||||
fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// use_hook provides a way to store data between renders for functional components.
|
||||
pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
|
||||
&'comp self,
|
||||
// The closure that builds the hook state
|
||||
initializer: impl FnOnce() -> InternalHookState,
|
||||
// The closure that takes the hookstate and returns some value
|
||||
runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
|
||||
// The closure that cleans up whatever mess is left when the component gets torn down
|
||||
// TODO: add this to the "clean up" group for when the component is dropped
|
||||
cleanup: impl FnOnce(InternalHookState),
|
||||
) -> Output {
|
||||
let raw_hook = {
|
||||
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
// Mutate hook list if necessary
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
|
||||
// Initialize the hook by allocating it in the typed arena.
|
||||
// We get a reference from the arena which is owned by the component scope
|
||||
// This is valid because "Context" is only valid while the scope is borrowed
|
||||
if idx >= hooks.len() {
|
||||
let new_state = initializer();
|
||||
let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
|
||||
let hook = self.arena.alloc(Hook::new(boxed_state));
|
||||
|
||||
// Push the raw pointer instead of the &mut
|
||||
// A "poor man's OwningRef"
|
||||
hooks.push(hook);
|
||||
}
|
||||
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
*hooks.get(idx).unwrap()
|
||||
};
|
||||
|
||||
/*
|
||||
** UNSAFETY ALERT **
|
||||
Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
|
||||
|
||||
However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
|
||||
into the arena. During the first call of the function, we need to add the mutable reference given to us by
|
||||
the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
|
||||
when the component itself is deallocated.
|
||||
|
||||
This is okay because:
|
||||
- The lifetime of the component arena is tied to the lifetime of these raw hooks
|
||||
- Usage of the raw hooks is tied behind the Vec refcell
|
||||
- Output is static, meaning it can't take a reference to the data
|
||||
- We don't expose the raw hook pointer outside of the scope of use_hook
|
||||
- The reference is tied to context, meaning it can only be used while ctx is around to free it
|
||||
*/
|
||||
let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
||||
|
||||
let internal_state = borrowed_hook
|
||||
.state
|
||||
.downcast_mut::<InternalHookState>()
|
||||
.unwrap();
|
||||
|
||||
// todo: set up an updater with the subscription API
|
||||
let updater = ();
|
||||
|
||||
runner(internal_state, updater)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Hook {
|
||||
state: Box<dyn std::any::Any>,
|
||||
}
|
||||
|
||||
impl Hook {
|
||||
fn new(state: Box<dyn std::any::Any>) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
/// A CallbackEvent wraps any event returned from the renderer's event system.
|
||||
pub struct CallbackEvent {}
|
||||
|
||||
pub struct EventListener {}
|
||||
|
|
Loading…
Reference in a new issue