diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index d994b9057f..47356ecd7e 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -1766,6 +1766,7 @@ fn looks_like_write_type(ty: &Type) -> bool { fn looks_like_store_type(ty: &Type) -> bool { last_segment_matches(ty, &parse_quote!(Store)) || last_segment_matches(ty, &parse_quote!(ReadStore)) + || last_segment_matches(ty, &parse_quote!(WriteStore)) } fn looks_like_callback_type(ty: &Type) -> bool { diff --git a/packages/signals/examples/send_store.rs b/packages/signals/examples/send_store.rs new file mode 100644 index 0000000000..9b6f807e39 --- /dev/null +++ b/packages/signals/examples/send_store.rs @@ -0,0 +1,33 @@ +use dioxus::prelude::*; + +fn main() { + dioxus::launch(app); +} + +fn app() -> Element { + let mut store = use_hook(|| Store::new_maybe_sync(0u32)); + + rsx! { + button { onclick: move |_| store += 1, "Increase" } + "{store}" + Child { store } + } +} + +#[component] +fn Child(store: WriteStore) -> Element { + // A `WriteStore` can be sent to another thread! + // Make sure to clean up your threads when the component unmounts. + use_hook(|| { + std::thread::spawn(move || loop { + std::thread::sleep(std::time::Duration::from_secs(1)); + store += 1; + + if store() >= 10 { + break; + } + }); + }); + + rsx! {} +} diff --git a/packages/signals/src/boxed.rs b/packages/signals/src/boxed.rs index 6f97679539..8868623b37 100644 --- a/packages/signals/src/boxed.rs +++ b/packages/signals/src/boxed.rs @@ -1,11 +1,12 @@ use std::{any::Any, ops::Deref}; use dioxus_core::{IntoAttributeValue, IntoDynNode, Subscribers}; -use generational_box::{BorrowResult, UnsyncStorage}; +use generational_box::{BorrowResult, Storage, SyncStorage, UnsyncStorage}; use crate::{ read_impls, write_impls, CopyValue, Global, InitializeFromFunction, MappedMutSignal, - MappedSignal, Memo, Readable, ReadableExt, ReadableRef, Signal, Writable, WritableExt, + MappedSignal, Memo, Readable, ReadableExt, ReadableRef, Signal, SignalData, Writable, + WritableExt, }; /// A signal that can only be read from. @@ -13,18 +14,29 @@ use crate::{ since = "0.7.0", note = "Use `ReadSignal` instead. Will be removed in 0.8" )] -pub type ReadOnlySignal = ReadSignal; +pub type ReadOnlySignal = ReadSignal; /// A boxed version of [Readable] that can be used to store any readable type. -pub struct ReadSignal { - value: CopyValue>>, +pub struct ReadSignal = UnsyncStorage> { + value: CopyValue>, S>, } impl ReadSignal { /// Create a new boxed readable value. pub fn new(value: impl Readable + 'static) -> Self { + Self::new_maybe_sync(value) + } +} + +impl> ReadSignal { + /// Create a new boxed readable value which may be sync + pub fn new_maybe_sync(value: R) -> Self + where + S: CreateBoxedSignalStorage, + R: Readable, + { Self { - value: CopyValue::new(Box::new(value)), + value: CopyValue::new_maybe_sync(S::new_readable(value, sealed::SealedToken)), } } @@ -56,29 +68,33 @@ impl ReadSignal { } } -impl Clone for ReadSignal { +impl> Clone for ReadSignal { fn clone(&self) -> Self { *self } } -impl Copy for ReadSignal {} +impl> Copy for ReadSignal {} -impl PartialEq for ReadSignal { +impl> PartialEq for ReadSignal { fn eq(&self, other: &Self) -> bool { self.value == other.value } } -impl Default for ReadSignal { +impl< + T: Default + 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage>, + > Default for ReadSignal +{ fn default() -> Self { - Self::new(Signal::new(T::default())) + Self::new_maybe_sync(Signal::new_maybe_sync(T::default())) } } -read_impls!(ReadSignal); +read_impls!(ReadSignal>); -impl IntoAttributeValue for ReadSignal +impl> IntoAttributeValue for ReadSignal where T: Clone + IntoAttributeValue + 'static, { @@ -87,16 +103,17 @@ where } } -impl IntoDynNode for ReadSignal +impl IntoDynNode for ReadSignal where T: Clone + IntoDynNode + 'static, + S: BoxedSignalStorage, { fn into_dyn_node(self) -> dioxus_core::DynamicNode { self.with(|f| f.clone().into_dyn_node()) } } -impl Deref for ReadSignal { +impl> Deref for ReadSignal { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { @@ -104,9 +121,9 @@ impl Deref for ReadSignal { } } -impl Readable for ReadSignal { +impl> Readable for ReadSignal { type Target = T; - type Storage = UnsyncStorage; + type Storage = S; #[track_caller] fn try_read_unchecked( @@ -143,9 +160,13 @@ impl Readable for ReadSignal { // We can't implement From > for ReadSignal // because it would conflict with the From for T implementation, but we can implement it for // all specific readable types -impl From> for ReadSignal { - fn from(value: Signal) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage>, + > From> for ReadSignal +{ + fn from(value: Signal) -> Self { + Self::new_maybe_sync(value) } } impl From> for ReadSignal { @@ -153,9 +174,13 @@ impl From> for ReadSignal { Self::new(value) } } -impl From> for ReadSignal { - fn from(value: CopyValue) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage, + > From> for ReadSignal +{ + fn from(value: CopyValue) -> Self { + Self::new_maybe_sync(value) } } impl From> for ReadSignal @@ -167,38 +192,41 @@ where Self::new(value) } } -impl From> for ReadSignal +impl From> for ReadSignal where O: ?Sized + 'static, - V: Readable + 'static, + V: Readable + 'static, F: Fn(&V::Target) -> &O + 'static, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, { fn from(value: MappedSignal) -> Self { - Self::new(value) + Self::new_maybe_sync(value) } } -impl From> for ReadSignal +impl From> for ReadSignal where O: ?Sized + 'static, - V: Readable + 'static, + V: Readable + 'static, F: Fn(&V::Target) -> &O + 'static, FMut: 'static, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, { fn from(value: MappedMutSignal) -> Self { - Self::new(value) + Self::new_maybe_sync(value) } } -impl From> for ReadSignal { - fn from(value: WriteSignal) -> Self { - Self::new(value) +impl From> for ReadSignal +where + S: BoxedSignalStorage + CreateBoxedSignalStorage>, +{ + fn from(value: WriteSignal) -> Self { + Self::new_maybe_sync(value) } } /// A boxed version of [Writable] that can be used to store any writable type. -pub struct WriteSignal { - value: CopyValue< - Box>>, - >, +pub struct WriteSignal = UnsyncStorage> { + value: CopyValue>, S>, } impl WriteSignal { @@ -206,8 +234,19 @@ impl WriteSignal { pub fn new( value: impl Writable + 'static, ) -> Self { + Self::new_maybe_sync(value) + } +} + +impl> WriteSignal { + /// Create a new boxed writable value which may be sync + pub fn new_maybe_sync(value: R) -> Self + where + R: Writable, + S: CreateBoxedSignalStorage, + { Self { - value: CopyValue::new(Box::new(BoxWriteMetadata::new(value))), + value: CopyValue::new_maybe_sync(S::new_writable(value, sealed::SealedToken)), } } } @@ -272,42 +311,44 @@ where } } -impl Clone for WriteSignal { +impl> Clone for WriteSignal { fn clone(&self) -> Self { *self } } -impl Copy for WriteSignal {} +impl> Copy for WriteSignal {} -impl PartialEq for WriteSignal { +impl> PartialEq for WriteSignal { fn eq(&self, other: &Self) -> bool { self.value == other.value } } -read_impls!(WriteSignal); -write_impls!(WriteSignal); +read_impls!(WriteSignal>); +write_impls!(WriteSignal>); -impl IntoAttributeValue for WriteSignal +impl IntoAttributeValue for WriteSignal where T: Clone + IntoAttributeValue + 'static, + S: BoxedSignalStorage, { fn into_value(self) -> dioxus_core::AttributeValue { self.with(|f| f.clone().into_value()) } } -impl IntoDynNode for WriteSignal +impl IntoDynNode for WriteSignal where T: Clone + IntoDynNode + 'static, + S: BoxedSignalStorage, { fn into_dyn_node(self) -> dioxus_core::DynamicNode { self.with(|f| f.clone().into_dyn_node()) } } -impl Deref for WriteSignal { +impl> Deref for WriteSignal { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { @@ -315,9 +356,9 @@ impl Deref for WriteSignal { } } -impl Readable for WriteSignal { +impl> Readable for WriteSignal { type Target = T; - type Storage = UnsyncStorage; + type Storage = S; #[track_caller] fn try_read_unchecked( @@ -351,7 +392,7 @@ impl Readable for WriteSignal { } } -impl Writable for WriteSignal { +impl> Writable for WriteSignal { type WriteMetadata = Box; fn try_write_unchecked( @@ -370,14 +411,22 @@ impl Writable for WriteSignal { // We can't implement From> for Write // because it would conflict with the From for T implementation, but we can implement it for // all specific readable types -impl From> for WriteSignal { - fn from(value: Signal) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage>, + > From> for WriteSignal +{ + fn from(value: Signal) -> Self { + Self::new_maybe_sync(value) } } -impl From> for WriteSignal { - fn from(value: CopyValue) -> Self { - Self::new(value) +impl< + T: 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage + Storage, + > From> for WriteSignal +{ + fn from(value: CopyValue) -> Self { + Self::new_maybe_sync(value) } } impl From> for WriteSignal @@ -389,14 +438,127 @@ where Self::new(value) } } -impl From> for WriteSignal +impl From> for WriteSignal where O: ?Sized + 'static, - V: Writable + 'static, + V: Writable + 'static, F: Fn(&V::Target) -> &O + 'static, FMut: Fn(&mut V::Target) -> &mut O + 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage, { fn from(value: MappedMutSignal) -> Self { - Self::new(value) + Self::new_maybe_sync(value) } } + +/// A trait for creating boxed readable and writable signals. This is implemented for +/// [UnsyncStorage] and [SyncStorage]. +/// +/// You may need to add this trait as a bound when you use [ReadSignal] or [WriteSignal] while +/// remaining generic over syncness. +pub trait BoxedSignalStorage: + Storage>> + + Storage>> + + sealed::Sealed + + 'static +{ + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + type DynReadable: Readable + ?Sized; + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + type DynWritable: Writable> + + ?Sized; +} + +/// A trait for creating boxed readable and writable signals. This is implemented for +/// [UnsyncStorage] and [SyncStorage]. +/// +/// The storage type must implement `CreateReadOnlySignalStorage` for every readable `T` type +/// to be used with `ReadSignal` and `WriteSignal`. +/// +/// You may need to add this trait as a bound when you call [ReadSignal::new_maybe_sync] or +/// [WriteSignal::new_maybe_sync] while remaining generic over syncness. +pub trait CreateBoxedSignalStorage: + BoxedSignalStorage + 'static +{ + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + fn new_readable( + value: T, + _: sealed::SealedToken, + ) -> Box> + where + T: Sized; + + // This is not a public api, and is sealed to prevent external usage and implementations + #[doc(hidden)] + fn new_writable( + value: T, + _: sealed::SealedToken, + ) -> Box> + where + T: Writable + Sized; +} + +impl BoxedSignalStorage for UnsyncStorage { + type DynReadable = dyn Readable; + type DynWritable = + dyn Writable>; +} + +impl + ?Sized + 'static> CreateBoxedSignalStorage + for UnsyncStorage +{ + fn new_readable(value: T, _: sealed::SealedToken) -> Box> + where + T: Sized, + { + Box::new(value) + } + + fn new_writable(value: T, _: sealed::SealedToken) -> Box> + where + T: Writable + Sized, + { + Box::new(BoxWriteMetadata::new(value)) + } +} + +impl BoxedSignalStorage for SyncStorage { + type DynReadable = + dyn Readable + Send + Sync; + type DynWritable = + dyn Writable> + Send + Sync; +} + +impl + Sync + Send + ?Sized + 'static> + CreateBoxedSignalStorage for SyncStorage +{ + fn new_readable(value: T, _: sealed::SealedToken) -> Box> + where + T: Sized, + { + Box::new(value) + } + + fn new_writable(value: T, _: sealed::SealedToken) -> Box> + where + T: Writable + Sized, + { + Box::new(BoxWriteMetadata::new(value)) + } +} + +mod sealed { + use generational_box::{SyncStorage, UnsyncStorage}; + + pub trait Sealed {} + impl Sealed for UnsyncStorage {} + impl Sealed for SyncStorage {} + + pub struct SealedToken; + + pub trait SealedTokenTrait {} + impl SealedTokenTrait for SealedToken {} +} diff --git a/packages/stores/src/store.rs b/packages/stores/src/store.rs index e731d8a4b3..cfc420ed54 100644 --- a/packages/stores/src/store.rs +++ b/packages/stores/src/store.rs @@ -6,9 +6,10 @@ use dioxus_core::{ use_hook, AttributeValue, DynamicNode, IntoAttributeValue, IntoDynNode, Subscribers, SuperInto, }; use dioxus_signals::{ - read_impls, write_impls, BorrowError, BorrowMutError, CopyValue, Global, - InitializeFromFunction, MappedMutSignal, ReadSignal, Readable, ReadableExt, ReadableRef, - Storage, UnsyncStorage, Writable, WritableExt, WritableRef, WriteSignal, + read_impls, write_impls, BorrowError, BorrowMutError, BoxedSignalStorage, CopyValue, + CreateBoxedSignalStorage, Global, InitializeFromFunction, MappedMutSignal, ReadSignal, + Readable, ReadableExt, ReadableRef, Storage, UnsyncStorage, Writable, WritableExt, WritableRef, + WriteSignal, }; use std::marker::PhantomData; @@ -21,10 +22,10 @@ pub(crate) type MappedStore< > = Store>; /// A type alias for a boxed read-only store. -pub type ReadStore = Store>; +pub type ReadStore = Store>; /// A type alias for a boxed writable-only store. -pub type WriteStore = Store>; +pub type WriteStore = Store>; /// Stores are a reactive type built for nested data structures. Each store will lazily create signals /// for each field/member of the data structure as needed. @@ -181,12 +182,13 @@ where } impl Copy for Store where Lens: Copy {} -impl<__F, __FMut, T: ?Sized, Lens> ::std::convert::From> - for Store> +impl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From> + for WriteStore where - Lens: Writable + 'static, + Lens: Writable + 'static, __F: Fn(&Lens::Target) -> &T + 'static, __FMut: Fn(&mut Lens::Target) -> &mut T + 'static, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, T: 'static, { fn from(value: MappedStore) -> Self { @@ -196,12 +198,13 @@ where } } } -impl<__F, __FMut, T: ?Sized, Lens> ::std::convert::From> - for Store> +impl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From> + for ReadStore where - Lens: Writable + 'static, + Lens: Writable + 'static, __F: Fn(&Lens::Target) -> &T + 'static, __FMut: Fn(&mut Lens::Target) -> &mut T + 'static, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, T: 'static, { fn from(value: MappedStore) -> Self { @@ -211,11 +214,36 @@ where } } } -impl ::std::convert::From> for ReadStore +impl ::std::convert::From> for ReadStore where T: ?Sized + 'static, + S: BoxedSignalStorage + CreateBoxedSignalStorage>, { - fn from(value: Store) -> Self { + fn from(value: Store>) -> Self { + Self { + selector: value.selector.map_writer(::std::convert::Into::into), + _phantom: ::std::marker::PhantomData, + } + } +} +impl ::std::convert::From>> for ReadStore +where + T: 'static, + S: BoxedSignalStorage + CreateBoxedSignalStorage> + Storage, +{ + fn from(value: Store>) -> Self { + Self { + selector: value.selector.map_writer(::std::convert::Into::into), + _phantom: ::std::marker::PhantomData, + } + } +} +impl ::std::convert::From>> for WriteStore +where + T: 'static, + S: BoxedSignalStorage + CreateBoxedSignalStorage> + Storage, +{ + fn from(value: Store>) -> Self { Self { selector: value.selector.map_writer(::std::convert::Into::into), _phantom: ::std::marker::PhantomData, @@ -225,25 +253,27 @@ where #[doc(hidden)] pub struct SuperIntoReadSignalMarker; -impl SuperInto, SuperIntoReadSignalMarker> for Store +impl SuperInto, SuperIntoReadSignalMarker> for Store where T: ?Sized + 'static, - Lens: Readable + 'static, + Lens: Readable + 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage, { - fn super_into(self) -> ReadSignal { - ReadSignal::new(self) + fn super_into(self) -> ReadSignal { + ReadSignal::new_maybe_sync(self) } } #[doc(hidden)] pub struct SuperIntoWriteSignalMarker; -impl SuperInto, SuperIntoWriteSignalMarker> for Store +impl SuperInto, SuperIntoWriteSignalMarker> for Store where T: ?Sized + 'static, - Lens: Writable + 'static, + Lens: Writable + 'static, + S: CreateBoxedSignalStorage> + BoxedSignalStorage, { - fn super_into(self) -> WriteSignal { - WriteSignal::new(self) + fn super_into(self) -> WriteSignal { + WriteSignal::new_maybe_sync(self) } }