From 0e9d5fc5306ab508d5af6999a4064f9b8b48460f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 29 Oct 2021 17:12:30 -0400 Subject: [PATCH 01/18] wip: move everything over to a stack dst --- examples/borrowed.rs | 8 +- examples/calculator.rs | 2 +- examples/framework_benchmark.rs | 4 +- examples/hello_world.rs | 2 +- examples/pattern_model.rs | 2 +- examples/rsx_usage.rs | 4 +- examples/ssr.rs | 2 +- examples/todomvc.rs | 2 +- examples/web_tick.rs | 2 +- packages/core-macro/src/rsx/body.rs | 4 +- packages/core/Cargo.toml | 6 +- packages/core/benches/jsframework.rs | 10 +-- packages/core/examples/alternative.rs | 2 +- packages/core/examples/borrowed.rs | 8 +- packages/core/examples/fragment_from_iter.rs | 4 +- packages/core/examples/jsframework.rs | 12 +-- packages/core/examples/syntax.rs | 5 ++ packages/core/examples/vdom_usage.rs | 2 +- packages/core/src/component.rs | 9 ++- packages/core/src/context.rs | 5 +- packages/core/src/hooks.rs | 18 +++-- packages/core/src/lib.rs | 9 ++- packages/core/src/nodes.rs | 77 ++++++++++++-------- packages/core/src/scope.rs | 33 +++++---- packages/core/src/test_dom.rs | 33 +++++---- packages/core/src/virtual_dom.rs | 40 +++++----- packages/core/tests/borrowedstate.rs | 18 ++--- packages/core/tests/create_dom.rs | 32 ++++---- packages/core/tests/diffing.rs | 4 +- packages/core/tests/display_vdom.rs | 8 +- packages/core/tests/lifecycle.rs | 2 +- packages/core/tests/sharedstate.rs | 4 +- packages/core/tests/vdom_rebuild.rs | 26 +++---- packages/desktop/examples/todomvc.rs | 2 +- packages/desktop/src/desktop_context.rs | 4 +- packages/web/examples/basic.rs | 4 +- 36 files changed, 224 insertions(+), 185 deletions(-) diff --git a/examples/borrowed.rs b/examples/borrowed.rs index e74254905..c9de40d89 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -20,7 +20,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -fn App((cx, props): Component<()>) -> DomTree { +fn App((cx, props): Component<()>) -> Element { let text: &mut Vec = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {}); let first = text.get_mut(0).unwrap(); @@ -43,7 +43,7 @@ impl<'a> Drop for C1Props<'a> { fn drop(&mut self) {} } -fn Child1<'a>((cx, props): Component<'a, C1Props>) -> DomTree<'a> { +fn Child1<'a>((cx, props): Component<'a, C1Props>) -> Element<'a> { let (left, right) = props.text.split_once("=").unwrap(); cx.render(rsx! { @@ -59,7 +59,7 @@ struct C2Props<'a> { text: &'a str, } -fn Child2<'a>((cx, props): Component<'a, C2Props>) -> DomTree<'a> { +fn Child2<'a>((cx, props): Component<'a, C2Props>) -> Element<'a> { cx.render(rsx! { Child3 { text: props.text @@ -72,7 +72,7 @@ struct C3Props<'a> { text: &'a str, } -fn Child3<'a>((cx, props): Component<'a, C3Props>) -> DomTree<'a> { +fn Child3<'a>((cx, props): Component<'a, C3Props>) -> Element<'a> { cx.render(rsx! { div { "{props.text}"} }) diff --git a/examples/calculator.rs b/examples/calculator.rs index deabb0d34..6d2d9b2ed 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -116,7 +116,7 @@ struct CalculatorKeyProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> DomTree<'a> { +fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> Element<'a> { rsx!(cx, button { class: "calculator-key {props.name}" onclick: {props.onclick} diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 5a5b2f8cc..b63b1e98d 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -80,7 +80,7 @@ struct ActionButtonProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn ActionButton<'a>((cx, props): Component<'a, ActionButtonProps>) -> DomTree<'a> { +fn ActionButton<'a>((cx, props): Component<'a, ActionButtonProps>) -> Element<'a> { rsx!(cx, div { class: "col-sm-6 smallpad" button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: {props.onclick}, "{props.name}" @@ -93,7 +93,7 @@ struct RowProps { row_id: usize, label: Rc, } -fn Row((cx, props): Component) -> DomTree { +fn Row((cx, props): Component) -> Element { rsx!(cx, tr { td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } diff --git a/examples/hello_world.rs b/examples/hello_world.rs index db9e22899..1115b7e60 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -4,7 +4,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -fn App((cx, props): Component<()>) -> DomTree { +fn App((cx, props): Component<()>) -> Element { cx.render(rsx! ( div { "Hello, world!" } )) diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index 1d046a76f..7c24d80de 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -76,7 +76,7 @@ struct CalculatorKeyProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> DomTree<'a> { +fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> Element<'a> { cx.render(rsx! { button { class: "calculator-key {props.name}" diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index f9c6a45a7..b14ced9e2 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -184,7 +184,7 @@ mod baller { pub struct BallerProps {} /// This component totally balls - pub fn Baller(_: Component) -> DomTree { + pub fn Baller(_: Component) -> Element { todo!() } } @@ -195,7 +195,7 @@ pub struct TallerProps { } /// This component is taller than most :) -pub fn Taller(_: Component) -> DomTree { +pub fn Taller(_: Component) -> Element { let b = true; todo!() } diff --git a/examples/ssr.rs b/examples/ssr.rs index 0991e53e4..2823e00fc 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -21,6 +21,6 @@ static App: FC<()> = |(cx, props)| { struct MyProps<'a> { text: &'a str, } -fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> DomTree<'a> { +fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> Element<'a> { None } diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 6a39220ef..f0da8d256 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -85,7 +85,7 @@ pub struct TodoEntryProps { todo: Rc, } -pub fn TodoEntry((cx, props): Component) -> DomTree { +pub fn TodoEntry((cx, props): Component) -> Element { let is_editing = use_state(cx, || false); let contents = use_state(cx, || String::from("")); let todo = &props.todo; diff --git a/examples/web_tick.rs b/examples/web_tick.rs index 5ff18adf5..f0c9d40df 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -45,7 +45,7 @@ struct RowProps { row_id: usize, label: Label, } -fn Row((cx, props): Component) -> DomTree { +fn Row((cx, props): Component) -> Element { let [adj, col, noun] = props.label.0; cx.render(rsx! { tr { diff --git a/packages/core-macro/src/rsx/body.rs b/packages/core-macro/src/rsx/body.rs index b5b487459..3e28e481e 100644 --- a/packages/core-macro/src/rsx/body.rs +++ b/packages/core-macro/src/rsx/body.rs @@ -57,11 +57,11 @@ impl ToTokens for CallBody { }), // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ + Some(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner - }) + })) }), }; } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 9c19720cf..7c3dadbdb 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -22,7 +22,9 @@ longest-increasing-subsequence = "0.1.0" # internall used log = { version = "0.4", features = ["release_max_level_off"] } -futures-util = { version = "0.3.15", no_default_features = true } +futures-util = { version = "0.3.15", default-features = false, features = [ + "std", +] } smallvec = "1.6.1" @@ -40,6 +42,8 @@ serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1.7", optional = true } +stack_dst = "0.6.1" + [dev-dependencies] anyhow = "1.0.42" dioxus-html = { path = "../html" } diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index 113df0b71..713d1fafa 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -34,13 +34,13 @@ fn create_rows(c: &mut Criterion) { } } }); - cx.render(rsx! { + rsx! { table { tbody { {rows} } } - }) + } }; c.bench_function("create rows", |b| { @@ -57,9 +57,9 @@ struct RowProps { row_id: usize, label: Label, } -fn Row((cx, props): Component) -> DomTree { +fn Row((cx, props): Component) -> Element { let [adj, col, noun] = props.label.0; - cx.render(rsx! { + rsx! { tr { td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } @@ -72,7 +72,7 @@ fn Row((cx, props): Component) -> DomTree { } td { class: "col-md-6" } } - }) + } } #[derive(PartialEq)] diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index edf037f89..3344aa4b2 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -9,7 +9,7 @@ fn main() { pub static EXAMPLE: FC<()> = |(cx, _)| { let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!())); - cx.render(LazyNodes::new(move |cx| { + Some(LazyNodes::new(move |cx| { cx.raw_element( "div", None, diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index a11f55225..f29159c8a 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -20,10 +20,10 @@ struct ListItem { age: u32, } -fn app<'a>(cx: Context<'a>, props: &AppProps) -> DomTree<'a> { +fn app<'a>(cx: Context<'a>, props: &AppProps) -> Element<'a> { // let (val, set_val) = use_state_classic(cx, || 0); - cx.render(LazyNodes::new(move |_nodecx| { + Some(LazyNodes::new(move |_nodecx| { todo!() // builder::ElementBuilder::new(_nodecx, "div") // .iter_child({ @@ -56,8 +56,8 @@ struct ChildProps { item_handler: Rc, } -fn ChildItem<'a>(cx: Context<'a>, props: &'a ChildProps) -> DomTree<'a> { - cx.render(LazyNodes::new(move |__cx| todo!())) +fn ChildItem<'a>(cx: Context<'a>, props: &'a ChildProps) -> Element<'a> { + Some(LazyNodes::new(move |__cx| todo!())) } impl PartialEq for ChildProps { diff --git a/packages/core/examples/fragment_from_iter.rs b/packages/core/examples/fragment_from_iter.rs index ae7d761e8..9cfd6cabe 100644 --- a/packages/core/examples/fragment_from_iter.rs +++ b/packages/core/examples/fragment_from_iter.rs @@ -2,7 +2,7 @@ use dioxus_core::prelude::*; fn main() {} -fn app<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> { +fn app<'a>(cx: Context<'a>, props: &()) -> Element<'a> { let vak = use_suspense( cx, || async {}, @@ -26,7 +26,7 @@ fn app<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> { ) })); - cx.render(LazyNodes::new(move |f| { + Some(LazyNodes::new(move |f| { f.raw_element( "div", None, diff --git a/packages/core/examples/jsframework.rs b/packages/core/examples/jsframework.rs index 2b2939a15..af698ec26 100644 --- a/packages/core/examples/jsframework.rs +++ b/packages/core/examples/jsframework.rs @@ -15,7 +15,7 @@ fn main() { assert!(g.edits.len() > 1); } -fn App((cx, props): Component<()>) -> DomTree { +fn App((cx, props): Component<()>) -> Element { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000_usize).map(|f| { let label = Label::new(&mut rng); @@ -26,13 +26,13 @@ fn App((cx, props): Component<()>) -> DomTree { } } }); - cx.render(rsx! { + rsx! { table { tbody { {rows} } } - }) + } } #[derive(PartialEq, Props)] @@ -41,11 +41,11 @@ struct RowProps { label: Label, } -fn Row((cx, props): Component) -> DomTree { +fn Row((cx, props): Component) -> Element { let handler = move |evt: MouseEvent| { let g = evt.button; }; - cx.render(rsx! { + rsx! { tr { td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } @@ -58,7 +58,7 @@ fn Row((cx, props): Component) -> DomTree { } td { class: "col-md-6" } } - }) + } } #[derive(PartialEq)] diff --git a/packages/core/examples/syntax.rs b/packages/core/examples/syntax.rs index bf695aa18..b2a8aeaf8 100644 --- a/packages/core/examples/syntax.rs +++ b/packages/core/examples/syntax.rs @@ -32,3 +32,8 @@ fn html_usage() { } }; } + +static App: FC<()> = |(cx, props)| { + // + rsx!(div {}) +}; diff --git a/packages/core/examples/vdom_usage.rs b/packages/core/examples/vdom_usage.rs index caf58faaf..cd381bf5a 100644 --- a/packages/core/examples/vdom_usage.rs +++ b/packages/core/examples/vdom_usage.rs @@ -4,7 +4,7 @@ use dioxus_core::prelude::*; #[async_std::main] async fn main() { - static App: FC<()> = |(cx, props)| cx.render(LazyNodes::new(|f| f.text(format_args!("hello")))); + static App: FC<()> = |(cx, props)| Some(LazyNodes::new(|f| f.text(format_args!("hello")))); let mut dom = VirtualDom::new(App); diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index d15aa2e39..901f4e4d6 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -5,7 +5,10 @@ //! if the type supports 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 cx. -use crate::innerlude::{Context, DomTree, LazyNodes, FC}; +use crate::{ + innerlude::{Context, Element, LazyNodes, FC}, + VNode, +}; /// A component is a wrapper around a Context and some Props that share a lifetime /// @@ -63,8 +66,8 @@ pub type Component<'a, T> = (Context<'a>, &'a T); /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment((cx, _): Component<()>) -> DomTree { - cx.render(LazyNodes::new(|f| f.fragment_from_iter(cx.children()))) +pub fn Fragment((cx, _): Component<()>) -> Element { + Some(LazyNodes::new(|f| f.fragment_from_iter(cx.children()))) } /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 2fd319f14..86d4749cb 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -137,10 +137,7 @@ impl<'src> Context<'src> { /// cx.render(lazy_tree) /// } ///``` - pub fn render) -> VNode<'src>>( - self, - lazy_nodes: LazyNodes<'src, F>, - ) -> DomTree<'src> { + pub fn render(self, lazy_nodes: LazyNodes<'src>) -> Option> { let bump = &self.scope.frames.wip_frame().bump; Some(lazy_nodes.into_vnode(NodeFactory { bump })) } diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 0e0d6de2a..0154dce3b 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -93,11 +93,11 @@ pub fn use_suspense<'src, Out, Fut, Cb>( cx: Context<'src>, task_initializer: impl FnOnce() -> Fut, user_callback: Cb, -) -> DomTree<'src> +) -> Element<'src> where Fut: Future + 'static, Out: 'static, - Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static, + Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> Element<'a> + 'static, { /* General strategy: @@ -135,12 +135,12 @@ where None => { let value = hook.value.clone(); - cx.render(LazyNodes::new(|f| { + Some(LazyNodes::new(|f| { let bump = f.bump(); use bumpalo::boxed::Box as BumpBox; - let f: &mut dyn FnMut(SuspendedContext<'src>) -> DomTree<'src> = + let f: &mut dyn FnMut(SuspendedContext<'src>) -> Element<'src> = bump.alloc(move |sus| { let val = value.borrow(); @@ -177,12 +177,14 @@ pub struct SuspendedContext<'a> { } impl<'src> SuspendedContext<'src> { - pub fn render) -> VNode<'src>>( + pub fn render( + // pub fn render) -> VNode<'src>>( self, - lazy_nodes: LazyNodes<'src, F>, - ) -> DomTree<'src> { + lazy_nodes: LazyNodes<'src>, + ) -> Element<'src> { let bump = &self.inner.scope.frames.wip_frame().bump; - Some(lazy_nodes.into_vnode(NodeFactory { bump })) + todo!("suspense") + // Some(lazy_nodes.into_vnode(NodeFactory { bump })) } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index c77aa50a0..02d6f8422 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -56,12 +56,13 @@ pub(crate) mod innerlude { pub use crate::util::*; pub use crate::virtual_dom::*; - pub type DomTree<'a> = Option>; - pub type FC

= for<'a> fn(Component<'a, P>) -> DomTree<'a>; + // pub type Element<'a> = Option>; + pub type Element<'a> = Option>; + pub type FC

= for<'a> fn(Component<'a, P>) -> Element<'a>; } pub use crate::innerlude::{ - Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType, + Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, }; @@ -70,7 +71,7 @@ pub mod prelude { pub use crate::component::{fc_to_builder, Component, Fragment, Properties}; pub use crate::context::Context; pub use crate::hooks::*; - pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, Mutations, NodeFactory, FC}; + pub use crate::innerlude::{DioxusElement, Element, LazyNodes, Mutations, NodeFactory, FC}; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 7fd7bccf0..142eab4b7 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::innerlude::{ - empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, SuspendedContext, FC, + empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, SuspendedContext, FC, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ @@ -13,6 +13,10 @@ use std::{ marker::PhantomData, }; +use stack_dst::Value as StackDST; + +pub type LazyNodeFactory<'a> = StackDST) -> VNode<'a>>; + /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. /// /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: @@ -287,7 +291,7 @@ pub struct VComponent<'src> { // Function pointer to the FC that was used to generate this component pub user_fc: *const (), - pub(crate) caller: &'src dyn for<'b> Fn(&'b Scope) -> DomTree<'b>, + pub(crate) caller: &'src dyn for<'b> Fn(&'b Scope) -> Element<'b>, pub(crate) children: &'src [VNode<'src>], @@ -306,7 +310,7 @@ pub struct VSuspended<'a> { pub dom_id: Cell>, #[allow(clippy::type_complexity)] - pub callback: RefCell) -> DomTree<'a>>>>, + pub callback: RefCell) -> Element<'a>>>>, } /// This struct provides an ergonomic API to quickly build VNodes. @@ -328,9 +332,10 @@ impl<'a> NodeFactory<'a> { self.bump } - pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> DomTree<'a> - where - F: FnOnce(NodeFactory<'a>) -> VNode<'a>, + pub fn render_directly(&self, lazy_nodes: LazyNodes<'a>) -> Option> +// pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> Element<'a> + // where + // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump })) } @@ -526,11 +531,11 @@ impl<'a> NodeFactory<'a> { let key = key.map(|f| self.raw_text(f).0); - let caller: &'a mut dyn for<'b> Fn(&'b Scope) -> DomTree<'b> = - bump.alloc(move |scope: &Scope| -> DomTree { + let caller: &'a mut dyn for<'b> Fn(&'b Scope) -> Element<'b> = + bump.alloc(move |scope: &Scope| -> Element { log::debug!("calling component renderr {:?}", scope.our_arena_idx); let props: &'_ P = unsafe { &*(raw_props as *const P) }; - let res = component((Context { scope }, props)); + let res: Element = component((Context { scope }, props)); unsafe { std::mem::transmute(res) } }); @@ -691,40 +696,43 @@ impl<'a> IntoVNode<'a> for VNode<'a> { /// ```rust /// LazyNodes::new(|f| f.element("div", [], [], [] None)) /// ``` -pub struct LazyNodes<'a, G> -where - G: FnOnce(NodeFactory<'a>) -> VNode<'a>, -{ - inner: G, - _p: PhantomData<&'a ()>, +pub struct LazyNodes<'a> { + inner: LazyNodeFactory<'a>, } +// where +// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, -impl<'a, G> LazyNodes<'a, G> -where - G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +impl<'a> LazyNodes<'a> +// where +// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, { - pub fn new(f: G) -> Self { - Self { - inner: f, - _p: PhantomData {}, - } + pub fn new(f: impl FnOnce(NodeFactory<'a>) -> VNode<'a>) -> Self { + todo!() + // pub fn new) -> VNode<'a>>(f: G) -> Self { + // let inner = LazyNodeFactory::new(f); + // Self { inner: f } } } // Our blanket impl -impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G> -where - G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +impl<'a> IntoVNode<'a> for LazyNodes<'a> +// where +// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - (self.inner)(cx) + // let f = self.inner; + todo!("manually drop here") + // (self.inner)(cx) } } // Our blanket impl -impl<'a, G> IntoIterator for LazyNodes<'a, G> -where - G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +impl<'a> IntoIterator for LazyNodes<'a> +// where +// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, +// impl<'a, G> IntoIterator for LazyNodes<'a, G> +// where +// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, { type Item = Self; type IntoIter = std::iter::Once; @@ -756,6 +764,15 @@ impl<'a> IntoVNode<'a> for Option> { } } +impl<'a> IntoVNode<'a> for Option> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { + match self { + Some(n) => n.into_vnode(cx), + None => cx.fragment_from_iter(None as Option), + } + } +} + impl IntoVNode<'_> for &'static str { fn into_vnode(self, cx: NodeFactory) -> VNode { cx.static_text(self) diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 9e66fe3f4..348e1b74b 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -28,7 +28,7 @@ pub struct Scope { // Nodes pub(crate) frames: ActiveFrame, - pub(crate) caller: *const dyn for<'b> Fn(&'b Scope) -> DomTree<'b>, + pub(crate) caller: *const dyn for<'b> Fn(&'b Scope) -> Element<'b>, pub(crate) child_nodes: ScopeChildren<'static>, /* @@ -176,7 +176,7 @@ impl Scope { // Scopes cannot be made anywhere else except for this file // Therefore, their lifetimes are connected exclusively to the virtual dom pub(crate) fn new( - caller: &dyn for<'b> Fn(&'b Scope) -> DomTree<'b>, + caller: &dyn for<'b> Fn(&'b Scope) -> Element<'b>, our_arena_idx: ScopeId, parent_idx: Option, height: u32, @@ -217,7 +217,7 @@ impl Scope { pub(crate) fn update_scope_dependencies<'creator_node>( &mut self, - caller: &'creator_node dyn for<'b> Fn(&'b Scope) -> DomTree<'b>, + caller: &'creator_node dyn for<'b> Fn(&'b Scope) -> Element<'b>, child_nodes: ScopeChildren, ) { log::debug!("Updating scope dependencies {:?}", self.our_arena_idx); @@ -317,7 +317,7 @@ impl Scope { let mut cb = sus.callback.borrow_mut().take().unwrap(); - let new_node: DomTree<'a> = (cb)(cx); + let new_node: Element<'a> = (cb)(cx); } } @@ -350,20 +350,25 @@ impl Scope { log::debug!("Borrowed stuff is successfully cleared"); // Cast the caller ptr from static to one with our own reference - let render: &dyn for<'b> Fn(&'b Scope) -> DomTree<'b> = unsafe { &*self.caller }; + let render: &dyn for<'b> Fn(&'b Scope) -> Element<'b> = unsafe { &*self.caller }; // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. - if let Some(new_head) = render(self) { - log::debug!("Render is successful"); + // + todo!() + // if let Some(builder) = render(self) { + // let new_head = builder.into_vnode(NodeFactory { + // bump: &self.frames.wip_frame().bump, + // }); + // log::debug!("Render is successful"); - // the user's component succeeded. We can safely cycle to the next frame - self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; - self.frames.cycle_frame(); + // // the user's component succeeded. We can safely cycle to the next frame + // self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; + // self.frames.cycle_frame(); - true - } else { - false - } + // true + // } else { + // false + // } } } diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index 7b94496c2..2f7977269 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -21,16 +21,16 @@ impl TestDom { NodeFactory::new(&self.bump) } - pub fn render_direct<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a> - where - F: FnOnce(NodeFactory<'a>) -> VNode<'a>, + pub fn render_direct<'a>(&'a self, lazy_nodes: Element<'a>) -> VNode<'a> +// where + // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) } - pub fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> &'a VNode<'a> - where - F: FnOnce(NodeFactory<'a>) -> VNode<'a>, + pub fn render<'a>(&'a self, lazy_nodes: Element<'a>) -> &'a VNode<'a> +// where + // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { self.bump .alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump))) @@ -43,9 +43,10 @@ impl TestDom { machine.mutations } - pub fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a> - where - F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, + pub fn create<'a>(&'a self, left: Element<'a>) -> Mutations<'a> +// pub fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a> + // where + // F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, { let old = self.bump.alloc(self.render_direct(left)); @@ -58,14 +59,16 @@ impl TestDom { machine.mutations } - pub fn lazy_diff<'a, F1, F2>( + pub fn lazy_diff<'a>( &'a self, - left: LazyNodes<'a, F1>, - right: LazyNodes<'a, F2>, + left: Element<'a>, + right: Element<'a>, + // left: LazyNodes<'a, F1>, + // right: LazyNodes<'a, F2>, ) -> (Mutations<'a>, Mutations<'a>) - where - F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, - F2: FnOnce(NodeFactory<'a>) -> VNode<'a>, +// where + // F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, + // F2: FnOnce(NodeFactory<'a>) -> VNode<'a>, { let (old, new) = (self.render(left), self.render(right)); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index acd8df52c..ff0600a08 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -143,7 +143,7 @@ impl VirtualDom { let _p = root_props.clone(); // Safety: this callback is only valid for the lifetime of the root props - let root_caller: Rc DomTree> = Rc::new(move |scope: &Scope| unsafe { + let root_caller: Rc Element> = Rc::new(move |scope: &Scope| unsafe { let props = _p.downcast_ref::

().unwrap(); std::mem::transmute(root((Context { scope }, props))) }); @@ -219,7 +219,7 @@ impl VirtualDom { let root = *self.root_fc.downcast_ref::>().unwrap(); - let root_caller: Box DomTree> = + let root_caller: Box Element> = Box::new(move |scope: &Scope| unsafe { let props: &'_ P = &*(props_ptr as *const P); std::mem::transmute(root((Context { scope }, props))) @@ -368,22 +368,24 @@ impl VirtualDom { use futures_util::StreamExt; // Wait for any new events if we have nothing to do - futures_util::select! { - _ = self.scheduler.async_tasks.next() => {} - msg = self.scheduler.receiver.next() => { - match msg.unwrap() { - SchedulerMsg::Task(t) => { - self.scheduler.handle_task(t); - }, - SchedulerMsg::Immediate(im) => { - self.scheduler.dirty_scopes.insert(im); - } - SchedulerMsg::UiEvent(evt) => { - self.scheduler.ui_events.push_back(evt); - } - } - }, - } + todo!("wait for work without select macro") + + // futures_util::select! { + // _ = self.scheduler.async_tasks.next() => {} + // msg = self.scheduler.receiver.next() => { + // match msg.unwrap() { + // SchedulerMsg::Task(t) => { + // self.scheduler.handle_task(t); + // }, + // SchedulerMsg::Immediate(im) => { + // self.scheduler.dirty_scopes.insert(im); + // } + // SchedulerMsg::UiEvent(evt) => { + // self.scheduler.ui_events.push_back(evt); + // } + // } + // }, + // } } } @@ -408,4 +410,4 @@ impl std::fmt::Display for VirtualDom { } // we never actually use the contents of this root caller -struct RootCaller(Rc Fn(&'b Scope) -> DomTree<'b> + 'static>); +struct RootCaller(Rc Fn(&'b Scope) -> Element<'b> + 'static>); diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index b7d2e3f31..09f319ac0 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -10,17 +10,17 @@ fn test_borrowed_state() { let _ = VirtualDom::new(Parent); } -fn Parent((cx, _): Component<()>) -> DomTree { +fn Parent((cx, _): Component<()>) -> Element { let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {}); - cx.render(rsx! { + rsx! { div { Child { name: value } Child { name: value } Child { name: value } Child { name: value } } - }) + } } #[derive(Props)] @@ -28,13 +28,13 @@ struct ChildProps<'a> { name: &'a str, } -fn Child<'a>((cx, props): Component<'a, ChildProps>) -> DomTree<'a> { - cx.render(rsx! { +fn Child<'a>((cx, props): Component<'a, ChildProps>) -> Element<'a> { + rsx! { div { h1 { "it's nested" } Child2 { name: props.name } } - }) + } } #[derive(Props)] @@ -42,8 +42,8 @@ struct Grandchild<'a> { name: &'a str, } -fn Child2<'a>((cx, props): Component<'a, Grandchild>) -> DomTree<'a> { - cx.render(rsx! { +fn Child2<'a>((cx, props): Component<'a, Grandchild>) -> Element<'a> { + rsx! { div { "Hello {props.name}!" } - }) + } } diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index d8a79162d..8339ceb9f 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -22,13 +22,13 @@ fn new_dom(app: FC

, props: P) -> VirtualDom { #[test] fn test_original_diff() { static APP: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { div { div { "Hello, world!" } } - }) + } }; let mut dom = new_dom(APP, ()); @@ -58,7 +58,7 @@ fn test_original_diff() { #[test] fn create() { static APP: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { div { div { "Hello, world!" @@ -72,7 +72,7 @@ fn create() { } } } - }) + } }; let mut dom = new_dom(APP, ()); @@ -121,11 +121,11 @@ fn create() { #[test] fn create_list() { static APP: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { {(0..3).map(|f| rsx!{ div { "hello" }})} - }) + } }; let mut dom = new_dom(APP, ()); @@ -170,12 +170,12 @@ fn create_list() { #[test] fn create_simple() { static APP: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { div {} div {} div {} div {} - }) + } }; let mut dom = new_dom(APP, ()); @@ -208,19 +208,19 @@ fn create_simple() { #[test] fn create_components() { static App: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { Child { "abc1" } Child { "abc2" } Child { "abc3" } - }) + } }; static Child: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { h1 {} div { {cx.children()} } p {} - }) + } }; let mut dom = new_dom(App, ()); @@ -269,10 +269,10 @@ fn create_components() { #[test] fn anchors() { static App: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { {true.then(|| rsx!{ div { "hello" } })} {false.then(|| rsx!{ div { "goodbye" } })} - }) + } }; let mut dom = new_dom(App, ()); @@ -303,9 +303,9 @@ fn suspended() { || async { // }, - |cx, p| cx.render(rsx! { "hi "}), + |cx, p| rsx! { "hi "}, ); - cx.render(rsx! { {val} }) + rsx! { {val} } }; let mut dom = new_dom(App, ()); diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index bfb33b4d6..55a332e57 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -807,14 +807,14 @@ fn controlled_keyed_diffing_out_of_order_max_test() { fn suspense() { let dom = new_dom(); - let edits = dom.create(LazyNodes::new(|f| { + let edits = dom.create(Some(LazyNodes::new(|f| { use std::cell::{Cell, RefCell}; VNode::Suspended(f.bump().alloc(VSuspended { task_id: 0, callback: RefCell::new(None), dom_id: Cell::new(None), })) - })); + }))); assert_eq!( edits.edits, [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] diff --git a/packages/core/tests/display_vdom.rs b/packages/core/tests/display_vdom.rs index ec81ac52c..734d0fd77 100644 --- a/packages/core/tests/display_vdom.rs +++ b/packages/core/tests/display_vdom.rs @@ -14,7 +14,7 @@ mod test_logging; #[test] fn please_work() { static App: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { div { hidden: "true" "hello" @@ -24,13 +24,13 @@ fn please_work() { Child {} } div { "hello" } - }) + } }; static Child: FC<()> = |(cx, props)| { - cx.render(rsx! { + rsx! { div { "child" } - }) + } }; let mut dom = VirtualDom::new(App); diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 3b9c05cca..98837fbc3 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -22,7 +22,7 @@ fn manual_diffing() { static App: FC = |(cx, props)| { let val = props.value.lock().unwrap(); - cx.render(rsx! { div { "{val}" } }) + rsx! { div { "{val}" } } }; let value = Arc::new(Mutex::new("Hello")); diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 1983ec8c7..f2a17b804 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -15,12 +15,12 @@ fn shared_state_test() { static App: FC<()> = |(cx, props)| { cx.provide_state(MySharedState("world!")); - rsx!(cx, Child {}) + rsx!(Child {}) }; static Child: FC<()> = |(cx, props)| { let shared = cx.consume_state::()?; - rsx!(cx, "Hello, {shared.0}") + rsx!("Hello, {shared.0}") }; let mut dom = VirtualDom::new(App); diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 9ea872a17..2350774c5 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -19,7 +19,7 @@ use dioxus_html as dioxus_elements; fn app_runs() { static App: FC<()> = |(cx, props)| { // - cx.render(rsx!( div{"hello"} )) + rsx!( div{"hello"} ) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -29,10 +29,10 @@ fn app_runs() { #[test] fn fragments_work() { static App: FC<()> = |(cx, props)| { - cx.render(rsx!( + rsx!( div{"hello"} div{"goodbye"} - )) + ) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -43,10 +43,10 @@ fn fragments_work() { #[test] fn lists_work() { static App: FC<()> = |(cx, props)| { - cx.render(rsx!( + rsx!( h1 {"hello"} {(0..6).map(|f| rsx!(span{ "{f}" }))} - )) + ) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -56,11 +56,11 @@ fn lists_work() { #[test] fn conditional_rendering() { static App: FC<()> = |(cx, props)| { - cx.render(rsx!( + rsx!( h1 {"hello"} {true.then(|| rsx!(span{ "a" }))} {false.then(|| rsx!(span{ "b" }))} - )) + ) }; let mut vdom = VirtualDom::new(App); @@ -73,16 +73,16 @@ fn conditional_rendering() { #[test] fn child_components() { static App: FC<()> = |(cx, props)| { - cx.render(rsx!( + rsx!( {true.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))} - )) + ) }; static Child: FC<()> = |(cx, props)| { - cx.render(rsx!( + rsx!( h1 {"hello"} h1 {"goodbye"} - )) + ) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -92,8 +92,8 @@ fn child_components() { #[test] fn suspended_works() { static App: FC<()> = |(cx, props)| { - let title = use_suspense(cx, || async { "bob" }, |cx, f| cx.render(rsx! { "{f}"})); - cx.render(rsx!("hello" { title })) + let title = use_suspense(cx, || async { "bob" }, |cx, f| rsx! { "{f}"}); + rsx!("hello" { title }) }; let mut vdom = VirtualDom::new(App); diff --git a/packages/desktop/examples/todomvc.rs b/packages/desktop/examples/todomvc.rs index fa2eae08e..5d0ebd5cd 100644 --- a/packages/desktop/examples/todomvc.rs +++ b/packages/desktop/examples/todomvc.rs @@ -136,7 +136,7 @@ pub struct TodoEntryProps { id: u32, } -pub fn TodoEntry((cx, props): Component) -> DomTree { +pub fn TodoEntry((cx, props): Component) -> Element { let todos = use_shared_state::(cx)?; let _todos = todos.read(); diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index f5c352c38..b5c1ab78f 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use dioxus::prelude::Component; use dioxus_core as dioxus; -use dioxus_core::{Context, DomTree, LazyNodes, NodeFactory, Properties}; +use dioxus_core::{Context, Element, LazyNodes, NodeFactory, Properties}; use dioxus_core_macro::Props; /* @@ -54,7 +54,7 @@ pub struct WebviewWindowProps<'a> { /// /// /// -pub fn WebviewWindow<'a>((cx, props): Component<'a, WebviewWindowProps>) -> DomTree<'a> { +pub fn WebviewWindow<'a>((cx, props): Component<'a, WebviewWindowProps>) -> Element<'a> { let dtcx = cx.consume_state::>()?; cx.use_hook( diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index 21ede5078..699305df2 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -68,13 +68,13 @@ static APP: FC<()> = |(cx, _)| { }) }; -fn render_bullets(cx: Context) -> DomTree { +fn render_bullets(cx: Context) -> Element { rsx!(cx, div { "bite me" }) } -fn render_list(cx: Context, count: usize) -> DomTree { +fn render_list(cx: Context, count: usize) -> Element { let items = (0..count).map(|f| { rsx! { li { "a - {f}" } From ba9e1dbb8fa24048a6c9ccef8a8722688226a845 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 29 Oct 2021 21:43:21 -0400 Subject: [PATCH 02/18] fix: messed up how lifetimes worked, need to render once per component --- examples/borrowed.rs | 8 +- examples/calculator.rs | 2 +- examples/framework_benchmark.rs | 4 +- examples/hello_world.rs | 2 +- examples/pattern_model.rs | 2 +- examples/rsx_usage.rs | 4 +- examples/todomvc.rs | 2 +- examples/web_tick.rs | 2 +- packages/core-macro/src/rsx/body.rs | 4 +- packages/core/.vscode/settings.json | 3 - packages/core/Cargo.toml | 2 - packages/core/benches/jsframework.rs | 10 +- packages/core/examples/alternative.rs | 2 +- packages/core/examples/borrowed.rs | 4 +- packages/core/examples/fragment_from_iter.rs | 2 +- packages/core/examples/jsframework.rs | 14 +-- packages/core/examples/syntax.rs | 106 ++++++++++++++--- packages/core/examples/vdom_usage.rs | 2 +- packages/core/src/component.rs | 6 +- packages/core/src/context.rs | 9 +- packages/core/src/diff.rs | 4 +- packages/core/src/hooks.rs | 51 +++++---- packages/core/src/lazynodes.rs | 113 +++++++++++++++++++ packages/core/src/lib.rs | 9 +- packages/core/src/nodes.rs | 77 ++----------- packages/core/src/resources.rs | 10 +- packages/core/src/scheduler.rs | 2 +- packages/core/src/scope.rs | 16 +-- packages/core/src/virtual_dom.rs | 21 ++-- packages/core/tests/borrowedstate.rs | 6 +- packages/core/tests/vdom_rebuild.rs | 2 +- packages/desktop/examples/todomvc.rs | 2 +- packages/desktop/src/desktop_context.rs | 4 +- 33 files changed, 323 insertions(+), 184 deletions(-) delete mode 100644 packages/core/.vscode/settings.json create mode 100644 packages/core/src/lazynodes.rs diff --git a/examples/borrowed.rs b/examples/borrowed.rs index c9de40d89..dc4805500 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -20,7 +20,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { let text: &mut Vec = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {}); let first = text.get_mut(0).unwrap(); @@ -43,7 +43,7 @@ impl<'a> Drop for C1Props<'a> { fn drop(&mut self) {} } -fn Child1<'a>((cx, props): Component<'a, C1Props>) -> Element<'a> { +fn Child1<'a>((cx, props): Scope<'a, C1Props>) -> Element<'a> { let (left, right) = props.text.split_once("=").unwrap(); cx.render(rsx! { @@ -59,7 +59,7 @@ struct C2Props<'a> { text: &'a str, } -fn Child2<'a>((cx, props): Component<'a, C2Props>) -> Element<'a> { +fn Child2<'a>((cx, props): Scope<'a, C2Props>) -> Element<'a> { cx.render(rsx! { Child3 { text: props.text @@ -72,7 +72,7 @@ struct C3Props<'a> { text: &'a str, } -fn Child3<'a>((cx, props): Component<'a, C3Props>) -> Element<'a> { +fn Child3<'a>((cx, props): Scope<'a, C3Props>) -> Element<'a> { cx.render(rsx! { div { "{props.text}"} }) diff --git a/examples/calculator.rs b/examples/calculator.rs index 6d2d9b2ed..a00ad98f3 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -116,7 +116,7 @@ struct CalculatorKeyProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> Element<'a> { +fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps>) -> Element<'a> { rsx!(cx, button { class: "calculator-key {props.name}" onclick: {props.onclick} diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index b63b1e98d..5ca02fb49 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -80,7 +80,7 @@ struct ActionButtonProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn ActionButton<'a>((cx, props): Component<'a, ActionButtonProps>) -> Element<'a> { +fn ActionButton<'a>((cx, props): Scope<'a, ActionButtonProps>) -> Element<'a> { rsx!(cx, div { class: "col-sm-6 smallpad" button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: {props.onclick}, "{props.name}" @@ -93,7 +93,7 @@ struct RowProps { row_id: usize, label: Rc, } -fn Row((cx, props): Component) -> Element { +fn Row((cx, props): Scope) -> Element { rsx!(cx, tr { td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 1115b7e60..7eaf74d7d 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -4,7 +4,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { cx.render(rsx! ( div { "Hello, world!" } )) diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index 7c24d80de..33d2063a4 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -76,7 +76,7 @@ struct CalculatorKeyProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> Element<'a> { +fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps>) -> Element<'a> { cx.render(rsx! { button { class: "calculator-key {props.name}" diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index b14ced9e2..dffd440b4 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -184,7 +184,7 @@ mod baller { pub struct BallerProps {} /// This component totally balls - pub fn Baller(_: Component) -> Element { + pub fn Baller(_: Scope) -> Element { todo!() } } @@ -195,7 +195,7 @@ pub struct TallerProps { } /// This component is taller than most :) -pub fn Taller(_: Component) -> Element { +pub fn Taller(_: Scope) -> Element { let b = true; todo!() } diff --git a/examples/todomvc.rs b/examples/todomvc.rs index f0da8d256..294ae36db 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -85,7 +85,7 @@ pub struct TodoEntryProps { todo: Rc, } -pub fn TodoEntry((cx, props): Component) -> Element { +pub fn TodoEntry((cx, props): Scope) -> Element { let is_editing = use_state(cx, || false); let contents = use_state(cx, || String::from("")); let todo = &props.todo; diff --git a/examples/web_tick.rs b/examples/web_tick.rs index f0c9d40df..ea2e087f8 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -45,7 +45,7 @@ struct RowProps { row_id: usize, label: Label, } -fn Row((cx, props): Component) -> Element { +fn Row((cx, props): Scope) -> Element { let [adj, col, noun] = props.label.0; cx.render(rsx! { tr { diff --git a/packages/core-macro/src/rsx/body.rs b/packages/core-macro/src/rsx/body.rs index 3e28e481e..b5b487459 100644 --- a/packages/core-macro/src/rsx/body.rs +++ b/packages/core-macro/src/rsx/body.rs @@ -57,11 +57,11 @@ impl ToTokens for CallBody { }), // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - Some(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ + dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner - })) + }) }), }; } diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json deleted file mode 100644 index 1c68f97cc..000000000 --- a/packages/core/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.inlayHints.enable": true -} diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 7c3dadbdb..9e7dcb279 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -42,8 +42,6 @@ serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1.7", optional = true } -stack_dst = "0.6.1" - [dev-dependencies] anyhow = "1.0.42" dioxus-html = { path = "../html" } diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index 713d1fafa..3fc3c273c 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -34,13 +34,13 @@ fn create_rows(c: &mut Criterion) { } } }); - rsx! { + cx.render(rsx! { table { tbody { {rows} } } - } + }) }; c.bench_function("create rows", |b| { @@ -57,9 +57,9 @@ struct RowProps { row_id: usize, label: Label, } -fn Row((cx, props): Component) -> Element { +fn Row((cx, props): Scope) -> Element { let [adj, col, noun] = props.label.0; - rsx! { + cx.render(rsx! { tr { td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } @@ -72,7 +72,7 @@ fn Row((cx, props): Component) -> Element { } td { class: "col-md-6" } } - } + }) } #[derive(PartialEq)] diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 3344aa4b2..edf037f89 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -9,7 +9,7 @@ fn main() { pub static EXAMPLE: FC<()> = |(cx, _)| { let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!())); - Some(LazyNodes::new(move |cx| { + cx.render(LazyNodes::new(move |cx| { cx.raw_element( "div", None, diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index f29159c8a..302bb138c 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -23,7 +23,7 @@ struct ListItem { fn app<'a>(cx: Context<'a>, props: &AppProps) -> Element<'a> { // let (val, set_val) = use_state_classic(cx, || 0); - Some(LazyNodes::new(move |_nodecx| { + cx.render(LazyNodes::new(move |_nodecx| { todo!() // builder::ElementBuilder::new(_nodecx, "div") // .iter_child({ @@ -57,7 +57,7 @@ struct ChildProps { } fn ChildItem<'a>(cx: Context<'a>, props: &'a ChildProps) -> Element<'a> { - Some(LazyNodes::new(move |__cx| todo!())) + cx.render(LazyNodes::new(move |__cx| todo!())) } impl PartialEq for ChildProps { diff --git a/packages/core/examples/fragment_from_iter.rs b/packages/core/examples/fragment_from_iter.rs index 9cfd6cabe..6daa7a690 100644 --- a/packages/core/examples/fragment_from_iter.rs +++ b/packages/core/examples/fragment_from_iter.rs @@ -26,7 +26,7 @@ fn app<'a>(cx: Context<'a>, props: &()) -> Element<'a> { ) })); - Some(LazyNodes::new(move |f| { + cx.render(LazyNodes::new(move |f| { f.raw_element( "div", None, diff --git a/packages/core/examples/jsframework.rs b/packages/core/examples/jsframework.rs index af698ec26..c0c7af057 100644 --- a/packages/core/examples/jsframework.rs +++ b/packages/core/examples/jsframework.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] -use dioxus::component::Component; +use dioxus::component::Scope; use dioxus::events::on::MouseEvent; use dioxus_core as dioxus; use dioxus_core::prelude::*; @@ -15,7 +15,7 @@ fn main() { assert!(g.edits.len() > 1); } -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000_usize).map(|f| { let label = Label::new(&mut rng); @@ -26,13 +26,13 @@ fn App((cx, props): Component<()>) -> Element { } } }); - rsx! { + cx.render(rsx! { table { tbody { {rows} } } - } + }) } #[derive(PartialEq, Props)] @@ -41,11 +41,11 @@ struct RowProps { label: Label, } -fn Row((cx, props): Component) -> Element { +fn Row((cx, props): Scope) -> Element { let handler = move |evt: MouseEvent| { let g = evt.button; }; - rsx! { + cx.render(rsx! { tr { td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } @@ -58,7 +58,7 @@ fn Row((cx, props): Component) -> Element { } td { class: "col-md-6" } } - } + }) } #[derive(PartialEq)] diff --git a/packages/core/examples/syntax.rs b/packages/core/examples/syntax.rs index b2a8aeaf8..e4bc82fa3 100644 --- a/packages/core/examples/syntax.rs +++ b/packages/core/examples/syntax.rs @@ -1,5 +1,8 @@ -use dioxus::component::Component; +use std::marker::PhantomData; + +use dioxus::component::Scope; use dioxus::events::on::MouseEvent; +use dioxus::nodes::{IntoVNode, IntoVNodeList}; use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::*; @@ -20,20 +23,97 @@ fn html_usage() { let items = ["bob", "bill", "jack"]; - let f = items.iter().filter(|f| f.starts_with("b")).map(|f| { - rsx! { - "hello {f}" - } - }); + let f = items + .iter() + .filter(|f| f.starts_with('b')) + .map(|f| rsx!("hello {f}")); - let p = rsx! { - div { - {f} - } - }; + let p = rsx!(div { {f} }); } +static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!")); + static App: FC<()> = |(cx, props)| { - // - rsx!(div {}) + let name = cx.use_state(|| 0); + + cx.render(rsx!(div { + h1 {} + h2 {} + })) +}; + +pub trait UseState<'a, T: 'static> { + fn use_state(self, f: impl FnOnce() -> T) -> &'a T; +} +impl<'a, T: 'static> UseState<'a, T> for Context<'a> { + fn use_state(self, f: impl FnOnce() -> T) -> &'a T { + todo!() + } +} + +fn App3((cx, props): Scope<()>) -> Element { + let p = rsx! { + Child { + bame: 10, + } + }; + cx.render(rsx!(Child { + bame: 102, + ..ChildProps { bame: 10 } + })) +} + +#[derive(Props, PartialEq, Debug)] +struct ChildProps { + bame: i32, // children: Children<'a>, +} + +fn Child<'a>((cx, props): Scope<'a, ChildProps>) -> Element<'a> { + cx.render(rsx!(div { + // {props.children} + })) +} + +// Some(LazyNodes::new(|f| { +// // +// // let r = f.fragment_from_iter(&props.children); +// r +// // todo!() +// })) +// todo!() +// rsx!({ Some(p) }) +// todo!() + +pub struct Children<'a> { + children: VNode<'static>, + _p: PhantomData<&'a ()>, +} + +impl<'a> Children<'a> { + pub fn new(children: VNode<'a>) -> Self { + Self { + children: unsafe { std::mem::transmute(children) }, + _p: PhantomData, + } + } +} +impl<'a> IntoVNodeList<'a> for &Children<'a> { + fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { + todo!() + } +} + +static Bapp: FC<()> = |(cx, props)| { + let name = cx.use_state(|| 0); + + cx.render(rsx!( + div { + div { + + } + div { + + } + } + )) }; diff --git a/packages/core/examples/vdom_usage.rs b/packages/core/examples/vdom_usage.rs index cd381bf5a..caf58faaf 100644 --- a/packages/core/examples/vdom_usage.rs +++ b/packages/core/examples/vdom_usage.rs @@ -4,7 +4,7 @@ use dioxus_core::prelude::*; #[async_std::main] async fn main() { - static App: FC<()> = |(cx, props)| Some(LazyNodes::new(|f| f.text(format_args!("hello")))); + static App: FC<()> = |(cx, props)| cx.render(LazyNodes::new(|f| f.text(format_args!("hello")))); let mut dom = VirtualDom::new(App); diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 901f4e4d6..a81a4078d 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -42,7 +42,7 @@ use crate::{ /// }; /// ``` /// -pub type Component<'a, T> = (Context<'a>, &'a T); +pub type Scope<'a, T> = (Context<'a>, &'a T); /// Create inline fragments using Component syntax. /// @@ -66,8 +66,8 @@ pub type Component<'a, T> = (Context<'a>, &'a T); /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment((cx, _): Component<()>) -> Element { - Some(LazyNodes::new(|f| f.fragment_from_iter(cx.children()))) +pub fn Fragment((cx, _): Scope<()>) -> Element { + cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children()))) } /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 86d4749cb..5d52a6e4e 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -32,7 +32,7 @@ use std::{any::TypeId, ops::Deref, rc::Rc}; /// } /// ``` pub struct Context<'src> { - pub scope: &'src Scope, + pub scope: &'src ScopeInner, } impl<'src> Copy for Context<'src> {} @@ -45,7 +45,7 @@ impl<'src> Clone for Context<'src> { // We currently deref to props, but it might make more sense to deref to Scope? // This allows for code that takes cx.xyz instead of cx.props.xyz impl<'a> Deref for Context<'a> { - type Target = &'a Scope; + type Target = &'a ScopeInner; fn deref(&self) -> &Self::Target { &self.scope } @@ -137,7 +137,10 @@ impl<'src> Context<'src> { /// cx.render(lazy_tree) /// } ///``` - pub fn render(self, lazy_nodes: LazyNodes<'src>) -> Option> { + pub fn render) -> VNode<'src>>( + self, + lazy_nodes: LazyNodes<'src, F>, + ) -> Option> { let bump = &self.scope.frames.wip_frame().bump; Some(lazy_nodes.into_vnode(NodeFactory { bump })) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index d84e59395..9788a75c1 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -342,7 +342,7 @@ impl<'bump> DiffMachine<'bump> { let parent_scope = self.vdom.get_scope(parent_idx).unwrap(); let new_idx = self.vdom.insert_scope_with_key(|new_idx| { - Scope::new( + ScopeInner::new( caller, new_idx, Some(parent_idx), @@ -1161,7 +1161,7 @@ impl<'bump> DiffMachine<'bump> { } /// Adds a listener closure to a scope during diff. - fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) { + fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &ScopeInner) { let mut queue = scope.listeners.borrow_mut(); let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) }; queue.push(long_listener as *const _) diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 0154dce3b..c1de46bfb 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -135,32 +135,35 @@ where None => { let value = hook.value.clone(); - Some(LazyNodes::new(|f| { - let bump = f.bump(); + let id = hook.handle.our_id; - use bumpalo::boxed::Box as BumpBox; + todo!() + // Some(LazyNodes::new(move |f| { + // let bump = f.bump(); - let f: &mut dyn FnMut(SuspendedContext<'src>) -> Element<'src> = - bump.alloc(move |sus| { - let val = value.borrow(); + // use bumpalo::boxed::Box as BumpBox; - let out = val - .as_ref() - .unwrap() - .as_ref() - .downcast_ref::() - .unwrap(); + // let f: &mut dyn FnMut(SuspendedContext<'src>) -> Element<'src> = + // bump.alloc(move |sus| { + // let val = value.borrow(); - user_callback(sus, out) - }); - let callback = unsafe { BumpBox::from_raw(f) }; + // let out = val + // .as_ref() + // .unwrap() + // .as_ref() + // .downcast_ref::() + // .unwrap(); - VNode::Suspended(bump.alloc(VSuspended { - dom_id: empty_cell(), - task_id: hook.handle.our_id, - callback: RefCell::new(Some(callback)), - })) - })) + // user_callback(sus, out) + // }); + // let callback = unsafe { BumpBox::from_raw(f) }; + + // VNode::Suspended(bump.alloc(VSuspended { + // dom_id: empty_cell(), + // task_id: id, + // callback: RefCell::new(Some(callback)), + // })) + // })) } }, |_| {}, @@ -177,10 +180,10 @@ pub struct SuspendedContext<'a> { } impl<'src> SuspendedContext<'src> { - pub fn render( - // pub fn render) -> VNode<'src>>( + // pub fn render( + pub fn render) -> VNode<'src>>( self, - lazy_nodes: LazyNodes<'src>, + lazy_nodes: LazyNodes<'src, F>, ) -> Element<'src> { let bump = &self.inner.scope.frames.wip_frame().bump; todo!("suspense") diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs new file mode 100644 index 000000000..f10cd2134 --- /dev/null +++ b/packages/core/src/lazynodes.rs @@ -0,0 +1,113 @@ +use std::marker::PhantomData; + +/* +Remember: calls to rsx! are lazy - they are not evaluated immediately. + +They also using dynamic dispatch, so we can return multiple rsx!'s from match statements and such. + +If we allocated every rsx! call on the heap, it would be quite wasteful. Rsx! calls are FnOnce, so they can be stored in a stack. + +Solutions like stackdst are useful, but they only support 'static closures. + +All our closures are bound by the bump lifetime, so stack-dst will not work for us + +Our solution is to try and manually allocate the closure onto the stack. +If it fails, then we default to Box. + +*/ +use crate::innerlude::{IntoVNode, NodeFactory, VNode}; + +/// A concrete type provider for closures that build VNode structures. +/// +/// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over +/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode. +/// +/// +/// ```rust +/// LazyNodes::new(|f| f.element("div", [], [], [] None)) +/// ``` +pub struct LazyNodes<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> { + inner: Box, + _p: PhantomData<&'a ()>, + // inner: StackNodeStorage<'a>, + // inner: StackNodeStorage<'a>, +} + +type StackHeapSize = [usize; 12]; + +enum StackNodeStorage<'a> { + Stack { + next_ofs: usize, + buf: StackHeapSize, + width: usize, + }, + Heap(Box) -> VNode<'a>>), +} + +impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> LazyNodes<'a, F> { + pub fn new(f: F) -> Self { + // let width = std::mem?::size_of::(); + // let b: Box) -> VNode<'a>> = Box::new(f); + + todo!() + // Self { inner: b } + // todo!() + + // if width > std::mem::size_of::() { + // let g: Box FnOnce(NodeFactory<'b>) -> VNode<'b> + 'g> = Box::new(f); + // LazyNodes { + // inner: StackNodeStorage::Heap(g), + // } + // } else { + // let mut buf = [0; 12]; + // let mut next_ofs = 0; + // next_ofs += 1; + // LazyNodes { + // inner: StackNodeStorage::Stack { + // next_ofs, + // buf, + // width, + // }, + // } + // } + } +} + +// Our blanket impl +impl<'a, F> IntoIterator for LazyNodes<'a, F> +where + F: FnOnce(NodeFactory<'a>) -> VNode<'a>, +{ + type Item = Self; + type IntoIter = std::iter::Once; + fn into_iter(self) -> Self::IntoIter { + std::iter::once(self) + } +} + +// Our blanket impl +impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for LazyNodes<'a, F> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { + todo!() + // match self.inner { + // StackNodeStorage::Stack { + // buf, + // next_ofs, + // width, + // } => { + // // get the start of the allocation + // let r = &buf[0]; + + // // recast the allocation as dyn FnOnce + + // // pretend the FnOnce is box + // let g: Box) -> VNode<'a>> = todo!(); + // // Box::from_raw(r as *const usize as *mut dyn FnOnce(NodeFactory<'a>)); + + // // use Box's ability to act as FnOnce + // g(cx) + // } + // StackNodeStorage::Heap(b) => b(cx), + // } + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 02d6f8422..df0804fc8 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -23,6 +23,7 @@ pub mod events; pub mod heuristics; pub mod hooklist; pub mod hooks; +pub mod lazynodes; pub mod mutations; pub mod nodes; pub mod resources; @@ -45,6 +46,7 @@ pub(crate) mod innerlude { pub use crate::heuristics::*; pub(crate) use crate::hooklist::*; pub use crate::hooks::*; + pub use crate::lazynodes::LazyNodes; pub use crate::mutations::*; pub use crate::nodes::*; pub(crate) use crate::resources::*; @@ -55,10 +57,9 @@ pub(crate) mod innerlude { pub use crate::threadsafe::*; pub use crate::util::*; pub use crate::virtual_dom::*; - // pub type Element<'a> = Option>; - pub type Element<'a> = Option>; - pub type FC

= for<'a> fn(Component<'a, P>) -> Element<'a>; + pub type Element<'a> = Option>; + pub type FC

= for<'a> fn(Scope<'a, P>) -> Element<'a>; } pub use crate::innerlude::{ @@ -68,7 +69,7 @@ pub use crate::innerlude::{ }; pub mod prelude { - pub use crate::component::{fc_to_builder, Component, Fragment, Properties}; + pub use crate::component::{fc_to_builder, Fragment, Properties, Scope}; pub use crate::context::Context; pub use crate::hooks::*; pub use crate::innerlude::{DioxusElement, Element, LazyNodes, Mutations, NodeFactory, FC}; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 142eab4b7..10f4d25f9 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,8 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::innerlude::{ - empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, SuspendedContext, FC, + empty_cell, Context, Element, ElementId, LazyNodes, Properties, ScopeId, ScopeInner, + SuspendedContext, FC, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ @@ -13,10 +14,6 @@ use std::{ marker::PhantomData, }; -use stack_dst::Value as StackDST; - -pub type LazyNodeFactory<'a> = StackDST) -> VNode<'a>>; - /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. /// /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: @@ -291,7 +288,7 @@ pub struct VComponent<'src> { // Function pointer to the FC that was used to generate this component pub user_fc: *const (), - pub(crate) caller: &'src dyn for<'b> Fn(&'b Scope) -> Element<'b>, + pub(crate) caller: &'src dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, pub(crate) children: &'src [VNode<'src>], @@ -332,11 +329,11 @@ impl<'a> NodeFactory<'a> { self.bump } - pub fn render_directly(&self, lazy_nodes: LazyNodes<'a>) -> Option> -// pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> Element<'a> - // where - // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, + pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> Element<'a> + where + F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { + // pub fn render_directly(&self, lazy_nodes: LazyNodes<'a>) -> Option> { Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump })) } @@ -531,8 +528,8 @@ impl<'a> NodeFactory<'a> { let key = key.map(|f| self.raw_text(f).0); - let caller: &'a mut dyn for<'b> Fn(&'b Scope) -> Element<'b> = - bump.alloc(move |scope: &Scope| -> Element { + let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = + bump.alloc(move |scope: &ScopeInner| -> Element { log::debug!("calling component renderr {:?}", scope.our_arena_idx); let props: &'_ P = unsafe { &*(raw_props as *const P) }; let res: Element = component((Context { scope }, props)); @@ -687,60 +684,6 @@ impl<'a> IntoVNode<'a> for VNode<'a> { } } -/// A concrete type provider for closures that build VNode structures. -/// -/// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over -/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode. -/// -/// -/// ```rust -/// LazyNodes::new(|f| f.element("div", [], [], [] None)) -/// ``` -pub struct LazyNodes<'a> { - inner: LazyNodeFactory<'a>, -} -// where -// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, - -impl<'a> LazyNodes<'a> -// where -// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, -{ - pub fn new(f: impl FnOnce(NodeFactory<'a>) -> VNode<'a>) -> Self { - todo!() - // pub fn new) -> VNode<'a>>(f: G) -> Self { - // let inner = LazyNodeFactory::new(f); - // Self { inner: f } - } -} - -// Our blanket impl -impl<'a> IntoVNode<'a> for LazyNodes<'a> -// where -// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, -{ - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - // let f = self.inner; - todo!("manually drop here") - // (self.inner)(cx) - } -} - -// Our blanket impl -impl<'a> IntoIterator for LazyNodes<'a> -// where -// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, -// impl<'a, G> IntoIterator for LazyNodes<'a, G> -// where -// G: FnOnce(NodeFactory<'a>) -> VNode<'a>, -{ - type Item = Self; - type IntoIter = std::iter::Once; - fn into_iter(self) -> Self::IntoIter { - std::iter::once(self) - } -} - // Conveniently, we also support "null" (nothing) passed in impl IntoVNode<'_> for () { fn into_vnode(self, cx: NodeFactory) -> VNode { @@ -764,7 +707,7 @@ impl<'a> IntoVNode<'a> for Option> { } } -impl<'a> IntoVNode<'a> for Option> { +impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for Option> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { match self { Some(n) => n.into_vnode(cx), diff --git a/packages/core/src/resources.rs b/packages/core/src/resources.rs index c3113e826..4163e30d3 100644 --- a/packages/core/src/resources.rs +++ b/packages/core/src/resources.rs @@ -15,7 +15,7 @@ pub(crate) struct ResourcePool { Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!) */ - pub components: Rc>>, + pub components: Rc>>, /* Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy @@ -32,18 +32,18 @@ pub(crate) struct ResourcePool { impl ResourcePool { /// this is unsafe because the caller needs to track which other scopes it's already using - pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> { + pub fn get_scope(&self, idx: ScopeId) -> Option<&ScopeInner> { let inner = unsafe { &*self.components.get() }; inner.get(idx.0) } /// this is unsafe because the caller needs to track which other scopes it's already using - pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> { + pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut ScopeInner> { let inner = unsafe { &mut *self.components.get() }; inner.get_mut(idx.0) } - pub fn try_remove(&self, id: ScopeId) -> Option { + pub fn try_remove(&self, id: ScopeId) -> Option { let inner = unsafe { &mut *self.components.get() }; Some(inner.remove(id.0)) // .try_remove(id.0) @@ -67,7 +67,7 @@ impl ResourcePool { els.remove(id.0); } - pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId { + pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> ScopeInner) -> ScopeId { let g = unsafe { &mut *self.components.get() }; let entry = g.vacant_entry(); let id = ScopeId(entry.key()); diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 745ac1905..fb0ee8f69 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -214,7 +214,7 @@ impl Scheduler { let components = components.clone(); Rc::new(move |id, ty| { let components = unsafe { &*components.get() }; - let mut search: Option<&Scope> = components.get(id.0); + let mut search: Option<&ScopeInner> = components.get(id.0); while let Some(inner) = search.take() { if let Some(shared) = inner.shared_contexts.borrow().get(&ty) { return Some(shared.clone()); diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 348e1b74b..bb20a32f1 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -18,7 +18,7 @@ use std::{ /// /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever /// use case they might have. -pub struct Scope { +pub struct ScopeInner { // Book-keeping about our spot in the arena pub(crate) parent_idx: Option, pub(crate) our_arena_idx: ScopeId, @@ -28,7 +28,7 @@ pub struct Scope { // Nodes pub(crate) frames: ActiveFrame, - pub(crate) caller: *const dyn for<'b> Fn(&'b Scope) -> Element<'b>, + pub(crate) caller: *const dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, pub(crate) child_nodes: ScopeChildren<'static>, /* @@ -55,7 +55,7 @@ pub struct Scope { } /// Public interface for Scopes. -impl Scope { +impl ScopeInner { /// Get the root VNode for this Scope. /// /// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment. @@ -167,7 +167,7 @@ impl Scope { pub type FiberTask = Pin>>; /// Private interface for Scopes. -impl Scope { +impl ScopeInner { // we are being created in the scope of an existing component (where the creator_node lifetime comes into play) // we are going to break this lifetime by force in order to save it on ourselves. // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies. @@ -176,7 +176,7 @@ impl Scope { // Scopes cannot be made anywhere else except for this file // Therefore, their lifetimes are connected exclusively to the virtual dom pub(crate) fn new( - caller: &dyn for<'b> Fn(&'b Scope) -> Element<'b>, + caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, our_arena_idx: ScopeId, parent_idx: Option, height: u32, @@ -217,7 +217,7 @@ impl Scope { pub(crate) fn update_scope_dependencies<'creator_node>( &mut self, - caller: &'creator_node dyn for<'b> Fn(&'b Scope) -> Element<'b>, + caller: &'creator_node dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, child_nodes: ScopeChildren, ) { log::debug!("Updating scope dependencies {:?}", self.our_arena_idx); @@ -350,7 +350,7 @@ impl Scope { log::debug!("Borrowed stuff is successfully cleared"); // Cast the caller ptr from static to one with our own reference - let render: &dyn for<'b> Fn(&'b Scope) -> Element<'b> = unsafe { &*self.caller }; + let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = unsafe { &*self.caller }; // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. // @@ -376,7 +376,7 @@ impl Scope { pub(crate) struct ScopeRenderer<'a> { pub skip_components: bool, pub show_fragments: bool, - pub _scope: &'a Scope, + pub _scope: &'a ScopeInner, pub _pre_render: bool, pub _newline: bool, pub _indent: bool, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index ff0600a08..3092d60b2 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -143,15 +143,16 @@ impl VirtualDom { let _p = root_props.clone(); // Safety: this callback is only valid for the lifetime of the root props - let root_caller: Rc Element> = Rc::new(move |scope: &Scope| unsafe { - let props = _p.downcast_ref::

().unwrap(); - std::mem::transmute(root((Context { scope }, props))) - }); + let root_caller: Rc Element> = + Rc::new(move |scope: &ScopeInner| unsafe { + let props = _p.downcast_ref::

().unwrap(); + std::mem::transmute(root((Context { scope }, props))) + }); let scheduler = Scheduler::new(sender, receiver); let base_scope = scheduler.pool.insert_scope_with_key(|myidx| { - Scope::new( + ScopeInner::new( root_caller.as_ref(), myidx, None, @@ -175,12 +176,12 @@ impl VirtualDom { /// /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus /// directly. - pub fn base_scope(&self) -> &Scope { + pub fn base_scope(&self) -> &ScopeInner { self.scheduler.pool.get_scope(self.base_scope).unwrap() } /// Get the [`Scope`] for a component given its [`ScopeId`] - pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> { + pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> { self.scheduler.pool.get_scope(id) } @@ -219,8 +220,8 @@ impl VirtualDom { let root = *self.root_fc.downcast_ref::>().unwrap(); - let root_caller: Box Element> = - Box::new(move |scope: &Scope| unsafe { + let root_caller: Box Element> = + Box::new(move |scope: &ScopeInner| unsafe { let props: &'_ P = &*(props_ptr as *const P); std::mem::transmute(root((Context { scope }, props))) }); @@ -410,4 +411,4 @@ impl std::fmt::Display for VirtualDom { } // we never actually use the contents of this root caller -struct RootCaller(Rc Fn(&'b Scope) -> Element<'b> + 'static>); +struct RootCaller(Rc Fn(&'b ScopeInner) -> Element<'b> + 'static>); diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 09f319ac0..f0e3fa544 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -10,7 +10,7 @@ fn test_borrowed_state() { let _ = VirtualDom::new(Parent); } -fn Parent((cx, _): Component<()>) -> Element { +fn Parent((cx, _): Scope<()>) -> Element { let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {}); rsx! { @@ -28,7 +28,7 @@ struct ChildProps<'a> { name: &'a str, } -fn Child<'a>((cx, props): Component<'a, ChildProps>) -> Element<'a> { +fn Child<'a>((cx, props): Scope<'a, ChildProps>) -> Element<'a> { rsx! { div { h1 { "it's nested" } @@ -42,7 +42,7 @@ struct Grandchild<'a> { name: &'a str, } -fn Child2<'a>((cx, props): Component<'a, Grandchild>) -> Element<'a> { +fn Child2<'a>((cx, props): Scope<'a, Grandchild>) -> Element<'a> { rsx! { div { "Hello {props.name}!" } } diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 2350774c5..a992cd449 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -92,7 +92,7 @@ fn child_components() { #[test] fn suspended_works() { static App: FC<()> = |(cx, props)| { - let title = use_suspense(cx, || async { "bob" }, |cx, f| rsx! { "{f}"}); + let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"}); rsx!("hello" { title }) }; diff --git a/packages/desktop/examples/todomvc.rs b/packages/desktop/examples/todomvc.rs index 5d0ebd5cd..cd78eb523 100644 --- a/packages/desktop/examples/todomvc.rs +++ b/packages/desktop/examples/todomvc.rs @@ -136,7 +136,7 @@ pub struct TodoEntryProps { id: u32, } -pub fn TodoEntry((cx, props): Component) -> Element { +pub fn TodoEntry((cx, props): Scope) -> Element { let todos = use_shared_state::(cx)?; let _todos = todos.read(); diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index b5c1ab78f..1cf369d9f 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,6 +1,6 @@ use std::cell::RefCell; -use dioxus::prelude::Component; +use dioxus::prelude::Scope; use dioxus_core as dioxus; use dioxus_core::{Context, Element, LazyNodes, NodeFactory, Properties}; use dioxus_core_macro::Props; @@ -54,7 +54,7 @@ pub struct WebviewWindowProps<'a> { /// /// /// -pub fn WebviewWindow<'a>((cx, props): Component<'a, WebviewWindowProps>) -> Element<'a> { +pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element<'a> { let dtcx = cx.consume_state::>()?; cx.use_hook( From 9ee2bfb010ce90ec97e93e173c31aab281db32c4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 30 Oct 2021 17:11:15 -0400 Subject: [PATCH 03/18] wip: worked backwards a bit and got it slightly figured out --- packages/core-macro/src/rsx/body.rs | 18 +++- packages/core/examples/jsframework.rs | 22 ++--- packages/core/examples/syntax.rs | 52 ++++++++-- packages/core/examples/syntax2.rs | 71 +++++++++++++ packages/core/src/component.rs | 9 +- packages/core/src/context.rs | 24 ++++- packages/core/src/hooks.rs | 6 +- packages/core/src/lazynodes.rs | 137 +++++++++++++------------- packages/core/src/nodes.rs | 122 ++++++++++++++--------- packages/core/src/test_dom.rs | 8 +- 10 files changed, 322 insertions(+), 147 deletions(-) create mode 100644 packages/core/examples/syntax2.rs diff --git a/packages/core-macro/src/rsx/body.rs b/packages/core-macro/src/rsx/body.rs index b5b487459..a9a86daef 100644 --- a/packages/core-macro/src/rsx/body.rs +++ b/packages/core-macro/src/rsx/body.rs @@ -57,11 +57,21 @@ impl ToTokens for CallBody { }), // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ - use dioxus_elements::{GlobalAttributes, SvgAttributes}; - #inner - }) + { + + let ___p: Box VNode + '_> = Box::new(move |__cx: NodeFactory|{ + use dioxus_elements::{GlobalAttributes, SvgAttributes}; + + #inner + }); + Some(___p) + } + // dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ + // use dioxus_elements::{GlobalAttributes, SvgAttributes}; + + // #inner + // }) }), }; } diff --git a/packages/core/examples/jsframework.rs b/packages/core/examples/jsframework.rs index c0c7af057..207f6e84e 100644 --- a/packages/core/examples/jsframework.rs +++ b/packages/core/examples/jsframework.rs @@ -41,22 +41,22 @@ struct RowProps { label: Label, } -fn Row((cx, props): Scope) -> Element { +fn Row<'a>((cx, props): Scope<'a, RowProps>) -> Element<'a> { let handler = move |evt: MouseEvent| { let g = evt.button; }; cx.render(rsx! { tr { - td { class:"col-md-1", "{props.row_id}" } - td { class:"col-md-1", onclick: move |_| { /* run onselect */ } - a { class: "lbl", "{props.label}" } - } - td { class: "col-md-1" - a { class: "remove", onclick: {handler} - span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } - } - } - td { class: "col-md-6" } + // td { class:"col-md-1", "{props.row_id}" } + // td { class:"col-md-1", onclick: move |_| { /* run onselect */ } + // a { class: "lbl", "{props.label}" } + // } + // td { class: "col-md-1" + // a { class: "remove", onclick: {handler} + // span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } + // } + // } + // td { class: "col-md-6" } } }) } diff --git a/packages/core/examples/syntax.rs b/packages/core/examples/syntax.rs index e4bc82fa3..2e8447949 100644 --- a/packages/core/examples/syntax.rs +++ b/packages/core/examples/syntax.rs @@ -57,10 +57,11 @@ fn App3((cx, props): Scope<()>) -> Element { bame: 10, } }; - cx.render(rsx!(Child { - bame: 102, - ..ChildProps { bame: 10 } - })) + todo!() + // cx.render(rsx!(Child { + // bame: 102, + // ..ChildProps { bame: 10 } + // })) } #[derive(Props, PartialEq, Debug)] @@ -97,11 +98,11 @@ impl<'a> Children<'a> { } } } -impl<'a> IntoVNodeList<'a> for &Children<'a> { - fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { - todo!() - } -} +// impl<'a> IntoVNodeList<'a> for &Children<'a> { +// fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { +// todo!() +// } +// } static Bapp: FC<()> = |(cx, props)| { let name = cx.use_state(|| 0); @@ -117,3 +118,36 @@ static Bapp: FC<()> = |(cx, props)| { } )) }; + +static Match: FC<()> = |(cx, props)| { + // + let b: Box VNode> = Box::new(|f| todo!()); + + let b = match "ag" { + "a" => { + let __b: Box VNode> = Box::new(|f: NodeFactory| todo!()); + __b + } + _ => { + let __b: Box VNode> = Box::new(|f: NodeFactory| todo!()); + __b + } + }; + + // let b: Box VNode> = match "alph" { + // "beta" => Box::new(|f: NodeFactory| { + // // + // todo!() + // }), + // _ => Box::new(|f: NodeFactory| { + // // + // todo!() + // }), + // }; + + cx.render(rsx! { + div { + + } + }) +}; diff --git a/packages/core/examples/syntax2.rs b/packages/core/examples/syntax2.rs new file mode 100644 index 000000000..fd5e1eb26 --- /dev/null +++ b/packages/core/examples/syntax2.rs @@ -0,0 +1,71 @@ +use std::marker::PhantomData; + +use dioxus::component::Scope; +use dioxus::events::on::MouseEvent; +use dioxus::nodes::{IntoVNode, IntoVNodeList}; +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; + +fn main() {} + +fn t() { + let g = rsx! { + div { + div { + + } + } + }; + + let g = { + let ___p: Box VNode> = Box::new(|__cx: NodeFactory| { + use dioxus_elements::{GlobalAttributes, SvgAttributes}; + __cx.element(dioxus_elements::div, [], [], [], None) + }); + // let __z = ___p as ; + // __z + }; +} + +fn App((cx, props): Scope<()>) -> Element { + let a = rsx! { + div { + "asd" + } + }; + + let p = (0..10).map(|f| { + rsx! { + div { + + } + } + }); + + let g = match "text" { + "a" => rsx!("asd"), + b => rsx!("asd"), + }; + + let items = ["bob", "bill", "jack"]; + + let f = items + .iter() + .filter(|f| f.starts_with('b')) + .map(|f| rsx!("hello {f}")); + + cx.render(rsx! { + div { + div { + {a} + {p} + {g} + {f} + } + } + }) +} + +// std::boxed::Box std::ops::FnOnce(dioxus_core::NodeFactory<'r>) -> dioxus_core::VNode<'_>> diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index a81a4078d..0738999dc 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -7,7 +7,7 @@ use crate::{ innerlude::{Context, Element, LazyNodes, FC}, - VNode, + NodeFactory, VNode, }; /// A component is a wrapper around a Context and some Props that share a lifetime @@ -66,8 +66,11 @@ pub type Scope<'a, T> = (Context<'a>, &'a T); /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment((cx, _): Scope<()>) -> Element { - cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children()))) +pub fn Fragment<'a>((cx, _): Scope<'a, ()>) -> Element<'a> { + todo!() + // let p: Box) -> VNode<'a> + '_> = + // Box::new(|f: NodeFactory| f.fragment_from_iter(cx.children())); + // cx.render(p) } /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 5d52a6e4e..33822ffde 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -137,12 +137,30 @@ impl<'src> Context<'src> { /// cx.render(lazy_tree) /// } ///``` - pub fn render) -> VNode<'src>>( + pub fn render( self, - lazy_nodes: LazyNodes<'src, F>, + lazy_nodes: Option>, + // lazy_nodes: Box) -> VNode<'src> + '_>, ) -> Option> { + // pub fn render) -> VNode<'src>>( + // self, + // lazy_nodes: LazyNodes<'src, F>, + // ) -> Option> { let bump = &self.scope.frames.wip_frame().bump; - Some(lazy_nodes.into_vnode(NodeFactory { bump })) + // Some(lazy_nodes.into_vnode(NodeFactory { bump })) + + let factory = NodeFactory { bump }; + // let vnode = lazy_nodes(factory); + + match lazy_nodes { + Some(f) => Some(f(factory)), + None => None, + } + + // match lazy_nodes { + // None => todo!(), + // } + // Some(lazy_nodes) } /// `submit_task` will submit the future to be polled. diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index c1de46bfb..8940671f4 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -181,9 +181,11 @@ pub struct SuspendedContext<'a> { impl<'src> SuspendedContext<'src> { // pub fn render( - pub fn render) -> VNode<'src>>( + pub fn render( + // pub fn render) -> VNode<'src>>( self, - lazy_nodes: LazyNodes<'src, F>, + lazy_nodes: LazyNodes<'_>, + // lazy_nodes: LazyNodes<'src, '_>, ) -> Element<'src> { let bump = &self.inner.scope.frames.wip_frame().bump; todo!("suspense") diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index f10cd2134..e069ac372 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -26,12 +26,13 @@ use crate::innerlude::{IntoVNode, NodeFactory, VNode}; /// ```rust /// LazyNodes::new(|f| f.element("div", [], [], [] None)) /// ``` -pub struct LazyNodes<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> { - inner: Box, - _p: PhantomData<&'a ()>, - // inner: StackNodeStorage<'a>, - // inner: StackNodeStorage<'a>, -} +// pub struct LazyNodes<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> { +// inner: Box, +// _p: PhantomData<&'a ()>, +// // inner: StackNodeStorage<'a>, +// // inner: StackNodeStorage<'a>, +// } +pub type LazyNodes<'b> = Box FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>; type StackHeapSize = [usize; 12]; @@ -44,70 +45,74 @@ enum StackNodeStorage<'a> { Heap(Box) -> VNode<'a>>), } -impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> LazyNodes<'a, F> { - pub fn new(f: F) -> Self { - // let width = std::mem?::size_of::(); - // let b: Box) -> VNode<'a>> = Box::new(f); +// impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> LazyNodes<'a, F> { +// pub fn new(f: F) -> Self { +// // let width = std::mem?::size_of::(); +// // let b: Box) -> VNode<'a>> = Box::new(f); - todo!() - // Self { inner: b } - // todo!() +// todo!() +// // Self { inner: b } +// // todo!() - // if width > std::mem::size_of::() { - // let g: Box FnOnce(NodeFactory<'b>) -> VNode<'b> + 'g> = Box::new(f); - // LazyNodes { - // inner: StackNodeStorage::Heap(g), - // } - // } else { - // let mut buf = [0; 12]; - // let mut next_ofs = 0; - // next_ofs += 1; - // LazyNodes { - // inner: StackNodeStorage::Stack { - // next_ofs, - // buf, - // width, - // }, - // } - // } - } -} +// // if width > std::mem::size_of::() { +// // let g: Box FnOnce(NodeFactory<'b>) -> VNode<'b> + 'g> = Box::new(f); +// // LazyNodes { +// // inner: StackNodeStorage::Heap(g), +// // } +// // } else { +// // let mut buf = [0; 12]; +// // let mut next_ofs = 0; +// // next_ofs += 1; +// // LazyNodes { +// // inner: StackNodeStorage::Stack { +// // next_ofs, +// // buf, +// // width, +// // }, +// // } +// // } +// } +// } -// Our blanket impl -impl<'a, F> IntoIterator for LazyNodes<'a, F> -where - F: FnOnce(NodeFactory<'a>) -> VNode<'a>, -{ - type Item = Self; - type IntoIter = std::iter::Once; - fn into_iter(self) -> Self::IntoIter { - std::iter::once(self) - } -} +// // Our blanket impl +// impl<'a> IntoIterator for LazyNodes<'a> +// // where +// // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, +// // impl<'a, F> IntoIterator for LazyNodes<'a, F> +// // where +// // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, +// { +// type Item = Self; +// type IntoIter = std::iter::Once; +// fn into_iter(self) -> Self::IntoIter { +// std::iter::once(self) +// } +// } -// Our blanket impl -impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for LazyNodes<'a, F> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - todo!() - // match self.inner { - // StackNodeStorage::Stack { - // buf, - // next_ofs, - // width, - // } => { - // // get the start of the allocation - // let r = &buf[0]; +// // Our blanket impl +// impl IntoVNode for LazyNodes<'_> { +// // impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for LazyNodes<'a, F> { +// fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { +// todo!() +// // match self.inner { +// // StackNodeStorage::Stack { +// // buf, +// // next_ofs, +// // width, +// // } => { +// // // get the start of the allocation +// // let r = &buf[0]; - // // recast the allocation as dyn FnOnce +// // // recast the allocation as dyn FnOnce - // // pretend the FnOnce is box - // let g: Box) -> VNode<'a>> = todo!(); - // // Box::from_raw(r as *const usize as *mut dyn FnOnce(NodeFactory<'a>)); +// // // pretend the FnOnce is box +// // let g: Box) -> VNode<'a>> = todo!(); +// // // Box::from_raw(r as *const usize as *mut dyn FnOnce(NodeFactory<'a>)); - // // use Box's ability to act as FnOnce - // g(cx) - // } - // StackNodeStorage::Heap(b) => b(cx), - // } - } -} +// // // use Box's ability to act as FnOnce +// // g(cx) +// // } +// // StackNodeStorage::Heap(b) => b(cx), +// // } +// } +// } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 10f4d25f9..964cdb42a 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -329,12 +329,19 @@ impl<'a> NodeFactory<'a> { self.bump } - pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> Element<'a> - where - F: FnOnce(NodeFactory<'a>) -> VNode<'a>, + pub fn render_directly(&self, lazy_nodes: LazyNodes<'_>) -> Element<'a> +// pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, '_>) -> Element<'a> +// where + // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { + // pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> Element<'a> + // where + // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, + // { // pub fn render_directly(&self, lazy_nodes: LazyNodes<'a>) -> Option> { - Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump })) + // Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump })) + + todo!() } pub fn unstable_place_holder() -> VNode<'static> { @@ -552,7 +559,7 @@ impl<'a> NodeFactory<'a> { })) } - pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> { + pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList) -> VNode<'a> { let children = node_iter.into_vnode_list(self); // TODO @@ -597,20 +604,20 @@ impl<'a> NodeFactory<'a> { /// /// As such, all node creation must go through the factory, which is only available in the component context. /// These strict requirements make it possible to manage lifetimes and state. -pub trait IntoVNode<'a> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; +pub trait IntoVNode { + fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a>; } -pub trait IntoVNodeList<'a> { - fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>]; +pub trait IntoVNodeList { + fn into_vnode_list<'a>(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>]; } -impl<'a, T, V> IntoVNodeList<'a> for T +impl IntoVNodeList for T where T: IntoIterator, - V: IntoVNode<'a>, + V: IntoVNode, { - fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { + fn into_vnode_list<'a>(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); for node in self.into_iter() { @@ -662,11 +669,11 @@ impl ScopeChildren<'_> { } } -impl<'a> IntoVNodeList<'a> for ScopeChildren<'a> { - fn into_vnode_list(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] { - self.0 - } -} +// impl IntoVNodeList for ScopeChildren<'_> { +// fn into_vnode_list<'a>(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] { +// self.0 +// } +// } // For the case where a rendered VNode is passed into the rsx! macro through curly braces impl<'a> IntoIterator for VNode<'a> { @@ -677,51 +684,74 @@ impl<'a> IntoIterator for VNode<'a> { } } -// For the case where a rendered VNode is passed into the rsx! macro through curly braces -impl<'a> IntoVNode<'a> for VNode<'a> { - fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { - self - } -} +// // For the case where a rendered VNode is passed into the rsx! macro through curly braces +// impl IntoVNode for VNode<'_> { +// fn into_vnode<'a>(self, _: NodeFactory<'a>) -> VNode<'a> { +// self +// } +// } // Conveniently, we also support "null" (nothing) passed in -impl IntoVNode<'_> for () { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.fragment_from_iter(None as Option) - } -} +// impl IntoVNode for () { +// fn into_vnode(self, cx: NodeFactory) -> VNode { +// cx.fragment_from_iter(None as Option) +// } +// } -// Conveniently, we also support "None" -impl IntoVNode<'_> for Option<()> { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.fragment_from_iter(None as Option) - } -} +// // Conveniently, we also support "None" +// impl IntoVNode for Option<()> { +// fn into_vnode(self, cx: NodeFactory) -> VNode { +// cx.fragment_from_iter(None as Option) +// } +// } -impl<'a> IntoVNode<'a> for Option> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { +// impl IntoVNode for Option> { +// fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { +// match self { +// Some(n) => n, +// None => cx.fragment_from_iter(None as Option), +// } +// } +// } + +impl IntoVNode for Option FnOnce(NodeFactory<'r>) -> VNode<'_> + '_>> { + fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { match self { - Some(n) => n, - None => cx.fragment_from_iter(None as Option), + Some(n) => { + // + // n.into_vnode(cx) + n(cx) + } + None => VNode::Fragment(VFragment { + children: &[], + is_static: false, + key: None, + }), } } } -impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for Option> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - match self { - Some(n) => n.into_vnode(cx), - None => cx.fragment_from_iter(None as Option), - } +impl IntoVNode for Box FnOnce(NodeFactory<'r>) -> VNode<'_> + '_> { + fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + self(cx) } } -impl IntoVNode<'_> for &'static str { +// impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for Option> { +// fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { +// match self { +// Some(n) => n.into_vnode(cx), +// None => cx.fragment_from_iter(None as Option), +// } +// } +// } + +impl IntoVNode for &'static str { fn into_vnode(self, cx: NodeFactory) -> VNode { cx.static_text(self) } } -impl IntoVNode<'_> for Arguments<'_> { +impl IntoVNode for Arguments<'_> { fn into_vnode(self, cx: NodeFactory) -> VNode { cx.text(self) } diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index 2f7977269..a11bea50e 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -25,15 +25,17 @@ impl TestDom { // where // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { - lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) + todo!() + // lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) } pub fn render<'a>(&'a self, lazy_nodes: Element<'a>) -> &'a VNode<'a> // where // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { - self.bump - .alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump))) + todo!() + // self.bump + // .alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump))) } pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { From 27d891934a70424b45e6278b7e2baaa2d1b78b35 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 30 Oct 2021 18:23:28 -0400 Subject: [PATCH 04/18] fix: use annotation method from rust/58052 to fix closure lifetimes --- packages/core-macro/src/rsx/body.rs | 15 +- packages/core/examples/alternative.rs | 14 +- packages/core/examples/borrowed.rs | 47 ++--- packages/core/examples/fragment_from_iter.rs | 75 ++++---- packages/core/examples/syntax.rs | 9 +- packages/core/examples/syntax2.rs | 180 +++++++++++++++++-- packages/core/examples/vdom_usage.rs | 3 +- packages/core/src/component.rs | 2 +- packages/core/src/context.rs | 3 +- packages/core/src/hooks.rs | 20 +-- packages/core/src/lazynodes.rs | 8 +- packages/core/src/lib.rs | 9 +- packages/core/src/nodes.rs | 100 +++++------ packages/core/tests/borrowedstate.rs | 12 +- packages/core/tests/display_vdom.rs | 16 +- 15 files changed, 333 insertions(+), 180 deletions(-) diff --git a/packages/core-macro/src/rsx/body.rs b/packages/core-macro/src/rsx/body.rs index a9a86daef..404d5083e 100644 --- a/packages/core-macro/src/rsx/body.rs +++ b/packages/core-macro/src/rsx/body.rs @@ -49,7 +49,7 @@ impl ToTokens for CallBody { match &self.custom_context { // The `in cx` pattern allows directly rendering Some(ident) => out_tokens.append_all(quote! { - #ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ + #ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner @@ -57,21 +57,12 @@ impl ToTokens for CallBody { }), // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - { - - let ___p: Box VNode + '_> = Box::new(move |__cx: NodeFactory|{ + NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; - #inner - }); - Some(___p) + }) } - // dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{ - // use dioxus_elements::{GlobalAttributes, SvgAttributes}; - - // #inner - // }) }), }; } diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index edf037f89..1de1c1e79 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -1,4 +1,7 @@ use dioxus_core::prelude::*; +use dioxus_core_macro::format_args_f; +use dioxus_core_macro::rsx; +use dioxus_html as dioxus_elements; fn main() { let mut dom = VirtualDom::new(EXAMPLE); @@ -7,9 +10,14 @@ fn main() { } pub static EXAMPLE: FC<()> = |(cx, _)| { - let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!())); + let list = (0..10).map(|_f| { + rsx! { + "{_f}" + } + }); + // let list = (0..10).map(|_f| Some(Box::new(move |_f| todo!()))); - cx.render(LazyNodes::new(move |cx| { + cx.render(Some(Box::new(move |cx| { cx.raw_element( "div", None, @@ -22,5 +30,5 @@ pub static EXAMPLE: FC<()> = |(cx, _)| { ], None, ) - })) + }))) }; diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index 302bb138c..239d5c348 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -23,28 +23,29 @@ struct ListItem { fn app<'a>(cx: Context<'a>, props: &AppProps) -> Element<'a> { // let (val, set_val) = use_state_classic(cx, || 0); - cx.render(LazyNodes::new(move |_nodecx| { - todo!() - // builder::ElementBuilder::new(_nodecx, "div") - // .iter_child({ - // cx.items.iter().map(|child| { - // builder::virtual_child( - // _nodecx, - // ChildItem, - // ChildProps { - // item: child.clone(), - // item_handler: set_val.clone(), - // }, - // None, - // &[], - // ) - // }) - // }) - // .iter_child([builder::ElementBuilder::new(_nodecx, "div") - // .iter_child([builder::text3(_nodecx.bump(), format_args!("{}", val))]) - // .finish()]) - // .finish() - })) + todo!() + // cx.render(LazyNodes::new(move |_nodecx| { + // todo!() + // // builder::ElementBuilder::new(_nodecx, "div") + // // .iter_child({ + // // cx.items.iter().map(|child| { + // // builder::virtual_child( + // // _nodecx, + // // ChildItem, + // // ChildProps { + // // item: child.clone(), + // // item_handler: set_val.clone(), + // // }, + // // None, + // // &[], + // // ) + // // }) + // // }) + // // .iter_child([builder::ElementBuilder::new(_nodecx, "div") + // // .iter_child([builder::text3(_nodecx.bump(), format_args!("{}", val))]) + // // .finish()]) + // // .finish() + // })) } // props should derive a partialeq implementation automatically, but implement ptr compare for & fields @@ -57,7 +58,7 @@ struct ChildProps { } fn ChildItem<'a>(cx: Context<'a>, props: &'a ChildProps) -> Element<'a> { - cx.render(LazyNodes::new(move |__cx| todo!())) + todo!() } impl PartialEq for ChildProps { diff --git a/packages/core/examples/fragment_from_iter.rs b/packages/core/examples/fragment_from_iter.rs index 6daa7a690..02f42b96d 100644 --- a/packages/core/examples/fragment_from_iter.rs +++ b/packages/core/examples/fragment_from_iter.rs @@ -3,43 +3,44 @@ use dioxus_core::prelude::*; fn main() {} fn app<'a>(cx: Context<'a>, props: &()) -> Element<'a> { - let vak = use_suspense( - cx, - || async {}, - |c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))), - ); + // let vak = use_suspense( + // cx, + // || async {}, + // |c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))), + // ); - let d1 = cx.render(LazyNodes::new(move |f| { - f.raw_element( - "div", - None, - [], - [], - [ - f.fragment_from_iter(vak), - f.text(format_args!("")), - f.text(format_args!("")), - f.text(format_args!("")), - f.text(format_args!("")), - ], - None, - ) - })); + // let d1 = cx.render(to_lazy_nodes(move |f| { + // f.raw_element( + // "div", + // None, + // [], + // [], + // [ + // // f.fragment_from_iter(vak), + // f.text(format_args!("")), + // f.text(format_args!("")), + // f.text(format_args!("")), + // f.text(format_args!("")), + // ], + // None, + // ) + // })); - cx.render(LazyNodes::new(move |f| { - f.raw_element( - "div", - None, - [], - [], - [ - f.text(format_args!("")), - f.text(format_args!("")), - f.text(format_args!("")), - f.text(format_args!("")), - d1.unwrap(), - ], - None, - ) - })) + todo!() + // cx.render(LazyNodes::new(move |f| { + // f.raw_element( + // "div", + // None, + // [], + // [], + // [ + // f.text(format_args!("")), + // f.text(format_args!("")), + // f.text(format_args!("")), + // f.text(format_args!("")), + // d1.unwrap(), + // ], + // None, + // ) + // })) } diff --git a/packages/core/examples/syntax.rs b/packages/core/examples/syntax.rs index 2e8447949..bda8c8f4d 100644 --- a/packages/core/examples/syntax.rs +++ b/packages/core/examples/syntax.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use dioxus::component::Scope; use dioxus::events::on::MouseEvent; -use dioxus::nodes::{IntoVNode, IntoVNodeList}; +use dioxus::nodes::IntoVNode; use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::*; @@ -28,7 +28,7 @@ fn html_usage() { .filter(|f| f.starts_with('b')) .map(|f| rsx!("hello {f}")); - let p = rsx!(div { {f} }); + // let p = rsx!(div { {f} }); } static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!")); @@ -98,11 +98,6 @@ impl<'a> Children<'a> { } } } -// impl<'a> IntoVNodeList<'a> for &Children<'a> { -// fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { -// todo!() -// } -// } static Bapp: FC<()> = |(cx, props)| { let name = cx.use_state(|| 0); diff --git a/packages/core/examples/syntax2.rs b/packages/core/examples/syntax2.rs index fd5e1eb26..be1a82a4a 100644 --- a/packages/core/examples/syntax2.rs +++ b/packages/core/examples/syntax2.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use dioxus::component::Scope; use dioxus::events::on::MouseEvent; -use dioxus::nodes::{IntoVNode, IntoVNodeList}; +use dioxus::nodes::{annotate_lazy, IntoVNode}; use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::*; @@ -29,10 +29,16 @@ fn t() { }; } -fn App((cx, props): Scope<()>) -> Element { +#[derive(PartialEq, Props)] +struct OurProps { + foo: String, +} + +fn App<'a>((cx, props): Scope<'a, OurProps>) -> Element<'a> { let a = rsx! { div { "asd" + "{props.foo}" } }; @@ -45,8 +51,12 @@ fn App((cx, props): Scope<()>) -> Element { }); let g = match "text" { - "a" => rsx!("asd"), - b => rsx!("asd"), + "a" => { + rsx!("asd") + } + _ => { + rsx!("asd") + } }; let items = ["bob", "bill", "jack"]; @@ -56,16 +66,164 @@ fn App((cx, props): Scope<()>) -> Element { .filter(|f| f.starts_with('b')) .map(|f| rsx!("hello {f}")); + // use dioxus_hooks; + // let g = use_state(|| "hello".to_string()); + + let s: &'a mut String = cx.use_hook(|_| String::new(), |f| f, |_| {}); + + /* + the final closure is allowed to borrow anything provided it + */ + + // cx.render({ + // let p: Option _>> = Some(Box::new(move |__cx: NodeFactory| { + // use dioxus_elements::{GlobalAttributes, SvgAttributes}; + + // let props = Child2Props { foo: s }; + // let ch: VNode = __cx.component(Child2, props, None, []); + // __cx.element( + // dioxus_elements::div, + // [], + // [], + // [ch], + // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])], + // None, + // ) + // })); + // p + // // let ___p: Box VNode> = Box::new(move |__cx| { + // // use dioxus_elements::{GlobalAttributes, SvgAttributes}; + + // // let props = Child2Props { foo: s }; + // // let ch: VNode = __cx.component(Child2, props, None, []); + // // __cx.element( + // // dioxus_elements::div, + // // [], + // // [], + // // [ch], + // // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])], + // // None, + // // ) + // // }); + // // Some(___p) + // }) + + let a = annotate_lazy(move |f| { + // + todo!() + }); + let b = annotate_lazy(move |f| { + // + f.text(format_args!("{}", props.foo)) + }); + + let c = annotate_lazy(move |f| { + // + f.component( + Child, + OurProps { + // + foo: "hello".to_string(), + }, + None, + [], + ) + }); + + let st: &'a String = cx.use_hook(|_| "hello".to_string(), |f| f, |_| {}); + + let d = annotate_lazy(move |f| { + // + f.component( + Child2, + Child2Props { + // + foo: st, + }, + None, + [], + ) + }); + + let e = match "asd" { + b => { + // + + annotate_lazy(move |f| { + // + f.text(format_args!("{}", props.foo)) + }) + } + a => { + // + + annotate_lazy(move |f| { + // + f.text(format_args!("{}", props.foo)) + }) + } + }; + + cx.render(annotate_lazy(move |f| { + // + + f.raw_element( + "div", + None, + [], + [], + [ + // + f.fragment_from_iter(a), + f.fragment_from_iter(b), + f.fragment_from_iter(c), + f.fragment_from_iter(e), + ], + None, + ) + // todo!() + })) + // cx.render(rsx! { + // div { + // div { + // {a} + // // {p} + // // {g} + // // {f} + // } + // // div { + // // "asd" + // // div { + // // "asd" + // // } + // // } + // // Child { + // // foo: "asd".to_string(), + // // } + // Child2 { + // foo: s, + // } + // } + // }) +} + +fn Child((cx, props): Scope) -> Element { cx.render(rsx! { div { - div { - {a} - {p} - {g} - {f} - } + div {} } }) } -// std::boxed::Box std::ops::FnOnce(dioxus_core::NodeFactory<'r>) -> dioxus_core::VNode<'_>> +#[derive(Props)] +struct Child2Props<'a> { + foo: &'a String, +} + +fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> { + cx.render(rsx! { + div { + // div {} + } + }) +} diff --git a/packages/core/examples/vdom_usage.rs b/packages/core/examples/vdom_usage.rs index caf58faaf..0f1d01404 100644 --- a/packages/core/examples/vdom_usage.rs +++ b/packages/core/examples/vdom_usage.rs @@ -4,7 +4,8 @@ use dioxus_core::prelude::*; #[async_std::main] async fn main() { - static App: FC<()> = |(cx, props)| cx.render(LazyNodes::new(|f| f.text(format_args!("hello")))); + static App: FC<()> = + |(cx, props)| cx.render(Some(Box::new(move |f| f.text(format_args!("hello"))))); let mut dom = VirtualDom::new(App); diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 0738999dc..24319d4a3 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -6,7 +6,7 @@ //! that ensures compile-time required and optional fields on cx. use crate::{ - innerlude::{Context, Element, LazyNodes, FC}, + innerlude::{Context, Element, FC}, NodeFactory, VNode, }; diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 33822ffde..ccbcfc92a 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -139,7 +139,8 @@ impl<'src> Context<'src> { ///``` pub fn render( self, - lazy_nodes: Option>, + lazy_nodes: Option) -> VNode<'src> + '_>>, + // lazy_nodes: Option VNode + '_>>, // lazy_nodes: Box) -> VNode<'src> + '_>, ) -> Option> { // pub fn render) -> VNode<'src>>( diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 8940671f4..1c52a3422 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -180,17 +180,17 @@ pub struct SuspendedContext<'a> { } impl<'src> SuspendedContext<'src> { + // // pub fn render( // pub fn render( - pub fn render( - // pub fn render) -> VNode<'src>>( - self, - lazy_nodes: LazyNodes<'_>, - // lazy_nodes: LazyNodes<'src, '_>, - ) -> Element<'src> { - let bump = &self.inner.scope.frames.wip_frame().bump; - todo!("suspense") - // Some(lazy_nodes.into_vnode(NodeFactory { bump })) - } + // // pub fn render) -> VNode<'src>>( + // self, + // lazy_nodes: LazyNodes<'_>, + // // lazy_nodes: LazyNodes<'src, '_>, + // ) -> Element<'src> { + // let bump = &self.inner.scope.frames.wip_frame().bump; + // todo!("suspense") + // // Some(lazy_nodes.into_vnode(NodeFactory { bump })) + // } } #[derive(Clone, Copy)] diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index e069ac372..3662d12ff 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -32,7 +32,13 @@ use crate::innerlude::{IntoVNode, NodeFactory, VNode}; // // inner: StackNodeStorage<'a>, // // inner: StackNodeStorage<'a>, // } -pub type LazyNodes<'b> = Box FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>; +// pub type LazyNodes<'b> = Box FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>; + +// pub fn to_lazy_nodes<'b>( +// f: impl for<'a> FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, +// ) -> Option> { +// Some(Box::new(f)) +// } type StackHeapSize = [usize; 12]; diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index df0804fc8..fc009b285 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -46,7 +46,6 @@ pub(crate) mod innerlude { pub use crate::heuristics::*; pub(crate) use crate::hooklist::*; pub use crate::hooks::*; - pub use crate::lazynodes::LazyNodes; pub use crate::mutations::*; pub use crate::nodes::*; pub(crate) use crate::resources::*; @@ -63,16 +62,16 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType, - Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, TaskHandle, - TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, + Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, MountType, Mutations, + NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, TaskHandle, TestDom, + ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, }; pub mod prelude { pub use crate::component::{fc_to_builder, Fragment, Properties, Scope}; pub use crate::context::Context; pub use crate::hooks::*; - pub use crate::innerlude::{DioxusElement, Element, LazyNodes, Mutations, NodeFactory, FC}; + pub use crate::innerlude::{DioxusElement, Element, Mutations, NodeFactory, FC}; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 964cdb42a..fbfa93d65 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,8 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::innerlude::{ - empty_cell, Context, Element, ElementId, LazyNodes, Properties, ScopeId, ScopeInner, - SuspendedContext, FC, + empty_cell, Context, Element, ElementId, Properties, ScopeId, ScopeInner, SuspendedContext, FC, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ @@ -329,21 +328,6 @@ impl<'a> NodeFactory<'a> { self.bump } - pub fn render_directly(&self, lazy_nodes: LazyNodes<'_>) -> Element<'a> -// pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, '_>) -> Element<'a> -// where - // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, - { - // pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> Element<'a> - // where - // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, - // { - // pub fn render_directly(&self, lazy_nodes: LazyNodes<'a>) -> Option> { - // Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump })) - - todo!() - } - pub fn unstable_place_holder() -> VNode<'static> { VNode::Text(VText { text: "", @@ -474,7 +458,8 @@ impl<'a> NodeFactory<'a> { ) -> VNode<'a> where P: Properties + 'a, - V: 'a + AsRef<[VNode<'a>]>, + V: AsRef<[VNode<'a>]> + 'a, + // V: 'a + AsRef<[VNode<'a>]>, { let bump = self.bump(); let children: &'a V = bump.alloc(children); @@ -559,8 +544,24 @@ impl<'a> NodeFactory<'a> { })) } - pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList) -> VNode<'a> { - let children = node_iter.into_vnode_list(self); + pub fn fragment_from_iter( + self, + node_iter: impl IntoIterator>, + ) -> VNode<'a> { + let bump = self.bump(); + let mut nodes = bumpalo::collections::Vec::new_in(bump); + + for node in node_iter { + nodes.push(node.into_vnode(self)); + } + + if nodes.is_empty() { + nodes.push(VNode::Anchor(VAnchor { + dom_id: empty_cell(), + })); + } + + let children = nodes.into_bump_slice(); // TODO // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning @@ -587,6 +588,15 @@ impl<'a> NodeFactory<'a> { is_static: false, }) } + + pub fn annotate_lazy<'z, 'b, F>( + f: F, + ) -> Option) -> VNode<'z> + 'b>> + where + F: FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b, + { + Some(Box::new(f)) + } } /// Trait implementations for use in the rsx! and html! macros. @@ -604,34 +614,8 @@ impl<'a> NodeFactory<'a> { /// /// As such, all node creation must go through the factory, which is only available in the component context. /// These strict requirements make it possible to manage lifetimes and state. -pub trait IntoVNode { - fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a>; -} - -pub trait IntoVNodeList { - fn into_vnode_list<'a>(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>]; -} - -impl IntoVNodeList for T -where - T: IntoIterator, - V: IntoVNode, -{ - fn into_vnode_list<'a>(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] { - let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); - - for node in self.into_iter() { - nodes.push(node.into_vnode(cx)); - } - - if nodes.is_empty() { - nodes.push(VNode::Anchor(VAnchor { - dom_id: empty_cell(), - })); - } - - nodes.into_bump_slice() - } +pub trait IntoVNode<'a> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; } /// Child nodes of the parent component. @@ -684,6 +668,7 @@ impl<'a> IntoIterator for VNode<'a> { } } +// TODO: do we even need this? It almost seems better not to // // For the case where a rendered VNode is passed into the rsx! macro through curly braces // impl IntoVNode for VNode<'_> { // fn into_vnode<'a>(self, _: NodeFactory<'a>) -> VNode<'a> { @@ -714,8 +699,15 @@ impl<'a> IntoIterator for VNode<'a> { // } // } -impl IntoVNode for Option FnOnce(NodeFactory<'r>) -> VNode<'_> + '_>> { - fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { +pub fn annotate_lazy<'a, 'b, F>(f: F) -> Option) -> VNode<'a> + 'b>> +where + F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, +{ + Some(Box::new(f)) +} + +impl<'a> IntoVNode<'a> for Option) -> VNode<'a> + '_>> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { match self { Some(n) => { // @@ -731,8 +723,8 @@ impl IntoVNode for Option FnOnce(NodeFactory<'r>) -> VNode<'_> + } } -impl IntoVNode for Box FnOnce(NodeFactory<'r>) -> VNode<'_> + '_> { - fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { +impl<'a> IntoVNode<'a> for Box) -> VNode<'a> + '_> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { self(cx) } } @@ -746,12 +738,12 @@ impl IntoVNode for Box FnOnce(NodeFactory<'r>) -> VNode<'_> + '_> { // } // } -impl IntoVNode for &'static str { +impl IntoVNode<'_> for &'static str { fn into_vnode(self, cx: NodeFactory) -> VNode { cx.static_text(self) } } -impl IntoVNode for Arguments<'_> { +impl IntoVNode<'_> for Arguments<'_> { fn into_vnode(self, cx: NodeFactory) -> VNode { cx.text(self) } diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index f0e3fa544..c46a8fe3c 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -13,14 +13,14 @@ fn test_borrowed_state() { fn Parent((cx, _): Scope<()>) -> Element { let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {}); - rsx! { + cx.render(rsx! { div { Child { name: value } Child { name: value } Child { name: value } Child { name: value } } - } + }) } #[derive(Props)] @@ -29,12 +29,12 @@ struct ChildProps<'a> { } fn Child<'a>((cx, props): Scope<'a, ChildProps>) -> Element<'a> { - rsx! { + cx.render(rsx! { div { h1 { "it's nested" } Child2 { name: props.name } } - } + }) } #[derive(Props)] @@ -43,7 +43,7 @@ struct Grandchild<'a> { } fn Child2<'a>((cx, props): Scope<'a, Grandchild>) -> Element<'a> { - rsx! { + cx.render(rsx! { div { "Hello {props.name}!" } - } + }) } diff --git a/packages/core/tests/display_vdom.rs b/packages/core/tests/display_vdom.rs index 734d0fd77..c7acc712c 100644 --- a/packages/core/tests/display_vdom.rs +++ b/packages/core/tests/display_vdom.rs @@ -14,23 +14,23 @@ mod test_logging; #[test] fn please_work() { static App: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { div { hidden: "true" "hello" div { "hello" } - Child {} - Child {} - Child {} + // Child {} + // Child {} + // Child {} } - div { "hello" } - } + // div { "hello" } + }) }; static Child: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { div { "child" } - } + }) }; let mut dom = VirtualDom::new(App); From f70b5b4e4acf589372f769153968922d2fcfedf1 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 30 Oct 2021 20:28:58 -0400 Subject: [PATCH 05/18] wip: try to get lifetimes passed in properly --- packages/core/Cargo.toml | 4 +- packages/core/examples/syntax2.rs | 438 +++++++++++++++++++----------- packages/core/src/bumpframe.rs | 19 +- packages/core/src/context.rs | 7 + packages/core/src/diff.rs | 5 + packages/core/src/nodes.rs | 23 +- 6 files changed, 312 insertions(+), 184 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 9e7dcb279..ea0ae505d 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -49,8 +49,8 @@ fern = { version = "0.6.0", features = ["colored"] } rand = { version = "0.8.4", features = ["small_rng"] } simple_logger = "1.13.0" dioxus-core-macro = { path = "../core-macro", version = "0.1.2" } -async-std = { version = "1.9.0", features = ["attributes"] } -criterion = "0.3.5" +# async-std = { version = "1.9.0", features = ["attributes"] } +# criterion = "0.3.5" [features] default = [] diff --git a/packages/core/examples/syntax2.rs b/packages/core/examples/syntax2.rs index be1a82a4a..d3af16013 100644 --- a/packages/core/examples/syntax2.rs +++ b/packages/core/examples/syntax2.rs @@ -1,8 +1,10 @@ +use std::borrow::BorrowMut; +use std::cell::{Cell, RefCell}; use std::marker::PhantomData; use dioxus::component::Scope; use dioxus::events::on::MouseEvent; -use dioxus::nodes::{annotate_lazy, IntoVNode}; +use dioxus::nodes::{annotate_lazy, IntoVNode, VComponent, VFragment, VText}; use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::*; @@ -29,191 +31,190 @@ fn t() { }; } -#[derive(PartialEq, Props)] -struct OurProps { - foo: String, -} +// #[derive(PartialEq, Props)] +// struct OurProps { +// foo: String, +// } -fn App<'a>((cx, props): Scope<'a, OurProps>) -> Element<'a> { - let a = rsx! { - div { - "asd" - "{props.foo}" - } - }; +// fn App<'a>((cx, props): Scope<'a, OurProps>) -> Element<'a> { +// let a = rsx! { +// div { +// "asd" +// "{props.foo}" +// } +// }; - let p = (0..10).map(|f| { - rsx! { - div { +// let p = (0..10).map(|f| { +// rsx! { +// div { - } - } - }); +// } +// } +// }); - let g = match "text" { - "a" => { - rsx!("asd") - } - _ => { - rsx!("asd") - } - }; +// let g = match "text" { +// "a" => { +// rsx!("asd") +// } +// _ => { +// rsx!("asd") +// } +// }; - let items = ["bob", "bill", "jack"]; +// let items = ["bob", "bill", "jack"]; - let f = items - .iter() - .filter(|f| f.starts_with('b')) - .map(|f| rsx!("hello {f}")); +// let f = items +// .iter() +// .filter(|f| f.starts_with('b')) +// .map(|f| rsx!("hello {f}")); - // use dioxus_hooks; - // let g = use_state(|| "hello".to_string()); +// // use dioxus_hooks; +// // let g = use_state(|| "hello".to_string()); - let s: &'a mut String = cx.use_hook(|_| String::new(), |f| f, |_| {}); +// let s: &'a mut String = cx.use_hook(|_| String::new(), |f| f, |_| {}); - /* - the final closure is allowed to borrow anything provided it - */ +// /* +// the final closure is allowed to borrow anything provided it +// */ +// // cx.render({ +// // let p: Option _>> = Some(Box::new(move |__cx: NodeFactory| { +// // use dioxus_elements::{GlobalAttributes, SvgAttributes}; - // cx.render({ - // let p: Option _>> = Some(Box::new(move |__cx: NodeFactory| { - // use dioxus_elements::{GlobalAttributes, SvgAttributes}; +// // let props = Child2Props { foo: s }; +// // let ch: VNode = __cx.component(Child2, props, None, []); +// // __cx.element( +// // dioxus_elements::div, +// // [], +// // [], +// // [ch], +// // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])], +// // None, +// // ) +// // })); +// // p +// // // let ___p: Box VNode> = Box::new(move |__cx| { +// // // use dioxus_elements::{GlobalAttributes, SvgAttributes}; - // let props = Child2Props { foo: s }; - // let ch: VNode = __cx.component(Child2, props, None, []); - // __cx.element( - // dioxus_elements::div, - // [], - // [], - // [ch], - // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])], - // None, - // ) - // })); - // p - // // let ___p: Box VNode> = Box::new(move |__cx| { - // // use dioxus_elements::{GlobalAttributes, SvgAttributes}; +// // // let props = Child2Props { foo: s }; +// // // let ch: VNode = __cx.component(Child2, props, None, []); +// // // __cx.element( +// // // dioxus_elements::div, +// // // [], +// // // [], +// // // [ch], +// // // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])], +// // // None, +// // // ) +// // // }); +// // // Some(___p) +// // }) - // // let props = Child2Props { foo: s }; - // // let ch: VNode = __cx.component(Child2, props, None, []); - // // __cx.element( - // // dioxus_elements::div, - // // [], - // // [], - // // [ch], - // // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])], - // // None, - // // ) - // // }); - // // Some(___p) - // }) +// let a = annotate_lazy(move |f| { +// // +// todo!() +// }); +// let b = annotate_lazy(move |f| { +// // +// f.text(format_args!("{}", props.foo)) +// }); - let a = annotate_lazy(move |f| { - // - todo!() - }); - let b = annotate_lazy(move |f| { - // - f.text(format_args!("{}", props.foo)) - }); +// let c = annotate_lazy(move |f| { +// // +// f.component( +// Child, +// OurProps { +// // +// foo: "hello".to_string(), +// }, +// None, +// [], +// ) +// }); - let c = annotate_lazy(move |f| { - // - f.component( - Child, - OurProps { - // - foo: "hello".to_string(), - }, - None, - [], - ) - }); +// let st: &'a String = cx.use_hook(|_| "hello".to_string(), |f| f, |_| {}); - let st: &'a String = cx.use_hook(|_| "hello".to_string(), |f| f, |_| {}); +// let d = annotate_lazy(move |f| { +// // +// f.component( +// Child2, +// Child2Props { +// // +// foo: st, +// }, +// None, +// [], +// ) +// }); - let d = annotate_lazy(move |f| { - // - f.component( - Child2, - Child2Props { - // - foo: st, - }, - None, - [], - ) - }); +// let e = match "asd" { +// b => { +// // - let e = match "asd" { - b => { - // +// annotate_lazy(move |f| { +// // +// f.text(format_args!("{}", props.foo)) +// }) +// } +// a => { +// // - annotate_lazy(move |f| { - // - f.text(format_args!("{}", props.foo)) - }) - } - a => { - // +// annotate_lazy(move |f| { +// // +// f.text(format_args!("{}", props.foo)) +// }) +// } +// }; - annotate_lazy(move |f| { - // - f.text(format_args!("{}", props.foo)) - }) - } - }; +// cx.render(annotate_lazy(move |f| { +// // - cx.render(annotate_lazy(move |f| { - // +// f.raw_element( +// "div", +// None, +// [], +// [], +// [ +// // +// f.fragment_from_iter(a), +// f.fragment_from_iter(b), +// f.fragment_from_iter(c), +// f.fragment_from_iter(e), +// ], +// None, +// ) +// // todo!() +// })) +// // cx.render(rsx! { +// // div { +// // div { +// // {a} +// // // {p} +// // // {g} +// // // {f} +// // } +// // // div { +// // // "asd" +// // // div { +// // // "asd" +// // // } +// // // } +// // // Child { +// // // foo: "asd".to_string(), +// // // } +// // Child2 { +// // foo: s, +// // } +// // } +// // }) +// } - f.raw_element( - "div", - None, - [], - [], - [ - // - f.fragment_from_iter(a), - f.fragment_from_iter(b), - f.fragment_from_iter(c), - f.fragment_from_iter(e), - ], - None, - ) - // todo!() - })) - // cx.render(rsx! { - // div { - // div { - // {a} - // // {p} - // // {g} - // // {f} - // } - // // div { - // // "asd" - // // div { - // // "asd" - // // } - // // } - // // Child { - // // foo: "asd".to_string(), - // // } - // Child2 { - // foo: s, - // } - // } - // }) -} - -fn Child((cx, props): Scope) -> Element { - cx.render(rsx! { - div { - div {} - } - }) -} +// fn Child((cx, props): Scope) -> Element { +// cx.render(rsx! { +// div { +// div {} +// } +// }) +// } #[derive(Props)] struct Child2Props<'a> { @@ -221,9 +222,114 @@ struct Child2Props<'a> { } fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> { + let node = cx + .render(rsx! { + div { + + } + }) + .unwrap(); + + let b = cx.bump(); + let node: &'a VNode<'a> = b.alloc(node); + + let children = ChildList { pthru: node }; + + // let c = VComponent { + // key: todo!(), + // associated_scope: todo!(), + // is_static: todo!(), + // user_fc: todo!(), + // caller: todo!(), + // children: todo!(), + // comparator: todo!(), + // drop_props: todo!(), + // can_memoize: todo!(), + // raw_props: todo!(), + // }; + + // Vcomp + // - borrowed + // - memoized + + cx.render({ + NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { + use dioxus_elements::{GlobalAttributes, SvgAttributes}; + __cx.element( + dioxus_elements::div, + [], + [], + [], + // [__cx.component( + // ChildrenComp, + // { + // let mut __manual_props = ChildrenTest { node: children }; + // __manual_props + // }, + // None, + // [], + // )], + None, + ) + }) + }) + // cx.render(rsx! { + // div { + // // div {} + + // ChildrenComp { + // // node: children + // ..ChildrenTest { + // node: children, + // } + // } + // } + // }) +} + +#[derive(Props)] +struct ChildrenTest<'a> { + node: ChildList<'a>, +} + +struct ChildList<'a> { + pthru: &'a VNode<'a>, +} + +impl<'a> Clone for ChildList<'a> { + fn clone(&self) -> Self { + Self { pthru: self.pthru } + } +} +impl<'a> Copy for ChildList<'a> {} + +impl<'a> IntoVNode<'a> for ChildList<'a> { + fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { + match self.pthru { + VNode::Text(f) => VNode::Text(*f), + VNode::Element(e) => VNode::Element(*e), + VNode::Component(c) => VNode::Component(*c), + VNode::Suspended(s) => VNode::Suspended(*s), + VNode::Anchor(a) => VNode::Anchor(a), + VNode::Fragment(f) => VNode::Fragment(VFragment { + children: f.children, + is_static: f.is_static, + key: f.key, + }), + } + } +} + +fn ChildrenComp<'a>((cx, props): Scope<'a, ChildrenTest<'a>>) -> Element<'a> { cx.render(rsx! { div { - // div {} + div { + + // if the node's id is already assigned, then it's being passed in as a child + // in these instances, we don't worry about re-checking the node? + + {Some(props.node)} + } } }) } diff --git a/packages/core/src/bumpframe.rs b/packages/core/src/bumpframe.rs index a696d82b5..ac1c66189 100644 --- a/packages/core/src/bumpframe.rs +++ b/packages/core/src/bumpframe.rs @@ -21,16 +21,27 @@ pub(crate) struct BumpFrame { impl ActiveFrame { pub fn new() -> Self { + let b1 = Bump::new(); + let b2 = Bump::new(); + let frame_a = BumpFrame { - bump: Bump::new(), - head_node: NodeFactory::unstable_place_holder(), + head_node: VNode::Fragment(VFragment { + key: None, + children: &[], + is_static: false, + }), + bump: b1, #[cfg(test)] _name: "wip", }; let frame_b = BumpFrame { - bump: Bump::new(), - head_node: NodeFactory::unstable_place_holder(), + head_node: VNode::Fragment(VFragment { + key: None, + children: &[], + is_static: false, + }), + bump: b2, #[cfg(test)] _name: "fin", diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index ccbcfc92a..58ec04680 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -4,6 +4,8 @@ //! //! We unsafely implement `send` for the VirtualDom, but those guarantees can only be +use bumpalo::Bump; + use crate::innerlude::*; use std::{any::TypeId, ops::Deref, rc::Rc}; @@ -122,6 +124,11 @@ impl<'src> Context<'src> { self.scope.our_arena_idx } + pub fn bump(&self) -> &'src Bump { + let bump = &self.scope.frames.wip_frame().bump; + bump + } + /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. /// /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned. diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 9788a75c1..c52f54284 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -421,6 +421,10 @@ impl<'bump> DiffMachine<'bump> { ) => self .stack .create_node(new_node, MountType::Replace { old: old_node }), + + (Placeholder, _) | (_, Placeholder) => { + unreachable!("placeholder should never be diffed"); + } } } @@ -1125,6 +1129,7 @@ impl<'bump> DiffMachine<'bump> { self.remove_nodes(e.children, false); } + VNode::Fragment(f) => { self.remove_nodes(f.children, gen_muts); } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index fbfa93d65..fb250f1c4 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -10,7 +10,6 @@ use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ cell::{Cell, RefCell}, fmt::{Arguments, Debug, Formatter}, - marker::PhantomData, }; /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. @@ -33,7 +32,7 @@ pub enum VNode<'src> { /// assert_eq!(vtext.is_static, true); /// } /// ``` - Text(VText<'src>), + Text(&'src VText<'src>), /// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children. /// @@ -116,7 +115,7 @@ pub enum VNode<'src> { /// assert_eq!(root, VNode::Anchor); /// } /// ``` - Anchor(VAnchor), + Anchor(&'src VAnchor), } impl<'src> VNode<'src> { @@ -328,21 +327,21 @@ impl<'a> NodeFactory<'a> { self.bump } - pub fn unstable_place_holder() -> VNode<'static> { - VNode::Text(VText { + pub(crate) fn unstable_place_holder(&self) -> VNode { + VNode::Text(self.bump.alloc(VText { text: "", dom_id: empty_cell(), is_static: true, - }) + })) } /// Directly pass in text blocks without the need to use the format_args macro. pub fn static_text(&self, text: &'static str) -> VNode<'a> { - VNode::Text(VText { + VNode::Text(self.bump.alloc(VText { dom_id: empty_cell(), text, is_static: true, - }) + })) } /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static @@ -365,11 +364,11 @@ impl<'a> NodeFactory<'a> { pub fn text(&self, args: Arguments) -> VNode<'a> { let (text, is_static) = self.raw_text(args); - VNode::Text(VText { + VNode::Text(self.bump.alloc(VText { text, is_static, dom_id: empty_cell(), - }) + })) } pub fn element( @@ -556,9 +555,9 @@ impl<'a> NodeFactory<'a> { } if nodes.is_empty() { - nodes.push(VNode::Anchor(VAnchor { + nodes.push(VNode::Anchor(bump.alloc(VAnchor { dom_id: empty_cell(), - })); + }))); } let children = nodes.into_bump_slice(); From 6b2645fd80ab20153e7bb51887443e994fbab5cb Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 30 Oct 2021 21:07:47 -0400 Subject: [PATCH 06/18] feat: improve FC handling to allow lifetimes of parent --- .vscode/settings.json | 2 +- packages/core/examples/syntax2.rs | 78 ++++++++++++++++++------------- packages/core/src/nodes.rs | 29 ++++++++++-- 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b3bde867c..6b608c765 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.cargo.allFeatures": true + // "rust-analyzer.cargo.allFeatures": true } diff --git a/packages/core/examples/syntax2.rs b/packages/core/examples/syntax2.rs index d3af16013..6ee3985d3 100644 --- a/packages/core/examples/syntax2.rs +++ b/packages/core/examples/syntax2.rs @@ -252,39 +252,49 @@ fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> { // - borrowed // - memoized - cx.render({ - NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { - use dioxus_elements::{GlobalAttributes, SvgAttributes}; - __cx.element( - dioxus_elements::div, - [], - [], - [], - // [__cx.component( - // ChildrenComp, - // { - // let mut __manual_props = ChildrenTest { node: children }; - // __manual_props - // }, - // None, - // [], - // )], - None, - ) - }) - }) - // cx.render(rsx! { - // div { - // // div {} - - // ChildrenComp { - // // node: children - // ..ChildrenTest { - // node: children, - // } - // } - // } + // cx.render({ + // NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { + // use dioxus_elements::{GlobalAttributes, SvgAttributes}; + // __cx.element( + // dioxus_elements::div, + // [], + // [], + // [ + // __cx.component(ChildrenMemo, (), None, []), + // __cx.component( + // ChildrenComp, + // // + // ChildrenTest { node: children }, + // None, + // [], + // ), + // // { + // // let _props: &_ = __cx.bump().alloc(ChildrenTest { node: children }); + // // __cx.component_v2_borrowed( + // // // + // // move |c| ChildrenComp((c, _props)), + // // ChildrenComp, + // // _props, + // // ) + // // }, + // // { + // // let _props: &_ = __cx.bump().alloc(()); + // // __cx.component_v2_borrowed(move |c| ChildrenMemo((c, _props))) + // // }, + // ], + // None, + // ) + // }) // }) + cx.render(rsx! { + div { + ChildrenComp { + ..ChildrenTest { + node: children, + } + } + } + }) } #[derive(Props)] @@ -333,3 +343,7 @@ fn ChildrenComp<'a>((cx, props): Scope<'a, ChildrenTest<'a>>) -> Element<'a> { } }) } + +fn ChildrenMemo((cx, props): Scope<()>) -> Element { + todo!() +} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index fb250f1c4..255f7ca74 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,8 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::innerlude::{ - empty_cell, Context, Element, ElementId, Properties, ScopeId, ScopeInner, SuspendedContext, FC, + empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner, + SuspendedContext, FC, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ @@ -448,9 +449,26 @@ impl<'a> NodeFactory<'a> { } } + pub fn component_v2_borrowed( + self, + f: impl Fn(Context<'a>) -> Option> + 'a, + fc: fn(Scope<'a, P>) -> Element<'a>, + p: &'a P, + ) -> VNode<'a> { + // + todo!() + } + // pub fn component_v2_memoized( + // self, + // f: impl for<'b> Fn(Context<'b>) -> Option> + 'static, + // ) -> VNode<'a> { + // // + // todo!() + // } + pub fn component( &self, - component: FC

, + component: fn(Scope<'a, P>) -> Element<'a>, props: P, key: Option, children: V, @@ -458,7 +476,6 @@ impl<'a> NodeFactory<'a> { where P: Properties + 'a, V: AsRef<[VNode<'a>]> + 'a, - // V: 'a + AsRef<[VNode<'a>]>, { let bump = self.bump(); let children: &'a V = bump.alloc(children); @@ -523,7 +540,11 @@ impl<'a> NodeFactory<'a> { bump.alloc(move |scope: &ScopeInner| -> Element { log::debug!("calling component renderr {:?}", scope.our_arena_idx); let props: &'_ P = unsafe { &*(raw_props as *const P) }; - let res: Element = component((Context { scope }, props)); + + let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) }; + let s: Scope<'a, P> = (Context { scope: scp }, props); + + let res: Element = component(s); unsafe { std::mem::transmute(res) } }); From 7b97e009ec74cd2f134bbb8e576404e7a964db74 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 02:22:08 -0400 Subject: [PATCH 07/18] wip: remove cx.children. start to move towards a "children" field --- packages/core/examples/syntax2.rs | 69 +++--------------- packages/core/examples/vdom_usage.rs | 6 +- packages/core/src/component.rs | 2 +- packages/core/src/context.rs | 63 +---------------- packages/core/src/diff.rs | 3 +- packages/core/src/nodes.rs | 102 ++++++--------------------- packages/core/src/scope.rs | 13 ---- packages/core/src/test_dom.rs | 21 ++++-- packages/core/src/virtual_dom.rs | 3 +- packages/core/tests/create_dom.rs | 46 +++++++----- packages/core/tests/diffing.rs | 29 ++++---- packages/core/tests/lifecycle.rs | 2 +- packages/core/tests/sharedstate.rs | 4 +- packages/core/tests/vdom_rebuild.rs | 27 +++---- 14 files changed, 114 insertions(+), 276 deletions(-) diff --git a/packages/core/examples/syntax2.rs b/packages/core/examples/syntax2.rs index 6ee3985d3..8ae715728 100644 --- a/packages/core/examples/syntax2.rs +++ b/packages/core/examples/syntax2.rs @@ -233,65 +233,12 @@ fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> { let b = cx.bump(); let node: &'a VNode<'a> = b.alloc(node); - let children = ChildList { pthru: node }; + let children = Children { pthru: node }; - // let c = VComponent { - // key: todo!(), - // associated_scope: todo!(), - // is_static: todo!(), - // user_fc: todo!(), - // caller: todo!(), - // children: todo!(), - // comparator: todo!(), - // drop_props: todo!(), - // can_memoize: todo!(), - // raw_props: todo!(), - // }; - - // Vcomp - // - borrowed - // - memoized - - // cx.render({ - // NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { - // use dioxus_elements::{GlobalAttributes, SvgAttributes}; - // __cx.element( - // dioxus_elements::div, - // [], - // [], - // [ - // __cx.component(ChildrenMemo, (), None, []), - // __cx.component( - // ChildrenComp, - // // - // ChildrenTest { node: children }, - // None, - // [], - // ), - // // { - // // let _props: &_ = __cx.bump().alloc(ChildrenTest { node: children }); - // // __cx.component_v2_borrowed( - // // // - // // move |c| ChildrenComp((c, _props)), - // // ChildrenComp, - // // _props, - // // ) - // // }, - // // { - // // let _props: &_ = __cx.bump().alloc(()); - // // __cx.component_v2_borrowed(move |c| ChildrenMemo((c, _props))) - // // }, - // ], - // None, - // ) - // }) - // }) cx.render(rsx! { div { ChildrenComp { - ..ChildrenTest { - node: children, - } + children: children } } }) @@ -299,21 +246,21 @@ fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> { #[derive(Props)] struct ChildrenTest<'a> { - node: ChildList<'a>, + children: Children<'a>, } -struct ChildList<'a> { +struct Children<'a> { pthru: &'a VNode<'a>, } -impl<'a> Clone for ChildList<'a> { +impl<'a> Clone for Children<'a> { fn clone(&self) -> Self { Self { pthru: self.pthru } } } -impl<'a> Copy for ChildList<'a> {} +impl<'a> Copy for Children<'a> {} -impl<'a> IntoVNode<'a> for ChildList<'a> { +impl<'a> IntoVNode<'a> for Children<'a> { fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { match self.pthru { VNode::Text(f) => VNode::Text(*f), @@ -338,7 +285,7 @@ fn ChildrenComp<'a>((cx, props): Scope<'a, ChildrenTest<'a>>) -> Element<'a> { // if the node's id is already assigned, then it's being passed in as a child // in these instances, we don't worry about re-checking the node? - {Some(props.node)} + {Some(props.children)} } } }) diff --git a/packages/core/examples/vdom_usage.rs b/packages/core/examples/vdom_usage.rs index 0f1d01404..07ec5f205 100644 --- a/packages/core/examples/vdom_usage.rs +++ b/packages/core/examples/vdom_usage.rs @@ -2,8 +2,8 @@ use std::time::Duration; use dioxus_core::prelude::*; -#[async_std::main] -async fn main() { +// #[async_std::main] +fn main() { static App: FC<()> = |(cx, props)| cx.render(Some(Box::new(move |f| f.text(format_args!("hello"))))); @@ -11,7 +11,7 @@ async fn main() { dom.rebuild(); - let deadline = async_std::task::sleep(Duration::from_millis(50)); + // let deadline = async_std::task::sleep(Duration::from_millis(50)); // let _fut = dom.run_with_deadline(|| deadline.); } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 24319d4a3..ce5fd2b38 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -138,6 +138,6 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder(_: FC) -> T::Builder { +pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder { T::builder() } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 58ec04680..b9661b294 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -54,49 +54,6 @@ impl<'a> Deref for Context<'a> { } impl<'src> Context<'src> { - /// Access the children elements passed into the component - /// - /// This enables patterns where a component is passed children from its parent. - /// - /// ## Details - /// - /// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions - /// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure - /// on the props that takes Context. - /// - /// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other - /// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's - /// props are valid for the static lifetime. - /// - /// ## Example - /// - /// ```rust - /// const App: FC<()> = |(cx, props)|{ - /// cx.render(rsx!{ - /// CustomCard { - /// h1 {} - /// p {} - /// } - /// }) - /// } - /// - /// const CustomCard: FC<()> = |(cx, props)|{ - /// cx.render(rsx!{ - /// div { - /// h1 {"Title card"} - /// {cx.children()} - /// } - /// }) - /// } - /// ``` - /// - /// ## Notes: - /// - /// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime. - pub fn children(&self) -> ScopeChildren<'src> { - self.scope.child_nodes() - } - /// Create a subscription that schedules a future render for the reference component /// /// ## Notice: you should prefer using prepare_update and get_scope_id @@ -147,28 +104,10 @@ impl<'src> Context<'src> { pub fn render( self, lazy_nodes: Option) -> VNode<'src> + '_>>, - // lazy_nodes: Option VNode + '_>>, - // lazy_nodes: Box) -> VNode<'src> + '_>, ) -> Option> { - // pub fn render) -> VNode<'src>>( - // self, - // lazy_nodes: LazyNodes<'src, F>, - // ) -> Option> { let bump = &self.scope.frames.wip_frame().bump; - // Some(lazy_nodes.into_vnode(NodeFactory { bump })) - let factory = NodeFactory { bump }; - // let vnode = lazy_nodes(factory); - - match lazy_nodes { - Some(f) => Some(f(factory)), - None => None, - } - - // match lazy_nodes { - // None => todo!(), - // } - // Some(lazy_nodes) + lazy_nodes.map(|f| f(factory)) } /// `submit_task` will submit the future to be polled. diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index c52f54284..eddbb447a 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -348,7 +348,6 @@ impl<'bump> DiffMachine<'bump> { Some(parent_idx), parent_scope.height + 1, parent_scope.subtree(), - ScopeChildren(vcomponent.children), shared, ) }); @@ -555,7 +554,7 @@ impl<'bump> DiffMachine<'bump> { // make sure the component's caller function is up to date let scope = self.vdom.get_scope_mut(scope_addr).unwrap(); - scope.update_scope_dependencies(new.caller, ScopeChildren(new.children)); + scope.update_scope_dependencies(new.caller); // React doesn't automatically memoize, but we do. let props_are_the_same = old.comparator.unwrap(); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 255f7ca74..894210dac 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -638,47 +638,6 @@ pub trait IntoVNode<'a> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; } -/// Child nodes of the parent component. -/// -/// # Example -/// -/// ```rust -/// let children = cx.children(); -/// let first_node = &children[0]; -/// rsx!{ -/// h1 { {first_node} } -/// p { {&children[1..]} } -/// } -/// ``` -/// -pub struct ScopeChildren<'a>(pub &'a [VNode<'a>]); - -impl Copy for ScopeChildren<'_> {} - -impl<'a> Clone for ScopeChildren<'a> { - fn clone(&self) -> Self { - ScopeChildren(self.0) - } -} - -impl ScopeChildren<'_> { - // dangerous method - used to fix the associated lifetime - pub(crate) unsafe fn extend_lifetime(self) -> ScopeChildren<'static> { - std::mem::transmute(self) - } - - // dangerous method - used to fix the associated lifetime - pub(crate) unsafe fn shorten_lifetime<'a>(self) -> ScopeChildren<'a> { - std::mem::transmute(self) - } -} - -// impl IntoVNodeList for ScopeChildren<'_> { -// fn into_vnode_list<'a>(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] { -// self.0 -// } -// } - // For the case where a rendered VNode is passed into the rsx! macro through curly braces impl<'a> IntoIterator for VNode<'a> { type Item = VNode<'a>; @@ -690,34 +649,31 @@ impl<'a> IntoIterator for VNode<'a> { // TODO: do we even need this? It almost seems better not to // // For the case where a rendered VNode is passed into the rsx! macro through curly braces -// impl IntoVNode for VNode<'_> { -// fn into_vnode<'a>(self, _: NodeFactory<'a>) -> VNode<'a> { -// self -// } -// } +impl<'a> IntoVNode<'a> for VNode<'a> { + fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { + self + } +} // Conveniently, we also support "null" (nothing) passed in -// impl IntoVNode for () { -// fn into_vnode(self, cx: NodeFactory) -> VNode { -// cx.fragment_from_iter(None as Option) -// } -// } +impl IntoVNode<'_> for () { + fn into_vnode(self, cx: NodeFactory) -> VNode { + cx.fragment_from_iter(None as Option) + } +} -// // Conveniently, we also support "None" -// impl IntoVNode for Option<()> { -// fn into_vnode(self, cx: NodeFactory) -> VNode { -// cx.fragment_from_iter(None as Option) -// } -// } +// Conveniently, we also support "None" +impl IntoVNode<'_> for Option<()> { + fn into_vnode(self, cx: NodeFactory) -> VNode { + cx.fragment_from_iter(None as Option) + } +} -// impl IntoVNode for Option> { -// fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { -// match self { -// Some(n) => n, -// None => cx.fragment_from_iter(None as Option), -// } -// } -// } +impl<'a> IntoVNode<'a> for Option> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { + self.unwrap_or_else(|| cx.fragment_from_iter(None as Option)) + } +} pub fn annotate_lazy<'a, 'b, F>(f: F) -> Option) -> VNode<'a> + 'b>> where @@ -729,11 +685,7 @@ where impl<'a> IntoVNode<'a> for Option) -> VNode<'a> + '_>> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { match self { - Some(n) => { - // - // n.into_vnode(cx) - n(cx) - } + Some(lazy) => lazy(cx), None => VNode::Fragment(VFragment { children: &[], is_static: false, @@ -749,20 +701,12 @@ impl<'a> IntoVNode<'a> for Box) -> VNode<'a> + '_> { } } -// impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for Option> { -// fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { -// match self { -// Some(n) => n.into_vnode(cx), -// None => cx.fragment_from_iter(None as Option), -// } -// } -// } - impl IntoVNode<'_> for &'static str { fn into_vnode(self, cx: NodeFactory) -> VNode { cx.static_text(self) } } + impl IntoVNode<'_> for Arguments<'_> { fn into_vnode(self, cx: NodeFactory) -> VNode { cx.text(self) diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index bb20a32f1..97333cd0d 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -29,7 +29,6 @@ pub struct ScopeInner { // Nodes pub(crate) frames: ActiveFrame, pub(crate) caller: *const dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, - pub(crate) child_nodes: ScopeChildren<'static>, /* we care about: @@ -181,11 +180,8 @@ impl ScopeInner { parent_idx: Option, height: u32, subtree: u32, - child_nodes: ScopeChildren, shared: EventChannel, ) -> Self { - let child_nodes = unsafe { child_nodes.extend_lifetime() }; - let schedule_any_update = shared.schedule_any_immediate.clone(); let memoized_updater = Rc::new(move || schedule_any_update(our_arena_idx)); @@ -198,7 +194,6 @@ impl ScopeInner { Self { memoized_updater, shared, - child_nodes, caller, parent_idx, our_arena_idx, @@ -218,18 +213,10 @@ impl ScopeInner { pub(crate) fn update_scope_dependencies<'creator_node>( &mut self, caller: &'creator_node dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, - child_nodes: ScopeChildren, ) { log::debug!("Updating scope dependencies {:?}", self.our_arena_idx); let caller = caller as *const _; self.caller = unsafe { std::mem::transmute(caller) }; - - let child_nodes = unsafe { child_nodes.extend_lifetime() }; - self.child_nodes = child_nodes; - } - - pub(crate) fn child_nodes(&self) -> ScopeChildren { - unsafe { self.child_nodes.shorten_lifetime() } } /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index a11bea50e..fa3a2649e 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -21,7 +21,10 @@ impl TestDom { NodeFactory::new(&self.bump) } - pub fn render_direct<'a>(&'a self, lazy_nodes: Element<'a>) -> VNode<'a> + pub fn render_direct<'a>( + &'a self, + lazy_nodes: Option) -> VNode<'a> + '_>>, + ) -> VNode<'a> // where // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { @@ -29,7 +32,10 @@ impl TestDom { // lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) } - pub fn render<'a>(&'a self, lazy_nodes: Element<'a>) -> &'a VNode<'a> + pub fn render<'a>( + &'a self, + lazy_nodes: Option) -> VNode<'a> + '_>>, + ) -> &'a VNode<'a> // where // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, { @@ -45,7 +51,10 @@ impl TestDom { machine.mutations } - pub fn create<'a>(&'a self, left: Element<'a>) -> Mutations<'a> + pub fn create<'a>( + &'a self, + left: Option) -> VNode<'a> + '_>>, + ) -> Mutations<'a> // pub fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a> // where // F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, @@ -63,10 +72,8 @@ impl TestDom { pub fn lazy_diff<'a>( &'a self, - left: Element<'a>, - right: Element<'a>, - // left: LazyNodes<'a, F1>, - // right: LazyNodes<'a, F2>, + left: Option) -> VNode<'a> + '_>>, + right: Option) -> VNode<'a> + '_>>, ) -> (Mutations<'a>, Mutations<'a>) // where // F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 3092d60b2..205854a4b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -158,7 +158,6 @@ impl VirtualDom { None, 0, 0, - ScopeChildren(&[]), scheduler.pool.channel.clone(), ) }); @@ -226,7 +225,7 @@ impl VirtualDom { std::mem::transmute(root((Context { scope }, props))) }); - root_scope.update_scope_dependencies(&root_caller, ScopeChildren(&[])); + root_scope.update_scope_dependencies(&root_caller); drop(root_props); diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 8339ceb9f..dbfc56539 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -22,13 +22,13 @@ fn new_dom(app: FC

, props: P) -> VirtualDom { #[test] fn test_original_diff() { static APP: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { div { div { "Hello, world!" } } - } + }) }; let mut dom = new_dom(APP, ()); @@ -58,7 +58,7 @@ fn test_original_diff() { #[test] fn create() { static APP: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { div { div { "Hello, world!" @@ -72,7 +72,7 @@ fn create() { } } } - } + }) }; let mut dom = new_dom(APP, ()); @@ -121,11 +121,11 @@ fn create() { #[test] fn create_list() { static APP: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { {(0..3).map(|f| rsx!{ div { "hello" }})} - } + }) }; let mut dom = new_dom(APP, ()); @@ -170,12 +170,12 @@ fn create_list() { #[test] fn create_simple() { static APP: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { div {} div {} div {} div {} - } + }) }; let mut dom = new_dom(APP, ()); @@ -208,19 +208,19 @@ fn create_simple() { #[test] fn create_components() { static App: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { Child { "abc1" } Child { "abc2" } Child { "abc3" } - } + }) }; static Child: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { h1 {} div { {cx.children()} } p {} - } + }) }; let mut dom = new_dom(App, ()); @@ -269,10 +269,10 @@ fn create_components() { #[test] fn anchors() { static App: FC<()> = |(cx, props)| { - rsx! { + cx.render(rsx! { {true.then(|| rsx!{ div { "hello" } })} {false.then(|| rsx!{ div { "goodbye" } })} - } + }) }; let mut dom = new_dom(App, ()); @@ -303,9 +303,23 @@ fn suspended() { || async { // }, - |cx, p| rsx! { "hi "}, + |cx, p| todo!(), + // |cx, p| rsx! { "hi "}, ); - rsx! { {val} } + + // let prom = use_task(fetch()); + + // Value { + // a: value.await, + // b: value.await, + // f: a.await + // div { + // div { + // hidden: should_hide.await, + // } + // } + // } + cx.render(rsx! { {val} }) }; let mut dom = new_dom(App, ()); diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 55a332e57..65aece563 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -433,11 +433,11 @@ fn keyed_diffing_order() { let dom = new_dom(); let left = rsx!( - {(0..5).map(|f| {rsx! { div { key: "{f}" }}})} + // {(0..5).map(|f| {rsx! { div { key: "{f}" }}})} p {"e"} ); let right = rsx!( - {(0..2).map(|f| {rsx! { div { key: "{f}" }}})} + {(0..2).map(|f| rsx! { div { key: "{f}" }})} p {"e"} ); @@ -807,16 +807,17 @@ fn controlled_keyed_diffing_out_of_order_max_test() { fn suspense() { let dom = new_dom(); - let edits = dom.create(Some(LazyNodes::new(|f| { - use std::cell::{Cell, RefCell}; - VNode::Suspended(f.bump().alloc(VSuspended { - task_id: 0, - callback: RefCell::new(None), - dom_id: Cell::new(None), - })) - }))); - assert_eq!( - edits.edits, - [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] - ); + todo!() + // let edits = dom.create(Some(LazyNodes::new(|f| { + // use std::cell::{Cell, RefCell}; + // VNode::Suspended(f.bump().alloc(VSuspended { + // task_id: 0, + // callback: RefCell::new(None), + // dom_id: Cell::new(None), + // })) + // }))); + // assert_eq!( + // edits.edits, + // [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }] + // ); } diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 98837fbc3..3b9c05cca 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -22,7 +22,7 @@ fn manual_diffing() { static App: FC = |(cx, props)| { let val = props.value.lock().unwrap(); - rsx! { div { "{val}" } } + cx.render(rsx! { div { "{val}" } }) }; let value = Arc::new(Mutex::new("Hello")); diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index f2a17b804..bad43442a 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -15,12 +15,12 @@ fn shared_state_test() { static App: FC<()> = |(cx, props)| { cx.provide_state(MySharedState("world!")); - rsx!(Child {}) + cx.render(rsx!(Child {})) }; static Child: FC<()> = |(cx, props)| { let shared = cx.consume_state::()?; - rsx!("Hello, {shared.0}") + cx.render(rsx!("Hello, {shared.0}")) }; let mut dom = VirtualDom::new(App); diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index a992cd449..79aaad112 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -19,7 +19,7 @@ use dioxus_html as dioxus_elements; fn app_runs() { static App: FC<()> = |(cx, props)| { // - rsx!( div{"hello"} ) + cx.render(rsx!( div{"hello"} )) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -29,10 +29,10 @@ fn app_runs() { #[test] fn fragments_work() { static App: FC<()> = |(cx, props)| { - rsx!( + cx.render(rsx!( div{"hello"} div{"goodbye"} - ) + )) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -43,10 +43,10 @@ fn fragments_work() { #[test] fn lists_work() { static App: FC<()> = |(cx, props)| { - rsx!( + cx.render(rsx!( h1 {"hello"} {(0..6).map(|f| rsx!(span{ "{f}" }))} - ) + )) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -56,11 +56,11 @@ fn lists_work() { #[test] fn conditional_rendering() { static App: FC<()> = |(cx, props)| { - rsx!( + cx.render(rsx!( h1 {"hello"} {true.then(|| rsx!(span{ "a" }))} {false.then(|| rsx!(span{ "b" }))} - ) + )) }; let mut vdom = VirtualDom::new(App); @@ -73,16 +73,16 @@ fn conditional_rendering() { #[test] fn child_components() { static App: FC<()> = |(cx, props)| { - rsx!( + cx.render(rsx!( {true.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))} - ) + )) }; static Child: FC<()> = |(cx, props)| { - rsx!( + cx.render(rsx!( h1 {"hello"} h1 {"goodbye"} - ) + )) }; let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -92,8 +92,9 @@ fn child_components() { #[test] fn suspended_works() { static App: FC<()> = |(cx, props)| { - let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"}); - rsx!("hello" { title }) + let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!()); + // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"}); + cx.render(rsx!("hello" { title })) }; let mut vdom = VirtualDom::new(App); From 3e07214272f20a47b168d6431fcb39880cd41a9d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 02:41:23 -0400 Subject: [PATCH 08/18] wip: implement lazy nodes properly --- packages/core/examples/alternative.rs | 2 +- packages/core/examples/syntax2.rs | 2 +- packages/core/examples/vdom_usage.rs | 4 +- packages/core/src/context.rs | 7 ++-- packages/core/src/lazynodes.rs | 36 ++++++++++------- packages/core/src/lib.rs | 6 +-- packages/core/src/nodes.rs | 57 +++++++-------------------- 7 files changed, 47 insertions(+), 67 deletions(-) diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 1de1c1e79..3f2fa9790 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -17,7 +17,7 @@ pub static EXAMPLE: FC<()> = |(cx, _)| { }); // let list = (0..10).map(|_f| Some(Box::new(move |_f| todo!()))); - cx.render(Some(Box::new(move |cx| { + cx.render(Some(LazyNodes::new(move |cx| { cx.raw_element( "div", None, diff --git a/packages/core/examples/syntax2.rs b/packages/core/examples/syntax2.rs index 8ae715728..4cbd22ab5 100644 --- a/packages/core/examples/syntax2.rs +++ b/packages/core/examples/syntax2.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use dioxus::component::Scope; use dioxus::events::on::MouseEvent; -use dioxus::nodes::{annotate_lazy, IntoVNode, VComponent, VFragment, VText}; +use dioxus::nodes::{IntoVNode, VComponent, VFragment, VText}; use dioxus_core as dioxus; use dioxus_core::prelude::*; use dioxus_core_macro::*; diff --git a/packages/core/examples/vdom_usage.rs b/packages/core/examples/vdom_usage.rs index 07ec5f205..8f5d278b1 100644 --- a/packages/core/examples/vdom_usage.rs +++ b/packages/core/examples/vdom_usage.rs @@ -1,11 +1,11 @@ use std::time::Duration; -use dioxus_core::prelude::*; +use dioxus_core::{lazynodes::LazyNodes, prelude::*}; // #[async_std::main] fn main() { static App: FC<()> = - |(cx, props)| cx.render(Some(Box::new(move |f| f.text(format_args!("hello"))))); + |(cx, props)| cx.render(Some(LazyNodes::new(move |f| f.text(format_args!("hello"))))); let mut dom = VirtualDom::new(App); diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index b9661b294..8e3389761 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -6,7 +6,7 @@ use bumpalo::Bump; -use crate::innerlude::*; +use crate::{innerlude::*, lazynodes::LazyNodes}; use std::{any::TypeId, ops::Deref, rc::Rc}; /// Components in Dioxus use the "Context" object to interact with their lifecycle. @@ -103,11 +103,12 @@ impl<'src> Context<'src> { ///``` pub fn render( self, - lazy_nodes: Option) -> VNode<'src> + '_>>, + lazy_nodes: Option>, + // lazy_nodes: Option) -> VNode<'src> + '_>>, ) -> Option> { let bump = &self.scope.frames.wip_frame().bump; let factory = NodeFactory { bump }; - lazy_nodes.map(|f| f(factory)) + lazy_nodes.map(|f| f.call(factory)) } /// `submit_task` will submit the future to be polled. diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 3662d12ff..78d7093c7 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -26,29 +26,37 @@ use crate::innerlude::{IntoVNode, NodeFactory, VNode}; /// ```rust /// LazyNodes::new(|f| f.element("div", [], [], [] None)) /// ``` -// pub struct LazyNodes<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> { -// inner: Box, -// _p: PhantomData<&'a ()>, -// // inner: StackNodeStorage<'a>, -// // inner: StackNodeStorage<'a>, -// } -// pub type LazyNodes<'b> = Box FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>; +pub struct LazyNodes<'a, 'b> { + inner: StackNodeStorage<'a, 'b>, +} -// pub fn to_lazy_nodes<'b>( -// f: impl for<'a> FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, -// ) -> Option> { -// Some(Box::new(f)) -// } +impl<'a, 'b> LazyNodes<'a, 'b> { + pub fn new(f: F) -> Self + where + F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, + { + Self { + inner: StackNodeStorage::Heap(Box::new(f)), + } + } + + pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> { + match self.inner { + StackNodeStorage::Heap(lazy) => lazy(f), + _ => todo!(), + } + } +} type StackHeapSize = [usize; 12]; -enum StackNodeStorage<'a> { +enum StackNodeStorage<'a, 'b> { Stack { next_ofs: usize, buf: StackHeapSize, width: usize, }, - Heap(Box) -> VNode<'a>>), + Heap(Box) -> VNode<'a> + 'b>), } // impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> LazyNodes<'a, F> { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index fc009b285..043e321e2 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -19,7 +19,6 @@ pub mod context; pub mod diff; pub mod diff_stack; pub mod events; -// pub mod events2; pub mod heuristics; pub mod hooklist; pub mod hooks; @@ -46,6 +45,7 @@ pub(crate) mod innerlude { pub use crate::heuristics::*; pub(crate) use crate::hooklist::*; pub use crate::hooks::*; + pub use crate::lazynodes::*; pub use crate::mutations::*; pub use crate::nodes::*; pub(crate) use crate::resources::*; @@ -56,7 +56,7 @@ pub(crate) mod innerlude { pub use crate::threadsafe::*; pub use crate::util::*; pub use crate::virtual_dom::*; - // pub type Element<'a> = Option>; + pub type Element<'a> = Option>; pub type FC

= for<'a> fn(Scope<'a, P>) -> Element<'a>; } @@ -71,7 +71,7 @@ pub mod prelude { pub use crate::component::{fc_to_builder, Fragment, Properties, Scope}; pub use crate::context::Context; pub use crate::hooks::*; - pub use crate::innerlude::{DioxusElement, Element, Mutations, NodeFactory, FC}; + pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, FC}; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 894210dac..7e23af372 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -3,9 +3,12 @@ //! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very* //! cheap and *very* fast to construct - building a full tree should be quick. -use crate::innerlude::{ - empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner, - SuspendedContext, FC, +use crate::{ + innerlude::{ + empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner, + SuspendedContext, + }, + lazynodes::LazyNodes, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ @@ -328,14 +331,6 @@ impl<'a> NodeFactory<'a> { self.bump } - pub(crate) fn unstable_place_holder(&self) -> VNode { - VNode::Text(self.bump.alloc(VText { - text: "", - dom_id: empty_cell(), - is_static: true, - })) - } - /// Directly pass in text blocks without the need to use the format_args macro. pub fn static_text(&self, text: &'static str) -> VNode<'a> { VNode::Text(self.bump.alloc(VText { @@ -449,23 +444,6 @@ impl<'a> NodeFactory<'a> { } } - pub fn component_v2_borrowed( - self, - f: impl Fn(Context<'a>) -> Option> + 'a, - fc: fn(Scope<'a, P>) -> Element<'a>, - p: &'a P, - ) -> VNode<'a> { - // - todo!() - } - // pub fn component_v2_memoized( - // self, - // f: impl for<'b> Fn(Context<'b>) -> Option> + 'static, - // ) -> VNode<'a> { - // // - // todo!() - // } - pub fn component( &self, component: fn(Scope<'a, P>) -> Element<'a>, @@ -609,13 +587,11 @@ impl<'a> NodeFactory<'a> { }) } - pub fn annotate_lazy<'z, 'b, F>( - f: F, - ) -> Option) -> VNode<'z> + 'b>> + pub fn annotate_lazy<'z, 'b, F>(f: F) -> Option> where F: FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b, { - Some(Box::new(f)) + Some(LazyNodes::new(f)) } } @@ -675,17 +651,11 @@ impl<'a> IntoVNode<'a> for Option> { } } -pub fn annotate_lazy<'a, 'b, F>(f: F) -> Option) -> VNode<'a> + 'b>> -where - F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, -{ - Some(Box::new(f)) -} - -impl<'a> IntoVNode<'a> for Option) -> VNode<'a> + '_>> { +impl<'a> IntoVNode<'a> for Option> { + // impl<'a> IntoVNode<'a> for Option) -> VNode<'a> + '_>> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { match self { - Some(lazy) => lazy(cx), + Some(lazy) => lazy.call(cx), None => VNode::Fragment(VFragment { children: &[], is_static: false, @@ -695,9 +665,10 @@ impl<'a> IntoVNode<'a> for Option) -> VNode<'a> + } } -impl<'a> IntoVNode<'a> for Box) -> VNode<'a> + '_> { +impl<'a> IntoVNode<'a> for LazyNodes<'a, '_> { + // impl<'a> IntoVNode<'a> for Box) -> VNode<'a> + '_> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - self(cx) + self.call(cx) } } From d4192d0de2fb352d31f33f2fadfc21de0b1b950c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 03:13:31 -0400 Subject: [PATCH 09/18] wip: implement dst magic --- packages/core/src/lazynodes.rs | 201 ++++++++++++++++++++------------- 1 file changed, 122 insertions(+), 79 deletions(-) diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 78d7093c7..b20e8cb04 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::mem; /* Remember: calls to rsx! are lazy - they are not evaluated immediately. @@ -15,7 +15,7 @@ Our solution is to try and manually allocate the closure onto the stack. If it fails, then we default to Box. */ -use crate::innerlude::{IntoVNode, NodeFactory, VNode}; +use crate::innerlude::{NodeFactory, VNode}; /// A concrete type provider for closures that build VNode structures. /// @@ -30,103 +30,146 @@ pub struct LazyNodes<'a, 'b> { inner: StackNodeStorage<'a, 'b>, } +type StackHeapSize = [usize; 12]; + +enum StackNodeStorage<'a, 'b> { + Stack(LazyStack), + Heap(Box) -> VNode<'a> + 'b>), +} + impl<'a, 'b> LazyNodes<'a, 'b> { - pub fn new(f: F) -> Self + pub fn new(val: F) -> Self where F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, { - Self { - inner: StackNodeStorage::Heap(Box::new(f)), + unsafe { + let mut ptr: *const _ = &val as &dyn FnOnce(NodeFactory<'a>) -> VNode<'a>; + + assert_eq!( + ptr as *const u8, &val as *const _ as *const u8, + "MISUSE: Closure returned different pointer" + ); + assert_eq!( + std::mem::size_of_val(&*ptr), + std::mem::size_of::(), + "MISUSE: Closure returned a subset pointer" + ); + let words = ptr_as_slice(&mut ptr); + assert!( + words[0] == &val as *const _ as usize, + "BUG: Pointer layout is not (data_ptr, info...)" + ); + + // - Ensure that Self is aligned same as data requires + assert!( + std::mem::align_of::() <= std::mem::align_of::(), + "TODO: Enforce alignment >{} (requires {})", + std::mem::align_of::(), + std::mem::align_of::() + ); + + let info = &words[1..]; + let data = words[0] as *mut (); + let size = mem::size_of::(); + + if info.len() * mem::size_of::() + size > mem::size_of::() { + log::error!("lazy nodes was too large to fit into stack. falling back to heap"); + + Self { + inner: StackNodeStorage::Heap(Box::new(val)), + } + } else { + log::error!("lazy nodes fits on stack!"); + let mut buf: StackHeapSize = [0; 12]; + + assert!(info.len() + round_to_words(size) <= buf.as_ref().len()); + + // Place pointer information at the end of the region + // - Allows the data to be at the start for alignment purposes + { + let info_ofs = buf.as_ref().len() - info.len(); + let info_dst = &mut buf.as_mut()[info_ofs..]; + for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) { + *d = *v; + } + } + + let src_ptr = data as *const u8; + let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8; + for i in 0..size { + *dataptr.add(i) = *src_ptr.add(i); + } + + Self { + inner: StackNodeStorage::Stack(LazyStack { _align: [], buf }), + } + } } } pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> { match self.inner { StackNodeStorage::Heap(lazy) => lazy(f), - _ => todo!(), + StackNodeStorage::Stack(stack) => stack.call(f), } } } -type StackHeapSize = [usize; 12]; - -enum StackNodeStorage<'a, 'b> { - Stack { - next_ofs: usize, - buf: StackHeapSize, - width: usize, - }, - Heap(Box) -> VNode<'a> + 'b>), +struct LazyStack { + _align: [u64; 0], + buf: StackHeapSize, } -// impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> LazyNodes<'a, F> { -// pub fn new(f: F) -> Self { -// // let width = std::mem?::size_of::(); -// // let b: Box) -> VNode<'a>> = Box::new(f); +impl LazyStack { + unsafe fn create_boxed<'a>(&mut self) -> Box) -> VNode<'a>> { + let LazyStack { buf, .. } = self; + let data = buf.as_ref(); -// todo!() -// // Self { inner: b } -// // todo!() + let info_size = mem::size_of::<*mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a>>() + / mem::size_of::() + - 1; -// // if width > std::mem::size_of::() { -// // let g: Box FnOnce(NodeFactory<'b>) -> VNode<'b> + 'g> = Box::new(f); -// // LazyNodes { -// // inner: StackNodeStorage::Heap(g), -// // } -// // } else { -// // let mut buf = [0; 12]; -// // let mut next_ofs = 0; -// // next_ofs += 1; -// // LazyNodes { -// // inner: StackNodeStorage::Stack { -// // next_ofs, -// // buf, -// // width, -// // }, -// // } -// // } -// } -// } + let info_ofs = data.len() - info_size; -// // Our blanket impl -// impl<'a> IntoIterator for LazyNodes<'a> -// // where -// // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, -// // impl<'a, F> IntoIterator for LazyNodes<'a, F> -// // where -// // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, -// { -// type Item = Self; -// type IntoIter = std::iter::Once; -// fn into_iter(self) -> Self::IntoIter { -// std::iter::once(self) -// } -// } + let g: *mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a> = + make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]); -// // Our blanket impl -// impl IntoVNode for LazyNodes<'_> { -// // impl<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>> IntoVNode<'a> for LazyNodes<'a, F> { -// fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { -// todo!() -// // match self.inner { -// // StackNodeStorage::Stack { -// // buf, -// // next_ofs, -// // width, -// // } => { -// // // get the start of the allocation -// // let r = &buf[0]; + Box::from_raw(g) + } -// // // recast the allocation as dyn FnOnce + fn call(mut self, f: NodeFactory) -> VNode { + let boxed = unsafe { self.create_boxed() }; + boxed(f) + } +} +impl Drop for LazyStack { + fn drop(&mut self) { + let boxed = unsafe { self.create_boxed() }; + mem::drop(boxed); + } +} -// // // pretend the FnOnce is box -// // let g: Box) -> VNode<'a>> = todo!(); -// // // Box::from_raw(r as *const usize as *mut dyn FnOnce(NodeFactory<'a>)); +/// Obtain mutable access to a pointer's words +fn ptr_as_slice(ptr: &mut T) -> &mut [usize] { + assert!(mem::size_of::() % mem::size_of::() == 0); + let words = mem::size_of::() / mem::size_of::(); + // SAFE: Points to valid memory (a raw pointer) + unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) } +} -// // // use Box's ability to act as FnOnce -// // g(cx) -// // } -// // StackNodeStorage::Heap(b) => b(cx), -// // } -// } -// } +/// Re-construct a fat pointer +unsafe fn make_fat_ptr(data_ptr: usize, meta_vals: &[usize]) -> *mut T { + let mut rv = mem::MaybeUninit::<*mut T>::uninit(); + { + let s = ptr_as_slice(&mut rv); + s[0] = data_ptr; + s[1..].copy_from_slice(meta_vals); + } + let rv = rv.assume_init(); + assert_eq!(rv as *const (), data_ptr as *const ()); + rv +} + +fn round_to_words(len: usize) -> usize { + (len + mem::size_of::() - 1) / mem::size_of::() +} From 9c1343610b3907bd8c6acadc086bfc9a8c7b3f3e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 03:29:50 -0400 Subject: [PATCH 10/18] wip: clean up using new children syntax --- packages/core/src/component.rs | 34 +++++++++++++++------- packages/core/src/diff.rs | 4 --- packages/core/src/lazynodes.rs | 52 ++++++++++++++++++++++------------ packages/core/src/nodes.rs | 16 +++++++++++ packages/core/src/scope.rs | 26 ++++++++--------- packages/core/src/test_dom.rs | 49 ++++++-------------------------- 6 files changed, 96 insertions(+), 85 deletions(-) diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index ce5fd2b38..26c920596 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -5,11 +5,7 @@ //! if the type supports 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 cx. -use crate::{ - innerlude::{Context, Element, FC}, - NodeFactory, VNode, -}; - +use crate::innerlude::{Context, Element, VNode}; /// A component is a wrapper around a Context and some Props that share a lifetime /// /// @@ -44,6 +40,27 @@ use crate::{ /// pub type Scope<'a, T> = (Context<'a>, &'a T); +pub struct FragmentProps<'a> { + childen: &'a VNode<'a>, +} +pub struct FragmentPropsBuilder<'a> { + childen: Option<&'a VNode<'a>>, +} + +impl<'a> Properties for FragmentProps<'a> { + type Builder = FragmentPropsBuilder<'a>; + + const IS_STATIC: bool = false; + + fn builder() -> Self::Builder { + todo!() + } + + unsafe fn memoize(&self, other: &Self) -> bool { + todo!() + } +} + /// Create inline fragments using Component syntax. /// /// Fragments capture a series of children without rendering extra nodes. @@ -66,11 +83,8 @@ pub type Scope<'a, T> = (Context<'a>, &'a T); /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>((cx, _): Scope<'a, ()>) -> Element<'a> { - todo!() - // let p: Box) -> VNode<'a> + '_> = - // Box::new(|f: NodeFactory| f.fragment_from_iter(cx.children())); - // cx.render(p) +pub fn Fragment<'a>((_, props): Scope<'a, FragmentProps<'a>>) -> Element<'a> { + Some(props.childen.decouple()) } /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index eddbb447a..654048267 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -420,10 +420,6 @@ impl<'bump> DiffMachine<'bump> { ) => self .stack .create_node(new_node, MountType::Replace { old: old_node }), - - (Placeholder, _) | (_, Placeholder) => { - unreachable!("placeholder should never be diffed"); - } } } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index b20e8cb04..249948517 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -1,21 +1,18 @@ -use std::mem; +//! Support for storing lazy-nodes on the stack +//! +//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls +//! to `rsx!` more efficient. +//! +//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on NodeFactory closures. +//! +//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case, +//! we build a tiny alloactor in the stack and allocate the closure into that. +//! +//! The logic for this was borrowed from https://docs.rs/stack_dst/0.6.1/stack_dst/. Unfortunately, this crate does not +//! support non-static closures, so we've implemented the core logic of `ValueA` in this module. -/* -Remember: calls to rsx! are lazy - they are not evaluated immediately. - -They also using dynamic dispatch, so we can return multiple rsx!'s from match statements and such. - -If we allocated every rsx! call on the heap, it would be quite wasteful. Rsx! calls are FnOnce, so they can be stored in a stack. - -Solutions like stackdst are useful, but they only support 'static closures. - -All our closures are bound by the bump lifetime, so stack-dst will not work for us - -Our solution is to try and manually allocate the closure onto the stack. -If it fails, then we default to Box. - -*/ use crate::innerlude::{NodeFactory, VNode}; +use std::mem; /// A concrete type provider for closures that build VNode structures. /// @@ -73,13 +70,13 @@ impl<'a, 'b> LazyNodes<'a, 'b> { let size = mem::size_of::(); if info.len() * mem::size_of::() + size > mem::size_of::() { - log::error!("lazy nodes was too large to fit into stack. falling back to heap"); + log::debug!("lazy nodes was too large to fit into stack. falling back to heap"); Self { inner: StackNodeStorage::Heap(Box::new(val)), } } else { - log::error!("lazy nodes fits on stack!"); + log::debug!("lazy nodes fits on stack!"); let mut buf: StackHeapSize = [0; 12]; assert!(info.len() + round_to_words(size) <= buf.as_ref().len()); @@ -173,3 +170,22 @@ unsafe fn make_fat_ptr(data_ptr: usize, meta_vals: &[usize]) -> *mut fn round_to_words(len: usize) -> usize { (len + mem::size_of::() - 1) / mem::size_of::() } + +#[test] +fn it_works() { + let bump = bumpalo::Bump::new(); + + simple_logger::init(); + + let factory = NodeFactory { bump: &bump }; + + let caller = NodeFactory::annotate_lazy(|f| { + // + f.text(format_args!("hello world!")) + }) + .unwrap(); + + let g = caller.call(factory); + + dbg!(g); +} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 7e23af372..bc06eb29d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -155,6 +155,22 @@ impl<'src> VNode<'src> { VNode::Component(_) => None, } } + + // Create an "owned" version of the vnode. + pub fn decouple(&self) -> VNode<'src> { + match self { + VNode::Text(t) => VNode::Text(*t), + VNode::Element(e) => VNode::Element(*e), + VNode::Component(c) => VNode::Component(*c), + VNode::Suspended(s) => VNode::Suspended(*s), + VNode::Anchor(a) => VNode::Anchor(*a), + VNode::Fragment(f) => VNode::Fragment(VFragment { + children: f.children, + key: f.key, + is_static: f.is_static, + }), + } + } } /// A placeholder node only generated when Fragments don't have any children. diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 97333cd0d..94db9d67f 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -341,21 +341,21 @@ impl ScopeInner { // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. // - todo!() - // if let Some(builder) = render(self) { - // let new_head = builder.into_vnode(NodeFactory { - // bump: &self.frames.wip_frame().bump, - // }); - // log::debug!("Render is successful"); + // todo!() + if let Some(builder) = render(self) { + let new_head = builder.into_vnode(NodeFactory { + bump: &self.frames.wip_frame().bump, + }); + log::debug!("Render is successful"); - // // the user's component succeeded. We can safely cycle to the next frame - // self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; - // self.frames.cycle_frame(); + // the user's component succeeded. We can safely cycle to the next frame + self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; + self.frames.cycle_frame(); - // true - // } else { - // false - // } + true + } else { + false + } } } diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index fa3a2649e..aab7f3373 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -21,27 +21,13 @@ impl TestDom { NodeFactory::new(&self.bump) } - pub fn render_direct<'a>( - &'a self, - lazy_nodes: Option) -> VNode<'a> + '_>>, - ) -> VNode<'a> -// where - // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, - { - todo!() - // lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) + pub fn render_direct<'a>(&'a self, lazy_nodes: Option>) -> VNode<'a> { + lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) } - pub fn render<'a>( - &'a self, - lazy_nodes: Option) -> VNode<'a> + '_>>, - ) -> &'a VNode<'a> -// where - // F: FnOnce(NodeFactory<'a>) -> VNode<'a>, - { - todo!() - // self.bump - // .alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump))) + pub fn render<'a>(&'a self, lazy_nodes: Option>) -> &'a VNode<'a> { + self.bump + .alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump))) } pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { @@ -51,14 +37,7 @@ impl TestDom { machine.mutations } - pub fn create<'a>( - &'a self, - left: Option) -> VNode<'a> + '_>>, - ) -> Mutations<'a> -// pub fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a> - // where - // F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, - { + pub fn create<'a>(&'a self, left: Option>) -> Mutations<'a> { let old = self.bump.alloc(self.render_direct(left)); let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); @@ -72,13 +51,9 @@ impl TestDom { pub fn lazy_diff<'a>( &'a self, - left: Option) -> VNode<'a> + '_>>, - right: Option) -> VNode<'a> + '_>>, - ) -> (Mutations<'a>, Mutations<'a>) -// where - // F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, - // F2: FnOnce(NodeFactory<'a>) -> VNode<'a>, - { + left: Option>, + right: Option>, + ) -> (Mutations<'a>, Mutations<'a>) { let (old, new) = (self.render(left), self.render(right)); let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); @@ -105,9 +80,3 @@ impl Default for TestDom { Self::new() } } - -impl VirtualDom { - pub fn simulate(&mut self) { - // - } -} From d1b294fff0c8618e50969ca59f198baf0565777d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 03:35:26 -0400 Subject: [PATCH 11/18] polish: move debugging virtualdom into its own feature --- packages/core/Cargo.toml | 1 + packages/core/src/debug_dom.rs | 128 +++++++++++++++++++++++++++++++ packages/core/src/lib.rs | 3 + packages/core/src/nodes.rs | 60 +++++++-------- packages/core/src/scope.rs | 107 -------------------------- packages/core/src/virtual_dom.rs | 56 +++++--------- 6 files changed, 182 insertions(+), 173 deletions(-) create mode 100644 packages/core/src/debug_dom.rs diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index ea0ae505d..334ebcee1 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -55,6 +55,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.2" } [features] default = [] serialize = ["serde", "serde_repr"] +debug_vdom = [] [[bench]] name = "create" diff --git a/packages/core/src/debug_dom.rs b/packages/core/src/debug_dom.rs new file mode 100644 index 000000000..13d874be3 --- /dev/null +++ b/packages/core/src/debug_dom.rs @@ -0,0 +1,128 @@ +use crate::{innerlude::ScopeInner, virtual_dom::VirtualDom, VNode}; + +impl std::fmt::Display for VirtualDom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let base = self.base_scope(); + let root = base.root_node(); + + let renderer = ScopeRenderer { + show_fragments: false, + skip_components: false, + + _scope: base, + _pre_render: false, + _newline: true, + _indent: true, + _max_depth: usize::MAX, + }; + + renderer.render(self, root, f, 0) + } +} + +/// render the scope to a string using the rsx! syntax +pub(crate) struct ScopeRenderer<'a> { + pub skip_components: bool, + pub show_fragments: bool, + pub _scope: &'a ScopeInner, + pub _pre_render: bool, + pub _newline: bool, + pub _indent: bool, + pub _max_depth: usize, +} + +// this is more or less a debug tool, but it'll render the entire tree to the terminal +impl<'a> ScopeRenderer<'a> { + pub fn render( + &self, + vdom: &VirtualDom, + node: &VNode, + f: &mut std::fmt::Formatter, + il: u16, + ) -> std::fmt::Result { + const INDENT: &str = " "; + let write_indent = |_f: &mut std::fmt::Formatter, le| { + for _ in 0..le { + write!(_f, "{}", INDENT).unwrap(); + } + }; + + match &node { + VNode::Text(text) => { + write_indent(f, il); + writeln!(f, "\"{}\"", text.text)? + } + VNode::Anchor(_anchor) => { + write_indent(f, il); + writeln!(f, "Anchor {{}}")?; + } + VNode::Element(el) => { + write_indent(f, il); + writeln!(f, "{} {{", el.tag_name)?; + // write!(f, "element: {}", el.tag_name)?; + let mut attr_iter = el.attributes.iter().peekable(); + + while let Some(attr) = attr_iter.next() { + match attr.namespace { + None => { + // + write_indent(f, il + 1); + writeln!(f, "{}: \"{}\"", attr.name, attr.value)? + } + + Some(ns) => { + // write the opening tag + write_indent(f, il + 1); + write!(f, " {}:\"", ns)?; + let mut cur_ns_el = attr; + 'ns_parse: loop { + write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?; + match attr_iter.peek() { + Some(next_attr) if next_attr.namespace == Some(ns) => { + cur_ns_el = attr_iter.next().unwrap(); + } + _ => break 'ns_parse, + } + } + // write the closing tag + write!(f, "\"")?; + } + } + } + + for child in el.children { + self.render(vdom, child, f, il + 1)?; + } + write_indent(f, il); + + writeln!(f, "}}")?; + } + VNode::Fragment(frag) => { + if self.show_fragments { + write_indent(f, il); + writeln!(f, "Fragment {{")?; + for child in frag.children { + self.render(vdom, child, f, il + 1)?; + } + write_indent(f, il); + writeln!(f, "}}")?; + } else { + for child in frag.children { + self.render(vdom, child, f, il)?; + } + } + } + VNode::Component(vcomp) => { + let idx = vcomp.associated_scope.get().unwrap(); + if !self.skip_components { + let new_node = vdom.get_scope(idx).unwrap().root_node(); + self.render(vdom, new_node, f, il)?; + } + } + VNode::Suspended { .. } => { + // we can't do anything with suspended nodes + } + } + Ok(()) + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 043e321e2..cdc532ac1 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -34,6 +34,9 @@ pub mod threadsafe; pub mod util; pub mod virtual_dom; +#[cfg(feature = "debug_vdom")] +pub mod debug_dom; + pub(crate) mod innerlude { pub(crate) use crate::bumpframe::*; pub(crate) use crate::childiter::*; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index bc06eb29d..217374666 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -173,6 +173,29 @@ impl<'src> VNode<'src> { } } +impl Debug for VNode<'_> { + fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + match &self { + VNode::Element(el) => s + .debug_struct("VElement") + .field("name", &el.tag_name) + .field("key", &el.key) + .finish(), + + VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text), + VNode::Anchor(_) => write!(s, "VAnchor"), + + VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children), + VNode::Suspended { .. } => write!(s, "VSuspended"), + VNode::Component(comp) => write!( + s, + "VComponent {{ fc: {:?}, children: {:?} }}", + comp.user_fc, comp.children + ), + } + } +} + /// A placeholder node only generated when Fragments don't have any children. pub struct VAnchor { pub dom_id: Cell>, @@ -611,6 +634,12 @@ impl<'a> NodeFactory<'a> { } } +impl Debug for NodeFactory<'_> { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + /// Trait implementations for use in the rsx! and html! macros. /// /// ## Details @@ -668,7 +697,6 @@ impl<'a> IntoVNode<'a> for Option> { } impl<'a> IntoVNode<'a> for Option> { - // impl<'a> IntoVNode<'a> for Option) -> VNode<'a> + '_>> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { match self { Some(lazy) => lazy.call(cx), @@ -682,7 +710,6 @@ impl<'a> IntoVNode<'a> for Option> { } impl<'a> IntoVNode<'a> for LazyNodes<'a, '_> { - // impl<'a> IntoVNode<'a> for Box) -> VNode<'a> + '_> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { self.call(cx) } @@ -699,32 +726,3 @@ impl IntoVNode<'_> for Arguments<'_> { cx.text(self) } } - -impl Debug for NodeFactory<'_> { - fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -impl Debug for VNode<'_> { - fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - match &self { - VNode::Element(el) => s - .debug_struct("VElement") - .field("name", &el.tag_name) - .field("key", &el.key) - .finish(), - - VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text), - VNode::Anchor(_) => write!(s, "VAnchor"), - - VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children), - VNode::Suspended { .. } => write!(s, "VSuspended"), - VNode::Component(comp) => write!( - s, - "VComponent {{ fc: {:?}, children: {:?} }}", - comp.user_fc, comp.children - ), - } - } -} diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 94db9d67f..ccfe64855 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -358,110 +358,3 @@ impl ScopeInner { } } } - -/// render the scope to a string using the rsx! syntax -pub(crate) struct ScopeRenderer<'a> { - pub skip_components: bool, - pub show_fragments: bool, - pub _scope: &'a ScopeInner, - pub _pre_render: bool, - pub _newline: bool, - pub _indent: bool, - pub _max_depth: usize, -} - -// this is more or less a debug tool, but it'll render the entire tree to the terminal -impl<'a> ScopeRenderer<'a> { - pub fn render( - &self, - vdom: &VirtualDom, - node: &VNode, - f: &mut std::fmt::Formatter, - il: u16, - ) -> std::fmt::Result { - const INDENT: &str = " "; - let write_indent = |_f: &mut std::fmt::Formatter, le| { - for _ in 0..le { - write!(_f, "{}", INDENT).unwrap(); - } - }; - - match &node { - VNode::Text(text) => { - write_indent(f, il); - writeln!(f, "\"{}\"", text.text)? - } - VNode::Anchor(_anchor) => { - write_indent(f, il); - writeln!(f, "Anchor {{}}")?; - } - VNode::Element(el) => { - write_indent(f, il); - writeln!(f, "{} {{", el.tag_name)?; - // write!(f, "element: {}", el.tag_name)?; - let mut attr_iter = el.attributes.iter().peekable(); - - while let Some(attr) = attr_iter.next() { - match attr.namespace { - None => { - // - write_indent(f, il + 1); - writeln!(f, "{}: \"{}\"", attr.name, attr.value)? - } - - Some(ns) => { - // write the opening tag - write_indent(f, il + 1); - write!(f, " {}:\"", ns)?; - let mut cur_ns_el = attr; - 'ns_parse: loop { - write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?; - match attr_iter.peek() { - Some(next_attr) if next_attr.namespace == Some(ns) => { - cur_ns_el = attr_iter.next().unwrap(); - } - _ => break 'ns_parse, - } - } - // write the closing tag - write!(f, "\"")?; - } - } - } - - for child in el.children { - self.render(vdom, child, f, il + 1)?; - } - write_indent(f, il); - - writeln!(f, "}}")?; - } - VNode::Fragment(frag) => { - if self.show_fragments { - write_indent(f, il); - writeln!(f, "Fragment {{")?; - for child in frag.children { - self.render(vdom, child, f, il + 1)?; - } - write_indent(f, il); - writeln!(f, "}}")?; - } else { - for child in frag.children { - self.render(vdom, child, f, il)?; - } - } - } - VNode::Component(vcomp) => { - let idx = vcomp.associated_scope.get().unwrap(); - if !self.skip_components { - let new_node = vdom.get_scope(idx).unwrap().root_node(); - self.render(vdom, new_node, f, il)?; - } - } - VNode::Suspended { .. } => { - // we can't do anything with suspended nodes - } - } - Ok(()) - } -} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 205854a4b..412d9c2a0 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -368,44 +368,30 @@ impl VirtualDom { use futures_util::StreamExt; // Wait for any new events if we have nothing to do - todo!("wait for work without select macro") - // futures_util::select! { - // _ = self.scheduler.async_tasks.next() => {} - // msg = self.scheduler.receiver.next() => { - // match msg.unwrap() { - // SchedulerMsg::Task(t) => { - // self.scheduler.handle_task(t); - // }, - // SchedulerMsg::Immediate(im) => { - // self.scheduler.dirty_scopes.insert(im); - // } - // SchedulerMsg::UiEvent(evt) => { - // self.scheduler.ui_events.push_back(evt); - // } - // } - // }, - // } - } -} + let tasks_fut = self.scheduler.async_tasks.next(); + let scheduler_fut = self.scheduler.receiver.next(); -impl std::fmt::Display for VirtualDom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let base = self.base_scope(); - let root = base.root_node(); + use futures_util::future::{select, Either}; + match select(tasks_fut, scheduler_fut).await { + // poll the internal futures + Either::Left((_id, _)) => { + // + } - let renderer = ScopeRenderer { - show_fragments: false, - skip_components: false, - - _scope: base, - _pre_render: false, - _newline: true, - _indent: true, - _max_depth: usize::MAX, - }; - - renderer.render(self, root, f, 0) + // wait for an external event + Either::Right((msg, _)) => match msg.unwrap() { + SchedulerMsg::Task(t) => { + self.scheduler.handle_task(t); + } + SchedulerMsg::Immediate(im) => { + self.scheduler.dirty_scopes.insert(im); + } + SchedulerMsg::UiEvent(evt) => { + self.scheduler.ui_events.push_back(evt); + } + }, + } } } From f438bbcfd20be889aac9a9ca8486e5896af1ce14 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 03:49:32 -0400 Subject: [PATCH 12/18] wip: move children onto scope --- packages/core/Cargo.toml | 2 +- packages/core/src/lib.rs | 6 +-- packages/core/src/nodes.rs | 62 ++++++++++++++++++++++++++++++ packages/core/src/scope.rs | 2 - packages/core/tests/create_dom.rs | 30 +++++++++------ packages/core/tests/sharedstate.rs | 2 +- 6 files changed, 85 insertions(+), 19 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 334ebcee1..8088b55d4 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -37,7 +37,7 @@ once_cell = "1.8.0" indexmap = "1.7.0" -# # Serialize the Edits for use in Webview/Liveview instances +# Serialize the Edits for use in Webview/Liveview instances serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1.7", optional = true } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index cdc532ac1..8fc6bddc7 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -66,15 +66,15 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, MountType, Mutations, - NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, TaskHandle, TestDom, - ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, + NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, SuspendedContext, TaskHandle, + TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, }; pub mod prelude { pub use crate::component::{fc_to_builder, Fragment, Properties, Scope}; pub use crate::context::Context; pub use crate::hooks::*; - pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, FC}; + pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC}; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 217374666..69ca60ede 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -726,3 +726,65 @@ impl IntoVNode<'_> for Arguments<'_> { cx.text(self) } } + +/// Access the children elements passed into the component +/// +/// This enables patterns where a component is passed children from its parent. +/// +/// ## Details +/// +/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions +/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure +/// on the props that takes Context. +/// +/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other +/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's +/// props are valid for the static lifetime. +/// +/// ## Example +/// +/// ```rust +/// const App: FC<()> = |(cx, props)|{ +/// cx.render(rsx!{ +/// CustomCard { +/// h1 {} +/// p {} +/// } +/// }) +/// } +/// +/// const CustomCard: FC<()> = |(cx, props)|{ +/// cx.render(rsx!{ +/// div { +/// h1 {"Title card"} +/// {props.children} +/// } +/// }) +/// } +/// ``` +/// +/// ## Notes: +/// +/// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime. +pub struct ScopeChildren<'a> { + root: Option>, +} + +impl IntoIterator for &ScopeChildren<'_> { + type Item = Self; + + type IntoIter = std::iter::Once; + + fn into_iter(self) -> Self::IntoIter { + todo!() + } +} + +impl<'a> IntoVNode<'a> for &ScopeChildren<'a> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { + match &self.root { + Some(n) => n.decouple(), + None => cx.fragment_from_iter(None as Option), + } + } +} diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index ccfe64855..a2ce1d87c 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -340,8 +340,6 @@ impl ScopeInner { let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = unsafe { &*self.caller }; // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. - // - // todo!() if let Some(builder) = render(self) { let new_head = builder.into_vnode(NodeFactory { bump: &self.frames.wip_frame().bump, diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index dbfc56539..f40343362 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -64,10 +64,10 @@ fn create() { "Hello, world!" div { div { - Fragment { - "hello" - "world" - } + // Fragment { + // "hello" + // "world" + // } } } } @@ -208,20 +208,26 @@ fn create_simple() { #[test] fn create_components() { static App: FC<()> = |(cx, props)| { - cx.render(rsx! { - Child { "abc1" } - Child { "abc2" } - Child { "abc3" } - }) + todo!() + // cx.render(rsx! { + // Child { "abc1" } + // Child { "abc2" } + // Child { "abc3" } + // }) }; - static Child: FC<()> = |(cx, props)| { + #[derive(Props)] + struct ChildProps<'a> { + children: ScopeChildren<'a>, + } + + fn Child<'a>((cx, props): Scope<'a, ChildProps<'a>>) -> Element { cx.render(rsx! { h1 {} - div { {cx.children()} } + div { {&props.children} } p {} }) - }; + } let mut dom = new_dom(App, ()); let mutations = dom.rebuild(); diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index bad43442a..7bb9e6501 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -1,6 +1,6 @@ #![allow(unused, non_upper_case_globals)] -use dioxus::{prelude::*, DomEdit, TestDom}; +use dioxus::{prelude::*, DomEdit, Mutations, TestDom}; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; From 2cf90b6903411e42f01a801f89037686194ee068 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 13:32:01 -0400 Subject: [PATCH 13/18] feat: pull children out of component definition --- packages/core-macro/src/rsx/component.rs | 24 ++++++++----- packages/core/examples/syntax2.rs | 43 ++---------------------- packages/core/src/nodes.rs | 37 ++++++++++---------- packages/core/tests/create_dom.rs | 11 +++--- 4 files changed, 40 insertions(+), 75 deletions(-) diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 35963b8b5..c65fc9ee1 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -161,12 +161,22 @@ impl ToTokens for Component { None => { let mut toks = quote! { fc_to_builder(#name) }; for field in &self.body { - if field.name == "key" { - has_key = Some(field); - } else { - toks.append_all(quote! {#field}) + match field.name.to_string().as_str() { + "key" => { + // + has_key = Some(field); + } + _ => toks.append_all(quote! {#field}), } } + + if !self.children.is_empty() { + let childs = &self.children; + toks.append_all(quote! { + .children(ScopeChildren::new(__cx.fragment_from_iter([ #( #childs ),* ]))) + }); + } + toks.append_all(quote! { .build() }); @@ -182,17 +192,13 @@ impl ToTokens for Component { None => quote! { None }, }; - let childs = &self.children; - let children = quote! { - [ #( #childs ),* ] - }; + // #children tokens.append_all(quote! { __cx.component( #name, #builder, #key_token, - #children ) }) } diff --git a/packages/core/examples/syntax2.rs b/packages/core/examples/syntax2.rs index 4cbd22ab5..54c0bedaa 100644 --- a/packages/core/examples/syntax2.rs +++ b/packages/core/examples/syntax2.rs @@ -230,15 +230,10 @@ fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> { }) .unwrap(); - let b = cx.bump(); - let node: &'a VNode<'a> = b.alloc(node); - - let children = Children { pthru: node }; - cx.render(rsx! { div { ChildrenComp { - children: children + div {} } } }) @@ -246,46 +241,14 @@ fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> { #[derive(Props)] struct ChildrenTest<'a> { - children: Children<'a>, -} - -struct Children<'a> { - pthru: &'a VNode<'a>, -} - -impl<'a> Clone for Children<'a> { - fn clone(&self) -> Self { - Self { pthru: self.pthru } - } -} -impl<'a> Copy for Children<'a> {} - -impl<'a> IntoVNode<'a> for Children<'a> { - fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { - match self.pthru { - VNode::Text(f) => VNode::Text(*f), - VNode::Element(e) => VNode::Element(*e), - VNode::Component(c) => VNode::Component(*c), - VNode::Suspended(s) => VNode::Suspended(*s), - VNode::Anchor(a) => VNode::Anchor(a), - VNode::Fragment(f) => VNode::Fragment(VFragment { - children: f.children, - is_static: f.is_static, - key: f.key, - }), - } - } + children: ScopeChildren<'a>, } fn ChildrenComp<'a>((cx, props): Scope<'a, ChildrenTest<'a>>) -> Element<'a> { cx.render(rsx! { div { div { - - // if the node's id is already assigned, then it's being passed in as a child - // in these instances, we don't worry about re-checking the node? - - {Some(props.children)} + {&props.children} } } }) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 69ca60ede..728a1f4cf 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -187,11 +187,7 @@ impl Debug for VNode<'_> { VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children), VNode::Suspended { .. } => write!(s, "VSuspended"), - VNode::Component(comp) => write!( - s, - "VComponent {{ fc: {:?}, children: {:?} }}", - comp.user_fc, comp.children - ), + VNode::Component(comp) => write!(s, "VComponent {{ fc: {:?}}}", comp.user_fc), } } } @@ -331,8 +327,6 @@ pub struct VComponent<'src> { pub(crate) caller: &'src dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, - pub(crate) children: &'src [VNode<'src>], - pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>, pub(crate) drop_props: RefCell>>, @@ -483,20 +477,16 @@ impl<'a> NodeFactory<'a> { } } - pub fn component( + pub fn component

( &self, component: fn(Scope<'a, P>) -> Element<'a>, props: P, key: Option, - children: V, ) -> VNode<'a> where P: Properties + 'a, - V: AsRef<[VNode<'a>]> + 'a, { let bump = self.bump(); - let children: &'a V = bump.alloc(children); - let children = children.as_ref(); let props = bump.alloc(props); let raw_props = props as *mut P as *mut (); let user_fc = component as *const (); @@ -518,7 +508,7 @@ impl<'a> NodeFactory<'a> { // It's only okay to memoize if there are no children and the props can be memoized // Implementing memoize is unsafe and done automatically with the props trait - matches!((props_memoized, children.is_empty()), (true, true)) + props_memoized } else { false } @@ -549,8 +539,6 @@ impl<'a> NodeFactory<'a> { RefCell::new(Some(drop_props)) }; - let is_static = children.is_empty() && P::IS_STATIC && key.is_none(); - let key = key.map(|f| self.raw_text(f).0); let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = @@ -565,17 +553,14 @@ impl<'a> NodeFactory<'a> { unsafe { std::mem::transmute(res) } }); - let can_memoize = children.is_empty() && P::IS_STATIC; - VNode::Component(bump.alloc(VComponent { user_fc, comparator, raw_props, - children, caller, - is_static, + is_static: P::IS_STATIC, key, - can_memoize, + can_memoize: P::IS_STATIC, drop_props, associated_scope: Cell::new(None), })) @@ -770,6 +755,18 @@ pub struct ScopeChildren<'a> { root: Option>, } +impl Default for ScopeChildren<'_> { + fn default() -> Self { + Self { root: None } + } +} + +impl<'a> ScopeChildren<'a> { + pub fn new(root: VNode<'a>) -> Self { + Self { root: Some(root) } + } +} + impl IntoIterator for &ScopeChildren<'_> { type Item = Self; diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index f40343362..875db7a75 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -208,12 +208,11 @@ fn create_simple() { #[test] fn create_components() { static App: FC<()> = |(cx, props)| { - todo!() - // cx.render(rsx! { - // Child { "abc1" } - // Child { "abc2" } - // Child { "abc3" } - // }) + cx.render(rsx! { + Child { "abc1" } + Child { "abc2" } + Child { "abc3" } + }) }; #[derive(Props)] From 5ffa60664c798acde1f959bf6d334507609c4364 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 13:39:07 -0400 Subject: [PATCH 14/18] docs: update docs surrounding cx.render --- .../src/concepts/conditional_rendering.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/guide/src/concepts/conditional_rendering.md b/docs/guide/src/concepts/conditional_rendering.md index c1a324cee..d5fc002d8 100644 --- a/docs/guide/src/concepts/conditional_rendering.md +++ b/docs/guide/src/concepts/conditional_rendering.md @@ -67,7 +67,7 @@ fn App((cx, props): Component<()>) -> Element { } ``` -Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. Because every closure is unique, we cannot return *just* `rsx!` from a match statement. Every `rsx!` call must be rendered first when used in match statements. +Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax: @@ -86,6 +86,19 @@ This syntax even enables us to write a one-line component: static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!"); ``` +Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`: + +```rust +fn App((cx, props): Component<()>) -> Element { + let greeting = match get_name() { + "jack" => rsx!("Hey Jack, how's Diane?" ), + "diane" => rsx!("Hey Diana, how's Jack?" ), + name => rsx!("Hello, {name}!" ), + }; + cx.render(greeting) +} +``` + ## Nesting RSX By looking at other examples, you might have noticed that it's possible to include `rsx!` calls inside other `rsx!` calls. With the curly-brace syntax, we can include anything in our `rsx!` that implements `IntoVnodeList`: a marker trait for iterators that produce Elements. `rsx!` itself implements this trait, so we can include it directly: @@ -117,8 +130,8 @@ In the case of a log-in screen, we might want to display the same NavBar and Foo ```rust let screen = match logged_in { - true => rsx!(cx, DashboardScreen {}), - false => rsx!(cx, LoginScreen {}) + true => rsx!(DashboardScreen {}), + false => rsx!(LoginScreen {}) }; cx.render(rsx!{ From 5a21493fb7162287989bd7c0cd1c496cc3143605 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 14:03:14 -0400 Subject: [PATCH 15/18] wip: remove send requirement on root props --- README.md | 7 +++++-- packages/core/src/scope.rs | 21 +++++++++++++++++++-- packages/core/src/virtual_dom.rs | 32 +++++++++++++++----------------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index cdad238d0..f27f8236f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust. ```rust -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { let mut count = use_state(cx, || 0); cx.render(rsx!( @@ -147,8 +147,11 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees | | Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | | Heuristic Engine | ✅ | ❓ | track component memory usage to minimize future allocations | -| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | | Effects | 🛠 | ✅ | Run effects after a component has been committed to render | +| Server Components | 🛠 | ✅ | Hybrid components for SPA and Server | +| Bundle Splitting | 👀 | ✅ | Hybrid components for SPA and Server | +| Lazy Components | 👀 | ✅ | Dynamically load the new components as the page is loaded | +| Fine-grained reactivity | 👀 | ❓ | Skip diffing for fine-grain updates | - ✅ = implemented and working - 🛠 = actively being worked on diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index a2ce1d87c..ec5f5ad33 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -210,9 +210,9 @@ impl ScopeInner { } } - pub(crate) fn update_scope_dependencies<'creator_node>( + pub(crate) fn update_scope_dependencies( &mut self, - caller: &'creator_node dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, + caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>, ) { log::debug!("Updating scope dependencies {:?}", self.our_arena_idx); let caller = caller as *const _; @@ -308,6 +308,23 @@ impl ScopeInner { } } + // run the list of effects + pub(crate) fn run_effects(&mut self, pool: &ResourcePool) { + todo!() + // let mut effects = self.frames.effects.borrow_mut(); + // let mut effects = effects.drain(..).collect::>(); + + // for effect in effects { + // let effect = unsafe { &*effect }; + // let effect = effect.as_ref(); + + // let mut effect = effect.borrow_mut(); + // let mut effect = effect.as_mut(); + + // effect.run(pool); + // } + } + /// Render this component. /// /// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated). diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 412d9c2a0..7f2765c6f 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -61,10 +61,10 @@ pub struct VirtualDom { root_fc: Box, - root_props: Rc, + root_props: Rc, // we need to keep the allocation around, but we don't necessarily use it - _root_caller: RootCaller, + _root_caller: Rc, } impl VirtualDom { @@ -131,7 +131,7 @@ impl VirtualDom { /// /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the /// VirtualDom to be created just to retrieve its channel receiver. - pub fn new_with_props_and_scheduler( + pub fn new_with_props_and_scheduler( root: FC

, root_props: P, sender: UnboundedSender, @@ -139,15 +139,16 @@ impl VirtualDom { ) -> Self { let root_fc = Box::new(root); - let root_props: Rc = Rc::new(root_props); + let root_props: Rc = Rc::new(root_props); - let _p = root_props.clone(); - // Safety: this callback is only valid for the lifetime of the root props - let root_caller: Rc Element> = - Rc::new(move |scope: &ScopeInner| unsafe { - let props = _p.downcast_ref::

().unwrap(); - std::mem::transmute(root((Context { scope }, props))) - }); + let props = root_props.clone(); + + let root_caller: Rc Element> = Rc::new(move |scope: &ScopeInner| { + let props = props.downcast_ref::

().unwrap(); + let node = root((Context { scope }, props)); + // cast into the right lifetime + unsafe { std::mem::transmute(node) } + }); let scheduler = Scheduler::new(sender, receiver); @@ -163,7 +164,7 @@ impl VirtualDom { }); Self { - _root_caller: RootCaller(root_caller), + _root_caller: Rc::new(root_caller), root_fc, base_scope, scheduler, @@ -204,14 +205,14 @@ impl VirtualDom { /// ``` pub fn update_root_props

(&mut self, root_props: P) -> Option where - P: 'static + Send, + P: 'static, { let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap(); // Pre-emptively drop any downstream references of the old props root_scope.ensure_drop_safety(&self.scheduler.pool); - let mut root_props: Rc = Rc::new(root_props); + let mut root_props: Rc = Rc::new(root_props); if let Some(props_ptr) = root_props.downcast_ref::

().map(|p| p as *const P) { // Swap the old props and new props @@ -394,6 +395,3 @@ impl VirtualDom { } } } - -// we never actually use the contents of this root caller -struct RootCaller(Rc Fn(&'b ScopeInner) -> Element<'b> + 'static>); From 9874f342e2bc1116410142c691545d75b0e0d6fe Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 1 Nov 2021 18:38:09 -0400 Subject: [PATCH 16/18] docs: more docs --- docs/guide/src/SUMMARY.md | 3 +- docs/guide/src/concepts/components.md | 8 +- .../src/concepts/conditional_rendering.md | 10 +- docs/guide/src/concepts/event_handlers.md | 10 + docs/guide/src/concepts/interactivity.md | 184 ++++++++++++++++++ docs/guide/src/concepts/usestate.md | 1 + docs/guide/src/concepts/vnodes.md | 2 +- docs/guide/src/images/component_tree.png | Bin 0 -> 3724 bytes docs/guide/src/images/diffing.png | Bin 0 -> 23486 bytes packages/core/src/context.rs | 10 +- 10 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 docs/guide/src/concepts/usestate.md create mode 100644 docs/guide/src/images/component_tree.png create mode 100644 docs/guide/src/images/diffing.png diff --git a/docs/guide/src/SUMMARY.md b/docs/guide/src/SUMMARY.md index cb710c33d..14e12b45b 100644 --- a/docs/guide/src/SUMMARY.md +++ b/docs/guide/src/SUMMARY.md @@ -11,6 +11,8 @@ - [Lists](concepts/lists.md) - [Adding Interactivity](concepts/interactivity.md) - [Event handlers](concepts/event_handlers.md) + - [Hooks and Internal State](concepts/hooks.md) + - [Fundamental Hooks and `use_hook`](concepts/usestate.md) - [User Input and Controlled Components](concepts/errorhandling.md) - [Lifecycle, updates, and effects](concepts/lifecycles.md) @@ -30,7 +32,6 @@ Extracting State Logic into a Reducer Passing Data Deeply with Context Scaling Up with Reducer and Context --> - [Managing State](concepts/managing_state.md) - - [Hooks and Internal State](concepts/hooks.md) - [Global State](concepts/sharedstate.md) - [Error handling](concepts/errorhandling.md) - [Effects](concepts/effects.md) diff --git a/docs/guide/src/concepts/components.md b/docs/guide/src/concepts/components.md index d18a3c556..a5b824817 100644 --- a/docs/guide/src/concepts/components.md +++ b/docs/guide/src/concepts/components.md @@ -84,7 +84,7 @@ struct PostProps { And our render function: ```rust -fn Post((cx, props): Component) -> Element { +fn Post((cx, props): Scope) -> Element { cx.render(rsx!{ div { class: "post-container" VoteButton { @@ -120,7 +120,7 @@ struct VoteButtonProps { score: i32 } -fn VoteButton((cx, props): Component) -> Element { +fn VoteButton((cx, props): Scope) -> Element { cx.render(rsx!{ div { class: "votebutton" div { class: "arrow up" } @@ -145,7 +145,7 @@ struct TitleCardProps<'a> { title: &'a str, } -fn TitleCard<'a>((cx, props): Component<'a, TitleCardProps>) -> Element<'a> { +fn TitleCard<'a>((cx, props): Scope<'a, TitleCardProps>) -> Element<'a> { cx.render(rsx!{ h1 { "{props.title}" } }) @@ -174,7 +174,7 @@ function Component(props) { Because Dioxus needs to work with the rules of Rust it uses the `Context` object to maintain some internal bookkeeping. That's what the `Context` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Context` object to build robust and performant extensions for Dioxus. ```rust -fn Post((cx /* <-- our Context object*/, props): Component) -> Element { +fn Post((cx /* <-- our Context object*/, props): Scope) -> Element { cx.render(rsx!{ }) } ``` diff --git a/docs/guide/src/concepts/conditional_rendering.md b/docs/guide/src/concepts/conditional_rendering.md index d5fc002d8..977f66597 100644 --- a/docs/guide/src/concepts/conditional_rendering.md +++ b/docs/guide/src/concepts/conditional_rendering.md @@ -26,7 +26,7 @@ struct AppProps { Now that we have a "logged_in" flag accessible in our props, we can render two different screens: ```rust -fn App((cx, props): Component) -> Element { +fn App((cx, props): Scope) -> Element { if props.logged_in { cx.render(rsx!{ DashboardScreen {} @@ -48,7 +48,7 @@ Rust provides us algebraic datatypes: enums that can contain values. Using the ` For instance, we could run a function that returns a Result: ```rust -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { match get_name() { Ok(name) => cx.render(rsx!( "Hello, {name}!" )), Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )), @@ -58,7 +58,7 @@ fn App((cx, props): Component<()>) -> Element { We can even match against values: ```rust -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { match get_name() { "jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )), "diane" => cx.render(rsx!( "Hey Diana, how's Jack?" )), @@ -72,7 +72,7 @@ Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax: ```rust -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { match get_name() { "jack" => rsx!(cx, "Hey Jack, how's Diane?" ), "diane" => rsx!(cx, "Hey Diana, how's Jack?" ), @@ -89,7 +89,7 @@ static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!"); Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`: ```rust -fn App((cx, props): Component<()>) -> Element { +fn App((cx, props): Scope<()>) -> Element { let greeting = match get_name() { "jack" => rsx!("Hey Jack, how's Diane?" ), "diane" => rsx!("Hey Diana, how's Jack?" ), diff --git a/docs/guide/src/concepts/event_handlers.md b/docs/guide/src/concepts/event_handlers.md index ee4560b1e..632511ec4 100644 --- a/docs/guide/src/concepts/event_handlers.md +++ b/docs/guide/src/concepts/event_handlers.md @@ -1 +1,11 @@ # Event handlers + +In the overview for this section, we mentioned how we can modify the state of our component by responding to user-generated events inside of event listeners. + +In this section, we'll talk more about event listeners: +- What events are available +- Handling event data +- Mutability in event listeners + +## Event Listeners + diff --git a/docs/guide/src/concepts/interactivity.md b/docs/guide/src/concepts/interactivity.md index 5e99d9853..f8e5e68e2 100644 --- a/docs/guide/src/concepts/interactivity.md +++ b/docs/guide/src/concepts/interactivity.md @@ -1 +1,185 @@ # Adding Interactivity + +So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite a bit uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks. + +## Primer on interactivity + +Before we get too deep into the mechanics of interactivity, we should first understand how Dioxus exactly chooses to handle user interaction and updates to your app. + +### What is state? + +Every app you'll ever build has some sort of information that needs to be rendered to the screen. Dioxus is responsible for translating your desired user interface to what is rendered to the screen. *You* are responsible for providing the content. + +The dynamic data in your user interface is called `State`. + +When you first launch your app with `dioxus::web::launch_with_props` you'll be providing the initial state. You need to declare the initial state *before* starting the app. + +```rust +fn main() { + // declare our initial state + let props = PostProps { + id: Uuid::new_v4(), + score: 10, + comment_count: 0, + post_time: std::time::Instant::now(), + url: String::from("dioxuslabs.com"), + title: String::from("Hello, world"), + original_poster: String::from("dioxus") + }; + + // start the render loop + dioxus::desktop::launch_with_props(Post, props); +} +``` + +When Dioxus renders your app, it will pass an immutable reference of `PostProps` to your `Post` component. Here, you can pass the state down into children. + +```rust +fn App((cx, props): Scope) -> Element { + cx.render(rsx!{ + Title { title: &props.title } + Score { score: &props.score } + // etc + }) +} +``` + +State in Dioxus follows a pattern called "one-way-data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree. + +You've probably seen the tree of UI components represented using an directed-acyclic-graph: + +![image](../images/component_tree.png) + +With Dioxus, your state will always flow down from parent components into child components. + +### How do I change my app's state? + +We've talked about the data flow of state, but we haven't yet talked about how to change that state dynamically. Dioxus provides a variety of ways to change the state of your app while it's running. + +For starters, we _could_ use the `update_root_props` method on the VirtualDom to provide an entirely new root state of your App. However, for most applications, you probably don't want to regenerate your entire app just to update some text or a flag. + +Instead, you'll want to store state internally in your components and let *that* flow down the tree. To store state in your components, you'll use something called a `hook`. Hooks are special functions that reserve a slot of state in your component's memory and provide some functionality to update that state. + +The most common hook you'll use for storing state is `use_state`. `use_state` provides a slot for some data that allows you to read and update the value without accidentally mutating it. + +```rust +fn App((cx, props): Scope<()>) -> Element { + let post = use_state(|| { + PostData { + id: Uuid::new_v4(), + score: 10, + comment_count: 0, + post_time: std::time::Instant::now(), + url: String::from("dioxuslabs.com"), + title: String::from("Hello, world"), + original_poster: String::from("dioxus") + } + }); + + cx.render(rsx!{ + Title { title: &post.title } + Score { score: &post.score } + // etc + }) +} +``` + +Whenever we have a new post that we want to render, we can call `set` on `post` and provide a new value: + +```rust +post.set(PostData { + id: Uuid::new_v4(), + score: 20, + comment_count: 0, + post_time: std::time::Instant::now(), + url: String::from("google.com"), + title: String::from("goodbye, world"), + original_poster: String::from("google") +}) +``` + +We'll dive deeper into how exactly these hooks work later. + +### When do I update my state? + +There are a few different approaches to choosing when to update your state. You can update your state in response to user-triggered events or asynchronously in some background task. + +### Updating state in listeners + +When responding to user-triggered events, we'll want to "listen" for an event on some element in our component. + +For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the `on_click` method of `button`. Whenever the button is clicked, our function will run, and we'll get new Post data to work with. + +```rust +fn App((cx, props): Scope<()>) -> Element { + let post = use_state(|| PostData::new()); + + cx.render(rsx!{ + button { + on_click: move |_| post.set(PostData::random()) + "Generate a random post" + } + Post { props: &post } + }) +} +``` + +We'll dive much deeper into event listeners later. + +### Updating state asynchronously + +We can also update our state outside of event listeners with `tasks`. `Tasks` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component. `Tasks` are one-shot - if they don't complete before the component is updated by another `.set` call, then they won't finish. + +We can use tasks in our components to build a tiny stopwatch that ticks every second. + +```rust + +fn App((cx, props): Scope<()>) -> Element { + let mut sec_elapsed = use_state(|| 0); + + cx.spawn_task(async move { + TimeoutFuture::from_ms(1000).await; + sec_elapsed += 1; + }); + + cx.render(rsx!{ + div { "Current stopwatch time: {sec_elapsed}" } + }) + +} +``` + +Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. + +### How do I tell Dioxus that my state changed? + +So far, we've only updated our state with `.set`. However, you might've noticed that we used `AddAssign` to increment the `sec_elapsed` value in our stopwatch example *without* calling set. This is because the `AddAssign` trait is implemented for `UseState` (the wrapper around our value returned from `use_state`). Under the hood, whenever you try to mutate our value through `UseState`, you're actually calling `.set` which informs Dioxus that _this_ component needs to be updated on the screen. + +Whenever you inform Dioxus that the component needs to be updated, it will "render" your component again, storing the previous and current Elements in memory. Dioxus will automatically figure out the differences between the old and the new and generate a list of edits that the renderer needs to apply to change what's on the screen. This process is called "diffing": + +![Diffing](../images/diffing.png) + +In React, the specifics of when a component gets re-rendered is somewhat blurry. With Dioxus, any component can mark itself as "dirty" through a method on `Context`: `needs_update`. In addition, any component can mark any _other_ component as dirty provided it knows the other component's ID with `needs_update_any`. + +With these building blocks, we can craft new hooks similar to `use_state` that let us easily tell Dioxus that new information is ready to be sent to the screen. + +### How do I update my state efficiently? + +In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick. + +- 1) **Don't call set—state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates. +- 2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function. +- 3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders. + +Don't worry - Dioxus is fast. But, if your app needs *extreme performance*, then take a look at the `Performance Tuning` in the `Advanced Guides` book. + + + +## Moving On + +This overview was a lot of information - but it doesn't tell you everything! + +In the next sections we'll go over: +- `use_state` in depth +- `use_ref` and other hooks +- Handling user input diff --git a/docs/guide/src/concepts/usestate.md b/docs/guide/src/concepts/usestate.md new file mode 100644 index 000000000..86a6b2efc --- /dev/null +++ b/docs/guide/src/concepts/usestate.md @@ -0,0 +1 @@ +# Fundamental Hooks and use_hook diff --git a/docs/guide/src/concepts/vnodes.md b/docs/guide/src/concepts/vnodes.md index 0bd5afc95..774b1bfba 100644 --- a/docs/guide/src/concepts/vnodes.md +++ b/docs/guide/src/concepts/vnodes.md @@ -129,7 +129,7 @@ rsx!( ) ``` -Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention. +Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention. When using custom attributes, make sure the name of the attribute exactly matches what the renderer is expecting. ## Listeners diff --git a/docs/guide/src/images/component_tree.png b/docs/guide/src/images/component_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..d9692407350138e0b7597e8705e33f58a463357a GIT binary patch literal 3724 zcmcInX*3iL*H;uzihqfO;UA%f>7+f_`)MmHV*g8YZH8`ILU zg?%^7>|JA`DcK=2){$?tlul(Y_?kk^SWfwBKFpIZJ^J42!>1ee>OZ`19zdjieh<%* zyK+xE^P%QBm~GuI)9#s4(mg_R_5y!%;T+!e@@$zET zQt2y3M0D-{xmfw`WFhxh3@Huqx_c7Hg@c6)I~1o-tlVQncze&Ojlh!OSBF#bC-T3e z?6q+K3X}n!H5}yQEm3P%it>3|8o}b|JocDZj=AuT~c}#A#+kY^84czx>#Nec;w3AQLe7w)E))FgB zy8u4REjaFXQ~avii&9@&Oy#p^^0!unzL0(;+5@UFty&I#RVN&9qe))*Tnh|x<5F6m zjW9FBk{DUu^97(LEdDg)(H4K^t7t%8y@`z^XD`%w`=PualCR^*&Bl8TDxesEdEqJB zWyzozh)u8^?<%S{xkxZE533SeeYV#fPu_mUYv#sBPlN40*CH=iYrRUdIw1^*zSH&V zLVaX~;Nr;`pfmQRl-EKTr&VZS7CQ0M^%Q9zMQmBED;9789HFg4(lLaG{=NvNy!~Qh z<+TUAh}H>q`+Y@Fw@R$}`{?s*zKo^e7Pz^}eIY{^%cU7vVH-3aYoZ@6m3Bq@4Zzd^ z@|G9!(^$SgCo5%`VQ386I%1F*KWzeJiAEtH5I$Mhd#=h|h&Zg!23zq~;zbVL{%i!p zxgYNRhE6GaiE+a}T4WaNY)1n;vn(FZ86SMJ$03$H-FSIak2cy-q&|6J)hgJqcYRWa zlEwLrAE;23CvmYAj#PUlisV(FLYCuqAkbYrZna?)8cA~W==BEi*ObxG;7K1(^qtAX zpxTxUhJn8{GPC(}3+g}|_aUfus?i~LkF+1odq4)V26=Y>kXcG+NDR45rU_BP*&_w% zAGHT5%Wt3enf%TEH~ZaAq$G}4ZLW37(RNdfZI|B4CZcf5dfgn=@qihJG;ExI0^Be| z#jCU}O*Ld2G5ZhkiLmaGzUAqj1*W8Riio!eJq*`+AC>G z+*R-%3(7`^(SVTF4}uGjRz3|)zV|}i3GkOr5SZ=0QI?D(l1oGilYgSZ_ldkY%YNSJ z+f7Jofsjg2#e!s$^lc=U@GiR(2nMOE5W)&i8cJoKGN%Epk9@RC@rv7`Bw`$Vr;(St8tsGp?`ON*osbqCz= zdQWlVtfdfm;9UVUWfbWfnSha0$sMQOQ_d7Yv!|tF}3T zGAfRaBl{}B2{5FB;+8AAa3hA4#!ze1tG1Bnqg2G_E$G;*vF^I$mr zwh-HTV7zV5fX8TsP@S`PEF9%tDj@}gUG7d$J83LPT6S%2>rl3BaV~eQ1YHI{wg^(Q zd(#;sBT<@to^CMcF;9N~`624yn!-Pkx#swKeF%fwQh7Pu52J7^?U$@e(u)E~4Szp9 z0m?B-J%eDv%dkUB?oYQJPn_^+Hkwu=w0BI^B=)z;O=|GtGa4|qSz26rxb;s|iY0y? zy8qponSQ_F=U6?0r%=SYa+lIGzsH{<=3``bc<6I-%=J8?leNWee##_EzF}j6t`TC- z$_-GTv4bk`-_>lca7^y|!|xWx@d`W+0{M)L37Gk45*0QR-J_JFP>FLlwDc+0^3BBl zXb;~Vs#awZzW_S0y19UaLrmPrjdmEYywhJT*$TINZpXT?mwJ96bA9ziN)F;OHQ>dv zeXdCEJn_c9S#jx3G=N#tr%u@xyD-M3Rleu3`VPQN99?hrM1(}SAa}rOV-&MolT5!Ry1#Uj;+PJiByvArvR&v0My&DoBELZ9)W(d~ z`=V6m7~1YoLeaaa0~mS)WyGV}T@&}{jvxv2W)g7Q4><{O$sC9mDI=MNlHeh}XCa#E za#bV^_G>ePDvjKOq)}y~zuGdUUPG>wj=ER@{ic51zFBiuPnRl}F{%^QJBS1|*{$Kh z4zq0URx9s~M+8I@Kzl@!SqtcKk^hW^ik%M;Rr2dDrbEWe3v7Mh?>)`yg@dlM>ol#|dqtkmf=&@(6j=_3>xmcWE#<$8x|}Zk{gN_1F_9skmm6 zl+346a8eJ`9ERjHC=81XR=yqw*d*=cYV4KN1=jsU^Qk%$5A)$SlQKkIB5i`;dJ*2a zN$GjBS*ssMeIi39!|~((+FpIzuN4T*f8m|^e8e-fbLty>;Ux#|{G*`qqp-rJe$q%Y zDB}a*RkR>l+N%hrCkP(!XoLdSonAxmSZ(n_kpE~~9)p>48SDtv3v;?8n$Rolk?Dr4 zmW5Mq%`)T`c!7O07VvfBvTRHL65Pr}M}$t#%P{25I)AUU2K**oPv4zdWmomMFT*YM zPvVx>pFxYyNPtsD`w2?DZj70D_Nz3q3|}zIwRRZe?r{r8K^06HQu-1)jC}3-tGDLv zs9o&lLO-s5l|t)8RfjIR6$h=6=CD4JM+O$t&$$nSTO7jS85(M%H^qj5@($mp%_jDX z6fpO-mhs~^yfb`f%3&YEBBdY(q4KI1iW@`M#1z3fr&rq1pDsmRT~{%(Nw8#`59~^n zIPnV(3-r;RqT8b4=O72^1JUrkiZ6f?bz5gh6hPL7b7g%^Vay<4$hFur_2D`JHH0u`OfIQOiFa5* z8(xDYEb)%Gob}a;v=5s|Hw^g@UV+%mZO2X!wmDgk%4a}SnWhj&+)xCLkP|5u+-#5Y zVB^M87zW%3crT((OdNh}%Dx)Bm{%D){ji}G@ z)Vvd(Cn0wU=rzoaBA+I>CSrO0Yn!nwePf+tK7>yzWVOR;EC#Ub8Gj3hy7WhW6KwvO z=b5h2X78W~CR?>^ATj^w-nE7#m^DT21?3-3T19q8J=etTwBAz=N~d#7;{a!_3mGkO zzDA+|m2ACPAv{erx+#dRD1IO4qnVlCSIlZ@F#Yxwu}t~ir@a^b+#(Tkfimon0x?*C+)HMM<$Ords3f}FO?&zV=p#;W)cdlUb{bKf8MO`9u;Gea*#YRs@Wnj_tzo?8pOZ#kx@USbOx|)A~<{{g*whKHE7evVU=3 z80pG>G`tej`@jE`f zOlkV6A}`p6tmFo?K&MDyIuX8ip8x!6;qEMdhZsM3Ijh-T*0B9>-${K|@!)IUAbEHi z5{zulnd}|70$-@`cU6Q(y~0@r1<$=a8rQHLo02HljiyDneuLp8sP+hU`+qZd#Vpsv zTt^jvI(tUD>VW%0F(2(xQ4FIq@SC)6kr+^soOjqOSveSoy#{;$K-yJ`(@{ literal 0 HcmV?d00001 diff --git a/docs/guide/src/images/diffing.png b/docs/guide/src/images/diffing.png new file mode 100644 index 0000000000000000000000000000000000000000..4c243bc759085f24ada3be039cb763e1d59fc2f5 GIT binary patch literal 23486 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 Bge?F7 literal 0 HcmV?d00001 diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 8e3389761..ca5d9cf25 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -65,6 +65,10 @@ impl<'src> Context<'src> { (self.scope.memoized_updater)() } + pub fn needs_update_any(&self, id: ScopeId) { + (self.scope.shared.schedule_any_immediate)(id) + } + /// Schedule an update for any component given its ScopeId. /// /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method. @@ -101,11 +105,7 @@ impl<'src> Context<'src> { /// cx.render(lazy_tree) /// } ///``` - pub fn render( - self, - lazy_nodes: Option>, - // lazy_nodes: Option) -> VNode<'src> + '_>>, - ) -> Option> { + pub fn render(self, lazy_nodes: Option>) -> Option> { let bump = &self.scope.frames.wip_frame().bump; let factory = NodeFactory { bump }; lazy_nodes.map(|f| f.call(factory)) From d298b626d3ae21a39a8ec4426373369ac94edf9f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 3 Nov 2021 00:35:56 -0400 Subject: [PATCH 17/18] wip: add router --- Cargo.toml | 3 +- docs/guide/src/concepts/interactivity.md | 2 - packages/core-macro/src/lib.rs | 42 +++- packages/core-macro/src/router.rs | 247 +++++++++++++++++++++++ packages/core-macro/src/rsx/body.rs | 3 +- packages/core/src/component.rs | 4 +- packages/core/src/context.rs | 9 +- packages/router/Cargo.toml | 39 ++++ packages/router/examples/blog.rs | 28 +++ packages/router/examples/v2.rs | 88 ++++++++ packages/router/src/lib.rs | 140 +++++++++++++ packages/router/src/platform.rs | 1 + packages/router/src/platform/desktop.rs | 0 packages/router/src/platform/mobile.rs | 0 packages/router/src/platform/web.rs | 0 packages/router/src/service.rs | 0 packages/router/src/utils.rs | 31 +++ 17 files changed, 617 insertions(+), 20 deletions(-) create mode 100644 packages/core-macro/src/router.rs create mode 100644 packages/router/Cargo.toml create mode 100644 packages/router/examples/blog.rs create mode 100644 packages/router/examples/v2.rs create mode 100644 packages/router/src/lib.rs create mode 100644 packages/router/src/platform.rs create mode 100644 packages/router/src/platform/desktop.rs create mode 100644 packages/router/src/platform/mobile.rs create mode 100644 packages/router/src/platform/web.rs create mode 100644 packages/router/src/service.rs create mode 100644 packages/router/src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index d265c0644..9a066caa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi [dependencies] dioxus-core = { path = "./packages/core", version = "0.1.3" } +dioxus-router = { path = "./packages/router", optional = true } dioxus-core-macro = { path = "./packages/core-macro", optional = true } dioxus-html = { path = "./packages/html", optional = true } dioxus-web = { path = "./packages/web", optional = true } @@ -24,10 +25,10 @@ core = ["macro", "hooks", "html"] macro = ["dioxus-core-macro"] hooks = ["dioxus-hooks"] html = ["dioxus-html"] +router = ["dioxus-router"] # utilities atoms = [] -router = [] # targets ssr = ["dioxus-ssr"] diff --git a/docs/guide/src/concepts/interactivity.md b/docs/guide/src/concepts/interactivity.md index f8e5e68e2..a7403933c 100644 --- a/docs/guide/src/concepts/interactivity.md +++ b/docs/guide/src/concepts/interactivity.md @@ -173,8 +173,6 @@ In general, Dioxus should be plenty fast for most use cases. However, there are Don't worry - Dioxus is fast. But, if your app needs *extreme performance*, then take a look at the `Performance Tuning` in the `Advanced Guides` book. - - ## Moving On This overview was a lot of information - but it doesn't tell you everything! diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index a132148c8..ce36fb83c 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -5,6 +5,7 @@ use syn::parse_macro_input; pub(crate) mod htm; pub(crate) mod ifmt; pub(crate) mod props; +pub(crate) mod router; pub(crate) mod rsx; #[proc_macro] @@ -82,12 +83,9 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token /// }} /// /// // Matching -/// // Matching will throw a Rust error about "no two closures are the same type" -/// // To fix this, call "render" method or use the "in" syntax to produce VNodes. -/// // There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros) /// {match true { -/// true => rsx!(cx, h1 {"Top text"}), -/// false => cx.render(rsx!( h1 {"Bottom text"})) +/// true => rsx!(h1 {"Top text"}), +/// false => rsx!(h1 {"Bottom text"}) /// }} /// /// // Conditional rendering @@ -95,11 +93,11 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token /// // You can convert a bool condition to rsx! with .then and .or /// {true.then(|| rsx!(div {}))} /// -/// // True conditions need to be rendered (same reasons as matching) +/// // True conditions /// {if true { -/// rsx!(cx, h1 {"Top text"}) +/// rsx!(h1 {"Top text"}) /// } else { -/// rsx!(cx, h1 {"Bottom text"}) +/// rsx!(h1 {"Bottom text"}) /// }} /// /// // returning "None" is a bit noisy... but rare in practice @@ -186,3 +184,31 @@ pub fn rsx(s: TokenStream) -> TokenStream { Ok(s) => s.to_token_stream().into(), } } + +/// Derive macro used to mark an enum as Routable. +/// +/// This macro can only be used on enums. Every varient of the macro needs to be marked +/// with the `at` attribute to specify the URL of the route. It generates an implementation of +/// `yew_router::Routable` trait and `const`s for the routes passed which are used with `Route` +/// component. +/// +/// # Example +/// +/// ``` +/// # use yew_router::Routable; +/// #[derive(Debug, Clone, Copy, PartialEq, Routable)] +/// enum Routes { +/// #[at("/")] +/// Home, +/// #[at("/secure")] +/// Secure, +/// #[at("/404")] +/// NotFound, +/// } +/// ``` +#[proc_macro_derive(Routable, attributes(at, not_found))] +pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + use router::{routable_derive_impl, Routable}; + let input = parse_macro_input!(input as Routable); + routable_derive_impl(input).into() +} diff --git a/packages/core-macro/src/router.rs b/packages/core-macro/src/router.rs new file mode 100644 index 000000000..684d2dc7d --- /dev/null +++ b/packages/core-macro/src/router.rs @@ -0,0 +1,247 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{Data, DeriveInput, Fields, Ident, LitStr, Variant}; + +const AT_ATTR_IDENT: &str = "at"; +const NOT_FOUND_ATTR_IDENT: &str = "not_found"; + +pub struct Routable { + ident: Ident, + ats: Vec, + variants: Punctuated, + not_found_route: Option, +} + +impl Parse for Routable { + fn parse(input: ParseStream) -> syn::Result { + let DeriveInput { ident, data, .. } = input.parse()?; + + let data = match data { + Data::Enum(data) => data, + Data::Struct(s) => { + return Err(syn::Error::new( + s.struct_token.span(), + "expected enum, found struct", + )) + } + Data::Union(u) => { + return Err(syn::Error::new( + u.union_token.span(), + "expected enum, found union", + )) + } + }; + + let (not_found_route, ats) = parse_variants_attributes(&data.variants)?; + + Ok(Self { + ident, + variants: data.variants, + ats, + not_found_route, + }) + } +} + +fn parse_variants_attributes( + variants: &Punctuated, +) -> syn::Result<(Option, Vec)> { + let mut not_founds = vec![]; + let mut ats: Vec = vec![]; + + let mut not_found_attrs = vec![]; + + for variant in variants.iter() { + if let Fields::Unnamed(ref field) = variant.fields { + return Err(syn::Error::new( + field.span(), + "only named fields are supported", + )); + } + + let attrs = &variant.attrs; + let at_attrs = attrs + .iter() + .filter(|attr| attr.path.is_ident(AT_ATTR_IDENT)) + .collect::>(); + + let attr = match at_attrs.len() { + 1 => *at_attrs.first().unwrap(), + 0 => { + return Err(syn::Error::new( + variant.span(), + format!( + "{} attribute must be present on every variant", + AT_ATTR_IDENT + ), + )) + } + _ => { + return Err(syn::Error::new_spanned( + quote! { #(#at_attrs)* }, + format!("only one {} attribute must be present", AT_ATTR_IDENT), + )) + } + }; + + let lit = attr.parse_args::()?; + ats.push(lit); + + for attr in attrs.iter() { + if attr.path.is_ident(NOT_FOUND_ATTR_IDENT) { + not_found_attrs.push(attr); + not_founds.push(variant.ident.clone()) + } + } + } + + if not_founds.len() > 1 { + return Err(syn::Error::new_spanned( + quote! { #(#not_found_attrs)* }, + format!("there can only be one {}", NOT_FOUND_ATTR_IDENT), + )); + } + + Ok((not_founds.into_iter().next(), ats)) +} + +impl Routable { + fn build_from_path(&self) -> TokenStream { + let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { + let ident = &variant.ident; + let right = match &variant.fields { + Fields::Unit => quote! { Self::#ident }, + Fields::Named(field) => { + let fields = field.named.iter().map(|it| { + //named fields have idents + it.ident.as_ref().unwrap() + }); + quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } } + } + Fields::Unnamed(_) => unreachable!(), // already checked + }; + + let left = self.ats.get(i).unwrap(); + quote! { + #left => ::std::option::Option::Some(#right) + } + }); + + quote! { + fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option { + match path { + #(#from_path_matches),*, + _ => ::std::option::Option::None, + } + } + } + } + + fn build_to_path(&self) -> TokenStream { + let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { + let ident = &variant.ident; + let mut right = self.ats.get(i).unwrap().value(); + + match &variant.fields { + Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) }, + Fields::Named(field) => { + let fields = field + .named + .iter() + .map(|it| it.ident.as_ref().unwrap()) + .collect::>(); + + for field in fields.iter() { + // :param -> {param} + // so we can pass it to `format!("...", param)` + right = right.replace(&format!(":{}", field), &format!("{{{}}}", field)) + } + + quote! { + Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*) + } + } + Fields::Unnamed(_) => unreachable!(), // already checked + } + }); + + quote! { + fn to_path(&self) -> ::std::string::String { + match self { + #(#to_path_matches),*, + } + } + } + } +} + +pub fn routable_derive_impl(input: Routable) -> TokenStream { + let Routable { + ats, + not_found_route, + ident, + .. + } = &input; + + let from_path = input.build_from_path(); + let to_path = input.build_to_path(); + + let not_found_route = match not_found_route { + Some(route) => quote! { ::std::option::Option::Some(Self::#route) }, + None => quote! { ::std::option::Option::None }, + }; + + let cache_thread_local_ident = Ident::new( + &format!("__{}_ROUTER_CURRENT_ROUTE_CACHE", ident), + ident.span(), + ); + + quote! { + ::std::thread_local! { + #[doc(hidden)] + #[allow(non_upper_case_globals)] + static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None); + } + + #[automatically_derived] + impl ::dioxus::router::Routable for #ident { + #from_path + #to_path + + fn routes() -> ::std::vec::Vec<&'static str> { + ::std::vec![#(#ats),*] + } + + fn not_found_route() -> ::std::option::Option { + #not_found_route + } + + fn current_route() -> ::std::option::Option { + #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow())) + } + + fn recognize(pathname: &str) -> ::std::option::Option { + ::std::thread_local! { + static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>(); + } + let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname)); + { + let route = ::std::clone::Clone::clone(&route); + #cache_thread_local_ident.with(move |val| { + *val.borrow_mut() = route; + }); + } + route + } + + fn cleanup() { + #cache_thread_local_ident.with(move |val| { + *val.borrow_mut() = ::std::option::Option::None; + }); + } + } + } +} diff --git a/packages/core-macro/src/rsx/body.rs b/packages/core-macro/src/rsx/body.rs index 404d5083e..34c16fb86 100644 --- a/packages/core-macro/src/rsx/body.rs +++ b/packages/core-macro/src/rsx/body.rs @@ -49,9 +49,8 @@ impl ToTokens for CallBody { match &self.custom_context { // The `in cx` pattern allows directly rendering Some(ident) => out_tokens.append_all(quote! { - #ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory| -> VNode { + #ident.render(NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; - #inner })) }), diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 26c920596..5c7b4c36c 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -15,7 +15,7 @@ use crate::innerlude::{Context, Element, VNode}; /// ```rust /// struct State {} /// -/// fn Example((cx, props): Component) -> DomTree { +/// fn Example((cx, props): Scope) -> DomTree { /// // ... /// } /// ``` @@ -26,7 +26,7 @@ use crate::innerlude::{Context, Element, VNode}; /// name: &'a str /// } /// -/// fn Example<'a>((cx, props): Component<'a, State>) -> DomTree<'a> { +/// fn Example<'a>((cx, props): Scope<'a, State>) -> DomTree<'a> { /// // ... /// } /// ``` diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index ca5d9cf25..95953ff16 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -132,12 +132,11 @@ impl<'src> Context<'src> { (self.scope.shared.submit_task)(task) } - /// This hook enables the ability to expose state to children further down the VirtualDOM Tree. + /// This method enables the ability to expose state to children further down the VirtualDOM Tree. /// - /// This is a hook, so it should not be called conditionally! + /// This is a "fundamental" operation and should only be called during initialization of a hook. /// - /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`) - /// so don't put it in a conditional. + /// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead. /// /// When the component is dropped, so is the context. Be aware of this behavior when consuming /// the context via Rc/Weak. @@ -148,7 +147,7 @@ impl<'src> Context<'src> { /// struct SharedState(&'static str); /// /// static App: FC<()> = |(cx, props)|{ - /// cx.provide_state(SharedState("world")); + /// cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {}); /// rsx!(cx, Child {}) /// } /// diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml new file mode 100644 index 000000000..93a811a76 --- /dev/null +++ b/packages/router/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "dioxus-router" +version = "0.0.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus-core = { path = "../core", version = "0.1.3", default-features = false } +dioxus-core-macro = { path = "../core-macro", version = "0.1" } +dioxus-html = { path = "../html", version = "0.0.0", default-features = false } + +web-sys = { version = "0.3", features = [ + "Attr", + "Document", + "History", + "HtmlBaseElement", + "Event", + "NamedNodeMap", + "Url", + "UrlSearchParams", + "Window", +], optional = true } + +serde = "1" + +serde_urlencoded = "0.7" + +wasm-bindgen = "0.2" +js-sys = "0.3" +gloo = "0.4" +route-recognizer = "0.3.1" + + +[features] +default = ["web"] +web = ["web-sys"] +desktop = [] +mobile = [] diff --git a/packages/router/examples/blog.rs b/packages/router/examples/blog.rs new file mode 100644 index 000000000..fa282363c --- /dev/null +++ b/packages/router/examples/blog.rs @@ -0,0 +1,28 @@ +use dioxus::prelude::*; + +fn main() {} + +enum Routes { + Home, + Blog, +} + +static App: FC<()> = |(cx, props)| { + let route = use_router::(cx); + + let content = match route { + Routes::Home => rsx!(Home {}), + Routes::Blog => rsx!(Blog {}), + }; + + cx.render(rsx! { + {content} + }) +}; + +fn use_router

(cx: Context) -> &P { + todo!() +} + +static Home: FC<()> = |(cx, props)| todo!(); +static Blog: FC<()> = |(cx, props)| todo!(); diff --git a/packages/router/examples/v2.rs b/packages/router/examples/v2.rs new file mode 100644 index 000000000..6bea4b58e --- /dev/null +++ b/packages/router/examples/v2.rs @@ -0,0 +1,88 @@ +use dioxus::prelude::*; +use dioxus_router::{use_router, Link, Routable}; + +fn main() { + dbg!(App as *const fn()); +} + +#[derive(PartialEq, Debug, Clone)] +pub enum Route { + // #[at("/")] + Home, + + // #[at("/:id")] + AllUsers { page: u32 }, + + // #[at("/:id")] + User { id: u32 }, + + // #[at("/:id")] + BlogList { page: u32 }, + + // #[at("/:id")] + BlogPost { post_id: u32 }, + + // #[at("/404")] + // #[not_found] + NotFound, +} + +static App: FC<()> = |(cx, props)| { + let route = use_router(cx, Route::parse)?; + + cx.render(rsx! { + nav { + Link { to: Route::Home, "Go home!" } + Link { to: Route::AllUsers { page: 0 }, "List all users" } + Link { to: Route::BlogList { page: 0 }, "Blog posts" } + } + {match route { + Route::Home => rsx!("Home"), + Route::AllUsers { page } => rsx!("All users - page {page}"), + Route::User { id } => rsx!("User - id: {id}"), + Route::BlogList { page } => rsx!("Blog posts - page {page}"), + Route::BlogPost { post_id } => rsx!("Blog post - post {post_id}"), + Route::NotFound => rsx!("Not found"), + }} + footer {} + }) +}; + +impl Route { + // Generate the appropriate route from the "tail" end of the URL + fn parse(url: &str) -> Self { + use Route::*; + match url { + "/" => Home, + "/users" => AllUsers { page: 1 }, + "/users/:page" => AllUsers { page: 1 }, + "/users/:page/:id" => User { id: 1 }, + "/blog" => BlogList { page: 1 }, + "/blog/:page" => BlogList { page: 1 }, + "/blog/:page/:id" => BlogPost { post_id: 1 }, + _ => NotFound, + } + } +} + +impl Routable for Route { + fn from_path(path: &str, params: &std::collections::HashMap<&str, &str>) -> Option { + todo!() + } + + fn to_path(&self) -> String { + todo!() + } + + fn routes() -> Vec<&'static str> { + todo!() + } + + fn not_found_route() -> Option { + todo!() + } + + fn recognize(pathname: &str) -> Option { + todo!() + } +} diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs new file mode 100644 index 000000000..378935546 --- /dev/null +++ b/packages/router/src/lib.rs @@ -0,0 +1,140 @@ +mod utils; + +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_core_macro::{format_args_f, rsx, Props}; +use dioxus_html as dioxus_elements; +use wasm_bindgen::JsValue; +use web_sys::Event; + +use crate::utils::fetch_base_url; + +pub struct RouterService { + history: RefCell>, + base_ur: RefCell>, +} + +impl RouterService { + fn push_route(&self, r: R) { + self.history.borrow_mut().push(r); + } + + fn get_current_route(&self) -> &str { + todo!() + } + + fn update_route_impl(&self, url: String, push: bool) { + let history = web_sys::window().unwrap().history().expect("no history"); + let base = self.base_ur.borrow(); + let path = match base.as_ref() { + Some(base) => { + let path = format!("{}{}", base, url); + if path.is_empty() { + "/".to_string() + } else { + path + } + } + None => url, + }; + + if push { + history + .push_state_with_url(&JsValue::NULL, "", Some(&path)) + .expect("push history"); + } else { + history + .replace_state_with_url(&JsValue::NULL, "", Some(&path)) + .expect("replace history"); + } + let event = Event::new("popstate").unwrap(); + + web_sys::window() + .unwrap() + .dispatch_event(&event) + .expect("dispatch"); + } +} + +/// This hould only be used once per app +/// +/// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine +pub fn use_router(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option { + // for the web, attach to the history api + cx.use_hook( + |f| { + // + use gloo::events::EventListener; + + let base_url = fetch_base_url(); + + let service: RouterService = RouterService { + history: RefCell::new(vec![]), + base_ur: RefCell::new(base_url), + }; + + cx.provide_state(service); + + let regenerate = cx.schedule_update(); + + // when "back" is called by the user, we want to to re-render the component + let listener = EventListener::new(&web_sys::window().unwrap(), "popstate", move |_| { + // + regenerate(); + }); + }, + |f| { + // + }, + |f| { + // + }, + ); + + let router = use_router_service::(cx)?; + Some(cfg(router.get_current_route())) +} + +pub fn use_router_service(cx: Context) -> Option<&Rc>> { + cx.use_hook( + |_| cx.consume_state::>(), + |f| f.as_ref(), + |f| {}, + ) +} + +#[derive(Props)] +pub struct LinkProps<'a, R: Routable> { + to: R, + children: ScopeChildren<'a>, +} + +pub fn Link<'a, R: Routable>((cx, props): Scope<'a, LinkProps<'a, R>>) -> Element { + let service = use_router_service::(cx)?; + cx.render(rsx! { + a { + href: format_args!("{}", props.to.to_path()), + onclick: move |_| service.push_route(props.to.clone()), + {&props.children}, + } + }) +} + +pub trait Routable: Sized + Clone + 'static { + /// Converts path to an instance of the routes enum. + fn from_path(path: &str, params: &HashMap<&str, &str>) -> Option; + + /// Converts the route to a string that can passed to the history API. + fn to_path(&self) -> String; + + /// Lists all the available routes + fn routes() -> Vec<&'static str>; + + /// The route to redirect to on 404 + fn not_found_route() -> Option; + + /// Match a route based on the path + fn recognize(pathname: &str) -> Option; +} diff --git a/packages/router/src/platform.rs b/packages/router/src/platform.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/router/src/platform.rs @@ -0,0 +1 @@ + diff --git a/packages/router/src/platform/desktop.rs b/packages/router/src/platform/desktop.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/router/src/platform/mobile.rs b/packages/router/src/platform/mobile.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/router/src/platform/web.rs b/packages/router/src/platform/web.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/router/src/utils.rs b/packages/router/src/utils.rs new file mode 100644 index 000000000..46764ebab --- /dev/null +++ b/packages/router/src/utils.rs @@ -0,0 +1,31 @@ +use wasm_bindgen::JsCast; +use web_sys::window; + +pub fn fetch_base_url() -> Option { + match window() + .unwrap() + .document() + .unwrap() + .query_selector("base[href]") + { + Ok(Some(base)) => { + let base = JsCast::unchecked_into::(base).href(); + + let url = web_sys::Url::new(&base).unwrap(); + let base = url.pathname(); + + let base = if base != "/" { + strip_slash_suffix(&base) + } else { + return None; + }; + + Some(base.to_string()) + } + _ => None, + } +} + +pub(crate) fn strip_slash_suffix(path: &str) -> &str { + path.strip_suffix('/').unwrap_or(path) +} From 1e6e5e611b61571f272289adefc9cdd7d59c4399 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 3 Nov 2021 15:13:50 -0400 Subject: [PATCH 18/18] wip: move examples around --- .vscode/settings.json | 2 +- Cargo.toml | 1 - examples/calculator.rs | 5 +- .../examples => examples/core}/alternative.rs | 0 .../core/examples => examples/core}/async.rs | 0 .../examples => examples/core}/borrowed.rs | 0 .../examples => examples/core}/contextapi.rs | 0 .../core/examples => examples/core}/expand.rs | 0 .../core}/fragment_from_iter.rs | 0 .../core/examples => examples/core}/html.rs | 0 .../examples => examples/core}/jsframework.rs | 0 .../core/examples => examples/core}/syntax.rs | 0 .../examples => examples/core}/syntax2.rs | 0 .../core/examples => examples/core}/tasks.rs | 0 .../examples => examples/core}/vdom_usage.rs | 0 .../core_reference}/README.md | 0 .../core_reference}/antipatterns.rs | 0 .../core_reference}/basics.rs | 0 .../core_reference}/children.rs | 0 .../core_reference}/conditional_rendering.rs | 0 .../core_reference}/controlled_inputs.rs | 0 .../core_reference}/custom_elements.rs | 0 .../core_reference}/empty.rs | 0 .../core_reference}/errorhandling.rs | 0 .../core_reference}/fragments.rs | 0 .../core_reference}/global_css.rs | 0 .../core_reference}/inline_styles.rs | 0 .../core_reference}/iterators.rs | 0 .../core_reference}/listener.rs | 0 .../core_reference}/memo.rs | 0 .../core_reference}/noderefs.rs | 0 .../core_reference}/signals.rs | 0 .../core_reference}/spreadpattern.rs | 0 .../core_reference}/statemanagement.rs | 0 .../core_reference}/suspense.rs | 0 .../core_reference}/task.rs | 0 .../core_reference}/testing.rs | 0 .../core_reference}/tostring.rs | 0 examples/coroutine.rs | 20 +++---- examples/crm.rs | 10 ++-- .../examples => examples/desktop}/core.rs | 0 .../examples => examples/desktop}/crm.rs | 0 .../examples => examples/desktop}/demo.rs | 0 .../desktop}/kitchensink.rs | 0 .../examples => examples/desktop}/tauri.rs | 0 .../examples => examples/desktop}/todomvc.css | 0 .../examples => examples/desktop}/todomvc.rs | 16 +++--- examples/framework_benchmark.rs | 4 +- examples/pattern_model.rs | 5 +- .../examples/v2.rs => examples/router.rs | 8 ++- examples/rsx_usage.rs | 15 +++--- .../ssr/examples => examples/ssr}/basic.rs | 0 .../examples => examples/ssr}/hydration.rs | 0 .../examples => examples/ssr}/template.html | 0 .../ssr/examples => examples/ssr}/tide.rs | 0 .../ssr/examples => examples/ssr}/tofile.rs | 10 ++-- examples/tailwind.rs | 4 +- examples/todomvc.rs | 10 ++-- examples/weather_app.rs | 2 +- .../examples => examples/web}/async_web.rs | 0 .../web/examples => examples/web}/basic.rs | 0 .../web/examples => examples/web}/blah.rs | 0 .../web/examples => examples/web}/btns.rs | 0 .../web/examples => examples/web}/crm2.rs | 0 .../web/examples => examples/web}/demo.rs | 0 examples/webview_web.rs | 1 - packages/core/benches/jsframework.rs | 1 + packages/core/src/component.rs | 39 ++++++++++---- packages/core/src/lazynodes.rs | 8 +-- packages/core/src/lib.rs | 6 +-- packages/core/src/nodes.rs | 5 +- packages/core/tests/create_dom.rs | 21 +------- packages/desktop/Cargo.toml | 3 -- packages/desktop/src/desktop_context.rs | 10 ++-- packages/desktop/src/index.html | 2 + packages/desktop/src/lib.rs | 47 ++++++++++++++--- packages/hooks/examples/lifecycle.rs | 52 ------------------- packages/hooks/examples/reducer.rs | 1 - packages/hooks/src/use_shared_state.rs | 2 +- packages/hooks/src/usestate.rs | 2 +- packages/router/examples/blog.rs | 28 ---------- packages/ssr/src/lib.rs | 14 ++--- packages/web/.vscode/settings.json | 3 -- packages/webview-client/Cargo.toml | 8 --- packages/webview-client/README.md | 8 --- packages/webview-client/src/main.rs | 3 -- 86 files changed, 160 insertions(+), 216 deletions(-) rename {packages/core/examples => examples/core}/alternative.rs (100%) rename {packages/core/examples => examples/core}/async.rs (100%) rename {packages/core/examples => examples/core}/borrowed.rs (100%) rename {packages/core/examples => examples/core}/contextapi.rs (100%) rename {packages/core/examples => examples/core}/expand.rs (100%) rename {packages/core/examples => examples/core}/fragment_from_iter.rs (100%) rename {packages/core/examples => examples/core}/html.rs (100%) rename {packages/core/examples => examples/core}/jsframework.rs (100%) rename {packages/core/examples => examples/core}/syntax.rs (100%) rename {packages/core/examples => examples/core}/syntax2.rs (100%) rename {packages/core/examples => examples/core}/tasks.rs (100%) rename {packages/core/examples => examples/core}/vdom_usage.rs (100%) rename {reference => examples/core_reference}/README.md (100%) rename {reference => examples/core_reference}/antipatterns.rs (100%) rename {reference => examples/core_reference}/basics.rs (100%) rename {reference => examples/core_reference}/children.rs (100%) rename {reference => examples/core_reference}/conditional_rendering.rs (100%) rename {reference => examples/core_reference}/controlled_inputs.rs (100%) rename {reference => examples/core_reference}/custom_elements.rs (100%) rename {reference => examples/core_reference}/empty.rs (100%) rename {reference => examples/core_reference}/errorhandling.rs (100%) rename {reference => examples/core_reference}/fragments.rs (100%) rename {reference => examples/core_reference}/global_css.rs (100%) rename {reference => examples/core_reference}/inline_styles.rs (100%) rename {reference => examples/core_reference}/iterators.rs (100%) rename {reference => examples/core_reference}/listener.rs (100%) rename {reference => examples/core_reference}/memo.rs (100%) rename {reference => examples/core_reference}/noderefs.rs (100%) rename {reference => examples/core_reference}/signals.rs (100%) rename {reference => examples/core_reference}/spreadpattern.rs (100%) rename {reference => examples/core_reference}/statemanagement.rs (100%) rename {reference => examples/core_reference}/suspense.rs (100%) rename {reference => examples/core_reference}/task.rs (100%) rename {reference => examples/core_reference}/testing.rs (100%) rename {reference => examples/core_reference}/tostring.rs (100%) rename {packages/desktop/examples => examples/desktop}/core.rs (100%) rename {packages/desktop/examples => examples/desktop}/crm.rs (100%) rename {packages/desktop/examples => examples/desktop}/demo.rs (100%) rename {packages/desktop/examples => examples/desktop}/kitchensink.rs (100%) rename {packages/desktop/examples => examples/desktop}/tauri.rs (100%) rename {packages/desktop/examples => examples/desktop}/todomvc.css (100%) rename {packages/desktop/examples => examples/desktop}/todomvc.rs (90%) rename packages/router/examples/v2.rs => examples/router.rs (95%) rename {packages/ssr/examples => examples/ssr}/basic.rs (100%) rename {packages/ssr/examples => examples/ssr}/hydration.rs (100%) rename {packages/ssr/examples => examples/ssr}/template.html (100%) rename {packages/ssr/examples => examples/ssr}/tide.rs (100%) rename {packages/ssr/examples => examples/ssr}/tofile.rs (95%) rename {packages/web/examples => examples/web}/async_web.rs (100%) rename {packages/web/examples => examples/web}/basic.rs (100%) rename {packages/web/examples => examples/web}/blah.rs (100%) rename {packages/web/examples => examples/web}/btns.rs (100%) rename {packages/web/examples => examples/web}/crm2.rs (100%) rename {packages/web/examples => examples/web}/demo.rs (100%) delete mode 100644 packages/hooks/examples/lifecycle.rs delete mode 100644 packages/hooks/examples/reducer.rs delete mode 100644 packages/router/examples/blog.rs delete mode 100644 packages/web/.vscode/settings.json delete mode 100644 packages/webview-client/Cargo.toml delete mode 100644 packages/webview-client/README.md delete mode 100644 packages/webview-client/src/main.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b608c765..b3bde867c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - // "rust-analyzer.cargo.allFeatures": true + "rust-analyzer.cargo.allFeatures": true } diff --git a/Cargo.toml b/Cargo.toml index 9a066caa8..4c1863dc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,6 @@ members = [ "packages/ssr", "packages/desktop", "packages/mobile", - "packages/webview-client", ] diff --git a/examples/calculator.rs b/examples/calculator.rs index a00ad98f3..3fc110b64 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -114,12 +114,13 @@ const APP: FC<()> = |(cx, _)| { struct CalculatorKeyProps<'a> { name: &'static str, onclick: &'a dyn Fn(MouseEvent), + children: ScopeChildren<'a>, } -fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps>) -> Element<'a> { +fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element { rsx!(cx, button { class: "calculator-key {props.name}" onclick: {props.onclick} - {cx.children()} + {&props.children} }) } diff --git a/packages/core/examples/alternative.rs b/examples/core/alternative.rs similarity index 100% rename from packages/core/examples/alternative.rs rename to examples/core/alternative.rs diff --git a/packages/core/examples/async.rs b/examples/core/async.rs similarity index 100% rename from packages/core/examples/async.rs rename to examples/core/async.rs diff --git a/packages/core/examples/borrowed.rs b/examples/core/borrowed.rs similarity index 100% rename from packages/core/examples/borrowed.rs rename to examples/core/borrowed.rs diff --git a/packages/core/examples/contextapi.rs b/examples/core/contextapi.rs similarity index 100% rename from packages/core/examples/contextapi.rs rename to examples/core/contextapi.rs diff --git a/packages/core/examples/expand.rs b/examples/core/expand.rs similarity index 100% rename from packages/core/examples/expand.rs rename to examples/core/expand.rs diff --git a/packages/core/examples/fragment_from_iter.rs b/examples/core/fragment_from_iter.rs similarity index 100% rename from packages/core/examples/fragment_from_iter.rs rename to examples/core/fragment_from_iter.rs diff --git a/packages/core/examples/html.rs b/examples/core/html.rs similarity index 100% rename from packages/core/examples/html.rs rename to examples/core/html.rs diff --git a/packages/core/examples/jsframework.rs b/examples/core/jsframework.rs similarity index 100% rename from packages/core/examples/jsframework.rs rename to examples/core/jsframework.rs diff --git a/packages/core/examples/syntax.rs b/examples/core/syntax.rs similarity index 100% rename from packages/core/examples/syntax.rs rename to examples/core/syntax.rs diff --git a/packages/core/examples/syntax2.rs b/examples/core/syntax2.rs similarity index 100% rename from packages/core/examples/syntax2.rs rename to examples/core/syntax2.rs diff --git a/packages/core/examples/tasks.rs b/examples/core/tasks.rs similarity index 100% rename from packages/core/examples/tasks.rs rename to examples/core/tasks.rs diff --git a/packages/core/examples/vdom_usage.rs b/examples/core/vdom_usage.rs similarity index 100% rename from packages/core/examples/vdom_usage.rs rename to examples/core/vdom_usage.rs diff --git a/reference/README.md b/examples/core_reference/README.md similarity index 100% rename from reference/README.md rename to examples/core_reference/README.md diff --git a/reference/antipatterns.rs b/examples/core_reference/antipatterns.rs similarity index 100% rename from reference/antipatterns.rs rename to examples/core_reference/antipatterns.rs diff --git a/reference/basics.rs b/examples/core_reference/basics.rs similarity index 100% rename from reference/basics.rs rename to examples/core_reference/basics.rs diff --git a/reference/children.rs b/examples/core_reference/children.rs similarity index 100% rename from reference/children.rs rename to examples/core_reference/children.rs diff --git a/reference/conditional_rendering.rs b/examples/core_reference/conditional_rendering.rs similarity index 100% rename from reference/conditional_rendering.rs rename to examples/core_reference/conditional_rendering.rs diff --git a/reference/controlled_inputs.rs b/examples/core_reference/controlled_inputs.rs similarity index 100% rename from reference/controlled_inputs.rs rename to examples/core_reference/controlled_inputs.rs diff --git a/reference/custom_elements.rs b/examples/core_reference/custom_elements.rs similarity index 100% rename from reference/custom_elements.rs rename to examples/core_reference/custom_elements.rs diff --git a/reference/empty.rs b/examples/core_reference/empty.rs similarity index 100% rename from reference/empty.rs rename to examples/core_reference/empty.rs diff --git a/reference/errorhandling.rs b/examples/core_reference/errorhandling.rs similarity index 100% rename from reference/errorhandling.rs rename to examples/core_reference/errorhandling.rs diff --git a/reference/fragments.rs b/examples/core_reference/fragments.rs similarity index 100% rename from reference/fragments.rs rename to examples/core_reference/fragments.rs diff --git a/reference/global_css.rs b/examples/core_reference/global_css.rs similarity index 100% rename from reference/global_css.rs rename to examples/core_reference/global_css.rs diff --git a/reference/inline_styles.rs b/examples/core_reference/inline_styles.rs similarity index 100% rename from reference/inline_styles.rs rename to examples/core_reference/inline_styles.rs diff --git a/reference/iterators.rs b/examples/core_reference/iterators.rs similarity index 100% rename from reference/iterators.rs rename to examples/core_reference/iterators.rs diff --git a/reference/listener.rs b/examples/core_reference/listener.rs similarity index 100% rename from reference/listener.rs rename to examples/core_reference/listener.rs diff --git a/reference/memo.rs b/examples/core_reference/memo.rs similarity index 100% rename from reference/memo.rs rename to examples/core_reference/memo.rs diff --git a/reference/noderefs.rs b/examples/core_reference/noderefs.rs similarity index 100% rename from reference/noderefs.rs rename to examples/core_reference/noderefs.rs diff --git a/reference/signals.rs b/examples/core_reference/signals.rs similarity index 100% rename from reference/signals.rs rename to examples/core_reference/signals.rs diff --git a/reference/spreadpattern.rs b/examples/core_reference/spreadpattern.rs similarity index 100% rename from reference/spreadpattern.rs rename to examples/core_reference/spreadpattern.rs diff --git a/reference/statemanagement.rs b/examples/core_reference/statemanagement.rs similarity index 100% rename from reference/statemanagement.rs rename to examples/core_reference/statemanagement.rs diff --git a/reference/suspense.rs b/examples/core_reference/suspense.rs similarity index 100% rename from reference/suspense.rs rename to examples/core_reference/suspense.rs diff --git a/reference/task.rs b/examples/core_reference/task.rs similarity index 100% rename from reference/task.rs rename to examples/core_reference/task.rs diff --git a/reference/testing.rs b/examples/core_reference/testing.rs similarity index 100% rename from reference/testing.rs rename to examples/core_reference/testing.rs diff --git a/reference/tostring.rs b/examples/core_reference/tostring.rs similarity index 100% rename from reference/tostring.rs rename to examples/core_reference/tostring.rs diff --git a/examples/coroutine.rs b/examples/coroutine.rs index 14faaaa47..3cafeb055 100644 --- a/examples/coroutine.rs +++ b/examples/coroutine.rs @@ -46,7 +46,8 @@ static App: FC<()> = |(cx, props)| { }); cx.render(rsx! { - div { style: { width: "400px", height: "400px", position: "relative", background: "yellow" } + div { + width: "400px", height: "400px", position: "relative", background: "yellow" button { "reset", onclick: move |_| {} } Horsey { pos: *p1, "horsey 1" } Horsey { pos: *p2, "horsey 2" } @@ -54,18 +55,19 @@ static App: FC<()> = |(cx, props)| { }) }; -#[derive(PartialEq, Props)] -struct HorseyProps { +#[derive(Props)] +struct HorseyProps<'a> { pos: i32, + children: ScopeChildren<'a>, } -static Horsey: FC = |(cx, props)| { +fn Horsey<'a>((cx, props): Scope<'a, HorseyProps<'a>>) -> Element { cx.render(rsx! { - div { - button { "pause" } div { - {cx.children()} + button { "pause" } + div { + {&props.children} + } } - } }) -}; +} diff --git a/examples/crm.rs b/examples/crm.rs index 994a43f72..be1abb3d9 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -20,12 +20,12 @@ pub struct Client { } static App: FC<()> = |(cx, _)| { - let clients = use_ref(cx, || vec![] as Vec); - let scene = use_state(cx, || Scene::ClientsList); + let mut clients = use_ref(cx, || vec![] as Vec); + let mut scene = use_state(cx, || Scene::ClientsList); - let firstname = use_state(cx, || String::new()); - let lastname = use_state(cx, || String::new()); - let description = use_state(cx, || String::new()); + let mut firstname = use_state(cx, || String::new()); + let mut lastname = use_state(cx, || String::new()); + let mut description = use_state(cx, || String::new()); let scene = match *scene { Scene::ClientsList => { diff --git a/packages/desktop/examples/core.rs b/examples/desktop/core.rs similarity index 100% rename from packages/desktop/examples/core.rs rename to examples/desktop/core.rs diff --git a/packages/desktop/examples/crm.rs b/examples/desktop/crm.rs similarity index 100% rename from packages/desktop/examples/crm.rs rename to examples/desktop/crm.rs diff --git a/packages/desktop/examples/demo.rs b/examples/desktop/demo.rs similarity index 100% rename from packages/desktop/examples/demo.rs rename to examples/desktop/demo.rs diff --git a/packages/desktop/examples/kitchensink.rs b/examples/desktop/kitchensink.rs similarity index 100% rename from packages/desktop/examples/kitchensink.rs rename to examples/desktop/kitchensink.rs diff --git a/packages/desktop/examples/tauri.rs b/examples/desktop/tauri.rs similarity index 100% rename from packages/desktop/examples/tauri.rs rename to examples/desktop/tauri.rs diff --git a/packages/desktop/examples/todomvc.css b/examples/desktop/todomvc.css similarity index 100% rename from packages/desktop/examples/todomvc.css rename to examples/desktop/todomvc.css diff --git a/packages/desktop/examples/todomvc.rs b/examples/desktop/todomvc.rs similarity index 90% rename from packages/desktop/examples/todomvc.rs rename to examples/desktop/todomvc.rs index cd78eb523..6e85b87c6 100644 --- a/packages/desktop/examples/todomvc.rs +++ b/examples/desktop/todomvc.rs @@ -34,11 +34,11 @@ pub type Todos = HashMap; pub static App: FC<()> = |(cx, _)| { // Share our TodoList to the todos themselves - use_provide_state(cx, || Todos::new()); + use_provide_state(cx, Todos::new); // Save state for the draft, filter - let mut draft = use_state(cx, || "".to_string()); - let mut filter = use_state(cx, || FilterState::All); + let draft = use_state(cx, || "".to_string()); + let filter = use_state(cx, || FilterState::All); let mut todo_id = use_state(cx, || 0); // Consume the todos @@ -95,7 +95,7 @@ pub static App: FC<()> = |(cx, _)| { placeholder: "What needs to be done?" value: "{draft}" autofocus: "true" - oninput: move |evt| draft.set(evt.value.clone()) + oninput: move |evt| draft.set(evt.value) onkeydown: move |evt| { if evt.key == "Enter" { submit_todo(); @@ -150,7 +150,9 @@ pub fn TodoEntry((cx, props): Scope) -> Element { div { class: "view" input { class: "toggle" r#type: "checkbox" id: "cbg-{todo.id}" checked: "{todo.checked}" onchange: move |evt| { - todos.write().get_mut(&props.id).map(|todo| todo.checked = evt.value.parse().unwrap()); + if let Some(todo) = todos.write().get_mut(&props.id) { + todo.checked = evt.value.parse().unwrap() + } } } @@ -161,7 +163,9 @@ pub fn TodoEntry((cx, props): Scope) -> Element { {is_editing.then(|| rsx!{ input { value: "{todo.contents}" oninput: move |evt| { - todos.write().get_mut(&props.id).map(|todo| todo.contents = evt.value.clone()); + if let Some(todo) = todos.write().get_mut(&props.id) { + todo.contents = evt.value + } }, } })} diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 5ca02fb49..6dd9a9073 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -10,11 +10,11 @@ fn main() { type RowList = im_rc::HashMap, FxBuildHasher>; static App: FC<()> = |(cx, _props)| { - let items = use_state(cx, || RowList::default()); + let mut items = use_state(cx, || RowList::default()); let create_rendered_rows = move |from, num| move |_| items.set(create_row_list(from, num)); - let append_1_000_rows = + let mut append_1_000_rows = move |_| items.set(create_row_list(items.len(), 1000).union((*items).clone())); let update_every_10th_row = move |_| { diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index 33d2063a4..0accd1702 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -74,14 +74,15 @@ static App: FC<()> = |(cx, props)| { struct CalculatorKeyProps<'a> { name: &'static str, onclick: &'a dyn Fn(MouseEvent), + children: ScopeChildren<'a>, } -fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps>) -> Element<'a> { +fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element<'a> { cx.render(rsx! { button { class: "calculator-key {props.name}" onclick: {props.onclick} - {cx.children()} + {&props.children} } }) } diff --git a/packages/router/examples/v2.rs b/examples/router.rs similarity index 95% rename from packages/router/examples/v2.rs rename to examples/router.rs index 6bea4b58e..e04f92ebd 100644 --- a/packages/router/examples/v2.rs +++ b/examples/router.rs @@ -1,9 +1,5 @@ use dioxus::prelude::*; -use dioxus_router::{use_router, Link, Routable}; - -fn main() { - dbg!(App as *const fn()); -} +use dioxus::router::Router; #[derive(PartialEq, Debug, Clone)] pub enum Route { @@ -86,3 +82,5 @@ impl Routable for Route { todo!() } } + +fn main() {} diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index dffd440b4..becf386f5 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -162,13 +162,13 @@ pub static Example: FC<()> = |(cx, props)| { // Can pass in props directly as an expression {{ - let props = TallerProps {a: "hello"}; + let props = TallerProps {a: "hello", children: Default::default()}; rsx!(Taller { ..props }) }} // Spreading can also be overridden manually Taller { - ..TallerProps { a: "ballin!" } + ..TallerProps { a: "ballin!", children: Default::default() } a: "not ballin!" } @@ -180,7 +180,7 @@ pub static Example: FC<()> = |(cx, props)| { mod baller { use super::*; - #[derive(PartialEq, Props)] + #[derive(Props, PartialEq)] pub struct BallerProps {} /// This component totally balls @@ -189,13 +189,16 @@ mod baller { } } -#[derive(Debug, PartialEq, Props)] -pub struct TallerProps { +#[derive(Props)] +pub struct TallerProps<'a> { a: &'static str, + + #[builder(default)] + children: ScopeChildren<'a>, } /// This component is taller than most :) -pub fn Taller(_: Scope) -> Element { +pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element { let b = true; todo!() } diff --git a/packages/ssr/examples/basic.rs b/examples/ssr/basic.rs similarity index 100% rename from packages/ssr/examples/basic.rs rename to examples/ssr/basic.rs diff --git a/packages/ssr/examples/hydration.rs b/examples/ssr/hydration.rs similarity index 100% rename from packages/ssr/examples/hydration.rs rename to examples/ssr/hydration.rs diff --git a/packages/ssr/examples/template.html b/examples/ssr/template.html similarity index 100% rename from packages/ssr/examples/template.html rename to examples/ssr/template.html diff --git a/packages/ssr/examples/tide.rs b/examples/ssr/tide.rs similarity index 100% rename from packages/ssr/examples/tide.rs rename to examples/ssr/tide.rs diff --git a/packages/ssr/examples/tofile.rs b/examples/ssr/tofile.rs similarity index 95% rename from packages/ssr/examples/tofile.rs rename to examples/ssr/tofile.rs index 83e4dc28a..35777240f 100644 --- a/packages/ssr/examples/tofile.rs +++ b/examples/ssr/tofile.rs @@ -119,14 +119,14 @@ pub static Entry: FC<()> = |(cx, props)| { pub static StacksIcon: FC<()> = |(cx, props)| { cx.render(rsx!( svg { - // xmlns: "http://www.w3.org/2000/svg" + xmlns: "http://www.w3.org/2000/svg" fill: "none" stroke: "currentColor" stroke_linecap: "round" stroke_linejoin: "round" stroke_width: "2" - // class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" - viewBox: "0 0 24 24" + class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" + view_box: "0 0 24 24" path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} } )) @@ -139,8 +139,8 @@ pub static RightArrowIcon: FC<()> = |(cx, props)| { stroke_linecap: "round" stroke_linejoin: "round" stroke_width: "2" - // class: "w-4 h-4 ml-1" - viewBox: "0 0 24 24" + class: "w-4 h-4 ml-1" + view_box: "0 0 24 24" path { d: "M5 12h14M12 5l7 7-7 7"} } )) diff --git a/examples/tailwind.rs b/examples/tailwind.rs index e9b8b5378..47046be8f 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -117,7 +117,7 @@ pub static StacksIcon: FC<()> = |(cx, props)| { stroke_linejoin: "round" stroke_width: "2" class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" - viewBox: "0 0 24 24" + view_box: "0 0 24 24" path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} } )) @@ -131,7 +131,7 @@ pub static RightArrowIcon: FC<()> = |(cx, props)| { stroke_linejoin: "round" stroke_width: "2" class: "w-4 h-4 ml-1" - viewBox: "0 0 24 24" + view_box: "0 0 24 24" path { d: "M5 12h14M12 5l7 7-7 7"} } )) diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 294ae36db..6ed2c1d7d 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -23,9 +23,9 @@ pub struct TodoItem { const STYLE: &str = include_str!("./assets/todomvc.css"); const App: FC<()> = |(cx, props)| { - let draft = use_state(cx, || "".to_string()); - let todos = use_state(cx, || HashMap::>::new()); - let filter = use_state(cx, || FilterState::All); + let mut draft = use_state(cx, || "".to_string()); + let mut todos = use_state(cx, || HashMap::>::new()); + let mut filter = use_state(cx, || FilterState::All); let todolist = todos .iter() @@ -86,8 +86,8 @@ pub struct TodoEntryProps { } pub fn TodoEntry((cx, props): Scope) -> Element { - let is_editing = use_state(cx, || false); - let contents = use_state(cx, || String::from("")); + let mut is_editing = use_state(cx, || false); + let mut contents = use_state(cx, || String::from("")); let todo = &props.todo; rsx!(cx, li { diff --git a/examples/weather_app.rs b/examples/weather_app.rs index f1772805b..2682b9f9b 100644 --- a/examples/weather_app.rs +++ b/examples/weather_app.rs @@ -26,7 +26,7 @@ static App: FC<()> = |(cx, props)| { }, |cx, props| { // - rsx!(cx, WeatherDisplay {}) + rsx!(WeatherDisplay {}) }, ); diff --git a/packages/web/examples/async_web.rs b/examples/web/async_web.rs similarity index 100% rename from packages/web/examples/async_web.rs rename to examples/web/async_web.rs diff --git a/packages/web/examples/basic.rs b/examples/web/basic.rs similarity index 100% rename from packages/web/examples/basic.rs rename to examples/web/basic.rs diff --git a/packages/web/examples/blah.rs b/examples/web/blah.rs similarity index 100% rename from packages/web/examples/blah.rs rename to examples/web/blah.rs diff --git a/packages/web/examples/btns.rs b/examples/web/btns.rs similarity index 100% rename from packages/web/examples/btns.rs rename to examples/web/btns.rs diff --git a/packages/web/examples/crm2.rs b/examples/web/crm2.rs similarity index 100% rename from packages/web/examples/crm2.rs rename to examples/web/crm2.rs diff --git a/packages/web/examples/demo.rs b/examples/web/demo.rs similarity index 100% rename from packages/web/examples/demo.rs rename to examples/web/demo.rs diff --git a/examples/webview_web.rs b/examples/webview_web.rs index 55be2dcea..408095c57 100644 --- a/examples/webview_web.rs +++ b/examples/webview_web.rs @@ -22,7 +22,6 @@ static App: FC<()> = |(cx, props)| { cx.render(rsx! { div { h1 { "Hifive counter: {count}" } - {cx.children()} button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } } diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index 3fc3c273c..d0c8aae77 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -19,6 +19,7 @@ use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use rand::prelude::*; +fn main() {} criterion_group!(mbenches, create_rows); criterion_main!(mbenches); diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 5c7b4c36c..e249bfae1 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -5,7 +5,10 @@ //! if the type supports 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 cx. -use crate::innerlude::{Context, Element, VNode}; +use crate::{ + innerlude::{Context, Element, VAnchor, VFragment, VNode}, + LazyNodes, ScopeChildren, +}; /// A component is a wrapper around a Context and some Props that share a lifetime /// /// @@ -41,23 +44,39 @@ use crate::innerlude::{Context, Element, VNode}; pub type Scope<'a, T> = (Context<'a>, &'a T); pub struct FragmentProps<'a> { - childen: &'a VNode<'a>, + children: ScopeChildren<'a>, } -pub struct FragmentPropsBuilder<'a> { - childen: Option<&'a VNode<'a>>, + +pub struct FragmentBuilder<'a, const BUILT: bool> { + children: Option>, +} +impl<'a> FragmentBuilder<'a, false> { + pub fn children(mut self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> { + FragmentBuilder { + children: Some(children), + } + } +} + +impl<'a, const A: bool> FragmentBuilder<'a, A> { + pub fn build(self) -> FragmentProps<'a> { + FragmentProps { + children: self.children.unwrap_or_default(), + } + } } impl<'a> Properties for FragmentProps<'a> { - type Builder = FragmentPropsBuilder<'a>; + type Builder = FragmentBuilder<'a, false>; const IS_STATIC: bool = false; fn builder() -> Self::Builder { - todo!() + FragmentBuilder { children: None } } unsafe fn memoize(&self, other: &Self) -> bool { - todo!() + false } } @@ -83,8 +102,10 @@ impl<'a> Properties for FragmentProps<'a> { /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>((_, props): Scope<'a, FragmentProps<'a>>) -> Element<'a> { - Some(props.childen.decouple()) +pub fn Fragment<'a>((cx, props): Scope<'a, FragmentProps<'a>>) -> Element<'a> { + cx.render(Some(LazyNodes::new(|f| { + f.fragment_from_iter(&props.children) + }))) } /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 249948517..19f56cf58 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -11,7 +11,7 @@ //! The logic for this was borrowed from https://docs.rs/stack_dst/0.6.1/stack_dst/. Unfortunately, this crate does not //! support non-static closures, so we've implemented the core logic of `ValueA` in this module. -use crate::innerlude::{NodeFactory, VNode}; +use crate::prelude::{NodeFactory, VNode}; use std::mem; /// A concrete type provider for closures that build VNode structures. @@ -27,7 +27,7 @@ pub struct LazyNodes<'a, 'b> { inner: StackNodeStorage<'a, 'b>, } -type StackHeapSize = [usize; 12]; +type StackHeapSize = [usize; 0]; enum StackNodeStorage<'a, 'b> { Stack(LazyStack), @@ -77,7 +77,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { } } else { log::debug!("lazy nodes fits on stack!"); - let mut buf: StackHeapSize = [0; 12]; + let mut buf: StackHeapSize = StackHeapSize::default(); assert!(info.len() + round_to_words(size) <= buf.as_ref().len()); @@ -97,6 +97,8 @@ impl<'a, 'b> LazyNodes<'a, 'b> { *dataptr.add(i) = *src_ptr.add(i); } + std::mem::forget(val); + Self { inner: StackNodeStorage::Stack(LazyStack { _align: [], buf }), } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8fc6bddc7..f602f512e 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -65,9 +65,9 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, MountType, Mutations, - NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, SuspendedContext, TaskHandle, - TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, + Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType, + Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, SuspendedContext, + TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC, }; pub mod prelude { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 728a1f4cf..9b9693a8e 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -765,6 +765,9 @@ impl<'a> ScopeChildren<'a> { pub fn new(root: VNode<'a>) -> Self { Self { root: Some(root) } } + pub fn new_option(root: Option>) -> Self { + Self { root } + } } impl IntoIterator for &ScopeChildren<'_> { @@ -773,7 +776,7 @@ impl IntoIterator for &ScopeChildren<'_> { type IntoIter = std::iter::Once; fn into_iter(self) -> Self::IntoIter { - todo!() + std::iter::once(self) } } diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 875db7a75..712bf3713 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -303,27 +303,8 @@ fn anchors() { #[test] fn suspended() { static App: FC<()> = |(cx, props)| { - let val = use_suspense( - cx, - || async { - // - }, - |cx, p| todo!(), - // |cx, p| rsx! { "hi "}, - ); + let val = use_suspense(cx, || async {}, |cx, p| todo!()); - // let prom = use_task(fetch()); - - // Value { - // a: value.await, - // b: value.await, - // f: a.await - // div { - // div { - // hidden: should_hide.await, - // } - // } - // } cx.render(rsx! { {val} }) }; diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 948ffcc0b..e6aef1378 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -34,6 +34,3 @@ tokio_runtime = ["tokio"] dioxus-html = { path = "../html" } dioxus-hooks = { path = "../hooks" } simple_logger = "1.13.0" - - -[build-dependencies] diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 1cf369d9f..54b62217a 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use dioxus::prelude::Scope; +use dioxus::ScopeChildren; use dioxus_core as dioxus; use dioxus_core::{Context, Element, LazyNodes, NodeFactory, Properties}; use dioxus_core_macro::Props; @@ -42,6 +43,8 @@ pub struct WebviewWindowProps<'a> { /// focuse me onfocused: &'a dyn FnMut(()), + + children: ScopeChildren<'a>, } /// A handle to a @@ -70,9 +73,10 @@ pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element< ); // render the children directly - cx.render(LazyNodes::new(move |f: NodeFactory| { - f.fragment_from_iter(cx.children()) - })) + todo!() + // cx.render(LazyNodes::new(move |f: NodeFactory| { + // f.fragment_from_iter(cx.children()) + // })) } pub struct WindowHandle {} diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index ebddd60d5..428a3b8d8 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -13,6 +13,8 @@ position: fixed; } --> + + diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 3f521c9ab..7484f1bbc 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -20,9 +20,9 @@ use serde::{Deserialize, Serialize}; pub use wry; use wry::application::accelerator::{Accelerator, SysMods}; -use wry::application::event::{Event, StartCause, WindowEvent}; +use wry::application::event::{ElementState, Event, StartCause, WindowEvent}; use wry::application::event_loop::{self, ControlFlow, EventLoop}; -use wry::application::keyboard::KeyCode; +use wry::application::keyboard::{Key, KeyCode, ModifiersState}; use wry::application::menu::{MenuBar, MenuItem, MenuItemAttributes}; use wry::application::window::Fullscreen; use wry::webview::{WebView, WebViewBuilder}; @@ -94,6 +94,11 @@ pub fn run( let props_shared = Cell::new(Some(props)); + // create local modifier state + let mut modifiers = ModifiersState::default(); + + let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ); + event_loop.run(move |event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; @@ -112,10 +117,8 @@ pub fn run( first_menu.add_native_item(MenuItem::HideOthers); first_menu.add_native_item(MenuItem::ShowAll); - let quit_item = first_menu.add_item( - MenuItemAttributes::new("Quit") - .with_accelerators(&Accelerator::new(SysMods::Cmd, KeyCode::KeyQ)), - ); + first_menu.add_native_item(MenuItem::Quit); + first_menu.add_native_item(MenuItem::CloseWindow); // create second menu let mut second_menu = MenuBar::new(); @@ -129,6 +132,7 @@ pub fn run( menu_bar_menu.add_submenu("Second menu", true, second_menu); let window = WindowBuilder::new() + .with_maximized(true) .with_menu(menu_bar_menu) .with_title("Dioxus App") .build(event_loop) @@ -197,6 +201,37 @@ pub fn run( event, window_id, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Destroyed { .. } => { + webviews.remove(&window_id); + if webviews.is_empty() { + *control_flow = ControlFlow::Exit; + } + } + // catch only pressed event + WindowEvent::KeyboardInput { event, .. } => { + log::debug!("keybowrd input"); + if quit_hotkey.matches(&modifiers, &event.physical_key) { + log::debug!("quitting"); + + webviews.remove(&window_id); + if webviews.is_empty() { + *control_flow = ControlFlow::Exit; + } + } + + // println!( + // "KeyEvent: `Shift` + `1` | logical_key: {:?}", + // &event.logical_key + // ); + // we can match manually without `Accelerator` + + // else if event.key_without_modifiers() == Key::Character("1") + // && modifiers.is_empty() + // { + // println!("KeyEvent: `1`"); + // } + } + WindowEvent::Resized(_) | WindowEvent::Moved(_) => { if let Some(view) = webviews.get_mut(&window_id) { let _ = view.resize(); diff --git a/packages/hooks/examples/lifecycle.rs b/packages/hooks/examples/lifecycle.rs deleted file mode 100644 index 6988b8ae5..000000000 --- a/packages/hooks/examples/lifecycle.rs +++ /dev/null @@ -1,52 +0,0 @@ -fn main() {} - -// struct CompState { -// tasks: Vec<()>, -// } -// enum Actions { -// Add, -// MoveUp, -// MoveDown, -// Remvoe, -// } - -// static Component: FC<()> = |(cx, props)|{ -// let (tasks, dispatch) = use_reducer( -// cx, -// || CompState { tasks: Vec::new() }, -// |state, action: Actions| match action { -// Actions::Add => state, -// Actions::MoveUp => state, -// Actions::MoveDown => state, -// Actions::Remvoe => state, -// }, -// ); - -// let tasklist = { (0..10).map(|f| html! {

  • }) }.collect::>(); - -// html! { -//
    -//
    -//

    "Tasks: "

    -//
      -// {tasklist} -//
    -//
    -//
    -// -// -// -// -//
    -//
    -// } -// }; - -// fn use_reducer( -// cx: &mut Context, -// init: fn() -> State, -// reducer: fn(State, Action) -> State, -// ) -> (State, impl Fn(Action)) { -// let ii = init(); -// (ii, |_| {}) -// } diff --git a/packages/hooks/examples/reducer.rs b/packages/hooks/examples/reducer.rs deleted file mode 100644 index f328e4d9d..000000000 --- a/packages/hooks/examples/reducer.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 593cf3922..b1febb498 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -126,7 +126,7 @@ impl<'a, T: 'static> UseSharedState<'a, T> { /// /// /// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock - pub fn write(&mut self) -> RefMut<'_, T> { + pub fn write(&self) -> RefMut<'_, T> { self.cx.needs_update(); self.notify_consumers(); self.value.borrow_mut() diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 79402f22d..76a72ea75 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -102,7 +102,7 @@ impl<'a, T: 'static> UseState<'a, T> { } } - pub fn set(&mut self, new_val: T) { + pub fn set(&self, new_val: T) { *self.inner.wip.borrow_mut() = Some(new_val); self.needs_update(); } diff --git a/packages/router/examples/blog.rs b/packages/router/examples/blog.rs deleted file mode 100644 index fa282363c..000000000 --- a/packages/router/examples/blog.rs +++ /dev/null @@ -1,28 +0,0 @@ -use dioxus::prelude::*; - -fn main() {} - -enum Routes { - Home, - Blog, -} - -static App: FC<()> = |(cx, props)| { - let route = use_router::(cx); - - let content = match route { - Routes::Home => rsx!(Home {}), - Routes::Blog => rsx!(Blog {}), - }; - - cx.render(rsx! { - {content} - }) -}; - -fn use_router

    (cx: Context) -> &P { - todo!() -} - -static Home: FC<()> = |(cx, props)| todo!(); -static Blog: FC<()> = |(cx, props)| todo!(); diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 2813a9d82..9e5faa693 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -31,10 +31,7 @@ impl SsrRenderer { } } - pub fn render_lazy<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>( - &'a mut self, - f: LazyNodes<'a, F>, - ) -> String { + pub fn render_lazy<'a>(&'a mut self, f: Option>) -> String { let bump = &mut self.inner as *mut _; let s = self.render_inner(f); // reuse the bump's memory @@ -42,10 +39,7 @@ impl SsrRenderer { s } - fn render_inner<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>( - &'a self, - f: LazyNodes<'a, F>, - ) -> String { + fn render_inner<'a>(&'a self, f: Option>) -> String { let factory = NodeFactory::new(&self.inner); let root = f.into_vnode(factory); format!( @@ -59,7 +53,7 @@ impl SsrRenderer { } } -pub fn render_lazy<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>(f: LazyNodes<'a, F>) -> String { +pub fn render_lazy<'a>(f: Option>) -> String { let bump = bumpalo::Bump::new(); let borrowed = ≎ @@ -398,7 +392,7 @@ mod tests { fn styles() { static STLYE_APP: FC<()> = |(cx, _)| { cx.render(rsx! { - div { style: { color: "blue", font_size: "46px" } } + div { color: "blue", font_size: "46px" } }) }; diff --git a/packages/web/.vscode/settings.json b/packages/web/.vscode/settings.json deleted file mode 100644 index 2b58ae251..000000000 --- a/packages/web/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.inlayHints.enable": false -} diff --git a/packages/webview-client/Cargo.toml b/packages/webview-client/Cargo.toml deleted file mode 100644 index 0218e3a0c..000000000 --- a/packages/webview-client/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "dioxus-webview-client" -version = "0.0.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] \ No newline at end of file diff --git a/packages/webview-client/README.md b/packages/webview-client/README.md deleted file mode 100644 index e3032b25c..000000000 --- a/packages/webview-client/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# the client part of the vdom - - -this crate is designed to be the "receiving end" of the dioxus virtualdom, using wasm_bindgen to expose an API for the receiving end. - - - - diff --git a/packages/webview-client/src/main.rs b/packages/webview-client/src/main.rs deleted file mode 100644 index e7a11a969..000000000 --- a/packages/webview-client/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -}