From cda759c659dfc4b1dde17e3896c35525005026df Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 25 Dec 2021 17:18:05 -0500 Subject: [PATCH 1/4] examples: upgrade to new version of dioxus core. also add the inline_props macro --- examples/async.rs | 2 +- examples/borrowed.rs | 4 +- examples/calculator.rs | 2 +- examples/coroutine.rs | 6 +- examples/file_explorer.rs | 2 +- examples/framework_benchmark.rs | 2 +- examples/hello_world.rs | 4 +- examples/hydration.rs | 4 +- examples/manual_edits.rs | 10 +- examples/pattern_model.rs | 14 ++- examples/router.rs | 26 +---- examples/rsx_usage.rs | 16 ++- examples/ssr.rs | 15 +-- examples/tailwind.rs | 2 +- examples/tasks.rs | 21 ++-- examples/todomvc.rs | 12 +- examples/web_tick.rs | 4 +- packages/core-macro/Cargo.toml | 2 +- packages/core-macro/examples/inline_props.rs | 22 ++++ packages/core-macro/src/inlineprops.rs | 102 +++++++++++++++++ packages/core-macro/src/lib.rs | 36 +++++- packages/core/Cargo.toml | 1 - packages/core/README.md | 4 +- packages/core/examples/borrowed.rs | 8 +- packages/core/src/diff.rs | 10 +- packages/core/src/lazynodes.rs | 19 ++-- packages/core/src/lib.rs | 12 +- packages/core/src/mutations.rs | 3 + packages/core/src/nodes.rs | 14 ++- packages/core/src/scopes.rs | 53 ++++++--- packages/core/src/virtual_dom.rs | 14 +-- packages/core/tests/create_dom.rs | 12 +- packages/core/tests/diffing.rs | 65 +++++++++++ packages/desktop/examples/async.rs | 5 +- packages/desktop/src/lib.rs | 71 +++++++----- packages/hooks/src/usecoroutine.rs | 112 +++++++++++++------ packages/hooks/src/usemodel.rs | 6 +- packages/hooks/src/usestate.rs | 6 +- packages/router/src/lib.rs | 45 ++++++-- packages/web/examples/suspense.rs | 39 +++---- packages/web/src/lib.rs | 2 +- 41 files changed, 556 insertions(+), 253 deletions(-) create mode 100644 packages/core-macro/examples/inline_props.rs create mode 100644 packages/core-macro/src/inlineprops.rs diff --git a/examples/async.rs b/examples/async.rs index 16f28809e..8a7027882 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -17,7 +17,7 @@ pub static App: Component<()> = |cx| { let (async_count, dir) = (count.for_async(), *direction); - let (task, _) = use_coroutine(cx, move || async move { + let task = use_coroutine(&cx, move || async move { loop { TimeoutFuture::new(250).await; *async_count.get_mut() += dir; diff --git a/examples/borrowed.rs b/examples/borrowed.rs index cf497bbc4..3b05b6f9f 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -40,7 +40,7 @@ struct C1Props<'a> { } fn Child1<'a>(cx: Scope<'a, C1Props<'a>>) -> Element { - let (left, right) = props.text.split_once("=").unwrap(); + let (left, right) = cx.props.text.split_once("=").unwrap(); cx.render(rsx! { div { @@ -58,7 +58,7 @@ struct C2Props<'a> { fn Child2<'a>(cx: Scope<'a, C2Props<'a>>) -> Element { cx.render(rsx! { Child3 { - text: props.text + text: cx.props.text } }) } diff --git a/examples/calculator.rs b/examples/calculator.rs index 6a83bfa63..1b464f361 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -124,6 +124,6 @@ fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element { rsx!(cx, button { class: "calculator-key {cx.props.name}" onclick: {cx.props.onclick} - {&props.children} + {&cx.props.children} }) } diff --git a/examples/coroutine.rs b/examples/coroutine.rs index e112c2995..0f647fde1 100644 --- a/examples/coroutine.rs +++ b/examples/coroutine.rs @@ -58,15 +58,15 @@ static App: Component<()> = |cx| { #[derive(Props)] struct HorseyProps<'a> { pos: i32, - children: ScopeChildren<'a>, + children: Element<'a>, } -fn Horsey<'a>((cx, props): ScopeState<'a, HorseyProps<'a>>) -> Element { +fn Horsey<'a>(cx: Scope<'a, HorseyProps<'a>>) -> Element { cx.render(rsx! { div { button { "pause" } div { - {&props.children} + {&cx.props.children} } } }) diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index 98b729896..025347b00 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -8,7 +8,7 @@ use dioxus::prelude::*; fn main() { - simple_logger::init_with_level(log::Level::Debug); + // simple_logger::init_with_level(log::Level::Debug); dioxus::desktop::launch_cfg(App, |c| { c.with_window(|w| { w.with_resizable(true).with_inner_size( diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index fada80ad4..bf5b7da24 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -97,7 +97,7 @@ struct ActionButtonProps<'a> { fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element { rsx!(cx, div { class: "col-sm-6 smallpad" - button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (props.onclick)(), + button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(), "{cx.props.name}" } }) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index f788f9791..24f54883a 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,10 +1,10 @@ use dioxus::prelude::*; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } -fn App((cx, props): ScopeState<()>) -> Element { +fn app(cx: Scope<()>) -> Element { cx.render(rsx! ( div { "Hello, world!" } )) diff --git a/examples/hydration.rs b/examples/hydration.rs index 8189f7cdd..a456c6468 100644 --- a/examples/hydration.rs +++ b/examples/hydration.rs @@ -14,9 +14,9 @@ use dioxus::ssr; fn main() { let vdom = VirtualDom::new(App); - let content = ssr::render_vdom(&vdom, |f| f.pre_render(true)); + let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true)); - dioxus::desktop::launch(App, |c| c.with_prerendered(content)); + dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content)); } static App: Component<()> = |cx| { diff --git a/examples/manual_edits.rs b/examples/manual_edits.rs index 7b4f9aaf2..ace529ee6 100644 --- a/examples/manual_edits.rs +++ b/examples/manual_edits.rs @@ -33,11 +33,7 @@ fn main() { AppendChildren { many: 1 }, ]; - dioxus_desktop::run(APP, (), |c| c.with_edits(edits)); -} + let app: Component<()> = |cx| rsx!(cx, div { "some app" }); -const APP: Component<()> = |(cx, _props)| { - rsx!(cx, div { - "some app" - }) -}; + dioxus_desktop::run(app, (), |c| c.with_edits(edits)); +} diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index f59a548cb..7b827542e 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -15,6 +15,8 @@ //! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two //! RefMuts at the same time. +use std::sync::Arc; + use dioxus::desktop::wry::application::dpi::LogicalSize; use dioxus::events::*; use dioxus::prelude::*; @@ -73,16 +75,16 @@ static App: Component<()> = |cx| { #[derive(Props)] struct CalculatorKeyProps<'a> { name: &'static str, - onclick: &'a dyn Fn(MouseEvent), - children: ScopeChildren<'a>, + onclick: &'a dyn Fn(Arc), + children: Element<'a>, } -fn CalculatorKey<'a>((cx, props): ScopeState<'a, CalculatorKeyProps<'a>>) -> Element<'a> { +fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element { cx.render(rsx! { button { class: "calculator-key {cx.props.name}" - onclick: {cx.props.onclick} - {&props.children} + onclick: move |e| (cx.props.onclick)(e) + {&cx.props.children} } }) } @@ -168,7 +170,7 @@ impl Calculator { self.cur_val = self.display_value.parse::().unwrap(); self.waiting_for_operand = true; } - fn handle_keydown(&mut self, evt: KeyboardEvent) { + fn handle_keydown(&mut self, evt: Arc) { match evt.key_code { KeyCode::Backspace => self.backspace(), KeyCode::Num0 => self.input_digit(0), diff --git a/examples/router.rs b/examples/router.rs index be055fe9e..b3e6d60b5 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -1,5 +1,5 @@ use dioxus::prelude::*; -use dioxus::router::Router; +use dioxus_router::{use_router, Link}; #[derive(PartialEq, Debug, Clone)] pub enum Route { @@ -24,7 +24,7 @@ pub enum Route { } static App: Component<()> = |cx| { - let route = use_router(cx, Route::parse)?; + let route = use_router(&cx, Route::parse); cx.render(rsx! { nav { @@ -61,26 +61,4 @@ impl Route { } } -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!() - } -} - fn main() {} diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 9b6283d92..a7205b8dd 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -39,7 +39,7 @@ //! - Allow top-level fragments //! fn main() { - dioxus::desktop::launch(Example); + dioxus::desktop::launch(EXAMPLE); } /// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad. @@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None; use baller::Baller; use dioxus::prelude::*; -pub static Example: Component<()> = |cx| { +pub static EXAMPLE: Component<()> = |cx| { let formatting = "formatting!"; let formatting_tuple = ("a", "b"); let lazy_fmt = format_args!("lazily formatted text"); @@ -173,12 +173,12 @@ pub static Example: Component<()> = |cx| { Taller { a: "asd", div {"hello world!"} } // helper functions - {helper(cx, "hello world!")} + {helper(&cx, "hello world!")} } }) }; -fn helper(cx: Scope, text: &str) -> Element { +fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> { rsx!(cx, p { "{text}" }) } @@ -188,7 +188,7 @@ mod baller { pub struct BallerProps {} /// This component totally balls - pub fn Baller(_: ScopeState) -> Element { + pub fn Baller(_: Scope) -> Element { todo!() } } @@ -196,13 +196,11 @@ mod baller { #[derive(Props)] pub struct TallerProps<'a> { a: &'static str, - - #[builder(default)] - children: ScopeChildren<'a>, + children: Element<'a>, } /// This component is taller than most :) -pub fn Taller<'a>(_: ScopeState<'a, TallerProps<'a>>) -> Element { +pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element { let b = true; todo!() } diff --git a/examples/ssr.rs b/examples/ssr.rs index 542417c00..55402f579 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -1,15 +1,13 @@ -#![allow(non_upper_case_globals)] - use dioxus::prelude::*; use dioxus::ssr; fn main() { - let mut vdom = VirtualDom::new(App); - vdom.rebuild_in_place().expect("Rebuilding failed"); + let mut vdom = VirtualDom::new(APP); + let _ = vdom.rebuild(); println!("{}", ssr::render_vdom(&vdom)); } -static App: Component<()> = |cx| { +static APP: Component<()> = |cx| { cx.render(rsx!( div { h1 { "Title" } @@ -17,10 +15,3 @@ static App: Component<()> = |cx| { } )) }; - -struct MyProps<'a> { - text: &'a str, -} -fn App2<'a>(cx: Scope<'a, MyProps<'a>>) -> Element { - None -} diff --git a/examples/tailwind.rs b/examples/tailwind.rs index 70a0448fd..91365ead3 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -2,7 +2,7 @@ use dioxus::prelude::*; fn main() { use dioxus::desktop::wry::application::platform::macos::*; - dioxus::desktop::launch(App, |c| { + dioxus::desktop::launch_cfg(App, |c| { c.with_window(|w| { w.with_fullsize_content_view(true) .with_titlebar_buttons_hidden(false) diff --git a/examples/tasks.rs b/examples/tasks.rs index 3b14faff1..e22026589 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -6,29 +6,24 @@ use std::time::Duration; use dioxus::prelude::*; fn main() { - dioxus::desktop::launch(App); + dioxus::desktop::launch(app); } -static App: Component<()> = |cx| { +fn app(cx: Scope<()>) -> Element { let mut count = use_state(&cx, || 0); - cx.push_task(async move { + cx.push_task(|| async move { tokio::time::sleep(Duration::from_millis(100)).await; - println!("setting count"); count += 1; - // count.set(10); - // *count += 1; - // let c = count.get() + 1; - // count.set(c); }); cx.render(rsx! { div { h1 { "High-Five counter: {count}" } - // button { - // onclick: move |_| count +=1 , - // "Click me!" - // } + button { + onclick: move |_| count +=1 , + "Click me!" + } } }) -}; +} diff --git a/examples/todomvc.rs b/examples/todomvc.rs index b73d4ef49..ca0df7c5b 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: Component<()> = |cx| { - 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 draft = use_state(&cx, || "".to_string()); + let todos = use_state(&cx, || HashMap::>::new()); + let filter = use_state(&cx, || FilterState::All); let todolist = todos .iter() @@ -86,9 +86,9 @@ pub struct TodoEntryProps { } pub fn TodoEntry(cx: Scope) -> Element { - let mut is_editing = use_state(&cx, || false); - let mut contents = use_state(&cx, || String::from("")); - let todo = &props.todo; + let is_editing = use_state(&cx, || false); + let contents = use_state(&cx, || String::from("")); + let todo = &cx.props.todo; rsx!(cx, li { "{todo.id}" diff --git a/examples/web_tick.rs b/examples/web_tick.rs index 098bfc1f3..a9a297221 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -45,8 +45,8 @@ struct RowProps { row_id: usize, label: Label, } -fn Row((cx, props): ScopeState) -> Element { - let [adj, col, noun] = props.label.0; +fn Row(cx: Scope) -> Element { + let [adj, col, noun] = cx.props.label.0; cx.render(rsx! { tr { td { class:"col-md-1", "{cx.props.row_id}" } diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index dd18287b0..dee8b0a36 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -2,7 +2,7 @@ name = "dioxus-core-macro" version = "0.1.2" authors = ["Jonathan Kelley"] -edition = "2018" +edition = "2021" description = "Core macro for Dioxus Virtual DOM" license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" diff --git a/packages/core-macro/examples/inline_props.rs b/packages/core-macro/examples/inline_props.rs new file mode 100644 index 000000000..5be5f575f --- /dev/null +++ b/packages/core-macro/examples/inline_props.rs @@ -0,0 +1,22 @@ +use dioxus_core_macro::{inline_props, Props}; + +fn main() {} + +type Element<'a> = (); + +pub struct Scope<'a, T> { + props: &'a T, +} + +#[inline_props] +pub fn component( + cx: Scope, + chkk: String, + chkk2: String, + r: u32, + cat: &'a str, + drd: String, + e: String, +) -> Element { + let r = chkk.len(); +} diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs new file mode 100644 index 000000000..ece53622f --- /dev/null +++ b/packages/core-macro/src/inlineprops.rs @@ -0,0 +1,102 @@ +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token, Block, FnArg, Generics, Ident, Result, ReturnType, Token, Visibility, +}; + +pub struct InlinePropsBody { + pub vis: syn::Visibility, + pub fn_token: Token![fn], + pub ident: Ident, + pub cx_token: Ident, + pub generics: Generics, + pub paren_token: token::Paren, + pub inputs: Punctuated, + // pub fields: FieldsNamed, + pub output: ReturnType, + pub block: Box, +} + +/// The custom rusty variant of parsing rsx! +impl Parse for InlinePropsBody { + fn parse(input: ParseStream) -> Result { + let vis: Visibility = input.parse()?; + + let fn_token = input.parse()?; + let ident = input.parse()?; + let generics = input.parse()?; + + let content; + let paren_token = syn::parenthesized!(content in input); + + let cx_token: Ident = content.parse()?; + let _: Token![:] = content.parse()?; + let _: Ident = content.parse()?; + let _: Result = content.parse(); + + let inputs = syn::punctuated::Punctuated::parse_terminated(&content)?; + + let output = input.parse()?; + + let block = input.parse()?; + + Ok(Self { + vis, + fn_token, + ident, + generics, + paren_token, + inputs, + output, + block, + cx_token, + }) + } +} + +/// Serialize the same way, regardless of flavor +impl ToTokens for InlinePropsBody { + fn to_tokens(&self, out_tokens: &mut TokenStream2) { + let Self { + vis, + ident, + generics, + inputs, + output, + block, + cx_token, + .. + } = self; + + let fields = inputs.iter().map(|f| { + quote! { #vis #f } + }); + + let struct_name = Ident::new(&format!("{}Props", ident), Span::call_site()); + + let field_names = inputs.iter().filter_map(|f| match f { + FnArg::Receiver(_) => todo!(), + FnArg::Typed(t) => Some(&t.pat), + }); + + let modifiers = if generics.params.is_empty() { + quote! { #[derive(Props, PartialEq)] } + } else { + quote! { #[derive(PartialEq)] } + }; + + out_tokens.append_all(quote! { + #modifiers + #vis struct #struct_name #generics { + #(#fields),* + } + + #vis fn #ident #generics (#cx_token: Scope<#struct_name>) #output { + let #struct_name { #(#field_names),* } = &cx.props; + #block + } + }); + } +} diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 268c38276..85a2fa602 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -2,8 +2,8 @@ use proc_macro::TokenStream; use quote::ToTokens; use syn::parse_macro_input; -pub(crate) mod htm; pub(crate) mod ifmt; +pub(crate) mod inlineprops; pub(crate) mod props; pub(crate) mod router; pub(crate) mod rsx; @@ -214,3 +214,37 @@ pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStrea let input = parse_macro_input!(input as Routable); routable_derive_impl(input).into() } + +/// Derive props for a component within the component definition. +/// +/// This macro provides a simple transformation from `Scope<{}>` to `Scope

`, +/// removing some boilerplate when defining props. +/// +/// You don't *need* to use this macro at all, but it can be helpful in cases where +/// you would be repeating a lot of the usual Rust boilerplate. +/// +/// # Example +/// ``` +/// #[inline_props] +/// fn app(cx: Scope<{ bob: String }>) -> Element { +/// cx.render(rsx!("hello, {bob}")) +/// } +/// +/// // is equivalent to +/// +/// #[derive(PartialEq, Props)] +/// struct AppProps { +/// bob: String, +/// } +/// +/// fn app(cx: Scope) -> Element { +/// cx.render(rsx!("hello, {bob}")) +/// } +/// ``` +#[proc_macro_attribute] +pub fn inline_props(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { + match syn::parse::(s) { + Err(e) => e.to_compile_error().into(), + Ok(s) => s.to_token_stream().into(), + } +} diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index edb841a54..21724e005 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -52,7 +52,6 @@ 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" } -# dioxus-hooks = { path = "../hooks" } criterion = "0.3.5" thiserror = "1.0.30" diff --git a/packages/core/README.md b/packages/core/README.md index e439ab44b..672a1dc78 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -46,11 +46,9 @@ fn main() { VNodes::Element(vel) => dbg!(vel), _ => todo!() } - } - - ``` + ## Internals Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts: diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index f4dc7d955..cc376789c 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -5,11 +5,11 @@ use dioxus_html as dioxus_elements; fn main() {} -fn App(cx: Scope<()>) -> Element { +fn app(cx: Scope) -> Element { cx.render(rsx!(div { - App2 { + app2 ( p: "asd" - } + ) })) } @@ -18,7 +18,7 @@ struct Borrowed<'a> { p: &'a str, } -fn App2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element { +fn app2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element { let g = eat2(&cx); rsx!(cx, "") } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2de598796..7f852a6d0 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,6 +1,6 @@ -//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children. +//! This module contains the stateful DiffState and all methods to diff VNodes, their properties, and their children. //! -//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set +//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set //! of mutations for the RealDom to apply. //! //! ## Notice: @@ -86,14 +86,14 @@ //! - Certain web-dom-specific optimizations. //! //! More info on how to improve this diffing algorithm: -//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ +//! - use crate::innerlude::*; use fxhash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; use DomEdit::*; -/// Our DiffMachine is an iterative tree differ. +/// Our DiffState is an iterative tree differ. /// /// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This /// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively, @@ -490,7 +490,7 @@ impl<'bump> DiffState<'bump> { (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new), // The normal pathway still works, but generates slightly weird instructions - // This pathway just ensures we get create and replace + // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove (Placeholder(_), Fragment(new)) => { self.stack .create_children(new.children, MountType::Replace { old: old_node }); diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 2d10622e3..bd551da50 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -8,10 +8,10 @@ //! 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 +//! The logic for this was borrowed from . Unfortunately, this crate does not //! support non-static closures, so we've implemented the core logic of `ValueA` in this module. -use crate::prelude::{NodeFactory, VNode}; +use crate::innerlude::{NodeFactory, VNode}; use std::mem; /// A concrete type provider for closures that build VNode structures. @@ -35,15 +35,15 @@ enum StackNodeStorage<'a, 'b> { } impl<'a, 'b> LazyNodes<'a, 'b> { - pub fn new_some(_val: F) -> Option + pub fn new_some(_val: F) -> Self where F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, { - Some(Self::new(_val)) + Self::new(_val) } /// force this call onto the stack - pub fn new_boxed(_val: F) -> Option + pub fn new_boxed(_val: F) -> Self where F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, { @@ -55,9 +55,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> { fac.map(inner) }; - Some(Self { + Self { inner: StackNodeStorage::Heap(Box::new(val)), - }) + } } pub fn new(_val: F) -> Self @@ -249,9 +249,7 @@ fn it_works() { let caller = LazyNodes::new_some(|f| { // f.text(format_args!("hello world!")) - }) - .unwrap(); - + }); let g = caller.call(factory); dbg!(g); @@ -291,7 +289,6 @@ fn it_drops() { log::debug!("main closure"); f.fragment_from_iter(it) }) - .unwrap() }; let _ = caller.call(factory); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 6d8129eed..dcc14268d 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -57,19 +57,25 @@ pub(crate) mod innerlude { /// // ... /// }; /// ``` - pub type Component

= fn(Scope

) -> Element; + pub type Component

= fn(Scope

) -> Element; + + /// A list of attributes + /// + pub type Attributes<'a> = Option<&'a [Attribute<'a>]>; } pub use crate::innerlude::{ Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope, - ScopeId, ScopeState, UserEvent, VElement, VFragment, VNode, VirtualDom, + ScopeId, ScopeState, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, VText, + VirtualDom, }; pub mod prelude { pub use crate::innerlude::Scope; pub use crate::innerlude::{ - Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, ScopeState, + Attributes, Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, + ScopeState, }; pub use crate::nodes::VNode; pub use crate::virtual_dom::{fc_to_builder, Fragment, Properties}; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2e6333386..aef3d0561 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,6 +1,9 @@ //! Instructions returned by the VirtualDOM on how to modify the Real DOM. //! //! This module contains an internal API to generate these instructions. +//! +//! Beware that changing code in this module will break compatibility with +//! interpreters for these types of DomEdits. use crate::innerlude::*; use std::{any::Any, fmt::Debug}; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0d7d76792..fc0e38f59 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -20,7 +20,7 @@ use std::{ /// /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: /// -/// - the [`rsx`] macro +/// - the `rsx!` macro /// - the [`NodeFactory`] API pub enum VNode<'src> { /// Text VNodes simply bump-allocated (or static) string slices @@ -51,7 +51,7 @@ pub enum VNode<'src> { /// key: "a", /// onclick: |e| log::info!("clicked"), /// hidden: "true", - /// style: { background_color: "red" } + /// style: { background_color: "red" }, /// "hello" /// } /// }); @@ -59,7 +59,7 @@ pub enum VNode<'src> { /// if let VNode::Element(velement) = node { /// assert_eq!(velement.tag_name, "div"); /// assert_eq!(velement.namespace, None); - /// assert_eq!(velement.key, Some("a)); + /// assert_eq!(velement.key, Some("a")); /// } /// ``` Element(&'src VElement<'src>), @@ -720,6 +720,14 @@ impl<'a> IntoVNode<'a> for Option> { } } +impl<'a, 'b> IntoIterator for LazyNodes<'a, 'b> { + type Item = LazyNodes<'a, 'b>; + type IntoIter = std::iter::Once; + fn into_iter(self) -> Self::IntoIter { + std::iter::once(self) + } +} + impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { self.call(cx) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 0b024f011..e957240f7 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -351,7 +351,7 @@ impl ScopeArena { /// cx.render(rsx!{ div {"Hello, {cx.props.name}"} }) /// } /// ``` -pub struct Scope<'a, P> { +pub struct Scope<'a, P = ()> { pub scope: &'a ScopeState, pub props: &'a P, } @@ -366,6 +366,26 @@ impl

Clone for Scope<'_, P> { } } +impl<'src, P> Scope<'src, P> { + pub fn register_task(self, task: impl Future + 'src) { + let fut: &'src mut dyn Future = self.scope.bump().alloc(task); + let boxed_fut: BumpBox<'src, dyn Future + 'src> = + unsafe { BumpBox::from_raw(fut) }; + let pinned_fut: Pin> = boxed_fut.into(); + + // erase the 'src lifetime for self-referential storage + // todo: provide a miri test around this + // concerned about segfaulting + let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) }; + + self.sender + .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) + .unwrap(); + + self.scope.items.borrow_mut().tasks.push(self_ref_fut); + } +} + impl<'a, P> std::ops::Deref for Scope<'a, P> { // rust will auto deref again to the original 'a lifetime at the call site type Target = &'a ScopeState; @@ -578,7 +598,7 @@ impl ScopeState { /// 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. + /// A component's ScopeId can be obtained from `use_hook` or the [`ScopeState::scope_id`] method. /// /// This method should be used when you want to schedule an update for a component pub fn schedule_update_any(&self) -> Rc { @@ -662,29 +682,33 @@ impl ScopeState { /// Pushes the future onto the poll queue to be polled after the component renders. /// /// The future is forcibly dropped if the component is not ready by the next render - pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize + pub fn push_task<'src, F>(&'src self, fut: F) -> usize where F: Future, F::Output: 'src, F: 'src, { - self.sender - .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) - .unwrap(); - // wrap it in a type that will actually drop the contents // // Safety: we just made the pointer above and will promise not to alias it! // The main reason we do this through from_raw is because Bumpalo's box does // not support unsized coercion - let fut: &mut dyn Future = self.bump().alloc(fut()); - let boxed_fut: BumpBox> = unsafe { BumpBox::from_raw(fut) }; - let pinned_fut: Pin> = boxed_fut.into(); + + let fut: &'src mut dyn Future = self.bump().alloc(fut); + let boxed_fut: BumpBox<'src, dyn Future + 'src> = + unsafe { BumpBox::from_raw(fut) }; + let pinned_fut: Pin> = boxed_fut.into(); // erase the 'src lifetime for self-referential storage // todo: provide a miri test around this // concerned about segfaulting - let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) }; + let self_ref_fut = unsafe { + std::mem::transmute::>, Pin>>(pinned_fut) + }; + + self.sender + .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) + .unwrap(); // Push the future into the tasks let mut items = self.items.borrow_mut(); @@ -707,14 +731,11 @@ impl ScopeState { /// cx.render(lazy_tree) /// } ///``` - pub fn render<'src>(&'src self, rsx: Option>) -> Option> { + pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option> { let fac = NodeFactory { bump: &self.wip_frame().bump, }; - match rsx { - Some(s) => Some(s.call(fac)), - None => todo!("oh no no nodes"), - } + Some(rsx.call(fac)) } /// Store a value between renders diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1d3d360ce..6fe0221db 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -15,7 +15,7 @@ use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::A /// /// ## Guide /// -/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`]. +/// Components are defined as simple functions that take [`Scope`] and return an [`Element`]. /// /// ```rust, ignore /// #[derive(Props, PartialEq)] @@ -552,11 +552,11 @@ impl VirtualDom { /// let dom = VirtualDom::new(Base); /// let nodes = dom.render_nodes(rsx!("div")); /// ``` - pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option>) -> &'a VNode<'a> { + pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> { let scope = self.scopes.get_scope(ScopeId(0)).unwrap(); let frame = scope.wip_frame(); let factory = NodeFactory { bump: &frame.bump }; - let node = lazy_nodes.unwrap().call(factory); + let node = lazy_nodes.call(factory); frame.bump.alloc(node) } @@ -594,7 +594,7 @@ impl VirtualDom { /// let dom = VirtualDom::new(Base); /// let nodes = dom.render_nodes(rsx!("div")); /// ``` - pub fn create_vnodes<'a>(&'a self, nodes: Option>) -> Mutations<'a> { + pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> { let mut machine = DiffState::new(&self.scopes); machine.stack.element_stack.push(ElementId(0)); machine @@ -619,8 +619,8 @@ impl VirtualDom { /// ``` pub fn diff_lazynodes<'a>( &'a self, - left: Option>, - right: Option>, + left: LazyNodes<'a, '_>, + right: LazyNodes<'a, '_>, ) -> (Mutations<'a>, Mutations<'a>) { let (old, new) = (self.render_vnodes(left), self.render_vnodes(right)); @@ -947,7 +947,7 @@ impl<'a> Properties for FragmentProps<'a> { #[allow(non_upper_case_globals, non_snake_case)] pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { let i = cx.props.0.as_ref().map(|f| f.decouple()); - cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(i)))) + cx.render(LazyNodes::new(|f| f.fragment_from_iter(i))) } /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index a37fd85c3..044be289c 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -21,7 +21,7 @@ fn new_dom(app: Component

, props: P) -> VirtualDom { #[test] fn test_original_diff() { - static APP: Component<()> = |cx| { + static APP: Component = |cx| { cx.render(rsx! { div { div { @@ -57,7 +57,7 @@ fn test_original_diff() { #[test] fn create() { - static APP: Component<()> = |cx| { + static APP: Component = |cx| { cx.render(rsx! { div { div { @@ -120,7 +120,7 @@ fn create() { #[test] fn create_list() { - static APP: Component<()> = |cx| { + static APP: Component = |cx| { cx.render(rsx! { {(0..3).map(|f| rsx!{ div { "hello" @@ -169,7 +169,7 @@ fn create_list() { #[test] fn create_simple() { - static APP: Component<()> = |cx| { + static APP: Component = |cx| { cx.render(rsx! { div {} div {} @@ -207,7 +207,7 @@ fn create_simple() { } #[test] fn create_components() { - static App: Component<()> = |cx| { + static App: Component = |cx| { cx.render(rsx! { Child { "abc1" } Child { "abc2" } @@ -273,7 +273,7 @@ fn create_components() { } #[test] fn anchors() { - static App: Component<()> = |cx| { + static App: Component = |cx| { cx.render(rsx! { {true.then(|| rsx!{ div { "hello" } })} {false.then(|| rsx!{ div { "goodbye" } })} diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 2125cb9fb..5b98ea80e 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -802,3 +802,68 @@ fn controlled_keyed_diffing_out_of_order_max_test() { ] ); } + +// noticed some weird behavior in the desktop interpreter +// just making sure it doesnt happen in the core implementation +#[test] +fn remove_list() { + let dom = new_dom(); + + let left = rsx!({ + (0..10).rev().take(5).map(|i| { + rsx! { Fragment { key: "{i}", "{i}" }} + }) + }); + + let right = rsx!({ + (0..10).rev().take(2).map(|i| { + rsx! { Fragment { key: "{i}", "{i}" }} + }) + }); + + let (create, changes) = dom.diff_lazynodes(left, right); + + // dbg!(create); + // dbg!(changes); + + assert_eq!( + changes.edits, + [ + // remove 5, 4, 3 + Remove { root: 3 }, + Remove { root: 4 }, + Remove { root: 5 }, + ] + ); +} + +// noticed some weird behavior in the desktop interpreter +// just making sure it doesnt happen in the core implementation +#[test] +fn remove_list_nokeyed() { + let dom = new_dom(); + + let left = rsx!({ + (0..10).rev().take(5).map(|i| { + rsx! { Fragment { "{i}" }} + }) + }); + + let right = rsx!({ + (0..10).rev().take(2).map(|i| { + rsx! { Fragment { "{i}" }} + }) + }); + + let (create, changes) = dom.diff_lazynodes(left, right); + + assert_eq!( + changes.edits, + [ + // remove 5, 4, 3 + Remove { root: 3 }, + Remove { root: 4 }, + Remove { root: 5 }, + ] + ); +} diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 2626355e4..d15ae4473 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -11,7 +11,7 @@ use dioxus_hooks::*; use dioxus_html as dioxus_elements; fn main() { - simple_logger::init().unwrap(); + // simple_logger::init().unwrap(); dioxus_desktop::launch(app); } @@ -21,6 +21,7 @@ fn app(cx: Scope<()>) -> Element { cx.push_task(|| async move { tokio::time::sleep(Duration::from_millis(1000)).await; + println!("count is now {:?}", count); count += 1; }); @@ -33,4 +34,4 @@ fn app(cx: Scope<()>) -> Element { } } }) -}; +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a4eee7b33..246360fad 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -23,6 +23,7 @@ use tao::{ pub use wry; pub use wry::application as tao; use wry::{ + application::event_loop::EventLoopProxy, webview::RpcRequest, webview::{WebView, WebViewBuilder}, }; @@ -37,7 +38,7 @@ pub fn launch_cfg( launch_with_props(root, (), config_builder) } -pub fn launch_with_props( +pub fn launch_with_props( root: Component

, props: P, builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, @@ -51,7 +52,7 @@ struct Response<'a> { edits: Vec>, } -pub fn run( +pub fn run( root: Component, props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, @@ -59,51 +60,61 @@ pub fn run( let mut desktop_cfg = DesktopConfig::new(); user_builder(&mut desktop_cfg); - let mut state = DesktopController::new_on_tokio(root, props); + let event_loop = EventLoop::with_user_event(); + let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy()); let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ); let modifiers = ModifiersState::default(); - let event_loop = EventLoop::new(); - - log::debug!("Starting event loop"); event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; match window_event { - Event::NewEvents(StartCause::Init) => state.new_window(&desktop_cfg, event_loop), + Event::NewEvents(StartCause::Init) => desktop.new_window(&desktop_cfg, event_loop), Event::WindowEvent { event, window_id, .. - } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Destroyed { .. } => state.close_window(window_id, control_flow), + } => { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow), - WindowEvent::KeyboardInput { event, .. } => { - if quit_hotkey.matches(&modifiers, &event.physical_key) { - state.close_window(window_id, control_flow); + WindowEvent::KeyboardInput { event, .. } => { + if quit_hotkey.matches(&modifiers, &event.physical_key) { + desktop.close_window(window_id, control_flow); + } } - } - WindowEvent::Resized(_) | WindowEvent::Moved(_) => { - if let Some(view) = state.webviews.get_mut(&window_id) { - let _ = view.resize(); + WindowEvent::Resized(_) | WindowEvent::Moved(_) => { + if let Some(view) = desktop.webviews.get_mut(&window_id) { + let _ = view.resize(); + } } + + // TODO: we want to shuttle all of these events into the user's app or provide some handler + _ => {} } + } - // TODO: we want to shuttle all of these events into the user's app or provide some handler - _ => {} - }, - - Event::MainEventsCleared => state.try_load_ready_webviews(), + Event::UserEvent(_evt) => { + desktop.try_load_ready_webviews(); + } + Event::MainEventsCleared => { + desktop.try_load_ready_webviews(); + } Event::Resumed => {} Event::Suspended => {} Event::LoopDestroyed => {} - + Event::RedrawRequested(_id) => {} _ => {} } }) } +pub enum UserWindowEvent { + Start, + Update, +} + pub struct DesktopController { webviews: HashMap, sender: futures_channel::mpsc::UnboundedSender, @@ -115,7 +126,11 @@ pub struct DesktopController { impl DesktopController { // Launch the virtualdom on its own thread managed by tokio // returns the desktop state - pub fn new_on_tokio(root: Component

, props: P) -> Self { + pub fn new_on_tokio( + root: Component

, + props: P, + evt: EventLoopProxy, + ) -> Self { let edit_queue = Arc::new(RwLock::new(VecDeque::new())); let pending_edits = edit_queue.clone(); @@ -130,7 +145,6 @@ impl DesktopController { .unwrap(); runtime.block_on(async move { - // LocalSet::new().block_on(&runtime, async move { let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver)); @@ -150,6 +164,7 @@ impl DesktopController { .unwrap() .push_front(serde_json::to_string(&edit.edits).unwrap()); } + let _ = evt.send_event(UserWindowEvent::Update); } }) }); @@ -164,7 +179,11 @@ impl DesktopController { } } - pub fn new_window(&mut self, cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget<()>) { + pub fn new_window( + &mut self, + cfg: &DesktopConfig, + event_loop: &EventLoopWindowTarget, + ) { let builder = cfg.window.clone().with_menu({ // create main menubar menu let mut menu_bar_menu = MenuBar::new(); diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index f593c2b77..e9c277506 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -5,44 +5,71 @@ use std::{ pin::Pin, rc::Rc, }; +/* -pub fn use_coroutine<'a, F: Future + 'static>( + + +let g = use_coroutine(&cx, || { + // clone the items in + async move { + + } +}) + + + +*/ +pub fn use_coroutine<'a, F>( cx: &'a ScopeState, - mut f: impl FnMut() -> F + 'a, -) -> CoroutineHandle<'a> { + create_future: impl FnOnce() -> F, +) -> CoroutineHandle<'a> +where + F: Future, + F: 'static, +{ cx.use_hook( move |_| State { running: Default::default(), - fut: Default::default(), - submit: Default::default(), + pending_fut: Default::default(), + running_fut: Default::default(), }, |state| { - let fut_slot = state.fut.clone(); - let running = state.running.clone(); - let submit: Box = Box::new(move || { - let g = async move { - running.set(true); - f().await; - running.set(false); - }; - let p: Pin>> = Box::pin(g); - fut_slot - .borrow_mut() - .replace(unsafe { std::mem::transmute(p) }); - }); + let f = create_future(); + state.pending_fut.set(Some(Box::pin(f))); - let submit = unsafe { std::mem::transmute(submit) }; - state.submit.get_mut().replace(submit); - - if state.running.get() { - let mut fut = state.fut.borrow_mut(); - cx.push_task(|| fut.as_mut().unwrap().as_mut()); - } else { - // make sure to drop the old future - if let Some(fut) = state.fut.borrow_mut().take() { - drop(fut); - } + if let Some(fut) = state.running_fut.as_mut() { + cx.push_task(fut); } + + // if let Some(fut) = state.running_fut.take() { + // state.running.set(true); + // fut.resume(); + // } + + // let submit: Box = Box::new(move || { + // let g = async move { + // running.set(true); + // create_future().await; + // running.set(false); + // }; + // let p: Pin>> = Box::pin(g); + // fut_slot + // .borrow_mut() + // .replace(unsafe { std::mem::transmute(p) }); + // }); + + // let submit = unsafe { std::mem::transmute(submit) }; + // state.submit.get_mut().replace(submit); + + // if state.running.get() { + // // let mut fut = state.fut.borrow_mut(); + // // cx.push_task(|| fut.as_mut().unwrap().as_mut()); + // } else { + // // make sure to drop the old future + // if let Some(fut) = state.fut.borrow_mut().take() { + // drop(fut); + // } + // } CoroutineHandle { cx, inner: state } }, ) @@ -50,14 +77,20 @@ pub fn use_coroutine<'a, F: Future + 'static>( struct State { running: Rc>, - submit: RefCell>>, - fut: Rc>>>>>, + + // the way this is structure, you can toggle the coroutine without re-rendering the comppnent + // this means every render *generates* the future, which is a bit of a waste + // todo: allocate pending futures in the bump allocator and then have a true promotion + pending_fut: Cell + 'static>>>>, + running_fut: Option + 'static>>>, + // running_fut: Rc + 'static>>>>>, } pub struct CoroutineHandle<'a> { cx: &'a ScopeState, inner: &'a State, } + impl Clone for CoroutineHandle<'_> { fn clone(&self) -> Self { CoroutineHandle { @@ -73,10 +106,11 @@ impl<'a> CoroutineHandle<'a> { if self.is_running() { return; } - if let Some(submit) = self.inner.submit.borrow_mut().take() { - submit(); - let mut fut = self.inner.fut.borrow_mut(); - self.cx.push_task(|| fut.as_mut().unwrap().as_mut()); + + if let Some(submit) = self.inner.pending_fut.take() { + // submit(); + // let inner = self.inner; + // self.cx.push_task(submit()); } } @@ -84,5 +118,11 @@ impl<'a> CoroutineHandle<'a> { self.inner.running.get() } - pub fn resume(&self) {} + pub fn resume(&self) { + // self.cx.push_task(fut) + } + + pub fn stop(&self) {} + + pub fn restart(&self) {} } diff --git a/packages/hooks/src/usemodel.rs b/packages/hooks/src/usemodel.rs index 642081cf8..d8ab70b7a 100644 --- a/packages/hooks/src/usemodel.rs +++ b/packages/hooks/src/usemodel.rs @@ -89,9 +89,9 @@ pub fn use_model_coroutine<'a, T, F: Future + 'static>( } }, |inner| { - if let Some(task) = inner.task.get_mut() { - cx.push_task(|| task); - } + // if let Some(task) = inner.task.get_mut() { + // cx.push_task(|| task); + // } // todo!() }, diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 1c4fe2d04..3286c95a6 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -171,7 +171,11 @@ impl<'a, T: 'static> UseState<'a, T> { } impl<'a, T: 'static + ToOwned> UseState<'a, T> { - /// Gain mutable access to the new value. This method is only available when the value is a `ToOwned` type. + /// Gain mutable access to the new value via RefMut. + /// + /// If `modify` is called, then the component will re-render. + /// + /// This method is only available when the value is a `ToOwned` type. /// /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value. /// diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 0ce88d59b..1a61b5deb 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -2,14 +2,28 @@ mod utils; use std::{cell::RefCell, rc::Rc}; +use dioxus::Attribute; use dioxus_core as dioxus; + use dioxus_core::prelude::*; -use dioxus_core_macro::{rsx, Props}; +use dioxus_core_macro::{format_args_f, rsx, Props}; use dioxus_html as dioxus_elements; // use wasm_bindgen::{JsCast, JsValue}; use crate::utils::strip_slash_suffix; +/// Initialize the app's router service and provide access to `Link` components +pub fn use_router(cx: &ScopeState, f: impl Fn(&str) -> R) -> &R { + let r = f("/"); + cx.use_hook( + |_| { + // + r + }, + |f| f, + ) +} + pub trait Routable: 'static + Send + Clone + PartialEq {} impl Routable for T where T: 'static + Send + Clone + PartialEq {} @@ -30,20 +44,29 @@ pub struct LinkProps<'a, R: Routable> { /// Link { to: Route::Home, href: Route::as_url } /// /// ``` - href: fn(&R) -> String, + #[props(default, setter(strip_option))] + href: Option<&'a str>, + + #[props(default, setter(strip_option))] + class: Option<&'a str>, - #[builder(default)] children: Element<'a>, + + #[props(default)] + attributes: Option<&'a [Attribute<'a>]>, } pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element { - let service = todo!(); + // let service = todo!(); // let service: todo!() = use_router_service::(&cx)?; - // cx.render(rsx! { - // a { - // href: format_args!("{}", (cx.props.href)(&cx.props.to)), - // onclick: move |_| service.push_route(cx.props.to.clone()), - // // todo!() {&cx.props.children}, - // } - // }) + let class = cx.props.class.unwrap_or(""); + cx.render(rsx! { + a { + href: "#", + class: "{class}", + {&cx.props.children} + // onclick: move |_| service.push_route(cx.props.to.clone()), + // href: format_args!("{}", (cx.props.href)(&cx.props.to)), + } + }) } diff --git a/packages/web/examples/suspense.rs b/packages/web/examples/suspense.rs index 4dd1b4c33..84c3b5f83 100644 --- a/packages/web/examples/suspense.rs +++ b/packages/web/examples/suspense.rs @@ -15,26 +15,27 @@ fn main() { } static App: Component<()> = |cx| { - let doggo = cx.suspend(|| async move { - #[derive(serde::Deserialize)] - struct Doggo { - message: String, - } + todo!("suspense is broken") + // let doggo = suspend(|| async move { + // #[derive(serde::Deserialize)] + // struct Doggo { + // message: String, + // } - let src = reqwest::get("https://dog.ceo/api/breeds/image/random") - .await - .expect("Failed to fetch doggo") - .json::() - .await - .expect("Failed to parse doggo") - .message; + // let src = reqwest::get("https://dog.ceo/api/breeds/image/random") + // .await + // .expect("Failed to fetch doggo") + // .json::() + // .await + // .expect("Failed to parse doggo") + // .message; - rsx!(cx, img { src: "{src}" }) - }); + // rsx!(cx, img { src: "{src}" }) + // }); - rsx!(cx, div { - h1 {"One doggo coming right up"} - button { onclick: move |_| cx.needs_update(), "Get a new doggo" } - {doggo} - }) + // rsx!(cx, div { + // h1 {"One doggo coming right up"} + // button { onclick: move |_| cx.needs_update(), "Get a new doggo" } + // {doggo} + // }) }; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 8796ba35e..73c9465b6 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -89,7 +89,7 @@ mod ric_raf; /// } /// ``` pub fn launch(root_component: Component<()>) { - launch_with_props(root_component, ()); + launch_with_props(root_component, (), |c| c); } /// Launches the VirtualDOM from the specified component function and props. From c7d001cbb457929b9742ad96c4997cdcc695bb1a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 26 Dec 2021 14:22:30 -0500 Subject: [PATCH 2/4] wip: rip out unsafe task engine --- examples/tasks.rs | 2 +- packages/core/examples/borrowed.rs | 2 +- packages/core/src/lib.rs | 4 +- packages/core/src/scopes.rs | 162 +++++++++++++---------------- packages/core/src/virtual_dom.rs | 46 +++----- packages/desktop/examples/async.rs | 2 +- packages/hooks/src/lib.rs | 7 +- packages/hooks/src/usecoroutine.rs | 19 ++-- packages/hooks/src/usefuture.rs | 34 ++++++ packages/web/examples/async.rs | 2 +- 10 files changed, 144 insertions(+), 136 deletions(-) create mode 100644 packages/hooks/src/usefuture.rs diff --git a/examples/tasks.rs b/examples/tasks.rs index e22026589..0db459786 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -12,7 +12,7 @@ fn main() { fn app(cx: Scope<()>) -> Element { let mut count = use_state(&cx, || 0); - cx.push_task(|| async move { + cx.push_future(|| async move { tokio::time::sleep(Duration::from_millis(100)).await; count += 1; }); diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index cc376789c..b7a9f513c 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -7,7 +7,7 @@ fn main() {} fn app(cx: Scope) -> Element { cx.render(rsx!(div { - app2 ( + app2( p: "asd" ) })) diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index dcc14268d..ad00536d2 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -67,8 +67,8 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope, - ScopeId, ScopeState, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, VText, - VirtualDom, + ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, + VText, VirtualDom, }; pub mod prelude { diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index e957240f7..67d2dba5a 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,5 +1,7 @@ +use crate::{innerlude::*, unsafe_utils::extend_vnode}; +use bumpalo::Bump; use futures_channel::mpsc::UnboundedSender; -use fxhash::{FxHashMap, FxHashSet}; +use fxhash::FxHashMap; use slab::Slab; use std::{ any::{Any, TypeId}, @@ -11,9 +13,6 @@ use std::{ rc::Rc, }; -use crate::{innerlude::*, unsafe_utils::extend_vnode}; -use bumpalo::{boxed::Box as BumpBox, Bump}; - pub(crate) type FcSlot = *const (); pub(crate) struct Heuristic { @@ -26,14 +25,13 @@ pub(crate) struct Heuristic { // // has an internal heuristics engine to pre-allocate arenas to the right size pub(crate) struct ScopeArena { - pub pending_futures: RefCell>, - pub scope_counter: Cell, - pub sender: UnboundedSender, + pub scope_gen: Cell, pub bump: Bump, pub scopes: RefCell>, pub heuristics: RefCell>, pub free_scopes: RefCell>, pub nodes: RefCell>>, + pub tasks: Rc, } impl ScopeArena { @@ -61,14 +59,13 @@ impl ScopeArena { debug_assert_eq!(root_id, 0); Self { - scope_counter: Cell::new(0), + scope_gen: Cell::new(0), bump, - pending_futures: RefCell::new(FxHashSet::default()), scopes: RefCell::new(FxHashMap::default()), heuristics: RefCell::new(FxHashMap::default()), free_scopes: RefCell::new(Vec::new()), nodes: RefCell::new(nodes), - sender, + tasks: TaskQueue::new(sender), } } @@ -92,8 +89,8 @@ impl ScopeArena { subtree: u32, ) -> ScopeId { // Increment the ScopeId system. ScopeIDs are never reused - let new_scope_id = ScopeId(self.scope_counter.get()); - self.scope_counter.set(self.scope_counter.get() + 1); + let new_scope_id = ScopeId(self.scope_gen.get()); + self.scope_gen.set(self.scope_gen.get() + 1); // Get the height of the scope let height = parent_scope @@ -129,9 +126,9 @@ impl ScopeArena { height, container, new_scope_id, - self.sender.clone(), parent_scope, vcomp, + self.tasks.clone(), self.heuristics .borrow() .get(&fc_ptr) @@ -236,15 +233,11 @@ impl ScopeArena { // - We've dropped all references to the wip bump frame with "ensure_drop_safety" unsafe { scope.reset_wip_frame() }; - let mut items = scope.items.borrow_mut(); - - // just forget about our suspended nodes while we're at it - items.tasks.clear(); + let items = scope.items.borrow(); // guarantee that we haven't screwed up - there should be no latent references anywhere debug_assert!(items.listeners.is_empty()); debug_assert!(items.borrowed_props.is_empty()); - debug_assert!(items.tasks.is_empty()); } // safety: this is definitely not dropped @@ -263,10 +256,6 @@ impl ScopeArena { let props = scope.props.borrow(); let render = props.as_ref().unwrap(); if let Some(node) = render.render(scope) { - if !scope.items.borrow().tasks.is_empty() { - self.pending_futures.borrow_mut().insert(id); - } - let frame = scope.wip_frame(); let node = frame.bump.alloc(node); frame.node.set(unsafe { extend_vnode(node) }); @@ -366,26 +355,6 @@ impl

Clone for Scope<'_, P> { } } -impl<'src, P> Scope<'src, P> { - pub fn register_task(self, task: impl Future + 'src) { - let fut: &'src mut dyn Future = self.scope.bump().alloc(task); - let boxed_fut: BumpBox<'src, dyn Future + 'src> = - unsafe { BumpBox::from_raw(fut) }; - let pinned_fut: Pin> = boxed_fut.into(); - - // erase the 'src lifetime for self-referential storage - // todo: provide a miri test around this - // concerned about segfaulting - let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) }; - - self.sender - .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) - .unwrap(); - - self.scope.items.borrow_mut().tasks.push(self_ref_fut); - } -} - impl<'a, P> std::ops::Deref for Scope<'a, P> { // rust will auto deref again to the original 'a lifetime at the call site type Target = &'a ScopeState; @@ -402,6 +371,14 @@ impl<'a, P> std::ops::Deref for Scope<'a, P> { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ScopeId(pub usize); +/// A task's unique identifier. +/// +/// `TaskId` is a `usize` that is unique across the entire VirtualDOM and across time. TaskIDs will never be reused +/// once a Task has been completed. +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct TaskId(pub usize); + /// Every component in Dioxus is represented by a `ScopeState`. /// /// Scopes contain the state for hooks, the component's props, and other lifecycle information. @@ -416,12 +393,10 @@ pub struct ScopeState { pub(crate) container: ElementId, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, - pub(crate) sender: UnboundedSender, // todo: subtrees pub(crate) is_subtree_root: Cell, pub(crate) subtree: Cell, - pub(crate) props: RefCell>>, // nodes, items @@ -436,12 +411,12 @@ pub struct ScopeState { // shared state -> todo: move this out of scopestate pub(crate) shared_contexts: RefCell>>, + pub(crate) tasks: Rc, } pub struct SelfReferentialItems<'a> { pub(crate) listeners: Vec<&'a Listener<'a>>, pub(crate) borrowed_props: Vec<&'a VComponent<'a>>, - pub(crate) tasks: Vec>>>, } // Public methods exposed to libraries and components @@ -450,13 +425,12 @@ impl ScopeState { height: u32, container: ElementId, our_arena_idx: ScopeId, - sender: UnboundedSender, parent_scope: Option<*mut ScopeState>, vcomp: Box, + tasks: Rc, (node_capacity, hook_capacity): (usize, usize), ) -> Self { ScopeState { - sender, container, our_arena_idx, parent_scope, @@ -470,12 +444,12 @@ impl ScopeState { generation: 0.into(), + tasks, shared_contexts: Default::default(), items: RefCell::new(SelfReferentialItems { listeners: Default::default(), borrowed_props: Default::default(), - tasks: Default::default(), }), hook_arena: Bump::new(), @@ -590,7 +564,7 @@ impl ScopeState { /// /// ## Notice: you should prefer using prepare_update and get_scope_id pub fn schedule_update(&self) -> Rc { - let (chan, id) = (self.sender.clone(), self.scope_id()); + let (chan, id) = (self.tasks.sender.clone(), self.scope_id()); Rc::new(move || { let _ = chan.unbounded_send(SchedulerMsg::Immediate(id)); }) @@ -602,7 +576,7 @@ impl ScopeState { /// /// This method should be used when you want to schedule an update for a component pub fn schedule_update_any(&self) -> Rc { - let chan = self.sender.clone(); + let chan = self.tasks.sender.clone(); Rc::new(move |id| { let _ = chan.unbounded_send(SchedulerMsg::Immediate(id)); }) @@ -619,7 +593,10 @@ impl ScopeState { /// /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. pub fn needs_update_any(&self, id: ScopeId) { - let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id)); + let _ = self + .tasks + .sender + .unbounded_send(SchedulerMsg::Immediate(id)); } /// Get the Root Node of this scope @@ -682,38 +659,19 @@ impl ScopeState { /// Pushes the future onto the poll queue to be polled after the component renders. /// /// The future is forcibly dropped if the component is not ready by the next render - pub fn push_task<'src, F>(&'src self, fut: F) -> usize - where - F: Future, - F::Output: 'src, - F: 'src, - { - // wrap it in a type that will actually drop the contents - // - // Safety: we just made the pointer above and will promise not to alias it! - // The main reason we do this through from_raw is because Bumpalo's box does - // not support unsized coercion - - let fut: &'src mut dyn Future = self.bump().alloc(fut); - let boxed_fut: BumpBox<'src, dyn Future + 'src> = - unsafe { BumpBox::from_raw(fut) }; - let pinned_fut: Pin> = boxed_fut.into(); - - // erase the 'src lifetime for self-referential storage - // todo: provide a miri test around this - // concerned about segfaulting - let self_ref_fut = unsafe { - std::mem::transmute::>, Pin>>(pinned_fut) - }; - - self.sender + pub fn push_future(&self, fut: impl Future + 'static) -> TaskId { + // wake up the scheduler if it is sleeping + self.tasks + .sender .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) .unwrap(); - // Push the future into the tasks - let mut items = self.items.borrow_mut(); - items.tasks.push(self_ref_fut); - items.tasks.len() - 1 + self.tasks.push_fut(fut) + } + + // todo: attach some state to the future to know if we should poll it + pub fn remove_future(&self, id: TaskId) { + self.tasks.remove_fut(id); } /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. @@ -834,11 +792,6 @@ impl ScopeState { self.generation.set(self.generation.get() + 1); } - /// Get the [`Bump`] of the WIP frame. - pub(crate) fn bump(&self) -> &Bump { - &self.wip_frame().bump - } - // todo: disable bookkeeping on drop (unncessary) pub(crate) fn reset(&mut self) { // first: book keaping @@ -855,11 +808,9 @@ impl ScopeState { let SelfReferentialItems { borrowed_props, listeners, - tasks, } = self.items.get_mut(); borrowed_props.clear(); listeners.clear(); - tasks.clear(); self.frames[0].reset(); self.frames[1].reset(); @@ -905,6 +856,43 @@ impl BumpFrame { } } +pub(crate) struct TaskQueue { + pub(crate) tasks: RefCell>, + gen: Cell, + sender: UnboundedSender, +} +pub(crate) type InnerTask = Pin>>; +impl TaskQueue { + fn new(sender: UnboundedSender) -> Rc { + Rc::new(Self { + tasks: RefCell::new(FxHashMap::default()), + gen: Cell::new(0), + sender, + }) + } + fn push_fut(&self, task: impl Future + 'static) -> TaskId { + let pinned = Box::pin(task); + let id = self.gen.get(); + self.gen.set(id + 1); + let tid = TaskId(id); + + self.tasks.borrow_mut().insert(tid, pinned); + tid + } + fn remove_fut(&self, id: TaskId) { + if let Ok(mut tasks) = self.tasks.try_borrow_mut() { + let _ = tasks.remove(&id); + } else { + // todo: it should be okay to remote a fut while the queue is being polled + // However, it's not currently possible to do that. + log::debug!("Unable to remove task from task queue. This is probably a bug."); + } + } + pub(crate) fn has_tasks(&self) -> bool { + !self.tasks.borrow().is_empty() + } +} + #[test] fn sizeof() { dbg!(std::mem::size_of::()); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 6fe0221db..f429fead5 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -7,7 +7,6 @@ use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_util::{Future, StreamExt}; use fxhash::FxHashSet; use indexmap::IndexSet; -use smallvec::SmallVec; use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc, task::Poll}; /// A virtual node s ystem that progresses user events and diffs UI trees. @@ -312,16 +311,16 @@ impl VirtualDom { } if self.pending_messages.is_empty() { - if self.scopes.pending_futures.borrow().is_empty() { - self.pending_messages - .push_front(self.channel.1.next().await.unwrap()); - } else { + if self.scopes.tasks.has_tasks() { use futures_util::future::{select, Either}; match select(PollTasks(&mut self.scopes), self.channel.1.next()).await { Either::Left((_, _)) => {} Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()), } + } else { + self.pending_messages + .push_front(self.channel.1.next().await.unwrap()); } } @@ -345,8 +344,8 @@ impl VirtualDom { pub fn process_message(&mut self, msg: SchedulerMsg) { match msg { - SchedulerMsg::NewTask(id) => { - self.scopes.pending_futures.borrow_mut().insert(id); + SchedulerMsg::NewTask(_id) => { + // uh, not sure? I think end up re-polling it anyways } SchedulerMsg::Event(event) => { if let Some(element) = event.element { @@ -705,33 +704,20 @@ impl<'a> Future for PollTasks<'a> { fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { let mut all_pending = true; - let mut unfinished_tasks: SmallVec<[_; 10]> = smallvec::smallvec![]; - let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![]; + let mut tasks = self.0.tasks.tasks.borrow_mut(); + let mut to_remove = vec![]; - // Poll every scope manually - for fut in self.0.pending_futures.borrow().iter().copied() { - let scope = self.0.get_scope(fut).expect("Scope should never be moved"); - - let mut items = scope.items.borrow_mut(); - - // really this should just be retain_mut but that doesn't exist yet - while let Some(mut task) = items.tasks.pop() { - if task.as_mut().poll(cx).is_ready() { - all_pending = false - } else { - unfinished_tasks.push(task); - } + // this would be better served by retain + for (id, task) in tasks.iter_mut() { + if task.as_mut().poll(cx).is_ready() { + to_remove.push(*id); + } else { + all_pending = false; } - - if unfinished_tasks.is_empty() { - scopes_to_clear.push(fut); - } - - items.tasks.extend(unfinished_tasks.drain(..)); } - for scope in scopes_to_clear { - self.0.pending_futures.borrow_mut().remove(&scope); + for id in to_remove { + tasks.remove(&id); } // Resolve the future if any singular task is ready diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index d15ae4473..81b464264 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -19,7 +19,7 @@ fn app(cx: Scope<()>) -> Element { let mut count = use_state(&cx, || 0); log::debug!("count is {:?}", count); - cx.push_task(|| async move { + cx.push_future(|| async move { tokio::time::sleep(Duration::from_millis(1000)).await; println!("count is now {:?}", count); count += 1; diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index ef5b699b6..4e5c5ce25 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -10,5 +10,8 @@ pub use use_shared_state::*; mod usecoroutine; pub use usecoroutine::*; -mod usemodel; -pub use usemodel::*; +mod usefuture; +pub use usefuture::*; + +// mod usemodel; +// pub use usemodel::*; diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index e9c277506..65cbbd711 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -1,10 +1,6 @@ use dioxus_core::ScopeState; use std::future::Future; -use std::{ - cell::{Cell, RefCell}, - pin::Pin, - rc::Rc, -}; +use std::{cell::Cell, pin::Pin, rc::Rc}; /* @@ -24,8 +20,7 @@ pub fn use_coroutine<'a, F>( create_future: impl FnOnce() -> F, ) -> CoroutineHandle<'a> where - F: Future, - F: 'static, + F: Future + 'static, { cx.use_hook( move |_| State { @@ -35,11 +30,13 @@ where }, |state| { let f = create_future(); - state.pending_fut.set(Some(Box::pin(f))); + let id = cx.push_future(f); - if let Some(fut) = state.running_fut.as_mut() { - cx.push_task(fut); - } + // state.pending_fut.set(Some(Box::pin(f))); + + // if let Some(fut) = state.running_fut.as_mut() { + // cx.push_future(fut); + // } // if let Some(fut) = state.running_fut.take() { // state.running.set(true); diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs new file mode 100644 index 000000000..c2b6ad0da --- /dev/null +++ b/packages/hooks/src/usefuture.rs @@ -0,0 +1,34 @@ +use dioxus_core::{ScopeState, TaskId}; +use std::{cell::Cell, future::Future}; + +pub fn use_future<'a, T: 'static, F: Future>( + cx: &'a ScopeState, + f: impl FnOnce() -> F, +) -> FutureHandle<'a, T> { + cx.use_hook( + |_| { + // + UseFutureInner { + needs_regen: true, + task: None, + } + }, + |_| { + // + FutureHandle { + cx, + value: Cell::new(None), + } + }, + ) +} + +struct UseFutureInner { + needs_regen: bool, + task: Option, +} + +pub struct FutureHandle<'a, T> { + cx: &'a ScopeState, + value: Cell>, +} diff --git a/packages/web/examples/async.rs b/packages/web/examples/async.rs index ae8af39bd..91a5ac33c 100644 --- a/packages/web/examples/async.rs +++ b/packages/web/examples/async.rs @@ -17,7 +17,7 @@ fn main() { static App: Component<()> = |cx| { let mut count = use_state(&cx, || 0); - cx.push_task(|| async move { + cx.push_future(|| async move { TimeoutFuture::new(100).await; count += 1; }); From c0e0196a67230ee4216b574391d6bb660fb98953 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 26 Dec 2021 14:22:40 -0500 Subject: [PATCH 3/4] docs: update the docs --- docs/guide/src/README.md | 8 +- docs/guide/src/SUMMARY.md | 6 +- docs/guide/src/concepts/00-index.md | 6 +- .../guide/src/concepts/06-subscription-api.md | 2 +- docs/guide/src/concepts/10-concurrent-mode.md | 14 +- docs/guide/src/concepts/11-arena-memo.md | 12 +- docs/guide/src/concepts/12-signals.md | 14 +- docs/guide/src/concepts/13-subtrees.md | 2 +- docs/guide/src/concepts/component_children.md | 217 ++++++++++++++++++ docs/guide/src/concepts/components.md | 30 +-- .../src/concepts/conditional_rendering.md | 12 +- docs/guide/src/concepts/event_handlers.md | 2 +- docs/guide/src/concepts/hooks.md | 187 +++++++++++++++ docs/guide/src/concepts/interactivity.md | 33 +-- docs/guide/src/concepts/lists.md | 4 +- docs/guide/src/concepts/rsx_in_depth.md | 4 +- docs/guide/src/concepts/user_input.md | 1 + docs/guide/src/concepts/vnodes.md | 2 - docs/guide/src/hello_world.md | 8 +- docs/guide/src/misc/contributors.md | 7 + docs/guide/src/tutorial/index.md | 10 + docs/guide/src/tutorial/new_app.md | 33 +++ packages/hooks/src/usemodel.rs | 30 --- 23 files changed, 536 insertions(+), 108 deletions(-) create mode 100644 docs/guide/src/concepts/component_children.md create mode 100644 docs/guide/src/concepts/user_input.md diff --git a/docs/guide/src/README.md b/docs/guide/src/README.md index 419828620..ab2cc0403 100644 --- a/docs/guide/src/README.md +++ b/docs/guide/src/README.md @@ -2,10 +2,10 @@ ![dioxuslogo](./images/dioxuslogo_full.png) -**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more. +**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get started with Dioxus running on the Web, Desktop, Mobile, and more. ```rust -fn App(cx: Context, props: &()) -> Element { +fn App(cx: Scope) -> Element { let mut count = use_state(&cx, || 0); cx.render(rsx!( @@ -18,7 +18,7 @@ fn App(cx: Context, props: &()) -> Element { In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you! -> This is introduction book! For advanced topics, check out the [Reference Guide]() instead. +> This is introduction book! For advanced topics, check out the [Reference](https://dioxuslabs.com/reference) instead. ## Multiplatform @@ -76,7 +76,7 @@ Examples: - [Bluetooth scanner]() - [Device Viewer]() -[![FileExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/) +[![File ExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/) ### Mobile Support --- diff --git a/docs/guide/src/SUMMARY.md b/docs/guide/src/SUMMARY.md index 14e12b45b..4149008c8 100644 --- a/docs/guide/src/SUMMARY.md +++ b/docs/guide/src/SUMMARY.md @@ -7,13 +7,13 @@ - [Intro to Elements](concepts/vnodes.md) - [Intro to Components](concepts/components.md) - [Reusing, Importing, and Exporting Components](concepts/exporting_components.md) + - [Passing children and attributes](concepts/component_children.md) - [Conditional Rendering](concepts/conditional_rendering.md) - [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) + - [Event handlers](concepts/event_handlers.md) + - [User Input and Controlled Components](concepts/user_input.md) - [Lifecycle, updates, and effects](concepts/lifecycles.md)