reactive_graph/signal/
arc_rw.rs

1use super::{
2    guards::{Plain, ReadGuard, UntrackedWriteGuard, WriteGuard},
3    subscriber_traits::AsSubscriberSet,
4    ArcReadSignal, ArcWriteSignal,
5};
6use crate::{
7    graph::{ReactiveNode, SubscriberSet},
8    prelude::{IsDisposed, Notify},
9    traits::{DefinedAt, IntoInner, ReadUntracked, UntrackableGuard, Write},
10};
11use core::fmt::{Debug, Formatter, Result};
12use std::{
13    hash::Hash,
14    panic::Location,
15    sync::{Arc, RwLock},
16};
17
18/// A reference-counted signal that can be read from or written to.
19///
20/// A signal is a piece of data that may change over time, and notifies other
21/// code when it has changed. This is the atomic unit of reactivity, which begins all other
22/// processes of reactive updates.
23///
24/// This is a reference-counted signal, which is `Clone` but not `Copy`.
25/// For arena-allocated `Copy` signals, use [`RwSignal`](super::RwSignal).
26///
27/// ## Core Trait Implementations
28///
29/// ### Reading the Value
30/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
31///   If you call it within an effect, it will cause that effect to subscribe
32///   to the signal, and to re-run whenever the value of the signal changes.
33///   - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of
34///     the signal without reactively tracking it.
35/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
36///   value of the signal by reference. If you call it within an effect, it will
37///   cause that effect to subscribe to the signal, and to re-run whenever the
38///   value of the signal changes.
39///   - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
40///     current value of the signal without reactively tracking it.
41/// - [`.with()`](crate::traits::With) allows you to reactively access the signal’s
42///   value without cloning by applying a callback function.
43///   - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
44///     the signal’s value by applying a callback function without reactively
45///     tracking it.
46/// - [`.to_stream()`](crate::traits::ToStream) converts the signal to an `async`
47///   stream of values.
48///
49/// ### Updating the Value
50/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
51/// - [`.update()`](crate::traits::Update) updates the value of the signal by
52///   applying a closure that takes a mutable reference.
53/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
54///   can be mutated, and which notifies subscribers when it is dropped.
55///
56/// > Each of these has a related `_untracked()` method, which updates the signal
57/// > without notifying subscribers. Untracked updates are not desirable in most
58/// > cases, as they cause “tearing” between the signal’s value and its observed
59/// > value. If you want a non-reactive container, used [`ArenaItem`](crate::owner::ArenaItem)
60/// > instead.
61///
62/// ## Examples
63///
64/// ```
65/// # use reactive_graph::prelude::*;
66/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
67/// let count = ArcRwSignal::new(0);
68///
69/// // ✅ calling the getter clones and returns the value
70/// //    this can be `count()` on nightly
71/// assert_eq!(count.get(), 0);
72///
73/// // ✅ calling the setter sets the value
74/// //    this can be `set_count(1)` on nightly
75/// count.set(1);
76/// assert_eq!(count.get(), 1);
77///
78/// // ❌ you could call the getter within the setter
79/// // set_count.set(count.get() + 1);
80///
81/// // ✅ however it's more efficient to use .update() and mutate the value in place
82/// count.update(|count: &mut i32| *count += 1);
83/// assert_eq!(count.get(), 2);
84///
85/// // ✅ you can create "derived signals" with a Fn() -> T interface
86/// let double_count = {
87///   // clone before moving into the closure because we use it below
88///   let count = count.clone();
89///   move || count.get() * 2
90/// };
91/// count.set(0);
92/// assert_eq!(double_count(), 0);
93/// count.set(1);
94/// assert_eq!(double_count(), 2);
95/// ```
96pub struct ArcRwSignal<T> {
97    #[cfg(any(debug_assertions, leptos_debuginfo))]
98    pub(crate) defined_at: &'static Location<'static>,
99    pub(crate) value: Arc<RwLock<T>>,
100    pub(crate) inner: Arc<RwLock<SubscriberSet>>,
101}
102
103impl<T> Clone for ArcRwSignal<T> {
104    #[track_caller]
105    fn clone(&self) -> Self {
106        Self {
107            #[cfg(any(debug_assertions, leptos_debuginfo))]
108            defined_at: self.defined_at,
109            value: Arc::clone(&self.value),
110            inner: Arc::clone(&self.inner),
111        }
112    }
113}
114
115impl<T> Debug for ArcRwSignal<T> {
116    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
117        f.debug_struct("ArcRwSignal")
118            .field("type", &std::any::type_name::<T>())
119            .field("value", &Arc::as_ptr(&self.value))
120            .finish()
121    }
122}
123
124impl<T> PartialEq for ArcRwSignal<T> {
125    fn eq(&self, other: &Self) -> bool {
126        Arc::ptr_eq(&self.value, &other.value)
127    }
128}
129
130impl<T> Eq for ArcRwSignal<T> {}
131
132impl<T> Hash for ArcRwSignal<T> {
133    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
134        std::ptr::hash(&Arc::as_ptr(&self.value), state);
135    }
136}
137
138impl<T> Default for ArcRwSignal<T>
139where
140    T: Default,
141{
142    #[track_caller]
143    fn default() -> Self {
144        Self::new(T::default())
145    }
146}
147
148impl<T> ArcRwSignal<T> {
149    /// Creates a new signal, taking the initial value as its argument.
150    #[cfg_attr(
151        feature = "tracing",
152        tracing::instrument(level = "trace", skip_all)
153    )]
154    #[track_caller]
155    pub fn new(value: T) -> Self {
156        Self {
157            #[cfg(any(debug_assertions, leptos_debuginfo))]
158            defined_at: Location::caller(),
159            value: Arc::new(RwLock::new(value)),
160            inner: Arc::new(RwLock::new(SubscriberSet::new())),
161        }
162    }
163
164    /// Returns a read-only handle to the signal.
165    #[track_caller]
166    pub fn read_only(&self) -> ArcReadSignal<T> {
167        ArcReadSignal {
168            #[cfg(any(debug_assertions, leptos_debuginfo))]
169            defined_at: Location::caller(),
170            value: Arc::clone(&self.value),
171            inner: Arc::clone(&self.inner),
172        }
173    }
174
175    /// Returns a write-only handle to the signal.
176    #[track_caller]
177    pub fn write_only(&self) -> ArcWriteSignal<T> {
178        ArcWriteSignal {
179            #[cfg(any(debug_assertions, leptos_debuginfo))]
180            defined_at: Location::caller(),
181            value: Arc::clone(&self.value),
182            inner: Arc::clone(&self.inner),
183        }
184    }
185
186    /// Splits the signal into its readable and writable halves.
187    #[track_caller]
188    pub fn split(&self) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
189        (self.read_only(), self.write_only())
190    }
191
192    /// Reunites the two halves of a signal. Returns `None` if the two signals
193    /// provided were not created from the same signal.
194    #[track_caller]
195    pub fn unite(
196        read: ArcReadSignal<T>,
197        write: ArcWriteSignal<T>,
198    ) -> Option<Self> {
199        if Arc::ptr_eq(&read.inner, &write.inner) {
200            Some(Self {
201                #[cfg(any(debug_assertions, leptos_debuginfo))]
202                defined_at: Location::caller(),
203                value: read.value,
204                inner: read.inner,
205            })
206        } else {
207            None
208        }
209    }
210}
211
212impl<T> DefinedAt for ArcRwSignal<T> {
213    #[inline(always)]
214    fn defined_at(&self) -> Option<&'static Location<'static>> {
215        #[cfg(any(debug_assertions, leptos_debuginfo))]
216        {
217            Some(self.defined_at)
218        }
219        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
220        {
221            None
222        }
223    }
224}
225
226impl<T> IsDisposed for ArcRwSignal<T> {
227    #[inline(always)]
228    fn is_disposed(&self) -> bool {
229        false
230    }
231}
232
233impl<T> IntoInner for ArcRwSignal<T> {
234    type Value = T;
235
236    #[inline(always)]
237    fn into_inner(self) -> Option<Self::Value> {
238        Some(Arc::into_inner(self.value)?.into_inner().unwrap())
239    }
240}
241
242impl<T> AsSubscriberSet for ArcRwSignal<T> {
243    type Output = Arc<RwLock<SubscriberSet>>;
244
245    #[inline(always)]
246    fn as_subscriber_set(&self) -> Option<Self::Output> {
247        Some(Arc::clone(&self.inner))
248    }
249}
250
251impl<T: 'static> ReadUntracked for ArcRwSignal<T> {
252    type Value = ReadGuard<T, Plain<T>>;
253
254    fn try_read_untracked(&self) -> Option<Self::Value> {
255        Plain::try_new(Arc::clone(&self.value)).map(ReadGuard::new)
256    }
257}
258
259impl<T> Notify for ArcRwSignal<T> {
260    fn notify(&self) {
261        self.mark_dirty();
262    }
263}
264
265impl<T: 'static> Write for ArcRwSignal<T> {
266    type Value = T;
267
268    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
269        self.value
270            .write()
271            .ok()
272            .map(|guard| WriteGuard::new(self.clone(), guard))
273    }
274
275    #[allow(refining_impl_trait)]
276    fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
277        UntrackedWriteGuard::try_new(Arc::clone(&self.value))
278    }
279}