diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index ff0033e49..9c217f640 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -92,3 +92,6 @@ pub use use_hook_did_run::*; mod use_signal; pub use use_signal::*; + +mod use_set_compare; +pub use use_set_compare::*; diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index bccf0b4f2..94a0f4d8a 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -1,8 +1,7 @@ #![allow(missing_docs)] use crate::{use_callback, use_hook_did_run, use_signal, UseCallback}; -use dioxus_core::{prelude::*, Task}; +use dioxus_core::prelude::*; use dioxus_signals::*; -use dioxus_signals::{Readable, Writable}; use std::future::Future; use std::ops::Deref; diff --git a/packages/hooks/src/use_set_compare.rs b/packages/hooks/src/use_set_compare.rs new file mode 100644 index 000000000..3df31682f --- /dev/null +++ b/packages/hooks/src/use_set_compare.rs @@ -0,0 +1,53 @@ +use std::hash::Hash; + +use dioxus_core::prelude::*; +use dioxus_signals::{ReadOnlySignal, SetCompare}; + +/// Creates a new SetCompare which efficiently tracks when a value changes to check if it is equal to a set of values. +/// +/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. +/// +/// ```rust +/// use dioxus::prelude::*; +/// +/// fn App() -> Element { +/// let mut count = use_signal(|| 0); +/// let compare = use_set_compare(move || count()); +/// +/// rsx! { +/// for i in 0..10 { +/// // Child will only re-render when i == count +/// Child { compare, i } +/// } +/// button { +/// // This will only rerender the child with the old and new value of i == count +/// // Because we are using a set compare, this will be O(1) instead of the O(n) performance of a selector +/// onclick: move |_| count += 1, +/// "Increment" +/// } +/// } +/// } +/// +/// #[component] +/// fn Child(i: usize, compare: SetCompare) -> Element { +/// let active = use_set_compare_equal(i, compare); +/// if active() { +/// rsx! { "Active" } +/// } else { +/// rsx! { "Inactive" } +/// } +/// } +/// ``` +#[must_use] +pub fn use_set_compare(f: impl FnMut() -> R + 'static) -> SetCompare { + use_hook(move || SetCompare::new(f)) +} + +/// A hook that returns true if the value is equal to the value in the set compare. +#[must_use] +pub fn use_set_compare_equal( + value: R, + mut compare: SetCompare, +) -> ReadOnlySignal { + use_hook(move || compare.equal(value)) +} diff --git a/packages/signals/src/comparer.rs b/packages/signals/src/comparer.rs deleted file mode 100644 index bdcfd2180..000000000 --- a/packages/signals/src/comparer.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::write::Writable; -use std::hash::Hash; - -use crate::read::Readable; -use dioxus_core::prelude::*; -use generational_box::{Storage, UnsyncStorage}; - -use crate::{CopyValue, Effect, ReadOnlySignal, Signal, SignalData}; -use rustc_hash::FxHashMap; - -/// An object that can efficiently compare a value to a set of values. -#[derive(Debug)] -pub struct Comparer> = UnsyncStorage> { - subscribers: CopyValue>>, -} - -impl Comparer { - /// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values. - /// - /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. - pub fn new(mut f: impl FnMut() -> R + 'static) -> Comparer { - let subscribers: CopyValue>> = - CopyValue::new(FxHashMap::default()); - let mut previous = CopyValue::new(None); - - Effect::new(move || { - let subscribers = subscribers.read(); - let mut previous = previous.write(); - - if let Some(previous) = previous.take() { - if let Some(mut value) = subscribers.get(&previous).cloned() { - value.set(false) - } - } - - let current = f(); - - if let Some(mut value) = subscribers.get(¤t).cloned() { - *value.write() = true; - } - - *previous = Some(current); - }); - - Comparer { subscribers } - } -} - -impl>> Comparer { - /// Creates a new Comparer that may be `Sync + Send` which efficiently tracks when a value changes to check if it is equal to a set of values. - /// - /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. - pub fn new_maybe_sync(mut f: impl FnMut() -> R + 'static) -> Comparer { - let subscribers: CopyValue>> = - CopyValue::new(FxHashMap::default()); - let mut previous = CopyValue::new(None); - - Effect::new(move || { - let subscribers = subscribers.read(); - let mut previous = previous.write(); - - if let Some(previous) = previous.take() { - if let Some(mut value) = subscribers.get(&previous).cloned() { - *value.write() = false; - } - } - - let current = f(); - - if let Some(mut value) = subscribers.get(¤t).cloned() { - *value.write() = true; - } - - *previous = Some(current); - }); - - Comparer { subscribers } - } - - /// Returns a signal which is true when the value is equal to the value passed to this function. - pub fn equal(&mut self, value: R) -> ReadOnlySignal { - let subscribers = self.subscribers.write(); - - match subscribers.get(&value) { - Some(&signal) => signal.into(), - None => { - drop(subscribers); - let mut subscribers = self.subscribers.write(); - let signal = Signal::new_maybe_sync(false); - subscribers.insert(value, signal); - signal.into() - } - } - } -} - -impl>> Clone for Comparer { - fn clone(&self) -> Self { - *self - } -} - -impl>> Copy for Comparer {} - -/// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values. -/// -/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. -/// -/// ```rust -/// use dioxus::prelude::*; -/// use dioxus_signals::*; -/// -/// fn App() -> Element { -/// let mut count = use_signal(cx, || 0); -/// let comparer = use_comparer(cx, move || count.value()); -/// -/// render! { -/// for i in 0..10 { -/// // Child will only re-render when i == count -/// Child { active: comparer.equal(i) } -/// } -/// button { -/// // This will only rerender the child with the old and new value of i == count -/// // Because we are using a comparer, this will be O(1) instead of the O(n) performance of a selector -/// onclick: move |_| count += 1, -/// "Increment" -/// } -/// } -/// } -/// -/// #[component] -/// fn Child(active: ReadOnlySignal) -> Element { -/// if *active() { -/// render! { "Active" } -/// } else { -/// render! { "Inactive" } -/// } -/// } -/// ``` -#[must_use] -pub fn use_comparer(f: impl FnMut() -> R + 'static) -> Comparer { - use_hook(move || Comparer::new(f)) -} diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index b702d2080..9668d3d9c 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -16,8 +16,8 @@ pub use read_only_signal::*; mod map; pub use map::*; -// mod comparer; -// pub use comparer::*; +mod set_compare; +pub use set_compare::*; mod memo; pub use memo::*; diff --git a/packages/signals/src/set_compare.rs b/packages/signals/src/set_compare.rs new file mode 100644 index 000000000..d8ddca912 --- /dev/null +++ b/packages/signals/src/set_compare.rs @@ -0,0 +1,99 @@ +use crate::{write::Writable, ReactiveContext}; +use std::hash::Hash; + +use crate::read::Readable; +use dioxus_core::prelude::*; +use futures_util::StreamExt; +use generational_box::{Storage, UnsyncStorage}; + +use crate::{CopyValue, ReadOnlySignal, Signal, SignalData}; +use rustc_hash::FxHashMap; + +/// An object that can efficiently compare a value to a set of values. +#[derive(Debug)] +pub struct SetCompare> = UnsyncStorage> { + subscribers: CopyValue>>, +} + +impl SetCompare { + /// Creates a new [`SetCompare`] which efficiently tracks when a value changes to check if it is equal to a set of values. + /// + /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. + #[track_caller] + pub fn new(f: impl FnMut() -> R + 'static) -> SetCompare { + Self::new_maybe_sync(f) + } +} + +impl>> SetCompare { + /// Creates a new [`SetCompare`] that may be `Sync + Send` which efficiently tracks when a value changes to check if it is equal to a set of values. + /// + /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. + #[track_caller] + pub fn new_maybe_sync(mut f: impl FnMut() -> R + 'static) -> SetCompare { + let subscribers: CopyValue>> = + CopyValue::new(FxHashMap::default()); + let mut previous = CopyValue::new(None); + + let mut recompute = move || { + let subscribers = subscribers.read(); + let mut previous = previous.write(); + + if let Some(previous) = previous.take() { + if let Some(mut value) = subscribers.get(&previous).cloned() { + *value.write() = false; + } + } + + let current = f(); + + if let Some(mut value) = subscribers.get(¤t).cloned() { + *value.write() = true; + } + + *previous = Some(current); + }; + let (rc, mut changed) = ReactiveContext::new(); + spawn(async move { + loop { + // Recompute the value + rc.run_in(&mut recompute); + + // Wait for context to change + let _ = changed.next().await; + } + }); + + SetCompare { subscribers } + } + + /// Returns a signal which is true when the value is equal to the value passed to this function. + pub fn equal(&mut self, value: R) -> ReadOnlySignal { + let subscribers = self.subscribers.write(); + + match subscribers.get(&value) { + Some(&signal) => signal.into(), + None => { + drop(subscribers); + let mut subscribers = self.subscribers.write(); + let signal = Signal::new_maybe_sync(false); + subscribers.insert(value, signal); + signal.into() + } + } + } +} + +impl>> PartialEq for SetCompare { + fn eq(&self, other: &Self) -> bool { + self.subscribers == other.subscribers + } +} + +impl>> Clone for SetCompare { + fn clone(&self) -> Self { + *self + } +} + +impl>> Copy for SetCompare {}