docs: finish reactive graph docs for 0.7

This commit is contained in:
Greg Johnston 2024-07-12 15:58:15 -04:00
parent 8f0a8e05b4
commit dac4589194
25 changed files with 603 additions and 70 deletions

View file

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

View file

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

View file

@ -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 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::*;
@ -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 memos
/// value without cloning by applying a callback function.
/// - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
/// the memos 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>,

View file

@ -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(),

View file

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

View file

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

View file

@ -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 memos
/// value without cloning by applying a callback function.
/// - [`.with_untracked()`](crate::traits::WithUntracked) allows you to access
/// the memos 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",

View file

@ -12,6 +12,52 @@ use std::{
/// A conditional signal that only notifies subscribers when a change
/// in the source signals value changes whether the given function is true.
///
/// **You probably dont 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,

View file

@ -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 \

View file

@ -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 {

View file

@ -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,
}

View file

@ -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,

View file

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

View file

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

View file

@ -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. \

View file

@ -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::*;

View file

@ -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>> {

View file

@ -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 signals 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
///

View file

@ -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 signals 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
/// ```

View file

@ -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,

View file

@ -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.

View file

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

View file

@ -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.

View file

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

View file

@ -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 {