feat: keyed store fields, with keyed iteration

This commit is contained in:
Greg Johnston 2024-09-13 17:21:09 -04:00
parent 69c4090d32
commit d7b2f9d05b
11 changed files with 964 additions and 60 deletions

View file

@ -3,6 +3,11 @@
<head> <head>
<link data-trunk rel="rust" data-wasm-opt="z"/> <link data-trunk rel="rust" data-wasm-opt="z"/>
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/> <link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
<style>
.hidden {
display: none;
}
</style>
</head> </head>
<body></body> <body></body>
</html> </html>

View file

@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use chrono::{Local, NaiveDate}; use chrono::{Local, NaiveDate};
use leptos::prelude::*; use leptos::prelude::*;
use reactive_stores::{Field, Store, StoreFieldIterator}; use reactive_stores::{Field, Store};
use reactive_stores_macro::Store; use reactive_stores_macro::Store;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -12,6 +12,7 @@ static NEXT_ID: AtomicUsize = AtomicUsize::new(3);
#[derive(Debug, Store, Serialize, Deserialize)] #[derive(Debug, Store, Serialize, Deserialize)]
struct Todos { struct Todos {
user: String, user: String,
#[store(key: usize = |todo| todo.id)]
todos: Vec<Todo>, todos: Vec<Todo>,
} }
@ -94,7 +95,7 @@ pub fn App() -> impl IntoView {
<input type="submit"/> <input type="submit"/>
</form> </form>
<ol> <ol>
<For each=move || store.todos().iter() key=|row| row.id().get() let:todo> <For each=move || store.todos().iter_keyed() key=|row| row.id().get() let:todo>
<TodoRow store todo/> <TodoRow store todo/>
</For> </For>
@ -111,7 +112,7 @@ fn TodoRow(
let status = todo.status(); let status = todo.status();
let title = todo.label(); let title = todo.label();
let editing = RwSignal::new(false); let editing = RwSignal::new(true);
view! { view! {
<li style:text-decoration=move || { <li style:text-decoration=move || {
@ -133,12 +134,9 @@ fn TodoRow(
prop:value=move || title.get() prop:value=move || title.get()
on:change=move |ev| { on:change=move |ev| {
title.set(event_target_value(&ev)); title.set(event_target_value(&ev));
editing.set(false);
} }
on:blur=move |_| editing.set(false)
autofocus
/> />
<button on:click=move |_| { <button on:click=move |_| {
status.write().next_step() status.write().next_step()
}> }>
@ -155,10 +153,11 @@ fn TodoRow(
</button> </button>
<button on:click=move |_| { <button on:click=move |_| {
let id = todo.id().get();
store store
.todos() .todos()
.update(|todos| { .update(|todos| {
todos.remove(todo.id().get()); todos.remove(id);
}); });
}>"X"</button> }>"X"</button>
<input <input

View file

@ -99,7 +99,8 @@ pub mod prelude {
// TODO remove this, it's just useful while developing // TODO remove this, it's just useful while developing
#[allow(unused)] #[allow(unused)]
fn log_warning(text: Arguments) { #[doc(hidden)]
pub fn log_warning(text: Arguments) {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
{ {
tracing::warn!(text); tracing::warn!(text);

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
path::{StorePath, StorePathSegment}, path::{StorePath, StorePathSegment},
AtIndex, StoreField, Subfield, AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, Subfield,
}; };
use reactive_graph::{ use reactive_graph::{
signal::ArcTrigger, signal::ArcTrigger,
@ -9,6 +9,8 @@ use reactive_graph::{
}, },
}; };
use std::{ use std::{
fmt::Debug,
hash::Hash,
ops::{Deref, DerefMut, IndexMut}, ops::{Deref, DerefMut, IndexMut},
panic::Location, panic::Location,
sync::Arc, sync::Arc,
@ -25,6 +27,7 @@ where
get_trigger: Arc<dyn Fn(StorePath) -> ArcTrigger + Send + Sync>, get_trigger: Arc<dyn Fn(StorePath) -> ArcTrigger + Send + Sync>,
read: Arc<dyn Fn() -> Option<StoreFieldReader<T>> + Send + Sync>, read: Arc<dyn Fn() -> Option<StoreFieldReader<T>> + Send + Sync>,
write: Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>, write: Arc<dyn Fn() -> Option<StoreFieldWriter<T>> + Send + Sync>,
keys: Arc<dyn Fn() -> Option<KeyMap> + Send + Sync>,
} }
pub struct StoreFieldReader<T>(Box<dyn Deref<Target = T>>); pub struct StoreFieldReader<T>(Box<dyn Deref<Target = T>>);
@ -98,6 +101,10 @@ impl<T> StoreField for ArcField<T> {
writer.untrack(); writer.untrack();
Some(writer) Some(writer)
} }
fn keys(&self) -> Option<KeyMap> {
(self.keys)()
}
} }
impl<Inner, Prev, T> From<Subfield<Inner, Prev, T>> for ArcField<T> impl<Inner, Prev, T> From<Subfield<Inner, Prev, T>> for ArcField<T>
@ -126,6 +133,10 @@ where
let value = value.clone(); let value = value.clone();
move || value.writer().map(StoreFieldWriter::new) 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(); let value = value.clone();
move || value.writer().map(StoreFieldWriter::new) move || value.writer().map(StoreFieldWriter::new)
}), }),
keys: Arc::new({
let value = value.clone();
move || value.keys()
}),
}
}
}
impl<Inner, Prev, K, T> From<AtKeyed<Inner, Prev, K, T>> for ArcField<T::Output>
where
AtKeyed<Inner, Prev, K, T>: Clone,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev> + Send + Sync + 'static,
Prev: 'static,
T: IndexMut<usize> + 'static,
T::Output: Sized,
{
#[track_caller]
fn from(value: AtKeyed<Inner, Prev, K, T>) -> 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<T> Clone for ArcField<T> {
get_trigger: Arc::clone(&self.get_trigger), get_trigger: Arc::clone(&self.get_trigger),
read: Arc::clone(&self.read), read: Arc::clone(&self.read),
write: Arc::clone(&self.write), write: Arc::clone(&self.write),
keys: Arc::clone(&self.keys),
} }
} }
} }

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
arc_field::{StoreFieldReader, StoreFieldWriter}, arc_field::{StoreFieldReader, StoreFieldWriter},
path::{StorePath, StorePathSegment}, path::{StorePath, StorePathSegment},
ArcField, AtIndex, StoreField, Subfield, ArcField, AtIndex, AtKeyed, KeyMap, KeyedSubfield, StoreField, Subfield,
}; };
use reactive_graph::{ use reactive_graph::{
owner::{Storage, StoredValue, SyncStorage}, owner::{Storage, StoredValue, SyncStorage},
@ -9,7 +9,7 @@ use reactive_graph::{
traits::{DefinedAt, IsDisposed, ReadUntracked, Track, Trigger}, traits::{DefinedAt, IsDisposed, ReadUntracked, Track, Trigger},
unwrap_signal, unwrap_signal,
}; };
use std::{ops::IndexMut, panic::Location}; use std::{fmt::Debug, hash::Hash, ops::IndexMut, panic::Location};
pub struct Field<T, S = SyncStorage> pub struct Field<T, S = SyncStorage>
where where
@ -56,6 +56,10 @@ where
.try_get_value() .try_get_value()
.and_then(|inner| inner.untracked_writer()) .and_then(|inner| inner.untracked_writer())
} }
fn keys(&self) -> Option<KeyMap> {
self.inner.try_get_value().and_then(|n| n.keys())
}
} }
impl<Inner, Prev, T, S> From<Subfield<Inner, Prev, T>> for Field<T, S> impl<Inner, Prev, T, S> From<Subfield<Inner, Prev, T>> for Field<T, S>
@ -94,6 +98,29 @@ where
} }
} }
impl<Inner, Prev, K, T, S> From<AtKeyed<Inner, Prev, K, T>>
for Field<T::Output, S>
where
S: Storage<ArcField<T::Output>>,
AtKeyed<Inner, Prev, K, T>: Clone,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev> + Send + Sync + 'static,
Prev: 'static,
T: IndexMut<usize> + 'static,
T::Output: Sized,
{
#[track_caller]
fn from(value: AtKeyed<Inner, Prev, K, T>) -> Self {
Field {
#[cfg(debug_assertions)]
defined_at: Location::caller(),
inner: StoredValue::new_with_storage(value.into()),
}
}
}
impl<T, S> Clone for Field<T, S> { impl<T, S> Clone for Field<T, S> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
path::{StorePath, StorePathSegment}, path::{StorePath, StorePathSegment},
store_field::StoreField, store_field::StoreField,
KeyMap,
}; };
use reactive_graph::{ use reactive_graph::{
signal::{ signal::{
@ -20,10 +21,7 @@ use std::{
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct AtIndex<Inner, Prev> pub struct AtIndex<Inner, Prev> {
where
Inner: StoreField<Value = Prev>,
{
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
defined_at: &'static Location<'static>, defined_at: &'static Location<'static>,
inner: Inner, inner: Inner,
@ -33,7 +31,7 @@ where
impl<Inner, Prev> Clone for AtIndex<Inner, Prev> impl<Inner, Prev> Clone for AtIndex<Inner, Prev>
where where
Inner: StoreField<Value = Prev> + Clone, Inner: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
@ -46,15 +44,9 @@ where
} }
} }
impl<Inner, Prev> Copy for AtIndex<Inner, Prev> where impl<Inner, Prev> Copy for AtIndex<Inner, Prev> where Inner: Copy {}
Inner: StoreField<Value = Prev> + Copy
{
}
impl<Inner, Prev> AtIndex<Inner, Prev> impl<Inner, Prev> AtIndex<Inner, Prev> {
where
Inner: StoreField<Value = Prev>,
{
#[track_caller] #[track_caller]
pub fn new(inner: Inner, index: usize) -> Self { pub fn new(inner: Inner, index: usize) -> Self {
Self { Self {
@ -117,6 +109,11 @@ where
guard.untrack(); guard.untrack();
Some(guard) Some(guard)
} }
#[inline(always)]
fn keys(&self) -> Option<KeyMap> {
self.inner.keys()
}
} }
impl<Inner, Prev> DefinedAt for AtIndex<Inner, Prev> impl<Inner, Prev> DefinedAt for AtIndex<Inner, Prev>
@ -203,16 +200,27 @@ where
} }
} }
pub trait StoreFieldIterator<Prev>: Sized { pub trait StoreFieldIterator<Prev>
where
Self: StoreField<Value = Prev>,
{
fn at(self, index: usize) -> AtIndex<Self, Prev>;
fn iter(self) -> StoreFieldIter<Self, Prev>; fn iter(self) -> StoreFieldIter<Self, Prev>;
} }
impl<Inner, Prev> StoreFieldIterator<Prev> for Inner impl<Inner, Prev> StoreFieldIterator<Prev> for Inner
where where
Inner: StoreField<Value = Prev>, Inner: StoreField<Value = Prev> + Clone,
Prev::Output: Sized, Prev::Output: Sized,
Prev: IndexMut<usize> + AsRef<[Prev::Output]>, Prev: IndexMut<usize> + AsRef<[Prev::Output]>,
{ {
#[track_caller]
fn at(self, index: usize) -> AtIndex<Inner, Prev> {
AtIndex::new(self.clone(), index)
}
#[track_caller]
fn iter(self) -> StoreFieldIter<Inner, Prev> { fn iter(self) -> StoreFieldIter<Inner, Prev> {
// reactively track changes to this field // reactively track changes to this field
let trigger = self.get_trigger(self.path().into_iter().collect()); let trigger = self.get_trigger(self.path().into_iter().collect());
@ -248,13 +256,7 @@ where
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.idx < self.len { if self.idx < self.len {
let field = AtIndex { let field = AtIndex::new(self.inner.clone(), self.idx);
#[cfg(debug_assertions)]
defined_at: Location::caller(),
index: self.idx,
inner: self.inner.clone(),
ty: PhantomData,
};
self.idx += 1; self.idx += 1;
Some(field) Some(field)
} else { } else {

View file

@ -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<Inner, Prev, K, T>
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<Inner, Prev, K, T> Clone for KeyedSubfield<Inner, Prev, K, T>
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<Inner, Prev, K, T> Copy for KeyedSubfield<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
Inner: Copy,
{
}
impl<Inner, Prev, K, T> KeyedSubfield<Inner, Prev, K, T>
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<Inner, Prev, K, T> StoreField for KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
{
type Value = T;
type Reader = Mapped<Inner::Reader, T>;
type Writer = KeyedSubfieldWriteGuard<
Inner,
Prev,
K,
T,
MappedMut<WriteGuard<ArcTrigger, Inner::Writer>, T>,
>;
type UntrackedWriter =
MappedMut<WriteGuard<ArcTrigger, Inner::UntrackedWriter>, T>;
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
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<Self::Reader> {
let inner = self.inner.reader()?;
Some(Mapped::new_with_guard(inner, self.read))
}
fn writer(&self) -> Option<Self::Writer> {
let path = self.path().into_iter().collect::<StorePath>();
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<Self::UntrackedWriter> {
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<KeyMap> {
self.inner.keys()
}
}
impl<Inner, Prev, K, T> KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
{
fn latest_keys(&self) -> Vec<K> {
self.reader()
.expect("trying to update keys")
.deref()
.into_iter()
.map(|n| (self.key_fn)(n))
.collect()
}
}
pub struct KeyedSubfieldWriteGuard<Inner, Prev, K, T, Guard>
where
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
{
inner: KeyedSubfield<Inner, Prev, K, T>,
guard: Option<Guard>,
}
impl<Inner, Prev, K, T, Guard> Deref
for KeyedSubfieldWriteGuard<Inner, Prev, K, T, Guard>
where
Guard: Deref,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
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<Inner, Prev, K, T, Guard> DerefMut
for KeyedSubfieldWriteGuard<Inner, Prev, K, T, Guard>
where
Guard: DerefMut,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
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<Inner, Prev, K, T, Guard> UntrackableGuard
for KeyedSubfieldWriteGuard<Inner, Prev, K, T, Guard>
where
Guard: UntrackableGuard,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
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<Inner, Prev, K, T, Guard> Drop
for KeyedSubfieldWriteGuard<Inner, Prev, K, T, Guard>
where
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
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<Inner, Prev, K, T> DefinedAt for KeyedSubfield<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
{
fn defined_at(&self) -> Option<&'static Location<'static>> {
#[cfg(debug_assertions)]
{
Some(self.defined_at)
}
#[cfg(not(debug_assertions))]
{
None
}
}
}
impl<Inner, Prev, K, T> IsDisposed for KeyedSubfield<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
Inner: IsDisposed,
{
fn is_disposed(&self) -> bool {
self.inner.is_disposed()
}
}
impl<Inner, Prev, K, T> Trigger for KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
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<Inner, Prev, K, T> Track for KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev> + 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<Inner, Prev, K, T> ReadUntracked for KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
{
type Value = <Self as StoreField>::Reader;
fn try_read_untracked(&self) -> Option<Self::Value> {
self.reader()
}
}
impl<Inner, Prev, K, T> Writeable for KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
T: 'static,
Inner: StoreField<Value = Prev>,
Prev: 'static,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
{
type Value = T;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
self.writer()
}
fn try_write_untracked(
&self,
) -> Option<impl DerefMut<Target = Self::Value>> {
self.writer().map(|mut writer| {
writer.untrack();
writer
})
}
}
#[derive(Debug)]
pub struct AtKeyed<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
{
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
inner: KeyedSubfield<Inner, Prev, K, T>,
key: K,
}
impl<Inner, Prev, K, T> Clone for AtKeyed<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
KeyedSubfield<Inner, Prev, K, T>: 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<Inner, Prev, K, T> Copy for AtKeyed<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
KeyedSubfield<Inner, Prev, K, T>: Copy,
K: Debug + Copy,
{
}
impl<Inner, Prev, K, T> AtKeyed<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
{
#[track_caller]
pub fn new(inner: KeyedSubfield<Inner, Prev, K, T>, key: K) -> Self {
Self {
#[cfg(debug_assertions)]
defined_at: Location::caller(),
inner,
key,
}
}
}
impl<Inner, Prev, K, T> StoreField for AtKeyed<Inner, Prev, K, T>
where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
T: IndexMut<usize>,
T::Output: Sized,
{
type Value = T::Output;
type Reader = MappedMutArc<
<KeyedSubfield<Inner, Prev, K, T> as StoreField>::Reader,
T::Output,
>;
type Writer = WriteGuard<
ArcTrigger,
MappedMutArc<
<KeyedSubfield<Inner, Prev, K, T> as StoreField>::Writer,
T::Output,
>,
>;
type UntrackedWriter = WriteGuard<
ArcTrigger,
MappedMutArc<
<KeyedSubfield<Inner, Prev, K, T> as StoreField>::Writer,
T::Output,
>,
>;
fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
let inner = self.inner.path().into_iter().collect::<StorePath>();
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<Self::Reader> {
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<Self::Writer> {
let inner = self.inner.writer()?;
let trigger = self.get_trigger(self.path().into_iter().collect());
let inner_path = self.inner.path().into_iter().collect::<StorePath>();
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<Self::UntrackedWriter> {
let mut guard = self.writer()?;
guard.untrack();
Some(guard)
}
#[inline(always)]
fn keys(&self) -> Option<KeyMap> {
self.inner.keys()
}
}
impl<Inner, Prev, K, T> DefinedAt for AtKeyed<Inner, Prev, K, T>
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<Inner, Prev, K, T> IsDisposed for AtKeyed<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
Inner: IsDisposed,
{
fn is_disposed(&self) -> bool {
self.inner.is_disposed()
}
}
impl<Inner, Prev, K, T> Trigger for AtKeyed<Inner, Prev, K, T>
where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
T: IndexMut<usize>,
T::Output: Sized,
{
fn trigger(&self) {
let trigger = self.get_trigger(self.path().into_iter().collect());
trigger.trigger();
}
}
impl<Inner, Prev, K, T> Track for AtKeyed<Inner, Prev, K, T>
where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
T: IndexMut<usize>,
T::Output: Sized,
{
fn track(&self) {
let trigger = self.get_trigger(self.path().into_iter().collect());
trigger.track();
}
}
impl<Inner, Prev, K, T> ReadUntracked for AtKeyed<Inner, Prev, K, T>
where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
T: IndexMut<usize>,
T::Output: Sized,
{
type Value = <Self as StoreField>::Reader;
fn try_read_untracked(&self) -> Option<Self::Value> {
self.reader()
}
}
impl<Inner, Prev, K, T> Writeable for AtKeyed<Inner, Prev, K, T>
where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
T: IndexMut<usize>,
T::Output: Sized + 'static,
{
type Value = T::Output;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
self.writer()
}
fn try_write_untracked(
&self,
) -> Option<impl DerefMut<Target = Self::Value>> {
self.writer().map(|mut writer| {
writer.untrack();
writer
})
}
}
impl<Inner, Prev, K, T> KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
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<Inner, Prev, K, T> KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
Inner: StoreField<Value = Prev>,
Prev: 'static,
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
T: IndexMut<usize>,
T::Output: Sized,
{
#[track_caller]
pub fn iter_keyed(self) -> StoreFieldKeyedIter<Inner, Prev, K, T> {
// 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::<VecDeque<_>>();
// return the iterator
StoreFieldKeyedIter { inner: self, keys }
}
}
pub struct StoreFieldKeyedIter<Inner, Prev, K, T>
where
for<'a> &'a T: IntoIterator,
T: IndexMut<usize>,
{
inner: KeyedSubfield<Inner, Prev, K, T>,
keys: VecDeque<K>,
}
impl<Inner, Prev, K, T> Iterator for StoreFieldKeyedIter<Inner, Prev, K, T>
where
Inner: StoreField<Value = Prev> + Clone + 'static,
T: IndexMut<usize> + 'static,
T::Output: Sized + 'static,
for<'a> &'a T: IntoIterator,
{
type Item = AtKeyed<Inner, Prev, K, T>;
fn next(&mut self) -> Option<Self::Item> {
self.keys
.pop_front()
.map(|key| AtKeyed::new(self.inner.clone(), key))
}
}

View file

@ -1,3 +1,4 @@
use or_poisoned::OrPoisoned;
use reactive_graph::{ use reactive_graph::{
owner::{LocalStorage, Storage, StoredValue, SyncStorage}, owner::{LocalStorage, Storage, StoredValue, SyncStorage},
signal::{ signal::{
@ -8,7 +9,10 @@ use reactive_graph::{
}; };
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::{ use std::{
any::Any,
collections::HashMap,
fmt::Debug, fmt::Debug,
hash::Hash,
panic::Location, panic::Location,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -16,6 +20,7 @@ use std::{
mod arc_field; mod arc_field;
mod field; mod field;
mod iter; mod iter;
mod keyed;
mod option; mod option;
mod patch; mod patch;
mod path; mod path;
@ -25,9 +30,10 @@ mod subfield;
pub use arc_field::ArcField; pub use arc_field::ArcField;
pub use field::Field; pub use field::Field;
pub use iter::*; pub use iter::*;
pub use keyed::*;
pub use option::*; pub use option::*;
pub use patch::*; pub use patch::*;
use path::StorePath; use path::{StorePath, StorePathSegment};
pub use store_field::{StoreField, Then}; pub use store_field::{StoreField, Then};
pub use subfield::Subfield; pub use subfield::Subfield;
@ -51,11 +57,93 @@ impl TriggerMap {
} }
} }
pub struct FieldKeys<K> {
current_key: usize,
keys: HashMap<K, (StorePathSegment, usize)>,
}
impl<K> FieldKeys<K>
where
K: Debug + Hash + PartialEq + Eq,
{
pub fn new(from_iter: impl IntoIterator<Item = K>) -> 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<K> FieldKeys<K>
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<Item = K>) {
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<K> Default for FieldKeys<K> {
fn default() -> Self {
Self {
current_key: Default::default(),
keys: Default::default(),
}
}
}
#[derive(Default, Clone)]
pub struct KeyMap(Arc<RwLock<HashMap<StorePath, Box<dyn Any + Send + Sync>>>>);
impl KeyMap {
pub fn with_field_keys<K, T>(
&self,
path: StorePath,
fun: impl FnOnce(&mut FieldKeys<K>) -> T,
initialize: impl FnOnce() -> Vec<K>,
) -> Option<T>
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::<FieldKeys<K>>()?;
Some(fun(entry))
}
}
pub struct ArcStore<T> { pub struct ArcStore<T> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
defined_at: &'static Location<'static>, defined_at: &'static Location<'static>,
pub(crate) value: Arc<RwLock<T>>, pub(crate) value: Arc<RwLock<T>>,
signals: Arc<RwLock<TriggerMap>>, signals: Arc<RwLock<TriggerMap>>,
keys: KeyMap,
} }
impl<T> ArcStore<T> { impl<T> ArcStore<T> {
@ -65,6 +153,7 @@ impl<T> ArcStore<T> {
defined_at: Location::caller(), defined_at: Location::caller(),
value: Arc::new(RwLock::new(value)), value: Arc::new(RwLock::new(value)),
signals: Default::default(), signals: Default::default(),
keys: Default::default(),
} }
} }
} }
@ -87,6 +176,7 @@ impl<T> Clone for ArcStore<T> {
defined_at: self.defined_at, defined_at: self.defined_at,
value: Arc::clone(&self.value), value: Arc::clone(&self.value),
signals: Arc::clone(&self.signals), signals: Arc::clone(&self.signals),
keys: self.keys.clone(),
} }
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
path::{StorePath, StorePathSegment}, path::{StorePath, StorePathSegment},
ArcStore, Store, ArcStore, KeyMap, Store,
}; };
use or_poisoned::OrPoisoned; use or_poisoned::OrPoisoned;
use reactive_graph::{ use reactive_graph::{
@ -32,12 +32,20 @@ pub trait StoreField: Sized {
fn path(&self) -> impl IntoIterator<Item = StorePathSegment>; fn path(&self) -> impl IntoIterator<Item = StorePathSegment>;
fn track_field(&self) {
let path = self.path().into_iter().collect();
let trigger = self.get_trigger(path);
trigger.track();
}
fn reader(&self) -> Option<Self::Reader>; fn reader(&self) -> Option<Self::Reader>;
fn writer(&self) -> Option<Self::Writer>; fn writer(&self) -> Option<Self::Writer>;
fn untracked_writer(&self) -> Option<Self::UntrackedWriter>; fn untracked_writer(&self) -> Option<Self::UntrackedWriter>;
fn keys(&self) -> Option<KeyMap>;
#[track_caller] #[track_caller]
fn then<T>( fn then<T>(
self, self,
@ -86,6 +94,10 @@ where
fn untracked_writer(&self) -> Option<Self::UntrackedWriter> { fn untracked_writer(&self) -> Option<Self::UntrackedWriter> {
UntrackedWriteGuard::try_new(Arc::clone(&self.value)) UntrackedWriteGuard::try_new(Arc::clone(&self.value))
} }
fn keys(&self) -> Option<KeyMap> {
Some(self.keys.clone())
}
} }
impl<T, S> StoreField for Store<T, S> impl<T, S> StoreField for Store<T, S>
@ -125,6 +137,10 @@ where
.try_get_value() .try_get_value()
.and_then(|n| n.untracked_writer()) .and_then(|n| n.untracked_writer())
} }
fn keys(&self) -> Option<KeyMap> {
self.inner.try_get_value().and_then(|inner| inner.keys())
}
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -190,6 +206,11 @@ where
let inner = self.inner.untracked_writer()?; let inner = self.inner.untracked_writer()?;
Some(MappedMut::new(inner, self.map_fn, self.map_fn_mut)) Some(MappedMut::new(inner, self.map_fn, self.map_fn_mut))
} }
#[inline(always)]
fn keys(&self) -> Option<KeyMap> {
self.inner.keys()
}
} }
impl<T, S> DefinedAt for Then<T, S> impl<T, S> DefinedAt for Then<T, S>

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
path::{StorePath, StorePathSegment}, path::{StorePath, StorePathSegment},
store_field::StoreField, store_field::StoreField,
KeyMap,
}; };
use reactive_graph::{ use reactive_graph::{
signal::{ signal::{
@ -15,10 +16,7 @@ use reactive_graph::{
use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location}; use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location};
#[derive(Debug)] #[derive(Debug)]
pub struct Subfield<Inner, Prev, T> pub struct Subfield<Inner, Prev, T> {
where
Inner: StoreField<Value = Prev>,
{
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
defined_at: &'static Location<'static>, defined_at: &'static Location<'static>,
path_segment: StorePathSegment, path_segment: StorePathSegment,
@ -30,7 +28,7 @@ where
impl<Inner, Prev, T> Clone for Subfield<Inner, Prev, T> impl<Inner, Prev, T> Clone for Subfield<Inner, Prev, T>
where where
Inner: StoreField<Value = Prev> + Clone, Inner: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
@ -45,15 +43,9 @@ where
} }
} }
impl<Inner, Prev, T> Copy for Subfield<Inner, Prev, T> where impl<Inner, Prev, T> Copy for Subfield<Inner, Prev, T> where Inner: Copy {}
Inner: StoreField<Value = Prev> + Copy
{
}
impl<Inner, Prev, T> Subfield<Inner, Prev, T> impl<Inner, Prev, T> Subfield<Inner, Prev, T> {
where
Inner: StoreField<Value = Prev>,
{
#[track_caller] #[track_caller]
pub fn new( pub fn new(
inner: Inner, inner: Inner,
@ -111,6 +103,11 @@ where
let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?); let inner = WriteGuard::new(trigger, self.inner.untracked_writer()?);
Some(MappedMut::new(inner, self.read, self.write)) Some(MappedMut::new(inner, self.read, self.write))
} }
#[inline(always)]
fn keys(&self) -> Option<KeyMap> {
self.inner.keys()
}
} }
impl<Inner, Prev, T> DefinedAt for Subfield<Inner, Prev, T> impl<Inner, Prev, T> DefinedAt for Subfield<Inner, Prev, T>
@ -131,7 +128,7 @@ where
impl<Inner, Prev, T> IsDisposed for Subfield<Inner, Prev, T> impl<Inner, Prev, T> IsDisposed for Subfield<Inner, Prev, T>
where where
Inner: StoreField<Value = Prev> + IsDisposed, Inner: IsDisposed,
{ {
fn is_disposed(&self) -> bool { fn is_disposed(&self) -> bool {
self.inner.is_disposed() self.inner.is_disposed()

View file

@ -7,7 +7,7 @@ use syn::{
punctuated::Punctuated, punctuated::Punctuated,
token::Comma, token::Comma,
Field, Fields, Generics, Ident, Index, Meta, Result, Token, Type, Variant, Field, Fields, Generics, Ident, Index, Meta, Result, Token, Type, Variant,
Visibility, WhereClause, Visibility, WhereClause, ExprClosure,
}; };
#[proc_macro_error] #[proc_macro_error]
@ -79,17 +79,17 @@ impl Parse for Model {
#[derive(Clone)] #[derive(Clone)]
enum SubfieldMode { enum SubfieldMode {
Keyed(Ident, Type), Keyed(ExprClosure, Type),
} }
impl Parse for SubfieldMode { impl Parse for SubfieldMode {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mode: Ident = input.parse()?; let mode: Ident = input.parse()?;
if mode == "key" { if mode == "key" {
let _eq: Token!(=) = input.parse()?;
let ident: Ident = input.parse()?;
let _col: Token!(:) = input.parse()?; let _col: Token!(:) = input.parse()?;
let ty: Type = input.parse()?; let ty: Type = input.parse()?;
let _eq: Token!(=) = input.parse()?;
let ident: ExprClosure = input.parse()?;
Ok(SubfieldMode::Keyed(ident, ty)) Ok(SubfieldMode::Keyed(ident, ty))
} else { } else {
Err(input.error("expected `key = <ident>: <Type>`")) Err(input.error("expected `key = <ident>: <Type>`"))
@ -281,15 +281,20 @@ fn field_to_tokens(
if modes.len() == 1 { if modes.len() == 1 {
let mode = &modes[0]; let mode = &modes[0];
// Can replace with a match if additional modes added // 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! { 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 { return if include_body {
quote! { quote! {
#signature { #signature {
todo!() #library_path::KeyedSubfield::new(
self,
#idx.into(),
#keyed_by,
|prev| &prev.#locator,
|prev| &mut prev.#locator,
)
} }
} }
} else { } else {
@ -474,7 +479,9 @@ fn variant_to_tokens(
); );
let ignore_before = (0..idx).map(|_| quote! { _, }); 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_after = (idx..number_of_fields.saturating_sub(1)).map(|_| quote !{_, });
let ignore_after2 = ignore_after.clone();
// default subfield // default subfield
if include_body { if include_body {
@ -497,7 +504,11 @@ fn variant_to_tokens(
.expect("accessed an enum field that is no longer matched") .expect("accessed an enum field that is no longer matched")
}, },
|prev| { |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 { } else {