Feat: WIP ctx

This commit is contained in:
Jonathan Kelley committed 2021-02-20 21:59:16 -05:00
1 parent b3e6886351
commit 7a6aabe4f3
18 files changed
+430 -99

No files matched your search

+25 -22
View File
@@ -2,44 +2,47 @@
//! for components to be used within Nodes.
//!
use std::fmt::Debug;
use crate::innerlude::*;
/// 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;
}
// pub trait Component {
// type Props: Properties<'static>;
// 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;
// // Auto implement component for a FC
// // Calling the FC is the same as "rendering" it
// impl<P: Properties<'static>> Component for FC<P> {
// type Props = P;
fn builder(&self) -> Self::Props {
todo!()
}
}
// 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 // where
// Self: Sized,
{
fn call(&self, ptr: *const ()) {}
}
// pub trait Properties<'a>
// where
// Self: Debug,
// {
// fn call(&self, ptr: *const ()) {}
// }
// Auto implement for no-prop components
impl Properties for () {
fn call(&self, ptr: *const ()) {}
}
// // Auto implement for no-prop components
// impl<'a> Properties<'a> for () {
// fn call(&self, ptr: *const ()) {}
// }
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::bumpalo::Bump;
fn test_static_fn<'a, P: Properties>(b: &'a Bump, r: FC<P>) -> VNode<'a> {
fn test_static_fn<'a, P>(b: &'a Bump, r: FC<P>) -> VNode<'a> {
todo!()
}
+9 -5
View File
@@ -68,8 +68,12 @@ impl<'a> Context<'a> {
/// ctx.view(lazy_tree)
/// }
///```
pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
lazy_nodes(self.bump)
pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> DomTree {
// pub fn view(self, lazy_nodes: impl for<'b> FnOnce(&'b Bump) -> VNode<'b> + 'a + 'p) -> DomTree {
// pub fn view<'p>(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a + 'p) -> DomTree {
// pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
let g = lazy_nodes(self.bump);
DomTree {}
}
pub fn callback(&self, f: impl Fn(()) + 'static) {}
@@ -103,12 +107,12 @@ pub mod hooks {
/// TODO: @jon, rework this so we dont have to use unsafe to make hooks and then return them
/// use_hook provides a way to store data between renders for functional components.
/// todo @jon: ensure the hook arena is stable with pin or is stable by default
pub fn use_hook<'internal, 'scope, InternalHookState: 'static, Output: 'internal>(
pub fn use_hook<'scope, InternalHookState: 'static, Output: 'a>(
&'scope 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(&'internal mut InternalHookState) -> Output,
runner: impl FnOnce(&'a 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),
@@ -152,7 +156,7 @@ pub mod hooks {
- 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: &'internal mut _ = unsafe { raw_hook.as_mut().unwrap() };
let borrowed_hook: &'a mut _ = unsafe { raw_hook.as_mut().unwrap() };
let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
+1 -1
View File
@@ -3,7 +3,7 @@
//!
//! Renderers don't actually need to own the virtual dom (it's up to the implementer).
use crate::prelude::{Properties, VirtualDom};
use crate::prelude::VirtualDom;
pub struct DebugRenderer {
vdom: VirtualDom,
+3 -3
View File
@@ -40,8 +40,8 @@ mod use_state_def {
/// }
/// }
/// ```
pub fn use_state<'a, T: 'static, F: FnOnce() -> T + 'static>(
ctx: &'_ Context<'a>,
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
ctx: &'c Context<'a>,
initial_state_fn: F,
) -> (&'a T, &'a impl Fn(T)) {
ctx.use_hook(
@@ -93,7 +93,7 @@ mod use_ref_def {
}
}
fn modify(&self, modifier: impl FnOnce(&mut T)) {
pub fn modify(&self, modifier: impl FnOnce(&mut T)) {
let mut val = self._current.borrow_mut();
let val_as_ref = val.deref_mut();
modifier(val_as_ref);
+11 -3
View File
@@ -87,7 +87,7 @@ pub mod builder {
// types used internally that are important
pub(crate) mod innerlude {
pub(crate) use crate::component::{Component, Properties};
// pub(crate) use crate::component::Properties;
use crate::context::hooks::Hook;
pub(crate) use crate::context::Context;
pub(crate) use crate::error::{Error, Result};
@@ -99,7 +99,15 @@ pub(crate) mod innerlude {
// 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>, &'a P) -> VNode<'a>;
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
mod fc2 {
use super::*;
}
// pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'scope P) -> DomTree;
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
// TODO @Jon, fix this
@@ -116,7 +124,7 @@ pub(crate) mod innerlude {
/// 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};
// pub use crate::component::Properties;
pub use crate::context::Context;
use crate::nodes;
pub use crate::virtual_dom::VirtualDom;
+6
View File
@@ -1,6 +1,7 @@
//! Helpers for building virtual DOM VNodes.
use crate::{
innerlude::VComponent,
nodes::{Attribute, Listener, NodeKey, VNode},
prelude::VElement,
};
@@ -1079,3 +1080,8 @@ pub fn on<'a, 'b, F: 'static>(
callback: bump.alloc(callback),
}
}
pub fn virtual_child<'a, T>(bump: &'a Bump, props: T, f: crate::innerlude::FC<T>) -> VNode<'a> {
todo!()
// VNode::Component()
}
+25 -10
View File
@@ -12,6 +12,10 @@ pub use velement::{Attribute, Listener, NodeKey};
pub use vnode::VNode;
pub use vtext::VText;
/// A domtree represents the result of "Viewing" the context
/// It's a placeholder over vnodes, to make working with lifetimes easier
pub struct DomTree;
/// Tools for the base unit of the virtual dom - the VNode
/// VNodes are intended to be quickly-allocated, lightweight enum values.
///
@@ -20,6 +24,7 @@ pub use vtext::VText;
mod vnode {
use super::*;
#[derive(Debug)]
pub enum VNode<'src> {
/// An element node (node type `ELEMENT_NODE`).
Element(&'src VElement<'src>),
@@ -92,8 +97,9 @@ mod vnode {
mod velement {
use super::*;
use std::collections::HashMap;
use std::{collections::HashMap, fmt::Debug};
#[derive(Debug)]
pub struct VElement<'a> {
/// Elements have a tag name, zero or more attributes, and zero or more
pub key: NodeKey,
@@ -186,6 +192,13 @@ mod velement {
// /// The callback to invoke when the event happens.
pub(crate) callback: &'bump (dyn Fn(())),
}
impl Debug for Listener<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Listener")
.field("event", &self.event)
.finish()
}
}
/// The key for keyed children.
///
@@ -231,7 +244,7 @@ mod velement {
}
mod vtext {
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct VText<'bump> {
pub text: &'bump str,
}
@@ -251,28 +264,30 @@ mod vtext {
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
mod vcomponent {
use crate::innerlude::{Properties, FC};
use crate::innerlude::FC;
use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
use super::VNode;
#[derive(Debug)]
pub struct VComponent<'src> {
_p: PhantomData<&'src ()>,
props: Box<dyn Properties>,
props: Box<dyn std::any::Any>,
// props: Box<dyn Properties + 'src>,
caller: *const (),
}
impl<'a> VComponent<'a> {
pub fn new<P: Properties + 'static>(caller: FC<P>, props: P) -> Self {
pub fn new<P>(caller: FC<P>, props: P) -> Self {
let caller = caller as *const ();
let props = Box::new(props);
Self {
_p: PhantomData {},
props,
caller,
}
todo!()
// Self {
// _p: PhantomData {},
// props,
// caller,
// }
}
}
}
+164 -29
View File
@@ -33,39 +33,39 @@ pub struct Scope {
// Map to the parent
pub parent: Option<Index>,
// todo, do better with the active frame stuff
// somehow build this vnode with a lifetime tied to self
// This root node has "static" lifetime, but it's really not static.
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
// ! do not copy this reference are things WILL break !
pub frames: ActiveFrame,
// IE Which listeners need to be woken up?
pub listeners: Vec<Box<dyn Fn()>>,
// lying, cheating reference >:(
pub props: Box<dyn std::any::Any>,
// pub props: Box<dyn Properties>,
//
pub props_type: TypeId,
pub caller: *const i32,
// pub props_type: TypeId,
pub caller: *const (),
}
impl Scope {
// create a new scope from a function
pub fn new<T: 'static>(
// pub(crate) fn new<T: 'static>(
f: FC<T>,
props: impl Properties + 'static,
pub fn new<'a, P1, P2: 'static>(
// pub fn new<'a, P: Properties, PFree: P + 'a, PLocked: P + 'static>(
f: FC<P1>,
props: P1,
parent: Option<Index>,
) -> Self {
) -> Self
// where
// PFree: 'a,
// PLocked: 'static,
{
// Capture the props type
let props_type = TypeId::of::<T>();
// let props_type = TypeId::of::<P>();
let hook_arena = typed_arena::Arena::new();
let hooks = RefCell::new(Vec::new());
// Capture the caller
let caller = f as *const i32;
let caller = f as *const ();
let listeners = Vec::new();
@@ -80,12 +80,20 @@ impl Scope {
};
let frames = ActiveFrame::from_frames(old_frame, new_frame);
// box the props
let props = Box::new(props);
// erase the lifetime
// we'll manage this with dom lifecycle
let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
// todo!()
Self {
hook_arena,
hooks,
props_type,
// props_type,
caller,
frames,
listeners,
@@ -94,13 +102,10 @@ impl Scope {
}
}
/// Update this component's props with a new set of props
/// Update this component's props with a new set of props, remotely
///
///
pub(crate) fn update_props<P: Properties + Sized + 'static>(
&mut self,
new_props: Box<P>,
) -> crate::error::Result<()> {
pub(crate) fn update_props<'a, P>(&self, new_props: P) -> crate::error::Result<()> {
Ok(())
}
@@ -108,7 +113,7 @@ impl Scope {
/// This function downcasts the function pointer based on the stored props_type
///
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
pub fn run<'bump, P: Properties + Sized + 'static>(&'bump mut self) {
pub fn run<'bump, PLocked: Sized + 'static>(&'bump mut self) {
let frame = {
let frame = self.frames.next();
frame.bump.reset();
@@ -123,6 +128,18 @@ impl Scope {
_p: PhantomData {},
};
unsafe {
// we use plocked to be able to remove the borrowed lifetime
// these lifetimes could be very broken, so we need to dynamically manage them
let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller);
let props = self.props.downcast_ref::<PLocked>().unwrap();
let nodes: DomTree = caller(ctx, props);
todo!("absorb domtree into self")
// let nodes: VNode<'bump> = caller(ctx, props);
// let unsafe_node = std::mem::transmute::<VNode<'bump>, VNode<'static>>(nodes);
// frame.head_node = unsafe_node;
}
/*
SAFETY ALERT
@@ -135,9 +152,6 @@ impl Scope {
This is safe because we check that the generic type matches before casting.
*/
let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
let nodes: VNode<'bump> = caller(ctx, self.props.downcast_ref::<P>().unwrap());
/*
SAFETY ALERT
@@ -149,9 +163,6 @@ impl Scope {
- The VNode has a private API and can only be used from accessors.
- Public API cannot drop or destructure VNode
*/
let unsafe_node = unsafe { std::mem::transmute::<VNode<'bump>, VNode<'static>>(nodes) };
frame.head_node = unsafe_node;
}
/// Accessor to get the root node and its children (safely)\
@@ -159,6 +170,7 @@ impl Scope {
pub fn current_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node()
}
pub fn prev_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
todo!()
}
@@ -169,6 +181,12 @@ pub struct BumpFrame {
pub head_node: VNode<'static>,
}
// todo, do better with the active frame stuff
// somehow build this vnode with a lifetime tied to self
// This root node has "static" lifetime, but it's really not static.
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
// ! do not copy this reference are things WILL break !
pub struct ActiveFrame {
pub idx: AtomicUsize,
pub frames: [BumpFrame; 2],
@@ -183,7 +201,7 @@ impl ActiveFrame {
}
fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
let cur_idx = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
let cur_idx = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed) % 1;
let raw_node = &self.frames[cur_idx];
unsafe {
let unsafe_head = &raw_node.head_node;
@@ -202,3 +220,120 @@ impl ActiveFrame {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scope() {
let example: FC<()> = |ctx, props| {
use crate::builder::*;
ctx.view(|b| div(b).child(text("a")).finish())
};
let props = ();
let parent = None;
let scope = Scope::new::<(), ()>(example, props, parent);
}
#[derive(Debug)]
struct ExampleProps<'src> {
name: &'src String,
}
// impl<'src> Properties<'src> for ExampleProps<'src> {}
#[derive(Debug)]
struct EmptyProps<'src> {
name: &'src String,
}
// impl<'src> Properties<'src> for EmptyProps<'src> {}
use crate::{builder::*, hooks::use_ref};
fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps) -> DomTree {
// fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps<'a>) -> DomTree {
let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
// let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
// let (text, set_val) = crate::hooks::use_state(&ctx, || "abcd".to_string());
let childprops: ExampleProps<'a> = ExampleProps { name: content };
// let childprops: ExampleProps<'a> = ExampleProps { name: content };
ctx.view(move |b: &'a Bump| {
div(b)
.child(text(props.name))
// .child(text(props.name))
.child(virtual_child::<ExampleProps>(b, childprops, child_example))
// .child(virtual_child::<ExampleProps<'a>>(b, childprops, CHILD))
// as for<'scope> fn(Context<'_>, &'scope ExampleProps<'scope>) -> DomTree
// |ctx, pops| todo!(),
// .child(virtual_child::<'a>(
// b,
// child_example,
// ExampleProps { name: text },
// ))
.finish()
})
}
fn child_example(ctx: Context, props: &ExampleProps) -> DomTree {
ctx.view(move |b| {
div(b)
.child(text(props.name))
//
.finish()
})
}
static CHILD: FC<ExampleProps> = |ctx, props: &'_ ExampleProps| {
// todo!()
ctx.view(move |b| {
div(b)
.child(text(props.name))
//
.finish()
})
};
#[test]
fn test_borrowed_scope() {
// use crate::builder::*;
let example: FC<EmptyProps> = |ctx, props| {
// render counter
// let mut val = crate::hooks::use_ref(&ctx, || 0);
// val.modify(|f| {
// *f += 1;
// });
// dbg!(val.current());
// only needs to be valid when ran?
// can only borrow from parent?
// props are boxed in parent's scope?
// passing complex structures down to child?
// stored value
// let (text, set_val) = crate::hooks::use_state(&ctx, || "abcd".to_string());
ctx.view(move |b| {
todo!()
// div(b)
// // .child(text(props.name))
// // .child(virtual_child(b, CHILD, ExampleProps { name: val.as_str() }))
// .child(virtual_child(b, CHILD, ExampleProps { name: }))
// .finish()
})
};
let source_text = "abcd123".to_string();
let props = ExampleProps { name: &source_text };
// let parent = None;
// let mut scope =
// Scope::new::<EmptyProps, EmptyProps>(example, EmptyProps { name: &() }, parent);
// scope.run::<ExampleProps>();
// scope.run::<ExampleProps>();
// scope.run::<ExampleProps>();
// scope.run::<ExampleProps>();
// scope.run::<ExampleProps>();
// let nodes = scope.current_root_node();
// dbg!(nodes);
}
}
+10 -11
View File
@@ -1,5 +1,9 @@
// use crate::{changelist::EditList, nodes::VNode};
use crate::{changelist::{self, EditList}, dodriodiff::DiffMachine, nodes::VNode};
use crate::{
changelist::{self, EditList},
dodriodiff::DiffMachine,
nodes::VNode,
};
use crate::{events::EventTrigger, innerlude::*};
use any::Any;
use bumpalo::Bump;
@@ -51,7 +55,7 @@ impl VirtualDom {
///
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
/// to toss out the entire tree.
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
// 1. Create the component arena
// 2. Create the base scope (can never be removed)
// 3. Create the lifecycle queue
@@ -82,7 +86,7 @@ impl VirtualDom {
}
/// With access to the virtual dom, schedule an update to the Root component's props
pub fn update_props<P: Properties + 'static>(&mut self, new_props: P) -> Result<()> {
pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<()> {
// Ensure the props match
if TypeId::of::<P>() != self._root_prop_type {
return Err(Error::WrongProps);
@@ -262,10 +266,10 @@ pub enum LifecycleType {
Mount {
to: Option<Index>,
under: usize,
props: Box<dyn Properties>,
props: Box<dyn std::any::Any>,
},
PropsChanged {
props: Box<dyn Properties>,
props: Box<dyn std::any::Any>,
},
Rendered,
Mounted,
@@ -279,12 +283,7 @@ pub enum LifecycleType {
impl LifecycleEvent {
// helper method for shortcutting to the enum type
// probably not necessary
fn mount<P: Properties + 'static>(
which: Index,
to: Option<Index>,
under: usize,
props: P,
) -> Self {
fn mount<P: 'static>(which: Index, to: Option<Index>, under: usize, props: P) -> Self {
Self {
component_index: which,
event_type: LifecycleType::Mount {