mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 11:04:40 -05:00
fix: remove SendOption from public API of actions (#3812)
This commit is contained in:
@@ -39,7 +39,6 @@ fn HomePage() -> impl IntoView {
|
|||||||
do_something_action
|
do_something_action
|
||||||
.value()
|
.value()
|
||||||
.get()
|
.get()
|
||||||
.take()
|
|
||||||
.unwrap_or_else(|| Ok(String::new()))
|
.unwrap_or_else(|| Ok(String::new()))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ pub fn FileUpload() -> impl IntoView {
|
|||||||
"Upload a file.".to_string()
|
"Upload a file.".to_string()
|
||||||
} else if upload_action.pending().get() {
|
} else if upload_action.pending().get() {
|
||||||
"Uploading...".to_string()
|
"Uploading...".to_string()
|
||||||
} else if let Some(Ok(value)) = *upload_action.value().get() {
|
} else if let Some(Ok(value)) = upload_action.value().get() {
|
||||||
value.to_string()
|
value.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{:?}", upload_action.value().get())
|
format!("{:?}", upload_action.value().get())
|
||||||
|
|||||||
@@ -125,13 +125,10 @@ where
|
|||||||
"Error converting form field into server function \
|
"Error converting form field into server function \
|
||||||
arguments: {err:?}"
|
arguments: {err:?}"
|
||||||
);
|
);
|
||||||
value.set(
|
value.set(Some(Err(ServerFnErrorErr::Serialization(
|
||||||
Some(Err(ServerFnErrorErr::Serialization(
|
err.to_string(),
|
||||||
err.to_string(),
|
)
|
||||||
)
|
.into_app_error())));
|
||||||
.into_app_error()))
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
version.update(|n| *n += 1);
|
version.update(|n| *n += 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
computed::{ArcMemo, Memo},
|
computed::{ArcMemo, Memo},
|
||||||
diagnostics::is_suppressing_resource_load,
|
diagnostics::is_suppressing_resource_load,
|
||||||
owner::{ArcStoredValue, ArenaItem, FromLocal, LocalStorage},
|
owner::{ArcStoredValue, ArenaItem},
|
||||||
send_wrapper_ext::SendOption,
|
send_wrapper_ext::SendOption,
|
||||||
signal::{ArcRwSignal, RwSignal},
|
signal::{ArcMappedSignal, ArcRwSignal, MappedSignal, RwSignal},
|
||||||
traits::{DefinedAt, Dispose, Get, GetUntracked, GetValue, Update, Write},
|
traits::{DefinedAt, Dispose, Get, GetUntracked, GetValue, Update, Write},
|
||||||
unwrap_signal,
|
unwrap_signal,
|
||||||
};
|
};
|
||||||
use any_spawner::Executor;
|
use any_spawner::Executor;
|
||||||
use futures::{channel::oneshot, select, FutureExt};
|
use futures::{channel::oneshot, select, FutureExt};
|
||||||
use send_wrapper::SendWrapper;
|
use send_wrapper::SendWrapper;
|
||||||
use std::{future::Future, panic::Location, pin::Pin, sync::Arc};
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
panic::Location,
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
/// An action runs some asynchronous code when you dispatch a new value to it, and gives you
|
/// An action runs some asynchronous code when you dispatch a new value to it, and gives you
|
||||||
/// reactive access to the result.
|
/// reactive access to the result.
|
||||||
@@ -48,25 +54,25 @@ use std::{future::Future, panic::Location, pin::Pin, sync::Arc};
|
|||||||
/// let version = save_data.version();
|
/// let version = save_data.version();
|
||||||
///
|
///
|
||||||
/// // before we do anything
|
/// // before we do anything
|
||||||
/// assert_eq!(*input.get(), None); // no argument yet
|
/// assert_eq!(input.get(), None); // no argument yet
|
||||||
/// assert_eq!(pending.get(), false); // isn't pending a response
|
/// assert_eq!(pending.get(), false); // isn't pending a response
|
||||||
/// assert_eq!(*result_of_call.get(), None); // there's no "last value"
|
/// assert_eq!(result_of_call.get(), None); // there's no "last value"
|
||||||
/// assert_eq!(version.get(), 0);
|
/// assert_eq!(version.get(), 0);
|
||||||
///
|
///
|
||||||
/// // dispatch the action
|
/// // dispatch the action
|
||||||
/// save_data.dispatch("My todo".to_string());
|
/// save_data.dispatch("My todo".to_string());
|
||||||
///
|
///
|
||||||
/// // when we're making the call
|
/// // when we're making the call
|
||||||
/// assert_eq!(*input.get(), Some("My todo".to_string()));
|
/// assert_eq!(input.get(), Some("My todo".to_string()));
|
||||||
/// assert_eq!(pending.get(), true); // is pending
|
/// assert_eq!(pending.get(), true); // is pending
|
||||||
/// assert_eq!(*result_of_call.get(), None); // has not yet gotten a response
|
/// assert_eq!(result_of_call.get(), None); // has not yet gotten a response
|
||||||
///
|
///
|
||||||
/// # any_spawner::Executor::tick().await;
|
/// # any_spawner::Executor::tick().await;
|
||||||
///
|
///
|
||||||
/// // after call has resolved
|
/// // after call has resolved
|
||||||
/// assert_eq!(*input.get(), None); // input clears out after resolved
|
/// assert_eq!(input.get(), None); // input clears out after resolved
|
||||||
/// assert_eq!(pending.get(), false); // no longer pending
|
/// assert_eq!(pending.get(), false); // no longer pending
|
||||||
/// assert_eq!(*result_of_call.get(), Some(42));
|
/// assert_eq!(result_of_call.get(), Some(42));
|
||||||
/// assert_eq!(version.get(), 1);
|
/// assert_eq!(version.get(), 1);
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
@@ -146,7 +152,7 @@ where
|
|||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*act.input().get(), Some(3));
|
/// assert_eq!(act.input().get(), Some(3));
|
||||||
///
|
///
|
||||||
/// // Remember that async functions already return a future if they are
|
/// // Remember that async functions already return a future if they are
|
||||||
/// // not `await`ed. You can save keystrokes by leaving out the `async move`
|
/// // not `await`ed. You can save keystrokes by leaving out the `async move`
|
||||||
@@ -156,7 +162,7 @@ where
|
|||||||
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
///
|
///
|
||||||
/// // after it resolves
|
/// // after it resolves
|
||||||
/// assert_eq!(*act2.value().get(), Some("I'M IN A DOCTEST".to_string()));
|
/// assert_eq!(act2.value().get(), Some("I'M IN A DOCTEST".to_string()));
|
||||||
///
|
///
|
||||||
/// async fn yell(n: String) -> String {
|
/// async fn yell(n: String) -> String {
|
||||||
/// n.to_uppercase()
|
/// n.to_uppercase()
|
||||||
@@ -380,7 +386,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, O> ArcAction<I, O> {
|
impl<I, O> ArcAction<I, O>
|
||||||
|
where
|
||||||
|
I: 'static,
|
||||||
|
O: 'static,
|
||||||
|
{
|
||||||
/// The number of times the action has successfully completed.
|
/// The number of times the action has successfully completed.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@@ -423,18 +433,22 @@ impl<I, O> ArcAction<I, O> {
|
|||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// let input = act.input();
|
/// let input = act.input();
|
||||||
/// assert_eq!(*input.get(), None);
|
/// assert_eq!(input.get(), None);
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*input.get(), Some(3));
|
/// assert_eq!(input.get(), Some(3));
|
||||||
///
|
///
|
||||||
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
/// // after it resolves
|
/// // after it resolves
|
||||||
/// assert_eq!(*input.get(), None);
|
/// assert_eq!(input.get(), None);
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn input(&self) -> ArcRwSignal<SendOption<I>> {
|
pub fn input(&self) -> ArcMappedSignal<Option<I>> {
|
||||||
self.input.clone()
|
ArcMappedSignal::new(
|
||||||
|
self.input.clone(),
|
||||||
|
|n| n.deref(),
|
||||||
|
|n| n.deref_mut(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The most recent return value of the `async` function. This will be `None` before
|
/// The most recent return value of the `async` function. This will be `None` before
|
||||||
@@ -453,21 +467,25 @@ impl<I, O> ArcAction<I, O> {
|
|||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// let value = act.value();
|
/// let value = act.value();
|
||||||
/// assert_eq!(*value.get(), None);
|
/// assert_eq!(value.get(), None);
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*value.get(), None);
|
/// assert_eq!(value.get(), None);
|
||||||
///
|
///
|
||||||
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
/// // after it resolves
|
/// // after it resolves
|
||||||
/// assert_eq!(*value.get(), Some(6));
|
/// assert_eq!(value.get(), Some(6));
|
||||||
/// // dispatch another value, and it still holds the old value
|
/// // dispatch another value, and it still holds the old value
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*value.get(), Some(6));
|
/// assert_eq!(value.get(), Some(6));
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn value(&self) -> ArcRwSignal<SendOption<O>> {
|
pub fn value(&self) -> ArcMappedSignal<Option<O>> {
|
||||||
self.value.clone()
|
ArcMappedSignal::new(
|
||||||
|
self.value.clone(),
|
||||||
|
|n| n.deref(),
|
||||||
|
|n| n.deref_mut(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the action has been dispatched and is currently waiting to resolve.
|
/// Whether the action has been dispatched and is currently waiting to resolve.
|
||||||
@@ -553,25 +571,25 @@ where
|
|||||||
/// let version = save_data.version();
|
/// let version = save_data.version();
|
||||||
///
|
///
|
||||||
/// // before we do anything
|
/// // before we do anything
|
||||||
/// assert_eq!(*input.get(), None); // no argument yet
|
/// assert_eq!(input.get(), None); // no argument yet
|
||||||
/// assert_eq!(pending.get(), false); // isn't pending a response
|
/// assert_eq!(pending.get(), false); // isn't pending a response
|
||||||
/// assert_eq!(*result_of_call.get(), None); // there's no "last value"
|
/// assert_eq!(result_of_call.get(), None); // there's no "last value"
|
||||||
/// assert_eq!(version.get(), 0);
|
/// assert_eq!(version.get(), 0);
|
||||||
///
|
///
|
||||||
/// // dispatch the action
|
/// // dispatch the action
|
||||||
/// save_data.dispatch("My todo".to_string());
|
/// save_data.dispatch("My todo".to_string());
|
||||||
///
|
///
|
||||||
/// // when we're making the call
|
/// // when we're making the call
|
||||||
/// assert_eq!(*input.get(), Some("My todo".to_string()));
|
/// assert_eq!(input.get(), Some("My todo".to_string()));
|
||||||
/// assert_eq!(pending.get(), true); // is pending
|
/// assert_eq!(pending.get(), true); // is pending
|
||||||
/// assert_eq!(*result_of_call.get(), None); // has not yet gotten a response
|
/// assert_eq!(result_of_call.get(), None); // has not yet gotten a response
|
||||||
///
|
///
|
||||||
/// # any_spawner::Executor::tick().await;
|
/// # any_spawner::Executor::tick().await;
|
||||||
///
|
///
|
||||||
/// // after call has resolved
|
/// // after call has resolved
|
||||||
/// assert_eq!(*input.get(), None); // input clears out after resolved
|
/// assert_eq!(input.get(), None); // input clears out after resolved
|
||||||
/// assert_eq!(pending.get(), false); // no longer pending
|
/// assert_eq!(pending.get(), false); // no longer pending
|
||||||
/// assert_eq!(*result_of_call.get(), Some(42));
|
/// assert_eq!(result_of_call.get(), Some(42));
|
||||||
/// assert_eq!(version.get(), 1);
|
/// assert_eq!(version.get(), 1);
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
@@ -635,7 +653,7 @@ where
|
|||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*act.input().get(), Some(3));
|
/// assert_eq!(act.input().get(), Some(3));
|
||||||
///
|
///
|
||||||
/// // Remember that async functions already return a future if they are
|
/// // Remember that async functions already return a future if they are
|
||||||
/// // not `await`ed. You can save keystrokes by leaving out the `async move`
|
/// // not `await`ed. You can save keystrokes by leaving out the `async move`
|
||||||
@@ -645,7 +663,7 @@ where
|
|||||||
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
///
|
///
|
||||||
/// // after it resolves
|
/// // after it resolves
|
||||||
/// assert_eq!(*act2.value().get(), Some("I'M IN A DOCTEST".to_string()));
|
/// assert_eq!(act2.value().get(), Some("I'M IN A DOCTEST".to_string()));
|
||||||
///
|
///
|
||||||
/// async fn yell(n: String) -> String {
|
/// async fn yell(n: String) -> String {
|
||||||
/// n.to_uppercase()
|
/// n.to_uppercase()
|
||||||
@@ -829,28 +847,27 @@ where
|
|||||||
/// # tokio_test::block_on(async move {
|
/// # tokio_test::block_on(async move {
|
||||||
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
|
||||||
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
|
||||||
/// let act = ArcAction::new(|n: &u8| {
|
/// let act = Action::new(|n: &u8| {
|
||||||
/// let n = n.to_owned();
|
/// let n = n.to_owned();
|
||||||
/// async move { n * 2 }
|
/// async move { n * 2 }
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// let input = act.input();
|
/// let input = act.input();
|
||||||
/// assert_eq!(*input.get(), None);
|
/// assert_eq!(input.get(), None);
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*input.get(), Some(3));
|
/// assert_eq!(input.get(), Some(3));
|
||||||
///
|
///
|
||||||
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
/// // after it resolves
|
/// // after it resolves
|
||||||
/// assert_eq!(*input.get(), None);
|
/// assert_eq!(input.get(), None);
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn input(&self) -> RwSignal<SendOption<I>> {
|
pub fn input(&self) -> MappedSignal<Option<I>> {
|
||||||
let inner = self
|
self.inner
|
||||||
.inner
|
|
||||||
.try_with_value(|inner| inner.input())
|
.try_with_value(|inner| inner.input())
|
||||||
.unwrap_or_else(unwrap_signal!(self));
|
.unwrap_or_else(unwrap_signal!(self))
|
||||||
inner.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current argument that was dispatched to the async function. This value will
|
/// The current argument that was dispatched to the async function. This value will
|
||||||
@@ -860,12 +877,11 @@ where
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[deprecated = "You can now use .input() for any value, whether it's \
|
#[deprecated = "You can now use .input() for any value, whether it's \
|
||||||
thread-safe or not."]
|
thread-safe or not."]
|
||||||
pub fn input_local(&self) -> RwSignal<SendOption<I>, LocalStorage> {
|
pub fn input_local(&self) -> MappedSignal<Option<I>> {
|
||||||
let inner = self
|
self.inner
|
||||||
.inner
|
|
||||||
.try_with_value(|inner| inner.input())
|
.try_with_value(|inner| inner.input())
|
||||||
.unwrap_or_else(unwrap_signal!(self));
|
.unwrap_or_else(unwrap_signal!(self))
|
||||||
RwSignal::from_local(inner)
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,25 +906,24 @@ where
|
|||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// let value = act.value();
|
/// let value = act.value();
|
||||||
/// assert_eq!(*value.get(), None);
|
/// assert_eq!(value.get(), None);
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*value.get(), None);
|
/// assert_eq!(value.get(), None);
|
||||||
///
|
///
|
||||||
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
/// // after it resolves
|
/// // after it resolves
|
||||||
/// assert_eq!(*value.get(), Some(6));
|
/// assert_eq!(value.get(), Some(6));
|
||||||
/// // dispatch another value, and it still holds the old value
|
/// // dispatch another value, and it still holds the old value
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*value.get(), Some(6));
|
/// assert_eq!(value.get(), Some(6));
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn value(&self) -> RwSignal<SendOption<O>> {
|
pub fn value(&self) -> MappedSignal<Option<O>> {
|
||||||
let inner = self
|
self.inner
|
||||||
.inner
|
|
||||||
.try_with_value(|inner| inner.value())
|
.try_with_value(|inner| inner.value())
|
||||||
.unwrap_or_else(unwrap_signal!(self));
|
.unwrap_or_else(unwrap_signal!(self))
|
||||||
inner.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The most recent return value of the `async` function. This will be `None` before
|
/// The most recent return value of the `async` function. This will be `None` before
|
||||||
@@ -919,15 +934,14 @@ where
|
|||||||
#[deprecated = "You can now use .value() for any value, whether it's \
|
#[deprecated = "You can now use .value() for any value, whether it's \
|
||||||
thread-safe or not."]
|
thread-safe or not."]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn value_local(&self) -> RwSignal<SendOption<O>, LocalStorage>
|
pub fn value_local(&self) -> MappedSignal<Option<O>>
|
||||||
where
|
where
|
||||||
O: Send + Sync,
|
O: Send + Sync,
|
||||||
{
|
{
|
||||||
let inner = self
|
self.inner
|
||||||
.inner
|
|
||||||
.try_with_value(|inner| inner.value())
|
.try_with_value(|inner| inner.value())
|
||||||
.unwrap_or_else(unwrap_signal!(self));
|
.unwrap_or_else(unwrap_signal!(self))
|
||||||
RwSignal::from_local(inner)
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,7 +1114,7 @@ impl<I, O> Copy for Action<I, O> {}
|
|||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// act.dispatch(3);
|
/// act.dispatch(3);
|
||||||
/// assert_eq!(*act.input().get(), Some(3));
|
/// assert_eq!(act.input().get(), Some(3));
|
||||||
///
|
///
|
||||||
/// // Remember that async functions already return a future if they are
|
/// // Remember that async functions already return a future if they are
|
||||||
/// // not `await`ed. You can save keystrokes by leaving out the `async move`
|
/// // not `await`ed. You can save keystrokes by leaving out the `async move`
|
||||||
@@ -1110,7 +1124,7 @@ impl<I, O> Copy for Action<I, O> {}
|
|||||||
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
/// # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
///
|
///
|
||||||
/// // after it resolves
|
/// // after it resolves
|
||||||
/// assert_eq!(*act2.value().get(), Some("I'M IN A DOCTEST".to_string()));
|
/// assert_eq!(act2.value().get(), Some("I'M IN A DOCTEST".to_string()));
|
||||||
///
|
///
|
||||||
/// async fn yell(n: String) -> String {
|
/// async fn yell(n: String) -> String {
|
||||||
/// n.to_uppercase()
|
/// n.to_uppercase()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ mod arc_rw;
|
|||||||
mod arc_trigger;
|
mod arc_trigger;
|
||||||
mod arc_write;
|
mod arc_write;
|
||||||
pub mod guards;
|
pub mod guards;
|
||||||
|
mod mapped;
|
||||||
mod read;
|
mod read;
|
||||||
mod rw;
|
mod rw;
|
||||||
mod subscriber_traits;
|
mod subscriber_traits;
|
||||||
@@ -17,6 +18,7 @@ pub use arc_read::*;
|
|||||||
pub use arc_rw::*;
|
pub use arc_rw::*;
|
||||||
pub use arc_trigger::*;
|
pub use arc_trigger::*;
|
||||||
pub use arc_write::*;
|
pub use arc_write::*;
|
||||||
|
pub use mapped::*;
|
||||||
pub use read::*;
|
pub use read::*;
|
||||||
pub use rw::*;
|
pub use rw::*;
|
||||||
pub use trigger::*;
|
pub use trigger::*;
|
||||||
|
|||||||
358
reactive_graph/src/signal/mapped.rs
Normal file
358
reactive_graph/src/signal/mapped.rs
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
use super::{
|
||||||
|
guards::{Mapped, MappedMutArc},
|
||||||
|
ArcRwSignal, RwSignal,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
owner::{StoredValue, SyncStorage},
|
||||||
|
signal::guards::WriteGuard,
|
||||||
|
traits::{
|
||||||
|
DefinedAt, GetValue, IsDisposed, Notify, ReadUntracked, Track,
|
||||||
|
UntrackableGuard, Write,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use guardian::ArcRwLockWriteGuardian;
|
||||||
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
panic::Location,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A derived signal type that wraps an [`ArcRwSignal`] with a mapping function,
|
||||||
|
/// allowing you to read or write directly to one of its field.
|
||||||
|
///
|
||||||
|
/// Tracking the mapped signal tracks changes to *any* part of the signal, and updating the signal notifies
|
||||||
|
/// and notifies *all* depenendencies of the signal. This is not a mechanism for fine-grained reactive updates
|
||||||
|
/// to more complex data structures. Instead, it allows you to provide a signal-like API for wrapped types
|
||||||
|
/// without exposing the original type directly to users.
|
||||||
|
pub struct ArcMappedSignal<T> {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
defined_at: &'static Location<'static>,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
try_read_untracked: Arc<
|
||||||
|
dyn Fn() -> Option<DoubleDeref<Box<dyn Deref<Target = T>>>>
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
>,
|
||||||
|
try_write: Arc<
|
||||||
|
dyn Fn() -> Option<Box<dyn UntrackableGuard<Target = T>>> + Send + Sync,
|
||||||
|
>,
|
||||||
|
notify: Arc<dyn Fn() + Send + Sync>,
|
||||||
|
track: Arc<dyn Fn() + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for ArcMappedSignal<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
defined_at: self.defined_at,
|
||||||
|
try_read_untracked: self.try_read_untracked.clone(),
|
||||||
|
try_write: self.try_write.clone(),
|
||||||
|
notify: self.notify.clone(),
|
||||||
|
track: self.track.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ArcMappedSignal<T> {
|
||||||
|
/// Wraps a signal with the given mapping functions for shared and exclusive references.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new<U>(
|
||||||
|
inner: ArcRwSignal<U>,
|
||||||
|
map: fn(&U) -> &T,
|
||||||
|
map_mut: fn(&mut U) -> &mut T,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
U: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
defined_at: Location::caller(),
|
||||||
|
try_read_untracked: {
|
||||||
|
let this = inner.clone();
|
||||||
|
Arc::new(move || {
|
||||||
|
this.try_read_untracked().map(|guard| DoubleDeref {
|
||||||
|
inner: Box::new(Mapped::new_with_guard(guard, map))
|
||||||
|
as Box<dyn Deref<Target = T>>,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
try_write: {
|
||||||
|
let this = inner.clone();
|
||||||
|
Arc::new(move || {
|
||||||
|
let guard = ArcRwLockWriteGuardian::try_take(Arc::clone(
|
||||||
|
&this.value,
|
||||||
|
))?
|
||||||
|
.ok()?;
|
||||||
|
let mapped = WriteGuard::new(
|
||||||
|
this.clone(),
|
||||||
|
MappedMutArc::new(guard, map, map_mut),
|
||||||
|
);
|
||||||
|
Some(Box::new(mapped))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
notify: {
|
||||||
|
let this = inner.clone();
|
||||||
|
Arc::new(move || {
|
||||||
|
this.notify();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
Arc::new(move || {
|
||||||
|
inner.track();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Debug for ArcMappedSignal<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut partial = f.debug_struct("ArcMappedSignal");
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
partial.field("defined_at", &self.defined_at);
|
||||||
|
partial.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DefinedAt for ArcMappedSignal<T> {
|
||||||
|
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
Some(self.defined_at)
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Notify for ArcMappedSignal<T> {
|
||||||
|
fn notify(&self) {
|
||||||
|
(self.notify)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Track for ArcMappedSignal<T> {
|
||||||
|
fn track(&self) {
|
||||||
|
(self.track)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ReadUntracked for ArcMappedSignal<T> {
|
||||||
|
type Value = DoubleDeref<Box<dyn Deref<Target = T>>>;
|
||||||
|
|
||||||
|
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||||
|
(self.try_read_untracked)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IsDisposed for ArcMappedSignal<T> {
|
||||||
|
fn is_disposed(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Write for ArcMappedSignal<T>
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
type Value = T;
|
||||||
|
|
||||||
|
fn try_write_untracked(
|
||||||
|
&self,
|
||||||
|
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||||
|
let mut guard = self.try_write()?;
|
||||||
|
guard.untrack();
|
||||||
|
Some(guard)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||||
|
let inner = (self.try_write)()?;
|
||||||
|
let inner = DoubleDeref { inner };
|
||||||
|
Some(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper for a smart pointer that implements [`Deref`] and [`DerefMut`]
|
||||||
|
/// by dereferencing the type *inside* the smart pointer.
|
||||||
|
///
|
||||||
|
/// This is quite obscure and mostly useful for situations in which we want
|
||||||
|
/// a wrapper for `Box<dyn Deref<Target = T>>` that dereferences to `T` rather
|
||||||
|
/// than dereferencing to `dyn Deref<Target = T>`.
|
||||||
|
///
|
||||||
|
/// This is used internally in [`MappedSignal`] and [`ArcMappedSignal`].
|
||||||
|
pub struct DoubleDeref<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for DoubleDeref<T>
|
||||||
|
where
|
||||||
|
T: Deref,
|
||||||
|
T::Target: Deref,
|
||||||
|
{
|
||||||
|
type Target = <T::Target as Deref>::Target;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.inner.deref().deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for DoubleDeref<T>
|
||||||
|
where
|
||||||
|
T: DerefMut,
|
||||||
|
T::Target: DerefMut,
|
||||||
|
{
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.inner.deref_mut().deref_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> UntrackableGuard for DoubleDeref<T>
|
||||||
|
where
|
||||||
|
T: UntrackableGuard,
|
||||||
|
T::Target: DerefMut,
|
||||||
|
{
|
||||||
|
fn untrack(&mut self) {
|
||||||
|
self.inner.untrack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A derived signal type that wraps an [`RwSignal`] with a mapping function,
|
||||||
|
/// allowing you to read or write directly to one of its field.
|
||||||
|
///
|
||||||
|
/// Tracking the mapped signal tracks changes to *any* part of the signal, and updating the signal notifies
|
||||||
|
/// and notifies *all* depenendencies of the signal. This is not a mechanism for fine-grained reactive updates
|
||||||
|
/// to more complex data structures. Instead, it allows you to provide a signal-like API for wrapped types
|
||||||
|
/// without exposing the original type directly to users.
|
||||||
|
pub struct MappedSignal<T, S = SyncStorage> {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
defined_at: &'static Location<'static>,
|
||||||
|
inner: StoredValue<ArcMappedSignal<T>, S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MappedSignal<T> {
|
||||||
|
/// Wraps a signal with the given mapping functions for shared and exclusive references.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new<U>(
|
||||||
|
inner: RwSignal<U>,
|
||||||
|
map: fn(&U) -> &T,
|
||||||
|
map_mut: fn(&mut U) -> &mut T,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
T: Send + Sync + 'static,
|
||||||
|
U: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
defined_at: Location::caller(),
|
||||||
|
inner: {
|
||||||
|
let this = ArcRwSignal::from(inner);
|
||||||
|
StoredValue::new_with_storage(ArcMappedSignal::new(
|
||||||
|
this, map, map_mut,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Debug for MappedSignal<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut partial = f.debug_struct("MappedSignal");
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
partial.field("defined_at", &self.defined_at);
|
||||||
|
partial.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DefinedAt for MappedSignal<T> {
|
||||||
|
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
Some(self.defined_at)
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Notify for MappedSignal<T>
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
fn notify(&self) {
|
||||||
|
if let Some(inner) = self.inner.try_get_value() {
|
||||||
|
inner.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Track for MappedSignal<T>
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
fn track(&self) {
|
||||||
|
if let Some(inner) = self.inner.try_get_value() {
|
||||||
|
inner.track();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ReadUntracked for MappedSignal<T>
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
type Value = DoubleDeref<Box<dyn Deref<Target = T>>>;
|
||||||
|
|
||||||
|
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||||
|
self.inner
|
||||||
|
.try_get_value()
|
||||||
|
.and_then(|inner| inner.try_read_untracked())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Write for MappedSignal<T>
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
type Value = T;
|
||||||
|
|
||||||
|
fn try_write_untracked(
|
||||||
|
&self,
|
||||||
|
) -> Option<impl DerefMut<Target = Self::Value>> {
|
||||||
|
let mut guard = self.try_write()?;
|
||||||
|
guard.untrack();
|
||||||
|
Some(guard)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
|
||||||
|
let inner = self.inner.try_get_value()?;
|
||||||
|
let inner = (inner.try_write)()?;
|
||||||
|
let inner = DoubleDeref { inner };
|
||||||
|
Some(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<ArcMappedSignal<T>> for MappedSignal<T>
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
#[track_caller]
|
||||||
|
fn from(value: ArcMappedSignal<T>) -> Self {
|
||||||
|
MappedSignal {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
defined_at: Location::caller(),
|
||||||
|
inner: StoredValue::new(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IsDisposed for MappedSignal<T> {
|
||||||
|
fn is_disposed(&self) -> bool {
|
||||||
|
self.inner.is_disposed()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -230,6 +230,12 @@ pub trait UntrackableGuard: DerefMut {
|
|||||||
fn untrack(&mut self);
|
fn untrack(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> UntrackableGuard for Box<dyn UntrackableGuard<Target = T>> {
|
||||||
|
fn untrack(&mut self) {
|
||||||
|
(**self).untrack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
|
/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
|
||||||
/// signal's subscribers will be notified.
|
/// signal's subscribers will be notified.
|
||||||
pub trait Write: Sized + DefinedAt + Notify {
|
pub trait Write: Sized + DefinedAt + Notify {
|
||||||
|
|||||||
Reference in New Issue
Block a user