Feat: include the helper

This commit is contained in:
Jonathan Kelley committed 2021-02-03 02:26:04 -05:00
1 parent f2eec79fd4
commit 07341d2c65
22 files changed
+2990 -1311

No files matched your search

+278
View File
@@ -0,0 +1,278 @@
/*
The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
The Dioxus VDom uses the same underlying mechanics as Dodrio (double buffering, bump dom, etc).
Instead of making the allocator very obvious, we choose to parametrize over the DomTree trait. For our purposes,
the DomTree trait is simply an abstraction over a lazy dom builder, much like the iterator trait.
This means we can accept DomTree anywhere as well as return it. All components therefore look like this:
```ignore
function Component(ctx: Context<()>) -> impl DomTree {
html! {<div> "hello world" </div>}
}
```
It's not quite as sexy as statics, but there's only so much you can do. The goal is to get statics working with the FC macro,
so types don't get in the way of you and your component writing. Fortunately, this is all generic enough to be split out
into its own lib (IE, lazy loading wasm chunks by function (exciting stuff!))
```ignore
#[fc] // gets translated into a function.
static Component: FC = |ctx| {
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::nodes::VNode;
use crate::prelude::*;
use bumpalo::Bump;
use generational_arena::Arena;
use std::future::Future;
/// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
components: Arena<Scope>,
/// Components generate lifecycle events
event_queue: Vec<LifecycleEvent>,
buffers: [Bump; 2],
}
impl VirtualDom {
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
///
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
/// The root component can access things like routing in its context.
pub fn new<F: DomTree>(root: FC<(), F>) -> Self {
Self::new_with_props(root)
}
/// Start a new VirtualDom instance with a dependent props.
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
///
/// 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<P: Properties, F: DomTree>(root: FC<P, F>) -> Self {
Self {
components: Arena::new(),
event_queue: vec![],
buffers: [Bump::new(), Bump::new()],
}
}
/// Pop an event off the even queue and process it
pub fn progress_event() {}
}
/// The internal lifecycle event system is managed by these
/// All events need to be confused before swapping doms over
pub enum LifecycleEvent {
Add {},
}
/// Anything that takes a "bump" and returns VNodes is a "DomTree"
/// This is used as a "trait alias" for function return types to look less hair
pub trait DomTree {
fn render(self, b: &Bump) -> VNode;
}
/// Implement DomTree for the type returned by the html! macro.
/// This lets the caller of the static function evaluate the builder closure with its own bump.
/// It keeps components pretty and removes the need for the user to get too involved with allocation.
impl<F> DomTree for F
where
F: FnOnce(&Bump) -> VNode,
{
fn render(self, b: &Bump) -> VNode {
self(b)
}
}
/// This type alias is an internal way of abstracting over the static functions that represent components.
pub type FC<P: Properties, F: DomTree> = fn(&Context<P>) -> F;
/// A gross AF helper that connects the dots for lifetimes
/// sigh.. todo: move this into the html macro or core crates
/// https://github.com/rust-lang/rust/issues/41078
#[inline]
pub fn __domtree_helper(
arg: impl for<'a> FnOnce(&'a Bump) -> VNode<'a>,
) -> impl for<'a> FnOnce(&'a Bump) -> VNode<'a> {
arg
}
/// 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<'a, P: Properties, F: DomTree> Component for FC<P, F> {
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::*;
// // Make sure this function builds properly.
// fn test_static_fn<'a, P: Properties, F: DomTree>(b: &'a Bump, r: &FC<P, F>) -> VNode<'a> {
// let p = P::new(); // new props
// let c = Context { props: p }; // new context with props
// let g = r(&c); // calling function with context
// g.render(&b) // rendering closure with bump allocator
// }
// fn test_component(ctx: &Context<()>) -> impl DomTree {
// // todo: helper should be part of html! macro
// html! { <div> </div> }
// }
// fn test_component2(ctx: &Context<()>) -> impl DomTree {
// __domtree_helper(move |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();
// let g: FC<_, _> = test_component;
// let nodes0 = test_static_fn(&b, &g);
// // Happiness! The VNodes are now allocated onto the bump vdom
// let g: FC<_, _> = test_component2;
// let nodes1 = test_static_fn(&b, &g);
// }
}
/// 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 {
hook_idx: i32,
hooks: Vec<()>,
}
impl Scope {
fn new<T>() -> Self {
Self {
hook_idx: 0,
hooks: vec![],
}
}
}
pub struct HookState {}
/// 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>
/// }
/// }
/// ```
pub struct Context<T> {
// pub struct Context<'source, T> {
/// Direct access to the properties used to create this component.
pub props: T,
// pub props: &'source T,
}
impl<'a, T> Context<T> {
// 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");
|| {}
}
/// Create VNodes from macros that use efficient allcators
/// The VDOM is configured to use a special allocator for VNodes
pub fn view(&self, v: impl Fn(&'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!()
}
}