From d7b2f9d05b8a7451c729a7f11139c3b11ba82f0c Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 13 Sep 2024 17:21:09 -0400 Subject: [PATCH] feat: keyed store fields, with keyed iteration --- examples/stores/index.html | 7 +- examples/stores/src/lib.rs | 15 +- reactive_graph/src/lib.rs | 3 +- reactive_stores/src/arc_field.rs | 56 ++- reactive_stores/src/field.rs | 31 +- reactive_stores/src/iter.rs | 46 +- reactive_stores/src/keyed.rs | 697 +++++++++++++++++++++++++++++ reactive_stores/src/lib.rs | 92 +++- reactive_stores/src/store_field.rs | 23 +- reactive_stores/src/subfield.rs | 25 +- reactive_stores_macro/src/lib.rs | 29 +- 11 files changed, 964 insertions(+), 60 deletions(-) create mode 100644 reactive_stores/src/keyed.rs diff --git a/examples/stores/index.html b/examples/stores/index.html index 75fa1f12a..f248365c5 100644 --- a/examples/stores/index.html +++ b/examples/stores/index.html @@ -3,6 +3,11 @@ + - \ No newline at end of file + diff --git a/examples/stores/src/lib.rs b/examples/stores/src/lib.rs index 61a7c9790..f4d74973e 100644 --- a/examples/stores/src/lib.rs +++ b/examples/stores/src/lib.rs @@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use chrono::{Local, NaiveDate}; use leptos::prelude::*; -use reactive_stores::{Field, Store, StoreFieldIterator}; +use reactive_stores::{Field, Store}; use reactive_stores_macro::Store; use serde::{Deserialize, Serialize}; @@ -12,6 +12,7 @@ static NEXT_ID: AtomicUsize = AtomicUsize::new(3); #[derive(Debug, Store, Serialize, Deserialize)] struct Todos { user: String, + #[store(key: usize = |todo| todo.id)] todos: Vec, } @@ -94,7 +95,7 @@ pub fn App() -> impl IntoView {
    - + @@ -111,7 +112,7 @@ fn TodoRow( let status = todo.status(); let title = todo.label(); - let editing = RwSignal::new(false); + let editing = RwSignal::new(true); view! {
  1. + ArcTrigger + Send + Sync>, read: Arc Option> + Send + Sync>, write: Arc Option> + Send + Sync>, + keys: Arc Option + Send + Sync>, } pub struct StoreFieldReader(Box>); @@ -98,6 +101,10 @@ impl StoreField for ArcField { writer.untrack(); Some(writer) } + + fn keys(&self) -> Option { + (self.keys)() + } } impl From> for ArcField @@ -126,6 +133,10 @@ where let value = value.clone(); move || value.writer().map(StoreFieldWriter::new) }), + keys: Arc::new({ + let value = value.clone(); + move || value.keys() + }), } } } @@ -156,6 +167,48 @@ where let value = value.clone(); move || value.writer().map(StoreFieldWriter::new) }), + keys: Arc::new({ + let value = value.clone(); + move || value.keys() + }), + } + } +} + +impl From> for ArcField +where + AtKeyed: Clone, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField + Send + Sync + 'static, + Prev: 'static, + T: IndexMut + 'static, + T::Output: Sized, +{ + #[track_caller] + fn from(value: AtKeyed) -> Self { + ArcField { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + path: value.path().into_iter().collect(), + trigger: value.get_trigger(value.path().into_iter().collect()), + get_trigger: Arc::new({ + let value = value.clone(); + move |path| value.get_trigger(path) + }), + read: Arc::new({ + let value = value.clone(); + move || value.reader().map(StoreFieldReader::new) + }), + write: Arc::new({ + let value = value.clone(); + move || value.writer().map(StoreFieldWriter::new) + }), + keys: Arc::new({ + let value = value.clone(); + move || value.keys() + }), } } } @@ -170,6 +223,7 @@ impl Clone for ArcField { get_trigger: Arc::clone(&self.get_trigger), read: Arc::clone(&self.read), write: Arc::clone(&self.write), + keys: Arc::clone(&self.keys), } } } diff --git a/reactive_stores/src/field.rs b/reactive_stores/src/field.rs index b0748a081..0f7d2ebaf 100644 --- a/reactive_stores/src/field.rs +++ b/reactive_stores/src/field.rs @@ -1,7 +1,7 @@ use crate::{ arc_field::{StoreFieldReader, StoreFieldWriter}, path::{StorePath, StorePathSegment}, - ArcField, AtIndex, StoreField, Subfield, + ArcField, AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, Subfield, }; use reactive_graph::{ owner::{Storage, StoredValue, SyncStorage}, @@ -9,7 +9,7 @@ use reactive_graph::{ traits::{DefinedAt, IsDisposed, ReadUntracked, Track, Trigger}, unwrap_signal, }; -use std::{ops::IndexMut, panic::Location}; +use std::{fmt::Debug, hash::Hash, ops::IndexMut, panic::Location}; pub struct Field where @@ -56,6 +56,10 @@ where .try_get_value() .and_then(|inner| inner.untracked_writer()) } + + fn keys(&self) -> Option { + self.inner.try_get_value().and_then(|n| n.keys()) + } } impl From> for Field @@ -94,6 +98,29 @@ where } } +impl From> + for Field +where + S: Storage>, + AtKeyed: Clone, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField + Send + Sync + 'static, + Prev: 'static, + T: IndexMut + 'static, + T::Output: Sized, +{ + #[track_caller] + fn from(value: AtKeyed) -> Self { + Field { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + inner: StoredValue::new_with_storage(value.into()), + } + } +} + impl Clone for Field { fn clone(&self) -> Self { *self diff --git a/reactive_stores/src/iter.rs b/reactive_stores/src/iter.rs index 0a4edfcfc..65b565b0a 100644 --- a/reactive_stores/src/iter.rs +++ b/reactive_stores/src/iter.rs @@ -1,6 +1,7 @@ use crate::{ path::{StorePath, StorePathSegment}, store_field::StoreField, + KeyMap, }; use reactive_graph::{ signal::{ @@ -20,10 +21,7 @@ use std::{ }; #[derive(Debug)] -pub struct AtIndex -where - Inner: StoreField, -{ +pub struct AtIndex { #[cfg(debug_assertions)] defined_at: &'static Location<'static>, inner: Inner, @@ -33,7 +31,7 @@ where impl Clone for AtIndex where - Inner: StoreField + Clone, + Inner: Clone, { fn clone(&self) -> Self { Self { @@ -46,15 +44,9 @@ where } } -impl Copy for AtIndex where - Inner: StoreField + Copy -{ -} +impl Copy for AtIndex where Inner: Copy {} -impl AtIndex -where - Inner: StoreField, -{ +impl AtIndex { #[track_caller] pub fn new(inner: Inner, index: usize) -> Self { Self { @@ -117,6 +109,11 @@ where guard.untrack(); Some(guard) } + + #[inline(always)] + fn keys(&self) -> Option { + self.inner.keys() + } } impl DefinedAt for AtIndex @@ -203,16 +200,27 @@ where } } -pub trait StoreFieldIterator: Sized { +pub trait StoreFieldIterator +where + Self: StoreField, +{ + fn at(self, index: usize) -> AtIndex; + fn iter(self) -> StoreFieldIter; } impl StoreFieldIterator for Inner where - Inner: StoreField, + Inner: StoreField + Clone, Prev::Output: Sized, Prev: IndexMut + AsRef<[Prev::Output]>, { + #[track_caller] + fn at(self, index: usize) -> AtIndex { + AtIndex::new(self.clone(), index) + } + + #[track_caller] fn iter(self) -> StoreFieldIter { // reactively track changes to this field let trigger = self.get_trigger(self.path().into_iter().collect()); @@ -248,13 +256,7 @@ where fn next(&mut self) -> Option { if self.idx < self.len { - let field = AtIndex { - #[cfg(debug_assertions)] - defined_at: Location::caller(), - index: self.idx, - inner: self.inner.clone(), - ty: PhantomData, - }; + let field = AtIndex::new(self.inner.clone(), self.idx); self.idx += 1; Some(field) } else { diff --git a/reactive_stores/src/keyed.rs b/reactive_stores/src/keyed.rs new file mode 100644 index 000000000..0a092469a --- /dev/null +++ b/reactive_stores/src/keyed.rs @@ -0,0 +1,697 @@ +use crate::{ + path::{StorePath, StorePathSegment}, + store_field::StoreField, + KeyMap, +}; +use reactive_graph::{ + signal::{ + guards::{Mapped, MappedMut, MappedMutArc, WriteGuard}, + ArcTrigger, + }, + traits::{ + DefinedAt, IsDisposed, ReadUntracked, Track, Trigger, UntrackableGuard, + Writeable, + }, +}; +use std::{ + collections::VecDeque, + fmt::Debug, + hash::Hash, + iter, + ops::{Deref, DerefMut, IndexMut}, + panic::Location, +}; + +#[derive(Debug)] +pub struct KeyedSubfield +where + for<'a> &'a T: IntoIterator, +{ + #[cfg(debug_assertions)] + defined_at: &'static Location<'static>, + path_segment: StorePathSegment, + inner: Inner, + read: fn(&Prev) -> &T, + write: fn(&mut Prev) -> &mut T, + key_fn: fn(<&T as IntoIterator>::Item) -> K, +} + +impl Clone for KeyedSubfield +where + for<'a> &'a T: IntoIterator, + Inner: Clone, +{ + fn clone(&self) -> Self { + Self { + #[cfg(debug_assertions)] + defined_at: self.defined_at, + path_segment: self.path_segment, + inner: self.inner.clone(), + read: self.read, + write: self.write, + key_fn: self.key_fn, + } + } +} + +impl Copy for KeyedSubfield +where + for<'a> &'a T: IntoIterator, + Inner: Copy, +{ +} + +impl KeyedSubfield +where + for<'a> &'a T: IntoIterator, +{ + #[track_caller] + pub fn new( + inner: Inner, + path_segment: StorePathSegment, + key_fn: fn(<&T as IntoIterator>::Item) -> K, + read: fn(&Prev) -> &T, + write: fn(&mut Prev) -> &mut T, + ) -> Self { + Self { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + inner, + path_segment, + read, + write, + key_fn, + } + } +} + +impl StoreField for KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + type Value = T; + type Reader = Mapped; + type Writer = KeyedSubfieldWriteGuard< + Inner, + Prev, + K, + T, + MappedMut, T>, + >; + type UntrackedWriter = + MappedMut, T>; + + fn path(&self) -> impl IntoIterator { + self.inner + .path() + .into_iter() + .chain(iter::once(self.path_segment)) + } + + fn get_trigger(&self, path: StorePath) -> ArcTrigger { + self.inner.get_trigger(path) + } + + fn reader(&self) -> Option { + let inner = self.inner.reader()?; + Some(Mapped::new_with_guard(inner, self.read)) + } + + fn writer(&self) -> Option { + let path = self.path().into_iter().collect::(); + let trigger = self.get_trigger(path.clone()); + let guard = WriteGuard::new(trigger, self.inner.writer()?); + let guard = MappedMut::new(guard, self.read, self.write); + Some(KeyedSubfieldWriteGuard { + inner: self.clone(), + guard: Some(guard), + }) + } + + fn untracked_writer(&self) -> Option { + let trigger = self.get_trigger(self.path().into_iter().collect()); + let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?); + Some(MappedMut::new(inner, self.read, self.write)) + } + + #[inline(always)] + fn keys(&self) -> Option { + self.inner.keys() + } +} + +impl KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + fn latest_keys(&self) -> Vec { + self.reader() + .expect("trying to update keys") + .deref() + .into_iter() + .map(|n| (self.key_fn)(n)) + .collect() + } +} + +pub struct KeyedSubfieldWriteGuard +where + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + inner: KeyedSubfield, + guard: Option, +} + +impl Deref + for KeyedSubfieldWriteGuard +where + Guard: Deref, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + type Target = Guard::Target; + + fn deref(&self) -> &Self::Target { + self.guard + .as_ref() + .expect("should be Some(_) until dropped") + .deref() + } +} + +impl DerefMut + for KeyedSubfieldWriteGuard +where + Guard: DerefMut, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.guard + .as_mut() + .expect("should be Some(_) until dropped") + .deref_mut() + } +} + +impl UntrackableGuard + for KeyedSubfieldWriteGuard +where + Guard: UntrackableGuard, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + fn untrack(&mut self) { + if let Some(inner) = self.guard.as_mut() { + inner.untrack(); + } + } +} + +impl Drop + for KeyedSubfieldWriteGuard +where + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + fn drop(&mut self) { + // dropping the inner guard will + // 1) synchronously release its write lock on the store's value + // 2) trigger an (asynchronous) reactive update + drop(self.guard.take()); + + // now that the write lock is release, we can get a read lock to refresh this keyed field + // based on the new value + self.inner.update_keys(); + + // reactive updates happen on the next tick + } +} + +impl DefinedAt for KeyedSubfield +where + for<'a> &'a T: IntoIterator, + Inner: StoreField, +{ + fn defined_at(&self) -> Option<&'static Location<'static>> { + #[cfg(debug_assertions)] + { + Some(self.defined_at) + } + #[cfg(not(debug_assertions))] + { + None + } + } +} + +impl IsDisposed for KeyedSubfield +where + for<'a> &'a T: IntoIterator, + Inner: IsDisposed, +{ + fn is_disposed(&self) -> bool { + self.inner.is_disposed() + } +} + +impl Trigger for KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + fn trigger(&self) { + let trigger = self.get_trigger(self.path().into_iter().collect()); + trigger.trigger(); + } +} + +impl Track for KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField + Track + 'static, + Prev: 'static, + T: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + fn track(&self) { + self.inner.track(); + let trigger = self.get_trigger(self.path().into_iter().collect()); + trigger.track(); + } +} + +impl ReadUntracked for KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + type Value = ::Reader; + + fn try_read_untracked(&self) -> Option { + self.reader() + } +} + +impl Writeable for KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + T: 'static, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + type Value = T; + + fn try_write(&self) -> Option> { + self.writer() + } + + fn try_write_untracked( + &self, + ) -> Option> { + self.writer().map(|mut writer| { + writer.untrack(); + writer + }) + } +} + +#[derive(Debug)] +pub struct AtKeyed +where + for<'a> &'a T: IntoIterator, +{ + #[cfg(debug_assertions)] + defined_at: &'static Location<'static>, + inner: KeyedSubfield, + key: K, +} + +impl Clone for AtKeyed +where + for<'a> &'a T: IntoIterator, + KeyedSubfield: Clone, + K: Debug + Clone, +{ + fn clone(&self) -> Self { + Self { + #[cfg(debug_assertions)] + defined_at: self.defined_at, + inner: self.inner.clone(), + key: self.key.clone(), + } + } +} + +impl Copy for AtKeyed +where + for<'a> &'a T: IntoIterator, + KeyedSubfield: Copy, + K: Debug + Copy, +{ +} + +impl AtKeyed +where + for<'a> &'a T: IntoIterator, +{ + #[track_caller] + pub fn new(inner: KeyedSubfield, key: K) -> Self { + Self { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + inner, + key, + } + } +} + +impl StoreField for AtKeyed +where + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + T: IndexMut, + T::Output: Sized, +{ + type Value = T::Output; + type Reader = MappedMutArc< + as StoreField>::Reader, + T::Output, + >; + type Writer = WriteGuard< + ArcTrigger, + MappedMutArc< + as StoreField>::Writer, + T::Output, + >, + >; + type UntrackedWriter = WriteGuard< + ArcTrigger, + MappedMutArc< + as StoreField>::Writer, + T::Output, + >, + >; + + fn path(&self) -> impl IntoIterator { + let inner = self.inner.path().into_iter().collect::(); + let keys = self + .inner + .keys() + .expect("using keys on a store with no keys"); + let this = keys + .with_field_keys( + inner.clone(), + |keys| keys.get(&self.key), + || self.inner.latest_keys(), + ) + .flatten() + .map(|(path, _)| path); + inner.into_iter().chain(this) + } + + fn get_trigger(&self, path: StorePath) -> ArcTrigger { + self.inner.get_trigger(path) + } + + fn reader(&self) -> Option { + let inner = self.inner.reader()?; + + let inner_path = self.inner.path().into_iter().collect(); + let keys = self + .inner + .keys() + .expect("using keys on a store with no keys"); + let index = keys + .with_field_keys( + inner_path, + |keys| keys.get(&self.key), + || self.inner.latest_keys(), + ) + .flatten() + .map(|(_, idx)| idx) + .expect("reading from a keyed field that has not yet been created"); + + Some(MappedMutArc::new( + inner, + move |n| &n[index], + move |n| &mut n[index], + )) + } + + fn writer(&self) -> Option { + let inner = self.inner.writer()?; + let trigger = self.get_trigger(self.path().into_iter().collect()); + + let inner_path = self.inner.path().into_iter().collect::(); + let keys = self + .inner + .keys() + .expect("using keys on a store with no keys"); + let index = keys + .with_field_keys( + inner_path.clone(), + |keys| keys.get(&self.key), + || self.inner.latest_keys(), + ) + .flatten() + .map(|(_, idx)| idx) + .expect("reading from a keyed field that has not yet been created"); + + Some(WriteGuard::new( + trigger, + MappedMutArc::new( + inner, + move |n| &n[index], + move |n| &mut n[index], + ), + )) + } + + fn untracked_writer(&self) -> Option { + let mut guard = self.writer()?; + guard.untrack(); + Some(guard) + } + + #[inline(always)] + fn keys(&self) -> Option { + self.inner.keys() + } +} + +impl DefinedAt for AtKeyed +where + for<'a> &'a T: IntoIterator, +{ + fn defined_at(&self) -> Option<&'static Location<'static>> { + #[cfg(debug_assertions)] + { + Some(self.defined_at) + } + #[cfg(not(debug_assertions))] + { + None + } + } +} + +impl IsDisposed for AtKeyed +where + for<'a> &'a T: IntoIterator, + Inner: IsDisposed, +{ + fn is_disposed(&self) -> bool { + self.inner.is_disposed() + } +} + +impl Trigger for AtKeyed +where + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + T: IndexMut, + T::Output: Sized, +{ + fn trigger(&self) { + let trigger = self.get_trigger(self.path().into_iter().collect()); + trigger.trigger(); + } +} + +impl Track for AtKeyed +where + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + T: IndexMut, + T::Output: Sized, +{ + fn track(&self) { + let trigger = self.get_trigger(self.path().into_iter().collect()); + trigger.track(); + } +} + +impl ReadUntracked for AtKeyed +where + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + T: IndexMut, + T::Output: Sized, +{ + type Value = ::Reader; + + fn try_read_untracked(&self) -> Option { + self.reader() + } +} + +impl Writeable for AtKeyed +where + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + KeyedSubfield: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + T: IndexMut, + T::Output: Sized + 'static, +{ + type Value = T::Output; + + fn try_write(&self) -> Option> { + self.writer() + } + + fn try_write_untracked( + &self, + ) -> Option> { + self.writer().map(|mut writer| { + writer.untrack(); + writer + }) + } +} + +impl KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, +{ + pub fn update_keys(&self) { + let inner_path = self.path().into_iter().collect(); + let keys = self + .inner + .keys() + .expect("updating keys on a store with no keys"); + keys.with_field_keys( + inner_path, + |keys| { + keys.update(self.latest_keys()); + }, + || self.latest_keys(), + ); + } +} + +impl KeyedSubfield +where + Self: Clone, + for<'a> &'a T: IntoIterator, + Inner: StoreField, + Prev: 'static, + K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static, + T: IndexMut, + T::Output: Sized, +{ + #[track_caller] + pub fn iter_keyed(self) -> StoreFieldKeyedIter { + // reactively track changes to this field + let trigger = self.get_trigger(self.path().into_iter().collect()); + trigger.track(); + + // get the current length of the field by accessing slice + let reader = self + .reader() + .expect("creating iterator from unavailable store field"); + let keys = reader + .into_iter() + .map(|item| (self.key_fn)(item)) + .collect::>(); + + // return the iterator + StoreFieldKeyedIter { inner: self, keys } + } +} + +pub struct StoreFieldKeyedIter +where + for<'a> &'a T: IntoIterator, + T: IndexMut, +{ + inner: KeyedSubfield, + keys: VecDeque, +} + +impl Iterator for StoreFieldKeyedIter +where + Inner: StoreField + Clone + 'static, + T: IndexMut + 'static, + T::Output: Sized + 'static, + for<'a> &'a T: IntoIterator, +{ + type Item = AtKeyed; + + fn next(&mut self) -> Option { + self.keys + .pop_front() + .map(|key| AtKeyed::new(self.inner.clone(), key)) + } +} diff --git a/reactive_stores/src/lib.rs b/reactive_stores/src/lib.rs index 9da9a090f..c54884031 100644 --- a/reactive_stores/src/lib.rs +++ b/reactive_stores/src/lib.rs @@ -1,3 +1,4 @@ +use or_poisoned::OrPoisoned; use reactive_graph::{ owner::{LocalStorage, Storage, StoredValue, SyncStorage}, signal::{ @@ -8,7 +9,10 @@ use reactive_graph::{ }; use rustc_hash::FxHashMap; use std::{ + any::Any, + collections::HashMap, fmt::Debug, + hash::Hash, panic::Location, sync::{Arc, RwLock}, }; @@ -16,6 +20,7 @@ use std::{ mod arc_field; mod field; mod iter; +mod keyed; mod option; mod patch; mod path; @@ -25,9 +30,10 @@ mod subfield; pub use arc_field::ArcField; pub use field::Field; pub use iter::*; +pub use keyed::*; pub use option::*; pub use patch::*; -use path::StorePath; +use path::{StorePath, StorePathSegment}; pub use store_field::{StoreField, Then}; pub use subfield::Subfield; @@ -51,11 +57,93 @@ impl TriggerMap { } } +pub struct FieldKeys { + current_key: usize, + keys: HashMap, +} + +impl FieldKeys +where + K: Debug + Hash + PartialEq + Eq, +{ + pub fn new(from_iter: impl IntoIterator) -> Self { + let mut current_key = 0; + let mut keys = HashMap::new(); + for (idx, key) in from_iter.into_iter().enumerate() { + let segment = current_key.into(); + keys.insert(key, (segment, idx)); + current_key += 1; + } + + Self { + current_key: 0, + keys, + } + } +} + +impl FieldKeys +where + K: Hash + PartialEq + Eq, +{ + pub fn get(&self, key: &K) -> Option<(StorePathSegment, usize)> { + self.keys.get(key).copied() + } + + pub fn update(&mut self, iter: impl IntoIterator) { + for (idx, key) in iter.into_iter().enumerate() { + if let Some((_, old_idx)) = self.keys.get_mut(&key) { + *old_idx = idx; + } else { + self.current_key += 1; + self.keys.insert( + key, + (StorePathSegment::from(self.current_key), idx), + ); + } + } + + // TODO: remove old keys and triggers, so it doesn't grow constantly + } +} + +impl Default for FieldKeys { + fn default() -> Self { + Self { + current_key: Default::default(), + keys: Default::default(), + } + } +} + +#[derive(Default, Clone)] +pub struct KeyMap(Arc>>>); + +impl KeyMap { + pub fn with_field_keys( + &self, + path: StorePath, + fun: impl FnOnce(&mut FieldKeys) -> T, + initialize: impl FnOnce() -> Vec, + ) -> Option + where + K: Debug + Hash + PartialEq + Eq + Send + Sync + 'static, + { + let mut guard = self.0.write().or_poisoned(); + let entry = guard + .entry(path) + .or_insert_with(|| Box::new(FieldKeys::new(initialize()))); + let entry = entry.downcast_mut::>()?; + Some(fun(entry)) + } +} + pub struct ArcStore { #[cfg(debug_assertions)] defined_at: &'static Location<'static>, pub(crate) value: Arc>, signals: Arc>, + keys: KeyMap, } impl ArcStore { @@ -65,6 +153,7 @@ impl ArcStore { defined_at: Location::caller(), value: Arc::new(RwLock::new(value)), signals: Default::default(), + keys: Default::default(), } } } @@ -87,6 +176,7 @@ impl Clone for ArcStore { defined_at: self.defined_at, value: Arc::clone(&self.value), signals: Arc::clone(&self.signals), + keys: self.keys.clone(), } } } diff --git a/reactive_stores/src/store_field.rs b/reactive_stores/src/store_field.rs index 62a234b93..10f3e330d 100644 --- a/reactive_stores/src/store_field.rs +++ b/reactive_stores/src/store_field.rs @@ -1,6 +1,6 @@ use crate::{ path::{StorePath, StorePathSegment}, - ArcStore, Store, + ArcStore, KeyMap, Store, }; use or_poisoned::OrPoisoned; use reactive_graph::{ @@ -32,12 +32,20 @@ pub trait StoreField: Sized { fn path(&self) -> impl IntoIterator; + fn track_field(&self) { + let path = self.path().into_iter().collect(); + let trigger = self.get_trigger(path); + trigger.track(); + } + fn reader(&self) -> Option; fn writer(&self) -> Option; fn untracked_writer(&self) -> Option; + fn keys(&self) -> Option; + #[track_caller] fn then( self, @@ -86,6 +94,10 @@ where fn untracked_writer(&self) -> Option { UntrackedWriteGuard::try_new(Arc::clone(&self.value)) } + + fn keys(&self) -> Option { + Some(self.keys.clone()) + } } impl StoreField for Store @@ -125,6 +137,10 @@ where .try_get_value() .and_then(|n| n.untracked_writer()) } + + fn keys(&self) -> Option { + self.inner.try_get_value().and_then(|inner| inner.keys()) + } } #[derive(Debug, Copy, Clone)] @@ -190,6 +206,11 @@ where let inner = self.inner.untracked_writer()?; Some(MappedMut::new(inner, self.map_fn, self.map_fn_mut)) } + + #[inline(always)] + fn keys(&self) -> Option { + self.inner.keys() + } } impl DefinedAt for Then diff --git a/reactive_stores/src/subfield.rs b/reactive_stores/src/subfield.rs index 01f56c46c..8b589816e 100644 --- a/reactive_stores/src/subfield.rs +++ b/reactive_stores/src/subfield.rs @@ -1,6 +1,7 @@ use crate::{ path::{StorePath, StorePathSegment}, store_field::StoreField, + KeyMap, }; use reactive_graph::{ signal::{ @@ -15,10 +16,7 @@ use reactive_graph::{ use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location}; #[derive(Debug)] -pub struct Subfield -where - Inner: StoreField, -{ +pub struct Subfield { #[cfg(debug_assertions)] defined_at: &'static Location<'static>, path_segment: StorePathSegment, @@ -30,7 +28,7 @@ where impl Clone for Subfield where - Inner: StoreField + Clone, + Inner: Clone, { fn clone(&self) -> Self { Self { @@ -45,15 +43,9 @@ where } } -impl Copy for Subfield where - Inner: StoreField + Copy -{ -} +impl Copy for Subfield where Inner: Copy {} -impl Subfield -where - Inner: StoreField, -{ +impl Subfield { #[track_caller] pub fn new( inner: Inner, @@ -111,6 +103,11 @@ where let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?); Some(MappedMut::new(inner, self.read, self.write)) } + + #[inline(always)] + fn keys(&self) -> Option { + self.inner.keys() + } } impl DefinedAt for Subfield @@ -131,7 +128,7 @@ where impl IsDisposed for Subfield where - Inner: StoreField + IsDisposed, + Inner: IsDisposed, { fn is_disposed(&self) -> bool { self.inner.is_disposed() diff --git a/reactive_stores_macro/src/lib.rs b/reactive_stores_macro/src/lib.rs index c247a7fae..6ad45b59e 100644 --- a/reactive_stores_macro/src/lib.rs +++ b/reactive_stores_macro/src/lib.rs @@ -7,7 +7,7 @@ use syn::{ punctuated::Punctuated, token::Comma, Field, Fields, Generics, Ident, Index, Meta, Result, Token, Type, Variant, - Visibility, WhereClause, + Visibility, WhereClause, ExprClosure, }; #[proc_macro_error] @@ -79,17 +79,17 @@ impl Parse for Model { #[derive(Clone)] enum SubfieldMode { - Keyed(Ident, Type), + Keyed(ExprClosure, Type), } impl Parse for SubfieldMode { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mode: Ident = input.parse()?; if mode == "key" { - let _eq: Token!(=) = input.parse()?; - let ident: Ident = input.parse()?; let _col: Token!(:) = input.parse()?; let ty: Type = input.parse()?; + let _eq: Token!(=) = input.parse()?; + let ident: ExprClosure = input.parse()?; Ok(SubfieldMode::Keyed(ident, ty)) } else { Err(input.error("expected `key = : `")) @@ -281,15 +281,20 @@ fn field_to_tokens( if modes.len() == 1 { let mode = &modes[0]; // Can replace with a match if additional modes added - // TODO keyed_by - let SubfieldMode::Keyed(_keyed_by, key_ty) = mode; + let SubfieldMode::Keyed(keyed_by, key_ty) = mode; let signature = quote! { - fn #ident(self) -> #library_path::KeyedField<#any_store_field, #name #generics, #ty, #key_ty> + fn #ident(self) -> #library_path::KeyedSubfield<#any_store_field, #name #generics, #key_ty, #ty> }; return if include_body { quote! { #signature { - todo!() + #library_path::KeyedSubfield::new( + self, + #idx.into(), + #keyed_by, + |prev| &prev.#locator, + |prev| &mut prev.#locator, + ) } } } else { @@ -474,7 +479,9 @@ fn variant_to_tokens( ); let ignore_before = (0..idx).map(|_| quote! { _, }); + let ignore_before2 = ignore_before.clone(); let ignore_after = (idx..number_of_fields.saturating_sub(1)).map(|_| quote !{_, }); + let ignore_after2 = ignore_after.clone(); // default subfield if include_body { @@ -497,7 +504,11 @@ fn variant_to_tokens( .expect("accessed an enum field that is no longer matched") }, |prev| { - todo!() + match prev { + #name::#orig_ident(#(#ignore_before2)* this, #(#ignore_after2)*) => Some(this), + _ => None, + } + .expect("accessed an enum field that is no longer matched") }, )) } else {