123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- 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<u32>) -> 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<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage> {
- pub(crate) inner: CopyValue<SignalData<T>, S>,
- }
- /// A signal that can safely shared between threads.
- pub type SyncSignal<T> = Signal<T, SyncStorage>;
- /// The data stored for tracking in a signal.
- pub struct SignalData<T> {
- pub(crate) subscribers: Mutex<HashSet<ReactiveContext>>,
- pub(crate) value: T,
- }
- impl<T: 'static> Signal<T> {
- /// 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<T> {
- GlobalSignal::new(constructor)
- }
- }
- impl<T: PartialEq + 'static> Signal<T> {
- /// Creates a new global Signal that can be used in a global static.
- #[track_caller]
- pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T> {
- 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<T> {
- Memo::new(f)
- }
- }
- impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
- /// 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::<SignalData<T>, 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::<SignalData<T>, 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<T, S: Storage<SignalData<T>>> Readable for Signal<T, S> {
- type Target = T;
- type Storage = S;
- #[track_caller]
- fn try_read(&self) -> Result<ReadableRef<Self>, 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<Self> {
- let inner = self.inner.read();
- S::map(inner, |v| &v.value)
- }
- }
- impl<T: 'static, S: Storage<SignalData<T>>> Writable for Signal<T, S> {
- type Mut<R: ?Sized + 'static> = Write<R, S>;
- fn map_mut<I: ?Sized, U: ?Sized + 'static, F: FnOnce(&mut I) -> &mut U>(
- ref_: Self::Mut<I>,
- f: F,
- ) -> Self::Mut<U> {
- Write::map(ref_, f)
- }
- fn try_map_mut<
- I: ?Sized + 'static,
- U: ?Sized + 'static,
- F: FnOnce(&mut I) -> Option<&mut U>,
- >(
- ref_: Self::Mut<I>,
- f: F,
- ) -> Option<Self::Mut<U>> {
- Write::filter_map(ref_, f)
- }
- #[track_caller]
- fn try_write(&mut self) -> Result<Self::Mut<T>, 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<T> IntoAttributeValue for Signal<T>
- where
- T: Clone + IntoAttributeValue,
- {
- fn into_value(self) -> dioxus_core::AttributeValue {
- self.with(|f| f.clone().into_value())
- }
- }
- impl<T: 'static, S: Storage<SignalData<T>>> PartialEq for Signal<T, S> {
- 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<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for Signal<T, S> {
- type Target = dyn Fn() -> T;
- fn deref(&self) -> &Self::Target {
- Readable::deref_impl(self)
- }
- }
- #[cfg(feature = "serde")]
- impl<T: serde::Serialize + 'static, Store: Storage<SignalData<T>>> serde::Serialize
- for Signal<T, Store>
- {
- fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
- self.read().serialize(serializer)
- }
- }
- #[cfg(feature = "serde")]
- impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>>>
- serde::Deserialize<'de> for Signal<T, Store>
- {
- fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
- 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<T: ?Sized + 'static, S: AnyStorage = UnsyncStorage> {
- write: S::Mut<T>,
- drop_signal: Box<dyn Any>,
- }
- impl<T: ?Sized + 'static, S: AnyStorage> Write<T, S> {
- /// Map the mutable reference to the signal's value to a new type.
- pub fn map<O: ?Sized>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<O, S> {
- 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<O: ?Sized>(
- myself: Self,
- f: impl FnOnce(&mut T) -> Option<&mut O>,
- ) -> Option<Write<O, S>> {
- let Self {
- write, drop_signal, ..
- } = myself;
- let write = S::try_map_mut(write, f);
- write.map(|write| Write { write, drop_signal })
- }
- }
- impl<T: ?Sized + 'static, S: AnyStorage> Deref for Write<T, S> {
- type Target = T;
- fn deref(&self) -> &Self::Target {
- &self.write
- }
- }
- impl<T: ?Sized, S: AnyStorage> DerefMut for Write<T, S> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.write
- }
- }
- struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
- signal: Signal<T, S>,
- #[cfg(debug_assertions)]
- origin: &'static std::panic::Location<'static>,
- }
- impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
- fn drop(&mut self) {
- #[cfg(debug_assertions)]
- tracing::trace!(
- "Write on signal at {:?} finished, updating subscribers",
- self.origin
- );
- self.signal.update_subscribers();
- }
- }
|