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

+31 -17
View File
@@ -1,4 +1,3 @@
use crate::write::Writable;
use crate::CopyValue;
use crate::{read::Readable, ReadableRef, Signal};
use crate::{read_impls, GlobalMemo, ReadableExt, WritableExt};
@@ -24,17 +23,17 @@ struct UpdateInformation<T> {
#[doc(alias = "Selector")]
#[doc(alias = "UseMemo")]
#[doc(alias = "Memorize")]
pub struct Memo<T: 'static> {
pub struct Memo<T> {
inner: Signal<T>,
update: CopyValue<UpdateInformation<T>>,
}
impl<T: 'static> Memo<T> {
impl<T> Memo<T> {
/// Create a new memo
#[track_caller]
pub fn new(f: impl FnMut() -> T + 'static) -> Self
where
T: PartialEq,
T: PartialEq + 'static,
{
Self::new_with_location(f, std::panic::Location::caller())
}
@@ -45,7 +44,7 @@ impl<T: 'static> Memo<T> {
location: &'static std::panic::Location<'static>,
) -> Self
where
T: PartialEq,
T: PartialEq + 'static,
{
let dirty = Arc::new(AtomicBool::new(false));
let (tx, mut rx) = futures_channel::mpsc::unbounded();
@@ -114,7 +113,7 @@ impl<T: 'static> Memo<T> {
#[track_caller]
pub const fn global(constructor: fn() -> T) -> GlobalMemo<T>
where
T: PartialEq,
T: PartialEq + 'static,
{
GlobalMemo::new(constructor)
}
@@ -123,7 +122,7 @@ impl<T: 'static> Memo<T> {
#[tracing::instrument(skip(self))]
fn recompute(&self)
where
T: PartialEq,
T: PartialEq + 'static,
{
let mut update_copy = self.update;
let update_write = update_copy.write();
@@ -141,12 +140,18 @@ impl<T: 'static> Memo<T> {
}
/// Get the scope that the signal was created in.
pub fn origin_scope(&self) -> ScopeId {
pub fn origin_scope(&self) -> ScopeId
where
T: 'static,
{
self.inner.origin_scope()
}
/// Get the id of the signal.
pub fn id(&self) -> generational_box::GenerationalBoxId {
pub fn id(&self) -> generational_box::GenerationalBoxId
where
T: 'static,
{
self.inner.id()
}
}
@@ -161,7 +166,10 @@ where
#[track_caller]
fn try_read_unchecked(
&self,
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
where
T: 'static,
{
// Read the inner generational box instead of the signal so we have more fine grained control over exactly when the subscription happens
let read = self.inner.inner.try_read_unchecked()?;
@@ -192,18 +200,24 @@ where
///
/// If the signal has been dropped, this will panic.
#[track_caller]
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>
where
T: 'static,
{
self.inner.try_peek_unchecked()
}
fn subscribers(&self) -> Option<Subscribers> {
fn subscribers(&self) -> Subscribers
where
T: 'static,
{
self.inner.subscribers()
}
}
impl<T> IntoAttributeValue for Memo<T>
where
T: Clone + IntoAttributeValue + PartialEq,
T: Clone + IntoAttributeValue + PartialEq + 'static,
{
fn into_value(self) -> dioxus_core::AttributeValue {
self.with(|f| f.clone().into_value())
@@ -212,7 +226,7 @@ where
impl<T> IntoDynNode for Memo<T>
where
T: Clone + IntoDynNode + PartialEq,
T: Clone + IntoDynNode + PartialEq + 'static,
{
fn into_dyn_node(self) -> dioxus_core::DynamicNode {
self().into_dyn_node()
@@ -227,7 +241,7 @@ impl<T: 'static> PartialEq for Memo<T> {
impl<T: Clone> Deref for Memo<T>
where
T: PartialEq,
T: PartialEq + 'static,
{
type Target = dyn Fn() -> T;
@@ -238,10 +252,10 @@ where
read_impls!(Memo<T> where T: PartialEq);
impl<T: 'static> Clone for Memo<T> {
impl<T> Clone for Memo<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: 'static> Copy for Memo<T> {}
impl<T> Copy for Memo<T> {}