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}