move hooks out of signals crate

This commit is contained in:
Jonathan Kelley committed 2024-01-29 17:02:26 -08:00
1 parent dcdada542b
commit cef64d43df
13 files changed
+386 -429

No files matched your search

-67
View File
@@ -1,67 +0,0 @@
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
pub trait Dependency: Sized + Clone {
/// The output of the dependency
type Out: Clone + PartialEq;
/// Returns the output of the dependency.
fn out(&self) -> Self::Out;
/// Returns true if the dependency has changed.
fn changed(&self, other: &Self::Out) -> bool {
self.out() != *other
}
}
impl Dependency for () {
type Out = ();
fn out(&self) -> Self::Out {}
}
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
pub trait Dep: 'static + PartialEq + Clone {}
impl<T> Dep for T where T: 'static + PartialEq + Clone {}
impl<A: Dep> Dependency for &A {
type Out = A;
fn out(&self) -> Self::Out {
(*self).clone()
}
}
macro_rules! impl_dep {
(
$($el:ident=$name:ident $other:ident,)*
) => {
impl< $($el),* > Dependency for ($(&$el,)*)
where
$(
$el: Dep
),*
{
type Out = ($($el,)*);
fn out(&self) -> Self::Out {
let ($($name,)*) = self;
($((*$name).clone(),)*)
}
fn changed(&self, other: &Self::Out) -> bool {
let ($($name,)*) = self;
let ($($other,)*) = other;
$(
if *$name != $other {
return true;
}
)*
false
}
}
};
}
impl_dep!(A = a1 a2,);
impl_dep!(A = a1 a2, B = b1 b2,);
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2,);
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2,);
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2,);
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2,);
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2,);
impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G = g1 g2, H = h1 h2,);
-6
View File
@@ -118,12 +118,6 @@ async fn effect_driver(
}
}
/// Create a new effect. The effect will be run immediately and whenever any signal it reads changes.
/// The signal will be owned by the current component and will be dropped when the component is dropped.
pub fn use_effect(callback: impl FnMut() + 'static) {
use_hook(|| Effect::new(callback));
}
/// Effects allow you to run code when a signal changes. Effects are run immediately and whenever any signal it reads changes.
#[derive(Copy, Clone, PartialEq)]
pub struct Effect {
-6
View File
@@ -10,18 +10,12 @@ pub use rt::*;
mod effect;
pub use effect::*;
mod memo;
pub use memo::*;
pub(crate) mod signal;
pub use signal::*;
mod read_only_signal;
pub use read_only_signal::*;
mod dependency;
pub use dependency::*;
mod map;
pub use map::*;
-123
View File
@@ -1,123 +0,0 @@
use crate::read::Readable;
use crate::write::Writable;
use dioxus_core::prelude::*;
use generational_box::Storage;
use crate::dependency::Dependency;
use crate::use_signal;
use crate::{signal::SignalData, ReadOnlySignal, Signal};
/// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes.
///
/// Selectors can be used to efficiently compute derived data from signals.
///
/// ```rust
/// use dioxus::prelude::*;
/// use dioxus_signals::*;
///
/// fn App() -> Element {
/// let mut count = use_signal(|| 0);
/// let double = use_memo(move || count * 2);
/// count += 1;
/// assert_eq!(double.value(), count * 2);
///
/// rsx! { "{double}" }
/// }
/// ```
#[track_caller]
pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> ReadOnlySignal<R> {
use_maybe_sync_memo(f)
}
/// Creates a new Selector that may be sync. The selector will be run immediately and whenever any signal it reads changes.
///
/// Selectors can be used to efficiently compute derived data from signals.
///
/// ```rust
/// use dioxus::prelude::*;
/// use dioxus_signals::*;
///
/// fn App(cx: Scope) -> Element {
/// let mut count = use_signal(cx, || 0);
/// let double = use_memo(cx, move || count * 2);
/// count += 1;
/// assert_eq!(double.value(), count * 2);
///
/// render! { "{double}" }
/// }
/// ```
#[track_caller]
pub fn use_maybe_sync_memo<R: PartialEq, S: Storage<SignalData<R>>>(
f: impl FnMut() -> R + 'static,
) -> ReadOnlySignal<R, S> {
use_hook(|| Signal::maybe_sync_memo(f))
}
/// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes
///
/// Selectors can be used to efficiently compute derived data from signals.
///
/// ```rust
/// use dioxus::prelude::*;
/// use dioxus_signals::*;
///
/// fn App(cx: Scope) -> Element {
/// let mut local_state = use_state(cx, || 0);
/// let double = use_memo_with_dependencies(cx, (local_state.get(),), move |(local_state,)| local_state * 2);
/// local_state.set(1);
///
/// render! { "{double}" }
/// }
/// ```
#[track_caller]
pub fn use_memo_with_dependencies<R: PartialEq, D: Dependency>(
dependencies: D,
f: impl FnMut(D::Out) -> R + 'static,
) -> ReadOnlySignal<R>
where
D::Out: 'static,
{
use_maybe_sync_selector_with_dependencies(dependencies, f)
}
/// Creates a new Selector that may be sync with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes
///
/// Selectors can be used to efficiently compute derived data from signals.
///
/// ```rust
/// use dioxus::prelude::*;
/// use dioxus_signals::*;
///
/// fn App(cx: Scope) -> Element {
/// let mut local_state = use_state(cx, || 0);
/// let double = use_memo_with_dependencies(cx, (local_state.get(),), move |(local_state,)| local_state * 2);
/// local_state.set(1);
///
/// render! { "{double}" }
/// }
/// ```
#[track_caller]
pub fn use_maybe_sync_selector_with_dependencies<
R: PartialEq,
D: Dependency,
S: Storage<SignalData<R>>,
>(
dependencies: D,
mut f: impl FnMut(D::Out) -> R + 'static,
) -> ReadOnlySignal<R, S>
where
D::Out: 'static,
{
let mut dependencies_signal = use_signal(|| dependencies.out());
let selector = use_hook(|| {
Signal::maybe_sync_memo(move || {
let deref = &*dependencies_signal.read();
f(deref.clone())
})
});
let changed = { dependencies.changed(&*dependencies_signal.read()) };
if changed {
dependencies_signal.set(dependencies.out());
}
selector
}
+78 -166
View File
@@ -1,7 +1,15 @@
use crate::{
read::Readable, write::Writable, Effect, EffectInner, GlobalMemo, GlobalSignal, MappedSignal,
ReadOnlySignal,
get_effect_ref, read::Readable, write::Writable, CopyValue, Effect, EffectInner,
EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReadOnlySignal, EFFECT_STACK,
};
use dioxus_core::{
prelude::{
current_scope_id, has_context, provide_context, schedule_update_any, IntoAttributeValue,
},
ScopeId,
};
use generational_box::{AnyStorage, GenerationalBoxId, Storage, SyncStorage, UnsyncStorage};
use parking_lot::RwLock;
use std::{
any::Any,
cell::RefCell,
@@ -10,154 +18,6 @@ use std::{
sync::Arc,
};
use dioxus_core::{
prelude::{
current_scope_id, has_context, provide_context, schedule_update_any, use_hook,
IntoAttributeValue,
},
ScopeId,
};
use generational_box::{AnyStorage, GenerationalBoxId, Storage, SyncStorage, UnsyncStorage};
use parking_lot::RwLock;
use crate::{get_effect_ref, CopyValue, EffectStackRef, EFFECT_STACK};
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
///
/// ```rust
/// use dioxus::prelude::*;
/// use dioxus_signals::*;
///
/// fn App() -> Element {
/// let mut count = use_signal(|| 0);
///
/// // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated.
/// // The app component will never be rerendered in this example.
/// rsx! { Child { state: count } }
/// }
///
/// #[component]
/// fn Child(state: Signal<u32>) -> Element {
/// let state = *state;
///
/// use_future( |()| async move {
/// // Because the signal is a Copy type, we can use it in an async block without cloning it.
/// *state.write() += 1;
/// });
///
/// rsx! {
/// button {
/// onclick: move |_| *state.write() += 1,
/// "{state}"
/// }
/// }
/// }
/// ```
#[track_caller]
#[must_use]
pub fn use_signal<T: 'static>(f: impl FnOnce() -> T) -> Signal<T, UnsyncStorage> {
#[cfg(debug_assertions)]
let caller = std::panic::Location::caller();
use_hook(|| {
Signal::new_with_caller(
f(),
#[cfg(debug_assertions)]
caller,
)
})
}
/// Creates a new `Send + Sync`` Signal. Signals are a Copy state management solution with automatic dependency tracking.
///
/// ```rust
/// use dioxus::prelude::*;
/// use dioxus_signals::*;
///
/// fn App(cx: Scope) -> Element {
/// let mut count = use_signal_sync(cx, || 0);
///
/// // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated.
/// // The app component will never be rerendered in this example.
/// render! { Child { state: count } }
/// }
///
/// #[component]
/// fn Child(cx: Scope, state: Signal<u32, SyncStorage>) -> Element {
/// let state = *state;
///
/// use_future!(cx, |()| async move {
/// // This signal is Send + Sync, so we can use it in an another thread
/// tokio::spawn(async move {
/// // Because the signal is a Copy type, we can use it in an async block without cloning it.
/// *state.write() += 1;
/// }).await;
/// });
///
/// render! {
/// button {
/// onclick: move |_| *state.write() += 1,
/// "{state}"
/// }
/// }
/// }
/// ```
#[must_use]
#[track_caller]
pub fn use_signal_sync<T: Send + Sync + 'static>(f: impl FnOnce() -> T) -> Signal<T, SyncStorage> {
#[cfg(debug_assertions)]
let caller = std::panic::Location::caller();
use_hook(|| {
Signal::new_with_caller(
f(),
#[cfg(debug_assertions)]
caller,
)
})
}
struct Unsubscriber {
scope: ScopeId,
subscribers: UnsubscriberArray,
}
type UnsubscriberArray = Vec<Rc<RefCell<Vec<ScopeId>>>>;
impl Drop for Unsubscriber {
fn drop(&mut self) {
for subscribers in &self.subscribers {
subscribers.borrow_mut().retain(|s| *s != self.scope);
}
}
}
fn current_unsubscriber() -> Rc<RefCell<Unsubscriber>> {
match has_context() {
Some(rt) => rt,
None => {
let owner = Unsubscriber {
scope: current_scope_id().expect("in a virtual dom"),
subscribers: Default::default(),
};
provide_context(Rc::new(RefCell::new(owner)))
}
}
}
#[derive(Default)]
pub(crate) struct SignalSubscribers {
pub(crate) subscribers: Vec<ScopeId>,
pub(crate) effect_subscribers: Vec<GenerationalBoxId>,
}
/// The data stored for tracking in a signal.
pub struct SignalData<T> {
pub(crate) subscribers: Arc<RwLock<SignalSubscribers>>,
pub(crate) update_any: Arc<dyn Fn(ScopeId) + Sync + Send>,
pub(crate) effect_ref: EffectStackRef,
pub(crate) value: T,
}
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
///
/// ```rust
@@ -197,22 +57,18 @@ pub struct Signal<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage> {
/// A signal that can safely shared between threads.
pub type SyncSignal<T> = Signal<T, SyncStorage>;
#[cfg(feature = "serde")]
impl<T: serde::Serialize + 'static, Store: Storage<SignalData<T>>> serde::Serialize
for Signal<T, Store>
{
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.read().serialize(serializer)
}
/// The data stored for tracking in a signal.
pub struct SignalData<T> {
pub(crate) subscribers: Arc<RwLock<SignalSubscribers>>,
pub(crate) update_any: Arc<dyn Fn(ScopeId) + Sync + Send>,
pub(crate) effect_ref: EffectStackRef,
pub(crate) value: T,
}
#[cfg(feature = "serde")]
impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>>>
serde::Deserialize<'de> for Signal<T, Store>
{
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(Self::new_maybe_sync(T::deserialize(deserializer)?))
}
#[derive(Default)]
pub(crate) struct SignalSubscribers {
pub(crate) subscribers: Vec<ScopeId>,
pub(crate) effect_subscribers: Vec<GenerationalBoxId>,
}
impl<T: 'static> Signal<T> {
@@ -312,7 +168,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
}
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
fn new_with_caller(
pub fn new_with_caller(
value: T,
#[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>,
) -> Self {
@@ -386,6 +242,15 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
}
}
pub fn unsubscribe(&self, scope: ScopeId) {
self.inner
.read()
.subscribers
.write()
.subscribers
.retain(|s| *s != scope);
}
/// Map the signal to a new type.
pub fn map<O>(self, f: impl Fn(&T) -> &O + 'static) -> MappedSignal<S::Ref<O>> {
MappedSignal::new(self, f)
@@ -411,7 +276,8 @@ impl<T, S: Storage<SignalData<T>>> Readable<T> for Signal<T, S> {
S::try_map(ref_, f)
}
/// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead.
/// Get the current value of the signal. This will subscribe the current scope to the signal.
/// If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead.
///
/// If the signal has been dropped, this will panic.
#[track_caller]
@@ -509,6 +375,52 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for Signal<T, S> {
}
}
#[cfg(feature = "serde")]
impl<T: serde::Serialize + 'static, Store: Storage<SignalData<T>>> serde::Serialize
for Signal<T, Store>
{
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.read().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>>>
serde::Deserialize<'de> for Signal<T, Store>
{
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(Self::new_maybe_sync(T::deserialize(deserializer)?))
}
}
struct Unsubscriber {
scope: ScopeId,
subscribers: UnsubscriberArray,
}
type UnsubscriberArray = Vec<Rc<RefCell<Vec<ScopeId>>>>;
impl Drop for Unsubscriber {
fn drop(&mut self) {
for subscribers in &self.subscribers {
subscribers.borrow_mut().retain(|s| *s != self.scope);
}
}
}
fn current_unsubscriber() -> Rc<RefCell<Unsubscriber>> {
match has_context() {
Some(rt) => rt,
None => {
let owner = Unsubscriber {
scope: current_scope_id().expect("in a virtual dom"),
subscribers: Default::default(),
};
provide_context(Rc::new(RefCell::new(owner)))
}
}
}
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
signal: Signal<T, S>,
}