docs: porting docs from 0.6 to 0.7

This commit is contained in:
Greg Johnston 2024-07-12 12:35:08 -04:00
parent 05d01141c5
commit 8f0a8e05b4
5 changed files with 145 additions and 0 deletions

View file

@ -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));

View file

@ -690,6 +690,7 @@ impl<I, O> Clone for ArcSubmission<I, O> {
}
}
/// An action that has been submitted by dispatching it to a [`MultiAction`].
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Submission<I, O>
where

View file

@ -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 memos
/// value without cloning by applying a callback function.
/// - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
/// the signals 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<T> {
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
@ -25,6 +95,10 @@ pub struct ArcMemo<T> {
}
impl<T: Send + Sync + 'static> ArcMemo<T> {
/// 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<T: Send + Sync + 'static> ArcMemo<T> {
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<T: Send + Sync + 'static> ArcMemo<T> {
})
}
/// 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",

View file

@ -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<T>`
/// 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<T> {
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static Location<'static>,

View file

@ -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<Fut> {
owner: Option<Owner>,
observer: Option<AnySubscriber>,
@ -27,6 +29,8 @@ pin_project! {
}
impl<Fut> ScopedFuture<Fut> {
/// 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<Fut: Future> Future for ScopedFuture<Fut> {
}
}
/// 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<Mutex<Option<Sender<()>>>>);
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<SlotMap<DefaultKey, ()>>,
}
impl SuspenseContext {
/// Generates a unique task ID.
pub fn task_id(&self) -> TaskHandle {
let key = self.tasks.write().insert(());
TaskHandle {