Skip to main content

reactive_graph/signal/
write.rs

1use super::{guards::WriteGuard, ArcWriteSignal};
2use crate::{
3    owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
4    traits::{
5        DefinedAt, Dispose, IntoInner, IsDisposed, Notify, UntrackableGuard,
6        Write,
7    },
8};
9use core::fmt::Debug;
10use guardian::ArcRwLockWriteGuardian;
11use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
12
13/// An arena-allocated setter for a reactive signal.
14///
15/// A signal is a piece of data that may change over time,
16/// and notifies other code when it has changed.
17///
18/// This is an arena-allocated signal, which is `Copy` and is disposed when its reactive
19/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that lives
20/// as long as a reference to it is alive, see [`ArcWriteSignal`].
21///
22/// ## Core Trait Implementations
23/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
24/// - [`.update()`](crate::traits::Update) updates the value of the signal by
25///   applying a closure that takes a mutable reference.
26/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
27///   can be mutated, and which notifies subscribers when it is dropped.
28///
29/// > Each of these has a related `_untracked()` method, which updates the signal
30/// > without notifying subscribers. Untracked updates are not desirable in most
31/// > cases, as they cause “tearing” between the signal’s value and its observed
32/// > value. If you want a non-reactive container, use [`ArenaItem`] instead.
33///
34/// ## Examples
35/// ```
36/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;  let owner = reactive_graph::owner::Owner::new(); owner.set();
37/// let (count, set_count) = signal(0);
38///
39/// // ✅ calling the setter sets the value
40/// //    `set_count(1)` on nightly
41/// set_count.set(1);
42/// assert_eq!(count.get(), 1);
43///
44/// // ❌ you could call the getter within the setter
45/// // set_count.set(count.get() + 1);
46///
47/// // ✅ however it's more efficient to use .update() and mutate the value in place
48/// set_count.update(|count: &mut i32| *count += 1);
49/// assert_eq!(count.get(), 2);
50///
51/// // ✅ `.write()` returns a guard that implements `DerefMut` and will notify when dropped
52/// *set_count.write() += 1;
53/// assert_eq!(count.get(), 3);
54/// ```
55pub struct WriteSignal<T, S = SyncStorage> {
56    #[cfg(any(debug_assertions, leptos_debuginfo))]
57    pub(crate) defined_at: &'static Location<'static>,
58    pub(crate) inner: ArenaItem<ArcWriteSignal<T>, S>,
59}
60
61impl<T, S> Dispose for WriteSignal<T, S> {
62    fn dispose(self) {
63        self.inner.dispose()
64    }
65}
66
67impl<T, S> Copy for WriteSignal<T, S> {}
68
69impl<T, S> Clone for WriteSignal<T, S> {
70    fn clone(&self) -> Self {
71        *self
72    }
73}
74
75impl<T, S> Debug for WriteSignal<T, S>
76where
77    S: Debug,
78{
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.debug_struct("WriteSignal")
81            .field("type", &std::any::type_name::<T>())
82            .field("store", &self.inner)
83            .finish()
84    }
85}
86
87impl<T, S> PartialEq for WriteSignal<T, S> {
88    fn eq(&self, other: &Self) -> bool {
89        self.inner == other.inner
90    }
91}
92
93impl<T, S> Eq for WriteSignal<T, S> {}
94
95impl<T, S> Hash for WriteSignal<T, S> {
96    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
97        self.inner.hash(state);
98    }
99}
100
101impl<T, S> DefinedAt for WriteSignal<T, S> {
102    fn defined_at(&self) -> Option<&'static Location<'static>> {
103        #[cfg(any(debug_assertions, leptos_debuginfo))]
104        {
105            Some(self.defined_at)
106        }
107        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
108        {
109            None
110        }
111    }
112}
113
114impl<T> From<ArcWriteSignal<T>> for WriteSignal<T>
115where
116    T: Send + Sync + 'static,
117{
118    #[track_caller]
119    fn from(value: ArcWriteSignal<T>) -> Self {
120        WriteSignal {
121            #[cfg(any(debug_assertions, leptos_debuginfo))]
122            defined_at: Location::caller(),
123            inner: ArenaItem::new_with_storage(value),
124        }
125    }
126}
127
128impl<T> FromLocal<ArcWriteSignal<T>> for WriteSignal<T, LocalStorage>
129where
130    T: 'static,
131{
132    #[track_caller]
133    fn from_local(value: ArcWriteSignal<T>) -> Self {
134        WriteSignal {
135            #[cfg(any(debug_assertions, leptos_debuginfo))]
136            defined_at: Location::caller(),
137            inner: ArenaItem::new_with_storage(value),
138        }
139    }
140}
141
142impl<T, S> IsDisposed for WriteSignal<T, S> {
143    fn is_disposed(&self) -> bool {
144        self.inner.is_disposed()
145    }
146}
147
148impl<T, S> IntoInner for WriteSignal<T, S>
149where
150    S: Storage<ArcWriteSignal<T>>,
151{
152    type Value = T;
153
154    #[inline(always)]
155    fn into_inner(self) -> Option<Self::Value> {
156        self.inner.into_inner()?.into_inner()
157    }
158}
159
160impl<T, S> Notify for WriteSignal<T, S>
161where
162    T: 'static,
163    S: Storage<ArcWriteSignal<T>>,
164{
165    fn notify(&self) {
166        if let Some(inner) = self.inner.try_get_value() {
167            inner.notify();
168        }
169    }
170}
171
172impl<T, S> Write for WriteSignal<T, S>
173where
174    T: 'static,
175    S: Storage<ArcWriteSignal<T>>,
176{
177    type Value = T;
178
179    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
180        let guard = self.inner.try_with_value(|n| {
181            ArcRwLockWriteGuardian::take(Arc::clone(&n.value)).ok()
182        })??;
183        Some(WriteGuard::new(*self, guard))
184    }
185
186    fn try_write_untracked(
187        &self,
188    ) -> Option<impl DerefMut<Target = Self::Value>> {
189        self.inner
190            .try_with_value(|n| n.try_write_untracked())
191            .flatten()
192    }
193}