diff --git a/packages/generational-box/README.md b/packages/generational-box/README.md index 95aab8402..14dc4e921 100644 --- a/packages/generational-box/README.md +++ b/packages/generational-box/README.md @@ -11,26 +11,20 @@ Three main types manage state in Generational Box: Example: ```rust -use generational_box::Store; +use generational_box::{UnsyncStorage, AnyStorage}; -// Create a store for this thread -let store = Store::default(); +// Create an owner for some state for a scope +let owner = UnsyncStorage::owner(); -{ - // Create an owner for some state for a scope - let owner = store.owner(); +// Create some non-copy data, move it into a owner, and work with copy data +let data: String = "hello world".to_string(); +let key = owner.insert(data); - // Create some non-copy data, move it into a owner, and work with copy data - let data: String = "hello world".to_string(); - let key = owner.insert(data); - - // The generational box can be read from and written to like a RefCell - let value = key.read(); - assert_eq!(*value, "hello world"); -} -// Reading value at this point will cause a panic +// The generational box can be read from and written to like a RefCell +let value = key.read(); +assert_eq!(*value, "hello world"); ``` ## How it works -Internally, `generational-box` creates an arena of generational RefCell's that are recyled when the owner is dropped. You can think of the cells as something like `&'static RefCell>` with a generational check to make recyling a cell easier to debug. Then GenerationalBox's are `Copy` because the `&'static` pointer is `Copy` +Internally, `generational-box` creates an arena of generational `RefCell`'s that are recycled when the owner is dropped. You can think of the cells as something like `&'static RefCell>` with a generational check to make recycling a cell easier to debug. Then GenerationalBox's are `Copy` because the `&'static` pointer is `Copy` diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 9fe47ba2a..b8fdee94b 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -1,10 +1,7 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs)] -use error::*; -use parking_lot::{Mutex, RwLock}; -use references::{GenerationalRefBorrowInfo, GenerationalRefMutBorrowInfo}; -use std::any::Any; +use parking_lot::Mutex; use std::sync::atomic::AtomicU32; use std::{ fmt::Debug, @@ -13,7 +10,10 @@ use std::{ sync::Arc, }; -use unsync::UnsyncStorage; +pub use error::*; +pub use references::*; +pub use sync::SyncStorage; +pub use unsync::UnsyncStorage; mod error; mod references; @@ -37,12 +37,12 @@ fn reused() { let first_ptr; { let owner = UnsyncStorage::owner(); - first_ptr = owner.insert(1).raw.data.data_ptr(); + first_ptr = owner.insert(1).raw.0.data.data_ptr(); drop(owner); } { let owner = UnsyncStorage::owner(); - let second_ptr = owner.insert(1234).raw.data.data_ptr(); + let second_ptr = owner.insert(1234).raw.0.data.data_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); } @@ -233,7 +233,7 @@ impl> GenerationalBox { created_at: self.created_at, })); } - self.raw.0.data.try_read( + let result = self.raw.0.data.try_read( #[cfg(any(debug_assertions, feature = "debug_ownership"))] self.created_at, #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -241,7 +241,18 @@ impl> GenerationalBox { borrowed_at: std::panic::Location::caller(), borrowed_from: &self.raw.0.borrow, }, - ) + ); + + if result.is_ok() { + self.raw + .0 + .borrow + .borrowed_at + .write() + .push(std::panic::Location::caller()); + } + + result } /// Read the value. Panics if the value is no longer valid. @@ -259,14 +270,20 @@ impl> GenerationalBox { created_at: self.created_at, })); } - self.raw.0.data.try_write( + let result = self.raw.0.data.try_write( #[cfg(any(debug_assertions, feature = "debug_ownership"))] self.created_at, #[cfg(any(debug_assertions, feature = "debug_ownership"))] GenerationalRefMutBorrowInfo { borrowed_from: &self.raw.0.borrow, }, - ) + ); + + if result.is_ok() { + *self.raw.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller()); + } + + result } /// Write the value. Panics if the value is no longer valid. @@ -296,9 +313,9 @@ impl> GenerationalBox { } } -impl Copy for GenerationalBox {} +impl Copy for GenerationalBox {} -impl Clone for GenerationalBox { +impl Clone for GenerationalBox { fn clone(&self) -> Self { *self } @@ -384,7 +401,8 @@ pub trait AnyStorage: Default { } } -struct MemoryLocation(&'static MemoryLocationInner); +/// A dynamic memory location that can be used in a generational box. +pub struct MemoryLocation(&'static MemoryLocationInner); impl Clone for MemoryLocation { fn clone(&self) -> Self { @@ -397,8 +415,31 @@ impl Copy for MemoryLocation {} #[cfg(any(debug_assertions, feature = "debug_borrows"))] #[derive(Debug, Default)] struct MemoryLocationBorrowInfo { - borrowed_at: RwLock>>, - borrowed_mut_at: RwLock>>, + pub(crate) borrowed_at: parking_lot::RwLock>>, + pub(crate) borrowed_mut_at: parking_lot::RwLock>>, +} + +impl MemoryLocationBorrowInfo { + fn borrow_mut_error(&self) -> BorrowMutError { + if let Some(borrowed_mut_at) = self.borrowed_mut_at.read().as_ref() { + BorrowMutError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at, + }) + } else { + BorrowMutError::AlreadyBorrowed(crate::error::AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: self.borrowed_at.read().clone(), + }) + } + } + + fn borrow_error(&self) -> BorrowError { + BorrowError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + borrowed_mut_at: self.borrowed_mut_at.read().unwrap(), + }) + } } struct MemoryLocationInner { @@ -444,65 +485,6 @@ impl MemoryLocation { _marker: PhantomData, } } - - #[track_caller] - fn try_borrow( - &self, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, - ) -> std::result::Result<>::Ref, error::BorrowError> - where - S: Storage, - { - match self.0.data.try_read( - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - GenerationalRefBorrowInfo { - borrowed_at: std::panic::Location::caller(), - borrowed_from: &self.0.borrow, - }, - ) { - Ok(read) => { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - self.0 - .borrow - .borrowed_at - .write() - .push(std::panic::Location::caller()); - Ok(read) - } - Err(err) => Err(err), - } - } - - #[track_caller] - fn try_borrow_mut( - &self, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, - ) -> std::result::Result<>::Mut, error::BorrowMutError> - where - S: Storage, - { - match self.0.data.try_write( - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - GenerationalRefMutBorrowInfo { - borrowed_from: &self.0.borrow, - }, - ) { - Ok(read) => { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - { - *self.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller()); - } - Ok(read) - } - Err(err) => Err(err), - } - } } /// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped. @@ -511,7 +493,20 @@ pub struct Owner { phantom: PhantomData, } -impl Owner { +impl Owner { + /// Insert a value into the store. The value will be dropped when the owner is dropped. + #[track_caller] + pub fn insert(&self, value: T) -> GenerationalBox + where + S: Storage, + { + self.insert_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + std::panic::Location::caller(), + ) + } + /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. pub fn insert_with_caller( &self, diff --git a/packages/generational-box/src/references.rs b/packages/generational-box/src/references.rs index 013541bd7..c2f52946c 100644 --- a/packages/generational-box/src/references.rs +++ b/packages/generational-box/src/references.rs @@ -3,7 +3,7 @@ use std::{ ops::{Deref, DerefMut}, }; -use crate::{Mappable, MappableMut, MemoryLocationBorrowInfo}; +use crate::{Mappable, MappableMut}; /// A reference to a value in a generational box. pub struct GenerationalRef> { @@ -73,9 +73,10 @@ impl> Deref for GenerationalRef { } #[cfg(any(debug_assertions, feature = "debug_borrows"))] -pub(crate) struct GenerationalRefBorrowInfo { +/// Information about a borrow. +pub struct GenerationalRefBorrowInfo { pub(crate) borrowed_at: &'static std::panic::Location<'static>, - pub(crate) borrowed_from: &'static MemoryLocationBorrowInfo, + pub(crate) borrowed_from: &'static crate::MemoryLocationBorrowInfo, } #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -157,8 +158,10 @@ impl> DerefMut for GenerationalRefMut { } #[cfg(any(debug_assertions, feature = "debug_borrows"))] -pub(crate) struct GenerationalRefMutBorrowInfo { - pub(crate) borrowed_from: &'static MemoryLocationBorrowInfo, +/// Information about a mutable borrow. +pub struct GenerationalRefMutBorrowInfo { + /// The location where the borrow occurred. + pub(crate) borrowed_from: &'static crate::MemoryLocationBorrowInfo, } #[cfg(any(debug_assertions, feature = "debug_borrows"))] diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 1432ca6d8..e11c17418 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -5,10 +5,7 @@ use std::sync::{Arc, OnceLock}; use crate::{ error::{self, ValueDroppedError}, - references::{ - GenerationalRef, GenerationalRefBorrowInfo, GenerationalRefMut, - GenerationalRefMutBorrowInfo, - }, + references::{GenerationalRef, GenerationalRefMut}, AnyStorage, Mappable, MappableMut, MemoryLocation, MemoryLocationInner, Storage, }; @@ -89,9 +86,14 @@ impl Storage for SyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefBorrowInfo, ) -> Result { - RwLockReadGuard::try_map(self.0.read(), |any| any.as_ref()?.downcast_ref()) + let read = self + .0 + .try_read() + .ok_or_else(|| at.borrowed_from.borrow_error())?; + RwLockReadGuard::try_map(read, |any| any.as_ref()?.downcast_ref()) .map_err(|_| { error::BorrowError::Dropped(ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -111,9 +113,14 @@ impl Storage for SyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefMutBorrowInfo, ) -> Result { - RwLockWriteGuard::try_map(self.0.write(), |any| any.as_mut()?.downcast_mut()) + let write = self + .0 + .try_write() + .ok_or_else(|| at.borrowed_from.borrow_mut_error())?; + RwLockWriteGuard::try_map(write, |any| any.as_mut()?.downcast_mut()) .map_err(|_| { error::BorrowMutError::Dropped(ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index 83c670480..dac9967f0 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -1,8 +1,5 @@ use crate::{ - references::{ - GenerationalRef, GenerationalRefBorrowInfo, GenerationalRefMut, - GenerationalRefMutBorrowInfo, - }, + references::{GenerationalRef, GenerationalRefMut}, AnyStorage, Mappable, MappableMut, MemoryLocation, MemoryLocationInner, Storage, }; use std::cell::{Ref, RefCell, RefMut}; @@ -54,14 +51,19 @@ impl Storage for UnsyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefBorrowInfo, ) -> Result { - Ref::filter_map(self.0.borrow(), |any| any.as_ref()?.downcast_ref()) + let borrow = self + .0 + .try_borrow() + .map_err(|_| at.borrowed_from.borrow_error())?; + Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref()) .map_err(|_| { - crate::error::BorrowError::Dropped( + crate::error::BorrowError::Dropped(crate::error::ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] - crate::error::ValueDroppedError { created_at }, - ) + created_at, + }) }) .map(|guard| { GenerationalRef::new( @@ -76,14 +78,19 @@ impl Storage for UnsyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefMutBorrowInfo, ) -> Result { - RefMut::filter_map(self.0.borrow_mut(), |any| any.as_mut()?.downcast_mut()) + let borrow = self + .0 + .try_borrow_mut() + .map_err(|_| at.borrowed_from.borrow_mut_error())?; + RefMut::filter_map(borrow, |any| any.as_mut()?.downcast_mut()) .map_err(|_| { - crate::error::BorrowMutError::Dropped( + crate::error::BorrowMutError::Dropped(crate::error::ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] - crate::error::ValueDroppedError { created_at }, - ) + created_at, + }) }) .map(|guard| { GenerationalRefMut::new( diff --git a/packages/signals/examples/errors.rs b/packages/signals/examples/errors.rs new file mode 100644 index 000000000..d082e55bb --- /dev/null +++ b/packages/signals/examples/errors.rs @@ -0,0 +1,90 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use dioxus_signals::{use_signal, use_signal_sync, Signal}; +use generational_box::SyncStorage; + +fn main() { + dioxus_desktop::launch(app); +} + +#[derive(Clone, Copy)] +enum ErrorComponent { + ReadsSignal, + ReadsMutSignal, + ReadDroppedSignal, +} + +fn app(cx: Scope) -> Element { + let error = use_signal(cx, || None); + + render! { + match *error() { + Some(ErrorComponent::ReadsSignal) => render! { ReadsSignal {} }, + Some(ErrorComponent::ReadsMutSignal) => render! { ReadsMutSignal {} }, + Some(ErrorComponent::ReadDroppedSignal) => render! { ReadDroppedSignal {} }, + None => render! { + button { + onclick: move |_| error.set(Some(ErrorComponent::ReadsSignal)), + "ReadsSignal" + } + button { + onclick: move |_| error.set(Some(ErrorComponent::ReadsMutSignal)), + "ReadsMutSignal" + } + button { + onclick: move |_| error.set(Some(ErrorComponent::ReadDroppedSignal)), + "ReadDroppedSignal" + } + } + } + } +} + +fn ReadsSignal(cx: Scope) -> Element { + let signal = use_signal_sync(cx, || 0); + + let _write = signal.write(); + let _read = signal.read(); + + todo!() +} + +fn ReadsMutSignal(cx: Scope) -> Element { + let signal = use_signal_sync(cx, || 0); + + let _read = signal.read(); + let _write = signal.write(); + + todo!() +} + +fn ReadDroppedSignal(cx: Scope) -> Element { + let signal = use_signal_sync(cx, || None); + if cx.generation() < 4 { + cx.needs_update(); + } + render! { + if let Some(value) = &*signal() { + render!{"{value:?}"} + } else { + render! { + ReadDroppedSignalChild { parent_signal: signal } + } + } + } +} + +#[component] +fn ReadDroppedSignalChild( + cx: Scope, + parent_signal: Signal>, SyncStorage>, +) -> Element { + let signal = use_signal_sync(cx, || 0); + cx.use_hook(move || { + parent_signal.set(Some(signal.clone())); + }); + render! { + "{signal}" + } +} diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 7e61e8014..84c19ee9c 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -170,7 +170,7 @@ impl Effect { /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. pub fn try_run(&self) { tracing::trace!("Running effect: {:?}", self); - if let Some(mut inner) = self.inner.try_write() { + if let Ok(mut inner) = self.inner.try_write() { { EFFECT_STACK.with(|stack| { stack.effects.write().push(*self); diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 676101b20..898fd98d2 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -12,32 +12,37 @@ use std::{ macro_rules! read_impls { ($ty:ident, $bound:path) => { impl Default for $ty { + #[track_caller] fn default() -> Self { Self::new_maybe_sync(Default::default()) } } impl std::clone::Clone for $ty { + #[track_caller] fn clone(&self) -> Self { *self } } - impl Copy for $ty {} + impl Copy for $ty {} impl Display for $ty { + #[track_caller] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Display::fmt(v, f)) } } impl Debug for $ty { + #[track_caller] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Debug::fmt(v, f)) } } impl PartialEq for $ty { + #[track_caller] fn eq(&self, other: &T) -> bool { self.with(|v| *v == *other) } @@ -50,18 +55,21 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Add for $ty { type Output = T; + #[track_caller] fn add(self, rhs: T) -> Self::Output { self.with(|v| *v + rhs) } } impl + Copy + 'static, S: $bound> std::ops::AddAssign for $ty { + #[track_caller] fn add_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v + rhs) } } impl + Copy + 'static, S: $bound> std::ops::SubAssign for $ty { + #[track_caller] fn sub_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v - rhs) } @@ -70,12 +78,14 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Sub for $ty { type Output = T; + #[track_caller] fn sub(self, rhs: T) -> Self::Output { self.with(|v| *v - rhs) } } impl + Copy + 'static, S: $bound> std::ops::MulAssign for $ty { + #[track_caller] fn mul_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v * rhs) } @@ -84,12 +94,14 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Mul for $ty { type Output = T; + #[track_caller] fn mul(self, rhs: T) -> Self::Output { self.with(|v| *v * rhs) } } impl + Copy + 'static, S: $bound> std::ops::DivAssign for $ty { + #[track_caller] fn div_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v / rhs) } @@ -98,6 +110,7 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Div for $ty { type Output = T; + #[track_caller] fn div(self, rhs: T) -> Self::Output { self.with(|v| *v / rhs) } @@ -105,51 +118,61 @@ macro_rules! write_impls { impl $ty, S> { /// Pushes a new value to the end of the vector. + #[track_caller] pub fn push(&self, value: T) { self.with_mut(|v| v.push(value)) } /// Pops the last value from the vector. + #[track_caller] pub fn pop(&self) -> Option { self.with_mut(|v| v.pop()) } /// Inserts a new value at the given index. + #[track_caller] pub fn insert(&self, index: usize, value: T) { self.with_mut(|v| v.insert(index, value)) } /// Removes the value at the given index. + #[track_caller] pub fn remove(&self, index: usize) -> T { self.with_mut(|v| v.remove(index)) } /// Clears the vector, removing all values. + #[track_caller] pub fn clear(&self) { self.with_mut(|v| v.clear()) } /// Extends the vector with the given iterator. + #[track_caller] pub fn extend(&self, iter: impl IntoIterator) { self.with_mut(|v| v.extend(iter)) } /// Truncates the vector to the given length. + #[track_caller] pub fn truncate(&self, len: usize) { self.with_mut(|v| v.truncate(len)) } /// Swaps two values in the vector. + #[track_caller] pub fn swap_remove(&self, index: usize) -> T { self.with_mut(|v| v.swap_remove(index)) } /// Retains only the values that match the given predicate. + #[track_caller] pub fn retain(&self, f: impl FnMut(&T) -> bool) { self.with_mut(|v| v.retain(f)) } /// Splits the vector into two at the given index. + #[track_caller] pub fn split_off(&self, at: usize) -> Vec { self.with_mut(|v| v.split_off(at)) } @@ -161,6 +184,7 @@ read_impls!(CopyValue, Storage); impl>> CopyValue, S> { /// Read a value from the inner vector. + #[track_caller] pub fn get(&self, index: usize) -> Option<>>::Mapped> { S::Ref::try_map(self.read(), move |v| v.get(index)) } @@ -168,6 +192,7 @@ impl>> CopyValue, S> { impl>> CopyValue, S> { /// Unwraps the inner value and clones it. + #[track_caller] pub fn unwrap(&self) -> T where T: Clone, @@ -176,6 +201,7 @@ impl>> CopyValue, S> { } /// Attempts to read the inner value of the Option. + #[track_caller] pub fn as_ref(&self) -> Option<>>::Mapped> { S::Ref::try_map(self.read(), |v| v.as_ref()) } @@ -185,21 +211,25 @@ write_impls!(CopyValue, Storage, Storage>); impl>> CopyValue, S> { /// Takes the value out of the Option. + #[track_caller] pub fn take(&self) -> Option { self.with_mut(|v| v.take()) } /// Replace the value in the Option. + #[track_caller] pub fn replace(&self, value: T) -> Option { self.with_mut(|v| v.replace(value)) } /// Gets the value out of the Option, or inserts the given value if the Option is empty. + #[track_caller] pub fn get_or_insert(&self, default: T) -> >>::Mapped { self.get_or_insert_with(|| default) } /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. + #[track_caller] pub fn get_or_insert_with( &self, default: impl FnOnce() -> T, diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index d9b178e54..2a8135e3a 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -7,10 +7,7 @@ use std::rc::Rc; use dioxus_core::prelude::*; use dioxus_core::ScopeId; -use generational_box::{ - BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, - Storage, Store, -}; +use generational_box::{GenerationalBox, Owner, Storage}; use crate::Effect; @@ -82,6 +79,7 @@ impl CopyValue { } /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. + #[track_caller] pub fn new_in_scope(value: T, scope: ScopeId) -> Self { Self::new_maybe_sync_in_scope(value, scope) } @@ -91,6 +89,7 @@ impl> CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. + #[track_caller] pub fn new_maybe_sync(value: T) -> Self { let owner = current_owner(); @@ -117,6 +116,7 @@ impl> CopyValue { } /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. + #[track_caller] pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); @@ -143,7 +143,7 @@ impl> CopyValue { /// Try to read the value. If the value has been dropped, this will return None. #[track_caller] - pub fn try_read(&self) -> Option { + pub fn try_read(&self) -> Result { self.value.try_read() } @@ -155,7 +155,7 @@ impl> CopyValue { /// Try to write the value. If the value has been dropped, this will return None. #[track_caller] - pub fn try_write(&self) -> Option { + pub fn try_write(&self) -> Result { self.value.try_write() } diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index 560c2c94b..c9564b08a 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -22,6 +22,7 @@ use crate::{use_signal, EffectInner, EFFECT_STACK}; /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_selector( cx: &ScopeState, @@ -47,6 +48,7 @@ pub fn use_selector( /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_maybe_sync_selector>>( cx: &ScopeState, @@ -71,6 +73,7 @@ pub fn use_maybe_sync_selector>>( /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_selector_with_dependencies( cx: &ScopeState, @@ -99,6 +102,7 @@ where /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_maybe_sync_selector_with_dependencies< R: PartialEq, @@ -129,6 +133,7 @@ where /// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. +#[track_caller] pub fn selector(f: impl FnMut() -> R + 'static) -> ReadOnlySignal { maybe_sync_selector(f) } @@ -136,6 +141,7 @@ pub fn selector(f: impl FnMut() -> R + 'static) -> ReadOnlySignal< /// Creates a new Selector that may be Sync + Send. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. +#[track_caller] pub fn maybe_sync_selector>>( mut f: impl FnMut() -> R + 'static, ) -> ReadOnlySignal { diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index d99807f0a..700d9cb1f 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -99,11 +99,20 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< /// } /// ``` #[must_use] +#[track_caller] pub fn use_signal_sync( cx: &ScopeState, f: impl FnOnce() -> T, ) -> Signal { - *cx.use_hook(|| Signal::new_maybe_sync(f())) + #[cfg(debug_assertions)] + let caller = std::panic::Location::caller(); + *cx.use_hook(|| { + Signal::new_with_caller( + f(), + #[cfg(debug_assertions)] + caller, + ) + }) } #[derive(Clone)] @@ -207,6 +216,7 @@ impl Signal { } /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. + #[track_caller] pub fn new_in_scope(value: T, owner: ScopeId) -> Self { Self::new_maybe_sync_in_scope(value, owner) } @@ -214,6 +224,7 @@ impl Signal { impl>> Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + #[track_caller] #[tracing::instrument(skip(value))] pub fn new_maybe_sync(value: T) -> Self { Self { @@ -246,6 +257,7 @@ impl>> Signal { } /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. + #[track_caller] #[tracing::instrument(skip(value))] pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self { Self { @@ -513,6 +525,7 @@ pub struct ReadOnlySignal> = UnsyncStorage> impl ReadOnlySignal { /// Create a new read-only signal. + #[track_caller] pub fn new(signal: Signal) -> Self { Self::new_maybe_sync(signal) } @@ -520,6 +533,7 @@ impl ReadOnlySignal { impl>> ReadOnlySignal { /// Create a new read-only signal that is maybe sync. + #[track_caller] pub fn new_maybe_sync(signal: Signal) -> Self { Self { inner: signal } } @@ -542,7 +556,9 @@ impl>> ReadOnlySignal { /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. - pub fn peek(&self) -> GenerationalRef { + pub fn peek( + &self, + ) -> <>>::Ref as Mappable>>::Mapped { self.inner.peek() }