use crate::Memo; use crate::{ read::Readable, write::Writable, CopyValue, GlobalMemo, GlobalSignal, ReactiveContext, ReadableRef, }; use dioxus_core::{prelude::IntoAttributeValue, ScopeId}; use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage}; use std::{ any::Any, collections::HashSet, ops::{Deref, DerefMut}, sync::Mutex, }; /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. /// /// ```rust /// use dioxus::prelude::*; /// use dioxus_signals::*; /// /// #[component] /// fn App() -> Element { /// let mut count = use_signal(|| 0); /// /// // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated. /// // The app component will never be rerendered in this example. /// rsx! { Child { state: count } } /// } /// /// #[component] /// fn Child(mut state: Signal) -> Element { /// use_future(move || async move { /// // Because the signal is a Copy type, we can use it in an async block without cloning it. /// state += 1; /// }); /// /// rsx! { /// button { /// onclick: move |_| state += 1, /// "{state}" /// } /// } /// } /// ``` pub struct Signal> = UnsyncStorage> { pub(crate) inner: CopyValue, S>, } /// A signal that can safely shared between threads. pub type SyncSignal = Signal; /// The data stored for tracking in a signal. pub struct SignalData { pub(crate) subscribers: Mutex>, pub(crate) value: T, } impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. #[track_caller] 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. #[track_caller] pub fn new_in_scope(value: T, owner: ScopeId) -> Self { Self::new_maybe_sync_in_scope(value, owner) } /// Creates a new global Signal that can be used in a global static. #[track_caller] pub const fn global(constructor: fn() -> T) -> GlobalSignal { GlobalSignal::new(constructor) } } impl Signal { /// Creates a new global Signal that can be used in a global static. #[track_caller] pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo { GlobalMemo::new(constructor) } /// 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 memo(f: impl FnMut() -> T + 'static) -> Memo { Memo::new(f) } } 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 { inner: CopyValue::, S>::new_maybe_sync(SignalData { subscribers: Default::default(), value, }), } } /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. pub fn new_with_caller( value: T, #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, ) -> Self { Self { inner: CopyValue::new_with_caller( SignalData { subscribers: Default::default(), value, }, #[cfg(debug_assertions)] caller, ), } } /// 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 { inner: CopyValue::, S>::new_maybe_sync_in_scope( SignalData { subscribers: Default::default(), value, }, owner, ), } } /// Take the value out of the signal, invalidating the signal in the process. pub fn take(&self) -> T { self.inner.take().value } /// Get the scope the signal was created in. pub fn origin_scope(&self) -> ScopeId { self.inner.origin_scope() } fn update_subscribers(&self) { { let inner = self.inner.read(); let mut subscribers = inner.subscribers.lock().unwrap(); subscribers.retain(|reactive_context| reactive_context.mark_dirty()) } } /// Get the generational id of the signal. pub fn id(&self) -> generational_box::GenerationalBoxId { self.inner.id() } } impl>> Readable for Signal { type Target = T; type Storage = S; #[track_caller] fn try_read(&self) -> Result, generational_box::BorrowError> { let inner = self.inner.try_read()?; if let Some(reactive_context) = ReactiveContext::current() { tracing::trace!("Subscribing to the reactive context {}", reactive_context); inner.subscribers.lock().unwrap().insert(reactive_context); } Ok(S::map(inner, |v| &v.value)) } /// 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) -> ReadableRef { 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< I: ?Sized + 'static, U: ?Sized + 'static, F: FnOnce(&mut I) -> Option<&mut U>, >( ref_: Self::Mut, f: F, ) -> Option> { Write::filter_map(ref_, f) } #[track_caller] fn try_write(&mut self) -> Result, generational_box::BorrowMutError> { self.inner.try_write().map(|inner| { let borrow = S::map_mut(inner, |v| &mut v.value); Write { write: borrow, drop_signal: Box::new(SignalSubscriberDrop { signal: *self, #[cfg(debug_assertions)] origin: std::panic::Location::caller(), }), } }) } } impl IntoAttributeValue for Signal where T: Clone + IntoAttributeValue, { fn into_value(self) -> dioxus_core::AttributeValue { self.with(|f| f.clone().into_value()) } } impl>> PartialEq for Signal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } /// Allow calling a signal with signal() syntax /// /// Currently only limited to copy types, though could probably specialize for string/arc/rc impl> + 'static> Deref for Signal { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { Readable::deref_impl(self) } } #[cfg(feature = "serde")] 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, Store: Storage>> serde::Deserialize<'de> for Signal { fn deserialize>(deserializer: D) -> Result { Ok(Self::new_maybe_sync(T::deserialize(deserializer)?)) } } /// A mutable reference to a signal's value. /// /// T is the current type of the write /// S is the storage type of the signal pub struct Write { write: S::Mut, drop_signal: Box, } 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, drop_signal, .. } = myself; Write { write: S::map_mut(write, f), drop_signal, } } /// Try to map the mutable reference to the signal's value to a new type pub fn filter_map( myself: Self, f: impl FnOnce(&mut T) -> Option<&mut O>, ) -> Option> { let Self { write, drop_signal, .. } = myself; let write = S::try_map_mut(write, f); write.map(|write| Write { write, drop_signal }) } } impl Deref for Write { type Target = T; fn deref(&self) -> &Self::Target { &self.write } } impl DerefMut for Write { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } } struct SignalSubscriberDrop>> { signal: Signal, #[cfg(debug_assertions)] origin: &'static std::panic::Location<'static>, } impl>> Drop for SignalSubscriberDrop { fn drop(&mut self) { #[cfg(debug_assertions)] tracing::trace!( "Write on signal at {:?} finished, updating subscribers", self.origin ); self.signal.update_subscribers(); } }