Feat: massive changes to definition of components

This change switches back to the original `ctx<props>` syntax for
commponents. This lets lifetime elision to remove the need to match
exactly which lifetime (props or ctx) gets  carried to the output. As
such, `Props` is currently required to be static. It *is* possible to
loosen this restriction, and will be done in the future, though only
through adding metadata about the props through the Props derive
macro. Implementing the IS_STATIC trait is unsafe, so the derive macro
will do it through some heuristics.

For now, this unlocks sharing vnodes from parents to children, enabling
pass-thru components, fragments, portals, etc.
This commit is contained in:
Jonathan Kelley committed 2021-06-01 18:33:15 -04:00
1 parent c1fd848f89
commit 508c560320
82 files changed
+692 -558

No files matched your search

+1 -1
View File
@@ -28,7 +28,7 @@ We have big goals for Dioxus. The final implementation must:
## Optimizations
- Support a pluggable allocation strategy that makes VNode creation **very** fast
- Support lazy DomTrees (ie DomTrees that are not actually created when the html! macro is used)
- Support lazy VNodes (ie VNodes that are not actually created when the html! macro is used)
- Support advanced diffing strategies (patience, Meyers, etc)
## Design Quirks
+14 -1
View File
@@ -2,7 +2,7 @@ fn main() {}
use dioxus_core::prelude::*;
static Example: FC<()> = |ctx, props| {
static Example: FC<()> = |ctx| {
ctx.render(dioxus_core::prelude::LazyNodes::new(move |ctx| {
let bump = ctx.bump();
dioxus::builder::ElementBuilder::new(ctx, "h1")
@@ -15,3 +15,16 @@ static Example: FC<()> = |ctx, props| {
.finish()
}))
};
struct Props {
text: String,
}
static Example2: FC<Props> = |ctx| {
ctx.render(dioxus_core::prelude::LazyNodes::new(move |__ctx| {
let bump = __ctx.bump();
dioxus::builder::ElementBuilder::new(__ctx, "h1")
.children([{ dioxus::builder::text3(bump, format_args!("{}", ctx.text)) }])
// .children([{ dioxus::builder::text3(bump, format_args!("hello")) }])
.finish()
}))
};
+5 -5
View File
@@ -21,12 +21,12 @@ struct ListItem {
age: u32,
}
fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
fn app(ctx: Context<Props>) -> VNode {
let (val, set_val) = use_state(&ctx, || 0);
ctx.render(dioxus::prelude::LazyNodes::new(move |c| {
let mut root = builder::ElementBuilder::new(c, "div");
for child in &props.items {
for child in &ctx.items {
// notice that the child directly borrows from our vec
// this makes lists very fast (simply views reusing lifetimes)
// <ChildItem item=child hanldler=setter />
@@ -55,11 +55,11 @@ struct ChildProps {
item_handler: Callback<i32>,
}
fn ChildItem<'a>(ctx: Context<'a>, props: &ChildProps) -> DomTree {
fn ChildItem<'a>(ctx: Context<ChildProps>) -> VNode {
ctx.render(rsx! {
div {
onclick: move |evt| (props.item_handler)(10)
h1 { "abcd123" }
// onclick: move |evt| (ctx.item_handler)(10)
h1 { "abcd123 {ctx.item.name}" }
h2 { "abcd123" }
div {
"abcd123"
+1 -41
View File
@@ -16,7 +16,7 @@ fn main() {
};
}
static Example: FC<()> = |ctx, props| {
static Example: FC<()> = |ctx| {
let nodes = ctx.children();
//
@@ -26,43 +26,3 @@ static Example: FC<()> = |ctx, props| {
}
}
};
#[derive(Clone, Copy)]
struct MyContext<'a, T> {
props: &'a T,
inner: &'a Scope,
}
impl<'a, T> MyContext<'a, T> {
fn children(&self) -> Vec<VNode<'a>> {
todo!()
}
pub fn render2<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
&self,
lazy_nodes: LazyNodes<'a, F>,
) -> VNode<'a> {
self.inner.render2(lazy_nodes)
}
}
impl<'a, T> Deref for MyContext<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.props
}
}
struct MyProps {
title: String,
}
fn example(scope: MyContext<MyProps>) -> VNode {
let childs = scope.children();
scope.inner.render2(rsx! {
div {
"{scope.title}"
{childs}
}
})
}
+4 -3
View File
@@ -10,19 +10,20 @@ struct Props {
}
#[allow(unused)]
static Example: FC<Props> = |ctx, props| {
static Example: FC<Props> = |cpt| {
todo!()
// let value = ctx.use_context(|c: &SomeContext| c.items.last().unwrap());
// ctx.render(LazyNodes::new(move |bump| {
// builder::ElementBuilder::new(bump, "button")
// .on("click", move |_| {
// println!("Value is {}", props.name);
// println!("Value is {}", ctx.name);
// println!("Value is {}", value.as_str());
// println!("Value is {}", *value);
// })
// .on("click", move |_| {
// println!("Value is {}", props.name);
// println!("Value is {}", ctx.name);
// })
// .finish()
// }))
+2 -2
View File
@@ -1,7 +1,7 @@
use dioxus_core::component::fc_to_builder;
use dioxus_core::prelude::*;
static BLAH: FC<()> = |ctx, _props| {
static BLAH: FC<()> = |ctx| {
let g = "asd".to_string();
ctx.render(rsx! {
div {
@@ -17,7 +17,7 @@ pub struct ExampleProps {
some_field: String,
}
static SomeComponent: FC<ExampleProps> = |ctx, _props| {
static SomeComponent: FC<ExampleProps> = |ctx| {
let blah = rsx! {
div {}
};
+15 -8
View File
@@ -10,14 +10,21 @@ fn main() {
.map(|f| f / 3);
}
static Example: FC<()> = |ctx, props| {
static Example: FC<()> = |ctx| {
let (name, set_name) = use_state(&ctx, || "...?");
ctx.render(html! {
<div>
<h1> "Hello, {name}" </h1>
<button onclick={move |_| set_name("jack")}> "jack!" </button>
<button onclick={move |_| set_name("jill")}> "jill!" </button>
</div>
})
ctx.render(rsx!(
div {
h1 { "Hello, {name}" }
// look ma - we can rsx! and html! together
{["jack", "jill"].iter().map(|f| html!(<button onclick={move |_| set_name(f)}> "{f}" </button>))}
}
))
};
pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a, P>(
ctx: &'a Context<'src, P>,
lazy_nodes: LazyNodes<'src, F>,
) -> VNode<'src> {
ctx.render(lazy_nodes)
}
+2 -2
View File
@@ -43,7 +43,7 @@ mod baller {
use super::*;
pub struct BallerProps {}
pub fn Baller(ctx: Context, props: &()) -> DomTree {
pub fn Baller(ctx: Context<()>) -> VNode {
todo!()
}
}
@@ -53,7 +53,7 @@ pub struct TallerProps {
a: &'static str,
}
pub fn Taller(ctx: Context, props: &TallerProps) -> DomTree {
pub fn Taller(ctx: Context<TallerProps>) -> VNode {
let b = true;
todo!()
}
+2 -2
View File
@@ -90,7 +90,7 @@ fn main() {}
// div(bump)
// .attr("class", "edit")
// .child(text("Hello"))
// .child(text(ctx.props.text.as_ref()))
// .child(text(ctx.ctx.text.as_ref()))
// .finish()
// })
// }
@@ -123,7 +123,7 @@ fn main() {}
// _ => html! { <div> </div> },
// };
// if ctx.props.blah {
// if ctx.ctx.blah {
// html! { <div> </div> }
// } else {
// tex
+41 -45
View File
@@ -7,23 +7,19 @@ use dioxus_core::prelude::*;
fn main() {}
static Header: FC<()> = |ctx, props| {
let inner = use_ref(&ctx, || 0);
static Header: FC<()> = |ctx| {
let inner = use_ref(ctx, || 0);
let handler1 = move || println!("Value is {}", inner.borrow());
ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| {
builder::ElementBuilder::new(nodectx, "div")
.child(VNode::Component(VComponent::new(
Bottom,
nodectx.bump().alloc(()),
None,
)))
.child(VNode::Component(VComponent::new(Bottom, (), None)))
.finish()
}))
};
static Bottom: FC<()> = |ctx, props| {
static Bottom: FC<()> = |ctx| {
ctx.render(html! {
<div>
<h1> "bruh 1" </h1>
@@ -32,7 +28,7 @@ static Bottom: FC<()> = |ctx, props| {
})
};
fn Top(ctx: Context, a: &str, b: &i32, c: &impl Fn()) -> DomTree {
fn Top(ctx: Context<()>) -> VNode {
ctx.render(html! {
<div>
<h1> "bruh 1" </h1>
@@ -124,44 +120,44 @@ fn try_test() {
// test_poc()
}
fn test_poc(ctx: Context) {
let b = Bump::new();
// fn test_poc(ctx: Context) {
// let b = Bump::new();
let h = Args {
a: CuOpt::Some("ASD"),
b: CuOpt::Some(123),
c: CuOpt::Some(|| {}),
// c: CuOpt::Some(b.alloc(|| {})),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
};
// let h = Args {
// a: CuOpt::Some("ASD"),
// b: CuOpt::Some(123),
// c: CuOpt::Some(|| {}),
// // c: CuOpt::Some(b.alloc(|| {})),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// };
let h2 = Args {
a: CuOpt::Some("ASD"),
b: CuOpt::Some(123),
c: CuOpt::Some(|| {}),
// c: CuOpt::Some(b.alloc(|| {})),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
};
// let h2 = Args {
// a: CuOpt::Some("ASD"),
// b: CuOpt::Some(123),
// c: CuOpt::Some(|| {}),
// // c: CuOpt::Some(b.alloc(|| {})),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// // c: CuOpt::Some(Box::new(|| {}) as Box<dyn Fn()>),
// };
// dbg!((&h.a).memo((&&h2.a)));
// dbg!((&h.b).memo((&&h2.b)));
// dbg!((&h.c).memo((&&h2.c)));
//
// ctx: Context
Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap());
}
// // dbg!((&h.a).memo((&&h2.a)));
// // dbg!((&h.b).memo((&&h2.b)));
// // dbg!((&h.c).memo((&&h2.c)));
// //
// // ctx: Context
// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap());
// }
fn test_realzies() {
let h = Args {
a: CuOpt::Some("ASD"),
b: CuOpt::Some(123),
c: CuOpt::Some(|| {}),
};
// fn test_realzies() {
// let h = Args {
// a: CuOpt::Some("ASD"),
// b: CuOpt::Some(123),
// c: CuOpt::Some(|| {}),
// };
let g = |ctx: Context| {
//
Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap())
};
}
// let g = |ctx: Context| {
// //
// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap())
// };
// }
+8 -8
View File
@@ -16,22 +16,22 @@ fn test() {
// let g: FC<ButtonProps> = CustomButton;
}
trait Render {
fn render(ctx: Context, props: &Self) -> DomTree;
trait Render: Sized {
fn render(ctx: Context<Self>) -> VNode;
}
// include as much as you might accept
struct Button<'a> {
onhover: Option<&'a dyn Fn()>,
struct Button {
onhover: Option<Box<dyn Fn()>>,
}
impl Render for Button<'_> {
fn render(ctx: Context, _props: &Self) -> DomTree {
impl Render for Button {
fn render(ctx: Context<Self>) -> VNode {
let _onfocus = move |_evt: ()| log::debug!("Focused");
// todo!()
ctx.render(rsx! {
button {
// ..props.attrs,
// ..ctx.attrs,
class: "abc123",
// style: { a: 2, b: 3, c: 4 },
onclick: move |_evt| {
@@ -47,7 +47,7 @@ impl Render for Button<'_> {
}
// #[fc]
// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> DomTree {}
// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> VNode {}
// h1 {
// tag: "type", abc: 123, class: "big small wide short",
+19 -1
View File
@@ -14,10 +14,28 @@ struct SomeProps {
name: String,
}
static Example: FC<SomeProps> = |ctx, _props| {
static Example: FC<SomeProps> = |ctx| {
ctx.render(html! {
<div>
<h1> "hello world!" </h1>
</div>
})
};
// #[test]
#[derive(PartialEq, Clone)]
struct MyStruct {
a: String,
}
fn check_before_to_owned() {
let new_str = MyStruct {
a: "asd".to_string(),
};
let out = town(&new_str);
}
fn town<T: ToOwned + PartialEq>(t: &T) -> T::Owned {
t.to_owned()
}
+1 -1
View File
@@ -3,7 +3,7 @@
//!
//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
//! that ensures compile-time required and optional fields on props.
//! that ensures compile-time required and optional fields on ctx.
use crate::innerlude::FC;
+3 -3
View File
@@ -15,7 +15,7 @@ impl DebugRenderer {
///
/// 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(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self {
pub fn new(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) -> Self {
Self::new_with_props(root, ())
}
@@ -24,7 +24,7 @@ impl DebugRenderer {
///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new_with_props<T: Properties + 'static>(
root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static,
root: impl for<'a> Fn(Context<'a, T>) -> VNode + 'static,
root_props: T,
) -> Self {
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
@@ -78,7 +78,7 @@ mod tests {
#[test]
fn ensure_creation() -> Result<(), ()> {
// static Example: FC<()> = |ctx, props| {
// static Example: FC<()> = |ctx| {
// //
// ctx.render(html! { <div> "hello world" </div> })
// };
+4 -4
View File
@@ -65,22 +65,22 @@ pub struct DiffMachine<'a> {
}
pub enum LifeCycleEvent<'a> {
Mount {
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
stable_scope_addr: Weak<VCompAssociatedScope>,
root_id: u32,
},
PropsChanged {
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
stable_scope_addr: Weak<VCompAssociatedScope>,
root_id: u32,
},
SameProps {
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
stable_scope_addr: Weak<VCompAssociatedScope>,
root_id: u32,
},
Replace {
caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
old_scope: Weak<VCompAssociatedScope>,
new_scope: Weak<VCompAssociatedScope>,
root_id: u32,
+11 -11
View File
@@ -48,8 +48,8 @@ mod use_state_def {
/// }
/// }
/// ```
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
ctx: &'c Context<'a>,
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P: 'static>(
ctx: &'c Context<'a, P>,
initial_state_fn: F,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
ctx.use_hook(
@@ -169,8 +169,8 @@ mod new_use_state_def {
/// }
/// }
/// ```
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
ctx: &'c Context<'a>,
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T, P: 'static>(
ctx: &'c Context<'a, P>,
initial_state_fn: F,
) -> &'a UseState<T> {
ctx.use_hook(
@@ -246,8 +246,8 @@ mod use_ref_def {
/// To change it, use modify.
/// Modifications to this value do not cause updates to the component
/// Attach to inner context reference, so context can be consumed
pub fn use_ref<'a, T: 'static>(
ctx: Context<'a>,
pub fn use_ref<'a, T: 'static, P>(
ctx: Context<'a, P>,
initial_state_fn: impl FnOnce() -> T + 'static,
) -> &'a RefCell<T> {
ctx.use_hook(|| RefCell::new(initial_state_fn()), |state| &*state, |_| {})
@@ -270,11 +270,11 @@ mod use_reducer_def {
///
/// This is behaves almost exactly the same way as React's "use_state".
///
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static>(
ctx: &'c Context<'a>,
pub fn use_reducer<'a, 'c, State: 'static, Action: 'static, P: 'static>(
ctx: &'c Context<'a, P>,
initial_state_fn: impl FnOnce() -> State,
_reducer: impl Fn(&mut State, Action),
) -> (&'a State, &'a impl Fn(Action)) {
) -> (&'a State, &'a Box<dyn Fn(Action)>) {
ctx.use_hook(
move || UseReducer {
new_val: Rc::new(RefCell::new(None)),
@@ -320,7 +320,7 @@ mod use_reducer_def {
}
// #[allow(unused)]
// static Example: FC<()> = |ctx, props| {
// static Example: FC<()> = |ctx| {
// let (count, reduce) = use_reducer(
// &ctx,
// || 0,
@@ -347,7 +347,7 @@ mod use_reducer_def {
}
}
pub fn use_is_initialized(ctx: Context) -> bool {
pub fn use_is_initialized<P>(ctx: Context<P>) -> bool {
let val = use_ref(ctx, || false);
match *val.borrow() {
true => true,
+5 -5
View File
@@ -16,8 +16,8 @@
//! #[derive(Properties)]
//! struct Props { name: String }
//!
//! fn Example(ctx: Context, props: &Props) -> DomTree {
//! html! { <div> "Hello {props.name}!" </div> }
//! fn Example(ctx: Context, props: &Props) -> VNode {
//! html! { <div> "Hello {ctx.name}!" </div> }
//! }
//! ```
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
@@ -31,8 +31,8 @@
//! #[derive(Properties)]
//! struct Props { name: String }
//!
//! static Example: FC<Props> = |ctx, props| {
//! html! { <div> "Hello {props.name}!" </div> }
//! static Example: FC<Props> = |ctx| {
//! html! { <div> "Hello {ctx.name}!" </div> }
//! }
//! ```
//!
@@ -101,7 +101,7 @@ pub(crate) mod innerlude {
pub use crate::patch::*;
pub use crate::virtual_dom::*;
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
pub type FC<P> = fn(Context<P>) -> VNode;
// Re-export the FC macro
pub use crate as dioxus;
+36 -28
View File
@@ -4,7 +4,7 @@ use std::{any::Any, borrow::BorrowMut, intrinsics::transmute, u128};
use crate::{
events::VirtualEvent,
innerlude::{DomTree, Properties, VComponent, FC},
innerlude::{Properties, VComponent, FC},
nodes::{Attribute, Listener, NodeKey, VNode},
prelude::VElement,
virtual_dom::NodeCtx,
@@ -477,7 +477,7 @@ where
/// .iter_child((0..10).map(|f| span(&b).finish())
/// .finish();
/// ```
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoDomTree<'a>>) -> Self {
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
for item in nodes {
let child = item.into_vnode(&self.ctx);
self.children.push(child);
@@ -486,13 +486,13 @@ where
}
}
impl IntoIterator for DomTree {
type Item = DomTree;
type IntoIter = std::iter::Once<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
std::iter::once(self)
}
}
// impl IntoIterator for VNode {
// type Item = VNode;
// type IntoIter = std::iter::Once<Self::Item>;
// fn into_iter(self) -> Self::IntoIter {
// std::iter::once(self)
// }
// }
impl<'a> IntoIterator for VNode<'a> {
type Item = VNode<'a>;
@@ -501,23 +501,30 @@ impl<'a> IntoIterator for VNode<'a> {
std::iter::once(self)
}
}
impl<'a> IntoDomTree<'a> for VNode<'a> {
impl<'a> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
self
}
}
pub trait IntoDomTree<'a> {
impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
// cloning is cheap since vnodes are just references into bump arenas
self.clone()
}
}
pub trait IntoVNode<'a> {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a>;
}
pub trait DomTreeBuilder<'a, G>: IntoIterator<Item = G>
pub trait VNodeBuilder<'a, G>: IntoIterator<Item = G>
where
G: IntoDomTree<'a>,
G: IntoVNode<'a>,
{
}
impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a
{
}
@@ -527,15 +534,15 @@ impl<'a, F> DomTreeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
// ----
// let nodes = ctx.render(rsx!{ ... };
// rsx! { {nodes } }
impl<'a> IntoDomTree<'a> for DomTree {
fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
self.root
}
}
// impl<'a> IntoVNode<'a> for VNode {
// fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
// self.root
// }
// }
// Wrap the the node-builder closure in a concrete type.
// ---
// This is a bit of a hack to implement the IntoDomTree trait for closure types.
// This is a bit of a hack to implement the IntoVNode trait for closure types.
pub struct LazyNodes<'a, G>
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
@@ -561,7 +568,7 @@ where
// ---
// let nodes = rsx!{ ... };
// rsx! { {nodes } }
impl<'a, G> IntoDomTree<'a> for LazyNodes<'a, G>
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
where
G: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
{
@@ -581,13 +588,13 @@ where
std::iter::once(self)
}
}
impl<'a> IntoDomTree<'a> for () {
impl<'a> IntoVNode<'a> for () {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
VNode::Suspended
}
}
impl<'a> IntoDomTree<'a> for Option<()> {
impl<'a> IntoVNode<'a> for Option<()> {
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
VNode::Suspended
}
@@ -650,15 +657,16 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
Attribute { name, value }
}
pub fn virtual_child<'a, T: Properties + 'a>(
pub fn virtual_child<'a, T: Properties>(
ctx: &NodeCtx<'a>,
f: FC<T>,
p: T,
props: T,
key: Option<&'a str>, // key: NodeKey<'a>,
) -> VNode<'a> {
// currently concerned about if props have a custom drop implementation
// might override it with the props macro
let bump = &ctx.bump();
let propsd: &'a mut _ = bump.alloc(p);
VNode::Component(crate::nodes::VComponent::new(f, propsd, key))
VNode::Component(
ctx.bump()
.alloc(crate::nodes::VComponent::new(f, props, key)),
)
}
+61 -43
View File
@@ -5,17 +5,10 @@
use crate::{
events::VirtualEvent,
innerlude::{Context, Properties, ScopeIdx, FC},
innerlude::{Context, Properties, Scope, ScopeIdx, FC},
};
use bumpalo::Bump;
use std::{cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
/// A domtree represents the result of "Viewing" the context
/// It's a placeholder over vnodes, to make working with lifetimes easier
pub struct DomTree {
// this should *never* be publicly accessible to external
pub root: VNode<'static>,
}
use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
/// Tools for the base unit of the virtual dom - the VNode
/// VNodes are intended to be quickly-allocated, lightweight enum values.
@@ -35,7 +28,19 @@ pub enum VNode<'src> {
Suspended,
/// A User-defined componen node (node type COMPONENT_NODE)
Component(VComponent<'src>),
Component(&'src VComponent<'src>),
}
// it's okay to clone because vnodes are just references to places into the bump
impl<'a> Clone for VNode<'a> {
fn clone(&self) -> Self {
match self {
VNode::Element(el) => VNode::Element(el),
VNode::Text(origi) => VNode::Text(VText { text: origi.text }),
VNode::Suspended => VNode::Suspended,
VNode::Component(c) => VNode::Component(c),
}
}
}
impl<'a> VNode<'a> {
@@ -196,6 +201,12 @@ pub struct VText<'bump> {
pub text: &'bump str,
}
impl<'b> Clone for VText<'b> {
fn clone(&self) -> Self {
Self { text: self.text }
}
}
impl<'a> VText<'a> {
// / Create an new `VText` instance with the specified text.
pub fn new(text: &'a str) -> Self {
@@ -218,13 +229,14 @@ pub struct VComponent<'src> {
pub stable_addr: Rc<StableScopeAddres>,
pub ass_scope: Rc<VCompAssociatedScope>,
pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
pub caller: Rc<dyn Fn(Context) -> DomTree + 'src>,
// pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
pub caller: Rc<dyn Fn(&Scope) -> VNode + 'src>,
pub children: &'src [VNode<'src>],
// a pointer into the bump arena (given by the 'src lifetime)
raw_props: *const (),
raw_props: Box<dyn Any>,
// raw_props: *const (),
// a pointer to the raw fn typ
pub user_fc: *const (),
@@ -237,28 +249,30 @@ impl<'a> VComponent<'a> {
// - perform comparisons when diffing (memoization)
// TODO: lift the requirement that props need to be static
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
pub fn new<P: Properties + 'a>(component: FC<P>, props: &'a P, key: Option<&'a str>) -> Self {
pub fn new<P: Properties>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
let caller_ref = component as *const ();
let raw_props = props as *const P as *const ();
// let raw_props = props as *const P as *const ();
let props_comparator = move |other: &VComponent| {
// Safety:
// We are guaranteed that the props will be of the same type because
// there is no way to create a VComponent other than this `new` method.
//
// Therefore, if the render functions are identical (by address), then so will be
// props type paramter (because it is the same render function). Therefore, we can be
// sure
if caller_ref == other.user_fc {
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
real_other == props
} else {
false
}
};
// let props_comparator = move |other: &VComponent| {
// // Safety:
// // We are guaranteed that the props will be of the same type because
// // there is no way to create a VComponent other than this `new` method.
// //
// // Therefore, if the render functions are identical (by address), then so will be
// // props type paramter (because it is the same render function). Therefore, we can be
// // sure
// if caller_ref == other.user_fc {
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
// // let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
// &props == g
// } else {
// false
// }
// };
let caller = Rc::new(create_closure(component, raw_props));
let caller: Rc<dyn Fn(&Scope) -> VNode + 'a> = Rc::new(move |scp| todo!());
// let caller = Rc::new(create_closure(component, raw_props));
let key = match key {
Some(key) => NodeKey::new(key),
@@ -269,23 +283,27 @@ impl<'a> VComponent<'a> {
key,
ass_scope: Rc::new(RefCell::new(None)),
user_fc: caller_ref,
raw_props: props as *const P as *const _,
raw_props: Box::new(props),
_p: PhantomData,
children: &[],
caller,
comparator: Rc::new(props_comparator),
// comparator: Rc::new(props_comparator),
stable_addr: Rc::new(RefCell::new(None)),
}
}
}
fn create_closure<'a, P: Properties + 'a>(
component: FC<P>,
raw_props: *const (),
) -> impl for<'r> Fn(Context<'r>) -> DomTree + 'a {
move |ctx: Context| -> DomTree {
// cast back into the right lifetime
let safe_props: &'a P = unsafe { &*(raw_props as *const P) };
component(ctx, safe_props)
}
}
// fn create_closure<P: Properties>(
// component: FC<P>,
// raw_props: *const (),
// ) -> impl for<'r> Fn(&'r Scope) -> VNode<'r> {
// move |ctx| -> VNode {
// // cast back into the right lifetime
// let safe_props: &P = unsafe { &*(raw_props as *const P) };
// component(Context {
// props: safe_props,
// scope: ctx,
// })
// // component(ctx, safe_props)
// }
// }
+90 -69
View File
@@ -28,6 +28,7 @@ use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::Debug,
future::Future,
ops::Deref,
pin::Pin,
rc::{Rc, Weak},
};
@@ -53,7 +54,7 @@ pub struct VirtualDom {
#[doc(hidden)]
_root_caller: Rc<OpaqueComponent<'static>>,
/// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic.
/// Type of the original ctx. This is stored as TypeId so VirtualDom does not need to be generic.
///
/// Whenver props need to be updated, an Error will be thrown if the new props do not
/// match the props used to create the VirtualDom.
@@ -77,11 +78,11 @@ impl VirtualDom {
/// ```ignore
/// // Directly from a closure
///
/// let dom = VirtualDom::new(|ctx, _| ctx.render(rsx!{ div {"hello world"} }));
/// let dom = VirtualDom::new(|ctx| ctx.render(rsx!{ div {"hello world"} }));
///
/// // or pass in...
///
/// let root = |ctx, _| {
/// let root = |ctx| {
/// ctx.render(rsx!{
/// div {"hello world"}
/// })
@@ -90,17 +91,17 @@ impl VirtualDom {
///
/// // or directly from a fn
///
/// fn Example(ctx: Context, props: &()) -> DomTree {
/// fn Example(ctx: Context<()>) -> VNode {
/// ctx.render(rsx!{ div{"hello world"} })
/// }
///
/// let dom = VirtualDom::new(Example);
/// ```
pub fn new(root: impl Fn(Context, &()) -> DomTree + 'static) -> Self {
pub fn new(root: impl Fn(Context<()>) -> VNode + 'static) -> Self {
Self::new_with_props(root, ())
}
/// Start a new VirtualDom instance with a dependent props.
/// Start a new VirtualDom instance with a dependent ctx.
/// 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
@@ -109,11 +110,11 @@ impl VirtualDom {
/// ```ignore
/// // Directly from a closure
///
/// let dom = VirtualDom::new(|ctx, props| ctx.render(rsx!{ div {"hello world"} }));
/// let dom = VirtualDom::new(|ctx| ctx.render(rsx!{ div {"hello world"} }));
///
/// // or pass in...
///
/// let root = |ctx, props| {
/// let root = |ctx| {
/// ctx.render(rsx!{
/// div {"hello world"}
/// })
@@ -122,21 +123,27 @@ impl VirtualDom {
///
/// // or directly from a fn
///
/// fn Example(ctx: Context, props: &SomeProps) -> DomTree {
/// fn Example(ctx: Context, props: &SomeProps) -> VNode {
/// ctx.render(rsx!{ div{"hello world"} })
/// }
///
/// let dom = VirtualDom::new(Example);
/// ```
pub fn new_with_props<P: Properties + 'static>(
root: impl for<'a> Fn(Context<'a>, &'a P) -> DomTree + 'static,
root: impl Fn(Context<P>) -> VNode + 'static,
root_props: P,
) -> Self {
let components = ScopeArena::new(Arena::new());
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| root(ctx, &root_props));
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
todo!()
// root(Context {
// props: &root_props,
// scope: ctx,
// })
});
// Create a weak reference to the OpaqueComponent for the root scope to use as its render function
let caller_ref = Rc::downgrade(&_root_caller);
@@ -396,7 +403,7 @@ impl VirtualDom {
stable_scope_addr,
..
} => {
// In this case, the parent made a new DomTree that resulted in the same props for us
// In this case, the parent made a new VNode that resulted in the same props for us
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
log::debug!("Received the same props");
@@ -617,7 +624,8 @@ impl Scope {
)
}(&self);
self.frames.cur_frame_mut().head_node = new_head.root;
todo!();
// self.frames.cur_frame_mut().head_node = new_head;
Ok(())
}
@@ -676,41 +684,63 @@ impl Scope {
///
/// fn example(ctx: Context, props: &Props -> VNode {
/// html! {
/// <div> "Hello, {ctx.props.name}" </div>
/// <div> "Hello, {ctx.ctx.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 type Context<'src> = &'src Scope;
impl Scope {
pub struct Context<'src, T> {
pub props: &'src T,
pub scope: &'src Scope,
}
impl<'src, T> Copy for Context<'src, T> {}
impl<'src, T> Clone for Context<'src, T> {
fn clone(&self) -> Self {
Self {
props: self.props,
scope: self.scope,
}
}
}
impl<'a, T> Deref for Context<'a, T> {
type Target = &'a T;
fn deref(&self) -> &Self::Target {
&self.props
}
}
impl<'src, T> Context<'src, T> {
/// Access the children elements passed into the component
pub fn children(&self) -> &[VNode] {
pub fn children(&self) -> &'src [VNode<'src>] {
todo!("Children API not yet implemented for component Context")
}
/// Create a subscription that schedules a future render for the reference component
pub fn schedule_update(&self) -> impl Fn() -> () {
self.event_queue.schedule_update(&self)
pub fn schedule_update(&self) -> impl Fn() -> () + 'static {
self.scope.event_queue.schedule_update(&self.scope)
}
/// Create a suspended component from a future.
///
/// When the future completes, the component will be renderered
pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
&'a self,
_fut: impl Future<Output = LazyNodes<'a, F>>,
) -> VNode<'a> {
todo!()
}
// /// Create a suspended component from a future.
// ///
// /// When the future completes, the component will be renderered
// pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
// &'a self,
// _fut: impl Future<Output = LazyNodes<'a, F>>,
// ) -> VNode<'src> {
// todo!()
// }
}
// ================================================
// Render Implementation for Components
// ================================================
//
impl Scope {
impl<'src, T> Context<'src, T> {
// impl<'scope> Context<'scope> {
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
///
@@ -719,7 +749,7 @@ impl Scope {
/// ## Example
///
/// ```ignore
/// fn Component(ctx: Context, props: &()) -> VNode {
/// fn Component(ctx: Context<()>) -> VNode {
/// // Lazy assemble the VNode tree
/// let lazy_tree = html! {<div> "Hello World" </div>};
///
@@ -727,31 +757,22 @@ impl Scope {
/// ctx.render(lazy_tree)
/// }
///```
pub fn render<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
&'scope self,
lazy_nodes: LazyNodes<'scope, F>,
) -> DomTree {
pub fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>(
self,
// &'a/ self,
lazy_nodes: LazyNodes<'src, F>,
) -> VNode<'src> {
let ctx = NodeCtx {
scope_ref: self,
scope_ref: self.scope,
listener_id: 0.into(),
};
DomTree {
root: unsafe {
std::mem::transmute::<VNode<'scope>, VNode<'static>>(lazy_nodes.into_vnode(&ctx))
},
}
}
pub fn render2<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
&'scope self,
lazy_nodes: LazyNodes<'scope, F>,
) -> VNode<'scope> {
let ctx = NodeCtx {
scope_ref: self,
listener_id: 0.into(),
};
lazy_nodes.into_vnode(&ctx)
todo!()
// VNode {
// root: unsafe {
// std::mem::transmute::<VNode<'scope>, VNode<'static>>(lazy_nodes.into_vnode(&ctx))
// },
// }
}
}
@@ -762,7 +783,7 @@ impl Scope {
// We need to pin the hook so it doesn't move as we initialize the list of hooks
type Hook = Pin<Box<dyn std::any::Any>>;
impl Scope {
impl<'src, T> Context<'src, T> {
// impl<'scope> Context<'scope> {
/// Store a value between renders
///
@@ -775,28 +796,28 @@ impl Scope {
/// pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
/// use_hook(
/// || Rc::new(RefCell::new(initial_value())),
/// |state, _| state.clone(),
/// |state| state.clone(),
/// |_| {},
/// )
/// }
/// ```
pub fn use_hook<'scope, InternalHookState: 'static, Output: 'scope>(
&'scope self,
pub fn use_hook<InternalHookState: 'static, Output: 'src>(
&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(&'scope mut InternalHookState) -> Output,
runner: impl FnOnce(&'src 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 idx = *self.hookidx.borrow();
let idx = *self.scope.hookidx.borrow();
// Grab out the hook list
let mut hooks = self.hooks.borrow_mut();
let mut hooks = self.scope.hooks.borrow_mut();
// If the idx is the same as the hook length, then we need to add the current hook
if idx >= hooks.len() {
@@ -804,7 +825,7 @@ impl Scope {
hooks.push(Box::pin(new_state));
}
*self.hookidx.borrow_mut() += 1;
*self.scope.hookidx.borrow_mut() += 1;
let stable_ref = hooks
.get_mut(idx)
@@ -831,7 +852,7 @@ Any function prefixed with "use" should not be called conditionally.
// ================================================
// Context API Implementation for Components
// ================================================
impl Scope {
impl<'src, P> Context<'src, P> {
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
///
/// This is a hook, so it may not be called conditionally!
@@ -845,7 +866,7 @@ impl Scope {
///
///
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
let mut ctxs = self.shared_contexts.borrow_mut();
let mut ctxs = self.scope.shared_contexts.borrow_mut();
let ty = TypeId::of::<T>();
let is_initialized = self.use_hook(
@@ -873,12 +894,12 @@ impl Scope {
}
/// There are hooks going on here!
pub fn use_context<'a, T: 'static>(&'a self) -> &'a Rc<T> {
pub fn use_context<T: 'static>(&self) -> &'src Rc<T> {
self.try_use_context().unwrap()
}
/// Uses a context, storing the cached value around
pub fn try_use_context<T: 'static>(&self) -> Result<&Rc<T>> {
pub fn try_use_context<T: 'static>(&self) -> Result<&'src Rc<T>> {
struct UseContextHook<C> {
par: Option<Rc<C>>,
we: Option<Weak<C>>,
@@ -890,7 +911,7 @@ impl Scope {
we: None as Option<Weak<T>>,
},
move |hook| {
let mut scope = Some(self);
let mut scope = Some(self.scope);
if let Some(we) = &hook.we {
if let Some(re) = we.upgrade() {
@@ -942,7 +963,7 @@ impl Scope {
// We actually allocate the properties for components in their parent's properties
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a;
pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e;
#[derive(Debug, Default, Clone)]
pub struct EventQueue(pub(crate) Rc<RefCell<Vec<HeightMarker>>>);
@@ -978,7 +999,7 @@ impl PartialOrd for HeightMarker {
}
// NodeCtx is used to build VNodes in the component's memory space.
// This struct adds metadata to the final DomTree about listeners, attributes, and children
// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Clone)]
pub struct NodeCtx<'a> {
pub scope_ref: &'a Scope,
@@ -1100,7 +1121,7 @@ mod tests {
#[test]
fn simulate() {
let dom = VirtualDom::new(|ctx, props| {
let dom = VirtualDom::new(|ctx| {
//
ctx.render(rsx! {
div {
@@ -1122,7 +1143,7 @@ mod tests {
todo!()
}
let _ = check_send(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
let _ = check_send(VirtualDom::new(|ctx| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx| ctx.render(rsx! { div {}})));
}
}