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>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-08-05 11:30:21 -07:00
1 parent 5591070e92
commit 58c41f8561
57 files changed
+4958 -436

No files matched your search

+30 -19
View File
@@ -3,7 +3,9 @@
use dioxus_core::Subscribers;
use dioxus_core::{current_owner, current_scope_id, ScopeId};
use generational_box::{BorrowResult, GenerationalBox, GenerationalBoxId, Storage, UnsyncStorage};
use generational_box::{
AnyStorage, BorrowResult, GenerationalBox, GenerationalBoxId, Storage, UnsyncStorage,
};
use std::ops::Deref;
use crate::read_impls;
@@ -18,15 +20,15 @@ use crate::{default_impl, write_impls, WritableExt};
/// CopyValue is a wrapper around a value to make the value mutable and Copy.
///
/// It is internally backed by [`generational_box::GenerationalBox`].
pub struct CopyValue<T: 'static, S: Storage<T> = UnsyncStorage> {
pub struct CopyValue<T, S: 'static = UnsyncStorage> {
pub(crate) value: GenerationalBox<T, S>,
pub(crate) origin_scope: ScopeId,
}
#[cfg(feature = "serialize")]
impl<T: 'static, Store: Storage<T>> serde::Serialize for CopyValue<T, Store>
impl<T, Store: Storage<T>> serde::Serialize for CopyValue<T, Store>
where
T: serde::Serialize,
T: serde::Serialize + 'static,
{
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.value.read().serialize(serializer)
@@ -34,9 +36,9 @@ where
}
#[cfg(feature = "serialize")]
impl<'de, T: 'static, Store: Storage<T>> serde::Deserialize<'de> for CopyValue<T, Store>
impl<'de, T, Store: Storage<T>> serde::Deserialize<'de> for CopyValue<T, Store>
where
T: serde::Deserialize<'de>,
T: serde::Deserialize<'de> + 'static,
{
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = T::deserialize(deserializer)?;
@@ -61,17 +63,23 @@ impl<T: 'static> CopyValue<T> {
}
}
impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
impl<T, S: Storage<T>> CopyValue<T, S> {
/// Create a new CopyValue. The value will be stored in the current component.
///
/// Once the component this value is created in is dropped, the value will be dropped.
#[track_caller]
pub fn new_maybe_sync(value: T) -> Self {
pub fn new_maybe_sync(value: T) -> Self
where
T: 'static,
{
Self::new_with_caller(value, std::panic::Location::caller())
}
/// Create a new CopyValue without an owner. This will leak memory if you don't manually drop it.
pub fn leak_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self {
pub fn leak_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self
where
T: 'static,
{
Self {
value: GenerationalBox::leak(value, caller),
origin_scope: current_scope_id().expect("in a virtual dom"),
@@ -117,7 +125,10 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
}
/// Manually drop the value in the CopyValue, invalidating the value in the process.
pub fn manually_drop(&self) {
pub fn manually_drop(&self)
where
T: 'static,
{
self.value.manually_drop()
}
@@ -137,7 +148,7 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
}
}
impl<T: 'static, S: Storage<T>> Readable for CopyValue<T, S> {
impl<T, S: Storage<T>> Readable for CopyValue<T, S> {
type Target = T;
type Storage = S;
@@ -155,12 +166,12 @@ impl<T: 'static, S: Storage<T>> Readable for CopyValue<T, S> {
self.value.try_read()
}
fn subscribers(&self) -> Option<Subscribers> {
None
fn subscribers(&self) -> Subscribers {
Subscribers::new_noop()
}
}
impl<T: 'static, S: Storage<T>> Writable for CopyValue<T, S> {
impl<T, S: Storage<T>> Writable for CopyValue<T, S> {
type WriteMetadata = ();
#[track_caller]
@@ -172,14 +183,14 @@ impl<T: 'static, S: Storage<T>> Writable for CopyValue<T, S> {
}
}
impl<T: 'static, S: Storage<T>> PartialEq for CopyValue<T, S> {
impl<T, S: AnyStorage> PartialEq for CopyValue<T, S> {
fn eq(&self, other: &Self) -> bool {
self.value.ptr_eq(&other.value)
}
}
impl<T: 'static, S: Storage<T>> Eq for CopyValue<T, S> {}
impl<T, S: AnyStorage> Eq for CopyValue<T, S> {}
impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
impl<T: Copy + 'static, S: Storage<T>> Deref for CopyValue<T, S> {
type Target = dyn Fn() -> T;
fn deref(&self) -> &Self::Target {
@@ -187,13 +198,13 @@ impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
}
}
impl<T, S: Storage<T>> Clone for CopyValue<T, S> {
impl<T, S> Clone for CopyValue<T, S> {
fn clone(&self) -> Self {
*self
}
}
impl<T, S: Storage<T>> Copy for CopyValue<T, S> {}
impl<T, S> Copy for CopyValue<T, S> {}
read_impls!(CopyValue<T, S: Storage<T>>);
default_impl!(CopyValue<T, S: Storage<T>>);