pub use crate::trait_options::*;
use crate::{
effect::Effect,
graph::{Observer, Source, Subscriber, ToAnySource},
owner::Owner,
signal::{arc_signal, guards::UntrackedWriteGuard, ArcReadSignal},
};
use any_spawner::Executor;
use futures::{Stream, StreamExt};
use std::{
ops::{Deref, DerefMut},
panic::Location,
};
#[doc(hidden)]
#[macro_export]
macro_rules! unwrap_signal {
($signal:ident) => {{
#[cfg(any(debug_assertions, leptos_debuginfo))]
let location = std::panic::Location::caller();
|| {
#[cfg(any(debug_assertions, leptos_debuginfo))]
{
panic!(
"{}",
$crate::traits::panic_getting_disposed_signal(
$signal.defined_at(),
location
)
);
}
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
{
panic!(
"Tried to access a reactive value that has already been \
disposed."
);
}
}
}};
}
pub trait Dispose {
fn dispose(self);
}
pub trait Track {
#[track_caller]
fn track(&self);
}
impl<T: Source + ToAnySource + DefinedAt> Track for T {
#[track_caller]
fn track(&self) {
if self.is_disposed() {
return;
}
if let Some(subscriber) = Observer::get() {
subscriber.add_source(self.to_any_source());
self.add_subscriber(subscriber);
} else {
#[cfg(all(debug_assertions, feature = "effects"))]
{
use crate::diagnostics::SpecialNonReactiveZone;
if !SpecialNonReactiveZone::is_inside() {
let called_at = Location::caller();
let ty = std::any::type_name::<T>();
let defined_at = self
.defined_at()
.map(ToString::to_string)
.unwrap_or_else(|| String::from("{unknown}"));
crate::log_warning(format_args!(
"At {called_at}, you access a {ty} (defined at \
{defined_at}) outside a reactive tracking context. \
This might mean your app is not responding to \
changes in signal values in the way you \
expect.\n\nHere’s how to fix it:\n\n1. If this is \
inside a `view!` macro, make sure you are passing a \
function, not a value.\n ❌ NO <p>{{x.get() * \
2}}</p>\n ✅ YES <p>{{move || x.get() * \
2}}</p>\n\n2. If it’s in the body of a component, \
try wrapping this access in a closure: \n ❌ NO \
let y = x.get() * 2\n ✅ YES let y = move || \
x.get() * 2.\n\n3. If you’re *trying* to access the \
value without tracking, use `.get_untracked()` or \
`.with_untracked()` instead."
));
}
}
}
}
}
pub trait ReadUntracked: Sized + DefinedAt {
type Value: Deref;
#[track_caller]
fn try_read_untracked(&self) -> Option<Self::Value>;
#[track_caller]
fn read_untracked(&self) -> Self::Value {
self.try_read_untracked()
.unwrap_or_else(unwrap_signal!(self))
}
#[track_caller]
fn custom_try_read(&self) -> Option<Option<Self::Value>> {
None
}
}
pub trait Read: DefinedAt {
type Value: Deref;
#[track_caller]
fn try_read(&self) -> Option<Self::Value>;
#[track_caller]
fn read(&self) -> Self::Value {
self.try_read().unwrap_or_else(unwrap_signal!(self))
}
}
impl<T> Read for T
where
T: Track + ReadUntracked,
{
type Value = T::Value;
fn try_read(&self) -> Option<Self::Value> {
if let Some(custom) = self.custom_try_read() {
custom
} else {
self.track();
self.try_read_untracked()
}
}
}
pub trait UntrackableGuard: DerefMut {
fn untrack(&mut self);
}
impl<T> UntrackableGuard for Box<dyn UntrackableGuard<Target = T>> {
fn untrack(&mut self) {
(**self).untrack();
}
}
pub trait Write: Sized + DefinedAt + Notify {
type Value: Sized + 'static;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>>;
fn try_write_untracked(
&self,
) -> Option<impl DerefMut<Target = Self::Value>>;
fn write(&self) -> impl UntrackableGuard<Target = Self::Value> {
self.try_write().unwrap_or_else(unwrap_signal!(self))
}
fn write_untracked(&self) -> impl DerefMut<Target = Self::Value> {
self.try_write_untracked()
.unwrap_or_else(unwrap_signal!(self))
}
}
pub trait WithUntracked: DefinedAt {
type Value: ?Sized;
#[track_caller]
fn try_with_untracked<U>(
&self,
fun: impl FnOnce(&Self::Value) -> U,
) -> Option<U>;
#[track_caller]
fn with_untracked<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
self.try_with_untracked(fun)
.unwrap_or_else(unwrap_signal!(self))
}
}
impl<T> WithUntracked for T
where
T: DefinedAt + ReadUntracked,
{
type Value = <<Self as ReadUntracked>::Value as Deref>::Target;
fn try_with_untracked<U>(
&self,
fun: impl FnOnce(&Self::Value) -> U,
) -> Option<U> {
self.try_read_untracked().map(|value| fun(&value))
}
}
pub trait With: DefinedAt {
type Value: ?Sized;
#[track_caller]
fn try_with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> Option<U>;
#[track_caller]
fn with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
self.try_with(fun).unwrap_or_else(unwrap_signal!(self))
}
}
impl<T> With for T
where
T: Read,
{
type Value = <<T as Read>::Value as Deref>::Target;
#[track_caller]
fn try_with<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> Option<U> {
self.try_read().map(|val| fun(&val))
}
}
pub trait GetUntracked: DefinedAt {
type Value;
#[track_caller]
fn try_get_untracked(&self) -> Option<Self::Value>;
#[track_caller]
fn get_untracked(&self) -> Self::Value {
self.try_get_untracked()
.unwrap_or_else(unwrap_signal!(self))
}
}
impl<T> GetUntracked for T
where
T: WithUntracked,
T::Value: Clone,
{
type Value = <Self as WithUntracked>::Value;
fn try_get_untracked(&self) -> Option<Self::Value> {
self.try_with_untracked(Self::Value::clone)
}
}
pub trait Get: DefinedAt {
type Value: Clone;
#[track_caller]
fn try_get(&self) -> Option<Self::Value>;
#[track_caller]
fn get(&self) -> Self::Value {
self.try_get().unwrap_or_else(unwrap_signal!(self))
}
}
impl<T> Get for T
where
T: With,
T::Value: Clone,
{
type Value = <T as With>::Value;
#[track_caller]
fn try_get(&self) -> Option<Self::Value> {
self.try_with(Self::Value::clone)
}
}
pub trait Notify {
#[track_caller]
fn notify(&self);
}
pub trait UpdateUntracked: DefinedAt {
type Value;
#[track_caller]
fn update_untracked<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> U,
) -> U {
self.try_update_untracked(fun)
.unwrap_or_else(unwrap_signal!(self))
}
fn try_update_untracked<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> U,
) -> Option<U>;
}
impl<T> UpdateUntracked for T
where
T: Write,
{
type Value = <Self as Write>::Value;
#[track_caller]
fn try_update_untracked<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> U,
) -> Option<U> {
let mut guard = self.try_write_untracked()?;
Some(fun(&mut *guard))
}
}
pub trait Update {
type Value;
#[track_caller]
fn update(&self, fun: impl FnOnce(&mut Self::Value)) {
self.try_update(fun);
}
#[track_caller]
fn maybe_update(&self, fun: impl FnOnce(&mut Self::Value) -> bool) {
self.try_maybe_update(|val| {
let did_update = fun(val);
(did_update, ())
});
}
#[track_caller]
fn try_update<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> U,
) -> Option<U> {
self.try_maybe_update(|val| (true, fun(val)))
}
fn try_maybe_update<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> (bool, U),
) -> Option<U>;
}
impl<T> Update for T
where
T: Write,
{
type Value = <Self as Write>::Value;
#[track_caller]
fn try_maybe_update<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> (bool, U),
) -> Option<U> {
let mut lock = self.try_write()?;
let (did_update, val) = fun(&mut *lock);
if !did_update {
lock.untrack();
}
drop(lock);
Some(val)
}
}
pub trait Set {
type Value;
fn set(&self, value: Self::Value);
fn try_set(&self, value: Self::Value) -> Option<Self::Value>;
}
impl<T> Set for T
where
T: Update + IsDisposed,
{
type Value = <Self as Update>::Value;
#[track_caller]
fn set(&self, value: Self::Value) {
self.try_update(|n| *n = value);
}
#[track_caller]
fn try_set(&self, value: Self::Value) -> Option<Self::Value> {
if self.is_disposed() {
Some(value)
} else {
self.set(value);
None
}
}
}
pub trait ToStream<T> {
#[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
}
}
pub trait FromStream<T> {
#[track_caller]
fn from_stream(stream: impl Stream<Item = T> + Send + 'static) -> Self;
#[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);
crate::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()
}
}
pub trait IsDisposed {
fn is_disposed(&self) -> bool;
}
pub trait IntoInner {
type Value;
fn into_inner(self) -> Option<Self::Value>;
}
pub trait DefinedAt {
fn defined_at(&self) -> Option<&'static Location<'static>>;
}
#[doc(hidden)]
pub fn panic_getting_disposed_signal(
defined_at: Option<&'static Location<'static>>,
location: &'static Location<'static>,
) -> String {
if let Some(defined_at) = defined_at {
format!(
"At {location}, you tried to access a reactive value which was \
defined at {defined_at}, but it has already been disposed."
)
} else {
format!(
"At {location}, you tried to access a reactive value, but it has \
already been disposed."
)
}
}
pub trait ReadValue: Sized + DefinedAt {
type Value: Deref;
#[track_caller]
fn try_read_value(&self) -> Option<Self::Value>;
#[track_caller]
fn read_value(&self) -> Self::Value {
self.try_read_value().unwrap_or_else(unwrap_signal!(self))
}
}
pub trait WithValue: DefinedAt {
type Value: ?Sized;
#[track_caller]
fn try_with_value<U>(
&self,
fun: impl FnOnce(&Self::Value) -> U,
) -> Option<U>;
#[track_caller]
fn with_value<U>(&self, fun: impl FnOnce(&Self::Value) -> U) -> U {
self.try_with_value(fun)
.unwrap_or_else(unwrap_signal!(self))
}
}
impl<T> WithValue for T
where
T: DefinedAt + ReadValue,
{
type Value = <<Self as ReadValue>::Value as Deref>::Target;
fn try_with_value<U>(
&self,
fun: impl FnOnce(&Self::Value) -> U,
) -> Option<U> {
self.try_read_value().map(|value| fun(&value))
}
}
pub trait GetValue: DefinedAt {
type Value: Clone;
#[track_caller]
fn try_get_value(&self) -> Option<Self::Value>;
#[track_caller]
fn get_value(&self) -> Self::Value {
self.try_get_value().unwrap_or_else(unwrap_signal!(self))
}
}
impl<T> GetValue for T
where
T: WithValue,
T::Value: Clone,
{
type Value = <Self as WithValue>::Value;
fn try_get_value(&self) -> Option<Self::Value> {
self.try_with_value(Self::Value::clone)
}
}
pub trait WriteValue: Sized + DefinedAt {
type Value: Sized + 'static;
#[track_caller]
fn try_write_value(&self) -> Option<UntrackedWriteGuard<Self::Value>>;
#[track_caller]
fn write_value(&self) -> UntrackedWriteGuard<Self::Value> {
self.try_write_value().unwrap_or_else(unwrap_signal!(self))
}
}
pub trait UpdateValue: DefinedAt {
type Value;
#[track_caller]
fn try_update_value<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> U,
) -> Option<U>;
#[track_caller]
fn update_value(&self, fun: impl FnOnce(&mut Self::Value)) {
self.try_update_value(fun);
}
}
impl<T> UpdateValue for T
where
T: WriteValue,
{
type Value = <Self as WriteValue>::Value;
#[track_caller]
fn try_update_value<U>(
&self,
fun: impl FnOnce(&mut Self::Value) -> U,
) -> Option<U> {
let mut guard = self.try_write_value()?;
Some(fun(&mut *guard))
}
}
pub trait SetValue: DefinedAt {
type Value;
#[track_caller]
fn try_set_value(&self, value: Self::Value) -> Option<Self::Value>;
#[track_caller]
fn set_value(&self, value: Self::Value) {
self.try_set_value(value);
}
}
impl<T> SetValue for T
where
T: WriteValue,
{
type Value = <Self as WriteValue>::Value;
fn try_set_value(&self, value: Self::Value) -> Option<Self::Value> {
if let Some(mut guard) = self.try_write_value() {
*guard = value;
None
} else {
Some(value)
}
}
}