reactive_graph/signal/
rw.rs

1use super::{
2    guards::{Plain, ReadGuard},
3    subscriber_traits::AsSubscriberSet,
4    ArcReadSignal, ArcRwSignal, ArcWriteSignal, ReadSignal, WriteSignal,
5};
6use crate::{
7    graph::{ReactiveNode, SubscriberSet},
8    owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
9    signal::guards::{UntrackedWriteGuard, WriteGuard},
10    traits::{
11        DefinedAt, Dispose, IntoInner, IsDisposed, Notify, ReadUntracked,
12        UntrackableGuard, Write,
13    },
14    unwrap_signal,
15};
16use core::fmt::Debug;
17use guardian::ArcRwLockWriteGuardian;
18use std::{
19    hash::Hash,
20    panic::Location,
21    sync::{Arc, RwLock},
22};
23
24/// An arena-allocated signal that can be read from or written to.
25///
26/// A signal is a piece of data that may change over time, and notifies other
27/// code when it has changed. This is the atomic unit of reactivity, which begins all other
28/// processes of reactive updates.
29///
30/// This is an arena-allocated signal, which is `Copy` and is disposed when its reactive
31/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that lives
32/// as long as a reference to it is alive, see [`ArcRwSignal`].
33///
34/// ## Core Trait Implementations
35///
36/// ### Reading the Value
37/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
38///   If you call it within an effect, it will cause that effect to subscribe
39///   to the signal, and to re-run whenever the value of the signal changes.
40///   - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of
41///     the signal without reactively tracking it.
42/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
43///   value of the signal by reference. If you call it within an effect, it will
44///   cause that effect to subscribe to the signal, and to re-run whenever the
45///   value of the signal changes.
46///   - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
47///     current value of the signal without reactively tracking it.
48/// - [`.with()`](crate::traits::With) allows you to reactively access the signal’s
49///   value without cloning by applying a callback function.
50///   - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
51///     the signal’s value by applying a callback function without reactively
52///     tracking it.
53/// - [`.to_stream()`](crate::traits::ToStream) converts the signal to an `async`
54///   stream of values.
55///
56/// ### Updating the Value
57/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
58/// - [`.update()`](crate::traits::Update) updates the value of the signal by
59///   applying a closure that takes a mutable reference.
60/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
61///   can be mutated, and which notifies subscribers when it is dropped.
62///
63/// > Each of these has a related `_untracked()` method, which updates the signal
64/// > without notifying subscribers. Untracked updates are not desirable in most
65/// > cases, as they cause “tearing” between the signal’s value and its observed
66/// > value. If you want a non-reactive container, used [`ArenaItem`] instead.
67///
68/// ## Examples
69///
70/// ```
71/// # use reactive_graph::prelude::*;
72/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
73/// let count = ArcRwSignal::new(0);
74///
75/// // ✅ calling the getter clones and returns the value
76/// //    this can be `count()` on nightly
77/// assert_eq!(count.get(), 0);
78///
79/// // ✅ calling the setter sets the value
80/// //    this can be `set_count(1)` on nightly
81/// count.set(1);
82/// assert_eq!(count.get(), 1);
83///
84/// // ❌ you could call the getter within the setter
85/// // set_count.set(count.get() + 1);
86///
87/// // ✅ however it's more efficient to use .update() and mutate the value in place
88/// count.update(|count: &mut i32| *count += 1);
89/// assert_eq!(count.get(), 2);
90///
91/// // ✅ you can create "derived signals" with a Fn() -> T interface
92/// let double_count = {
93///   // clone before moving into the closure because we use it below
94///   let count = count.clone();
95///   move || count.get() * 2
96/// };
97/// count.set(0);
98/// assert_eq!(double_count(), 0);
99/// count.set(1);
100/// assert_eq!(double_count(), 2);
101/// ```
102pub struct RwSignal<T, S = SyncStorage> {
103    #[cfg(any(debug_assertions, leptos_debuginfo))]
104    defined_at: &'static Location<'static>,
105    inner: ArenaItem<ArcRwSignal<T>, S>,
106}
107
108impl<T, S> Dispose for RwSignal<T, S> {
109    fn dispose(self) {
110        self.inner.dispose()
111    }
112}
113
114impl<T> RwSignal<T>
115where
116    T: Send + Sync + 'static,
117{
118    /// Creates a new signal, taking the initial value as its argument.
119    #[cfg_attr(
120        feature = "tracing",
121        tracing::instrument(level = "trace", skip_all)
122    )]
123    #[track_caller]
124    pub fn new(value: T) -> Self {
125        Self::new_with_storage(value)
126    }
127}
128
129impl<T, S> RwSignal<T, S>
130where
131    T: 'static,
132    S: Storage<ArcRwSignal<T>>,
133{
134    /// Creates a new signal with the given arena storage method.
135    #[cfg_attr(
136        feature = "tracing",
137        tracing::instrument(level = "trace", skip_all)
138    )]
139    #[track_caller]
140    pub fn new_with_storage(value: T) -> Self {
141        Self {
142            #[cfg(any(debug_assertions, leptos_debuginfo))]
143            defined_at: Location::caller(),
144            inner: ArenaItem::new_with_storage(ArcRwSignal::new(value)),
145        }
146    }
147}
148
149impl<T> RwSignal<T, LocalStorage>
150where
151    T: 'static,
152{
153    /// Creates a new signal, taking the initial value as its argument. Unlike [`RwSignal::new`],
154    /// this pins the value to the current thread. Accessing it from any other thread will panic.
155    #[cfg_attr(
156        feature = "tracing",
157        tracing::instrument(level = "trace", skip_all)
158    )]
159    #[track_caller]
160    pub fn new_local(value: T) -> Self {
161        Self::new_with_storage(value)
162    }
163}
164
165impl<T, S> RwSignal<T, S>
166where
167    T: 'static,
168    S: Storage<ArcRwSignal<T>> + Storage<ArcReadSignal<T>>,
169{
170    /// Returns a read-only handle to the signal.
171    #[inline(always)]
172    #[track_caller]
173    pub fn read_only(&self) -> ReadSignal<T, S> {
174        ReadSignal {
175            #[cfg(any(debug_assertions, leptos_debuginfo))]
176            defined_at: Location::caller(),
177            inner: ArenaItem::new_with_storage(
178                self.inner
179                    .try_get_value()
180                    .map(|inner| inner.read_only())
181                    .unwrap_or_else(unwrap_signal!(self)),
182            ),
183        }
184    }
185}
186
187impl<T, S> RwSignal<T, S>
188where
189    T: 'static,
190    S: Storage<ArcRwSignal<T>> + Storage<ArcWriteSignal<T>>,
191{
192    /// Returns a write-only handle to the signal.
193    #[inline(always)]
194    #[track_caller]
195    pub fn write_only(&self) -> WriteSignal<T, S> {
196        WriteSignal {
197            #[cfg(any(debug_assertions, leptos_debuginfo))]
198            defined_at: Location::caller(),
199            inner: ArenaItem::new_with_storage(
200                self.inner
201                    .try_get_value()
202                    .map(|inner| inner.write_only())
203                    .unwrap_or_else(unwrap_signal!(self)),
204            ),
205        }
206    }
207}
208
209impl<T, S> RwSignal<T, S>
210where
211    T: 'static,
212    S: Storage<ArcRwSignal<T>>
213        + Storage<ArcWriteSignal<T>>
214        + Storage<ArcReadSignal<T>>,
215{
216    /// Splits the signal into its readable and writable halves.
217    #[track_caller]
218    #[inline(always)]
219    pub fn split(&self) -> (ReadSignal<T, S>, WriteSignal<T, S>) {
220        (self.read_only(), self.write_only())
221    }
222
223    /// Reunites the two halves of a signal. Returns `None` if the two signals
224    /// provided were not created from the same signal.
225    #[track_caller]
226    pub fn unite(
227        read: ReadSignal<T, S>,
228        write: WriteSignal<T, S>,
229    ) -> Option<Self> {
230        match (read.inner.try_get_value(), write.inner.try_get_value()) {
231            (Some(read), Some(write)) => {
232                if Arc::ptr_eq(&read.inner, &write.inner) {
233                    Some(Self {
234                        #[cfg(any(debug_assertions, leptos_debuginfo))]
235                        defined_at: Location::caller(),
236                        inner: ArenaItem::new_with_storage(ArcRwSignal {
237                            #[cfg(any(debug_assertions, leptos_debuginfo))]
238                            defined_at: Location::caller(),
239                            value: Arc::clone(&read.value),
240                            inner: Arc::clone(&read.inner),
241                        }),
242                    })
243                } else {
244                    None
245                }
246            }
247            _ => None,
248        }
249    }
250}
251
252impl<T, S> Copy for RwSignal<T, S> {}
253
254impl<T, S> Clone for RwSignal<T, S> {
255    fn clone(&self) -> Self {
256        *self
257    }
258}
259
260impl<T, S> Debug for RwSignal<T, S>
261where
262    S: Debug,
263{
264    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265        f.debug_struct("RwSignal")
266            .field("type", &std::any::type_name::<T>())
267            .field("store", &self.inner)
268            .finish()
269    }
270}
271
272impl<T, S> Default for RwSignal<T, S>
273where
274    T: Default + 'static,
275    S: Storage<ArcRwSignal<T>>,
276{
277    #[track_caller]
278    fn default() -> Self {
279        Self::new_with_storage(T::default())
280    }
281}
282
283impl<T, S> PartialEq for RwSignal<T, S> {
284    fn eq(&self, other: &Self) -> bool {
285        self.inner == other.inner
286    }
287}
288
289impl<T, S> Eq for RwSignal<T, S> {}
290
291impl<T, S> Hash for RwSignal<T, S> {
292    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
293        self.inner.hash(state);
294    }
295}
296
297impl<T, S> DefinedAt for RwSignal<T, S> {
298    fn defined_at(&self) -> Option<&'static Location<'static>> {
299        #[cfg(any(debug_assertions, leptos_debuginfo))]
300        {
301            Some(self.defined_at)
302        }
303        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
304        {
305            None
306        }
307    }
308}
309
310impl<T: 'static, S> IsDisposed for RwSignal<T, S> {
311    fn is_disposed(&self) -> bool {
312        self.inner.is_disposed()
313    }
314}
315
316impl<T, S> IntoInner for RwSignal<T, S>
317where
318    S: Storage<ArcRwSignal<T>>,
319{
320    type Value = T;
321
322    #[inline(always)]
323    fn into_inner(self) -> Option<Self::Value> {
324        self.inner.into_inner()?.into_inner()
325    }
326}
327
328impl<T, S> AsSubscriberSet for RwSignal<T, S>
329where
330    S: Storage<ArcRwSignal<T>>,
331{
332    type Output = Arc<RwLock<SubscriberSet>>;
333
334    fn as_subscriber_set(&self) -> Option<Self::Output> {
335        self.inner
336            .try_with_value(|inner| inner.as_subscriber_set())
337            .flatten()
338    }
339}
340
341impl<T, S> ReadUntracked for RwSignal<T, S>
342where
343    T: 'static,
344    S: Storage<ArcRwSignal<T>>,
345{
346    type Value = ReadGuard<T, Plain<T>>;
347
348    fn try_read_untracked(&self) -> Option<Self::Value> {
349        self.inner
350            .try_get_value()
351            .map(|inner| inner.read_untracked())
352    }
353}
354
355impl<T, S> Notify for RwSignal<T, S>
356where
357    S: Storage<ArcRwSignal<T>>,
358{
359    fn notify(&self) {
360        self.mark_dirty();
361    }
362}
363
364impl<T, S> Write for RwSignal<T, S>
365where
366    T: 'static,
367    S: Storage<ArcRwSignal<T>>,
368{
369    type Value = T;
370
371    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
372        let guard = self.inner.try_with_value(|n| {
373            ArcRwLockWriteGuardian::take(Arc::clone(&n.value)).ok()
374        })??;
375        Some(WriteGuard::new(*self, guard))
376    }
377
378    #[allow(refining_impl_trait)]
379    fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
380        self.inner
381            .try_with_value(|n| n.try_write_untracked())
382            .flatten()
383    }
384}
385
386impl<T> From<ArcRwSignal<T>> for RwSignal<T>
387where
388    T: Send + Sync + 'static,
389{
390    #[track_caller]
391    fn from(value: ArcRwSignal<T>) -> Self {
392        RwSignal {
393            #[cfg(any(debug_assertions, leptos_debuginfo))]
394            defined_at: Location::caller(),
395            inner: ArenaItem::new_with_storage(value),
396        }
397    }
398}
399
400impl<'a, T> From<&'a ArcRwSignal<T>> for RwSignal<T>
401where
402    T: Send + Sync + 'static,
403{
404    #[track_caller]
405    fn from(value: &'a ArcRwSignal<T>) -> Self {
406        value.clone().into()
407    }
408}
409
410impl<T> FromLocal<ArcRwSignal<T>> for RwSignal<T, LocalStorage>
411where
412    T: 'static,
413{
414    #[track_caller]
415    fn from_local(value: ArcRwSignal<T>) -> Self {
416        RwSignal {
417            #[cfg(any(debug_assertions, leptos_debuginfo))]
418            defined_at: Location::caller(),
419            inner: ArenaItem::new_with_storage(value),
420        }
421    }
422}
423
424impl<T, S> From<RwSignal<T, S>> for ArcRwSignal<T>
425where
426    T: 'static,
427    S: Storage<ArcRwSignal<T>>,
428{
429    #[track_caller]
430    fn from(value: RwSignal<T, S>) -> Self {
431        value
432            .inner
433            .try_get_value()
434            .unwrap_or_else(unwrap_signal!(value))
435    }
436}