mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
docs: finish reactive graph docs for 0.7
This commit is contained in:
parent
8f0a8e05b4
commit
dac4589194
25 changed files with 603 additions and 70 deletions
|
@ -335,7 +335,7 @@ where
|
|||
|
||||
/// Creates a new action that will only run on the current thread, initializing it with the given value.
|
||||
///
|
||||
/// In all other ways, this is identical to [`ArcAsync::new_with_value`].
|
||||
/// In all other ways, this is identical to [`ArcAction::new_with_value`].
|
||||
#[track_caller]
|
||||
pub fn new_unsync_with_value<F, Fu>(value: Option<O>, action_fn: F) -> Self
|
||||
where
|
||||
|
|
|
@ -622,7 +622,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// An action that has been submitted by dispatching it to a [MultiAction](crate::MultiAction).
|
||||
/// An action that has been submitted by dispatching it to a [`MultiAction`].
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ArcSubmission<I, O>
|
||||
where
|
||||
|
|
|
@ -21,7 +21,7 @@ use std::{
|
|||
/// 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`].
|
||||
/// For arena-allocated `Copy` memos, use [`Memo`](super::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
|
||||
|
@ -34,31 +34,9 @@ use std::{
|
|||
/// 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,
|
||||
/// As with an [`Effect`](crate::effect::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::*;
|
||||
|
@ -88,6 +66,28 @@ use std::{
|
|||
/// // ✅ reads the current value **without re-running the calculation**
|
||||
/// let some_value = memoized.get();
|
||||
/// ```
|
||||
///
|
||||
/// ## 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 memo’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.
|
||||
pub struct ArcMemo<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
|
|
|
@ -38,6 +38,9 @@ use std::{
|
|||
/// 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.
|
||||
///
|
||||
/// This is a reference-counted type, which is `Clone` but not `Copy`.
|
||||
/// For arena-allocated `Copy` memos, use [`AsyncDerived`](super::AsyncDerived).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
|
@ -71,9 +74,29 @@ use std::{
|
|||
///
|
||||
/// // setting multiple dependencies will hold until the latest change is ready
|
||||
/// signal2.set(1);
|
||||
/// assert_eq!(derived.clone().await, 2);
|
||||
/// assert_eq!(derived.await, 2);
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value as an `Option<T>`.
|
||||
/// 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
|
||||
/// without reactively tracking it.
|
||||
/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
|
||||
/// value 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 changes.
|
||||
/// - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
|
||||
/// current value without reactively tracking it.
|
||||
/// - [`.with()`](crate::traits::With) allows you to reactively access the
|
||||
/// value without cloning by applying a callback function.
|
||||
/// - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
|
||||
/// the value by applying a callback function without reactively
|
||||
/// tracking it.
|
||||
/// - [`IntoFuture`](std::future::Future) allows you to create a [`Future`] that resolves
|
||||
/// when this resource is done loading.
|
||||
pub struct ArcAsyncDerived<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
|
@ -310,6 +333,10 @@ macro_rules! spawn_derived {
|
|||
}
|
||||
|
||||
impl<T: 'static> ArcAsyncDerived<T> {
|
||||
/// Creates a new async derived computation.
|
||||
///
|
||||
/// This runs eagerly: i.e., calls `fun` once when created and immediately spawns the `Future`
|
||||
/// as a new task.
|
||||
#[track_caller]
|
||||
pub fn new<Fut>(fun: impl Fn() -> Fut + Send + Sync + 'static) -> Self
|
||||
where
|
||||
|
@ -319,6 +346,9 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
|||
Self::new_with_initial(None, fun)
|
||||
}
|
||||
|
||||
/// Creates a new async derived computation with an initial value.
|
||||
///
|
||||
/// If the initial value is `Some(_)`, the task will not be run initially.
|
||||
#[track_caller]
|
||||
pub fn new_with_initial<Fut>(
|
||||
initial_value: Option<T>,
|
||||
|
@ -332,6 +362,11 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
|||
this
|
||||
}
|
||||
|
||||
/// Creates a new async derived computation that will be guaranteed to run on the current
|
||||
/// thread.
|
||||
///
|
||||
/// This runs eagerly: i.e., calls `fun` once when created and immediately spawns the `Future`
|
||||
/// as a new task.
|
||||
#[track_caller]
|
||||
pub fn new_unsync<Fut>(fun: impl Fn() -> Fut + 'static) -> Self
|
||||
where
|
||||
|
@ -341,6 +376,10 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
|||
Self::new_unsync_with_initial(None, fun)
|
||||
}
|
||||
|
||||
/// Creates a new async derived computation with an initial value. Async work will be
|
||||
/// guaranteed to run only on the current thread.
|
||||
///
|
||||
/// If the initial value is `Some(_)`, the task will not be run initially.
|
||||
#[track_caller]
|
||||
pub fn new_unsync_with_initial<Fut>(
|
||||
initial_value: Option<T>,
|
||||
|
@ -355,6 +394,7 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
|||
this
|
||||
}
|
||||
|
||||
/// Returns a `Future` that is ready when this resource has next finished loading.
|
||||
pub fn ready(&self) -> AsyncDerivedReadyFuture {
|
||||
AsyncDerivedReadyFuture {
|
||||
source: self.to_any_source(),
|
||||
|
|
|
@ -12,6 +12,72 @@ use crate::{
|
|||
use core::fmt::Debug;
|
||||
use std::{future::Future, panic::Location};
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This is an arena-allocated type, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that livesas
|
||||
/// as long as a reference to it is alive, see [`ArcAsyncDerived`].
|
||||
///
|
||||
/// ## 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 = AsyncDerived::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.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.await, 2);
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value as an `Option<T>`.
|
||||
/// 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
|
||||
/// without reactively tracking it.
|
||||
/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
|
||||
/// value 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 changes.
|
||||
/// - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
|
||||
/// current value without reactively tracking it.
|
||||
/// - [`.with()`](crate::traits::With) allows you to reactively access the
|
||||
/// value without cloning by applying a callback function.
|
||||
/// - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
|
||||
/// the value by applying a callback function without reactively
|
||||
/// tracking it.
|
||||
/// - [`IntoFuture`](std::future::Future) allows you to create a [`Future`] that resolves
|
||||
/// when this resource is done loading.
|
||||
pub struct AsyncDerived<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
|
@ -37,6 +103,10 @@ impl<T: Send + Sync + 'static> From<ArcAsyncDerived<T>> for AsyncDerived<T> {
|
|||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> AsyncDerived<T> {
|
||||
/// Creates a new async derived computation.
|
||||
///
|
||||
/// This runs eagerly: i.e., calls `fun` once when created and immediately spawns the `Future`
|
||||
/// as a new task.
|
||||
#[track_caller]
|
||||
pub fn new<Fut>(fun: impl Fn() -> Fut + Send + Sync + 'static) -> Self
|
||||
where
|
||||
|
@ -50,6 +120,9 @@ impl<T: Send + Sync + 'static> AsyncDerived<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new async derived computation with an initial value.
|
||||
///
|
||||
/// If the initial value is `Some(_)`, the task will not be run initially.
|
||||
pub fn new_with_initial<Fut>(
|
||||
initial_value: Option<T>,
|
||||
fun: impl Fn() -> Fut + Send + Sync + 'static,
|
||||
|
@ -68,6 +141,11 @@ impl<T: Send + Sync + 'static> AsyncDerived<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new async derived computation that will be guaranteed to run on the current
|
||||
/// thread.
|
||||
///
|
||||
/// This runs eagerly: i.e., calls `fun` once when created and immediately spawns the `Future`
|
||||
/// as a new task.
|
||||
pub fn new_unsync<Fut>(fun: impl Fn() -> Fut + 'static) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -80,6 +158,10 @@ impl<T: Send + Sync + 'static> AsyncDerived<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new async derived computation with an initial value. Async work will be
|
||||
/// guaranteed to run only on the current thread.
|
||||
///
|
||||
/// If the initial value is `Some(_)`, the task will not be run initially.
|
||||
pub fn new_unsync_with_initial<Fut>(
|
||||
initial_value: Option<T>,
|
||||
fun: impl Fn() -> Fut + 'static,
|
||||
|
@ -98,6 +180,7 @@ impl<T: Send + Sync + 'static> AsyncDerived<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a `Future` that is ready when this resource has next finished loading.
|
||||
#[track_caller]
|
||||
pub fn ready(&self) -> AsyncDerivedReadyFuture {
|
||||
let this = self.inner.get().unwrap_or_else(unwrap_signal!(self));
|
||||
|
|
|
@ -111,6 +111,8 @@ where
|
|||
}
|
||||
|
||||
impl<T: 'static> ArcAsyncDerived<T> {
|
||||
/// Returns a `Future` that resolves when the computation is finished, and accesses the inner
|
||||
/// value by reference rather than by cloning it.
|
||||
#[track_caller]
|
||||
pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
|
||||
AsyncDerivedRefFuture {
|
||||
|
@ -123,6 +125,8 @@ impl<T: 'static> ArcAsyncDerived<T> {
|
|||
}
|
||||
|
||||
impl<T: 'static> AsyncDerived<T> {
|
||||
/// Returns a `Future` that resolves when the computation is finished, and accesses the inner
|
||||
/// value by reference rather than by cloning it.
|
||||
#[track_caller]
|
||||
pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
|
||||
let this = self.inner.get().unwrap_or_else(unwrap_signal!(self));
|
||||
|
|
|
@ -26,6 +26,10 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
|
|||
/// Memos are lazy: they do not run at all until they are read for the first time, and they will
|
||||
/// not re-run the calculation when a source signal changes until they are read again.
|
||||
///
|
||||
/// This is an arena-allocated type, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that livesas
|
||||
/// as long as a reference to it is alive, see [`ArcMemo`].
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::computed::Memo;
|
||||
|
@ -70,6 +74,28 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
|
|||
/// # });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// ## 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 memo’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.
|
||||
pub struct Memo<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
|
@ -154,6 +180,13 @@ impl<T: Send + Sync + 'static> Memo<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",
|
||||
|
|
|
@ -12,6 +12,52 @@ use std::{
|
|||
|
||||
/// A conditional signal that only notifies subscribers when a change
|
||||
/// in the source signal’s value changes whether the given function is true.
|
||||
///
|
||||
/// **You probably don’t need this,** but it can be a very useful optimization
|
||||
/// in certain situations (e.g., “set the class `selected` if `selected() == this_row_index`)
|
||||
/// because it reduces them from `O(n)` to `O(1)`.
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let is_selected = Selector::new(move || a.get());
|
||||
/// let total_notifications = StoredValue::new(0);
|
||||
/// Effect::new({
|
||||
/// let is_selected = is_selected.clone();
|
||||
/// move |_| {
|
||||
/// if is_selected.selected(5) {
|
||||
/// total_notifications.update_value(|n| *n += 1);
|
||||
/// }
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(is_selected.selected(5), false);
|
||||
/// assert_eq!(total_notifications.get_value(), 0);
|
||||
/// a.set(5);
|
||||
/// # any_spawner::Executor::tick().await;
|
||||
///
|
||||
/// assert_eq!(is_selected.selected(5), true);
|
||||
/// assert_eq!(total_notifications.get_value(), 1);
|
||||
/// a.set(5);
|
||||
/// # any_spawner::Executor::tick().await;
|
||||
///
|
||||
/// assert_eq!(is_selected.selected(5), true);
|
||||
/// assert_eq!(total_notifications.get_value(), 1);
|
||||
/// a.set(4);
|
||||
///
|
||||
/// # any_spawner::Executor::tick().await;
|
||||
/// assert_eq!(is_selected.selected(5), false);
|
||||
/// # });
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct Selector<T>
|
||||
where
|
||||
|
@ -30,10 +76,13 @@ impl<T> Selector<T>
|
|||
where
|
||||
T: PartialEq + Eq + Clone + Hash + 'static,
|
||||
{
|
||||
/// Creates a new selector that compares values using [`PartialEq`].
|
||||
pub fn new(source: impl Fn() -> T + Clone + 'static) -> Self {
|
||||
Self::new_with_fn(source, PartialEq::eq)
|
||||
}
|
||||
|
||||
/// Creates a new selector that compares values by returning `true` from a comparator function
|
||||
/// if the values are the same.
|
||||
pub fn new_with_fn(
|
||||
source: impl Fn() -> T + Clone + 'static,
|
||||
f: impl Fn(&T, &T) -> bool + Send + Sync + Clone + 'static,
|
||||
|
|
|
@ -15,6 +15,62 @@ use std::{
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// Effects run a certain chunk of code whenever the signals they depend on change.
|
||||
/// Creating an effect runs the given function once after any current synchronous work is done.
|
||||
/// This tracks its reactive values read within it, and reruns the function whenever the value
|
||||
/// of a dependency changes.
|
||||
///
|
||||
/// Effects are intended to run *side-effects* of the system, not to synchronize state
|
||||
/// *within* the system. In other words: In most cases, you usually should not write to
|
||||
/// signals inside effects. (If you need to define a signal that depends on the value of
|
||||
/// other signals, use a derived signal or a [`Memo`](crate::computed::Memo)).
|
||||
///
|
||||
/// The effect function is called with an argument containing whatever value it returned
|
||||
/// the last time it ran. On the initial run, this is `None`.
|
||||
///
|
||||
/// Effects stop running when their reactive [`Owner`] is disposed.
|
||||
///
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::effect::Effect;
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # tokio::task::LocalSet::new().run_until(async move {
|
||||
/// let a = RwSignal::new(0);
|
||||
/// let b = RwSignal::new(0);
|
||||
///
|
||||
/// // ✅ use effects to interact between reactive state and the outside world
|
||||
/// Effect::new(move |_| {
|
||||
/// // on the next “tick” prints "Value: 0" and subscribes to `a`
|
||||
/// println!("Value: {}", a.get());
|
||||
/// });
|
||||
///
|
||||
/// a.set(1);
|
||||
/// // ✅ because it's subscribed to `a`, the effect reruns and prints "Value: 1"
|
||||
///
|
||||
/// // ❌ don't use effects to synchronize state within the reactive system
|
||||
/// Effect::new(move |_| {
|
||||
/// // this technically works but can cause unnecessary re-renders
|
||||
/// // and easily lead to problems like infinite loops
|
||||
/// b.set(a.get() + 1);
|
||||
/// });
|
||||
/// # });
|
||||
/// # });
|
||||
/// ```
|
||||
/// ## Web-Specific Notes
|
||||
///
|
||||
/// 1. **Scheduling**: Effects run after synchronous work, on the next “tick” of the reactive
|
||||
/// system. This makes them suitable for “on mount” actions: they will fire immediately after
|
||||
/// DOM rendering.
|
||||
/// 2. By default, effects do not run unless the `effects` feature is enabled. If you are using
|
||||
/// this with a web framework, this generally means that effects **do not run on the server**.
|
||||
/// and you can call browser-specific APIs within the effect function without causing issues.
|
||||
/// If you need an effect to run on the server, use [`Effect::new_isomorphic`].
|
||||
pub struct Effect {
|
||||
inner: StoredValue<Option<Arc<RwLock<EffectInner>>>>,
|
||||
}
|
||||
|
@ -44,10 +100,17 @@ fn effect_base() -> (Receiver, Owner, Arc<RwLock<EffectInner>>) {
|
|||
}
|
||||
|
||||
impl Effect {
|
||||
/// Stops this effect before it is disposed.
|
||||
pub fn stop(self) {
|
||||
drop(self.inner.try_update_value(|inner| inner.take()));
|
||||
}
|
||||
|
||||
/// Creates a new effect, which runs once on the next “tick”, and then runs again when reactive values
|
||||
/// that are read inside it change.
|
||||
///
|
||||
/// This spawns a task on the local thread using
|
||||
/// [`spawn_local`](any_spawner::Executor::spawn_local). For an effect that can be spawned on
|
||||
/// any thread, use [`new_sync`](Effect::new_sync).
|
||||
pub fn new<T>(mut fun: impl FnMut(Option<T>) -> T + 'static) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -88,6 +151,11 @@ impl Effect {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new effect, which runs once on the next “tick”, and then runs again when reactive values
|
||||
/// that are read inside it change.
|
||||
///
|
||||
/// This spawns a task that can be run on any thread. For an effect that will be spawned on
|
||||
/// the current thread, use [`new`](Effect::new).
|
||||
pub fn new_sync<T>(
|
||||
mut fun: impl FnMut(Option<T>) -> T + Send + Sync + 'static,
|
||||
) -> Self
|
||||
|
@ -130,6 +198,10 @@ impl Effect {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new effect, which runs once on the next “tick”, and then runs again when reactive values
|
||||
/// that are read inside it change.
|
||||
///
|
||||
/// This will run whether the `effects` feature is enabled or not.
|
||||
pub fn new_isomorphic<T>(
|
||||
mut fun: impl FnMut(Option<T>) -> T + Send + Sync + 'static,
|
||||
) -> Self
|
||||
|
@ -180,6 +252,7 @@ impl ToAnySubscriber for Effect {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates an [`Effect`].
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
#[deprecated = "This function is being removed to conform to Rust \
|
||||
|
|
|
@ -15,6 +15,18 @@ use std::{
|
|||
sync::{Arc, RwLock, Weak},
|
||||
};
|
||||
|
||||
/// A render effect is similar to an [`Effect`](super::Effect), but with two key differences:
|
||||
/// 1. Its first run takes place immediately and synchronously: for example, if it is being used to
|
||||
/// drive a user interface, it will run during rendering, not on the next tick after rendering.
|
||||
/// (Hence “render effect.”)
|
||||
/// 2. It is canceled when the `RenderEffect` itself is dropped, rather than being stored in the
|
||||
/// reactive system and canceled when the `Owner` cleans up.
|
||||
///
|
||||
/// Unless you are implementing a rendering framework, or require one of these two characteristics,
|
||||
/// it is unlikely you will use render effects directly.
|
||||
///
|
||||
/// Like an [`Effect`](super::Effect), a render effect runs only with the `effects` feature
|
||||
/// enabled.
|
||||
#[must_use = "A RenderEffect will be canceled when it is dropped. Creating a \
|
||||
RenderEffect that is not stored in some other data structure or \
|
||||
leaked will drop it immediately, and it will not react to \
|
||||
|
@ -39,10 +51,12 @@ impl<T> RenderEffect<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Creates a new render effect, which immediately runs `fun`.
|
||||
pub fn new(fun: impl FnMut(Option<T>) -> T + 'static) -> Self {
|
||||
Self::new_with_value(fun, None)
|
||||
}
|
||||
|
||||
/// Creates a new render effect with an initial value.
|
||||
pub fn new_with_value(
|
||||
fun: impl FnMut(Option<T>) -> T + 'static,
|
||||
initial_value: Option<T>,
|
||||
|
@ -100,6 +114,7 @@ where
|
|||
erased(Box::new(fun), initial_value)
|
||||
}
|
||||
|
||||
/// Mutably accesses the current value.
|
||||
pub fn with_value_mut<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
|
@ -107,6 +122,7 @@ where
|
|||
self.value.write().or_poisoned().as_mut().map(fun)
|
||||
}
|
||||
|
||||
/// Takes the current value, replacing it with `None`.
|
||||
pub fn take_value(&self) -> Option<T> {
|
||||
self.value.write().or_poisoned().take()
|
||||
}
|
||||
|
@ -116,7 +132,7 @@ impl<T> RenderEffect<T>
|
|||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[doc(hidden)]
|
||||
/// Creates a render effect that will run whether the `effects` feature is enabled or not.
|
||||
pub fn new_isomorphic(
|
||||
mut fun: impl FnMut(Option<T>) -> T + Send + 'static,
|
||||
) -> Self {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// A node in the reactive graph.
|
||||
pub trait ReactiveNode {
|
||||
/// Notifies the source's dependencies that it has changed.
|
||||
fn mark_dirty(&self);
|
||||
|
@ -13,9 +14,14 @@ pub trait ReactiveNode {
|
|||
fn update_if_necessary(&self) -> bool;
|
||||
}
|
||||
|
||||
/// The current state of a reactive node.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ReactiveNodeState {
|
||||
/// The node is known to be clean: i.e., either none of its sources have changed, or its
|
||||
/// sources have changed but its value is unchanged and its dependencies do not need to change.
|
||||
Clean,
|
||||
/// The node may have changed, but it is not yet known whether it has actually changed.
|
||||
Check,
|
||||
/// The node's value has definitely changed, and subscribers will need to update.
|
||||
Dirty,
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::traits::DefinedAt;
|
|||
use core::{fmt::Debug, hash::Hash};
|
||||
use std::{panic::Location, sync::Weak};
|
||||
|
||||
/// Abstracts over the type of any reactive source.
|
||||
pub trait ToAnySource {
|
||||
/// Converts this type to its type-erased equivalent.
|
||||
fn to_any_source(&self) -> AnySource;
|
||||
|
@ -20,6 +21,7 @@ pub trait Source: ReactiveNode {
|
|||
fn clear_subscribers(&self);
|
||||
}
|
||||
|
||||
/// A weak reference to any reactive source node.
|
||||
#[derive(Clone)]
|
||||
pub struct AnySource(
|
||||
pub(crate) usize,
|
||||
|
|
|
@ -6,6 +6,11 @@ thread_local! {
|
|||
static OBSERVER: RefCell<Option<AnySubscriber>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// The current reactive observer.
|
||||
///
|
||||
/// The observer is whatever reactive node is currently listening for signals that need to be
|
||||
/// tracked. For example, if an effect is running, that effect is the observer, which means it will
|
||||
/// subscribe to changes in any signals that are read.
|
||||
pub struct Observer;
|
||||
|
||||
struct SetObserverOnDrop(Option<AnySubscriber>);
|
||||
|
@ -17,6 +22,7 @@ impl Drop for SetObserverOnDrop {
|
|||
}
|
||||
|
||||
impl Observer {
|
||||
/// Returns the current observer, if any.
|
||||
pub fn get() -> Option<AnySubscriber> {
|
||||
OBSERVER.with_borrow(Clone::clone)
|
||||
}
|
||||
|
@ -41,6 +47,34 @@ impl Observer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Suspends reactive tracking while running the given function.
|
||||
///
|
||||
/// This can be used to isolate parts of the reactive graph from one another.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use reactive_graph::computed::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::untrack;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// let (a, set_a) = signal(0);
|
||||
/// let (b, set_b) = signal(0);
|
||||
/// let c = Memo::new(move |_| {
|
||||
/// // this memo will *only* update when `a` changes
|
||||
/// a.get() + untrack(move || b.get())
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(c.get(), 0);
|
||||
/// set_a.set(1);
|
||||
/// assert_eq!(c.get(), 1);
|
||||
/// set_b.set(1);
|
||||
/// // hasn't updated, because we untracked before reading b
|
||||
/// assert_eq!(c.get(), 1);
|
||||
/// set_a.set(2);
|
||||
/// assert_eq!(c.get(), 3);
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn untrack<T>(fun: impl FnOnce() -> T) -> T {
|
||||
#[cfg(debug_assertions)]
|
||||
let _warning_guard = crate::diagnostics::SpecialNonReactiveZone::enter();
|
||||
|
@ -60,7 +94,7 @@ pub trait Subscriber: ReactiveNode {
|
|||
/// Adds a subscriber to this subscriber's list of dependencies.
|
||||
fn add_source(&self, source: AnySource);
|
||||
|
||||
// Clears the set of sources for this subscriber.
|
||||
/// Clears the set of sources for this subscriber.
|
||||
fn clear_sources(&self, subscriber: &AnySubscriber);
|
||||
}
|
||||
|
||||
|
@ -117,6 +151,7 @@ impl ReactiveNode for AnySubscriber {
|
|||
}
|
||||
|
||||
impl AnySubscriber {
|
||||
/// Runs the given function with this subscriber as the thread-local [`Observer`].
|
||||
pub fn with_observer<T>(&self, fun: impl FnOnce() -> T) -> T {
|
||||
let _prev = Observer::replace(self.clone());
|
||||
fun()
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
//#![deny(missing_docs)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::fmt::Arguments;
|
||||
|
||||
|
|
|
@ -3,11 +3,18 @@ use super::{
|
|||
OWNER,
|
||||
};
|
||||
use crate::{
|
||||
traits::{DefinedAt, Dispose},
|
||||
traits::{DefinedAt, Dispose, IsDisposed},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::{any::Any, hash::Hash, marker::PhantomData, panic::Location};
|
||||
|
||||
/// A **non-reactive**, `Copy` handle for any value.
|
||||
///
|
||||
/// This allows you to create a stable reference for any value by storing it within
|
||||
/// the reactive system. Like the signal types (e.g., [`ReadSignal`](crate::signal::ReadSignal)
|
||||
/// and [`RwSignal`](crate::signal::RwSignal)), it is `Copy` and `'static`. Unlike the signal
|
||||
/// types, it is not reactive; accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
#[derive(Debug)]
|
||||
pub struct StoredValue<T> {
|
||||
node: NodeId,
|
||||
|
@ -56,6 +63,7 @@ impl<T> StoredValue<T>
|
|||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Stores the given value in the arena allocator.
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
let node = {
|
||||
|
@ -79,6 +87,8 @@ where
|
|||
}
|
||||
|
||||
impl<T: 'static> StoredValue<T> {
|
||||
/// Same as [`StoredValue::with_value`] but returns `Some(O)` only if
|
||||
/// the stored value has not yet been disposed, `None` otherwise.
|
||||
pub fn try_with_value<U>(&self, fun: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
Arena::with(|arena| {
|
||||
let m = arena.get(self.node);
|
||||
|
@ -86,11 +96,28 @@ impl<T: 'static> StoredValue<T> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Applies a function to the current stored value and returns the result.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value owned by a reactive node that has been disposed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String,
|
||||
/// }
|
||||
/// let data = StoredValue::new(MyUncloneableData { value: "a".into() });
|
||||
///
|
||||
/// // calling .with_value() to extract the value
|
||||
/// data.with_value(|data| assert_eq!(data.value, "a"));
|
||||
pub fn with_value<U>(&self, fun: impl FnOnce(&T) -> U) -> U {
|
||||
self.try_with_value(fun)
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Updates the current value by applying the given closure, returning the return value of the
|
||||
/// closure, or `None` if the value has already been disposed.
|
||||
pub fn try_update_value<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&mut T) -> U,
|
||||
|
@ -101,10 +128,23 @@ impl<T: 'static> StoredValue<T> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Updates the stored value by applying the given closure.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::owner::StoredValue;
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String,
|
||||
/// }
|
||||
/// let data = StoredValue::new(MyUncloneableData { value: "a".into() });
|
||||
/// data.update_value(|data| data.value = "b".into());
|
||||
/// assert_eq!(data.with_value(|data| data.value.clone()), "b");
|
||||
/// ```
|
||||
pub fn update_value<U>(&self, fun: impl FnOnce(&mut T) -> U) {
|
||||
self.try_update_value(fun);
|
||||
}
|
||||
|
||||
/// Tries to set the value. If the value has been disposed, returns `Some(value)`.
|
||||
pub fn try_set_value(&self, value: T) -> Option<T> {
|
||||
Arena::with_mut(|arena| {
|
||||
let m = arena.get_mut(self.node);
|
||||
|
@ -118,10 +158,12 @@ impl<T: 'static> StoredValue<T> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Sets the value to a new value.
|
||||
pub fn set_value(&self, value: T) {
|
||||
self.update_value(|n| *n = value);
|
||||
}
|
||||
|
||||
/// Returns `true` if the value has not yet been disposed.
|
||||
pub fn exists(&self) -> bool
|
||||
where
|
||||
T: Clone,
|
||||
|
@ -130,14 +172,25 @@ impl<T: 'static> StoredValue<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> IsDisposed for StoredValue<T> {
|
||||
fn is_disposed(&self) -> bool {
|
||||
Arena::with(|arena| arena.contains_key(self.node))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StoredValue<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
/// Clones and returns the current value, or `None` if it has already been disposed.
|
||||
pub fn try_get_value(&self) -> Option<T> {
|
||||
self.try_with_value(T::clone)
|
||||
}
|
||||
|
||||
/// Clones and returns the current value.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value owned by a reactive node that has been disposed.
|
||||
pub fn get_value(&self) -> T {
|
||||
self.with_value(T::clone)
|
||||
}
|
||||
|
@ -153,6 +206,7 @@ impl<T> Dispose for StoredValue<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`StoredValue`].
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
#[deprecated = "This function is being removed to conform to Rust idioms. \
|
||||
|
|
|
@ -75,8 +75,8 @@ pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
|||
/// [`ReadSignal`] and a [`WriteSignal`].
|
||||
///
|
||||
/// This returns an arena-allocated signal, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`] cleans up. For a reference-counted signal that lives as long as a reference to it is
|
||||
/// alive, see [`arc_signal`].
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that lives
|
||||
/// as long as a reference to it is alive, see [`arc_signal`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
|
@ -121,8 +121,8 @@ pub fn signal<T: Send + Sync>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
|
|||
/// [`ReadSignal`] and a [`WriteSignal`].
|
||||
///
|
||||
/// This returns an arena-allocated signal, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`] cleans up. For a reference-counted signal that lives as long as a reference to it is
|
||||
/// alive, see [`arc_signal`].
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that lives
|
||||
/// as long as a reference to it is alive, see [`arc_signal`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
|
|
|
@ -19,7 +19,7 @@ use std::{
|
|||
/// and notifies other code when it has changed.
|
||||
///
|
||||
/// This is a reference-counted signal, which is `Clone` but not `Copy`.
|
||||
/// For arena-allocated `Copy` signals, use [`ReadSignal`].
|
||||
/// For arena-allocated `Copy` signals, use [`ReadSignal`](super::ReadSignal).
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
|
||||
|
@ -82,8 +82,14 @@ impl<T> Debug for ArcReadSignal<T> {
|
|||
}
|
||||
|
||||
impl<T: Default> Default for ArcReadSignal<T> {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(T::default())),
|
||||
inner: Arc::new(RwLock::new(SubscriberSet::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,21 +107,6 @@ impl<T> Hash for ArcReadSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> ArcReadSignal<T> {
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all,)
|
||||
)]
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
value: Arc::new(RwLock::new(value)),
|
||||
inner: Arc::new(RwLock::new(SubscriberSet::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DefinedAt for ArcReadSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
|
|
|
@ -22,7 +22,7 @@ use std::{
|
|||
/// processes of reactive updates.
|
||||
///
|
||||
/// This is a reference-counted signal, which is `Clone` but not `Copy`.
|
||||
/// For arena-allocated `Copy` signals, use [`RwSignl`].
|
||||
/// For arena-allocated `Copy` signals, use [`RwSignal`](super::RwSignal).
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
///
|
||||
|
@ -56,7 +56,8 @@ use std::{
|
|||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`] instead.
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`](crate::owner::StoredValue)
|
||||
/// > instead.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::{
|
|||
/// and notifies other code when it has changed.
|
||||
///
|
||||
/// This is a reference-counted signal, which is `Clone` but not `Copy`.
|
||||
/// For arena-allocated `Copy` signals, use [`ReadSignal`].
|
||||
/// For arena-allocated `Copy` signals, use [`WriteSignal`](super::WriteSignal).
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
|
||||
|
@ -29,7 +29,8 @@ use std::{
|
|||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
/// > without notifying subscribers. Untracked updates are not desirable in most
|
||||
/// > cases, as they cause “tearing” between the signal’s value and its observed
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`] instead.
|
||||
/// > value. If you want a non-reactive container, used [`StoredValue`](crate::owner::StoredValue)
|
||||
/// > instead.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
|
|
|
@ -97,6 +97,7 @@ impl<T: 'static> Debug for Plain<T> {
|
|||
}
|
||||
|
||||
impl<T: 'static> Plain<T> {
|
||||
/// Takes a reference-counted read guard on the given lock.
|
||||
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
|
||||
ArcRwLockReadGuardian::take(inner)
|
||||
.ok()
|
||||
|
@ -142,6 +143,7 @@ impl<T: 'static> Debug for AsyncPlain<T> {
|
|||
}
|
||||
|
||||
impl<T: 'static> AsyncPlain<T> {
|
||||
/// Takes a reference-counted async read guard on the given lock.
|
||||
pub fn try_new(inner: &Arc<async_lock::RwLock<T>>) -> Option<Self> {
|
||||
Some(Self {
|
||||
guard: inner.blocking_read_arc(),
|
||||
|
@ -186,6 +188,7 @@ where
|
|||
}
|
||||
|
||||
impl<T: 'static, U> Mapped<Plain<T>, U> {
|
||||
/// Creates a mapped read guard from the inner lock.
|
||||
pub fn try_new(
|
||||
inner: Arc<RwLock<T>>,
|
||||
map_fn: fn(&T) -> &U,
|
||||
|
@ -199,6 +202,7 @@ impl<Inner, U> Mapped<Inner, U>
|
|||
where
|
||||
Inner: Deref,
|
||||
{
|
||||
/// Creates a mapped read guard from the inner guard.
|
||||
pub fn new_with_guard(
|
||||
inner: Inner,
|
||||
map_fn: fn(&Inner::Target) -> &U,
|
||||
|
@ -320,6 +324,7 @@ where
|
|||
pub struct UntrackedWriteGuard<T: 'static>(ArcRwLockWriteGuardian<T>);
|
||||
|
||||
impl<T: 'static> UntrackedWriteGuard<T> {
|
||||
/// Creates a write guard from the given lock.
|
||||
pub fn try_new(inner: Arc<RwLock<T>>) -> Option<Self> {
|
||||
ArcRwLockWriteGuardian::take(inner)
|
||||
.ok()
|
||||
|
@ -357,6 +362,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A mutable guard that maps over an inner mutable guard.
|
||||
#[derive(Debug)]
|
||||
pub struct MappedMut<Inner, U>
|
||||
where
|
||||
|
@ -380,6 +386,7 @@ impl<Inner, U> MappedMut<Inner, U>
|
|||
where
|
||||
Inner: DerefMut,
|
||||
{
|
||||
/// Creates a new writable guard from the inner guard.
|
||||
pub fn new(
|
||||
inner: Inner,
|
||||
map_fn: fn(&Inner::Target) -> &U,
|
||||
|
@ -431,6 +438,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A mapped read guard in which the mapping function is a closure. If the mapping function is a
|
||||
/// function pointed, use [`Mapped`].
|
||||
pub struct MappedArc<Inner, U>
|
||||
where
|
||||
Inner: Deref,
|
||||
|
@ -467,6 +476,7 @@ impl<Inner, U> MappedArc<Inner, U>
|
|||
where
|
||||
Inner: Deref,
|
||||
{
|
||||
/// Creates a new mapped guard from the inner guard and the map function.
|
||||
pub fn new(
|
||||
inner: Inner,
|
||||
map_fn: impl Fn(&Inner::Target) -> &U + 'static,
|
||||
|
@ -507,6 +517,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A mapped write guard in which the mapping function is a closure. If the mapping function is a
|
||||
/// function pointed, use [`MappedMut`].
|
||||
pub struct MappedMutArc<Inner, U>
|
||||
where
|
||||
Inner: Deref,
|
||||
|
@ -555,6 +567,7 @@ impl<Inner, U> MappedMutArc<Inner, U>
|
|||
where
|
||||
Inner: Deref,
|
||||
{
|
||||
/// Creates the new mapped mutable guard from the inner guard and mapping functions.
|
||||
pub fn new(
|
||||
inner: Inner,
|
||||
map_fn: impl Fn(&Inner::Target) -> &U + 'static,
|
||||
|
|
|
@ -22,8 +22,8 @@ use std::{
|
|||
/// and notifies other code when it has changed.
|
||||
///
|
||||
/// This is an arena-allocated signal, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`] cleans up. For a reference-counted signal that lives as long as a reference to it is
|
||||
/// alive, see [`ArcReadSignal`].
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that lives
|
||||
/// as long as a reference to it is alive, see [`ArcReadSignal`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
|
||||
|
|
|
@ -28,8 +28,8 @@ use std::{
|
|||
/// processes of reactive updates.
|
||||
///
|
||||
/// This is an arena-allocated signal, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`] cleans up. For a reference-counted signal that lives as long as a reference to it is
|
||||
/// alive, see [`ArcRwSignal`e.
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that lives
|
||||
/// as long as a reference to it is alive, see [`ArcRwSignal`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
///
|
||||
|
@ -57,7 +57,7 @@ use std::{
|
|||
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
|
||||
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
|
||||
/// applying a closure that takes a mutable reference.
|
||||
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
|
||||
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
|
||||
/// can be mutated, and which notifies subscribers when it is dropped.
|
||||
///
|
||||
/// > Each of these has a related `_untracked()` method, which updates the signal
|
||||
|
|
|
@ -15,8 +15,8 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
|||
/// and notifies other code when it has changed.
|
||||
///
|
||||
/// This is an arena-allocated signal, which is `Copy` and is disposed when its reactive
|
||||
/// [`Owner`] cleans up. For a reference-counted signal that lives as long as a reference to it is
|
||||
/// alive, see [`ArcWriteSignal`].
|
||||
/// [`Owner`](crate::owner::Owner) cleans up. For a reference-counted signal that lives
|
||||
/// as long as a reference to it is alive, see [`ArcWriteSignal`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
//! For example, if you have a struct for which you can implement [`ReadUntracked`] and [`Track`], then
|
||||
//! [`WithUntracked`] and [`With`] will be implemented automatically (as will [`GetUntracked`] and
|
||||
//! [`Get`] for `Clone` types). But if you cannot implement [`ReadUntracked`] (because, for example,
|
||||
//! there isn't an `RwLock` you can wrap in a [`SignalReadGuard`](crate::signal::SignalReadGuard),
|
||||
//! there isn't an `RwLock` so you can't wrap in a [`ReadGuard`](crate::signal::guards::ReadGuard),
|
||||
//! but you can still implement [`WithUntracked`] and [`Track`], the same traits will still be implemented.
|
||||
|
||||
use crate::{
|
||||
|
@ -200,6 +200,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A reactive, mutable guard that can be untracked to prevent it from notifying subscribers when
|
||||
/// it is dropped.
|
||||
pub trait UntrackableGuard: DerefMut {
|
||||
/// Removes the notifier from the guard, such that it will no longer notify subscribers when it is dropped.
|
||||
fn untrack(&mut self);
|
||||
|
|
|
@ -54,6 +54,13 @@ pub mod read {
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of reference-counted reactive signal:
|
||||
/// an [`ArcReadSignal`], [`ArcMemo`], [`ArcRwSignal`],
|
||||
/// or derived signal closure.
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `ArcSignal<T>` as an argument,
|
||||
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
|
||||
/// function call, `with()`, and `get()` APIs as other signals.
|
||||
pub struct ArcSignal<T: 'static> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
|
@ -92,6 +99,26 @@ pub mod read {
|
|||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::wrappers::read::ArcSignal;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = arc_signal(2);
|
||||
/// let double_count = ArcSignal::derive({
|
||||
/// let count = count.clone();
|
||||
/// move || count.get() * 2
|
||||
/// });
|
||||
///
|
||||
/// // this function takes any kind of wrapped signal
|
||||
/// fn above_3(arg: &ArcSignal<i32>) -> bool {
|
||||
/// arg.get() > 3
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(above_3(&count.into()), false);
|
||||
/// assert_eq!(above_3(&double_count), true);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn derive(
|
||||
derived_signal: impl Fn() -> T + Send + Sync + 'static,
|
||||
|
@ -204,6 +231,12 @@ pub mod read {
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of arena-allocated reactive signal:
|
||||
/// an [`ReadSignal`], [`Memo`], [`RwSignal`], or derived signal closure.
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `Signal<T>` as an argument,
|
||||
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
|
||||
/// function call, `with()`, and `get()` APIs as other signals.
|
||||
pub struct Signal<T: 'static> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
|
@ -305,6 +338,23 @@ pub mod read {
|
|||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::wrappers::read::Signal;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = signal(2);
|
||||
/// let double_count = Signal::derive(move || count.get() * 2);
|
||||
///
|
||||
/// // this function takes any kind of wrapped signal
|
||||
/// fn above_3(arg: &Signal<i32>) -> bool {
|
||||
/// arg.get() > 3
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(above_3(&count.into()), false);
|
||||
/// assert_eq!(above_3(&double_count), true);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn derive(
|
||||
derived_signal: impl Fn() -> T + Send + Sync + 'static,
|
||||
|
@ -408,6 +458,33 @@ pub mod read {
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper for a value that is *either* `T` or [`Signal<T>`].
|
||||
///
|
||||
/// This allows you to create APIs that take either a reactive or a non-reactive value
|
||||
/// of the same type. This is especially useful for component properties.
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::wrappers::read::MaybeSignal;
|
||||
/// # use reactive_graph::computed::Memo;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = signal(2);
|
||||
/// let double_count = MaybeSignal::derive(move || count.get() * 2);
|
||||
/// let memoized_double_count = Memo::new(move |_| count.get() * 2);
|
||||
/// let static_value = 5;
|
||||
///
|
||||
/// // this function takes either a reactive or non-reactive value
|
||||
/// fn above_3(arg: &MaybeSignal<i32>) -> bool {
|
||||
/// // ✅ calling the signal clones and returns the value
|
||||
/// // it is a shorthand for arg.get()
|
||||
/// arg.get() > 3
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(above_3(&static_value.into()), true);
|
||||
/// assert_eq!(above_3(&count.into()), false);
|
||||
/// assert_eq!(above_3(&double_count), true);
|
||||
/// assert_eq!(above_3(&memoized_double_count.into()), true);
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum MaybeSignal<T>
|
||||
where
|
||||
|
@ -476,6 +553,8 @@ pub mod read {
|
|||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
pub fn derive(
|
||||
derived_signal: impl Fn() -> T + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
|
@ -537,6 +616,37 @@ pub mod read {
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapping type for an optional component prop, which can either be a signal or a
|
||||
/// non-reactive value, and which may or may not have a value. In other words, this is
|
||||
/// an `Option<MaybeSignal<Option<T>>>` that automatically flattens its getters.
|
||||
///
|
||||
/// This creates an extremely flexible type for component libraries, etc.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```rust
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// # use reactive_graph::wrappers::read::MaybeProp;
|
||||
/// # use reactive_graph::computed::Memo;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = signal(Some(2));
|
||||
/// let double = |n| n * 2;
|
||||
/// let double_count = MaybeProp::derive(move || count.get().map(double));
|
||||
/// let memoized_double_count = Memo::new(move |_| count.get().map(double));
|
||||
/// let static_value = 5;
|
||||
///
|
||||
/// // this function takes either a reactive or non-reactive value
|
||||
/// fn above_3(arg: &MaybeProp<i32>) -> bool {
|
||||
/// // ✅ calling the signal clones and returns the value
|
||||
/// // it is a shorthand for arg.get()q
|
||||
/// arg.get().map(|arg| arg > 3).unwrap_or(false)
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(above_3(&None::<i32>.into()), false);
|
||||
/// assert_eq!(above_3(&static_value.into()), true);
|
||||
/// assert_eq!(above_3(&count.into()), false);
|
||||
/// assert_eq!(above_3(&double_count), true);
|
||||
/// assert_eq!(above_3(&memoized_double_count.into()), true);
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct MaybeProp<T: Send + Sync + 'static>(
|
||||
pub(crate) Option<MaybeSignal<Option<T>>>,
|
||||
|
@ -589,6 +699,8 @@ pub mod read {
|
|||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
pub fn derive(
|
||||
derived_signal: impl Fn() -> Option<T> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
|
@ -699,8 +811,8 @@ pub mod write {
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of settable reactive signal: a [`WriteSignal`](crate::WriteSignal),
|
||||
/// [`RwSignal`](crate::RwSignal), or closure that receives a value and sets a signal depending
|
||||
/// A wrapper for any kind of settable reactive signal: a [`WriteSignal`],
|
||||
/// [`RwSignal`], or closure that receives a value and sets a signal depending
|
||||
/// on it.
|
||||
///
|
||||
/// This allows you to create APIs that take any kind of `SignalSetter<T>` as an argument,
|
||||
|
@ -717,9 +829,6 @@ pub mod write {
|
|||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::wrappers::write::SignalSetter;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||
/// let (count, set_count) = signal(2);
|
||||
/// let set_double_input = SignalSetter::map(move |n| set_count.set(n * 2));
|
||||
///
|
||||
|
@ -734,7 +843,6 @@ pub mod write {
|
|||
/// assert_eq!(count.get(), 4);
|
||||
/// set_to_4(&set_double_input);
|
||||
/// assert_eq!(count.get(), 8);
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SignalSetter<T>
|
||||
|
@ -799,6 +907,7 @@ pub mod write {
|
|||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Wraps a signal-setting closure, i.e., any computation that sets one or more reactive signals.
|
||||
#[track_caller]
|
||||
pub fn map(mapped_setter: impl Fn(T) + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
|
@ -815,6 +924,27 @@ pub mod write {
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Calls the setter function with the given value.
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::wrappers::write::SignalSetter;
|
||||
/// # use reactive_graph::signal::signal;
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// let (count, set_count) = signal(2);
|
||||
/// let set_double_count = SignalSetter::map(move |n| set_count.set(n * 2));
|
||||
///
|
||||
/// // this function takes any kind of signal setter
|
||||
/// fn set_to_4(setter: &SignalSetter<i32>) {
|
||||
/// // ✅ calling the signal sets the value
|
||||
/// // can be `setter(4)` on nightly
|
||||
/// setter.set(4);
|
||||
/// }
|
||||
///
|
||||
/// set_to_4(&set_count.into());
|
||||
/// assert_eq!(count.get(), 4);
|
||||
/// set_to_4(&set_double_count);
|
||||
/// assert_eq!(count.get(), 8);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn set(&self, value: T) {
|
||||
match &self.inner {
|
||||
|
|
Loading…
Reference in a new issue