diff --git a/Cargo.toml b/Cargo.toml index 81280d7df..722666d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,7 @@ futures-util = { version = "0.3", default-features = false } rustc-hash = "1.1.0" wasm-bindgen = "0.2.87" html_parser = "0.7.0" +thiserror = "1.0.40" # This is a "virtual package" # It is not meant to be published, but is used so "cargo run --example XYZ" works properly @@ -117,6 +118,6 @@ rand = { version = "0.8.4", features = ["small_rng"] } tokio = { version = "1.16.1", features = ["full"] } reqwest = { version = "0.11.9", features = ["json"] } fern = { version = "0.6.0", features = ["colored"] } -thiserror = "1.0.30" env_logger = "0.10.0" simple_logger = "4.0.0" +thiserror = { workspace = true } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 9edad76e9..16b44ebc4 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" [dependencies] # cli core clap = { version = "4.2", features = ["derive"] } -thiserror = "1.0.30" +thiserror = { workspace = true } wasm-bindgen-cli-support = "0.2" colored = "2.0.0" diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 29e867b4c..ed99fbfd4 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -17,7 +17,7 @@ dioxus-hot-reload = { workspace = true, optional = true } serde = "1.0.136" serde_json = "1.0.79" -thiserror = "1.0.30" +thiserror = { workspace = true } log = { workspace = true } wry = { version = "0.28.0" } futures-channel = { workspace = true } diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index b6bfe6536..fedf65894 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -33,7 +33,7 @@ futures-util = { workspace = true } log = { workspace = true } rand = { version = "0.8.4", features = ["small_rng"] } criterion = "0.3.5" -thiserror = "1.0.30" +thiserror = { workspace = true } env_logger = "0.10.0" tokio = { workspace = true, features = ["full"] } # dioxus-edit-stream = { workspace = true } diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index b73b8adc2..57f414194 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -39,7 +39,7 @@ dioxus-router = { workspace = true, optional = true } log = { workspace = true } once_cell = "1.17.1" -thiserror = "1.0.40" +thiserror = { workspace = true } tokio = { workspace = true, features = ["full"], optional = true } object-pool = "0.5.4" anymap = "0.12.1" diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index 758db4f64..e4e10809c 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -9,12 +9,16 @@ repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] +[features] +default = [] +nightly-features = [] [dependencies] dioxus-core = { workspace = true } futures-channel = { workspace = true } log = { workspace = true } - +thiserror = { workspace = true } +debug-cell = { git = "https://github.com/Niedzwiedzw/debug-cell", rev = "3352a1f8aff19f56f5e3b2018200a3338fd43d2e" } # waiting for the merge / official DioxusLabs fork [dev-dependencies] futures-util = { workspace = true, default-features = false } diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index a2fb23ed1..65f390eda 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "nightly-features", feature(debug_refcell))] + #[macro_export] /// A helper macro for using hooks and properties in async environements. /// diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 3a3a4d639..bde1fabb8 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -1,11 +1,79 @@ +use self::error::{UseSharedStateError, UseSharedStateResult}; use dioxus_core::{ScopeId, ScopeState}; -use std::{ - cell::{Ref, RefCell, RefMut}, - collections::HashSet, - rc::Rc, - sync::Arc, +use std::{collections::HashSet, rc::Rc, sync::Arc}; + +#[cfg(debug_assertions)] +pub use debug_cell::{ + error::{BorrowError, BorrowMutError}, + Ref, RefCell, RefMut, }; +#[cfg(not(debug_assertions))] +pub use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}; + +#[macro_export] +macro_rules! debug_location { + () => {{ + #[cfg(debug_assertions)] + { + std::panic::Location::caller() + } + #[cfg(not(debug_assertions))] + { + () + } + }}; +} + +pub mod error { + fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String { + locations + .iter() + .map(|location| format!(" - {location}")) + .collect::>() + .join("\n") + } + #[derive(thiserror::Error, Debug)] + pub enum UseSharedStateError { + #[cfg_attr( + debug_assertions, + error( + "[{0}] {1} is already borrowed at, so it cannot be borrowed mutably. Previous borrows:\n[{2}]\n\n", + .source.attempted_at, + .type_name, + locations_display(&.source.already_borrowed_at) + ) + )] + #[cfg_attr( + not(debug_assertions), + error("{type_name} is already borrowed, so it cannot be borrowed mutably. (More detail available in debug mode)") + )] + AlreadyBorrowed { + source: super::BorrowMutError, + type_name: &'static str, + }, + #[cfg_attr( + debug_assertions, + error( + "[{0}] {1} is already borrowed mutably at [{2}], so it cannot be borrowed anymore.", + .source.attempted_at, + .type_name, + locations_display(&.source.already_borrowed_at) + ) + )] + #[cfg_attr( + not(debug_assertions), + error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore. (More detail available in debug mode)") + )] + AlreadyBorrowedMutably { + source: super::BorrowError, + type_name: &'static str, + }, + } + + pub type UseSharedStateResult = Result; +} + type ProvidedState = Rc>>; // Tracks all the subscribers to a shared State @@ -95,7 +163,7 @@ pub fn use_shared_state(cx: &ScopeState) -> Option<&UseSharedState { } impl UseSharedState { + fn new(inner: Rc>>) -> Self { + Self { inner } + } + /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") pub fn notify_consumers(&self) { self.inner.borrow_mut().notify_consumers(); } + /// Try reading the shared state + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] + pub fn try_read(&self) -> UseSharedStateResult> { + match self.inner.try_borrow() { + Ok(value) => Ok(Ref::map(value, |inner| &inner.value)), + Err(source) => Err(UseSharedStateError::AlreadyBorrowedMutably { + source, + type_name: std::any::type_name::(), + }), + } + } + /// Read the shared value + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn read(&self) -> Ref<'_, T> { - Ref::map(self.inner.borrow(), |inner| &inner.value) + match self.try_read() { + Ok(value) => value, + Err(message) => panic!( + "Reading the shared state failed: {}\n({:?})", + message, message + ), + } + } + + /// Try writing the shared state + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] + pub fn try_write(&self) -> UseSharedStateResult> { + match self.inner.try_borrow_mut() { + Ok(mut value) => { + value.notify_consumers(); + Ok(RefMut::map(value, |inner| &mut inner.value)) + } + Err(source) => Err(UseSharedStateError::AlreadyBorrowed { + source, + type_name: std::any::type_name::(), + }), + } } /// Calling "write" will force the component to re-render /// /// // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn write(&self) -> RefMut<'_, T> { - let mut value = self.inner.borrow_mut(); - value.notify_consumers(); - RefMut::map(value, |inner| &mut inner.value) + match self.try_write() { + Ok(value) => value, + Err(message) => panic!( + "Writing to shared state failed: {}\n({:?})", + message, message + ), + } } - /// Allows the ability to write the value without forcing a re-render + /// Tries writing the value without forcing a re-render + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] + pub fn try_write_silent(&self) -> UseSharedStateResult> { + match self.inner.try_borrow_mut() { + Ok(value) => Ok(RefMut::map(value, |inner| &mut inner.value)), + Err(source) => Err(UseSharedStateError::AlreadyBorrowed { + source, + type_name: std::any::type_name::(), + }), + } + } + + /// Writes the value without forcing a re-render + #[cfg_attr(debug_assertions, track_caller)] + #[cfg_attr(debug_assertions, inline(never))] pub fn write_silent(&self) -> RefMut<'_, T> { - RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.value) + match self.try_write_silent() { + Ok(value) => value, + Err(message) => panic!( + "Writing to shared state silently failed: {}\n({:?})", + message, message + ), + } } } diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 33964753a..b2c701877 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -9,7 +9,7 @@ description = "Build server-side apps with Dioxus" license = "MIT OR Apache-2.0" [dependencies] -thiserror = "1.0.38" +thiserror = { workspace = true } log = { workspace = true } slab = { workspace = true } futures-util = { workspace = true, default-features = false, features = [ diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index dcdcd7b4b..a23e87d2c 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -14,6 +14,9 @@ dioxus = { workspace = true } dioxus-router-macro = { workspace = true } gloo = { version = "0.8.0", optional = true } log = { workspace = true } +thiserror = { workspace = true } +futures-util = { workspace = true } +serde_urlencoded = { version = "0.7.1", optional = true } serde = { version = "1", features = ["derive"], optional = true } thiserror = "1.0.37" url = "2.3.1" diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 77cc49534..f336b5f99 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -21,6 +21,8 @@ tokio = { version = "1.28", features = ["full"] } [dev-dependencies] dioxus = { workspace = true } +thiserror = { workspace = true } +log = { workspace = true } fern = { version = "0.6.0", features = ["colored"] } anyhow = "1.0" argh = "0.1.4"