comparer.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. use std::hash::Hash;
  2. use dioxus_core::prelude::*;
  3. use generational_box::{Storage, UnsyncStorage};
  4. use crate::{CopyValue, Effect, ReadOnlySignal, Signal, SignalData};
  5. use rustc_hash::FxHashMap;
  6. /// An object that can efficiently compare a value to a set of values.
  7. #[derive(Debug)]
  8. pub struct Comparer<R: 'static, S: Storage<SignalData<bool>> = UnsyncStorage> {
  9. subscribers: CopyValue<FxHashMap<R, Signal<bool, S>>>,
  10. }
  11. impl<R: Eq + Hash> Comparer<R> {
  12. /// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values.
  13. ///
  14. /// 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.
  15. pub fn new(mut f: impl FnMut() -> R + 'static) -> Comparer<R> {
  16. let subscribers: CopyValue<FxHashMap<R, Signal<bool>>> =
  17. CopyValue::new(FxHashMap::default());
  18. let previous = CopyValue::new(None);
  19. Effect::new(move || {
  20. let subscribers = subscribers.read();
  21. let mut previous = previous.write();
  22. if let Some(previous) = previous.take() {
  23. if let Some(value) = subscribers.get(&previous) {
  24. value.set(false);
  25. }
  26. }
  27. let current = f();
  28. if let Some(value) = subscribers.get(&current) {
  29. value.set(true);
  30. }
  31. *previous = Some(current);
  32. });
  33. Comparer { subscribers }
  34. }
  35. }
  36. impl<R: Eq + Hash, S: Storage<SignalData<bool>>> Comparer<R, S> {
  37. /// 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.
  38. ///
  39. /// 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.
  40. pub fn new_maybe_sync(mut f: impl FnMut() -> R + 'static) -> Comparer<R> {
  41. let subscribers: CopyValue<FxHashMap<R, Signal<bool>>> =
  42. CopyValue::new(FxHashMap::default());
  43. let previous = CopyValue::new(None);
  44. Effect::new(move || {
  45. let subscribers = subscribers.read();
  46. let mut previous = previous.write();
  47. if let Some(previous) = previous.take() {
  48. if let Some(value) = subscribers.get(&previous) {
  49. value.set(false);
  50. }
  51. }
  52. let current = f();
  53. if let Some(value) = subscribers.get(&current) {
  54. value.set(true);
  55. }
  56. *previous = Some(current);
  57. });
  58. Comparer { subscribers }
  59. }
  60. /// Returns a signal which is true when the value is equal to the value passed to this function.
  61. pub fn equal(&self, value: R) -> ReadOnlySignal<bool, S> {
  62. let subscribers = self.subscribers.read();
  63. match subscribers.get(&value) {
  64. Some(&signal) => signal.into(),
  65. None => {
  66. drop(subscribers);
  67. let mut subscribers = self.subscribers.write();
  68. let signal = Signal::new_maybe_sync(false);
  69. subscribers.insert(value, signal);
  70. signal.into()
  71. }
  72. }
  73. }
  74. }
  75. impl<R, S: Storage<SignalData<bool>>> Clone for Comparer<R, S> {
  76. fn clone(&self) -> Self {
  77. *self
  78. }
  79. }
  80. impl<R, S: Storage<SignalData<bool>>> Copy for Comparer<R, S> {}
  81. /// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values.
  82. ///
  83. /// 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.
  84. ///
  85. /// ```rust
  86. /// use dioxus::prelude::*;
  87. /// use dioxus_signals::*;
  88. ///
  89. /// fn App(cx: Scope) -> Element {
  90. /// let mut count = use_signal(cx, || 0);
  91. /// let comparer = use_comparer(cx, move || count.value());
  92. ///
  93. /// render! {
  94. /// for i in 0..10 {
  95. /// // Child will only re-render when i == count
  96. /// Child { active: comparer.equal(i) }
  97. /// }
  98. /// button {
  99. /// // This will only rerender the child with the old and new value of i == count
  100. /// // Because we are using a comparer, this will be O(1) instead of the O(n) performance of a selector
  101. /// onclick: move |_| count += 1,
  102. /// "Increment"
  103. /// }
  104. /// }
  105. /// }
  106. ///
  107. /// #[component]
  108. /// fn Child(cx: Scope, active: ReadOnlySignal<bool>) -> Element {
  109. /// if *active() {
  110. /// render! { "Active" }
  111. /// } else {
  112. /// render! { "Inactive" }
  113. /// }
  114. /// }
  115. /// ```
  116. #[must_use]
  117. pub fn use_comparer<R: Eq + Hash>(cx: &ScopeState, f: impl FnMut() -> R + 'static) -> Comparer<R> {
  118. *cx.use_hook(move || Comparer::new(f))
  119. }