瀏覽代碼

Merge branch 'create-comparer' into maybe-sync-signal

Evan Almloff 1 年之前
父節點
當前提交
69a1ec0e3c
共有 3 個文件被更改,包括 115 次插入0 次删除
  1. 107 0
      packages/signals/src/comparer.rs
  2. 2 0
      packages/signals/src/lib.rs
  3. 6 0
      packages/signals/src/signal.rs

+ 107 - 0
packages/signals/src/comparer.rs

@@ -0,0 +1,107 @@
+use std::hash::Hash;
+
+use dioxus_core::prelude::*;
+
+use crate::{CopyValue, Effect, ReadOnlySignal, Signal};
+use rustc_hash::FxHashMap;
+
+/// An object that can efficiently compare a value to a set of values.
+#[derive(Debug)]
+pub struct Comparer<R: 'static> {
+    subscribers: CopyValue<FxHashMap<R, Signal<bool>>>,
+}
+
+impl<R: Eq + Hash> Comparer<R> {
+    /// Returns a signal which is true when the value is equal to the value passed to this function.
+    pub fn equal(&self, value: R) -> ReadOnlySignal<bool> {
+        let subscribers = self.subscribers.read();
+
+        match subscribers.get(&value) {
+            Some(&signal) => signal.into(),
+            None => {
+                drop(subscribers);
+                let mut subscribers = self.subscribers.write();
+                let signal = Signal::new(false);
+                subscribers.insert(value, signal);
+                signal.into()
+            }
+        }
+    }
+}
+
+impl<R> Clone for Comparer<R> {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl<R> Copy for Comparer<R> {}
+
+/// 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_selector`]. 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(cx: Scope) -> 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(cx: Scope, active: ReadOnlySignal<bool>) -> Element {
+///     if *active() {
+///         render! { "Active" }
+///     } else {
+///         render! { "Inactive" }
+///     }
+/// }
+/// ```
+#[must_use]
+pub fn use_comparer<R: Eq + Hash>(cx: &ScopeState, f: impl FnMut() -> R + 'static) -> Comparer<R> {
+    *cx.use_hook(move || comparer(f))
+}
+
+/// 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_selector`]. 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 comparer<R: Eq + Hash>(mut f: impl FnMut() -> R + 'static) -> Comparer<R> {
+    let subscribers: CopyValue<FxHashMap<R, Signal<bool>>> = CopyValue::new(FxHashMap::default());
+    let 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(value) = subscribers.get(&previous) {
+                value.set(false);
+            }
+        }
+
+        let current = f();
+
+        if let Some(value) = subscribers.get(&current) {
+            value.set(true);
+        }
+
+        *previous = Some(current);
+    });
+
+    Comparer { subscribers }
+}

+ 2 - 0
packages/signals/src/lib.rs

@@ -18,3 +18,5 @@ pub use dependency::*;
 mod map;
 pub use generational_box::{Storage, SyncStorage, UnsyncStorage};
 pub use map::*;
+mod comparer;
+pub use comparer::*;

+ 6 - 0
packages/signals/src/signal.rs

@@ -536,6 +536,12 @@ pub struct ReadOnlySignal<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage>
     inner: Signal<T, S>,
 }
 
+impl<T: 'static> From<Signal<T>> for ReadOnlySignal<T> {
+    fn from(inner: Signal<T>) -> Self {
+        Self { inner }
+    }
+}
+
 impl<T: 'static> ReadOnlySignal<T> {
     /// Create a new read-only signal.
     #[track_caller]