From aef414ac0f48e938bbd93f5dba79aa32aaf444fa Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 26 Jan 2022 01:44:15 -0500 Subject: [PATCH 01/11] wip: use_coroutine new behavior --- packages/hooks/src/usecoroutine.rs | 178 ++++++++++++++--------------- 1 file changed, 86 insertions(+), 92 deletions(-) diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 7faf507ed..593371b23 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -1,115 +1,93 @@ -use dioxus_core::{ScopeState, TaskId}; +use bumpalo::boxed::Box as BumpBox; +use dioxus_core::exports::bumpalo; +use dioxus_core::exports::futures_channel; +use dioxus_core::{LazyNodes, ScopeState, TaskId}; use std::future::Future; use std::{cell::Cell, rc::Rc}; -/* - - -let g = use_coroutine(&cx, || { - // clone the items in - async move { - - } -}) - - - -*/ -pub fn use_coroutine(cx: &ScopeState, create_future: impl FnOnce() -> F) -> CoroutineHandle<'_> +/// Maintain a handle over a future that can be paused, resumed, and canceled. +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +pub fn use_coroutine<'a, F>( + cx: &'a ScopeState, + create_future: impl FnOnce() -> F + 'a, +) -> UseCoroutine where F: Future + 'static, { - let state = cx.use_hook(move |_| { - let f = create_future(); - let id = cx.push_future(f); - State { - running: Default::default(), - _id: id - // pending_fut: Default::default(), - // running_fut: Default::default(), - } + let state = cx.use_hook(move |_| CoroutineInner { + _id: None, + running: Default::default(), }); - // state.pending_fut.set(Some(Box::pin(f))); + let mut bump = None; - // if let Some(fut) = state.running_fut.as_mut() { - // cx.push_future(fut); - // } + // as an optimization, we use the bump arena to allocate the callback instead of boxes + // that way we don't always call the constructor, but it's still efficient + cx.render(LazyNodes::new(move |f| { + bump.replace(f.bump()); + f.static_text("") + })); - // if let Some(fut) = state.running_fut.take() { - // state.running.set(true); - // fut.resume(); - // } + let mut slot = Some(create_future); - // 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) }); - // }); + // safety: bumpalo is limited in constructing unsized box types, so we have to do it through dynamic dispatch + let boxed: BumpBox<'a, dyn FnMut() -> F + 'a> = unsafe { + BumpBox::from_raw(bump.unwrap().alloc(move || { + let inner = slot.take().unwrap(); + inner() + })) + }; - // 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 } -} - -struct State { - running: Rc>, - _id: TaskId, - // 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 { - cx: self.cx, - inner: self.inner, - } + UseCoroutine { + inner: state, + create_fut: Cell::new(Some(boxed)), + cx, } } -impl Copy for CoroutineHandle<'_> {} -impl<'a> CoroutineHandle<'a> { - #[allow(clippy::needless_return)] +struct CoroutineInner { + running: Rc>, + _id: Option, +} + +pub struct UseCoroutine<'a, F: Future + 'static> { + create_fut: Cell F + 'a>>>, + inner: &'a CoroutineInner, + cx: &'a ScopeState, +} + +impl<'a, F: Future + 'static> UseCoroutine<'a, F> { + pub fn auto_start(&self, start: bool) -> &Self { + todo!() + } + pub fn start(&self) { - if self.is_running() { - return; + if !self.is_running() { + if let Some(mut fut) = self.create_fut.take() { + let fut = fut(); + self.cx.push_future(fut); + } } - - // if let Some(submit) = self.inner.pending_fut.take() { - // submit(); - // let inner = self.inner; - // self.cx.push_task(submit()); - // } } pub fn is_running(&self) -> bool { - self.inner.running.get() + // self.inner.running.get() + false } pub fn resume(&self) { @@ -120,3 +98,19 @@ impl<'a> CoroutineHandle<'a> { pub fn restart(&self) {} } + +#[test] +fn it_works() { + use dioxus_core::prelude::*; + fn app(cx: Scope) -> Element { + let poll_tasks = use_coroutine(&cx, || async { + loop { + println!("polling tasks"); + } + }); + + poll_tasks.auto_start(true); + + todo!() + } +} From bd1535a0eda26b448b157d0ff5c8d5f2e10967ed Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 26 Jan 2022 02:29:42 -0500 Subject: [PATCH 02/11] feat: use_coroutine happily compiles --- packages/hooks/Cargo.toml | 6 +- packages/hooks/src/usecoroutine.rs | 98 ++++++++++++++++++++---------- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 375fef087..23af30627 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -12,4 +12,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../../packages/core", version ="^0.1.7"} +dioxus-core = { path = "../../packages/core", version = "^0.1.7" } + + +[dev-dependencies] +futures-util = { version = "0.3", default-features = false } diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 593371b23..5636b4031 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -1,6 +1,7 @@ +#![warn(clippy::pedantic)] + use bumpalo::boxed::Box as BumpBox; use dioxus_core::exports::bumpalo; -use dioxus_core::exports::futures_channel; use dioxus_core::{LazyNodes, ScopeState, TaskId}; use std::future::Future; use std::{cell::Cell, rc::Rc}; @@ -25,34 +26,35 @@ use std::{cell::Cell, rc::Rc}; pub fn use_coroutine<'a, F>( cx: &'a ScopeState, create_future: impl FnOnce() -> F + 'a, -) -> UseCoroutine +) -> UseCoroutine<'a, F> where F: Future + 'static, { let state = cx.use_hook(move |_| CoroutineInner { - _id: None, - running: Default::default(), + task_id: Cell::new(None), + running: std::rc::Rc::default(), + run_count: Cell::new(0), }); - let mut bump = None; - // as an optimization, we use the bump arena to allocate the callback instead of boxes // that way we don't always call the constructor, but it's still efficient - cx.render(LazyNodes::new(move |f| { - bump.replace(f.bump()); - f.static_text("") - })); - - let mut slot = Some(create_future); - // safety: bumpalo is limited in constructing unsized box types, so we have to do it through dynamic dispatch let boxed: BumpBox<'a, dyn FnMut() -> F + 'a> = unsafe { - BumpBox::from_raw(bump.unwrap().alloc(move || { - let inner = slot.take().unwrap(); + let mut bump = None; + cx.render(LazyNodes::new(move |f| { + bump.replace(f.bump()); + f.static_text("") + })); + let mut slot = Some(create_future); + let bump = bump.expect("bump is assigned during render"); + BumpBox::from_raw(bump.alloc(move || { + let inner = slot.take().expect("closure to not be called twice"); inner() })) }; + state.run_count.set(state.run_count.get() + 1); + UseCoroutine { inner: state, create_fut: Cell::new(Some(boxed)), @@ -62,7 +64,8 @@ where struct CoroutineInner { running: Rc>, - _id: Option, + task_id: Cell>, + run_count: Cell, } pub struct UseCoroutine<'a, F: Future + 'static> { @@ -73,35 +76,50 @@ pub struct UseCoroutine<'a, F: Future + 'static> { impl<'a, F: Future + 'static> UseCoroutine<'a, F> { pub fn auto_start(&self, start: bool) -> &Self { - todo!() + if start && self.inner.run_count.get() == 1 { + self.start(); + } + self + } + + pub fn is_running(&self) -> bool { + self.inner.running.get() } pub fn start(&self) { if !self.is_running() { if let Some(mut fut) = self.create_fut.take() { let fut = fut(); - self.cx.push_future(fut); + let ready_handle = self.inner.running.clone(); + + let task = self.cx.push_future(async move { + ready_handle.set(true); + fut.await; + ready_handle.set(false); + }); + + self.inner.task_id.set(Some(task)); } } } - pub fn is_running(&self) -> bool { - // self.inner.running.get() - false - } + // todo: wire these up, either into the task system or into the coroutine system itself + // we would have change how we poll the coroutine and how its awaken - pub fn resume(&self) { - // self.cx.push_task(fut) - } - - pub fn stop(&self) {} - - pub fn restart(&self) {} + // pub fn resume(&self) {} + // pub fn stop(&self) {} + // pub fn restart(&self) {} } -#[test] -fn it_works() { +#[cfg(test)] +mod tests { + #![allow(unused)] + + use super::*; + use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::prelude::*; + use futures_util::StreamExt; + fn app(cx: Scope) -> Element { let poll_tasks = use_coroutine(&cx, || async { loop { @@ -113,4 +131,22 @@ fn it_works() { todo!() } + + fn app_with_channel(cx: Scope) -> Element { + let (tx, mut rx) = unbounded(); + + let tx = cx.use_hook(|_| tx); + + let poll_tasks = use_coroutine(&cx, move || async move { + while let Some(msg) = rx.next().await { + println!("polling tasks: {}", msg); + } + }); + + poll_tasks.auto_start(true); + + tx.unbounded_send("asd").unwrap(); + + todo!() + } } From 86729d929d2e39fed46b338d8d9286246e302ba6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Feb 2022 00:37:46 -0500 Subject: [PATCH 03/11] wip: coroutine works basically --- packages/hooks/Cargo.toml | 2 + packages/hooks/src/usecoroutine.rs | 376 +++++++++++++++++++++++------ 2 files changed, 304 insertions(+), 74 deletions(-) diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 12b671d14..e97885743 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -13,6 +13,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../../packages/core", version = "^0.1.9" } +futures-channel = "0.3.21" +log = { version = "0.4", features = ["release_max_level_off"] } [dev-dependencies] diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 5636b4031..7be9377fe 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -1,13 +1,15 @@ #![warn(clippy::pedantic)] -use bumpalo::boxed::Box as BumpBox; use dioxus_core::exports::bumpalo; use dioxus_core::{LazyNodes, ScopeState, TaskId}; +use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use std::any::Any; use std::future::Future; use std::{cell::Cell, rc::Rc}; /// Maintain a handle over a future that can be paused, resumed, and canceled. /// +/// This is an upgraded form of use_future with lots of bells-and-whistles. /// /// /// @@ -23,82 +25,95 @@ use std::{cell::Cell, rc::Rc}; /// /// /// -pub fn use_coroutine<'a, F>( +/// +pub fn use_coroutine<'a, O: 'static, M: 'static>( cx: &'a ScopeState, - create_future: impl FnOnce() -> F + 'a, -) -> UseCoroutine<'a, F> -where - F: Future + 'static, -{ - let state = cx.use_hook(move |_| CoroutineInner { - task_id: Cell::new(None), - running: std::rc::Rc::default(), - run_count: Cell::new(0), +) -> UseCoroutineBuilder<'a, O, M> { + let inner = cx.use_hook(|_| { + // + UseCoroutine { + val: Cell::new(None), + rx: Cell::new(None), + tx: None, + } }); - // as an optimization, we use the bump arena to allocate the callback instead of boxes - // that way we don't always call the constructor, but it's still efficient - // safety: bumpalo is limited in constructing unsized box types, so we have to do it through dynamic dispatch - let boxed: BumpBox<'a, dyn FnMut() -> F + 'a> = unsafe { - let mut bump = None; - cx.render(LazyNodes::new(move |f| { - bump.replace(f.bump()); - f.static_text("") - })); - let mut slot = Some(create_future); - let bump = bump.expect("bump is assigned during render"); - BumpBox::from_raw(bump.alloc(move || { - let inner = slot.take().expect("closure to not be called twice"); - inner() - })) - }; - - state.run_count.set(state.run_count.get() + 1); - - UseCoroutine { - inner: state, - create_fut: Cell::new(Some(boxed)), - cx, - } + UseCoroutineBuilder { cx, inner } } -struct CoroutineInner { - running: Rc>, - task_id: Cell>, - run_count: Cell, -} - -pub struct UseCoroutine<'a, F: Future + 'static> { - create_fut: Cell F + 'a>>>, - inner: &'a CoroutineInner, +pub struct UseCoroutineBuilder<'a, O, M = ()> { cx: &'a ScopeState, + inner: &'a mut UseCoroutine, } -impl<'a, F: Future + 'static> UseCoroutine<'a, F> { - pub fn auto_start(&self, start: bool) -> &Self { - if start && self.inner.run_count.get() == 1 { - self.start(); - } +impl<'a, O: 'static, M> UseCoroutineBuilder<'a, O, M> { + // fn with_channel(self) -> UseCoroutineBuilder<'a, O, I> { + // UseCoroutineBuilder { + // cx: self.cx, + // inner: self.inner, + // } + // } + + fn build>(mut self, f: impl FnOnce() -> F) -> &'a UseCoroutine { + todo!() + } + fn build_channel>( + mut self, + f: impl FnOnce(UnboundedReceiver) -> F, + ) -> &'a UseCoroutine { + todo!() + } + + pub fn use_dep(mut self) -> Self { self } + /// Provide the channel to downstream consumers + pub fn provide_context(mut self) -> Self { + self + } + + pub fn auto_start(mut self, start: bool) -> Self { + // if start && self.inner.run_count.get() == 1 { + // self.start(); + // } + self + } +} + +pub struct UseCoroutine { + val: Cell>, + rx: Cell>>, + tx: Option>, +} + +impl UseCoroutine { pub fn is_running(&self) -> bool { - self.inner.running.get() + false + // self.inner.running.get() } pub fn start(&self) { - if !self.is_running() { - if let Some(mut fut) = self.create_fut.take() { - let fut = fut(); - let ready_handle = self.inner.running.clone(); + // if !self.is_running() { + // if let Some(mut fut) = self.create_fut.take() { + // let fut = fut(); + // let ready_handle = self.inner.running.clone(); - let task = self.cx.push_future(async move { - ready_handle.set(true); - fut.await; - ready_handle.set(false); - }); + // let task = self.cx.push_future(async move { + // ready_handle.set(true); + // fut.await; + // ready_handle.set(false); + // }); - self.inner.task_id.set(Some(task)); + // self.inner.task_id.set(Some(task)); + // } + // } + } + + pub fn send(&self, msg: M) { + if let Some(tx) = self.tx.clone() { + if tx.unbounded_send(msg).is_err() { + log::error!("Failed to send message"); } } } @@ -121,32 +136,245 @@ mod tests { use futures_util::StreamExt; fn app(cx: Scope) -> Element { - let poll_tasks = use_coroutine(&cx, || async { + let poll_tasks = use_coroutine(&cx).auto_start(false).build(|| async { loop { println!("polling tasks"); } }); - poll_tasks.auto_start(true); - todo!() } fn app_with_channel(cx: Scope) -> Element { - let (tx, mut rx) = unbounded(); + // let poll_tasks = use_coroutine(&cx).build_channel(|mut rx| async move { + // while let Some(msg) = rx.next().await { + // println!("polling tasks: {}", msg); + // } + // }); - let tx = cx.use_hook(|_| tx); + let poll_tasks = + use_coroutine(&cx).build_channel(|mut rx: UnboundedReceiver<()>| async move { + while let Some(msg) = rx.next().await { + println!("polling tasks: {:?}", msg); + } + }); - let poll_tasks = use_coroutine(&cx, move || async move { - while let Some(msg) = rx.next().await { - println!("polling tasks: {}", msg); - } - }); - - poll_tasks.auto_start(true); - - tx.unbounded_send("asd").unwrap(); + // poll_tasks.send(10); todo!() } } + +mod use2 { + #![allow(missing_docs)] + + use dioxus_core::{ScopeState, TaskId}; + use std::{ + any::Any, + cell::{Cell, RefCell}, + future::Future, + rc::Rc, + }; + + /// A future that resolves to a value. + /// + /// This runs the future only once - though the future may be regenerated + /// through the [`UseFuture::restart`] method. + /// + /// This is commonly used for components that cannot be rendered until some + /// asynchronous operation has completed. + /// + /// + /// + /// + /// + pub fn use_future<'a>( + // pub fn use_future<'a, T: 'static, F: Future + 'static>( + cx: &'a ScopeState, + ) -> &'a UseFuture<()> { + // let state = cx.use_hook(move |_| UseFuture { + // update: cx.schedule_update(), + // needs_regen: Cell::new(true), + // slot: Rc::new(Cell::new(None)), + // value: None, + // task: None, + // pending: true, + // dep_cont: Cell::new(0), + // deps: RefCell::new(Vec::new()), + // first_time: true, + // }); + + // if let Some(value) = state.slot.take() { + // state.value = Some(value); + // state.task = None; + // } + + // if state.needs_regen.get() { + // // We don't need regen anymore + // state.needs_regen.set(false); + // state.pending = false; + + // // Create the new future + // let fut = new_fut(); + + // // Clone in our cells + // let slot = state.slot.clone(); + // let updater = state.update.clone(); + + // state.task = Some(cx.push_future(async move { + // let res = fut.await; + // slot.set(Some(res)); + // updater(); + // })); + // } + + // state.first_time = false; + + // state + + // new_fut: impl FnOnce() -> F, + + todo!() + } + + pub enum FutureState<'a, T> { + Pending, + Complete(&'a T), + Regenerating(&'a T), // the old value + } + + pub struct UseFuture { + update: Rc, + needs_regen: Cell, + value: Option, + pending: bool, + slot: Rc>>, + task: Option, + deps: RefCell>>, + dep_cont: Cell, + first_time: bool, + } + + impl UseFuture { + pub fn restart(&self) { + self.needs_regen.set(true); + (self.update)(); + } + + // clears the value in the future slot without starting the future over + pub fn clear(&self) -> Option { + (self.update)(); + self.slot.replace(None) + } + + // Manually set the value in the future slot without starting the future over + pub fn set(&self, new_value: T) { + self.slot.set(Some(new_value)); + self.needs_regen.set(true); + (self.update)(); + } + + pub fn value(&self) -> Option<&T> { + self.value.as_ref() + } + + pub fn state(&self) -> FutureState { + // self.value.as_ref() + FutureState::Pending + } + + /// Add this value to the dependency list + /// + /// This is a hook and should be called during the initial hook process. + /// It should •not• be called in a conditional. + pub fn use_dep(&self, dependency: &F) -> &Self { + let count = self.dep_cont.get(); + let mut deps = self.deps.borrow_mut(); + + match deps.get_mut(count) { + Some(dep) => match dep.downcast_mut::() { + Some(saved_dep) => { + if dependency != saved_dep { + *saved_dep = dependency.clone(); + self.needs_regen.set(true); + } + } + None => { + if cfg!(debug_assertions) { + panic!("Tried to use a dependency for use_future outside of the use_future hook."); + } + } + }, + None => deps.push(Box::new(dependency.to_owned())), + } + + self + } + + pub fn restart_if(&self, f: impl FnOnce() -> bool) -> &Self { + self + } + + pub fn build(&self, new_fut: impl FnOnce() -> F) {} + } + + #[test] + fn test_use_future_deps() { + use dioxus_core::prelude::*; + + struct MyProps { + val: String, + name: i32, + } + + fn app(cx: Scope) -> Element { + let MyProps { val, name } = cx.props; + + // let val = use_c(&cx) + // .use_dep(val) + // .restart_if(|| false) + // .use_dep(name) + // .build(|(val, name)| async move {}); + + // async fn fetch_thing(name: String, num: i32) -> String { + // format!("{} {}", name, num) + // } + + // let val = use_future(&cx, || fetch_thing(val.clone(), *name)) + // .with_dep(val) + // .with_dep(name) + // .restart_if(|| false); + + None + } + } + + // pub struct CoroutineBuilder<'a, const PARAM: usize> { + // deps: Vec>, + // cx: &'a ScopeState, + // } + + // macro_rules! dep_impl { + // ($id1:literal to $id2:literal) => { + // impl<'a> CoroutineBuilder<'a, $id1> { + // pub fn use_dep( + // mut self, + // dep: &F, + // ) -> CoroutineBuilder<'a, $id2> { + // self.deps.push(Box::new(dep.clone())); + // unsafe { std::mem::transmute(self) } + // } + // } + // }; + // } + + // dep_impl!(0 to 1); + // dep_impl!(1 to 2); + // dep_impl!(2 to 3); + // dep_impl!(3 to 4); + // dep_impl!(4 to 5); + // dep_impl!(5 to 6); + // dep_impl!(6 to 7); + // dep_impl!(7 to 8); + // dep_impl!(8 to 9); +} From 9211e1fc7862ddb46ebbb8a6ddb26d5476f6d4b3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Feb 2022 15:39:47 -0500 Subject: [PATCH 04/11] feat: use future fully figured out --- examples/dog_app.rs | 38 +-- examples/suspense.rs | 6 +- examples/tasks.rs | 4 +- packages/core/src/scopes.rs | 2 +- packages/hooks/docs/old.md | 182 +++++++++++++ packages/hooks/src/usecoroutine.rs | 395 +++++++---------------------- packages/hooks/src/usefuture.rs | 202 +++++++++++++-- 7 files changed, 481 insertions(+), 348 deletions(-) create mode 100644 packages/hooks/docs/old.md diff --git a/examples/dog_app.rs b/examples/dog_app.rs index a30f3e5e8..ad983035e 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -16,7 +16,9 @@ struct ListBreeds { } fn app(cx: Scope) -> Element { - let breeds = use_future(&cx, || async move { + let (breed, set_breed) = use_state(&cx, || None); + + let breeds = use_future(&cx, (), |_| async move { reqwest::get("https://dog.ceo/api/breeds/list/all") .await .unwrap() @@ -24,13 +26,10 @@ fn app(cx: Scope) -> Element { .await }); - let (breed, set_breed) = use_state(&cx, || None); - match breeds.value() { Some(Ok(breeds)) => cx.render(rsx! { div { - h1 {"Select a dog breed!"} - + h1 { "Select a dog breed!" } div { display: "flex", ul { flex: "50%", breeds.message.keys().map(|breed| rsx!( @@ -51,34 +50,23 @@ fn app(cx: Scope) -> Element { } } }), - Some(Err(_e)) => cx.render(rsx! { - div { "Error fetching breeds" } - }), - None => cx.render(rsx! { - div { "Loading dogs..." } - }), + Some(Err(_e)) => cx.render(rsx! { div { "Error fetching breeds" } }), + None => cx.render(rsx! { div { "Loading dogs..." } }), } } +#[derive(serde::Deserialize, Debug)] +struct DogApi { + message: String, +} + #[inline_props] fn Breed(cx: Scope, breed: String) -> Element { - #[derive(serde::Deserialize, Debug)] - struct DogApi { - message: String, - } - - let endpoint = format!("https://dog.ceo/api/breed/{}/images/random", breed); - - let fut = use_future(&cx, || async move { + let fut = use_future(&cx, (breed,), |(breed,)| async move { + let endpoint = format!("https://dog.ceo/api/breed/{}/images/random", breed); reqwest::get(endpoint).await.unwrap().json::().await }); - let (name, set_name) = use_state(&cx, || breed.clone()); - if name != breed { - set_name(breed.clone()); - fut.restart(); - } - cx.render(match fut.value() { Some(Ok(resp)) => rsx! { button { diff --git a/examples/suspense.rs b/examples/suspense.rs index 5a9bf4fd8..ace5954f2 100644 --- a/examples/suspense.rs +++ b/examples/suspense.rs @@ -35,8 +35,8 @@ fn app(cx: Scope) -> Element { div { h1 {"Dogs are very important"} p { - "The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])" - "is a domesticated descendant of the wolf which is characterized by an upturning tail." + "The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])" + "is a domesticated descendant of the wolf which is characterized by an upturning tail." "The dog derived from an ancient, extinct wolf,[6][7] and the modern grey wolf is the" "dog's nearest living relative.[8] The dog was the first species to be domesticated,[9][8]" "by hunter–gatherers over 15,000 years ago,[7] before the development of agriculture.[1]" @@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element { /// Suspense is achieved my moving the future into only the component that /// actually renders the data. fn Doggo(cx: Scope) -> Element { - let fut = use_future(&cx, || async move { + let fut = use_future(&cx, (), |_| async move { reqwest::get("https://dog.ceo/api/breeds/image/random/") .await .unwrap() diff --git a/examples/tasks.rs b/examples/tasks.rs index 3c4d4cba3..22b61eab8 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -12,8 +12,8 @@ fn main() { fn app(cx: Scope) -> Element { let (count, set_count) = use_state(&cx, || 0); - use_future(&cx, move || { - let set_count = set_count.to_owned(); + use_future(&cx, (), move |_| { + let set_count = set_count.clone(); async move { loop { tokio::time::sleep(Duration::from_millis(1000)).await; diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 00c3024bf..7508a93d4 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -748,7 +748,7 @@ impl ScopeState { } // todo: attach some state to the future to know if we should poll it - pub fn remove_future(&self, id: TaskId) { + pub fn cancel_future(&self, id: TaskId) { self.tasks.remove_fut(id); } diff --git a/packages/hooks/docs/old.md b/packages/hooks/docs/old.md new file mode 100644 index 000000000..269cd90ed --- /dev/null +++ b/packages/hooks/docs/old.md @@ -0,0 +1,182 @@ + +mod use2 { + #![allow(missing_docs)] + + use dioxus_core::{ScopeState, TaskId}; + use std::{ + any::Any, + cell::{Cell, RefCell}, + future::Future, + rc::Rc, + }; + + /// A future that resolves to a value. + /// + /// This runs the future only once - though the future may be regenerated + /// through the [`UseFuture::restart`] method. + /// + /// This is commonly used for components that cannot be rendered until some + /// asynchronous operation has completed. + /// + /// + /// + /// + /// + pub fn use_future<'a>( + // pub fn use_future<'a, T: 'static, F: Future + 'static>( + cx: &'a ScopeState, + ) -> &'a UseFuture<()> { + // let state = cx.use_hook(move |_| UseFuture { + // update: cx.schedule_update(), + // needs_regen: Cell::new(true), + // slot: Rc::new(Cell::new(None)), + // value: None, + // task: None, + // pending: true, + // dep_cont: Cell::new(0), + // deps: RefCell::new(Vec::new()), + // first_time: true, + // }); + + // if let Some(value) = state.slot.take() { + // state.value = Some(value); + // state.task = None; + // } + + // if state.needs_regen.get() { + // // We don't need regen anymore + // state.needs_regen.set(false); + // state.pending = false; + + // // Create the new future + // let fut = new_fut(); + + // // Clone in our cells + // let slot = state.slot.clone(); + // let updater = state.update.clone(); + + // state.task = Some(cx.push_future(async move { + // let res = fut.await; + // slot.set(Some(res)); + // updater(); + // })); + // } + + // state.first_time = false; + + // state + + // new_fut: impl FnOnce() -> F, + + todo!() + } + + pub enum FutureState<'a, T> { + Pending, + Complete(&'a T), + Regenerating(&'a T), // the old value + } + + pub struct UseFuture { + update: Rc, + needs_regen: Cell, + value: Option, + slot: Rc>>, + task: Option, + pending: bool, + deps: RefCell>>, + dep_cont: Cell, + first_time: bool, + } + + impl UseFuture { + pub fn restart(&self) { + self.needs_regen.set(true); + (self.update)(); + } + + // clears the value in the future slot without starting the future over + pub fn clear(&self) -> Option { + (self.update)(); + self.slot.replace(None) + } + + // Manually set the value in the future slot without starting the future over + pub fn set(&self, new_value: T) { + self.slot.set(Some(new_value)); + self.needs_regen.set(true); + (self.update)(); + } + + pub fn value(&self) -> Option<&T> { + self.value.as_ref() + } + + pub fn state(&self) -> FutureState { + // self.value.as_ref() + FutureState::Pending + } + + pub fn build(&self, new_fut: impl FnOnce() -> F) {} + } + + #[test] + fn test_use_future_deps() { + use dioxus_core::prelude::*; + + struct MyProps { + val: String, + name: i32, + } + + fn app(cx: Scope) -> Element { + let MyProps { val, name } = cx.props; + + // let val = use_c(&cx) + // .use_dep(val) + // .restart_if(|| false) + // .use_dep(name) + // .build(|(val, name)| async move {}); + + // async fn fetch_thing(name: String, num: i32) -> String { + // format!("{} {}", name, num) + // } + + // let val = use_future(&cx, || fetch_thing(val.clone(), *name)) + // .with_dep(val) + // .with_dep(name) + // .restart_if(|| false); + + None + } + } + + // pub struct CoroutineBuilder<'a, const PARAM: usize> { + // deps: Vec>, + // cx: &'a ScopeState, + // } + + // macro_rules! dep_impl { + // ($id1:literal to $id2:literal) => { + // impl<'a> CoroutineBuilder<'a, $id1> { + // pub fn use_dep( + // mut self, + // dep: &F, + // ) -> CoroutineBuilder<'a, $id2> { + // self.deps.push(Box::new(dep.clone())); + // unsafe { std::mem::transmute(self) } + // } + // } + // }; + // } + + // dep_impl!(0 to 1); + // dep_impl!(1 to 2); + // dep_impl!(2 to 3); + // dep_impl!(3 to 4); + // dep_impl!(4 to 5); + // dep_impl!(5 to 6); + // dep_impl!(6 to 7); + // dep_impl!(7 to 8); + // dep_impl!(8 to 9); +} diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 7be9377fe..40a12443d 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -9,95 +9,82 @@ use std::{cell::Cell, rc::Rc}; /// Maintain a handle over a future that can be paused, resumed, and canceled. /// -/// This is an upgraded form of use_future with lots of bells-and-whistles. +/// This is an upgraded form of [`use_future`] with lots of bells-and-whistles. +/// +/// [`use_coroutine`] is well suited for long-running tasks and is very customizable. +/// +/// +/// ## Long running tasks /// /// /// +/// ## One-off tasks /// /// +/// ## Cancellation /// /// -/// -/// -/// -/// -/// -/// -/// -/// -/// -pub fn use_coroutine<'a, O: 'static, M: 'static>( - cx: &'a ScopeState, -) -> UseCoroutineBuilder<'a, O, M> { - let inner = cx.use_hook(|_| { +/// ## Global State +#[allow(clippy::mut_from_ref)] +pub fn use_coroutine(cx: &ScopeState) -> &mut UseCoroutine { + cx.use_hook(|_| { // UseCoroutine { val: Cell::new(None), rx: Cell::new(None), tx: None, + first_run: true, + deps: vec![], + dep_cnt: 0, + needs_regen: false, + auto_start: true, } - }); - - UseCoroutineBuilder { cx, inner } -} - -pub struct UseCoroutineBuilder<'a, O, M = ()> { - cx: &'a ScopeState, - inner: &'a mut UseCoroutine, -} - -impl<'a, O: 'static, M> UseCoroutineBuilder<'a, O, M> { - // fn with_channel(self) -> UseCoroutineBuilder<'a, O, I> { - // UseCoroutineBuilder { - // cx: self.cx, - // inner: self.inner, - // } - // } - - fn build>(mut self, f: impl FnOnce() -> F) -> &'a UseCoroutine { - todo!() - } - fn build_channel>( - mut self, - f: impl FnOnce(UnboundedReceiver) -> F, - ) -> &'a UseCoroutine { - todo!() - } - - pub fn use_dep(mut self) -> Self { - self - } - - /// Provide the channel to downstream consumers - pub fn provide_context(mut self) -> Self { - self - } - - pub fn auto_start(mut self, start: bool) -> Self { - // if start && self.inner.run_count.get() == 1 { - // self.start(); - // } - self - } + }) } pub struct UseCoroutine { val: Cell>, rx: Cell>>, tx: Option>, + first_run: bool, + deps: Vec>, + dep_cnt: usize, + needs_regen: bool, + auto_start: bool, +} + +pub enum FutureState<'a, T> { + Pending, + Complete(&'a T), + Regenerating(&'a T), // the old value +} + +impl UseCoroutine { + /// explicitly set the type of the channel used by the coroutine + fn with_channel(&mut self) -> &mut UseCoroutine { + if self.first_run { + // self.provide_context() + } + todo!() + } + + /// explicitly set the type of the channel used by the coroutine + fn with_channel_isolate(&mut self) -> &mut UseCoroutine { + todo!() + } } impl UseCoroutine { pub fn is_running(&self) -> bool { false - // self.inner.running.get() + // self.running.get() } pub fn start(&self) { // if !self.is_running() { // if let Some(mut fut) = self.create_fut.take() { // let fut = fut(); - // let ready_handle = self.inner.running.clone(); + // let ready_handle = self.running.clone(); // let task = self.cx.push_future(async move { // ready_handle.set(true); @@ -105,7 +92,7 @@ impl UseCoroutine { // ready_handle.set(false); // }); - // self.inner.task_id.set(Some(task)); + // self.task_id.set(Some(task)); // } // } } @@ -121,11 +108,55 @@ impl UseCoroutine { // todo: wire these up, either into the task system or into the coroutine system itself // we would have change how we poll the coroutine and how its awaken + fn build>(&mut self, f: impl FnOnce(UnboundedReceiver) -> F) -> &Self { + self.first_run = false; + if self.auto_start || self.needs_regen { + // + } + + self + } + + pub fn auto_start(mut self, start: bool) -> Self { + // if start && self.run_count.get() == 1 { + // self.start(); + // } + self + } + + /// Add this value to the dependency list + /// + /// This is a hook and should be called during the initial hook process. + /// It should •not• be called in a conditional. + pub fn with_dep(&mut self, dependency: &F) -> &mut Self { + if let Some(dep) = self.deps.get_mut(self.dep_cnt) { + if let Some(saved_dep) = dep.downcast_mut::() { + if dependency != saved_dep { + *saved_dep = dependency.clone(); + self.needs_regen = true; + } + }; + } else { + self.deps.push(Box::new(dependency.to_owned())); + self.needs_regen = true; + } + + self + } + + pub fn restart_if(&self, f: impl FnOnce() -> bool) -> &Self { + self + } + // pub fn resume(&self) {} // pub fn stop(&self) {} // pub fn restart(&self) {} } +pub struct CoroutineContext { + tx: UnboundedSender, +} + #[cfg(test)] mod tests { #![allow(unused)] @@ -135,246 +166,16 @@ mod tests { use dioxus_core::prelude::*; use futures_util::StreamExt; - fn app(cx: Scope) -> Element { - let poll_tasks = use_coroutine(&cx).auto_start(false).build(|| async { - loop { - println!("polling tasks"); - } - }); - - todo!() - } - - fn app_with_channel(cx: Scope) -> Element { - // let poll_tasks = use_coroutine(&cx).build_channel(|mut rx| async move { - // while let Some(msg) = rx.next().await { - // println!("polling tasks: {}", msg); - // } - // }); - - let poll_tasks = - use_coroutine(&cx).build_channel(|mut rx: UnboundedReceiver<()>| async move { + fn app(cx: Scope, name: String) -> Element { + let task = use_coroutine(&cx) + .with_dep(&name) + .with_channel::() + .build(|mut rx| async move { while let Some(msg) = rx.next().await { - println!("polling tasks: {:?}", msg); + println!("got message: {}", msg); } }); - // poll_tasks.send(10); - - todo!() + None } } - -mod use2 { - #![allow(missing_docs)] - - use dioxus_core::{ScopeState, TaskId}; - use std::{ - any::Any, - cell::{Cell, RefCell}, - future::Future, - rc::Rc, - }; - - /// A future that resolves to a value. - /// - /// This runs the future only once - though the future may be regenerated - /// through the [`UseFuture::restart`] method. - /// - /// This is commonly used for components that cannot be rendered until some - /// asynchronous operation has completed. - /// - /// - /// - /// - /// - pub fn use_future<'a>( - // pub fn use_future<'a, T: 'static, F: Future + 'static>( - cx: &'a ScopeState, - ) -> &'a UseFuture<()> { - // let state = cx.use_hook(move |_| UseFuture { - // update: cx.schedule_update(), - // needs_regen: Cell::new(true), - // slot: Rc::new(Cell::new(None)), - // value: None, - // task: None, - // pending: true, - // dep_cont: Cell::new(0), - // deps: RefCell::new(Vec::new()), - // first_time: true, - // }); - - // if let Some(value) = state.slot.take() { - // state.value = Some(value); - // state.task = None; - // } - - // if state.needs_regen.get() { - // // We don't need regen anymore - // state.needs_regen.set(false); - // state.pending = false; - - // // Create the new future - // let fut = new_fut(); - - // // Clone in our cells - // let slot = state.slot.clone(); - // let updater = state.update.clone(); - - // state.task = Some(cx.push_future(async move { - // let res = fut.await; - // slot.set(Some(res)); - // updater(); - // })); - // } - - // state.first_time = false; - - // state - - // new_fut: impl FnOnce() -> F, - - todo!() - } - - pub enum FutureState<'a, T> { - Pending, - Complete(&'a T), - Regenerating(&'a T), // the old value - } - - pub struct UseFuture { - update: Rc, - needs_regen: Cell, - value: Option, - pending: bool, - slot: Rc>>, - task: Option, - deps: RefCell>>, - dep_cont: Cell, - first_time: bool, - } - - impl UseFuture { - pub fn restart(&self) { - self.needs_regen.set(true); - (self.update)(); - } - - // clears the value in the future slot without starting the future over - pub fn clear(&self) -> Option { - (self.update)(); - self.slot.replace(None) - } - - // Manually set the value in the future slot without starting the future over - pub fn set(&self, new_value: T) { - self.slot.set(Some(new_value)); - self.needs_regen.set(true); - (self.update)(); - } - - pub fn value(&self) -> Option<&T> { - self.value.as_ref() - } - - pub fn state(&self) -> FutureState { - // self.value.as_ref() - FutureState::Pending - } - - /// Add this value to the dependency list - /// - /// This is a hook and should be called during the initial hook process. - /// It should •not• be called in a conditional. - pub fn use_dep(&self, dependency: &F) -> &Self { - let count = self.dep_cont.get(); - let mut deps = self.deps.borrow_mut(); - - match deps.get_mut(count) { - Some(dep) => match dep.downcast_mut::() { - Some(saved_dep) => { - if dependency != saved_dep { - *saved_dep = dependency.clone(); - self.needs_regen.set(true); - } - } - None => { - if cfg!(debug_assertions) { - panic!("Tried to use a dependency for use_future outside of the use_future hook."); - } - } - }, - None => deps.push(Box::new(dependency.to_owned())), - } - - self - } - - pub fn restart_if(&self, f: impl FnOnce() -> bool) -> &Self { - self - } - - pub fn build(&self, new_fut: impl FnOnce() -> F) {} - } - - #[test] - fn test_use_future_deps() { - use dioxus_core::prelude::*; - - struct MyProps { - val: String, - name: i32, - } - - fn app(cx: Scope) -> Element { - let MyProps { val, name } = cx.props; - - // let val = use_c(&cx) - // .use_dep(val) - // .restart_if(|| false) - // .use_dep(name) - // .build(|(val, name)| async move {}); - - // async fn fetch_thing(name: String, num: i32) -> String { - // format!("{} {}", name, num) - // } - - // let val = use_future(&cx, || fetch_thing(val.clone(), *name)) - // .with_dep(val) - // .with_dep(name) - // .restart_if(|| false); - - None - } - } - - // pub struct CoroutineBuilder<'a, const PARAM: usize> { - // deps: Vec>, - // cx: &'a ScopeState, - // } - - // macro_rules! dep_impl { - // ($id1:literal to $id2:literal) => { - // impl<'a> CoroutineBuilder<'a, $id1> { - // pub fn use_dep( - // mut self, - // dep: &F, - // ) -> CoroutineBuilder<'a, $id2> { - // self.deps.push(Box::new(dep.clone())); - // unsafe { std::mem::transmute(self) } - // } - // } - // }; - // } - - // dep_impl!(0 to 1); - // dep_impl!(1 to 2); - // dep_impl!(2 to 3); - // dep_impl!(3 to 4); - // dep_impl!(4 to 5); - // dep_impl!(5 to 6); - // dep_impl!(6 to 7); - // dep_impl!(7 to 8); - // dep_impl!(8 to 9); -} diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 58f350124..9db67aef2 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -1,42 +1,54 @@ use dioxus_core::{ScopeState, TaskId}; -use std::{cell::Cell, future::Future, rc::Rc}; +use std::{any::Any, cell::Cell, future::Future, rc::Rc}; -pub fn use_future<'a, T: 'static, F: Future + 'static>( +/// A hook that provides a future that will resolve to a value. +/// +/// Whenever the hooks dependencies change, the future will be re-evaluated. +/// If a future is pending when the dependencies change, the previous future +/// will be allowed to continue +/// +/// +/// - dependencies: a tuple of references to values that are PartialEq + Clone +pub fn use_future<'a, T: 'static, F: Future + 'static, D: UseFutureDep>( cx: &'a ScopeState, - new_fut: impl FnOnce() -> F, + dependencies: D, + future: impl FnOnce(D::Out) -> F, ) -> &'a UseFuture { - let state = cx.use_hook(move |_| { - // - UseFuture { - update: cx.schedule_update(), - needs_regen: Cell::new(true), - slot: Rc::new(Cell::new(None)), - value: None, - task: None, - } + let state = cx.use_hook(move |_| UseFuture { + update: cx.schedule_update(), + needs_regen: Cell::new(true), + slot: Rc::new(Cell::new(None)), + value: None, + task: Cell::new(None), + dependencies: Vec::new(), }); if let Some(value) = state.slot.take() { state.value = Some(value); - state.task = None; + state.task.set(None); } - if state.needs_regen.get() { + if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { // We don't need regen anymore state.needs_regen.set(false); // Create the new future - let fut = new_fut(); + let fut = future(dependencies.out()); // Clone in our cells let slot = state.slot.clone(); - let updater = state.update.clone(); + let schedule_update = state.update.clone(); - state.task = Some(cx.push_future(async move { + // Cancel the current future + if let Some(current) = state.task.take() { + cx.cancel_future(current); + } + + state.task.set(Some(cx.push_future(async move { let res = fut.await; slot.set(Some(res)); - updater(); - })); + schedule_update(); + }))); } state @@ -47,15 +59,33 @@ pub struct UseFuture { needs_regen: Cell, value: Option, slot: Rc>>, - task: Option, + task: Cell>, + dependencies: Vec>, +} + +pub enum UseFutureState<'a, T> { + Pending, + Complete(&'a T), + Reloading(&'a T), } impl UseFuture { + /// Restart the future with new dependencies. + /// + /// Will not cancel the previous future, but will ignore any values that it + /// generates. pub fn restart(&self) { self.needs_regen.set(true); (self.update)(); } + /// Forcefully cancel a future + pub fn cancel(&self, cx: &ScopeState) { + if let Some(task) = self.task.take() { + cx.cancel_future(task); + } + } + // clears the value in the future slot without starting the future over pub fn clear(&self) -> Option { (self.update)(); @@ -69,7 +99,139 @@ impl UseFuture { (self.update)(); } + /// Return any value, even old values if the future has not yet resolved. + /// + /// If the future has never completed, the returned value will be `None`. pub fn value(&self) -> Option<&T> { self.value.as_ref() } + + /// Get the ID of the future in Dioxus' internal scheduler + pub fn task(&self) -> Option { + self.task.get() + } + + /// Get the current stateof the future. + pub fn state(&self) -> UseFutureState { + match (&self.task.get(), &self.value) { + // If we have a task and an existing value, we're reloading + (Some(_), Some(val)) => UseFutureState::Reloading(val), + + // no task, but value - we're done + (None, Some(val)) => UseFutureState::Complete(val), + + // no task, no value - something's wrong? return pending + (None, None) => UseFutureState::Pending, + + // Task, no value - we're still pending + (Some(_), None) => UseFutureState::Pending, + } + } +} + +pub trait UseFutureDep: Sized + Clone { + type Out; + fn out(&self) -> Self::Out; + fn apply(self, state: &mut Vec>) -> bool; +} + +impl UseFutureDep for () { + type Out = (); + fn out(&self) -> Self::Out {} + fn apply(self, _state: &mut Vec>) -> bool { + false + } +} + +pub trait Dep: 'static + PartialEq + Clone {} +impl Dep for T where T: 'static + PartialEq + Clone {} + +macro_rules! impl_dep { + ( + $($el:ident=$name:ident,)* + ) => { + impl< $($el),* > UseFutureDep for ($(&$el,)*) + where + $( + $el: Dep + ),* + { + type Out = ($($el,)*); + + fn out(&self) -> Self::Out { + let ($($name,)*) = self; + ($((*$name).clone(),)*) + } + + #[allow(unused)] + fn apply(self, state: &mut Vec>) -> bool { + let ($($name,)*) = self; + let mut idx = 0; + let mut needs_regen = false; + + $( + match state.get_mut(idx).map(|f| f.downcast_mut::<$el>()).flatten() { + Some(val) => { + if *val != *$name { + *val = $name.clone(); + needs_regen = true; + } + } + None => { + state.push(Box::new($name.clone())); + needs_regen = true; + } + } + idx += 1; + )* + + needs_regen + } + } + }; +} + +impl_dep!(A = a,); +impl_dep!(A = a, B = b,); +impl_dep!(A = a, B = b, C = c,); +impl_dep!(A = a, B = b, C = c, D = d,); +impl_dep!(A = a, B = b, C = c, D = d, E = e,); +impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,); + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(unused)] + #[test] + fn test_use_future() { + use dioxus_core::prelude::*; + + struct MyProps { + a: String, + b: i32, + c: i32, + d: i32, + e: i32, + } + + fn app(cx: Scope) -> Element { + // should only ever run once + let fut = use_future(&cx, (), |_| async move { + // + }); + + // runs when a is changed + let fut = use_future(&cx, (&cx.props.a,), |(a,)| async move { + // + }); + + // runs when a or b is changed + let fut = use_future(&cx, (&cx.props.a, &cx.props.b), |(a, b)| async move { + // + }); + + None + } + } } From fbaf25dc6b381f0797fc4d38e6d14700739143de Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Feb 2022 23:34:18 -0500 Subject: [PATCH 05/11] feat: pair down usecoroutine and make it much happier --- packages/hooks/src/usecoroutine.rs | 263 +++++++++++++---------------- 1 file changed, 113 insertions(+), 150 deletions(-) diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 40a12443d..b4b2907ff 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -1,160 +1,102 @@ -#![warn(clippy::pedantic)] - -use dioxus_core::exports::bumpalo; -use dioxus_core::{LazyNodes, ScopeState, TaskId}; -use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; -use std::any::Any; +use dioxus_core::{ScopeState, TaskId}; +pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use std::future::Future; -use std::{cell::Cell, rc::Rc}; +use std::rc::Rc; /// Maintain a handle over a future that can be paused, resumed, and canceled. /// -/// This is an upgraded form of [`use_future`] with lots of bells-and-whistles. +/// This is an upgraded form of [`use_future`] with an integrated channel system. +/// Specifically, the coroutine generated here comes with an [`UnboundedChannel`] +/// built into it - saving you the hassle of building your own. /// -/// [`use_coroutine`] is well suited for long-running tasks and is very customizable. -/// -/// -/// ## Long running tasks -/// -/// -/// -/// ## One-off tasks -/// -/// -/// ## Cancellation +/// Addititionally, coroutines are automatically injected as shared contexts, so +/// downstream components can tap into a coroutine's channel and send messages +/// into a singular async event loop. /// +/// This makes it effective for apps that need to interact with an event loop or +/// some asynchronous code without thinking too hard about state. /// /// ## Global State -#[allow(clippy::mut_from_ref)] -pub fn use_coroutine(cx: &ScopeState) -> &mut UseCoroutine { +/// +/// Typically, writing apps that handle concurrency properly can be difficult, +/// so the intention of this hook is to make it easy to join and poll async tasks +/// concurrently in a centralized place. You'll find that you can have much better +/// control over your app's state if you centralize your async actions, even under +/// the same concurrent context. This makes it easier to prevent undeseriable +/// states in your UI while various async tasks are already running. +/// +/// This hook is especially powerful when combined with Fermi. We can store important +/// global data in a coroutine, and then access display-level values from the rest +/// of our app through atoms. +/// +/// ## UseCallback instead +/// +/// However, you must plan out your own concurrency and synchronization. If you +/// don't care about actions in your app being synchronized, you can use [`use_callback`] +/// hook to spawn multiple tasks and run them concurrently. +/// +/// ## Example +/// +/// ```rust, ignore +/// enum Action { +/// Start, +/// Stop, +/// } +/// +/// let chat_client = use_coroutine(&cx, |rx: UnboundedReceiver| async move { +/// while let Some(action) = rx.next().await { +/// match action { +/// Action::Start => {} +/// Action::Stop => {}, +/// } +/// } +/// }); +/// +/// +/// cx.render(rsx!{ +/// button { +/// onclick: move |_| chat_client.send(Action::Start), +/// "Start Chat Service" +/// } +/// }) +/// ``` +pub fn use_coroutine(cx: &ScopeState, init: G) -> &CoroutineHandle +where + M: 'static, + G: FnOnce(UnboundedReceiver) -> F, + F: Future + 'static, +{ cx.use_hook(|_| { - // - UseCoroutine { - val: Cell::new(None), - rx: Cell::new(None), - tx: None, - first_run: true, - deps: vec![], - dep_cnt: 0, - needs_regen: false, - auto_start: true, - } + let (tx, rx) = futures_channel::mpsc::unbounded(); + let task = cx.push_future(init(rx)); + cx.provide_context(CoroutineHandle { tx, task }) }) } -pub struct UseCoroutine { - val: Cell>, - rx: Cell>>, - tx: Option>, - first_run: bool, - deps: Vec>, - dep_cnt: usize, - needs_regen: bool, - auto_start: bool, +/// Get a handle to a coroutine higher in the tree +/// +/// See the docs for [`use_coroutine`] for more details. +pub fn use_coroutine_handle(cx: &ScopeState) -> Option<&Rc>> { + cx.use_hook(|_| cx.consume_context::>()) + .as_ref() } -pub enum FutureState<'a, T> { - Pending, - Complete(&'a T), - Regenerating(&'a T), // the old value -} - -impl UseCoroutine { - /// explicitly set the type of the channel used by the coroutine - fn with_channel(&mut self) -> &mut UseCoroutine { - if self.first_run { - // self.provide_context() - } - todo!() - } - - /// explicitly set the type of the channel used by the coroutine - fn with_channel_isolate(&mut self) -> &mut UseCoroutine { - todo!() - } -} - -impl UseCoroutine { - pub fn is_running(&self) -> bool { - false - // self.running.get() - } - - pub fn start(&self) { - // if !self.is_running() { - // if let Some(mut fut) = self.create_fut.take() { - // let fut = fut(); - // let ready_handle = self.running.clone(); - - // let task = self.cx.push_future(async move { - // ready_handle.set(true); - // fut.await; - // ready_handle.set(false); - // }); - - // self.task_id.set(Some(task)); - // } - // } - } - - pub fn send(&self, msg: M) { - if let Some(tx) = self.tx.clone() { - if tx.unbounded_send(msg).is_err() { - log::error!("Failed to send message"); - } - } - } - - // todo: wire these up, either into the task system or into the coroutine system itself - // we would have change how we poll the coroutine and how its awaken - - fn build>(&mut self, f: impl FnOnce(UnboundedReceiver) -> F) -> &Self { - self.first_run = false; - if self.auto_start || self.needs_regen { - // - } - - self - } - - pub fn auto_start(mut self, start: bool) -> Self { - // if start && self.run_count.get() == 1 { - // self.start(); - // } - self - } - - /// Add this value to the dependency list - /// - /// This is a hook and should be called during the initial hook process. - /// It should •not• be called in a conditional. - pub fn with_dep(&mut self, dependency: &F) -> &mut Self { - if let Some(dep) = self.deps.get_mut(self.dep_cnt) { - if let Some(saved_dep) = dep.downcast_mut::() { - if dependency != saved_dep { - *saved_dep = dependency.clone(); - self.needs_regen = true; - } - }; - } else { - self.deps.push(Box::new(dependency.to_owned())); - self.needs_regen = true; - } - - self - } - - pub fn restart_if(&self, f: impl FnOnce() -> bool) -> &Self { - self - } - - // pub fn resume(&self) {} - // pub fn stop(&self) {} - // pub fn restart(&self) {} -} - -pub struct CoroutineContext { +pub struct CoroutineHandle { tx: UnboundedSender, + task: TaskId, +} + +impl CoroutineHandle { + /// Get the ID of this coroutine + #[must_use] + pub fn task_id(&self) -> TaskId { + self.task + } + + /// Send a message to the coroutine + pub fn send(&self, msg: T) { + let _ = self.tx.unbounded_send(msg); + } } #[cfg(test)] @@ -167,15 +109,36 @@ mod tests { use futures_util::StreamExt; fn app(cx: Scope, name: String) -> Element { - let task = use_coroutine(&cx) - .with_dep(&name) - .with_channel::() - .build(|mut rx| async move { - while let Some(msg) = rx.next().await { - println!("got message: {}", msg); - } - }); + let task = use_coroutine(&cx, |mut rx: UnboundedReceiver| async move { + while let Some(msg) = rx.next().await { + println!("got message: {}", msg); + } + }); + + let task2 = use_coroutine(&cx, view_task); + + let task3 = use_coroutine(&cx, |rx| complex_task(rx, 10)); None } + + async fn view_task(mut rx: UnboundedReceiver) { + while let Some(msg) = rx.next().await { + println!("got message: {}", msg); + } + } + + enum Actions { + CloseAll, + OpenAll, + } + + async fn complex_task(mut rx: UnboundedReceiver, name: i32) { + while let Some(msg) = rx.next().await { + match msg { + Actions::CloseAll => todo!(), + Actions::OpenAll => todo!(), + } + } + } } From 3837899a7e7ee9704a32a35f3e5e3a4ffcef9aab Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Feb 2022 23:35:12 -0500 Subject: [PATCH 06/11] fix: remove clutter --- packages/hooks/docs/old.md | 182 ------------------------------------- 1 file changed, 182 deletions(-) delete mode 100644 packages/hooks/docs/old.md diff --git a/packages/hooks/docs/old.md b/packages/hooks/docs/old.md deleted file mode 100644 index 269cd90ed..000000000 --- a/packages/hooks/docs/old.md +++ /dev/null @@ -1,182 +0,0 @@ - -mod use2 { - #![allow(missing_docs)] - - use dioxus_core::{ScopeState, TaskId}; - use std::{ - any::Any, - cell::{Cell, RefCell}, - future::Future, - rc::Rc, - }; - - /// A future that resolves to a value. - /// - /// This runs the future only once - though the future may be regenerated - /// through the [`UseFuture::restart`] method. - /// - /// This is commonly used for components that cannot be rendered until some - /// asynchronous operation has completed. - /// - /// - /// - /// - /// - pub fn use_future<'a>( - // pub fn use_future<'a, T: 'static, F: Future + 'static>( - cx: &'a ScopeState, - ) -> &'a UseFuture<()> { - // let state = cx.use_hook(move |_| UseFuture { - // update: cx.schedule_update(), - // needs_regen: Cell::new(true), - // slot: Rc::new(Cell::new(None)), - // value: None, - // task: None, - // pending: true, - // dep_cont: Cell::new(0), - // deps: RefCell::new(Vec::new()), - // first_time: true, - // }); - - // if let Some(value) = state.slot.take() { - // state.value = Some(value); - // state.task = None; - // } - - // if state.needs_regen.get() { - // // We don't need regen anymore - // state.needs_regen.set(false); - // state.pending = false; - - // // Create the new future - // let fut = new_fut(); - - // // Clone in our cells - // let slot = state.slot.clone(); - // let updater = state.update.clone(); - - // state.task = Some(cx.push_future(async move { - // let res = fut.await; - // slot.set(Some(res)); - // updater(); - // })); - // } - - // state.first_time = false; - - // state - - // new_fut: impl FnOnce() -> F, - - todo!() - } - - pub enum FutureState<'a, T> { - Pending, - Complete(&'a T), - Regenerating(&'a T), // the old value - } - - pub struct UseFuture { - update: Rc, - needs_regen: Cell, - value: Option, - slot: Rc>>, - task: Option, - pending: bool, - deps: RefCell>>, - dep_cont: Cell, - first_time: bool, - } - - impl UseFuture { - pub fn restart(&self) { - self.needs_regen.set(true); - (self.update)(); - } - - // clears the value in the future slot without starting the future over - pub fn clear(&self) -> Option { - (self.update)(); - self.slot.replace(None) - } - - // Manually set the value in the future slot without starting the future over - pub fn set(&self, new_value: T) { - self.slot.set(Some(new_value)); - self.needs_regen.set(true); - (self.update)(); - } - - pub fn value(&self) -> Option<&T> { - self.value.as_ref() - } - - pub fn state(&self) -> FutureState { - // self.value.as_ref() - FutureState::Pending - } - - pub fn build(&self, new_fut: impl FnOnce() -> F) {} - } - - #[test] - fn test_use_future_deps() { - use dioxus_core::prelude::*; - - struct MyProps { - val: String, - name: i32, - } - - fn app(cx: Scope) -> Element { - let MyProps { val, name } = cx.props; - - // let val = use_c(&cx) - // .use_dep(val) - // .restart_if(|| false) - // .use_dep(name) - // .build(|(val, name)| async move {}); - - // async fn fetch_thing(name: String, num: i32) -> String { - // format!("{} {}", name, num) - // } - - // let val = use_future(&cx, || fetch_thing(val.clone(), *name)) - // .with_dep(val) - // .with_dep(name) - // .restart_if(|| false); - - None - } - } - - // pub struct CoroutineBuilder<'a, const PARAM: usize> { - // deps: Vec>, - // cx: &'a ScopeState, - // } - - // macro_rules! dep_impl { - // ($id1:literal to $id2:literal) => { - // impl<'a> CoroutineBuilder<'a, $id1> { - // pub fn use_dep( - // mut self, - // dep: &F, - // ) -> CoroutineBuilder<'a, $id2> { - // self.deps.push(Box::new(dep.clone())); - // unsafe { std::mem::transmute(self) } - // } - // } - // }; - // } - - // dep_impl!(0 to 1); - // dep_impl!(1 to 2); - // dep_impl!(2 to 3); - // dep_impl!(3 to 4); - // dep_impl!(4 to 5); - // dep_impl!(5 to 6); - // dep_impl!(6 to 7); - // dep_impl!(7 to 8); - // dep_impl!(8 to 9); -} From 7a5c403e381ab7353e44257bc790dc68ed01a8ed Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 23 Feb 2022 09:47:53 -0500 Subject: [PATCH 07/11] wip: add use_callback --- packages/hooks/src/lib.rs | 4 +- packages/hooks/src/usecallback.rs | 68 +++++++++++++++++++++++++++++++ packages/hooks/src/usefuture.rs | 22 ++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 packages/hooks/src/usecallback.rs diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index cb4afd297..598442e19 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -13,8 +13,8 @@ pub use usecoroutine::*; mod usefuture; pub use usefuture::*; -mod usesuspense; -pub use usesuspense::*; +mod usecallback; +pub use usecallback::*; #[macro_export] /// A helper macro for using hooks in async environements. diff --git a/packages/hooks/src/usecallback.rs b/packages/hooks/src/usecallback.rs new file mode 100644 index 000000000..1affd323f --- /dev/null +++ b/packages/hooks/src/usecallback.rs @@ -0,0 +1,68 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::use_state; +use crate::UseFutureDep; +use dioxus_core::{ScopeState, UiEvent}; +use std::future::Future; + +pub fn use_callback + 'static>( + cx: &ScopeState, + // + g: G, + f: impl FnMut(I, G::Out) -> F, +) -> &UseCallback +where + G::Out: 'static, + I: 'static, +{ + cx.use_hook(|_| { + // + UseCallback { + f: todo!(), + f2: Box::new(|f| {}), + } + }) +} + +pub struct UseCallback { + f: Rc>>>, + f2: Box, // f: Rc>>>, +} + +impl std::ops::Deref for UseCallback { + type Target = dyn Fn(I); + + fn deref(&self) -> &Self::Target { + &self.f2 + } +} + +trait MyThing {} +impl MyThing for Box {} +impl MyThing for Box {} + +#[test] +fn demo() { + use dioxus_core::prelude::*; + fn example(cx: Scope) -> Element { + let (name, _) = use_state(&cx, || 0); + let (age, _) = use_state(&cx, || 0); + + let onsubmit = use_callback(&cx, (name,), |event: (), (name,)| async move { + // + }); + + let onsubmit = use_callback(&cx, (name,), my_callback); + async fn my_callback(event: UiEvent<()>, name: (i32,)) { + // + } + + let onsubmit = use_callback(&cx, name, my_callback2); + + async fn my_callback2(event: UiEvent<()>, name: i32) { + // + } + + None + } +} diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 9db67aef2..0c066363e 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -146,6 +146,28 @@ impl UseFutureDep for () { pub trait Dep: 'static + PartialEq + Clone {} impl Dep for T where T: 'static + PartialEq + Clone {} +impl UseFutureDep for &A { + type Out = A; + fn out(&self) -> Self::Out { + (*self).clone() + } + fn apply(self, state: &mut Vec>) -> bool { + match state.get_mut(0).map(|f| f.downcast_mut::()).flatten() { + Some(val) => { + if *val != *self { + *val = self.clone(); + return true; + } + } + None => { + state.push(Box::new(self.clone())); + return true; + } + } + false + } +} + macro_rules! impl_dep { ( $($el:ident=$name:ident,)* From 64edc6e1b117ec87e3bad120a50332b151f11f4e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 26 Feb 2022 03:12:22 -0500 Subject: [PATCH 08/11] feat: useeffect implemented --- packages/hooks/src/lib.rs | 3 ++ packages/hooks/src/usecallback.rs | 8 +-- packages/hooks/src/useeffect.rs | 81 +++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 packages/hooks/src/useeffect.rs diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 598442e19..e63353a4e 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -16,6 +16,9 @@ pub use usefuture::*; mod usecallback; pub use usecallback::*; +mod useeffect; +pub use useeffect::*; + #[macro_export] /// A helper macro for using hooks in async environements. /// diff --git a/packages/hooks/src/usecallback.rs b/packages/hooks/src/usecallback.rs index 1affd323f..94e74cf1c 100644 --- a/packages/hooks/src/usecallback.rs +++ b/packages/hooks/src/usecallback.rs @@ -7,7 +7,6 @@ use std::future::Future; pub fn use_callback + 'static>( cx: &ScopeState, - // g: G, f: impl FnMut(I, G::Out) -> F, ) -> &UseCallback @@ -26,7 +25,7 @@ where pub struct UseCallback { f: Rc>>>, - f2: Box, // f: Rc>>>, + f2: Box, } impl std::ops::Deref for UseCallback { @@ -37,10 +36,6 @@ impl std::ops::Deref for UseCallback { } } -trait MyThing {} -impl MyThing for Box {} -impl MyThing for Box {} - #[test] fn demo() { use dioxus_core::prelude::*; @@ -53,6 +48,7 @@ fn demo() { }); let onsubmit = use_callback(&cx, (name,), my_callback); + async fn my_callback(event: UiEvent<()>, name: (i32,)) { // } diff --git a/packages/hooks/src/useeffect.rs b/packages/hooks/src/useeffect.rs new file mode 100644 index 000000000..b6f6a01eb --- /dev/null +++ b/packages/hooks/src/useeffect.rs @@ -0,0 +1,81 @@ +use dioxus_core::{ScopeState, TaskId}; +use std::{any::Any, cell::Cell, future::Future}; + +use crate::UseFutureDep; + +/// A hook that provides a future that executes after the hooks have been applied +/// +/// Whenever the hooks dependencies change, the future will be re-evaluated. +/// If a future is pending when the dependencies change, the previous future +/// will be allowed to continue +/// +/// - dependencies: a tuple of references to values that are PartialEq + Clone +pub fn use_effect<'a, T: 'static, F: Future + 'static, D: UseFutureDep>( + cx: &'a ScopeState, + dependencies: D, + future: impl FnOnce(D::Out) -> F, +) -> &'a UseEffect { + let state = cx.use_hook(move |_| UseEffect { + needs_regen: Cell::new(true), + task: Cell::new(None), + dependencies: Vec::new(), + }); + + if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { + // We don't need regen anymore + state.needs_regen.set(false); + + // Create the new future + let fut = future(dependencies.out()); + + state.task.set(Some(cx.push_future(async move { + fut.await; + }))); + } + + state +} + +pub struct UseEffect { + needs_regen: Cell, + task: Cell>, + dependencies: Vec>, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(unused)] + #[test] + fn test_use_future() { + use dioxus_core::prelude::*; + + struct MyProps { + a: String, + b: i32, + c: i32, + d: i32, + e: i32, + } + + fn app(cx: Scope) -> Element { + // should only ever run once + use_effect(&cx, (), |_| async move { + // + }); + + // runs when a is changed + use_effect(&cx, (&cx.props.a,), |(a,)| async move { + // + }); + + // runs when a or b is changed + use_effect(&cx, (&cx.props.a, &cx.props.b), |(a, b)| async move { + // + }); + + None + } + } +} From 4ad4b18e4bcfc57aa487f11b45c38d516c93b2d7 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 26 Feb 2022 17:31:35 -0500 Subject: [PATCH 09/11] feat: useeffect --- packages/hooks/src/useeffect.rs | 43 +++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/packages/hooks/src/useeffect.rs b/packages/hooks/src/useeffect.rs index b6f6a01eb..09366fc35 100644 --- a/packages/hooks/src/useeffect.rs +++ b/packages/hooks/src/useeffect.rs @@ -10,20 +10,39 @@ use crate::UseFutureDep; /// will be allowed to continue /// /// - dependencies: a tuple of references to values that are PartialEq + Clone -pub fn use_effect<'a, T: 'static, F: Future + 'static, D: UseFutureDep>( - cx: &'a ScopeState, - dependencies: D, - future: impl FnOnce(D::Out) -> F, -) -> &'a UseEffect { +/// +/// ## Examples +/// +/// ```rust, ignore +/// +/// #[inline_props] +/// fn app(cx: Scope, name: &str) -> Element { +/// use_effect(&cx, (name,), |(name,)| async move { +/// set_title(name); +/// })) +/// } +/// ``` +pub fn use_effect(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> F) +where + T: 'static, + F: Future + 'static, + D: UseFutureDep, +{ + struct UseEffect { + needs_regen: bool, + task: Cell>, + dependencies: Vec>, + } + let state = cx.use_hook(move |_| UseEffect { - needs_regen: Cell::new(true), + needs_regen: true, task: Cell::new(None), dependencies: Vec::new(), }); - if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { + if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen { // We don't need regen anymore - state.needs_regen.set(false); + state.needs_regen = false; // Create the new future let fut = future(dependencies.out()); @@ -32,14 +51,6 @@ pub fn use_effect<'a, T: 'static, F: Future + 'static, D: UseFutureD fut.await; }))); } - - state -} - -pub struct UseEffect { - needs_regen: Cell, - task: Cell>, - dependencies: Vec>, } #[cfg(test)] From 74f40da997af0c2c5902d7000f56465fd036e62c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 26 Feb 2022 17:36:33 -0500 Subject: [PATCH 10/11] clippy: remove unused module --- packages/hooks/src/lib.rs | 3 -- packages/hooks/src/usecallback.rs | 64 ------------------------------- 2 files changed, 67 deletions(-) delete mode 100644 packages/hooks/src/usecallback.rs diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index e63353a4e..823a4fc81 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -13,9 +13,6 @@ pub use usecoroutine::*; mod usefuture; pub use usefuture::*; -mod usecallback; -pub use usecallback::*; - mod useeffect; pub use useeffect::*; diff --git a/packages/hooks/src/usecallback.rs b/packages/hooks/src/usecallback.rs deleted file mode 100644 index 94e74cf1c..000000000 --- a/packages/hooks/src/usecallback.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use crate::use_state; -use crate::UseFutureDep; -use dioxus_core::{ScopeState, UiEvent}; -use std::future::Future; - -pub fn use_callback + 'static>( - cx: &ScopeState, - g: G, - f: impl FnMut(I, G::Out) -> F, -) -> &UseCallback -where - G::Out: 'static, - I: 'static, -{ - cx.use_hook(|_| { - // - UseCallback { - f: todo!(), - f2: Box::new(|f| {}), - } - }) -} - -pub struct UseCallback { - f: Rc>>>, - f2: Box, -} - -impl std::ops::Deref for UseCallback { - type Target = dyn Fn(I); - - fn deref(&self) -> &Self::Target { - &self.f2 - } -} - -#[test] -fn demo() { - use dioxus_core::prelude::*; - fn example(cx: Scope) -> Element { - let (name, _) = use_state(&cx, || 0); - let (age, _) = use_state(&cx, || 0); - - let onsubmit = use_callback(&cx, (name,), |event: (), (name,)| async move { - // - }); - - let onsubmit = use_callback(&cx, (name,), my_callback); - - async fn my_callback(event: UiEvent<()>, name: (i32,)) { - // - } - - let onsubmit = use_callback(&cx, name, my_callback2); - - async fn my_callback2(event: UiEvent<()>, name: i32) { - // - } - - None - } -} From d07e145df66201c588b64f9c224b63bc007bde59 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 26 Feb 2022 17:39:25 -0500 Subject: [PATCH 11/11] clippy: update for recent update --- packages/hooks/src/usefuture.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 1af254843..c7bdd819b 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -152,7 +152,7 @@ impl UseFutureDep for &A { (*self).clone() } fn apply(self, state: &mut Vec>) -> bool { - match state.get_mut(0).map(|f| f.downcast_mut::()).flatten() { + match state.get_mut(0).and_then(|f| f.downcast_mut::()) { Some(val) => { if *val != *self { *val = self.clone();