use std::{cell::RefCell, rc::Rc};
use crate::{
state_owner::{SharableRef, SignalInner, SignalOwner},
utils::RcStatus,
};
impl<T> SharableRef for Rc<T> {
type Value = T;
fn create(value: Self::Value) -> Self {
Rc::new(value)
}
fn map<R>(&self, f: impl FnOnce(&Self::Value) -> R) -> R {
f(self)
}
fn shared_count(&self) -> usize {
Rc::strong_count(self)
}
fn map_mut_or_borrow_mut<V, R>(
&mut self,
on_map_mut: impl FnOnce(&mut Self::Value) -> &mut V,
on_borrow_mut: impl FnOnce(&Self::Value) -> &RefCell<V>,
f: impl FnOnce(&mut V, RcStatus) -> R,
) -> R {
if let Some(value) = Rc::get_mut(self) {
f(on_map_mut(value), RcStatus::Owned)
} else {
let mut value = on_borrow_mut(self).borrow_mut();
f(&mut value, RcStatus::Shared)
}
}
}
pub struct SharedSignal<T> {
imp: SignalOwner<T, Rc<SignalInner<T>>>,
}
impl<T> PartialEq for SharedSignal<T> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(self.inner(), other.inner())
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for SharedSignal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.imp.debug_fmt("SharedState", f)
}
}
impl<T> Clone for SharedSignal<T> {
fn clone(&self) -> Self {
Self {
imp: self.imp.clone(),
}
}
}
impl<T> SharedSignal<T> {
#[inline]
pub fn new(initial_value: T) -> Self {
Self {
imp: SignalOwner::new(initial_value),
}
}
pub(crate) fn imp_notify_changed(&self) {
self.imp.notify_changed()
}
pub(crate) fn imp_map_mut_and_notify_if<R>(&self, f: impl FnOnce(&mut T) -> (R, bool)) -> R {
self.imp.map_mut_and_notify_if(f)
}
fn inner(&self) -> &Rc<SignalInner<T>> {
self.imp.inner()
}
}
#[cfg(feature = "ShareValue")]
impl<T> crate::ShareValue for SharedSignal<T> {
type Value = T;
fn try_unwrap(self) -> Result<Self::Value, Self>
where
Self: Sized,
{
let inner = self.inner();
if Rc::strong_count(inner) == 1 {
let inner = inner.clone();
drop(self);
match Rc::try_unwrap(inner) {
Ok(inner) => Ok(inner.into_value()),
Err(_) => unreachable!(),
}
} else {
Err(self)
}
}
#[inline]
fn get(&self) -> T
where
T: Copy,
{
*self.inner().value.borrow()
}
#[inline]
fn get_cloned(&self) -> T
where
T: Clone,
{
self.inner().value.borrow().clone()
}
#[inline]
fn replace(&self, new_value: T) -> T {
self.map_mut(|v| std::mem::replace(v, new_value))
}
#[inline]
fn map<R>(&self, f: impl FnOnce(&T) -> R) -> R {
f(&self.inner().value.borrow())
}
#[inline]
fn map_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
self.imp_notify_changed();
f(&mut self.inner().value.borrow_mut())
}
fn equivalent_to(&self, other: &Self) -> bool {
*self == *other
}
}
#[cfg(feature = "Signal")]
impl<T> crate::SignalHook for SharedSignal<T> {
type SignalShareValue = T;
type SignalHookUninitialized = crate::utils::UninitializedHook<Self>;
fn to_signal(&self) -> &Self {
self
}
}
#[cfg(feature = "Signal")]
impl<T> crate::Signal for SharedSignal<T> {
type SignalHook = Self;
fn is_signal_of(&self, signal_hook: &Self::SignalHook) -> bool {
use crate::SignalHook;
self == signal_hook.to_signal()
}
fn to_signal_hook(&self) -> Self::SignalHook {
self.clone()
}
fn update_signal_hook(&self, mut hook: std::pin::Pin<&mut Self::SignalHook>) {
use crate::ShareValue;
if !hook.equivalent_to(self) {
hook.set(self.clone())
}
}
fn h_signal_hook<'hook>(
&self,
hook: std::pin::Pin<
&'hook mut <Self::SignalHook as crate::SignalHook>::SignalHookUninitialized,
>,
) -> crate::Value<'hook, Self::SignalHook> {
hook.get_mut().use_with_signal(self)
}
fn notify_changed(&self) {
self.imp_notify_changed()
}
fn map_mut_and_notify_if<R>(&self, f: impl FnOnce(&mut Self::Value) -> (R, bool)) -> R {
self.imp_map_mut_and_notify_if(f)
}
}
#[cfg(feature = "ShareValue")]
impl<T> crate::ToOwnedShareValue for SharedSignal<T> {
type OwnedShareValue = Self;
#[inline]
fn to_owned_share_value(&self) -> Self::OwnedShareValue {
self.clone()
}
}
hooks_core::impl_hook![
impl<T> SharedSignal<T> {
fn unmount() {}
#[inline]
fn poll_next_update(self, cx: _) {
use hooks_core::HookPollNextUpdateExt;
self.get_mut().imp.poll_next_update(cx)
}
#[inline]
fn use_hook(self) -> &'hook Self {
use hooks_core::HookExt;
let this = self.get_mut();
_ = this.imp.use_hook();
this
}
}
];
pub struct UseSharedSignal<T>(pub T);
hooks_core::impl_hook![
impl<T> UseSharedSignal<T> {
#[inline]
fn into_hook(self) -> SharedSignal<T> {
SharedSignal::new(self.0)
}
#[inline(always)]
fn update_hook(self, _hook: _) {}
fn h(self, hook: crate::utils::UninitializedHook<SharedSignal<T>>) {
hook.get_mut().use_into_or_update_hook(self)
}
}
];
pub struct UseSharedSignalWith<T, F: FnOnce() -> T>(pub F);
hooks_core::impl_hook![
impl<T, F: FnOnce() -> T> UseSharedSignalWith<T, F> {
#[inline]
fn into_hook(self) -> SharedSignal<T> {
SharedSignal::new(self.0())
}
#[inline(always)]
fn update_hook(self, _hook: _) {}
fn h(self, hook: crate::utils::UninitializedHook<SharedSignal<T>>) {
hook.get_mut().use_into_or_update_hook(self)
}
}
];
pub type SharedSignalEq<T> = crate::SignalEq<SharedSignal<T>>;
pub type RefSharedSignalEq<'a, T> = crate::SignalEq<&'a SharedSignal<T>>;
#[cfg(feature = "ShareValue")]
#[cfg(feature = "futures-core")]
#[cfg(test)]
mod tests {
use futures_lite::StreamExt;
use hooks_core::hook_fn;
use crate::{use_shared_signal, utils::testing::assert_always_pending, ShareValue};
#[test]
#[cfg(feature = "use_effect")]
fn shared_signal() {
use hooks_core::IntoHook;
use crate::use_effect;
hook_fn!(
fn use_test() -> i32 {
let state = h![use_shared_signal(0)];
let value = state.get();
let s = state.clone();
h![use_effect(
move |v: &_| {
if *v < 2 {
s.set(*v + 1);
}
},
value,
)];
value
}
);
futures_lite::future::block_on(async {
let values = use_test().into_hook_values();
let values = values.collect::<Vec<_>>().await;
assert_eq!(values, [0, 1, 2]);
});
}
#[test]
fn drop_in_map() {
use hooks_core::IntoHook;
hook_fn!(
fn use_test() -> i32 {
let state = h!(use_shared_signal(0));
let value = state.get();
let s = state.clone();
let _: () = state.map(|_| drop(s));
value
}
);
assert_eq!(
futures_lite::future::block_on(use_test().into_hook_values().collect::<Vec<_>>(),),
[0]
)
}
#[test]
fn drop_in_conditional_map_mut() {
use hooks_core::IntoHook;
hook_fn!(
fn use_test() -> i32 {
let state = h!(use_shared_signal(0));
let value = state.get();
let s = state.clone();
if value == 0 {
let _: () = state.map_mut(|v| {
drop(s);
*v = 1;
});
}
value
}
);
assert_eq!(
futures_lite::future::block_on(use_test().into_hook_values().collect::<Vec<_>>(),),
[0, 1]
)
}
#[cfg(feature = "Signal")]
#[test]
fn reference_cycle_should_always_pending() {
use hooks_core::IntoHook;
use crate::IntoEq;
#[derive(PartialEq)]
struct Data(#[allow(dead_code)] Option<super::SharedSignal<Self>>);
hook_fn!(
fn use_test() {
let state = h!(use_shared_signal(Data(None))).into_eq();
state.set(Data(Some(state.0.clone())));
}
);
assert_always_pending(|| use_test().into_hook_values().collect::<Vec<_>>());
}
#[test]
fn unconditional_map_mut_should_emit_infinite_values() {
use hooks_core::IntoHook;
hook_fn!(
fn use_test() -> i32 {
let state = h!(use_shared_signal(0));
let value = state.get();
let _: () = state.map_mut(|_| {});
value
}
);
futures_lite::future::block_on(async {
let res = use_test()
.into_hook_values()
.take(100)
.collect::<Vec<_>>()
.await;
assert_eq!(res, [0; 100])
});
}
}