Files
dioxus/packages/signals/src/set_compare.rs
T
Evan AlmloffandJonathan Kelley 58c41f8561 Stores: Externally tracked state and reactive collections (#4483)
* map signals mutably

* boxed signals

* remove mutable borrow type from boxed writable

* improve map mut example

* restore boxed_mut helper

* make boxed readable/writable types copy

* Move away from the ReadOnlySignal alias in examples

* automatically convert Write

* Fix some tests

* ReadSignal and WriteSignal

* move read only signal alias

* make maps copy

* store prototype

* implement derive macro

* Handle generics in the derive macro

* Make Selector boxable

* derive boxed conversion

* Get rid of store type

* Remove hardcoded type from derive macro

* selector -> store

* Refactor store implementation

* more vec methods

* todo mvc store example

* Implement Storable for HashMap

* fix write

* restore sorting logic in todomvc

* derive readable and writable

* improve todomvc store example

* Result store

* Option store

* collect dead subscribers

* fix clippy and formatting

* recognize some common foreign types automatically

* fix foreign stores in props

* slice and array selectors

* clean up store example

* writable is child owned

* box selector

* read and write to builtins

* re-use the proc macro in the derive macro

* Switch to a extension trait based approach

* switch to extension traits in the derive macro

* transposed method

* derive enums

* Expose Store::new

* remove stores from the prelude

* More consistent naming scheme for SelectorScope methods

* document hashmap methods

* document index extension

* document option ext

* document result ext

* document slice extension

* document the vec extension

* make more methods borrow the store mutably

* Move to impls to have higher priority over the builtin write extension traits

* fix deadlock

* finish stores documentation

* Document the store macro

* cleanup docs and organization

* finish derive docs

* fix typo

* fix clippy

* fix doc tests

* fix derive macro with generics

* fix clippy

* test and fix empty structs

* clean up todomvc store example

* default to a boxed store writer to make it easier to type when providing to the context api

* fix cargo doc

* fix clippy in store example

* relax generational box lifetime bounds

* relax bounds

* relax signal bounds

* fix compilation

* read store

* relax bounds on default store impls

* fix enum derive

* Convert to readsignal and writesignal automatically

* simplify type matching logic

* add write store

* unsized stores

* shrink the path vec type

* use more concrete types

* remove opaque types

* remove copy bounds

* use lens as generic name everywhere

* more consistent borrow semantics

* use a concrete type for the signal metadata and mention references in the map docs

* improve todomvc store docs

* document store derive macro internals

* refactor macro

* improve selector node documentation

* fix clippy

* add btreemap

* fix doc tests

* more option methods

* fix doc

* fix clippy

* add a bunch more result methods

* add a bunch of bounds to the iterators

* fix clippy

* remove option from subscribers

* add store macro to generate extension traits

* fix store example

* fix deadlock

* fix docs

* fix doc tests

* fix clippy

* add stores to public api

* add store macro, and then update todomvc_store example

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
2025-08-05 11:30:21 -07:00

100 lines
3.6 KiB
Rust

use crate::{write::WritableExt, ReadableExt};
use std::hash::Hash;
use dioxus_core::ReactiveContext;
use futures_util::StreamExt;
use generational_box::{Storage, UnsyncStorage};
use crate::{CopyValue, ReadSignal, Signal, SignalData};
use rustc_hash::FxHashMap;
/// An object that can efficiently compare a value to a set of values.
pub struct SetCompare<R, S: 'static = UnsyncStorage> {
subscribers: CopyValue<FxHashMap<R, Signal<bool, S>>>,
}
impl<R: Eq + Hash + 'static> SetCompare<R> {
/// 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::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<R> {
Self::new_maybe_sync(f)
}
}
impl<R: Eq + Hash + 'static, S: Storage<SignalData<bool>> + 'static> SetCompare<R, S> {
/// 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::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<R, S> {
let subscribers: CopyValue<FxHashMap<R, Signal<bool, S>>> =
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(&current).cloned() {
*value.write() = true;
}
*previous = Some(current);
};
let (rc, mut changed) = ReactiveContext::new();
dioxus_core::spawn(async move {
loop {
// Recompute the value
rc.reset_and_run_in(&mut recompute);
// Wait for context to change
let _ = changed.next().await;
}
});
SetCompare { subscribers }
}
}
impl<R: Eq + Hash + 'static> SetCompare<R> {
/// 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) -> ReadSignal<bool> {
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<R, S: Storage<SignalData<bool>>> PartialEq for SetCompare<R, S> {
fn eq(&self, other: &Self) -> bool {
self.subscribers == other.subscribers
}
}
impl<R, S: Storage<SignalData<bool>>> Clone for SetCompare<R, S> {
fn clone(&self) -> Self {
*self
}
}
impl<R, S: Storage<SignalData<bool>>> Copy for SetCompare<R, S> {}