From 8f0a8e05b4794a406ccd8bf08be82a4ae592f2a5 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 12 Jul 2024 12:35:08 -0400 Subject: [PATCH] docs: porting docs from 0.6 to 0.7 --- reactive_graph/src/actions/action.rs | 4 + reactive_graph/src/actions/multi_action.rs | 1 + reactive_graph/src/computed/arc_memo.rs | 87 +++++++++++++++++++ .../async_derived/arc_async_derived.rs | 42 +++++++++ .../src/computed/async_derived/mod.rs | 11 +++ 5 files changed, 145 insertions(+) diff --git a/reactive_graph/src/actions/action.rs b/reactive_graph/src/actions/action.rs index 05ae35e49..546679df2 100644 --- a/reactive_graph/src/actions/action.rs +++ b/reactive_graph/src/actions/action.rs @@ -211,6 +211,7 @@ where I: Send + Sync + 'static, O: Send + Sync + 'static, { + /// Calls the `async` function with a reference to the input type as its argument. #[track_caller] pub fn dispatch(&self, input: I) { if !is_suppressing_resource_load() { @@ -261,6 +262,8 @@ where } } + /// Calls the `async` function with a reference to the input type as its argument, + /// ensuring that it is spawned on the current thread. #[track_caller] pub fn dispatch_local(&self, input: I) { if !is_suppressing_resource_load() { @@ -771,6 +774,7 @@ where inner.into() } + /// Calls the `async` function with a reference to the input type as its argument. #[track_caller] pub fn dispatch(&self, input: I) { self.inner.with_value(|inner| inner.dispatch(input)); diff --git a/reactive_graph/src/actions/multi_action.rs b/reactive_graph/src/actions/multi_action.rs index 62844b71b..da1ccbe76 100644 --- a/reactive_graph/src/actions/multi_action.rs +++ b/reactive_graph/src/actions/multi_action.rs @@ -690,6 +690,7 @@ impl Clone for ArcSubmission { } } +/// An action that has been submitted by dispatching it to a [`MultiAction`]. #[derive(Debug, PartialEq, Eq, Hash)] pub struct Submission where diff --git a/reactive_graph/src/computed/arc_memo.rs b/reactive_graph/src/computed/arc_memo.rs index fe281b4ed..a499b9841 100644 --- a/reactive_graph/src/computed/arc_memo.rs +++ b/reactive_graph/src/computed/arc_memo.rs @@ -18,6 +18,76 @@ use std::{ sync::{Arc, RwLock, Weak}, }; +/// An efficient derived reactive value based on other reactive values. +/// +/// This is a reference-counted memo, which is `Clone` but not `Copy`. +/// For arena-allocated `Copy` memos, use [`Memo`]. +/// +/// Unlike a "derived signal," a memo comes with two guarantees: +/// 1. The memo will only run *once* per change, no matter how many times you +/// access its value. +/// 2. The memo will only notify its dependents if the value of the computation changes. +/// +/// This makes a memo the perfect tool for expensive computations. +/// +/// Memos have a certain overhead compared to derived signals. In most cases, you should +/// create a derived signal. But if the derivation calculation is expensive, you should +/// create a memo. +/// +/// As with an [`Effect`](crate::effects::Effect), the argument to the memo function is the previous value, +/// i.e., the current value of the memo, which will be `None` for the initial calculation. +/// +/// ## Core Trait Implementations +/// - [`.get()`](crate::traits::Get) clones the current value of the memo. +/// If you call it within an effect, it will cause that effect to subscribe +/// to the memo, and to re-run whenever the value of the memo changes. +/// - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of +/// the memo without reactively tracking it. +/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the +/// value of the memo by reference. If you call it within an effect, it will +/// cause that effect to subscribe to the memo, and to re-run whenever the +/// value of the memo changes. +/// - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the +/// current value of the memo without reactively tracking it. +/// - [`.with()`](crate::traits::With) allows you to reactively access the memoโ€™s +/// value without cloning by applying a callback function. +/// - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access +/// the signalโ€™s value by applying a callback function without reactively +/// tracking it. +/// - [`.to_stream()`](crate::traits::ToStream) converts the memo to an `async` +/// stream of values. +/// - [`::from_stream()`](crate::traits::FromStream) converts an `async` stream +/// of values into a memo containing the latest value. +/// +/// ## Examples +/// ``` +/// # use reactive_graph::prelude::*; +/// # use reactive_graph::computed::*; +/// # use reactive_graph::signal::signal; +/// # fn really_expensive_computation(value: i32) -> i32 { value }; +/// let (value, set_value) = signal(0); +/// +/// // ๐Ÿ†— we could create a derived signal with a simple function +/// let double_value = move || value.get() * 2; +/// set_value.set(2); +/// assert_eq!(double_value(), 4); +/// +/// // but imagine the computation is really expensive +/// let expensive = move || really_expensive_computation(value.get()); // lazy: doesn't run until called +/// // ๐Ÿ†— run #1: calls `really_expensive_computation` the first time +/// println!("expensive = {}", expensive()); +/// // โŒ run #2: this calls `really_expensive_computation` a second time! +/// let some_value = expensive(); +/// +/// // instead, we create a memo +/// // ๐Ÿ†— run #1: the calculation runs once immediately +/// let memoized = ArcMemo::new(move |_| really_expensive_computation(value.get())); +/// // ๐Ÿ†— reads the current value of the memo +/// // can be `memoized()` on nightly +/// println!("memoized = {}", memoized.get()); +/// // โœ… reads the current value **without re-running the calculation** +/// let some_value = memoized.get(); +/// ``` pub struct ArcMemo { #[cfg(debug_assertions)] defined_at: &'static Location<'static>, @@ -25,6 +95,10 @@ pub struct ArcMemo { } impl ArcMemo { + /// Creates a new memo by passing a function that computes the value. + /// + /// This is lazy: the function will not be called until the memo's value is read for the first + /// time. #[track_caller] #[cfg_attr( feature = "tracing", @@ -37,6 +111,12 @@ impl ArcMemo { Self::new_with_compare(fun, |lhs, rhs| lhs.as_ref() != rhs.as_ref()) } + /// Creates a new memo by passing a function that computes the value, and a comparison function + /// that takes the previous value and the new value and returns `true` if the value has + /// changed. + /// + /// This is lazy: the function will not be called until the memo's value is read for the first + /// time. #[track_caller] #[cfg_attr( feature = "tracing", @@ -56,6 +136,13 @@ impl ArcMemo { }) } + /// Creates a new memo by passing a function that computes the value. + /// + /// Unlike [`ArcMemo::new`](), this receives ownership of the previous value. As a result, it + /// must return both the new value and a `bool` that is `true` if the value has changed. + /// + /// This is lazy: the function will not be called until the memo's value is read for the first + /// time. #[track_caller] #[cfg_attr( feature = "tracing", diff --git a/reactive_graph/src/computed/async_derived/arc_async_derived.rs b/reactive_graph/src/computed/async_derived/arc_async_derived.rs index 0c67d3ca2..f6f76364d 100644 --- a/reactive_graph/src/computed/async_derived/arc_async_derived.rs +++ b/reactive_graph/src/computed/async_derived/arc_async_derived.rs @@ -32,6 +32,48 @@ use std::{ task::Waker, }; +/// A reactive value that is derived by running an asynchronous computation in response to changes +/// in its sources. +/// +/// When one of its dependencies changes, this will re-run its async computation, then notify other +/// values that depend on it that it has changed. +/// +/// ## Examples +/// ```rust +/// # use reactive_graph::computed::*; +/// # use reactive_graph::signal::*; +/// # use reactive_graph::prelude::*; +/// # tokio_test::block_on(async move { +/// # any_spawner::Executor::init_tokio(); +/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter(); +/// +/// let signal1 = RwSignal::new(0); +/// let signal2 = RwSignal::new(0); +/// let derived = ArcAsyncDerived::new(move || async move { +/// // reactive values can be tracked anywhere in the `async` block +/// let value1 = signal1.get(); +/// tokio::time::sleep(std::time::Duration::from_millis(25)).await; +/// let value2 = signal2.get(); +/// +/// value1 + value2 +/// }); +/// +/// // the value can be accessed synchronously as `Option` +/// assert_eq!(derived.get(), None); +/// // we can also .await the value, i.e., convert it into a Future +/// assert_eq!(derived.clone().await, 0); +/// assert_eq!(derived.get(), Some(0)); +/// +/// signal1.set(1); +/// // while the new value is still pending, the signal holds the old value +/// tokio::time::sleep(std::time::Duration::from_millis(5)).await; +/// assert_eq!(derived.get(), Some(0)); +/// +/// // setting multiple dependencies will hold until the latest change is ready +/// signal2.set(1); +/// assert_eq!(derived.clone().await, 2); +/// # }); +/// ``` pub struct ArcAsyncDerived { #[cfg(debug_assertions)] pub(crate) defined_at: &'static Location<'static>, diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index 1e0e4eadf..f44534b2d 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -18,6 +18,8 @@ use std::{ }; pin_project! { + /// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner + /// `Future`. pub struct ScopedFuture { owner: Option, observer: Option, @@ -27,6 +29,8 @@ pin_project! { } impl ScopedFuture { + /// Wraps the given `Future` by taking the current [`Owner`] and [`Observer`] and re-setting + /// them as the active owner and observer every time the inner `Future` is polled. pub fn new(fut: Fut) -> Self { let owner = Owner::current(); let observer = Observer::get(); @@ -54,6 +58,7 @@ impl Future for ScopedFuture { } } +/// Utilities used to track whether asynchronous computeds are currently loading. pub mod suspense { use crate::{ signal::ArcRwSignal, @@ -64,10 +69,13 @@ pub mod suspense { use slotmap::{DefaultKey, SlotMap}; use std::sync::{Arc, Mutex}; + /// Sends a one-time notification that the resource being read from is "local only," i.e., + /// that it will only run on the client, not the server. #[derive(Clone, Debug)] pub struct LocalResourceNotifier(Arc>>>); impl LocalResourceNotifier { + /// Send the notification. If the inner channel has already been used, this does nothing. pub fn notify(&mut self) { if let Some(tx) = self.0.lock().or_poisoned().take() { tx.send(()).unwrap(); @@ -81,12 +89,15 @@ pub mod suspense { } } + /// Tracks the collection of active async tasks. #[derive(Clone, Debug)] pub struct SuspenseContext { + /// The set of active tasks. pub tasks: ArcRwSignal>, } impl SuspenseContext { + /// Generates a unique task ID. pub fn task_id(&self) -> TaskHandle { let key = self.tasks.write().insert(()); TaskHandle {