mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
docs: initial work on porting docs from 0.6 to 0.7
This commit is contained in:
parent
db8f5e4899
commit
6590749956
25 changed files with 791 additions and 207 deletions
|
@ -13,6 +13,7 @@ leptos = { path = "../../leptos", features = ["csr"] }
|
|||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
gloo-timers = { version = "0.3.0", features = ["futures"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
|
|
|
@ -10,7 +10,7 @@ pub fn SimpleCounter(
|
|||
/// The change that should be applied each time the button is clicked.
|
||||
step: i32,
|
||||
) -> impl IntoView {
|
||||
let (value, set_value) = signal(initial_value);
|
||||
/*let (value, set_value) = signal(initial_value);
|
||||
|
||||
view! {
|
||||
<div>
|
||||
|
@ -19,5 +19,24 @@ pub fn SimpleCounter(
|
|||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
}*/
|
||||
App()
|
||||
}
|
||||
|
||||
use gloo_timers::future::TimeoutFuture;
|
||||
use leptos::{html::Input, prelude::*};
|
||||
|
||||
#[component]
|
||||
fn Widget() -> impl IntoView {
|
||||
let input_ref = NodeRef::<Input>::new();
|
||||
|
||||
Effect::new(move |_| {
|
||||
let Some(_) = input_ref.get() else {
|
||||
log!("no ref");
|
||||
return;
|
||||
};
|
||||
log!("ref");
|
||||
});
|
||||
|
||||
view! { <input node_ref=input_ref type="text"/> }
|
||||
}
|
||||
|
|
|
@ -169,8 +169,8 @@ pub mod prelude {
|
|||
pub use leptos_server::*;
|
||||
pub use oco_ref::*;
|
||||
pub use reactive_graph::{
|
||||
actions::*, computed::*, effect::*, owner::*, selector::*,
|
||||
signal::*, wrappers::read::*, *,
|
||||
actions::*, computed::*, effect::*, owner::*, signal::*,
|
||||
wrappers::read::*, *,
|
||||
};
|
||||
pub use server_fn::{self, ServerFnError};
|
||||
pub use tachys::{
|
||||
|
|
|
@ -31,7 +31,6 @@ use tachys::{
|
|||
};
|
||||
use throw_error::ErrorHookFuture;
|
||||
|
||||
/// TODO docs!
|
||||
#[component]
|
||||
pub fn Suspense<Chil>(
|
||||
#[prop(optional, into)] fallback: ViewFnOnce,
|
||||
|
@ -40,6 +39,7 @@ pub fn Suspense<Chil>(
|
|||
where
|
||||
Chil: IntoView + Send + 'static,
|
||||
{
|
||||
leptos::logging::log!("Suspense body");
|
||||
let owner = Owner::new();
|
||||
owner.with(|| {
|
||||
let (starts_local, id) = {
|
||||
|
|
|
@ -56,6 +56,7 @@ where
|
|||
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
#[track_caller]
|
||||
pub fn new_with_options<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -235,6 +236,7 @@ where
|
|||
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -247,6 +249,7 @@ where
|
|||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -270,6 +273,7 @@ where
|
|||
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
#[track_caller]
|
||||
pub fn new_miniserde<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -282,6 +286,7 @@ where
|
|||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn new_miniserde_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -304,6 +309,7 @@ where
|
|||
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -316,6 +322,7 @@ where
|
|||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -340,6 +347,7 @@ where
|
|||
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
#[track_caller]
|
||||
pub fn new_rkyv<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -352,6 +360,7 @@ where
|
|||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn new_rkyv_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -630,6 +639,7 @@ where
|
|||
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
#[track_caller]
|
||||
pub fn new_with_options<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
|
@ -656,6 +666,7 @@ where
|
|||
type Output = T;
|
||||
type IntoFuture = AsyncDerivedFuture<T>;
|
||||
|
||||
#[track_caller]
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
self.data.into_future()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
//! Computed reactive values that derive from other reactive values.
|
||||
|
||||
mod arc_memo;
|
||||
mod async_derived;
|
||||
mod inner;
|
||||
mod memo;
|
||||
mod selector;
|
||||
pub use arc_memo::*;
|
||||
pub use async_derived::*;
|
||||
pub use memo::*;
|
||||
pub use selector::*;
|
||||
|
|
|
@ -4,8 +4,11 @@ use crate::{
|
|||
AnySource, AnySubscriber, ReactiveNode, Source, Subscriber,
|
||||
ToAnySource, ToAnySubscriber,
|
||||
},
|
||||
signal::guards::{Mapped, Plain, ReadGuard},
|
||||
traits::{DefinedAt, ReadUntracked},
|
||||
signal::{
|
||||
guards::{Mapped, Plain, ReadGuard},
|
||||
ArcReadSignal,
|
||||
},
|
||||
traits::{DefinedAt, Get, ReadUntracked},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use or_poisoned::OrPoisoned;
|
||||
|
@ -207,3 +210,13 @@ impl<T: 'static> ReadUntracked for ArcMemo<T> {
|
|||
.map(ReadGuard::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcReadSignal<T>> for ArcMemo<T>
|
||||
where
|
||||
T: Clone + PartialEq + Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
ArcMemo::new(move |_| value.get())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use super::{inner::MemoInner, ArcMemo};
|
||||
use crate::{
|
||||
owner::StoredValue,
|
||||
signal::guards::{Mapped, Plain, ReadGuard},
|
||||
traits::{DefinedAt, Dispose, ReadUntracked, Track},
|
||||
signal::{
|
||||
guards::{Mapped, Plain, ReadGuard},
|
||||
ArcReadSignal,
|
||||
},
|
||||
traits::{DefinedAt, Dispose, Get, ReadUntracked, Track},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::{fmt::Debug, hash::Hash, panic::Location};
|
||||
|
@ -11,7 +14,7 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
|
|||
///
|
||||
/// Unlike a "derived signal," a memo comes with two guarantees:
|
||||
/// 1. The memo will only run *once* per change, no matter how many times you
|
||||
/// access its value.
|
||||
/// access its value.
|
||||
/// 2. The memo will only notify its dependents if the value of the computation changes.
|
||||
///
|
||||
/// This makes a memo the perfect tool for expensive computations.
|
||||
|
@ -30,6 +33,7 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
|
|||
/// # use reactive_graph::signal::signal;
|
||||
/// # tokio_test::block_on(async move {
|
||||
/// # any_spawner::Executor::init_tokio();
|
||||
/// # tokio::task::LocalSet::new().run_until(async {
|
||||
/// # fn really_expensive_computation(value: i32) -> i32 { value };
|
||||
/// let (value, set_value) = signal(0);
|
||||
///
|
||||
|
@ -64,6 +68,7 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
|
|||
/// // do something else...
|
||||
/// });
|
||||
/// # });
|
||||
/// # });
|
||||
/// ```
|
||||
pub struct Memo<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -235,3 +240,13 @@ impl<T: Send + Sync + 'static> From<Memo<T>> for ArcMemo<T> {
|
|||
value.inner.get().unwrap_or_else(unwrap_signal!(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcReadSignal<T>> for Memo<T>
|
||||
where
|
||||
T: Clone + PartialEq + Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Memo::new(move |_| value.get())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Side effects that run in response to changes in the reactive values they read from.
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod effect;
|
||||
mod inner;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//! Types that define the reactive graph itself. These are mostly internal, but can be used to
|
||||
//! create custom reactive primitives.
|
||||
|
||||
mod node;
|
||||
mod sets;
|
||||
mod source;
|
||||
|
|
|
@ -69,9 +69,9 @@
|
|||
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
//#![deny(missing_docs)]
|
||||
|
||||
use futures::Stream;
|
||||
use std::{fmt::Arguments, future::Future, pin::Pin};
|
||||
use std::fmt::Arguments;
|
||||
|
||||
pub mod actions;
|
||||
pub(crate) mod channel;
|
||||
|
@ -80,7 +80,6 @@ pub mod diagnostics;
|
|||
pub mod effect;
|
||||
pub mod graph;
|
||||
pub mod owner;
|
||||
pub mod selector;
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde;
|
||||
pub mod signal;
|
||||
|
@ -93,10 +92,7 @@ pub use graph::untrack;
|
|||
#[cfg(feature = "nightly")]
|
||||
mod nightly;
|
||||
|
||||
pub type PinnedFuture<T> = Pin<Box<dyn Future<Output = T> + Send + Sync>>;
|
||||
pub type PinnedLocalFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
pub type PinnedStream<T> = Pin<Box<dyn Stream<Item = T> + Send + Sync>>;
|
||||
|
||||
/// Reexports frequently-used traits.
|
||||
pub mod prelude {
|
||||
pub use crate::traits::*;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! The reactive ownership model, which manages effect cancelation, cleanups, and arena allocation.
|
||||
|
||||
#[cfg(feature = "hydration")]
|
||||
use hydration_context::SharedContext;
|
||||
use or_poisoned::OrPoisoned;
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
use crate::{
|
||||
effect::RenderEffect,
|
||||
signal::ArcRwSignal,
|
||||
traits::{Track, Update},
|
||||
};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
hash::Hash,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A conditional signal that only notifies subscribers when a change
|
||||
/// in the source signal’s value changes whether the given function is true.
|
||||
#[derive(Clone)]
|
||||
pub struct Selector<T>
|
||||
where
|
||||
T: PartialEq + Eq + Clone + Hash + 'static,
|
||||
{
|
||||
subs: Arc<RwLock<FxHashMap<T, ArcRwSignal<bool>>>>,
|
||||
v: Arc<RwLock<Option<T>>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
f: Arc<dyn Fn(&T, &T) -> bool + Send + Sync>,
|
||||
// owning the effect keeps it alive, to keep updating the selector
|
||||
#[allow(dead_code)]
|
||||
effect: Arc<RenderEffect<T>>,
|
||||
}
|
||||
|
||||
impl<T> Selector<T>
|
||||
where
|
||||
T: PartialEq + Eq + Clone + Hash + 'static,
|
||||
{
|
||||
pub fn new(source: impl Fn() -> T + Clone + 'static) -> Self {
|
||||
Self::new_with_fn(source, PartialEq::eq)
|
||||
}
|
||||
|
||||
pub fn new_with_fn(
|
||||
source: impl Fn() -> T + Clone + 'static,
|
||||
f: impl Fn(&T, &T) -> bool + Send + Sync + Clone + 'static,
|
||||
) -> Self {
|
||||
let subs: Arc<RwLock<FxHashMap<T, ArcRwSignal<bool>>>> =
|
||||
Default::default();
|
||||
let v: Arc<RwLock<Option<T>>> = Default::default();
|
||||
let f = Arc::new(f) as Arc<dyn Fn(&T, &T) -> bool + Send + Sync>;
|
||||
|
||||
let effect = Arc::new(RenderEffect::new({
|
||||
let subs = Arc::clone(&subs);
|
||||
let f = Arc::clone(&f);
|
||||
let v = Arc::clone(&v);
|
||||
move |prev: Option<T>| {
|
||||
let next_value = source();
|
||||
*v.write().or_poisoned() = Some(next_value.clone());
|
||||
if prev.as_ref() != Some(&next_value) {
|
||||
for (key, signal) in &*subs.read().or_poisoned() {
|
||||
if f(key, &next_value)
|
||||
|| (prev.is_some()
|
||||
&& f(key, prev.as_ref().unwrap()))
|
||||
{
|
||||
signal.update(|n| *n = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
next_value
|
||||
}
|
||||
}));
|
||||
|
||||
Selector { subs, v, f, effect }
|
||||
}
|
||||
|
||||
/// Reactively checks whether the given key is selected.
|
||||
pub fn selected(&self, key: T) -> bool {
|
||||
let read = {
|
||||
let mut subs = self.subs.write().or_poisoned();
|
||||
subs.entry(key.clone())
|
||||
.or_insert_with(|| ArcRwSignal::new(false))
|
||||
.clone()
|
||||
};
|
||||
read.track();
|
||||
(self.f)(&key, self.v.read().or_poisoned().as_ref().unwrap())
|
||||
}
|
||||
|
||||
/// Removes the listener for the given key.
|
||||
pub fn remove(&self, key: &T) {
|
||||
let mut subs = self.subs.write().or_poisoned();
|
||||
subs.remove(key);
|
||||
}
|
||||
|
||||
/// Clears the listeners for all keys.
|
||||
pub fn clear(&self) {
|
||||
let mut subs = self.subs.write().or_poisoned();
|
||||
subs.clear();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
//! Reactive primitives for root values that can be changed, notifying other nodes in the reactive
|
||||
//! graph.
|
||||
|
||||
mod arc_read;
|
||||
mod arc_rw;
|
||||
mod arc_trigger;
|
||||
|
@ -16,18 +19,138 @@ pub use read::*;
|
|||
pub use rw::*;
|
||||
pub use write::*;
|
||||
|
||||
/// Creates a reference-counted signal.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time, and notifies other
|
||||
/// code when it has changed. This is the atomic unit of reactivity, which begins all other
|
||||
/// processes of updating.
|
||||
///
|
||||
/// Takes the initial value as an argument, and returns a tuple containing an
|
||||
/// [`ArcReadSignal`] and an [`ArcWriteSignal`].
|
||||
///
|
||||
/// This returns reference-counted signals, which are `Clone` but not `Copy`. For arena-allocated
|
||||
/// `Copy` signals, use [`signal`].
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// let (count, set_count) = arc_signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
/// // this can be `count()` on nightly
|
||||
/// assert_eq!(count.get(), 0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
/// // this can be `set_count(1)` on nightly
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
///
|
||||
/// // ✅ you can create "derived signals" with a Fn() -> T interface
|
||||
/// let double_count = move || count.get() * 2;
|
||||
/// set_count.set(0);
|
||||
/// assert_eq!(double_count(), 0);
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
||||
ArcRwSignal::new(value).split()
|
||||
}
|
||||
|
||||
/// Creates an arena-allocated signal, the basic reactive primitive.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time, and notifies other
|
||||
/// code when it has changed. This is the atomic unit of reactivity, which begins all other
|
||||
/// processes of updating.
|
||||
///
|
||||
/// Takes the initial value as an argument, and returns a tuple containing a
|
||||
/// [`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`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
/// // this can be `count()` on nightly
|
||||
/// assert_eq!(count.get(), 0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
/// // this can be `set_count(1)` on nightly
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
///
|
||||
/// // ✅ you can create "derived signals" with a Fn() -> T interface
|
||||
/// let double_count = move || count.get() * 2; // signals are `Copy` so you can `move` them anywhere
|
||||
/// set_count.set(0);
|
||||
/// assert_eq!(double_count(), 0);
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn signal<T: Send + Sync>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
RwSignal::new(value).split()
|
||||
}
|
||||
|
||||
/// Creates an arena-allocated signal, the basic reactive primitive.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time, and notifies other
|
||||
/// code when it has changed. This is the atomic unit of reactivity, which begins all other
|
||||
/// processes of updating.
|
||||
///
|
||||
/// Takes the initial value as an argument, and returns a tuple containing a
|
||||
/// [`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`].
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// let (count, set_count) = create_signal(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
/// // this can be `count()` on nightly
|
||||
/// assert_eq!(count.get(), 0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
/// // this can be `set_count(1)` on nightly
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
///
|
||||
/// // ✅ you can create "derived signals" with a Fn() -> T interface
|
||||
/// let double_count = move || count.get() * 2; // signals are `Copy` so you can `move` them anywhere
|
||||
/// set_count.set(0);
|
||||
/// assert_eq!(double_count(), 0);
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
#[deprecated = "This function is being renamed to `signal()` to conform to \
|
||||
|
|
|
@ -13,6 +13,46 @@ use std::{
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A reference-counted getter for a reactive signal.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time,
|
||||
/// 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`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
|
||||
/// If you call it within an effect, it will cause that effect to subscribe
|
||||
/// to the signal, and to re-run whenever the value of the signal changes.
|
||||
/// - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of
|
||||
/// the signal without reactively tracking it.
|
||||
/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
|
||||
/// value of the signal by reference. If you call it within an effect, it will
|
||||
/// cause that effect to subscribe to the signal, and to re-run whenever the
|
||||
/// value of the signal changes.
|
||||
/// - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
|
||||
/// current value of the signal without reactively tracking it.
|
||||
/// - [`.with()`](crate::traits::With) allows you to reactively access the signal’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 signal to an `async`
|
||||
/// stream of values.
|
||||
/// - [`::from_stream()`](crate::traits::FromStream) converts an `async` stream
|
||||
/// of values into a signal containing the latest value.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// let (count, set_count) = arc_signal(0);
|
||||
///
|
||||
/// // calling .get() clones and returns the value
|
||||
/// assert_eq!(count.get(), 0);
|
||||
/// // calling .read() accesses the value by reference
|
||||
/// assert_eq!(count.read(), 0);
|
||||
/// ```
|
||||
pub struct ArcReadSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
|
|
|
@ -11,11 +11,87 @@ use crate::{
|
|||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
hash::Hash,
|
||||
ops::DerefMut,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A reference-counted signal that can be read from or written to.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time, and notifies other
|
||||
/// code when it has changed. This is the atomic unit of reactivity, which begins all other
|
||||
/// processes of reactive updates.
|
||||
///
|
||||
/// This is a reference-counted signal, which is `Clone` but not `Copy`.
|
||||
/// For arena-allocated `Copy` signals, use [`RwSignl`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
///
|
||||
/// ### Reading the Value
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
|
||||
/// If you call it within an effect, it will cause that effect to subscribe
|
||||
/// to the signal, and to re-run whenever the value of the signal changes.
|
||||
/// - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of
|
||||
/// the signal without reactively tracking it.
|
||||
/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
|
||||
/// value of the signal by reference. If you call it within an effect, it will
|
||||
/// cause that effect to subscribe to the signal, and to re-run whenever the
|
||||
/// value of the signal changes.
|
||||
/// - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
|
||||
/// current value of the signal without reactively tracking it.
|
||||
/// - [`.with()`](crate::traits::With) allows you to reactively access the signal’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 signal to an `async`
|
||||
/// stream of values.
|
||||
///
|
||||
/// ### Updating the Value
|
||||
/// - [`.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::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
|
||||
/// > 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.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// let count = ArcRwSignal::new(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
/// // this can be `count()` on nightly
|
||||
/// assert_eq!(count.get(), 0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
/// // this can be `set_count(1)` on nightly
|
||||
/// count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
///
|
||||
/// // ✅ you can create "derived signals" with a Fn() -> T interface
|
||||
/// let double_count = {
|
||||
/// // clone before moving into the closure because we use it below
|
||||
/// let count = count.clone();
|
||||
/// move || count.get() * 2
|
||||
/// };
|
||||
/// count.set(0);
|
||||
/// assert_eq!(double_count(), 0);
|
||||
/// count.set(1);
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
pub struct ArcRwSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
|
@ -69,6 +145,7 @@ where
|
|||
}
|
||||
|
||||
impl<T> ArcRwSignal<T> {
|
||||
/// Creates a new signal, taking the initial value as its argument.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all,)
|
||||
|
@ -83,6 +160,7 @@ impl<T> ArcRwSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a read-only handle to the signal.
|
||||
#[track_caller]
|
||||
pub fn read_only(&self) -> ArcReadSignal<T> {
|
||||
ArcReadSignal {
|
||||
|
@ -93,6 +171,7 @@ impl<T> ArcRwSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a write-only handle to the signal.
|
||||
#[track_caller]
|
||||
pub fn write_only(&self) -> ArcWriteSignal<T> {
|
||||
ArcWriteSignal {
|
||||
|
@ -103,10 +182,30 @@ impl<T> ArcRwSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Splits the signal into its readable and writable halves.
|
||||
#[track_caller]
|
||||
pub fn split(&self) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
|
||||
(self.read_only(), self.write_only())
|
||||
}
|
||||
|
||||
/// Reunites the two halves of a signal. Returns `None` if the two signals
|
||||
/// provided were not created from the same signal.
|
||||
#[track_caller]
|
||||
pub fn unite(
|
||||
read: ArcReadSignal<T>,
|
||||
write: ArcWriteSignal<T>,
|
||||
) -> Option<Self> {
|
||||
if Arc::ptr_eq(&read.inner, &write.inner) {
|
||||
Some(Self {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: Location::caller(),
|
||||
value: read.value,
|
||||
inner: read.inner,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DefinedAt for ArcRwSignal<T> {
|
||||
|
@ -163,6 +262,7 @@ impl<T: 'static> Writeable for ArcRwSignal<T> {
|
|||
.map(|guard| WriteGuard::new(self.clone(), guard))
|
||||
}
|
||||
|
||||
#[allow(refining_impl_trait)]
|
||||
fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
|
||||
UntrackedWriteGuard::try_new(Arc::clone(&self.value))
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@ use std::{
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A trigger is a data-less signal with the sole purpose of notifying other reactive code of a change.
|
||||
///
|
||||
/// This can be useful for when using external data not stored in signals, for example.
|
||||
pub struct ArcTrigger {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
|
@ -16,6 +19,7 @@ pub struct ArcTrigger {
|
|||
}
|
||||
|
||||
impl ArcTrigger {
|
||||
/// Creates a new trigger.
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
|
|
@ -2,16 +2,56 @@ use super::guards::{UntrackedWriteGuard, WriteGuard};
|
|||
use crate::{
|
||||
graph::{ReactiveNode, SubscriberSet},
|
||||
prelude::{IsDisposed, Trigger},
|
||||
traits::{DefinedAt, Writeable},
|
||||
traits::{DefinedAt, UntrackableGuard, Writeable},
|
||||
};
|
||||
use core::fmt::{Debug, Formatter, Result};
|
||||
use std::{
|
||||
hash::Hash,
|
||||
ops::DerefMut,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A reference-counted setter for a reactive signal.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time,
|
||||
/// 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`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.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::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
|
||||
/// > 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.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// let (count, set_count) = arc_signal(0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
/// // `set_count(1)` on nightly
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
///
|
||||
/// // ✅ `.write()` returns a guard that implements `DerefMut` and will notify when dropped
|
||||
/// *set_count.write() += 1;
|
||||
/// assert_eq!(count.get(), 3);
|
||||
/// ```
|
||||
pub struct ArcWriteSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
|
@ -40,12 +80,6 @@ impl<T> Debug for ArcWriteSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for ArcWriteSignal<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for ArcWriteSignal<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.value, &other.value)
|
||||
|
@ -60,21 +94,6 @@ impl<T> Hash for ArcWriteSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> ArcWriteSignal<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 ArcWriteSignal<T> {
|
||||
#[inline(always)]
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
|
@ -105,15 +124,14 @@ impl<T> Trigger for ArcWriteSignal<T> {
|
|||
impl<T: 'static> Writeable for ArcWriteSignal<T> {
|
||||
type Value = T;
|
||||
|
||||
fn try_write(
|
||||
&self,
|
||||
) -> Option<WriteGuard<Self, impl DerefMut<Target = Self::Value>>> {
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||
self.value
|
||||
.write()
|
||||
.ok()
|
||||
.map(|guard| WriteGuard::new(self.clone(), guard))
|
||||
}
|
||||
|
||||
#[allow(refining_impl_trait)]
|
||||
fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
|
||||
UntrackedWriteGuard::try_new(Arc::clone(&self.value))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Guards that integrate with the reactive system, wrapping references to the values of signals.
|
||||
|
||||
use crate::{
|
||||
computed::BlockingLock,
|
||||
traits::{Trigger, UntrackableGuard},
|
||||
|
@ -12,6 +14,9 @@ use std::{
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// A wrapper type for any kind of guard returned by [`Read`](crate::traits::Read).
|
||||
///
|
||||
/// If `Inner` implements `Deref`, so does `ReadGuard<_, Inner>`.
|
||||
#[derive(Debug)]
|
||||
pub struct ReadGuard<T, Inner> {
|
||||
ty: PhantomData<T>,
|
||||
|
@ -19,6 +24,7 @@ pub struct ReadGuard<T, Inner> {
|
|||
}
|
||||
|
||||
impl<T, Inner> ReadGuard<T, Inner> {
|
||||
/// Creates a new wrapper around another guard type.
|
||||
pub fn new(inner: Inner) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
|
@ -79,6 +85,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A guard that provides access to a signal's inner value.
|
||||
pub struct Plain<T: 'static> {
|
||||
guard: ArcRwLockReadGuardian<T>,
|
||||
}
|
||||
|
@ -123,6 +130,7 @@ impl<T: Display> Display for Plain<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A guard that provides access to an async signal's value.
|
||||
pub struct AsyncPlain<T: 'static> {
|
||||
pub(crate) guard: async_lock::RwLockReadGuardArc<T>,
|
||||
}
|
||||
|
@ -167,6 +175,7 @@ impl<T: Display> Display for AsyncPlain<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A guard that maps over another guard.
|
||||
#[derive(Debug)]
|
||||
pub struct Mapped<Inner, U>
|
||||
where
|
||||
|
@ -236,6 +245,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A guard that provides mutable access to a signal's value, triggering some reactive change
|
||||
/// when it is dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct WriteGuard<S, G>
|
||||
where
|
||||
|
@ -249,6 +260,8 @@ impl<S, G> WriteGuard<S, G>
|
|||
where
|
||||
S: Trigger,
|
||||
{
|
||||
/// Creates a new guard from the inner mutable guard type, and the signal that should be
|
||||
/// triggered on drop.
|
||||
pub fn new(triggerable: S, guard: G) -> Self {
|
||||
Self {
|
||||
triggerable: Some(triggerable),
|
||||
|
@ -262,6 +275,7 @@ where
|
|||
S: Trigger,
|
||||
G: DerefMut,
|
||||
{
|
||||
/// Removes the triggerable type, so that it is no longer notifies when dropped.
|
||||
fn untrack(&mut self) {
|
||||
self.triggerable.take();
|
||||
}
|
||||
|
@ -301,6 +315,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A guard that provides mutable access to a signal's inner value, but does not notify of any
|
||||
/// changes.
|
||||
pub struct UntrackedWriteGuard<T: 'static>(ArcRwLockWriteGuardian<T>);
|
||||
|
||||
impl<T: 'static> UntrackedWriteGuard<T> {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use super::{
|
||||
arc_signal,
|
||||
guards::{Plain, ReadGuard},
|
||||
subscriber_traits::AsSubscriberSet,
|
||||
ArcReadSignal,
|
||||
|
@ -7,18 +6,57 @@ use super::{
|
|||
use crate::{
|
||||
graph::SubscriberSet,
|
||||
owner::StoredValue,
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadUntracked, Set},
|
||||
traits::{DefinedAt, Dispose, IsDisposed, ReadUntracked},
|
||||
unwrap_signal,
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use core::fmt::Debug;
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::{
|
||||
hash::Hash,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// An arena-allocated getter for a reactive signal.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time,
|
||||
/// 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`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
|
||||
/// If you call it within an effect, it will cause that effect to subscribe
|
||||
/// to the signal, and to re-run whenever the value of the signal changes.
|
||||
/// - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of
|
||||
/// the signal without reactively tracking it.
|
||||
/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
|
||||
/// value of the signal by reference. If you call it within an effect, it will
|
||||
/// cause that effect to subscribe to the signal, and to re-run whenever the
|
||||
/// value of the signal changes.
|
||||
/// - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
|
||||
/// current value of the signal without reactively tracking it.
|
||||
/// - [`.with()`](crate::traits::With) allows you to reactively access the signal’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 signal to an `async`
|
||||
/// stream of values.
|
||||
/// - [`::from_stream()`](crate::traits::FromStream) converts an `async` stream
|
||||
/// of values into a signal containing the latest value.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // calling .get() clones and returns the value
|
||||
/// assert_eq!(count.get(), 0);
|
||||
/// // calling .read() accesses the value by reference
|
||||
/// assert_eq!(count.read(), 0);
|
||||
/// ```
|
||||
pub struct ReadSignal<T: 'static> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
|
@ -118,63 +156,3 @@ impl<T: Send + Sync + 'static> From<ReadSignal<T>> for ArcReadSignal<T> {
|
|||
value.inner.get().unwrap_or_else(unwrap_signal!(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> ArcReadSignal<T> {
|
||||
pub fn from_stream(
|
||||
stream: impl Stream<Item = T> + Send + 'static,
|
||||
) -> ArcReadSignal<Option<T>> {
|
||||
let (read, write) = arc_signal(None);
|
||||
let mut stream = Box::pin(stream);
|
||||
Executor::spawn(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
}
|
||||
});
|
||||
read
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ArcReadSignal<T> {
|
||||
pub fn from_stream_unsync(
|
||||
stream: impl Stream<Item = T> + 'static,
|
||||
) -> ArcReadSignal<Option<T>> {
|
||||
let (read, write) = arc_signal(None);
|
||||
let mut stream = Box::pin(stream);
|
||||
Executor::spawn_local(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
}
|
||||
});
|
||||
read
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> ReadSignal<T> {
|
||||
pub fn from_stream(
|
||||
stream: impl Stream<Item = T> + Send + 'static,
|
||||
) -> ArcReadSignal<Option<T>> {
|
||||
let (read, write) = arc_signal(None);
|
||||
let mut stream = Box::pin(stream);
|
||||
Executor::spawn(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
}
|
||||
});
|
||||
read
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ReadSignal<T> {
|
||||
pub fn from_stream_unsync(
|
||||
stream: impl Stream<Item = T> + 'static,
|
||||
) -> ArcReadSignal<Option<T>> {
|
||||
let (read, write) = arc_signal(None);
|
||||
let mut stream = Box::pin(stream);
|
||||
Executor::spawn_local(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
}
|
||||
});
|
||||
read
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,88 @@ use core::fmt::Debug;
|
|||
use guardian::ArcRwLockWriteGuardian;
|
||||
use std::{
|
||||
hash::Hash,
|
||||
ops::DerefMut,
|
||||
panic::Location,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
/// An arena-allocated signal that can be read from or written to.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time, and notifies other
|
||||
/// code when it has changed. This is the atomic unit of reactivity, which begins all other
|
||||
/// 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.
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
///
|
||||
/// ### Reading the Value
|
||||
/// - [`.get()`](crate::traits::Get) clones the current value of the signal.
|
||||
/// If you call it within an effect, it will cause that effect to subscribe
|
||||
/// to the signal, and to re-run whenever the value of the signal changes.
|
||||
/// - [`.get_untracked()`](crate::traits::GetUntracked) clones the value of
|
||||
/// the signal without reactively tracking it.
|
||||
/// - [`.read()`](crate::traits::Read) returns a guard that allows accessing the
|
||||
/// value of the signal by reference. If you call it within an effect, it will
|
||||
/// cause that effect to subscribe to the signal, and to re-run whenever the
|
||||
/// value of the signal changes.
|
||||
/// - [`.read_untracked()`](crate::traits::ReadUntracked) gives access to the
|
||||
/// current value of the signal without reactively tracking it.
|
||||
/// - [`.with()`](crate::traits::With) allows you to reactively access the signal’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 signal to an `async`
|
||||
/// stream of values.
|
||||
///
|
||||
/// ### Updating the Value
|
||||
/// - [`.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
|
||||
/// can be mutated, and which notifies subscribers when it is dropped.
|
||||
///
|
||||
/// > 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.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*;
|
||||
/// # use reactive_graph::signal::*;
|
||||
/// let count = ArcRwSignal::new(0);
|
||||
///
|
||||
/// // ✅ calling the getter clones and returns the value
|
||||
/// // this can be `count()` on nightly
|
||||
/// assert_eq!(count.get(), 0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
/// // this can be `set_count(1)` on nightly
|
||||
/// count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
///
|
||||
/// // ✅ you can create "derived signals" with a Fn() -> T interface
|
||||
/// let double_count = {
|
||||
/// // clone before moving into the closure because we use it below
|
||||
/// let count = count.clone();
|
||||
/// move || count.get() * 2
|
||||
/// };
|
||||
/// count.set(0);
|
||||
/// assert_eq!(double_count(), 0);
|
||||
/// count.set(1);
|
||||
/// assert_eq!(double_count(), 2);
|
||||
/// ```
|
||||
pub struct RwSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
|
@ -35,6 +112,11 @@ impl<T: Send + Sync + 'static> Dispose for RwSignal<T> {
|
|||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> RwSignal<T> {
|
||||
/// Creates a new signal, taking the initial value as its argument.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all,)
|
||||
)]
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
|
@ -44,6 +126,7 @@ impl<T: Send + Sync + 'static> RwSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a read-only handle to the signal.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn read_only(&self) -> ReadSignal<T> {
|
||||
|
@ -59,6 +142,7 @@ impl<T: Send + Sync + 'static> RwSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a write-only handle to the signal.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
pub fn write_only(&self) -> WriteSignal<T> {
|
||||
|
@ -74,12 +158,15 @@ impl<T: Send + Sync + 'static> RwSignal<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Splits the signal into its readable and writable halves.
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn split(&self) -> (ReadSignal<T>, WriteSignal<T>) {
|
||||
(self.read_only(), self.write_only())
|
||||
}
|
||||
|
||||
/// Reunites the two halves of a signal. Returns `None` if the two signals
|
||||
/// provided were not created from the same signal.
|
||||
#[track_caller]
|
||||
pub fn unite(read: ReadSignal<T>, write: WriteSignal<T>) -> Option<Self> {
|
||||
match (read.inner.get(), write.inner.get()) {
|
||||
|
@ -195,6 +282,7 @@ impl<T: 'static> Writeable for RwSignal<T> {
|
|||
Some(WriteGuard::new(*self, guard))
|
||||
}
|
||||
|
||||
#[allow(refining_impl_trait)]
|
||||
fn try_write_untracked(&self) -> Option<UntrackedWriteGuard<Self::Value>> {
|
||||
self.inner.with_value(|n| n.try_write_untracked())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use super::{
|
||||
guards::{UntrackedWriteGuard, WriteGuard},
|
||||
ArcWriteSignal,
|
||||
};
|
||||
use super::{guards::WriteGuard, ArcWriteSignal};
|
||||
use crate::{
|
||||
owner::StoredValue,
|
||||
traits::{
|
||||
|
@ -12,6 +9,48 @@ use core::fmt::Debug;
|
|||
use guardian::ArcRwLockWriteGuardian;
|
||||
use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
|
||||
|
||||
/// An arena-allocated setter for a reactive signal.
|
||||
///
|
||||
/// A signal is a piece of data that may change over time,
|
||||
/// 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`].
|
||||
///
|
||||
/// ## Core Trait Implementations
|
||||
/// - [`.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::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
|
||||
/// > 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.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
|
||||
/// let (count, set_count) = signal(0);
|
||||
///
|
||||
/// // ✅ calling the setter sets the value
|
||||
/// // `set_count(1)` on nightly
|
||||
/// set_count.set(1);
|
||||
/// assert_eq!(count.get(), 1);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count.get(), 2);
|
||||
///
|
||||
/// // ✅ `.write()` returns a guard that implements `DerefMut` and will notify when dropped
|
||||
/// *set_count.write() += 1;
|
||||
/// assert_eq!(count.get(), 3);
|
||||
/// ```
|
||||
pub struct WriteSignal<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) defined_at: &'static Location<'static>,
|
||||
|
|
|
@ -49,14 +49,19 @@
|
|||
//! but you can still implement [`WithUntracked`] and [`Track`], the same traits will still be implemented.
|
||||
|
||||
use crate::{
|
||||
effect::Effect,
|
||||
graph::{Observer, Source, Subscriber, ToAnySource},
|
||||
signal::guards::{UntrackedWriteGuard, WriteGuard},
|
||||
owner::Owner,
|
||||
signal::{arc_signal, ArcReadSignal},
|
||||
};
|
||||
use any_spawner::Executor;
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
panic::Location,
|
||||
};
|
||||
|
||||
/// Provides a sensible panic message for accessing disposed signals.
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_signal {
|
||||
($signal:ident) => {{
|
||||
|
@ -84,11 +89,18 @@ macro_rules! unwrap_signal {
|
|||
}};
|
||||
}
|
||||
|
||||
/// Allows disposing an arena-allocated signal before its owner has been disposed.
|
||||
pub trait Dispose {
|
||||
/// Disposes of the signal. This:
|
||||
/// 1. Detaches the signal from the reactive graph, preventing it from triggering
|
||||
/// further updates; and
|
||||
/// 2. Drops the value contained in the signal.
|
||||
fn dispose(self);
|
||||
}
|
||||
|
||||
/// Allows tracking the value of some reactive data.
|
||||
pub trait Track {
|
||||
/// Subscribes to this signal in the current reactive scope without doing anything with its value.
|
||||
fn track(&self);
|
||||
}
|
||||
|
||||
|
@ -132,12 +144,20 @@ impl<T: Source + ToAnySource + DefinedAt> Track for T {
|
|||
}
|
||||
}
|
||||
|
||||
/// Give read-only access to a signal's value by reference through a guard type,
|
||||
/// without tracking the value reactively.
|
||||
pub trait ReadUntracked: Sized + DefinedAt {
|
||||
/// The guard type that will be returned, which can be dereferenced to the value.
|
||||
type Value: Deref;
|
||||
|
||||
/// Returns the guard, or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_read_untracked(&self) -> Option<Self::Value>;
|
||||
|
||||
/// Returns the guard.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn read_untracked(&self) -> Self::Value {
|
||||
self.try_read_untracked()
|
||||
|
@ -145,12 +165,20 @@ pub trait ReadUntracked: Sized + DefinedAt {
|
|||
}
|
||||
}
|
||||
|
||||
/// Give read-only access to a signal's value by reference through a guard type,
|
||||
/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
|
||||
pub trait Read {
|
||||
/// The guard type that will be returned, which can be dereferenced to the value.
|
||||
type Value: Deref;
|
||||
|
||||
/// Subscribes to the signal, and returns the guard, or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_read(&self) -> Option<Self::Value>;
|
||||
|
||||
/// Subscribes to the signal, and returns the guard.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn read(&self) -> Self::Value;
|
||||
}
|
||||
|
@ -173,37 +201,61 @@ where
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
|
||||
/// signal's subscribers will be notified.
|
||||
pub trait Writeable: Sized + DefinedAt + Trigger {
|
||||
/// The type of the signal's value.
|
||||
type Value: Sized + 'static;
|
||||
|
||||
/// Returns the guard, or `None` if the signal has already been disposed.
|
||||
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>>;
|
||||
|
||||
// Returns a guard that will not notify subscribers when dropped,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
fn try_write_untracked(
|
||||
&self,
|
||||
) -> Option<impl DerefMut<Target = Self::Value>>;
|
||||
|
||||
/// Returns the guard.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
fn write(&self) -> impl UntrackableGuard<Target = Self::Value> {
|
||||
self.try_write().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Returns a guard that will not notify subscribers when dropped.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
fn write_untracked(&self) -> impl DerefMut<Target = Self::Value> {
|
||||
self.try_write_untracked()
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Give read-only access to a signal's value by reference inside a closure,
|
||||
/// without tracking the value reactively.
|
||||
pub trait WithUntracked: DefinedAt {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value: ?Sized;
|
||||
|
||||
/// Applies the closure to the value, and returns the result,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_with_untracked<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&Self::Value) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Applies the closure to the value, and returns the result.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn with_untracked<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
|
||||
self.try_with_untracked(fun)
|
||||
|
@ -225,11 +277,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Give read-only access to a signal's value by reference inside a closure,
|
||||
/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
|
||||
pub trait With: DefinedAt {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value: ?Sized;
|
||||
|
||||
/// Subscribes to the signal, applies the closure to the value, and returns the result,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> Option<U>;
|
||||
|
||||
/// Subscribes to the signal, applies the closure to the value, and returns the result.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
|
||||
self.try_with(fun).unwrap_or_else(unwrap_signal!(self))
|
||||
|
@ -249,11 +311,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Clones the value of the signal, without tracking the value reactively.
|
||||
pub trait GetUntracked: DefinedAt {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value;
|
||||
|
||||
/// Clones and returns the value of the signal,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_get_untracked(&self) -> Option<Self::Value>;
|
||||
|
||||
/// Clones and returns the value of the signal,
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn get_untracked(&self) -> Self::Value {
|
||||
self.try_get_untracked()
|
||||
|
@ -273,11 +344,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Clones the value of the signal, without tracking the value reactively.
|
||||
/// and subscribes the active reactive observer (an effect or computed) to changes in its value.
|
||||
pub trait Get: DefinedAt {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value: Clone;
|
||||
|
||||
/// Subscribes to the signal, then clones and returns the value of the signal,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_get(&self) -> Option<Self::Value>;
|
||||
|
||||
/// Subscribes to the signal, then clones and returns the value of the signal.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn get(&self) -> Self::Value {
|
||||
self.try_get().unwrap_or_else(unwrap_signal!(self))
|
||||
|
@ -297,13 +378,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
pub trait Trigger {
|
||||
/// Notifies subscribers of a change in this signal.
|
||||
fn trigger(&self);
|
||||
}
|
||||
|
||||
/// Updates the value of a signal by applying a function that updates it in place,
|
||||
/// without notifying subscribers.
|
||||
pub trait UpdateUntracked: DefinedAt {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value;
|
||||
|
||||
/// Updates the value by applying a function, returning the value returned by that function.
|
||||
/// Does not notify subscribers that the signal has changed.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to update a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn update_untracked<U>(
|
||||
&self,
|
||||
|
@ -313,6 +404,9 @@ pub trait UpdateUntracked: DefinedAt {
|
|||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
|
||||
/// Updates the value by applying a function, returning the value returned by that function,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
/// Does not notify subscribers that the signal has changed.
|
||||
fn try_update_untracked<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&mut Self::Value) -> U,
|
||||
|
@ -335,14 +429,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Updates the value of a signal by applying a function that updates it in place,
|
||||
/// notifying its subscribers that the value has changed.
|
||||
pub trait Update {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value;
|
||||
|
||||
/// Updates the value of the signal and notifies subscribers.
|
||||
#[track_caller]
|
||||
fn update(&self, fun: impl FnOnce(&mut Self::Value)) {
|
||||
self.try_update(fun);
|
||||
}
|
||||
|
||||
/// Updates the value of the signal, but only notifies subscribers if the function
|
||||
/// returns `true`.
|
||||
#[track_caller]
|
||||
fn maybe_update(&self, fun: impl FnOnce(&mut Self::Value) -> bool) {
|
||||
self.try_maybe_update(|val| {
|
||||
|
@ -351,6 +451,8 @@ pub trait Update {
|
|||
});
|
||||
}
|
||||
|
||||
/// Updates the value of the signal and notifies subscribers, returning the value that is
|
||||
/// returned by the update function, or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_update<U>(
|
||||
&self,
|
||||
|
@ -359,6 +461,9 @@ pub trait Update {
|
|||
self.try_maybe_update(|val| (true, fun(val)))
|
||||
}
|
||||
|
||||
/// Updates the value of the signal, notifying subscribers if the update function returns
|
||||
/// `(true, _)`, and returns the value returned by the update function,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
fn try_maybe_update<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(&mut Self::Value) -> (bool, U),
|
||||
|
@ -386,11 +491,18 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Updates the value of the signal by replacing it.
|
||||
pub trait Set {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value;
|
||||
|
||||
/// Updates the value by replacing it, and notifies subscribers that it has changed.
|
||||
fn set(&self, value: Self::Value);
|
||||
|
||||
/// Updates the value by replacing it, and notifies subscribers that it has changed.
|
||||
///
|
||||
/// If the signal has already been disposed, returns `Some(value)` with the value that was
|
||||
/// passed in. Otherwise, returns `None`.
|
||||
fn try_set(&self, value: Self::Value) -> Option<Self::Value>;
|
||||
}
|
||||
|
||||
|
@ -416,11 +528,90 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Allows converting a signal into an async [`Stream`].
|
||||
pub trait ToStream<T> {
|
||||
/// Generates a [`Stream`] that emits the new value of the signal
|
||||
/// whenever it changes.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that is owned by a reactive node that has been disposed.
|
||||
#[track_caller]
|
||||
fn to_stream(&self) -> impl Stream<Item = T> + Send;
|
||||
}
|
||||
|
||||
impl<S> ToStream<S::Value> for S
|
||||
where
|
||||
S: Clone + Get + Send + Sync + 'static,
|
||||
S::Value: Send + 'static,
|
||||
{
|
||||
fn to_stream(&self) -> impl Stream<Item = S::Value> + Send {
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
let close_channel = tx.clone();
|
||||
|
||||
Owner::on_cleanup(move || close_channel.close_channel());
|
||||
|
||||
Effect::new_isomorphic({
|
||||
let this = self.clone();
|
||||
move |_| {
|
||||
let _ = tx.unbounded_send(this.get());
|
||||
}
|
||||
});
|
||||
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows creating a signal from an async [`Stream`].
|
||||
pub trait FromStream<T> {
|
||||
/// Creates a signal that contains the latest value of the stream.
|
||||
#[track_caller]
|
||||
fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self;
|
||||
|
||||
/// Creates a signal that contains the latest value of the stream.
|
||||
#[track_caller]
|
||||
fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self;
|
||||
}
|
||||
|
||||
impl<S, T> FromStream<T> for S
|
||||
where
|
||||
S: From<ArcReadSignal<Option<T>>> + Send + Sync,
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self {
|
||||
let (read, write) = arc_signal(None);
|
||||
let mut stream = Box::pin(stream);
|
||||
Executor::spawn(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
}
|
||||
});
|
||||
read.into()
|
||||
}
|
||||
|
||||
fn from_stream_unsync(stream: impl Stream<Item = T> + 'static) -> Self {
|
||||
let (read, write) = arc_signal(None);
|
||||
let mut stream = Box::pin(stream);
|
||||
Executor::spawn_local(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
}
|
||||
});
|
||||
read.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether a signal has already been disposed.
|
||||
pub trait IsDisposed {
|
||||
/// If `true`, the signal cannot be accessed without a panic.
|
||||
fn is_disposed(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Describes where the signal was defined. This is used for diagnostic warnings and is purely a
|
||||
/// debug-mode tool.
|
||||
pub trait DefinedAt {
|
||||
/// Returns the location at which the signal was defined. This is usually simply `None` in
|
||||
/// release mode.
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Utilities to wait for asynchronous primitives to resolve.
|
||||
|
||||
use futures::{channel::oneshot, future::join_all};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use std::{
|
||||
|
@ -16,10 +18,18 @@ struct TransitionInner {
|
|||
tx: mpsc::Sender<oneshot::Receiver<()>>,
|
||||
}
|
||||
|
||||
/// Transitions allow you to wait for all asynchronous resources created during them to resolve.
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncTransition;
|
||||
|
||||
impl AsyncTransition {
|
||||
/// Calls the `action` function, and returns a `Future` that resolves when any
|
||||
/// [`AsyncDerived`](crate::computed::AsyncDerived) or
|
||||
/// or [`ArcAsyncDerived`](crate::computed::ArcAsyncDerived) that is read during the action
|
||||
/// has resolved.
|
||||
///
|
||||
/// This allows for an inversion of control: the caller does not need to know when all the
|
||||
/// resources created inside the `action` will resolve, but can wait for them to notify it.
|
||||
pub async fn run<T, U>(action: impl FnOnce() -> T) -> U
|
||||
where
|
||||
T: Future<Output = U>,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//! Types to abstract over different kinds of readable or writable reactive values.
|
||||
|
||||
/// Types that abstract over signals with values that can be read.
|
||||
pub mod read {
|
||||
use crate::{
|
||||
computed::{ArcMemo, Memo},
|
||||
|
@ -672,6 +675,7 @@ pub mod read {
|
|||
}
|
||||
}
|
||||
|
||||
/// Types that abstract over the ability to update a signal.
|
||||
pub mod write {
|
||||
use crate::{
|
||||
owner::StoredValue,
|
||||
|
|
Loading…
Reference in a new issue