reactive_graph/signal/
arc_write.rs

1use super::guards::{UntrackedWriteGuard, WriteGuard};
2use crate::{
3    graph::{ReactiveNode, SubscriberSet},
4    prelude::{IsDisposed, Notify},
5    traits::{DefinedAt, IntoInner, UntrackableGuard, Write},
6};
7use core::fmt::{Debug, Formatter, Result};
8use std::{
9    hash::Hash,
10    panic::Location,
11    sync::{Arc, RwLock},
12};
13
14/// A reference-counted setter for a reactive signal.
15///
16/// A signal is a piece of data that may change over time,
17/// and notifies other code when it has changed.
18///
19/// This is a reference-counted signal, which is `Clone` but not `Copy`.
20/// For arena-allocated `Copy` signals, use [`WriteSignal`](super::WriteSignal).
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, used [`ArenaItem`](crate::owner::ArenaItem)
33/// > instead.
34///
35/// ## Examples
36/// ```
37/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
38/// let (count, set_count) = arc_signal(0);
39///
40/// // ✅ calling the setter sets the value
41/// //    `set_count(1)` on nightly
42/// set_count.set(1);
43/// assert_eq!(count.get(), 1);
44///
45/// // ❌ you could call the getter within the setter
46/// // set_count.set(count.get() + 1);
47///
48/// // ✅ however it's more efficient to use .update() and mutate the value in place
49/// set_count.update(|count: &mut i32| *count += 1);
50/// assert_eq!(count.get(), 2);
51///
52/// // ✅ `.write()` returns a guard that implements `DerefMut` and will notify when dropped
53/// *set_count.write() += 1;
54/// assert_eq!(count.get(), 3);
55/// ```
56pub struct ArcWriteSignal<T> {
57    #[cfg(any(debug_assertions, leptos_debuginfo))]
58    pub(crate) defined_at: &'static Location<'static>,
59    pub(crate) value: Arc<RwLock<T>>,
60    pub(crate) inner: Arc<RwLock<SubscriberSet>>,
61}
62
63impl<T> Clone for ArcWriteSignal<T> {
64    #[track_caller]
65    fn clone(&self) -> Self {
66        Self {
67            #[cfg(any(debug_assertions, leptos_debuginfo))]
68            defined_at: self.defined_at,
69            value: Arc::clone(&self.value),
70            inner: Arc::clone(&self.inner),
71        }
72    }
73}
74
75impl<T> Debug for ArcWriteSignal<T> {
76    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
77        f.debug_struct("ArcWriteSignal")
78            .field("type", &std::any::type_name::<T>())
79            .field("value", &Arc::as_ptr(&self.value))
80            .finish()
81    }
82}
83
84impl<T> PartialEq for ArcWriteSignal<T> {
85    fn eq(&self, other: &Self) -> bool {
86        Arc::ptr_eq(&self.value, &other.value)
87    }
88}
89
90impl<T> Eq for ArcWriteSignal<T> {}
91
92impl<T> Hash for ArcWriteSignal<T> {
93    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
94        std::ptr::hash(&Arc::as_ptr(&self.value), state);
95    }
96}
97
98impl<T> DefinedAt for ArcWriteSignal<T> {
99    #[inline(always)]
100    fn defined_at(&self) -> Option<&'static Location<'static>> {
101        #[cfg(any(debug_assertions, leptos_debuginfo))]
102        {
103            Some(self.defined_at)
104        }
105        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
106        {
107            None
108        }
109    }
110}
111
112impl<T> IsDisposed for ArcWriteSignal<T> {
113    #[inline(always)]
114    fn is_disposed(&self) -> bool {
115        false
116    }
117}
118
119impl<T> IntoInner for ArcWriteSignal<T> {
120    type Value = T;
121
122    #[inline(always)]
123    fn into_inner(self) -> Option<Self::Value> {
124        Some(Arc::into_inner(self.value)?.into_inner().unwrap())
125    }
126}
127
128impl<T> Notify for ArcWriteSignal<T> {
129    fn notify(&self) {
130        self.inner.mark_dirty();
131    }
132}
133
134impl<T: 'static> Write for ArcWriteSignal<T> {
135    type Value = T;
136
137    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
138        self.value
139            .write()
140            .ok()
141            .map(|guard| WriteGuard::new(self.clone(), guard))
142    }
143
144    #[allow(refining_impl_trait)]
145    fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
146        UntrackedWriteGuard::try_new(Arc::clone(&self.value))
147    }
148}