From 9211e1fc7862ddb46ebbb8a6ddb26d5476f6d4b3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Feb 2022 15:39:47 -0500 Subject: [PATCH] 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 + } + } }