mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-12-04 01:49: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)]
|
#![allow(unused, non_upper_case_globals)]
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use dioxus_core::nodebuilder::*;
|
||||||
use dioxus_core::prelude::VNode;
|
use dioxus_core::prelude::VNode;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core::{nodebuilder::*, virtual_dom::Properties};
|
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
use once_cell::sync::{Lazy, OnceCell};
|
||||||
use std::{collections::HashMap, future::Future, marker::PhantomData};
|
use std::{collections::HashMap, future::Future, marker::PhantomData};
|
||||||
|
|
||||||
fn main() {}
|
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)]
|
#![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use dioxus_core::nodebuilder::*;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core::{nodebuilder::*, virtual_dom::Properties};
|
|
||||||
use std::{collections::HashMap, future::Future, marker::PhantomData};
|
use std::{collections::HashMap, future::Future, marker::PhantomData};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -9,7 +9,7 @@ fn main() {
|
||||||
component,
|
component,
|
||||||
Props {
|
Props {
|
||||||
blah: false,
|
blah: false,
|
||||||
text: "blah",
|
text: "blah".into(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -45,11 +45,12 @@ fn main() {
|
||||||
|
|
||||||
// ~~~ Text shared between components via props can be done with lifetimes! ~~~
|
// ~~~ Text shared between components via props can be done with lifetimes! ~~~
|
||||||
// Super duper efficient :)
|
// Super duper efficient :)
|
||||||
struct Props<'src> {
|
struct Props {
|
||||||
blah: bool,
|
blah: bool,
|
||||||
text: &'src str,
|
text: String,
|
||||||
|
// text: &'src str,
|
||||||
}
|
}
|
||||||
impl<'src> Properties for Props<'src> {
|
impl Properties for Props {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,7 @@ fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
|
||||||
div(bump)
|
div(bump)
|
||||||
.attr("class", "edit")
|
.attr("class", "edit")
|
||||||
.child(text("Hello"))
|
.child(text("Hello"))
|
||||||
.child(text(ctx.props.text))
|
.child(text(ctx.props.text.as_ref()))
|
||||||
.finish()
|
.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")
|
// 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
|
// We can "view" them with Context for ultimate speed while inside components
|
||||||
// use "phase" style allocation;
|
// 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! {
|
ctx.view(html! {
|
||||||
<div>
|
<div>
|
||||||
// your template goes here
|
// your template goes here
|
||||||
|
@ -129,6 +114,8 @@ fn FullySuspended<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|
||||||
// full suspended works great with just returning VNodes!
|
// 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 {
|
let tex = match i {
|
||||||
1 => html! { <div> </div> },
|
1 => html! { <div> </div> },
|
||||||
2 => html! { <div> </div> },
|
2 => html! { <div> </div> },
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::{
|
use dioxus_core::{
|
||||||
prelude::{html, Context, VElement, VNode, FC},
|
prelude::{html, Context, Properties, VElement, VNode, FC},
|
||||||
virtual_dom::{Properties, Scope},
|
scope::Scope,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
|
|
@ -5,17 +5,15 @@
|
||||||
//! render it again
|
//! render it again
|
||||||
//! consume the diffs and write that to a renderer
|
//! consume the diffs and write that to a renderer
|
||||||
|
|
||||||
use dioxus_core::{
|
use dioxus_core::{prelude::*, scope::Scope};
|
||||||
prelude::*,
|
|
||||||
virtual_dom::{Properties, Scope},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), ()> {
|
||||||
let mut scope = Scope::new(Example);
|
|
||||||
let ctx = scope.create_context::<Props>();
|
|
||||||
let p1 = Props { name: "bob".into() };
|
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 {
|
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)
|
//! - dioxus-liveview (SSR + StringRenderer)
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
pub mod component;
|
||||||
|
pub mod context;
|
||||||
|
pub mod debug_renderer;
|
||||||
|
pub mod events;
|
||||||
pub mod nodebuilder;
|
pub mod nodebuilder;
|
||||||
pub mod nodes;
|
pub mod nodes;
|
||||||
|
pub mod scope;
|
||||||
pub mod validation;
|
pub mod validation;
|
||||||
pub mod virtual_dom;
|
pub mod virtual_dom;
|
||||||
|
|
||||||
|
@ -74,33 +79,52 @@ pub mod builder {
|
||||||
pub use super::nodebuilder::*;
|
pub use super::nodebuilder::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-export common types for ease of development use.
|
// types used internally that are important
|
||||||
/// Essential when working with the html! macro
|
pub(crate) mod inner {
|
||||||
pub mod prelude {
|
pub use crate::component::{Component, Properties};
|
||||||
use crate::nodes;
|
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::*;
|
||||||
// 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.
|
/// 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>;
|
||||||
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
|
||||||
|
|
||||||
// TODO @Jon, fix this
|
// TODO @Jon, fix this
|
||||||
// hack the VNode type until VirtualNode is fixed in the macro crate
|
// hack the VNode type until VirtualNode is fixed in the macro crate
|
||||||
pub type VirtualNode<'a> = VNode<'a>;
|
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
|
// 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_core_macro::fc;
|
||||||
pub use dioxus_html_2::html;
|
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
|
/// Virtual Components for custom user-defined components
|
||||||
/// Only supports the functional syntax
|
/// Only supports the functional syntax
|
||||||
mod vcomponent {
|
mod vcomponent {
|
||||||
use crate::virtual_dom::Properties;
|
use crate::prelude::Properties;
|
||||||
use std::{any::TypeId, fmt, future::Future};
|
use std::{any::TypeId, fmt, future::Future};
|
||||||
|
|
||||||
use super::VNode;
|
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>})
|
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::nodes::VNode;
|
||||||
use crate::prelude::*;
|
|
||||||
use any::Any;
|
use any::Any;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use generational_arena::{Arena, Index};
|
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.
|
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||||
components: Arena<Scope>,
|
components: Arena<Scope>,
|
||||||
|
|
||||||
|
/// The index of the root component.
|
||||||
base_scope: Index,
|
base_scope: Index,
|
||||||
|
|
||||||
/// Components generate lifecycle events
|
/// Components generate lifecycle events
|
||||||
event_queue: Vec<LifecycleEvent>,
|
event_queue: Vec<LifecycleEvent>,
|
||||||
|
|
||||||
buffers: [Bump; 2],
|
|
||||||
|
|
||||||
selected_buf: u8,
|
|
||||||
|
|
||||||
root_props: P,
|
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
|
/// 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.
|
/// to toss out the entire tree.
|
||||||
pub fn new_with_props(root: FC<P>, root_props: P) -> Self {
|
pub fn new_with_props(root: FC<P>, root_props: P) -> Self {
|
||||||
// 1. Create the buffers
|
// 1. Create the component arena
|
||||||
// 2. Create the component arena
|
// 2. Create the base scope (can never be removed)
|
||||||
// 3. Create the base scope (can never be removed)
|
// 3. Create the lifecycle queue
|
||||||
// 4. Create the lifecycle queue
|
// 4. Create the event queue
|
||||||
// 5. Create the event queue
|
|
||||||
let buffers = [Bump::new(), Bump::new()];
|
|
||||||
|
|
||||||
// Arena allocate all the components
|
// Arena allocate all the components
|
||||||
// This should make it *really* easy to store references in events and such
|
// 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
|
// Create a reference to the component in the arena
|
||||||
let base_scope = components.insert(Scope::new(root));
|
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
|
// Create an event queue with a mount for the base scope
|
||||||
let event_queue = vec![];
|
let event_queue = vec![first_event];
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
components,
|
components,
|
||||||
base_scope,
|
base_scope,
|
||||||
event_queue,
|
event_queue,
|
||||||
buffers,
|
|
||||||
root_props,
|
root_props,
|
||||||
selected_buf: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,9 +104,15 @@ impl<P: Properties + 'static> VirtualDom<P> {
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
// Component needs to be mounted to the virtual dom
|
// Component needs to be mounted to the virtual dom
|
||||||
LifecycleType::Mount {} => {
|
LifecycleType::Mount { to, under } => {
|
||||||
// todo! run the FC with the bump allocator
|
// todo! run the FC with the bump allocator
|
||||||
// Run it with its properties
|
// 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
|
// 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);
|
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
|
// Component was messaged via the internal subscription service
|
||||||
LifecycleType::Messaged => {}
|
LifecycleType::Messaged => {}
|
||||||
}
|
}
|
||||||
|
@ -183,309 +156,18 @@ pub struct LifecycleEvent {
|
||||||
pub event_type: LifecycleType,
|
pub event_type: LifecycleType,
|
||||||
}
|
}
|
||||||
impl LifecycleEvent {
|
impl LifecycleEvent {
|
||||||
fn mount(index: Index) -> Self {
|
fn mount(which: Index, to: Option<Index>, under: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
index,
|
index: which,
|
||||||
event_type: LifecycleType::Mount,
|
event_type: LifecycleType::Mount { to, under },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The internal lifecycle event system is managed by these
|
/// The internal lifecycle event system is managed by these
|
||||||
pub enum LifecycleType {
|
pub enum LifecycleType {
|
||||||
Mount,
|
Mount { to: Option<Index>, under: usize },
|
||||||
PropsChanged,
|
PropsChanged,
|
||||||
Mounted,
|
Mounted,
|
||||||
Removed,
|
Removed,
|
||||||
Moved,
|
|
||||||
Messaged,
|
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