diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 6d22ee0c8..c081155a8 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -14,6 +14,7 @@ simple_logger = "4.2.0" serde = { version = "1", features = ["derive"], optional = true } parking_lot = "0.12.1" once_cell = "1.18.0" +ghost = "0.1.16" [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index bf994cf4b..f59b8391f 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -14,7 +14,7 @@ macro_rules! read_impls { ($ty:ident, $bound:path) => { impl Default for $ty { fn default() -> Self { - Self::new(Default::default()) + Self::new_maybe_sync(Default::default()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index a6005748a..be4810680 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,3 +1,4 @@ +use generational_box::UnsyncStorage; use std::mem::MaybeUninit; use std::ops::Deref; use std::rc::Rc; @@ -44,7 +45,7 @@ fn owner_in_scope, T>(scope: ScopeId) -> Rc> { /// CopyValue is a wrapper around a value to make the value mutable and Copy. /// /// It is internally backed by [`generational_box::GenerationalBox`]. -pub struct CopyValue> { +pub struct CopyValue = UnsyncStorage> { pub(crate) value: GenerationalBox, origin_scope: ScopeId, } @@ -71,11 +72,25 @@ where } } -impl> CopyValue { +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. pub fn new(value: T) -> Self { + Self::new_maybe_sync(value) + } + + /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. + pub fn new_in_scope(value: T, scope: ScopeId) -> Self { + Self::new_maybe_sync_in_scope(value, scope) + } +} + +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. + pub fn new_maybe_sync(value: T) -> Self { let owner = current_owner(); Self { @@ -85,7 +100,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. - pub fn new_in_scope(value: T, scope: ScopeId) -> Self { + pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); Self { diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index 28d8ca38e..5de6395d5 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -5,7 +5,7 @@ use crate::dependency::Dependency; use crate::use_signal; use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal}; -/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes. +/// 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. /// @@ -23,14 +23,41 @@ use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySig /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_selector>>( +pub fn use_selector( + cx: &ScopeState, + f: impl FnMut() -> R + 'static, +) -> ReadOnlySignal { + use_maybe_sync_selector(cx, f) +} + + +/// Creates a new Selector that may be sync. The selector will be run immediately and whenever any signal it reads changes. +/// +/// Selectors can be used to efficiently compute derived data from signals. +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_signals::*; +/// +/// fn App(cx: Scope) -> Element { +/// let mut count = use_signal(cx, || 0); +/// let double = use_selector(cx, move || count * 2); +/// count += 1; +/// assert_eq!(double.value(), count * 2); +/// +/// render! { "{double}" } +/// } +/// ``` +#[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] +pub fn use_maybe_sync_selector>>( cx: &ScopeState, f: impl FnMut() -> R + 'static, ) -> ReadOnlySignal { - *cx.use_hook(|| selector(f)) + *cx.use_hook(|| maybe_sync_selector(f)) } -/// Creates a new Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes + +/// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes /// /// Selectors can be used to efficiently compute derived data from signals. /// @@ -47,7 +74,35 @@ pub fn use_selector>>( /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_selector_with_dependencies>>( +pub fn use_selector_with_dependencies( + cx: &ScopeState, + dependencies: D, + mut f: impl FnMut(D::Out) -> R + 'static, +) -> ReadOnlySignal +where + D::Out: 'static, +{ + use_maybe_sync_selector_with_dependencies(cx, dependencies, f) +} + +/// Creates a new Selector that may be sync with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes +/// +/// Selectors can be used to efficiently compute derived data from signals. +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_signals::*; +/// +/// fn App(cx: Scope) -> Element { +/// let mut local_state = use_state(cx, || 0); +/// let double = use_selector_with_dependencies(cx, (local_state.get(),), move |(local_state,)| local_state * 2); +/// local_state.set(1); +/// +/// render! { "{double}" } +/// } +/// ``` +#[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] +pub fn use_maybe_sync_selector_with_dependencies>>( cx: &ScopeState, dependencies: D, mut f: impl FnMut(D::Out) -> R + 'static, @@ -57,7 +112,7 @@ where { let dependencies_signal = use_signal(cx, || dependencies.out()); let selector = *cx.use_hook(|| { - selector(move || { + maybe_sync_selector(move || { let deref = &*dependencies_signal.read(); f(deref.clone()) }) @@ -69,10 +124,19 @@ where selector } -/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes. +/// 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. -pub fn selector>>( +pub fn selector( + mut f: impl FnMut() -> R + 'static, +) -> ReadOnlySignal { + maybe_sync_selector(f) +} + +/// 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. +pub fn maybe_sync_selector>>( mut f: impl FnMut() -> R + 'static, ) -> ReadOnlySignal { let state = Signal:: { @@ -108,5 +172,5 @@ pub fn selector>>( } })); - ReadOnlySignal::new(state) + ReadOnlySignal::new_maybe_sync(state) } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 61186b92c..7d83c5d52 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -170,7 +170,7 @@ pub struct SignalData { /// } /// } /// ``` -pub struct Signal>> { +pub struct Signal> = UnsyncStorage> { pub(crate) inner: CopyValue, S>, } @@ -188,11 +188,23 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign } } -impl>> Signal { +impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. pub fn new(value: T) -> Self { + Self::new_maybe_sync(value) + } + + /// 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. + pub fn new_in_scope(value: T, owner: ScopeId) -> Self { + Self::new_maybe_sync_in_scope(value, owner) + } +} + +impl>> Signal { + /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + pub fn new_maybe_sync(value: T) -> Self { Self { - inner: CopyValue::new(SignalData { + inner: CopyValue::, S>::new_maybe_sync(SignalData { subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, @@ -202,9 +214,9 @@ 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. - pub fn new_in_scope(value: T, owner: ScopeId) -> Self { + pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self { Self { - inner: CopyValue::new_in_scope( + inner: CopyValue::, S>::new_maybe_sync_in_scope( SignalData { subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), @@ -228,10 +240,11 @@ impl>> Signal { ) -> <>>::Ref as Mappable>>::Mapped { let inner = self.inner.read(); if let Some(effect) = inner.effect_stack.current() { - let mut subscribers = inner.subscribers.write(); - let mut effect_subscribers = &mut subscribers.effect_subscribers; - if !effect_subscribers.contains(&effect) { - effect_subscribers.push(effect); + let subscribers = inner.subscribers.read(); + if !subscribers.effect_subscribers.contains(&effect) { + drop(subscribers); + let mut subscribers = inner.subscribers.write(); + subscribers.effect_subscribers.push(effect); } } else if let Some(current_scope_id) = current_scope_id() { // only subscribe if the vdom is rendering @@ -241,17 +254,13 @@ impl>> Signal { self.inner.value, current_scope_id ); - let mut subscribers = inner.subscribers.write(); - let subscribers = &mut subscribers.subscribers; - if !subscribers.contains(¤t_scope_id) { - subscribers.push(current_scope_id); + let subscribers = inner.subscribers.read(); + if !subscribers.subscribers.contains(¤t_scope_id) { drop(subscribers); + let mut subscribers = inner.subscribers.write(); + subscribers.subscribers.push(current_scope_id); let unsubscriber = current_unsubscriber(); - inner - .subscribers - .write() - .subscribers - .push(unsubscriber.scope); + subscribers.subscribers.push(unsubscriber.scope); } } } @@ -441,13 +450,20 @@ impl, S: Storage>, I> DerefMut for Write>> { +pub struct ReadOnlySignal> = UnsyncStorage> { inner: Signal, } -impl>> ReadOnlySignal { +impl ReadOnlySignal { /// Create a new read-only signal. - pub fn new(signal: Signal) -> Self { + pub fn new(signal: Signal) -> Self { + Self::new_maybe_sync(signal) + } +} + +impl>> ReadOnlySignal { + /// Create a new read-only signal that is maybe sync. + pub fn new_maybe_sync(signal: Signal) -> Self { Self { inner: signal } }