From 028f499bf624b9d81963bf09feb6c75342c6d663 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 21 Jan 2024 12:46:19 -0800 Subject: [PATCH] Fix generational box in release mode --- examples/counter.rs | 2 +- examples/crm.rs | 18 ++-- examples/dog_app.rs | 4 +- packages/desktop/src/launch.rs | 2 +- packages/generational-box/src/lib.rs | 7 +- packages/generational-box/src/sync.rs | 25 +++-- packages/generational-box/src/unsync.rs | 24 +++-- packages/hooks/src/lib.rs | 3 + packages/hooks/src/use_future.rs | 4 - packages/hooks/src/use_resource.rs | 127 ++++++++++++++++++++++++ 10 files changed, 182 insertions(+), 34 deletions(-) create mode 100644 packages/hooks/src/use_resource.rs diff --git a/examples/counter.rs b/examples/counter.rs index 81ef687e2..a25115b22 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -29,7 +29,7 @@ fn app() -> Element { } #[component] -fn Child(i: usize, counters: Signal>) -> Element { +fn Child(counters: Signal>, i: usize) -> Element { rsx! { li { button { onclick: move |_| counters.write()[i] -= 1, "-1" } diff --git a/examples/crm.rs b/examples/crm.rs index 5f518a36c..564b7e0c9 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -36,21 +36,21 @@ struct Client { #[derive(Routable, Clone)] enum Route { #[route("/")] - ClientList {}, + ClientList, #[route("/new")] - ClientAdd {}, + ClientAdd, #[route("/settings")] - Settings {}, + Settings, } #[component] fn ClientList() -> Element { rsx! { h2 { "List of Clients" } - Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" } - Link { to: Route::Settings {}, class: "pure-button", "Settings" } + Link { to: Route::ClientAdd, class: "pure-button pure-button-primary", "Add Client" } + Link { to: Route::Settings, class: "pure-button", "Settings" } for client in CLIENTS.read().iter() { div { class: "client", style: "margin-bottom: 50px", p { "Name: {client.first_name} {client.last_name}" } @@ -75,7 +75,7 @@ fn ClientAdd() -> Element { }); // And then navigate back to the client list - dioxus::router::router().push(Route::ClientList {}); + dioxus::router::router().push(Route::ClientList); }; rsx! { @@ -121,7 +121,7 @@ fn ClientAdd() -> Element { div { class: "pure-controls", button { r#type: "submit", class: "pure-button pure-button-primary", "Save" } - Link { to: Route::ClientList {}, class: "pure-button pure-button-primary red", "Cancel" } + Link { to: Route::ClientList, class: "pure-button pure-button-primary red", "Cancel" } } } } @@ -136,10 +136,10 @@ fn Settings() -> Element { class: "pure-button pure-button-primary red", onclick: move |_| { CLIENTS.write().clear(); - dioxus::router::router().push(Route::ClientList {}); + dioxus::router::router().push(Route::ClientList); }, "Remove all Clients" } - Link { to: Route::ClientList {}, class: "pure-button", "Go back" } + Link { to: Route::ClientList, class: "pure-button", "Go back" } } } diff --git a/examples/dog_app.rs b/examples/dog_app.rs index b98cb0f3b..21edbcb93 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -7,7 +7,7 @@ fn main() { fn app() -> Element { let mut breed = use_signal(|| "deerhound".to_string()); - let mut breed_list = use_future(|| async move { + let breed_list = use_resource(|| async move { let list = reqwest::get("https://dog.ceo/api/breeds/list/all") .await .unwrap() @@ -44,7 +44,7 @@ fn app() -> Element { #[component] fn BreedPic(breed: Signal) -> Element { - let fut = use_future(|| async move { + let fut = use_resource(|| async move { reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random")) .await .unwrap() diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index cd32a7a94..b5602d10e 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -30,7 +30,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf EventData::Poll => app.poll_vdom(id), EventData::NewWindow => app.handle_new_window(), EventData::CloseWindow => app.handle_close_msg(id), - #[cfg(feature = "hot-reload")] + #[cfg(all(feature = "hot-reload", debug_assertions))] EventData::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg), EventData::Ipc(msg) => match msg.method() { IpcMethod::FileDialog => app.handle_file_dialog_msg(msg, id), diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 8009a1e9e..0ef80fce4 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -244,6 +244,7 @@ impl> GenerationalBox { ); if result.is_ok() { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] self.raw .0 .borrow @@ -280,7 +281,10 @@ impl> GenerationalBox { ); if result.is_ok() { - *self.raw.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller()); + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + { + *self.raw.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller()); + } } result @@ -419,6 +423,7 @@ struct MemoryLocationBorrowInfo { pub(crate) borrowed_mut_at: parking_lot::RwLock>>, } +#[cfg(any(debug_assertions, feature = "debug_ownership"))] impl MemoryLocationBorrowInfo { fn borrow_mut_error(&self) -> BorrowMutError { if let Some(borrowed_mut_at) = self.borrowed_mut_at.read().as_ref() { diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 7d940278f..5a7a8a625 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -89,10 +89,15 @@ impl Storage for SyncStorage { #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefBorrowInfo, ) -> Result { - let read = self - .0 - .try_read() - .ok_or_else(|| at.borrowed_from.borrow_error())?; + let read = self.0.try_read(); + // .ok_or_else(|| at.borrowed_from.borrow_error())?; + + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + let read = read.ok_or_else(|| at.borrowed_from.borrow_error())?; + + #[cfg(not(any(debug_assertions, feature = "debug_ownership")))] + let read = read.unwrap(); + RwLockReadGuard::try_map(read, |any| any.as_ref()?.downcast_ref()) .map_err(|_| { error::BorrowError::Dropped(ValueDroppedError { @@ -116,10 +121,14 @@ impl Storage for SyncStorage { #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefMutBorrowInfo, ) -> Result { - let write = self - .0 - .try_write() - .ok_or_else(|| at.borrowed_from.borrow_mut_error())?; + let write = self.0.try_write(); + + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + let write = write.ok_or_else(|| at.borrowed_from.borrow_mut_error())?; + + #[cfg(not(any(debug_assertions, feature = "debug_ownership")))] + let write = write.unwrap(); + RwLockWriteGuard::try_map(write, |any| any.as_mut()?.downcast_mut()) .map_err(|_| { error::BorrowMutError::Dropped(ValueDroppedError { diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index d941dd00b..d5424c9f1 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -54,10 +54,14 @@ impl Storage for UnsyncStorage { #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefBorrowInfo, ) -> Result { - let borrow = self - .0 - .try_borrow() - .map_err(|_| at.borrowed_from.borrow_error())?; + let borrow = self.0.try_borrow(); + + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + let borrow = borrow.map_err(|_| at.borrowed_from.borrow_error())?; + + #[cfg(not(any(debug_assertions, feature = "debug_ownership")))] + let borrow = borrow.unwrap(); + Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref()) .map_err(|_| { crate::error::BorrowError::Dropped(crate::error::ValueDroppedError { @@ -81,10 +85,14 @@ impl Storage for UnsyncStorage { #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: crate::GenerationalRefMutBorrowInfo, ) -> Result { - let borrow = self - .0 - .try_borrow_mut() - .map_err(|_| at.borrowed_from.borrow_mut_error())?; + let borrow = self.0.try_borrow_mut(); + + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + let borrow = borrow.map_err(|_| at.borrowed_from.borrow_mut_error())?; + + #[cfg(not(any(debug_assertions, feature = "debug_ownership")))] + let borrow = borrow.unwrap(); + RefMut::filter_map(borrow, |any| any.as_mut()?.downcast_mut()) .map_err(|_| { crate::error::BorrowMutError::Dropped(crate::error::ValueDroppedError { diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index b36bf4ec5..8cd70f70a 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -70,6 +70,9 @@ pub use use_future::*; mod use_sorted; pub use use_sorted::*; +mod use_resource; +pub use use_resource::*; + // mod use_on_create; // pub use use_on_create::*; diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index 314516cd8..048242d13 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -15,11 +15,7 @@ use std::{any::Any, cell::Cell, future::Future, pin::Pin, rc::Rc, sync::Arc, tas /// This is commonly used for components that cannot be rendered until some /// asynchronous operation has completed. /// -/// 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 canceled before the new one is started. /// -/// - dependencies: a tuple of references to values that are PartialEq + Clone pub fn use_future(mut future: impl FnMut() -> F) -> UseFuture where T: 'static, diff --git a/packages/hooks/src/use_resource.rs b/packages/hooks/src/use_resource.rs new file mode 100644 index 000000000..126a8fde0 --- /dev/null +++ b/packages/hooks/src/use_resource.rs @@ -0,0 +1,127 @@ +#![allow(missing_docs)] +use dioxus_core::{ + prelude::{spawn, use_hook}, + ScopeState, Task, +}; +use dioxus_signals::{use_effect, use_signal, Effect, Signal}; +use futures_util::{future, pin_mut, FutureExt}; +use std::{any::Any, cell::Cell, future::Future, pin::Pin, rc::Rc, sync::Arc, task::Poll}; + +/// 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. +/// +/// 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 canceled before the new one is started. +/// +/// - dependencies: a tuple of references to values that are PartialEq + Clone +pub fn use_resource(future: impl Fn() -> F) -> UseResource +where + T: 'static, + F: Future + 'static, +{ + let mut value = use_signal(|| None); + let mut state = use_signal(|| UseResourceState::Pending); + + let task = use_signal(|| { + // Create the user's task + let fut = future(); + + // Spawn a wrapper task that polls the innner future and watch its dependencies + let task = spawn(async move { + // move the future here and pin it so we can poll it + let fut = fut; + pin_mut!(fut); + + let res = future::poll_fn(|cx| { + // Set the effect stack properly + + // Poll the inner future + let ready = fut.poll_unpin(cx); + + // add any dependencies to the effect stack that we need to watch when restarting the future + + ready + }) + .await; + + // Set the value + value.set(Some(res)); + }); + + Some(task) + }); + + UseResource { task, value, state } +} + +pub struct UseResource { + value: Signal>, + task: Signal>, + state: Signal>, +} + +impl UseResource { + /// 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) { + // if let Some(task) = self.task.take() { + // cx.remove_future(task); + // } + } + + // Manually set the value in the future slot without starting the future over + pub fn set(&mut self, new_value: T) { + self.value.set(Some(new_value)); + } + + /// 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) -> Signal> { + self.value + } + + /// Get the ID of the future in Dioxus' internal scheduler + pub fn task(&self) -> Option { + todo!() + // self.task.get() + } + + /// Get the current state of the future. + pub fn state(&self) -> UseResourceState { + todo!() + // match (&self.task.get(), &self.value()) { + // // If we have a task and an existing value, we're reloading + // (Some(_), Some(val)) => UseResourceState::Reloading(val), + + // // no task, but value - we're done + // (None, Some(val)) => UseResourceState::Complete(val), + + // // no task, no value - something's wrong? return pending + // (None, None) => UseResourceState::Pending, + + // // Task, no value - we're still pending + // (Some(_), None) => UseResourceState::Pending, + // } + } +} + +pub enum UseResourceState { + Pending, + Complete(Signal), + Regenerating(Signal), // the old value +}