diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 46d005007..268569b10 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -198,36 +198,6 @@ impl Clone for GenerationalBox { /// A trait for a storage backing type. (RefCell, RwLock, etc.) pub trait Storage: AnyStorage + 'static { - /// The reference this storage type returns. - type Ref: Deref; - /// The mutable reference this storage type returns. - type Mut: DerefMut; - - /// Try to map the mutable ref. - fn try_map_mut( - mut_ref: Self::Mut, - f: impl FnOnce(&mut T) -> Option<&mut U>, - ) -> Option>; - - /// Map the mutable ref. - fn map_mut( - mut_ref: Self::Mut, - f: impl FnOnce(&mut T) -> &mut U, - ) -> Self::Mut { - Self::try_map_mut(mut_ref, |v| Some(f(v))).unwrap() - } - - /// Try to map the ref. - fn try_map( - ref_: Self::Ref, - f: impl FnOnce(&T) -> Option<&U>, - ) -> Option>; - - /// Map the ref. - fn map(ref_: Self::Ref, f: impl FnOnce(&T) -> &U) -> Self::Ref { - Self::try_map(ref_, |v| Some(f(v))).unwrap() - } - /// Try to read the value. Returns None if the value is no longer valid. fn try_read( &'static self, @@ -246,6 +216,36 @@ pub trait Storage: AnyStorage + 'static { /// A trait for any storage backing type. pub trait AnyStorage: Default { + /// The reference this storage type returns. + type Ref: Deref; + /// The mutable reference this storage type returns. + type Mut: DerefMut; + + /// Try to map the mutable ref. + fn try_map_mut( + mut_ref: Self::Mut, + f: impl FnOnce(&mut T) -> Option<&mut U>, + ) -> Option>; + + /// Map the mutable ref. + fn map_mut( + mut_ref: Self::Mut, + f: impl FnOnce(&mut T) -> &mut U, + ) -> Self::Mut { + Self::try_map_mut(mut_ref, |v| Some(f(v))).unwrap() + } + + /// Try to map the ref. + fn try_map( + ref_: Self::Ref, + f: impl FnOnce(&T) -> Option<&U>, + ) -> Option>; + + /// Map the ref. + fn map(ref_: Self::Ref, f: impl FnOnce(&T) -> &U) -> Self::Ref { + Self::try_map(ref_, |v| Some(f(v))).unwrap() + } + /// Get the data pointer. No guarantees are made about the data pointer. It should only be used for debugging. fn data_ptr(&self) -> *const (); diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 007a820a3..9e6532485 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -20,6 +20,54 @@ fn sync_runtime() -> &'static Arc>>> { } impl AnyStorage for SyncStorage { + type Ref = GenerationalRef>; + type Mut = GenerationalRefMut>; + + fn try_map( + ref_: Self::Ref, + f: impl FnOnce(&I) -> Option<&U>, + ) -> Option> { + let GenerationalRef { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + .. + } = ref_; + MappedRwLockReadGuard::try_map(inner, f) + .ok() + .map(|inner| GenerationalRef { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: crate::GenerationalRefBorrowInfo { + borrowed_at: borrow.borrowed_at, + borrowed_from: borrow.borrowed_from, + created_at: borrow.created_at, + }, + }) + } + + fn try_map_mut( + mut_ref: Self::Mut, + f: impl FnOnce(&mut I) -> Option<&mut U>, + ) -> Option> { + let GenerationalRefMut { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + .. + } = mut_ref; + MappedRwLockWriteGuard::try_map(inner, f) + .ok() + .map(|inner| GenerationalRefMut { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: crate::GenerationalRefMutBorrowInfo { + borrowed_from: borrow.borrowed_from, + created_at: borrow.created_at, + }, + }) + } + fn data_ptr(&self) -> *const () { self.0.data_ptr() as *const () } @@ -49,9 +97,6 @@ impl AnyStorage for SyncStorage { } impl Storage for SyncStorage { - type Ref = GenerationalRef>; - type Mut = GenerationalRefMut>; - fn try_read( &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -117,49 +162,4 @@ impl Storage for SyncStorage { fn set(&self, value: T) { *self.0.write() = Some(Box::new(value)); } - - fn try_map( - ref_: Self::Ref, - f: impl FnOnce(&I) -> Option<&U>, - ) -> Option> { - let GenerationalRef { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - .. - } = ref_; - MappedRwLockReadGuard::try_map(inner, f) - .ok() - .map(|inner| GenerationalRef { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: crate::GenerationalRefBorrowInfo { - borrowed_at: borrow.borrowed_at, - borrowed_from: borrow.borrowed_from, - created_at: borrow.created_at, - }, - }) - } - - fn try_map_mut( - mut_ref: Self::Mut, - f: impl FnOnce(&mut I) -> Option<&mut U>, - ) -> Option> { - let GenerationalRefMut { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - .. - } = mut_ref; - MappedRwLockWriteGuard::try_map(inner, f) - .ok() - .map(|inner| GenerationalRefMut { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: crate::GenerationalRefMutBorrowInfo { - borrowed_from: borrow.borrowed_from, - created_at: borrow.created_at, - }, - }) - } } diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index 8c13d357c..c01417656 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -10,9 +10,6 @@ use std::cell::{Ref, RefCell, RefMut}; pub struct UnsyncStorage(RefCell>>); impl Storage for UnsyncStorage { - type Ref = GenerationalRef>; - type Mut = GenerationalRefMut>; - fn try_read( &'static self, @@ -78,6 +75,15 @@ impl Storage for UnsyncStorage { fn set(&self, value: T) { *self.0.borrow_mut() = Some(Box::new(value)); } +} + +thread_local! { + static UNSYNC_RUNTIME: RefCell>> = RefCell::new(Vec::new()); +} + +impl AnyStorage for UnsyncStorage { + type Ref = GenerationalRef>; + type Mut = GenerationalRefMut>; fn try_map( _self: Self::Ref, @@ -96,7 +102,7 @@ impl Storage for UnsyncStorage { }) } - fn try_map_mut( + fn try_map_mut( mut_ref: Self::Mut, f: impl FnOnce(&mut I) -> Option<&mut U>, ) -> Option> { @@ -117,13 +123,7 @@ impl Storage for UnsyncStorage { }, }) } -} -thread_local! { - static UNSYNC_RUNTIME: RefCell>> = RefCell::new(Vec::new()); -} - -impl AnyStorage for UnsyncStorage { fn data_ptr(&self) -> *const () { self.0.as_ptr() as *const () } diff --git a/packages/signals/src/comparer.rs b/packages/signals/src/comparer.rs index 1c9de1cbc..e8954ee9d 100644 --- a/packages/signals/src/comparer.rs +++ b/packages/signals/src/comparer.rs @@ -1,3 +1,4 @@ +use crate::write::Writable; use std::hash::Hash; use dioxus_core::prelude::*; @@ -27,14 +28,14 @@ impl Comparer { if let Some(previous) = previous.take() { if let Some(value) = subscribers.get(&previous) { - *value.write_unchecked() = false; + *value.write() = false; } } let current = f(); if let Some(value) = subscribers.get(¤t) { - *value.write_unchecked() = true; + *value.write() = true; } *previous = Some(current); @@ -59,14 +60,14 @@ impl>> Comparer { if let Some(previous) = previous.take() { if let Some(value) = subscribers.get(&previous) { - *value.write_unchecked() = false; + *value.write() = false; } } let current = f(); if let Some(value) = subscribers.get(¤t) { - *value.write_unchecked() = true; + *value.write() = true; } *previous = Some(current); diff --git a/packages/signals/src/global.rs b/packages/signals/src/global.rs deleted file mode 100644 index ba4a4e9e5..000000000 --- a/packages/signals/src/global.rs +++ /dev/null @@ -1,327 +0,0 @@ -use dioxus_core::prelude::{ - provide_root_context, try_consume_context, IntoAttributeValue, ScopeId, -}; -use generational_box::GenerationalRef; -use std::{ - any::Any, - cell::{Ref, RefCell}, - collections::HashMap, - mem::MaybeUninit, - ops::Deref, - rc::Rc, -}; - -use crate::{MappedSignal, ReadOnlySignal, Signal, Write}; - -/// A signal that can be accessed from anywhere in the application and created in a static -pub struct GlobalSignal { - initializer: fn() -> T, -} - -#[derive(Clone)] -struct GlobalSignalContext { - signal: Rc>>>, -} - -fn get_global_context() -> GlobalSignalContext { - match try_consume_context() { - Some(context) => context, - None => { - let context = GlobalSignalContext { - signal: Rc::new(RefCell::new(HashMap::new())), - }; - provide_root_context(context).unwrap() - } - } -} - -impl GlobalSignal { - /// Create a new global signal with the given initializer. - pub const fn new(initializer: fn() -> T) -> GlobalSignal { - GlobalSignal { initializer } - } - - /// Get the signal that backs this global. - pub fn signal(&self) -> Signal { - let key = self as *const _ as *const (); - let context = get_global_context(); - let read = context.signal.borrow(); - - match read.get(&key) { - Some(signal) => *signal.downcast_ref::>().unwrap(), - None => { - drop(read); - - // Constructors are always run in the root scope - // The signal also exists in the root scope - let value = ScopeId::ROOT.in_runtime(self.initializer); - let signal = Signal::new_in_scope(value, ScopeId::ROOT); - - let entry = context.signal.borrow_mut().insert(key, Box::new(signal)); - debug_assert!(entry.is_none(), "Global signal already exists"); - - signal - } - } - } - - /// Get the scope the signal was created in. - pub fn origin_scope(&self) -> ScopeId { - ScopeId::ROOT - } - - /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead. - /// - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn read(&self) -> GenerationalRef> { - self.signal().read() - } - - /// 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> { - self.signal().peek() - } - - /// Get a mutable reference to the signal's value. - /// - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn write(&self) -> Write { - self.signal().write() - } - - /// Set the value of the signal. This will trigger an update on all subscribers. - #[track_caller] - pub fn set(&self, value: T) { - self.signal().set(value); - } - - /// Set the value of the signal without triggering an update on subscribers. - #[track_caller] - pub fn set_untracked(&self, value: T) { - self.signal().set_untracked(value); - } - - /// Run a closure with a reference to the signal's value. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { - self.signal().with(f) - } - - /// Run a closure with a mutable reference to the signal's value. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { - self.signal().with_mut(f) - } - - /// Map the signal to a new type. - pub fn map( - &self, - f: impl Fn(&T) -> &O + 'static, - ) -> MappedSignal>> { - MappedSignal::new(self.signal(), f) - } - - /// Get the generational id of the signal. - pub fn id(&self) -> generational_box::GenerationalBoxId { - self.signal().id() - } -} - -impl IntoAttributeValue for GlobalSignal -where - T: Clone + IntoAttributeValue, -{ - fn into_value(self) -> dioxus_core::AttributeValue { - self.signal().into_value() - } -} - -impl GlobalSignal { - /// Get the current value of the signal. This will subscribe the current scope to the signal. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn cloned(&self) -> T { - self.read().clone() - } -} - -impl GlobalSignal { - /// Invert the boolean value of the signal. This will trigger an update on all subscribers. - pub fn toggle(&self) { - self.set(!self.cloned()); - } -} - -impl PartialEq for GlobalSignal { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} - -/// Allow calling a signal with signal() syntax -/// -/// Currently only limited to copy types, though could probably specialize for string/arc/rc -impl Deref for GlobalSignal { - type Target = dyn Fn() -> T; - - fn deref(&self) -> &Self::Target { - // https://github.com/dtolnay/case-studies/tree/master/callable-types - - // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit). - let uninit_callable = MaybeUninit::::uninit(); - // Then move that value into the closure. We assume that the closure now has a in memory layout of Self. - let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone(); - - // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure. - let size_of_closure = std::mem::size_of_val(&uninit_closure); - assert_eq!(size_of_closure, std::mem::size_of::()); - - // Then cast the lifetime of the closure to the lifetime of &self. - fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T { - b - } - let reference_to_closure = cast_lifetime( - { - // The real closure that we will never use. - &uninit_closure - }, - // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self. - unsafe { std::mem::transmute(self) }, - ); - - // Cast the closure to a trait object. - reference_to_closure as &Self::Target - } -} - -/// A signal that can be accessed from anywhere in the application and created in a static -pub struct GlobalMemo { - selector: fn() -> T, -} - -impl GlobalMemo { - /// Create a new global signal - pub const fn new(selector: fn() -> T) -> GlobalMemo - where - T: PartialEq, - { - GlobalMemo { selector } - } - - /// Get the signal that backs this global. - pub fn signal(&self) -> ReadOnlySignal { - let key = self as *const _ as *const (); - - let context = get_global_context(); - - let read = context.signal.borrow(); - match read.get(&key) { - Some(signal) => *signal.downcast_ref::>().unwrap(), - None => { - drop(read); - // Constructors are always run in the root scope - let signal = ScopeId::ROOT.in_runtime(|| Signal::selector(self.selector)); - context.signal.borrow_mut().insert(key, Box::new(signal)); - signal - } - } - } - - /// Get the scope the signal was created in. - pub fn origin_scope(&self) -> ScopeId { - ScopeId::ROOT - } - - /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead. - /// - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn read(&self) -> GenerationalRef> { - self.signal().read() - } - - /// 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> { - self.signal().peek() - } - - /// Run a closure with a reference to the signal's value. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { - self.signal().with(f) - } - - /// Get the generational id of the signal. - pub fn id(&self) -> generational_box::GenerationalBoxId { - self.signal().id() - } -} - -impl IntoAttributeValue for GlobalMemo -where - T: Clone + IntoAttributeValue, -{ - fn into_value(self) -> dioxus_core::AttributeValue { - self.signal().into_value() - } -} - -impl GlobalMemo { - /// Get the current value of the signal. This will subscribe the current scope to the signal. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn cloned(&self) -> T { - self.read().clone() - } -} - -impl PartialEq for GlobalMemo { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} - -/// Allow calling a signal with signal() syntax -/// -/// Currently only limited to copy types, though could probably specialize for string/arc/rc -impl Deref for GlobalMemo { - type Target = dyn Fn() -> T; - - fn deref(&self) -> &Self::Target { - // https://github.com/dtolnay/case-studies/tree/master/callable-types - - // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit). - let uninit_callable = MaybeUninit::::uninit(); - // Then move that value into the closure. We assume that the closure now has a in memory layout of Self. - let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone(); - - // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure. - let size_of_closure = std::mem::size_of_val(&uninit_closure); - assert_eq!(size_of_closure, std::mem::size_of::()); - - // Then cast the lifetime of the closure to the lifetime of &self. - fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T { - b - } - let reference_to_closure = cast_lifetime( - { - // The real closure that we will never use. - &uninit_closure - }, - // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self. - unsafe { std::mem::transmute(self) }, - ); - - // Cast the closure to a trait object. - reference_to_closure as &Self::Target - } -} diff --git a/packages/signals/src/global/memo.rs b/packages/signals/src/global/memo.rs new file mode 100644 index 000000000..b34eff989 --- /dev/null +++ b/packages/signals/src/global/memo.rs @@ -0,0 +1,128 @@ +use crate::read::Readable; +use dioxus_core::prelude::{IntoAttributeValue, ScopeId}; +use generational_box::{AnyStorage, UnsyncStorage}; +use std::{mem::MaybeUninit, ops::Deref}; + +use crate::{ReadOnlySignal, Signal}; + +use super::get_global_context; + +/// A signal that can be accessed from anywhere in the application and created in a static +pub struct GlobalMemo { + selector: fn() -> T, +} + +impl GlobalMemo { + /// Create a new global signal + pub const fn new(selector: fn() -> T) -> GlobalMemo + where + T: PartialEq, + { + GlobalMemo { selector } + } + + /// Get the signal that backs this global. + pub fn signal(&self) -> ReadOnlySignal { + let key = self as *const _ as *const (); + + let context = get_global_context(); + + let read = context.signal.borrow(); + match read.get(&key) { + Some(signal) => *signal.downcast_ref::>().unwrap(), + None => { + drop(read); + // Constructors are always run in the root scope + let signal = ScopeId::ROOT.in_runtime(|| Signal::selector(self.selector)); + context.signal.borrow_mut().insert(key, Box::new(signal)); + signal + } + } + } + + /// Get the scope the signal was created in. + pub fn origin_scope(&self) -> ScopeId { + ScopeId::ROOT + } + + /// Get the generational id of the signal. + pub fn id(&self) -> generational_box::GenerationalBoxId { + self.signal().id() + } +} + +impl Readable for GlobalMemo { + type Ref = generational_box::GenerationalRef>; + + fn map_ref &U>(ref_: Self::Ref, f: F) -> Self::Ref { + ::map(ref_, f) + } + + fn try_map_ref Option<&U>>( + ref_: Self::Ref, + f: F, + ) -> Option> { + ::try_map(ref_, f) + } + + #[track_caller] + fn read(&self) -> Self::Ref { + self.signal().read() + } + + #[track_caller] + fn peek(&self) -> Self::Ref { + self.signal().peek() + } +} + +impl IntoAttributeValue for GlobalMemo +where + T: Clone + IntoAttributeValue, +{ + fn into_value(self) -> dioxus_core::AttributeValue { + self.signal().into_value() + } +} + +impl PartialEq for GlobalMemo { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } +} + +/// Allow calling a signal with signal() syntax +/// +/// Currently only limited to copy types, though could probably specialize for string/arc/rc +impl Deref for GlobalMemo { + type Target = dyn Fn() -> T; + + fn deref(&self) -> &Self::Target { + // https://github.com/dtolnay/case-studies/tree/master/callable-types + + // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit). + let uninit_callable = MaybeUninit::::uninit(); + // Then move that value into the closure. We assume that the closure now has a in memory layout of Self. + let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone(); + + // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure. + let size_of_closure = std::mem::size_of_val(&uninit_closure); + assert_eq!(size_of_closure, std::mem::size_of::()); + + // Then cast the lifetime of the closure to the lifetime of &self. + fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T { + b + } + let reference_to_closure = cast_lifetime( + { + // The real closure that we will never use. + &uninit_closure + }, + // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self. + unsafe { std::mem::transmute(self) }, + ); + + // Cast the closure to a trait object. + reference_to_closure as &Self::Target + } +} diff --git a/packages/signals/src/global/mod.rs b/packages/signals/src/global/mod.rs new file mode 100644 index 000000000..e4d6e9222 --- /dev/null +++ b/packages/signals/src/global/mod.rs @@ -0,0 +1,25 @@ +use dioxus_core::prelude::{provide_root_context, try_consume_context}; +use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc}; + +mod memo; +pub use memo::*; + +mod signal; +pub use signal::*; + +#[derive(Clone)] +pub(crate) struct GlobalSignalContext { + signal: Rc>>>, +} + +pub(crate) fn get_global_context() -> GlobalSignalContext { + match try_consume_context() { + Some(context) => context, + None => { + let context = GlobalSignalContext { + signal: Rc::new(RefCell::new(HashMap::new())), + }; + provide_root_context(context).unwrap() + } + } +} diff --git a/packages/signals/src/global/signal.rs b/packages/signals/src/global/signal.rs new file mode 100644 index 000000000..e6896d399 --- /dev/null +++ b/packages/signals/src/global/signal.rs @@ -0,0 +1,145 @@ +use crate::read::Readable; +use crate::write::Writable; +use dioxus_core::prelude::{IntoAttributeValue, ScopeId}; +use generational_box::{AnyStorage, GenerationalRef, UnsyncStorage}; +use std::{cell::Ref, mem::MaybeUninit, ops::Deref}; + +use super::get_global_context; +use crate::{MappedSignal, Signal}; + +/// A signal that can be accessed from anywhere in the application and created in a static +pub struct GlobalSignal { + initializer: fn() -> T, +} + +impl GlobalSignal { + /// Create a new global signal with the given initializer. + pub const fn new(initializer: fn() -> T) -> GlobalSignal { + GlobalSignal { initializer } + } + + /// Get the signal that backs this global. + pub fn signal(&self) -> Signal { + let key = self as *const _ as *const (); + let context = get_global_context(); + let read = context.signal.borrow(); + + match read.get(&key) { + Some(signal) => *signal.downcast_ref::>().unwrap(), + None => { + drop(read); + + // Constructors are always run in the root scope + // The signal also exists in the root scope + let value = ScopeId::ROOT.in_runtime(self.initializer); + let signal = Signal::new_in_scope(value, ScopeId::ROOT); + + let entry = context.signal.borrow_mut().insert(key, Box::new(signal)); + debug_assert!(entry.is_none(), "Global signal already exists"); + + signal + } + } + } + + /// Get the scope the signal was created in. + pub fn origin_scope(&self) -> ScopeId { + ScopeId::ROOT + } + + /// Run a closure with a mutable reference to the signal's value. + /// If the signal has been dropped, this will panic. + #[track_caller] + pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { + self.signal().with_mut(f) + } + + /// Map the signal to a new type. + pub fn map( + &self, + f: impl Fn(&T) -> &O + 'static, + ) -> MappedSignal>> { + MappedSignal::new(self.signal(), f) + } + + /// Get the generational id of the signal. + pub fn id(&self) -> generational_box::GenerationalBoxId { + self.signal().id() + } +} + +impl Readable for GlobalSignal { + type Ref = generational_box::GenerationalRef>; + + fn map_ref &U>(ref_: Self::Ref, f: F) -> Self::Ref { + ::map(ref_, f) + } + + fn try_map_ref Option<&U>>( + ref_: Self::Ref, + f: F, + ) -> Option> { + ::try_map(ref_, f) + } + + #[track_caller] + fn read(&self) -> Self::Ref { + self.signal().read() + } + + #[track_caller] + fn peek(&self) -> Self::Ref { + self.signal().peek() + } +} + +impl IntoAttributeValue for GlobalSignal +where + T: Clone + IntoAttributeValue, +{ + fn into_value(self) -> dioxus_core::AttributeValue { + self.signal().into_value() + } +} + +impl PartialEq for GlobalSignal { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } +} + +/// Allow calling a signal with signal() syntax +/// +/// Currently only limited to copy types, though could probably specialize for string/arc/rc +impl Deref for GlobalSignal { + type Target = dyn Fn() -> T; + + fn deref(&self) -> &Self::Target { + // https://github.com/dtolnay/case-studies/tree/master/callable-types + + // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit). + let uninit_callable = MaybeUninit::::uninit(); + // Then move that value into the closure. We assume that the closure now has a in memory layout of Self. + let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone(); + + // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure. + let size_of_closure = std::mem::size_of_val(&uninit_closure); + assert_eq!(size_of_closure, std::mem::size_of::()); + + // Then cast the lifetime of the closure to the lifetime of &self. + fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T { + b + } + let reference_to_closure = cast_lifetime( + { + // The real closure that we will never use. + &uninit_closure + }, + // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self. + unsafe { std::mem::transmute(self) }, + ); + + // Cast the closure to a trait object. + reference_to_closure as &Self::Target + } +} diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 4fb7008c9..cf2069814 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,7 +1,9 @@ +use crate::read::Readable; use crate::rt::CopyValue; -use crate::signal::{ReadOnlySignal, Signal, Write}; -use crate::{GlobalMemo, GlobalSignal, SignalData}; -use generational_box::Storage; +use crate::signal::{Signal, Write}; +use crate::write::Writable; +use crate::{GlobalMemo, GlobalSignal, ReadOnlySignal, SignalData}; +use generational_box::{AnyStorage, Storage}; use generational_box::{GenerationalRef, UnsyncStorage}; use std::cell::Ref; @@ -331,7 +333,7 @@ read_impls!(GlobalSignal); impl GlobalSignal> { /// Read a value from the inner vector. pub fn get(&'static self, index: usize) -> Option>> { - ::try_map(self.read(), move |v| v.get(index)) + ::try_map(self.read(), move |v| v.get(index)) } } @@ -346,7 +348,7 @@ impl GlobalSignal> { /// Attempts to read the inner value of the Option. pub fn as_ref(&'static self) -> Option>> { - ::try_map(self.read(), |v| v.as_ref()) + ::try_map(self.read(), |v| v.as_ref()) } } @@ -377,9 +379,9 @@ impl GlobalSignal> { if borrow.is_none() { drop(borrow); self.with_mut(|v| *v = Some(default())); - ::map(self.read(), |v| v.as_ref().unwrap()) + ::map(self.read(), |v| v.as_ref().unwrap()) } else { - ::map(borrow, |v| v.as_ref().unwrap()) + ::map(borrow, |v| v.as_ref().unwrap()) } } } @@ -389,7 +391,7 @@ read_impls!(GlobalMemo: PartialEq); impl GlobalMemo> { /// Read a value from the inner vector. pub fn get(&'static self, index: usize) -> Option>> { - ::try_map(self.read(), move |v| v.get(index)) + ::try_map(self.read(), move |v| v.get(index)) } } @@ -404,7 +406,7 @@ impl GlobalMemo> { /// Attempts to read the inner value of the Option. pub fn as_ref(&'static self) -> Option>> { - ::try_map(self.read(), |v| v.as_ref()) + ::try_map(self.read(), |v| v.as_ref()) } } @@ -482,14 +484,14 @@ impl>>> IntoIterator for Signal, impl>>> Signal, S> { /// Returns a reference to an element or `None` if out of bounds. - pub fn get_mut(&mut self, index: usize) -> Option>> { + pub fn get_mut(&mut self, index: usize) -> Option> { Write::filter_map(self.write(), |v| v.get_mut(index)) } } impl>>> Signal, S> { /// Returns a reference to an element or `None` if out of bounds. - pub fn as_mut(&mut self) -> Option>> { + pub fn as_mut(&mut self) -> Option> { Write::filter_map(self.write(), |v| v.as_mut()) } } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index c1117df06..5424e9903 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -16,6 +16,9 @@ pub use selector::*; pub(crate) mod signal; pub use signal::*; +mod read_only_signal; +pub use read_only_signal::*; + mod dependency; pub use dependency::*; @@ -30,3 +33,9 @@ pub use global::*; mod impls; pub use generational_box::{Storage, SyncStorage, UnsyncStorage}; + +mod read; +pub use read::*; + +mod write; +pub use write::*; diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index 163959c3c..640ca9604 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -1,3 +1,4 @@ +use crate::read::Readable; use crate::CopyValue; use crate::Signal; use crate::SignalData; diff --git a/packages/signals/src/read.rs b/packages/signals/src/read.rs new file mode 100644 index 000000000..604a5efac --- /dev/null +++ b/packages/signals/src/read.rs @@ -0,0 +1,72 @@ +use std::ops::Deref; + +pub trait Readable { + type Ref: Deref; + + fn map_ref &U>(ref_: Self::Ref, f: F) -> Self::Ref; + + fn try_map_ref Option<&U>>( + ref_: Self::Ref, + f: F, + ) -> Option>; + + fn read(&self) -> Self::Ref; + + fn peek(&self) -> Self::Ref; + + fn cloned(&self) -> T + where + T: Clone, + { + self.read().clone() + } + + fn with(&self, f: impl FnOnce(&T) -> O) -> O { + f(&*self.read()) + } + + fn with_peek(&self, f: impl FnOnce(&T) -> O) -> O { + f(&*self.peek()) + } + + /// Index into the inner value and return a reference to the result. + #[track_caller] + fn index(&self, index: I) -> Self::Ref + where + T: std::ops::Index, + { + Self::map_ref(self.read(), |v| v.index(index)) + } +} + +pub trait ReadableVecExt: Readable> { + /// Returns the length of the inner vector. + #[track_caller] + fn len(&self) -> usize { + self.with(|v| v.len()) + } + + /// Returns true if the inner vector is empty. + #[track_caller] + fn is_empty(&self) -> bool { + self.with(|v| v.is_empty()) + } + + /// Get the first element of the inner vector. + #[track_caller] + fn first(&self) -> Option> { + Self::try_map_ref(self.read(), |v| v.first()) + } + + /// Get the last element of the inner vector. + #[track_caller] + fn last(&self) -> Option> { + Self::try_map_ref(self.read(), |v| v.last()) + } + + /// Get the element at the given index of the inner vector. + #[track_caller] + fn get(&self, index: usize) -> Option> { + Self::try_map_ref(self.read(), |v| v.get(index)) + } +} diff --git a/packages/signals/src/read_only_signal.rs b/packages/signals/src/read_only_signal.rs new file mode 100644 index 000000000..f27e238a3 --- /dev/null +++ b/packages/signals/src/read_only_signal.rs @@ -0,0 +1,119 @@ +use crate::{read::Readable, Signal, SignalData}; +use std::{mem::MaybeUninit, ops::Deref}; + +use dioxus_core::{prelude::IntoAttributeValue, ScopeId}; +use generational_box::{Storage, UnsyncStorage}; + +/// A signal that can only be read from. +pub struct ReadOnlySignal> = UnsyncStorage> { + inner: Signal, +} + +impl>> From> for ReadOnlySignal { + fn from(inner: Signal) -> Self { + Self { inner } + } +} + +impl ReadOnlySignal { + /// Create a new read-only signal. + #[track_caller] + pub fn new(signal: Signal) -> Self { + Self::new_maybe_sync(signal) + } +} + +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 } + } + + /// Get the scope that the signal was created in. + pub fn origin_scope(&self) -> ScopeId { + self.inner.origin_scope() + } + + /// Get the id of the signal. + pub fn id(&self) -> generational_box::GenerationalBoxId { + self.inner.id() + } +} + +impl>> Readable for ReadOnlySignal { + type Ref = S::Ref; + + fn map_ref &U>(ref_: Self::Ref, f: F) -> Self::Ref { + S::map(ref_, f) + } + + fn try_map_ref Option<&U>>( + ref_: Self::Ref, + f: F, + ) -> Option> { + S::try_map(ref_, f) + } + + /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead. + /// + /// If the signal has been dropped, this will panic. + #[track_caller] + fn read(&self) -> S::Ref { + self.inner.read() + } + + /// 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. + fn peek(&self) -> S::Ref { + self.inner.peek() + } +} +impl IntoAttributeValue for ReadOnlySignal +where + T: Clone + IntoAttributeValue, +{ + fn into_value(self) -> dioxus_core::AttributeValue { + self.with(|f| f.clone().into_value()) + } +} + +impl>> PartialEq for ReadOnlySignal { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl> + 'static> Deref for ReadOnlySignal { + type Target = dyn Fn() -> T; + + fn deref(&self) -> &Self::Target { + // https://github.com/dtolnay/case-studies/tree/master/callable-types + + // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit). + let uninit_callable = MaybeUninit::::uninit(); + // Then move that value into the closure. We assume that the closure now has a in memory layout of Self. + let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone(); + + // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure. + let size_of_closure = std::mem::size_of_val(&uninit_closure); + assert_eq!(size_of_closure, std::mem::size_of::()); + + // Then cast the lifetime of the closure to the lifetime of &self. + fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T { + b + } + let reference_to_closure = cast_lifetime( + { + // The real closure that we will never use. + &uninit_closure + }, + // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self. + unsafe { std::mem::transmute(self) }, + ); + + // Cast the closure to a trait object. + reference_to_closure as &Self::Target + } +} diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 7728f8c49..53ae8b8aa 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -162,12 +162,6 @@ impl> CopyValue { /// /// Note: This is completely safe because the value is stored in a generational box. The lifetime that normally is passed to the returned reference is only used as a hint to user to prevent runtime overlapping borrow panics. #[track_caller] - pub fn write_unchecked(&self) -> S::Mut { - self.value.write() - } - - /// Write the value. If the value has been dropped, this will panic. - #[track_caller] pub fn write(&self) -> S::Mut { self.value.write() } diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index ab888183a..2c8263634 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -1,3 +1,5 @@ +use crate::read::Readable; +use crate::write::Writable; use dioxus_core::prelude::*; use generational_box::Storage; diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index a702739be..309b31f95 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,7 +1,10 @@ -use crate::{Effect, EffectInner, GlobalMemo, GlobalSignal, MappedSignal}; +use crate::{ + read::Readable, write::Writable, Effect, EffectInner, GlobalMemo, GlobalSignal, MappedSignal, + ReadOnlySignal, +}; use std::{ + any::Any, cell::RefCell, - marker::PhantomData, mem::MaybeUninit, ops::{Deref, DerefMut}, rc::Rc, @@ -15,7 +18,7 @@ use dioxus_core::{ }, ScopeId, }; -use generational_box::{GenerationalBoxId, Storage, SyncStorage, UnsyncStorage}; +use generational_box::{AnyStorage, GenerationalBoxId, Storage, SyncStorage, UnsyncStorage}; use parking_lot::RwLock; use crate::{get_effect_ref, CopyValue, EffectStackRef, EFFECT_STACK}; @@ -196,14 +199,16 @@ pub struct Signal> = UnsyncStorage> { pub type SyncSignal = Signal; #[cfg(feature = "serde")] -impl serde::Serialize for Signal { +impl>> serde::Serialize for Signal { fn serialize(&self, serializer: S) -> Result { self.read().serialize(serializer) } } #[cfg(feature = "serde")] -impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Signal { +impl<'de, T: serde::Deserialize<'de> + 'static, S: Storage>> serde::Deserialize<'de> + for Signal +{ fn deserialize>(deserializer: D) -> Result { Ok(Self::new(T::deserialize(deserializer)?)) } @@ -352,11 +357,65 @@ impl>> Signal { self.inner.origin_scope() } + fn update_subscribers(&self) { + { + let inner = self.inner.read(); + for &scope_id in &*inner.subscribers.read().subscribers { + tracing::trace!( + "Write on {:?} triggered update on {:?}", + self.inner.value, + scope_id + ); + (inner.update_any)(scope_id); + } + } + + let self_read = &self.inner.read(); + let subscribers = { + let effects = &mut self_read.subscribers.write().effect_subscribers; + std::mem::take(&mut *effects) + }; + let effect_ref = &self_read.effect_ref; + for effect in subscribers { + tracing::trace!( + "Write on {:?} triggered effect {:?}", + self.inner.value, + effect + ); + effect_ref.rerun_effect(effect); + } + } + + /// Map the signal to a new type. + pub fn map(self, f: impl Fn(&T) -> &O + 'static) -> MappedSignal> { + MappedSignal::new(self, f) + } + + /// Get the generational id of the signal. + pub fn id(&self) -> generational_box::GenerationalBoxId { + self.inner.id() + } +} + +impl>> Readable for Signal { + type Ref = S::Ref; + + fn map_ref &U>(ref_: Self::Ref, f: F) -> Self::Ref { + S::map(ref_, f) + } + + fn try_map_ref Option<&U>>( + ref_: Self::Ref, + f: F, + ) -> Option> { + S::try_map(ref_, f) + } + /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead. /// /// If the signal has been dropped, this will panic. #[track_caller] - pub fn read(&self) -> S::Ref { + fn read(&self) -> S::Ref { let inner = self.inner.read(); if let Some(effect) = EFFECT_STACK.with(|stack| stack.current()) { let subscribers = inner.subscribers.read(); @@ -389,102 +448,41 @@ impl>> Signal { /// 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) -> S::Ref { + fn peek(&self) -> S::Ref { let inner = self.inner.read(); S::map(inner, |v| &v.value) } +} + +impl>> Writable for Signal { + type Mut = Write; + + fn map_mut &mut U>( + ref_: Self::Mut, + f: F, + ) -> Self::Mut { + Write::map(ref_, f) + } + + fn try_map_mut Option<&mut U>>( + ref_: Self::Mut, + f: F, + ) -> Option> { + Write::filter_map(ref_, f) + } /// Get a mutable reference to the signal's value. /// /// If the signal has been dropped, this will panic. #[track_caller] - pub fn write<'a>(&'a mut self) -> Write { - self.write_unchecked() - } - - /// Write to the value through an immutable reference. - /// - /// This is public since it's useful in many scenarios, but we generally recommend mutation through [`Self::write`] instead. - #[track_caller] - pub fn write_unchecked(&self) -> Write { + fn write(&self) -> Self::Mut { let inner = self.inner.write(); let borrow = S::map_mut(inner, |v| &mut v.value); Write { write: borrow, - signal: SignalSubscriberDrop { signal: *self }, - phantom: std::marker::PhantomData, + drop_signal: Box::new(SignalSubscriberDrop { signal: *self }), } } - - fn update_subscribers(&self) { - { - let inner = self.inner.read(); - for &scope_id in &*inner.subscribers.read().subscribers { - tracing::trace!( - "Write on {:?} triggered update on {:?}", - self.inner.value, - scope_id - ); - (inner.update_any)(scope_id); - } - } - - let self_read = &self.inner.read(); - let subscribers = { - let effects = &mut self_read.subscribers.write().effect_subscribers; - std::mem::take(&mut *effects) - }; - let effect_ref = &self_read.effect_ref; - for effect in subscribers { - tracing::trace!( - "Write on {:?} triggered effect {:?}", - self.inner.value, - effect - ); - effect_ref.rerun_effect(effect); - } - } - - /// Set the value of the signal. This will trigger an update on all subscribers. - #[track_caller] - pub fn set(&mut self, value: T) { - *self.write() = value; - } - - /// Set the value of the signal without triggering an update on subscribers. - /// - // todo: we should make it so setting while rendering doesn't trigger an update s - #[track_caller] - pub fn set_untracked(&self, value: T) { - let mut inner = self.inner.write(); - inner.value = value; - } - - /// Run a closure with a reference to the signal's value. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { - let write = self.read(); - f(&*write) - } - - /// Run a closure with a mutable reference to the signal's value. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn with_mut(&mut self, f: impl FnOnce(&mut T) -> O) -> O { - let mut write = self.write(); - f(&mut *write) - } - - /// Map the signal to a new type. - pub fn map(self, f: impl Fn(&T) -> &O + 'static) -> MappedSignal> { - MappedSignal::new(self, f) - } - - /// Get the generational id of the signal. - pub fn id(&self) -> generational_box::GenerationalBoxId { - self.inner.id() - } } impl IntoAttributeValue for Signal @@ -496,22 +494,6 @@ where } } -impl>> Signal { - /// Get the current value of the signal. This will subscribe the current scope to the signal. - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn cloned(&self) -> T { - self.read().clone() - } -} - -impl>> Signal { - /// Invert the boolean value of the signal. This will trigger an update on all subscribers. - pub fn toggle(&mut self) { - self.set(!self.cloned()); - } -} - impl>> PartialEq for Signal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner @@ -567,42 +549,38 @@ impl>> Drop for SignalSubscriberDrop /// A mutable reference to a signal's value. /// /// T is the current type of the write -/// B is the dynamically checked type of the write (RefMut) /// S is the storage type of the signal -/// I is the type of the original signal -pub struct Write> = UnsyncStorage, I: 'static = T> { +pub struct Write { write: S::Mut, - signal: SignalSubscriberDrop, - phantom: std::marker::PhantomData, + drop_signal: Box, } -impl>, I: 'static> Write { +impl Write { /// Map the mutable reference to the signal's value to a new type. - pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write { - let Self { write, signal, .. } = myself; + pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write { + let Self { + write, drop_signal, .. + } = myself; Write { write: S::map_mut(write, f), - signal, - phantom: std::marker::PhantomData, + drop_signal, } } /// Try to map the mutable reference to the signal's value to a new type - pub fn filter_map( + pub fn filter_map( myself: Self, f: impl FnOnce(&mut T) -> Option<&mut O>, - ) -> Option> { - let Self { write, signal, .. } = myself; + ) -> Option> { + let Self { + write, drop_signal, .. + } = myself; let write = S::try_map_mut(write, f); - write.map(|write| Write { - write, - signal, - phantom: PhantomData, - }) + write.map(|write| Write { write, drop_signal }) } } -impl>, I: 'static> Deref for Write { +impl Deref for Write { type Target = T; fn deref(&self) -> &Self::Target { @@ -610,121 +588,8 @@ impl>, I: 'static> Deref for Write } } -impl>, I> DerefMut for Write { +impl DerefMut for Write { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } } - -/// A signal that can only be read from. -pub struct ReadOnlySignal> = UnsyncStorage> { - inner: Signal, -} - -impl>> From> for ReadOnlySignal { - fn from(inner: Signal) -> Self { - Self { inner } - } -} - -impl ReadOnlySignal { - /// Create a new read-only signal. - #[track_caller] - pub fn new(signal: Signal) -> Self { - Self::new_maybe_sync(signal) - } -} - -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 } - } - - /// Get the scope that the signal was created in. - pub fn origin_scope(&self) -> ScopeId { - self.inner.origin_scope() - } - - /// Get the current value of the signal. This will subscribe the current scope to the signal. - /// - /// If the signal has been dropped, this will panic. - #[track_caller] - pub fn read(&self) -> S::Ref { - self.inner.read() - } - - /// 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) -> S::Ref { - self.inner.peek() - } - - /// Run a closure with a reference to the signal's value. - #[track_caller] - pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { - self.inner.with(f) - } - - /// Get the id of the signal. - pub fn id(&self) -> generational_box::GenerationalBoxId { - self.inner.id() - } -} - -impl IntoAttributeValue for ReadOnlySignal -where - T: Clone + IntoAttributeValue, -{ - fn into_value(self) -> dioxus_core::AttributeValue { - self.with(|f| f.clone().into_value()) - } -} - -impl>> ReadOnlySignal { - /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn value(&self) -> T { - self.read().clone() - } -} - -impl>> PartialEq for ReadOnlySignal { - fn eq(&self, other: &Self) -> bool { - self.inner == other.inner - } -} - -impl> + 'static> Deref for ReadOnlySignal { - type Target = dyn Fn() -> T; - - fn deref(&self) -> &Self::Target { - // https://github.com/dtolnay/case-studies/tree/master/callable-types - - // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit). - let uninit_callable = MaybeUninit::::uninit(); - // Then move that value into the closure. We assume that the closure now has a in memory layout of Self. - let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone(); - - // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure. - let size_of_closure = std::mem::size_of_val(&uninit_closure); - assert_eq!(size_of_closure, std::mem::size_of::()); - - // Then cast the lifetime of the closure to the lifetime of &self. - fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T { - b - } - let reference_to_closure = cast_lifetime( - { - // The real closure that we will never use. - &uninit_closure - }, - // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self. - unsafe { std::mem::transmute(self) }, - ); - - // Cast the closure to a trait object. - reference_to_closure as &Self::Target - } -} diff --git a/packages/signals/src/write.rs b/packages/signals/src/write.rs new file mode 100644 index 000000000..7a04ab713 --- /dev/null +++ b/packages/signals/src/write.rs @@ -0,0 +1,47 @@ +use std::ops::DerefMut; + +use crate::read::Readable; + +pub trait Writable: Readable { + type Mut: DerefMut; + + fn map_mut &mut U>( + ref_: Self::Mut, + f: F, + ) -> Self::Mut; + + fn try_map_mut Option<&mut U>>( + ref_: Self::Mut, + f: F, + ) -> Option>; + + fn write(&self) -> Self::Mut; + + #[track_caller] + fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { + f(&mut *self.write()) + } + + /// Set the value of the signal. This will trigger an update on all subscribers. + #[track_caller] + fn set(&mut self, value: T) { + *self.write() = value; + } + + /// Invert the boolean value of the signal. This will trigger an update on all subscribers. + fn toggle(&mut self) + where + T: std::ops::Not + Clone, + { + self.set(!self.cloned()); + } + + /// Index into the inner value and return a reference to the result. + #[track_caller] + fn index_mut(&self, index: I) -> Self::Mut + where + T: std::ops::IndexMut, + { + Self::map_mut(self.write(), |v| v.index_mut(index)) + } +}