use crate::{
debug_warn,
runtime::{with_runtime, RuntimeId},
spawn_local, Runtime, Scope, ScopeProperty, UntrackedGettableSignal, UntrackedSettableSignal,
};
use futures::Stream;
use std::{fmt::Debug, marker::PhantomData};
use thiserror::Error;
pub fn create_signal<T>(cx: Scope, value: T) -> (ReadSignal<T>, WriteSignal<T>) {
let s = cx.runtime.create_signal(value);
cx.with_scope_property(|prop| prop.push(ScopeProperty::Signal(s.0.id)));
s
}
pub fn create_signal_from_stream<T>(
cx: Scope,
mut stream: impl Stream<Item = T> + Unpin + 'static,
) -> ReadSignal<Option<T>> {
use futures::StreamExt;
let (read, write) = create_signal(cx, None);
spawn_local(async move {
while let Some(value) = stream.next().await {
write.set(Some(value));
}
});
read
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ReadSignal<T>
where
T: 'static,
{
pub(crate) runtime: RuntimeId,
pub(crate) id: SignalId,
pub(crate) ty: PhantomData<T>,
}
impl<T> UntrackedGettableSignal<T> for ReadSignal<T> {
fn get_untracked(&self) -> T
where
T: Clone,
{
self.with_no_subscription(|v| v.clone())
}
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.with_no_subscription(f)
}
}
impl<T> ReadSignal<T>
where
T: 'static,
{
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
self.id.with(self.runtime, f)
}
pub(crate) fn with_no_subscription<U>(&self, f: impl FnOnce(&T) -> U) -> U {
self.id.with_no_subscription(self.runtime, f)
}
#[cfg(feature = "hydrate")]
pub(crate) fn subscribe(&self) {
with_runtime(self.runtime, |runtime| self.id.subscribe(runtime))
}
pub fn get(&self) -> T
where
T: Clone,
{
self.id.with(self.runtime, T::clone)
}
pub(crate) fn try_with<U>(&self, f: impl FnOnce(&T) -> U) -> Result<U, SignalError> {
with_runtime(self.runtime, |runtime| self.id.try_with(runtime, f))
}
pub fn to_stream(&self) -> impl Stream<Item = T>
where
T: Clone,
{
let (tx, rx) = futures::channel::mpsc::unbounded();
let id = self.id;
let runtime = self.runtime;
runtime.create_effect(move |_| {
_ = tx.unbounded_send(id.with(runtime, T::clone));
});
rx
}
}
impl<T> Clone for ReadSignal<T> {
fn clone(&self) -> Self {
Self {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
}
}
}
impl<T> Copy for ReadSignal<T> {}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for ReadSignal<T>
where
T: Clone,
{
type Output = T;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for ReadSignal<T>
where
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for ReadSignal<T>
where
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct WriteSignal<T>
where
T: 'static,
{
pub(crate) runtime: RuntimeId,
pub(crate) id: SignalId,
pub(crate) ty: PhantomData<T>,
}
impl<T> UntrackedSettableSignal<T> for WriteSignal<T>
where
T: 'static,
{
fn set_untracked(&self, new_value: T) {
self.id
.update_with_no_effect(self.runtime, |v| *v = new_value);
}
fn update_untracked(&self, f: impl FnOnce(&mut T)) {
self.id.update_with_no_effect(self.runtime, f);
}
fn update_returning_untracked<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
self.id.update_with_no_effect(self.runtime, f)
}
}
impl<T> WriteSignal<T>
where
T: 'static,
{
pub fn update(&self, f: impl FnOnce(&mut T)) {
self.id.update(self.runtime, f);
}
pub fn update_returning<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
self.id.update(self.runtime, f)
}
pub fn set(&self, new_value: T) {
self.id.update(self.runtime, |n| *n = new_value);
}
}
impl<T> Clone for WriteSignal<T> {
fn clone(&self) -> Self {
Self {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
}
}
}
impl<T> Copy for WriteSignal<T> {}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<(T,)> for WriteSignal<T>
where
T: 'static,
{
type Output = ();
extern "rust-call" fn call_once(self, args: (T,)) -> Self::Output {
self.update(move |n| *n = args.0)
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<(T,)> for WriteSignal<T>
where
T: 'static,
{
extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
self.update(move |n| *n = args.0)
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<(T,)> for WriteSignal<T>
where
T: 'static,
{
extern "rust-call" fn call(&self, args: (T,)) -> Self::Output {
self.update(move |n| *n = args.0)
}
}
pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
let s = cx.runtime.create_rw_signal(value);
cx.with_scope_property(|prop| prop.push(ScopeProperty::Signal(s.id)));
s
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct RwSignal<T>
where
T: 'static,
{
pub(crate) runtime: RuntimeId,
pub(crate) id: SignalId,
pub(crate) ty: PhantomData<T>,
}
impl<T> Clone for RwSignal<T> {
fn clone(&self) -> Self {
Self {
runtime: self.runtime,
id: self.id,
ty: self.ty,
}
}
}
impl<T> Copy for RwSignal<T> {}
impl<T> UntrackedGettableSignal<T> for RwSignal<T> {
fn get_untracked(&self) -> T
where
T: Clone,
{
self.id
.with_no_subscription(self.runtime, |v: &T| v.clone())
}
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.id.with_no_subscription(self.runtime, f)
}
}
impl<T> UntrackedSettableSignal<T> for RwSignal<T> {
fn set_untracked(&self, new_value: T) {
self.id
.update_with_no_effect(self.runtime, |v| *v = new_value);
}
fn update_untracked(&self, f: impl FnOnce(&mut T)) {
self.id.update_with_no_effect(self.runtime, f);
}
fn update_returning_untracked<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
self.id.update_with_no_effect(self.runtime, f)
}
}
impl<T> RwSignal<T>
where
T: 'static,
{
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
self.id.with(self.runtime, f)
}
pub fn get(&self) -> T
where
T: Clone,
{
self.id.with(self.runtime, T::clone)
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
self.id.update(self.runtime, f);
}
pub fn update_returning<U>(&self, f: impl FnOnce(&mut T) -> U) -> Option<U> {
self.id.update(self.runtime, f)
}
pub fn set(&self, value: T) {
self.id.update(self.runtime, |n| *n = value);
}
pub fn read_only(&self) -> ReadSignal<T> {
ReadSignal {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
}
}
pub fn write_only(&self) -> WriteSignal<T> {
WriteSignal {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
}
}
pub fn split(&self) -> (ReadSignal<T>, WriteSignal<T>) {
(
ReadSignal {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
},
WriteSignal {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
},
)
}
pub fn to_stream(&self) -> impl Stream<Item = T>
where
T: Clone,
{
self.read_only().to_stream()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for RwSignal<T>
where
T: Clone,
{
type Output = T;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for RwSignal<T>
where
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for RwSignal<T>
where
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
}
}
slotmap::new_key_type! {
pub struct SignalId;
}
#[derive(Debug, Error)]
pub(crate) enum SignalError {
#[error("tried to access a signal that had been disposed")]
Disposed,
#[error("error casting signal to type {0}")]
Type(&'static str),
}
impl SignalId {
pub(crate) fn subscribe(&self, runtime: &Runtime) {
if let Some(observer) = runtime.observer.get() {
let mut subs = runtime.signal_subscribers.borrow_mut();
if let Some(subs) = subs.entry(*self) {
subs.or_default().borrow_mut().insert(observer);
}
}
}
pub(crate) fn try_with_no_subscription<T, U>(
&self,
runtime: &Runtime,
f: impl FnOnce(&T) -> U,
) -> Result<U, SignalError>
where
T: 'static,
{
let value = {
let signals = runtime.signals.borrow();
match signals.get(*self).cloned().ok_or(SignalError::Disposed) {
Ok(s) => Ok(s),
Err(e) => {
debug_warn!("[Signal::try_with] {e}");
Err(e)
}
}
}?;
let value = value.try_borrow().unwrap_or_else(|e| {
debug_warn!(
"Signal::try_with_no_subscription failed on Signal<{}>. It seems you're trying to read the value of a signal within an effect caused by updating the signal.",
std::any::type_name::<T>()
);
panic!("{e}");
});
let value = value
.downcast_ref::<T>()
.ok_or_else(|| SignalError::Type(std::any::type_name::<T>()))?;
Ok(f(value))
}
pub(crate) fn try_with<T, U>(
&self,
runtime: &Runtime,
f: impl FnOnce(&T) -> U,
) -> Result<U, SignalError>
where
T: 'static,
{
self.subscribe(runtime);
self.try_with_no_subscription(runtime, f)
}
pub(crate) fn with_no_subscription<T, U>(
&self,
runtime: RuntimeId,
f: impl FnOnce(&T) -> U,
) -> U
where
T: 'static,
{
with_runtime(runtime, |runtime| {
self.try_with_no_subscription(runtime, f).unwrap()
})
}
pub(crate) fn with<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&T) -> U) -> U
where
T: 'static,
{
with_runtime(runtime, |runtime| self.try_with(runtime, f).unwrap())
}
fn update_value<T, U>(&self, runtime: RuntimeId, f: impl FnOnce(&mut T) -> U) -> Option<U>
where
T: 'static,
{
with_runtime(runtime, |runtime| {
let value = {
let signals = runtime.signals.borrow();
signals.get(*self).cloned()
};
if let Some(value) = value {
let mut value = value.borrow_mut();
if let Some(value) = value.downcast_mut::<T>() {
Some(f(value))
} else {
debug_warn!(
"[Signal::update] failed when downcasting to Signal<{}>",
std::any::type_name::<T>()
);
None
}
} else {
debug_warn!(
"[Signal::update] You’re trying to update a Signal<{}> that has already been disposed of. This is probably either a logic error in a component that creates and disposes of scopes, or a Resource resolving after its scope has been dropped without having been cleaned up.",
std::any::type_name::<T>()
);
None
}
})
}
pub(crate) fn update<T, U>(
&self,
runtime_id: RuntimeId,
f: impl FnOnce(&mut T) -> U,
) -> Option<U>
where
T: 'static,
{
with_runtime(runtime_id, |runtime| {
let updated = self.update_value(runtime_id, f);
if updated.is_some() {
let subs = {
let subs = runtime.signal_subscribers.borrow();
let subs = subs.get(*self);
subs.map(|subs| subs.borrow().clone())
};
if let Some(subs) = subs {
for sub in subs {
let effect = {
let effects = runtime.effects.borrow();
effects.get(sub).cloned()
};
if let Some(effect) = effect {
effect.run(sub, runtime_id);
}
}
}
};
updated
})
}
pub(crate) fn update_with_no_effect<T, U>(
&self,
runtime: RuntimeId,
f: impl FnOnce(&mut T) -> U,
) -> Option<U>
where
T: 'static,
{
self.update_value(runtime, f)
}
}