reactive_graph/computed/
arc_memo.rs

1use super::inner::MemoInner;
2use crate::{
3    graph::{
4        AnySource, AnySubscriber, ReactiveNode, Source, Subscriber,
5        ToAnySource, ToAnySubscriber,
6    },
7    owner::{Storage, StorageAccess, SyncStorage},
8    signal::{
9        guards::{Mapped, Plain, ReadGuard},
10        ArcReadSignal, ArcRwSignal,
11    },
12    traits::{DefinedAt, Get, IsDisposed, ReadUntracked},
13};
14use core::fmt::Debug;
15use std::{
16    hash::Hash,
17    panic::Location,
18    sync::{Arc, Weak},
19};
20
21/// An efficient derived reactive value based on other reactive values.
22///
23/// This is a reference-counted memo, which is `Clone` but not `Copy`.
24/// For arena-allocated `Copy` memos, use [`Memo`](super::Memo).
25///
26/// Unlike a "derived signal," a memo comes with two guarantees:
27/// 1. The memo will only run *once* per change, no matter how many times you
28///    access its value.
29/// 2. The memo will only notify its dependents if the value of the computation changes.
30///
31/// This makes a memo the perfect tool for expensive computations.
32///
33/// Memos have a certain overhead compared to derived signals. In most cases, you should
34/// create a derived signal. But if the derivation calculation is expensive, you should
35/// create a memo.
36///
37/// As with an [`Effect`](crate::effect::Effect), the argument to the memo function is the previous value,
38/// i.e., the current value of the memo, which will be `None` for the initial calculation.
39///
40/// ## Examples
41/// ```
42/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
43/// # use reactive_graph::computed::*;
44/// # use reactive_graph::signal::signal;
45/// # fn really_expensive_computation(value: i32) -> i32 { value };
46/// let (value, set_value) = signal(0);
47///
48/// // πŸ†— we could create a derived signal with a simple function
49/// let double_value = move || value.get() * 2;
50/// set_value.set(2);
51/// assert_eq!(double_value(), 4);
52///
53/// // but imagine the computation is really expensive
54/// let expensive = move || really_expensive_computation(value.get()); // lazy: doesn't run until called
55/// // πŸ†— run #1: calls `really_expensive_computation` the first time
56/// println!("expensive = {}", expensive());
57/// // ❌ run #2: this calls `really_expensive_computation` a second time!
58/// let some_value = expensive();
59///
60/// // instead, we create a memo
61/// // πŸ†— run #1: the calculation runs once immediately
62/// let memoized = ArcMemo::new(move |_| really_expensive_computation(value.get()));
63/// // πŸ†— reads the current value of the memo
64/// //    can be `memoized()` on nightly
65/// println!("memoized = {}", memoized.get());
66/// // βœ… reads the current value **without re-running the calculation**
67/// let some_value = memoized.get();
68/// ```
69///
70/// ## Core Trait Implementations
71/// - [`.get()`](crate::traits::Get) clones the current value of the memo.
72///   If you call it within an effect, it will cause that effect to subscribe
73///   to the memo, and to re-run whenever the value of the memo changes.
74///   - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of
75///     the memo without reactively tracking it.
76/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
77///   value of the memo by reference. If you call it within an effect, it will
78///   cause that effect to subscribe to the memo, and to re-run whenever the
79///   value of the memo changes.
80///   - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
81///     current value of the memo without reactively tracking it.
82/// - [`.with()`](crate::traits::With) allows you to reactively access the memo’s
83///   value without cloning by applying a callback function.
84///   - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
85///     the memo’s value by applying a callback function without reactively
86///     tracking it.
87/// - [`.to_stream()`](crate::traits::ToStream) converts the memo to an `async`
88///   stream of values.
89/// - [`::from_stream()`](crate::traits::FromStream) converts an `async` stream
90///   of values into a memo containing the latest value.
91pub struct ArcMemo<T, S = SyncStorage>
92where
93    S: Storage<T>,
94{
95    #[cfg(any(debug_assertions, leptos_debuginfo))]
96    defined_at: &'static Location<'static>,
97    inner: Arc<MemoInner<T, S>>,
98}
99
100impl<T: 'static> ArcMemo<T, SyncStorage>
101where
102    SyncStorage: Storage<T>,
103{
104    /// Creates a new memo by passing a function that computes the value.
105    ///
106    /// This is lazy: the function will not be called until the memo's value is read for the first
107    /// time.
108    #[track_caller]
109    #[cfg_attr(
110        feature = "tracing",
111        tracing::instrument(level = "trace", skip_all)
112    )]
113    pub fn new(fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static) -> Self
114    where
115        T: PartialEq,
116    {
117        Self::new_with_compare(fun, |lhs, rhs| lhs.as_ref() != rhs.as_ref())
118    }
119
120    /// Creates a new memo by passing a function that computes the value, and a comparison function
121    /// that takes the previous value and the new value and returns `true` if the value has
122    /// changed.
123    ///
124    /// This is lazy: the function will not be called until the memo's value is read for the first
125    /// time.
126    #[track_caller]
127    #[cfg_attr(
128        feature = "tracing",
129        tracing::instrument(level = "trace", skip_all)
130    )]
131    pub fn new_with_compare(
132        fun: impl Fn(Option<&T>) -> T + Send + Sync + 'static,
133        changed: fn(Option<&T>, Option<&T>) -> bool,
134    ) -> Self {
135        Self::new_owning(move |prev: Option<T>| {
136            let new_value = fun(prev.as_ref());
137            let changed = changed(prev.as_ref(), Some(&new_value));
138            (new_value, changed)
139        })
140    }
141
142    /// Creates a new memo by passing a function that computes the value.
143    ///
144    /// Unlike [`ArcMemo::new`](), this receives ownership of the previous value. As a result, it
145    /// must return both the new value and a `bool` that is `true` if the value has changed.
146    ///
147    /// This is lazy: the function will not be called until the memo's value is read for the first
148    /// time.
149    #[track_caller]
150    #[cfg_attr(
151        feature = "tracing",
152        tracing::instrument(level = "trace", skip_all)
153    )]
154    pub fn new_owning(
155        fun: impl Fn(Option<T>) -> (T, bool) + Send + Sync + 'static,
156    ) -> Self {
157        let inner = Arc::new_cyclic(|weak| {
158            let subscriber = AnySubscriber(
159                weak.as_ptr() as usize,
160                Weak::clone(weak) as Weak<dyn Subscriber + Send + Sync>,
161            );
162
163            MemoInner::new(Arc::new(fun), subscriber)
164        });
165        Self {
166            #[cfg(any(debug_assertions, leptos_debuginfo))]
167            defined_at: Location::caller(),
168            inner,
169        }
170    }
171}
172
173impl<T, S> Clone for ArcMemo<T, S>
174where
175    S: Storage<T>,
176{
177    fn clone(&self) -> Self {
178        Self {
179            #[cfg(any(debug_assertions, leptos_debuginfo))]
180            defined_at: self.defined_at,
181            inner: Arc::clone(&self.inner),
182        }
183    }
184}
185
186impl<T, S> DefinedAt for ArcMemo<T, S>
187where
188    S: Storage<T>,
189{
190    #[inline(always)]
191    fn defined_at(&self) -> Option<&'static Location<'static>> {
192        #[cfg(any(debug_assertions, leptos_debuginfo))]
193        {
194            Some(self.defined_at)
195        }
196        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
197        {
198            None
199        }
200    }
201}
202
203impl<T, S> Debug for ArcMemo<T, S>
204where
205    S: Storage<T>,
206{
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        f.debug_struct("ArcMemo")
209            .field("type", &std::any::type_name::<T>())
210            .field("data", &Arc::as_ptr(&self.inner))
211            .finish()
212    }
213}
214
215impl<T, S> PartialEq for ArcMemo<T, S>
216where
217    S: Storage<T>,
218{
219    fn eq(&self, other: &Self) -> bool {
220        Arc::ptr_eq(&self.inner, &other.inner)
221    }
222}
223
224impl<T, S> Eq for ArcMemo<T, S> where S: Storage<T> {}
225
226impl<T, S> Hash for ArcMemo<T, S>
227where
228    S: Storage<T>,
229{
230    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
231        std::ptr::hash(&Arc::as_ptr(&self.inner), state);
232    }
233}
234
235impl<T: 'static, S> ReactiveNode for ArcMemo<T, S>
236where
237    S: Storage<T>,
238{
239    fn mark_dirty(&self) {
240        self.inner.mark_dirty();
241    }
242
243    fn mark_check(&self) {
244        self.inner.mark_check();
245    }
246
247    fn mark_subscribers_check(&self) {
248        self.inner.mark_subscribers_check();
249    }
250
251    fn update_if_necessary(&self) -> bool {
252        self.inner.update_if_necessary()
253    }
254}
255
256impl<T: 'static, S> IsDisposed for ArcMemo<T, S>
257where
258    S: Storage<T>,
259{
260    #[inline(always)]
261    fn is_disposed(&self) -> bool {
262        false
263    }
264}
265
266impl<T: 'static, S> ToAnySource for ArcMemo<T, S>
267where
268    S: Storage<T>,
269{
270    fn to_any_source(&self) -> AnySource {
271        AnySource(
272            Arc::as_ptr(&self.inner) as usize,
273            Arc::downgrade(&self.inner) as Weak<dyn Source + Send + Sync>,
274            #[cfg(any(debug_assertions, leptos_debuginfo))]
275            self.defined_at,
276        )
277    }
278}
279
280impl<T: 'static, S> Source for ArcMemo<T, S>
281where
282    S: Storage<T>,
283{
284    fn add_subscriber(&self, subscriber: AnySubscriber) {
285        self.inner.add_subscriber(subscriber);
286    }
287
288    fn remove_subscriber(&self, subscriber: &AnySubscriber) {
289        self.inner.remove_subscriber(subscriber);
290    }
291
292    fn clear_subscribers(&self) {
293        self.inner.clear_subscribers();
294    }
295}
296
297impl<T: 'static, S> ToAnySubscriber for ArcMemo<T, S>
298where
299    S: Storage<T>,
300{
301    fn to_any_subscriber(&self) -> AnySubscriber {
302        AnySubscriber(
303            Arc::as_ptr(&self.inner) as usize,
304            Arc::downgrade(&self.inner) as Weak<dyn Subscriber + Send + Sync>,
305        )
306    }
307}
308
309impl<T: 'static, S> Subscriber for ArcMemo<T, S>
310where
311    S: Storage<T>,
312{
313    fn add_source(&self, source: AnySource) {
314        self.inner.add_source(source);
315    }
316
317    fn clear_sources(&self, subscriber: &AnySubscriber) {
318        self.inner.clear_sources(subscriber);
319    }
320}
321
322impl<T: 'static, S> ReadUntracked for ArcMemo<T, S>
323where
324    S: Storage<T>,
325{
326    type Value = ReadGuard<T, Mapped<Plain<Option<S::Wrapped>>, T>>;
327
328    fn try_read_untracked(&self) -> Option<Self::Value> {
329        self.update_if_necessary();
330
331        Mapped::try_new(Arc::clone(&self.inner.value), |t| {
332            // safe to unwrap here because update_if_necessary
333            // guarantees the value is Some
334            t.as_ref().unwrap().as_borrowed()
335        })
336        .map(ReadGuard::new)
337    }
338}
339
340impl<T> From<ArcReadSignal<T>> for ArcMemo<T, SyncStorage>
341where
342    T: Clone + PartialEq + Send + Sync + 'static,
343{
344    #[track_caller]
345    fn from(value: ArcReadSignal<T>) -> Self {
346        ArcMemo::new(move |_| value.get())
347    }
348}
349
350impl<T> From<ArcRwSignal<T>> for ArcMemo<T, SyncStorage>
351where
352    T: Clone + PartialEq + Send + Sync + 'static,
353{
354    #[track_caller]
355    fn from(value: ArcRwSignal<T>) -> Self {
356        ArcMemo::new(move |_| value.get())
357    }
358}