Skip to main content

reactive_stores/
patch.rs

1use crate::{path::StorePath, KeyMap, KeyedAccess, KeyedSubfield, StoreField};
2use indexmap::IndexMap;
3use itertools::{EitherOrBoth, Itertools};
4use reactive_graph::traits::{Notify, UntrackableGuard};
5use std::{
6    borrow::Cow,
7    collections::{BTreeMap, HashMap},
8    fmt::Debug,
9    hash::Hash,
10    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
11    num::{
12        NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8,
13        NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64,
14        NonZeroU8, NonZeroUsize,
15    },
16    rc::Rc,
17    sync::Arc,
18};
19
20/// Allows updating a store or field in place with a new value.
21pub trait Patch {
22    /// The type of the new value.
23    type Value;
24
25    /// Patches a store or field with a new value, only notifying fields that have changed.
26    fn patch(&self, new: Self::Value);
27}
28
29impl<T> Patch for T
30where
31    T: StoreField,
32    T::Value: PatchField,
33{
34    type Value = T::Value;
35
36    fn patch(&self, new: Self::Value) {
37        let path = self.path_unkeyed().into_iter().collect::<StorePath>();
38        let keys = self.keys();
39
40        if let Some(mut writer) = self.writer() {
41            // don't track the writer for the whole store
42            writer.untrack();
43            let mut notify = |path: &StorePath| {
44                self.triggers_for_path_unkeyed(path.to_owned()).notify();
45            };
46            writer.patch_field(new, &path, &mut notify, keys.as_ref());
47        }
48    }
49}
50
51impl<Inner, Prev, K, T> KeyedSubfield<Inner, Prev, K, T>
52where
53    Self: Clone,
54    for<'a> &'a T: IntoIterator,
55    Self: StoreField<Value = T>,
56    <Self as StoreField>::Value: PatchFieldKeyed<K>,
57    Inner: StoreField<Value = Prev>,
58    T: PatchFieldKeyed<K>,
59    K: Clone + Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
60    Prev: 'static,
61{
62    /// This implements a custom, keyed patch for keyed subfields.
63    ///
64    /// It is used in the same way as the [`Patch`] trait, but uses a keyed data diff for
65    /// data structures that implement [`PatchFieldKeyed`].
66    pub fn patch(&self, new: T) {
67        let path = self.path_unkeyed().into_iter().collect::<StorePath>();
68        let keys = self.keys();
69
70        let structure_changed = if let Some(mut writer) = self.writer() {
71            // don't track the writer for the whole store
72            writer.untrack();
73            let mut notify = |path: &StorePath| {
74                self.triggers_for_path_unkeyed(path.to_owned()).notify();
75            };
76            writer.patch_field_keyed(
77                new,
78                &mut notify,
79                keys.as_ref(),
80                self.key_fn,
81                |key| self.path_at_key(&path, key),
82            )
83        } else {
84            false
85        };
86
87        if structure_changed {
88            // Only notify `children` (not `this`) at the collection path, so that
89            // individual keyed items — which track `this` on all ancestor paths —
90            // are not spuriously notified when only the collection order has changed.
91            let trigger = self.get_trigger_unkeyed(path.clone());
92            trigger.children.notify();
93
94            let mut ancestor_path = path;
95            while !ancestor_path.is_empty() {
96                ancestor_path.pop();
97                let inner = self.get_trigger_unkeyed(ancestor_path.clone());
98                inner.children.notify();
99            }
100        }
101
102        self.update_keys();
103    }
104}
105
106/// Allows patching a store field with some new value.
107pub trait PatchField {
108    /// Patches the field with some new value, only notifying if the value has changed.
109    ///
110    /// # Arguments
111    ///
112    /// - **new** - new value
113    /// - **path** - path to the field
114    /// - **notify** - callback to notify about update
115    /// - **keys** - a reference to the KeyMap for the store that's being patched
116    fn patch_field(
117        &mut self,
118        new: Self,
119        path: &StorePath,
120        notify: &mut dyn FnMut(&StorePath),
121        keys: Option<&KeyMap>,
122    );
123}
124
125/// Allows patching a collection in a store field with a new value, after doing a keyed diff.
126///     
127/// This takes a `key_fn` that is applied to each entry in the collection and returns a
128/// unique key. Items in the old collection and new collection with the same key are treated
129/// as the same value, and the items are patched using [`PatchField`].
130///
131/// The exact notification behavior will depend on the collection type. For example, patching
132/// a vector or slice-like type should notify on the collection itself if the order of items changes.
133/// If all the same keys are present in the same order, however, the parent collection will not
134/// be notified; only the keyed items that have changed.
135pub trait PatchFieldKeyed<K>
136where
137    Self: Sized + KeyedAccess<K>,
138    for<'a> &'a Self: IntoIterator,
139{
140    /// Patches a collection with a new value.
141    ///
142    /// Returns `true` if the structure of the collection changed (items added, removed,
143    /// or reordered). Individual item changes are notified via the `notify` callback.
144    ///
145    /// # Arguments
146    ///
147    /// - **new** - updated values
148    /// - **notify** - callback to notify about the update
149    /// - **keys** - a reference to the KeyMap for the store that's being patched
150    /// - **key_fn** - callback returning the key from an item in the collection
151    /// - **path_at_key** - callback returning a store path for the element in the collection identified by the key
152    fn patch_field_keyed(
153        &mut self,
154        new: Self,
155        notify: &mut dyn FnMut(&StorePath),
156        keys: Option<&KeyMap>,
157        key_fn: impl Fn(<&Self as IntoIterator>::Item) -> K,
158        path_at_key: impl Fn(&K) -> Option<StorePath>,
159    ) -> bool
160    where
161        K: Clone + Debug + Send + Sync + PartialEq + Eq + Hash + 'static;
162}
163
164macro_rules! patch_primitives {
165    ($($ty:ty),*) => {
166        $(impl PatchField for $ty {
167            fn patch_field(
168                &mut self,
169                new: Self,
170                path: &StorePath,
171                notify: &mut dyn FnMut(&StorePath),
172                _keys: Option<&KeyMap>
173            ) {
174                if new != *self {
175                    *self = new;
176                    notify(path);
177                }
178            }
179        })*
180    };
181}
182
183patch_primitives! {
184    &str,
185    String,
186    Arc<str>,
187    Rc<str>,
188    Cow<'_, str>,
189    usize,
190    u8,
191    u16,
192    u32,
193    u64,
194    u128,
195    isize,
196    i8,
197    i16,
198    i32,
199    i64,
200    i128,
201    f32,
202    f64,
203    char,
204    bool,
205    IpAddr,
206    SocketAddr,
207    SocketAddrV4,
208    SocketAddrV6,
209    Ipv4Addr,
210    Ipv6Addr,
211    NonZeroI8,
212    NonZeroU8,
213    NonZeroI16,
214    NonZeroU16,
215    NonZeroI32,
216    NonZeroU32,
217    NonZeroI64,
218    NonZeroU64,
219    NonZeroI128,
220    NonZeroU128,
221    NonZeroIsize,
222    NonZeroUsize
223}
224
225impl<T> PatchField for Option<T>
226where
227    T: PatchField,
228{
229    fn patch_field(
230        &mut self,
231        new: Self,
232        path: &StorePath,
233        notify: &mut dyn FnMut(&StorePath),
234        keys: Option<&KeyMap>,
235    ) {
236        match (self, new) {
237            (None, None) => {}
238            (old @ Some(_), None) => {
239                old.take();
240                notify(path);
241            }
242            (old @ None, new @ Some(_)) => {
243                *old = new;
244                notify(path);
245            }
246            (Some(old), Some(new)) => {
247                let mut new_path = path.to_owned();
248                new_path.push(0);
249                old.patch_field(new, &new_path, notify, keys);
250            }
251        }
252    }
253}
254
255impl<T> PatchField for Vec<T>
256where
257    T: PatchField,
258{
259    fn patch_field(
260        &mut self,
261        new: Self,
262        path: &StorePath,
263        notify: &mut dyn FnMut(&StorePath),
264        keys: Option<&KeyMap>,
265    ) {
266        if self.is_empty() && new.is_empty() {
267            return;
268        }
269
270        if new.is_empty() {
271            self.clear();
272            notify(path);
273        } else if self.is_empty() {
274            self.extend(new);
275            notify(path);
276        } else {
277            let mut adds = vec![];
278            let mut removes_at_end = 0;
279            let mut new_path = path.to_owned();
280            new_path.push(0);
281            for (idx, item) in
282                new.into_iter().zip_longest(self.iter_mut()).enumerate()
283            {
284                match item {
285                    EitherOrBoth::Both(new, old) => {
286                        old.patch_field(new, &new_path, notify, keys);
287                    }
288                    EitherOrBoth::Left(new) => {
289                        adds.push(new);
290                    }
291                    EitherOrBoth::Right(_) => {
292                        removes_at_end += 1;
293                    }
294                }
295                new_path.replace_last(idx + 1);
296            }
297
298            let length_changed = removes_at_end > 0 || !adds.is_empty();
299            self.truncate(self.len() - removes_at_end);
300            self.append(&mut adds);
301
302            if length_changed {
303                notify(path);
304            }
305        }
306    }
307}
308
309impl<K, T> PatchFieldKeyed<K> for Vec<T>
310where
311    T: PatchField,
312{
313    fn patch_field_keyed(
314        &mut self,
315        mut new: Self,
316        notify: &mut dyn FnMut(&StorePath),
317        keys: Option<&KeyMap>,
318        key_fn: impl Fn(<&Self as IntoIterator>::Item) -> K,
319        path_at_key: impl Fn(&K) -> Option<StorePath>,
320    ) -> bool
321    where
322        K: Clone + Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
323    {
324        let mut has_changed = false;
325
326        let mut old_keyed = HashMap::new();
327        let mut new_keyed = IndexMap::new();
328
329        // first, calculate keys and indices for all the old values
330        for (idx, item) in self.drain(0..).enumerate() {
331            let key = key_fn(&item);
332            old_keyed.insert(key, (idx, item));
333        }
334
335        // then, calculate keys and indices for all the new values
336        for (idx, item) in new.drain(0..).enumerate() {
337            let key = key_fn(&item);
338            new_keyed.insert(key, (idx, item));
339        }
340
341        // if there are any old keys not included in the new keys, the list has changed
342        for old_key in old_keyed.keys() {
343            if !new_keyed.contains_key(old_key) {
344                has_changed = true;
345            }
346        }
347
348        // iterate over the new entries, rebuilding the `new` Vec (which we emptied with `drain` above)
349        //
350        // because we're using an IndexMap, this will iterate over the values in the same order
351        // as the new Vec had them
352        //
353        // for each entry, either
354        // 1) push it directly into the `new` Vec again, or
355        // 2) take the old
356        for (key, (new_idx, new_value)) in new_keyed {
357            let old_at_key = old_keyed.remove(&key);
358
359            match old_at_key {
360                None => {
361                    // add this item into the new vec
362                    new.push(new_value);
363
364                    // not found in old map, list has changed and will trigger
365                    has_changed = true;
366                }
367                // found in old map
368                Some((old_idx, old_value)) => {
369                    // if indices are different, list has changed
370                    if old_idx != new_idx {
371                        has_changed = true;
372                    }
373
374                    // if we had an old value for this key, we're actually going to push the *old*
375                    // value into the vec, and then patch it with the new value; because we're iterating
376                    // in the new order, it will be at the `new_idx`
377                    new.push(old_value);
378                    let field_to_patch = &mut new[new_idx];
379
380                    // now we need to actually patch the old item with this key with the new item
381                    // we do this by calling patch_field(); to get the correct path, we need to get the
382                    // path to the field at this key
383
384                    // we do th
385                    if let Some(path) = path_at_key(&key) {
386                        field_to_patch
387                            .patch_field(new_value, &path, notify, keys);
388                    } else {
389                        has_changed = true;
390                    }
391                }
392            }
393        }
394
395        // update the value
396        *self = new;
397
398        has_changed
399    }
400}
401
402impl<K, V> PatchFieldKeyed<K> for HashMap<K, V>
403where
404    V: PatchField,
405    K: Eq + Hash,
406{
407    fn patch_field_keyed(
408        &mut self,
409        mut new: Self,
410        notify: &mut dyn FnMut(&StorePath),
411        keys: Option<&KeyMap>,
412        key_fn: impl Fn(<&Self as IntoIterator>::Item) -> K,
413        path_at_key: impl Fn(&K) -> Option<StorePath>,
414    ) -> bool
415    where
416        K: Clone + Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
417    {
418        let mut has_changed = false;
419
420        let mut old_keyed = HashMap::with_capacity(self.len());
421        let mut new_keyed = HashMap::with_capacity(new.len());
422
423        // first, calculate keys for all the old values
424        for item in self.drain() {
425            let key = key_fn((&item.0, &item.1));
426            old_keyed.insert(key, item);
427        }
428
429        // then, calculate keys and indices for all the new values
430        for item in new.drain() {
431            let key = key_fn((&item.0, &item.1));
432            new_keyed.insert(key, item);
433        }
434
435        // if there are any old keys not included in the new keys, the map has changed
436        for old_key in old_keyed.keys() {
437            if !new_keyed.contains_key(old_key) {
438                has_changed = true;
439            }
440        }
441
442        // iterate over the new entries, rebuilding the `new` map (which we emptied with `drain` above)
443        //
444        // for each entry, either
445        // 1) push it directly into the `new` map again, or
446        // 2) take the old value and patch it
447        for (key, new_value) in new_keyed {
448            let old_at_key = old_keyed.remove(&key);
449
450            match old_at_key {
451                None => {
452                    // add this item into the new map
453                    new.insert(new_value.0, new_value.1);
454
455                    // not found in old map, list has changed and will trigger
456                    has_changed = true;
457                }
458                // found in old map
459                Some(mut old_value) => {
460                    // now we need to actually patch the old item with this key with the new item
461                    // we do this by calling patch_field(); to get the correct path, we need to get the
462                    // path to the field at this key
463                    if let Some(path) = path_at_key(&key) {
464                        old_value.1.patch_field(
465                            new_value.1,
466                            &path,
467                            notify,
468                            keys,
469                        );
470                    } else {
471                        has_changed = true;
472                    }
473
474                    // and we'll insert it into the new map
475                    new.insert(new_value.0, old_value.1);
476                }
477            }
478        }
479
480        // update the value
481        *self = new;
482
483        has_changed
484    }
485}
486
487impl<K, V> PatchFieldKeyed<K> for BTreeMap<K, V>
488where
489    V: PatchField + Clone,
490    K: Eq + Ord + Clone,
491{
492    fn patch_field_keyed(
493        &mut self,
494        new: Self,
495        notify: &mut dyn FnMut(&StorePath),
496        keys: Option<&KeyMap>,
497        key_fn: impl Fn(<&Self as IntoIterator>::Item) -> K,
498        path_at_key: impl Fn(&K) -> Option<StorePath>,
499    ) -> bool
500    where
501        K: Clone + Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
502    {
503        let mut has_changed = false;
504        let mut old_keyed = BTreeMap::new();
505        let mut new_keyed = BTreeMap::new();
506
507        // first, calculate keys for all the old values
508        //
509        // BTreeMap doesn't have a drain method - https://github.com/rust-lang/rust/issues/81074
510        for item in self.iter() {
511            let key = key_fn(item);
512            old_keyed.insert(key, (item.0.clone(), item.1.clone()));
513        }
514
515        // then, calculate keys and indices for all the new values
516        //
517        // BTreeMap doesn't have a drain method - https://github.com/rust-lang/rust/issues/81074
518        for item in new {
519            let key = key_fn((&item.0, &item.1));
520            new_keyed.insert(key, item);
521        }
522
523        let mut new = BTreeMap::new();
524
525        // if there are any old keys not included in the new keys, the map has changed
526        for old_key in old_keyed.keys() {
527            if !new_keyed.contains_key(old_key) {
528                has_changed = true;
529            }
530        }
531
532        // iterate over the new entries, rebuilding the `new` map (which we emptied with `drain` above)
533        //
534        // for each entry, either
535        // 1) push it directly into the `new` map again, or
536        // 2) take the old value and patch it
537        for (key, new_value) in new_keyed {
538            let old_at_key = old_keyed.remove(&key);
539
540            match old_at_key {
541                None => {
542                    // add this item into the new map
543                    new.insert(new_value.0, new_value.1);
544
545                    // not found in old map, list has changed and will trigger
546                    has_changed = true;
547                }
548                // found in old map
549                Some(mut old_value) => {
550                    // now we need to actually patch the old item with this key with the new item
551                    // we do this by calling patch_field(); to get the correct path, we need to get the
552                    // path to the field at this key
553                    if let Some(path) = path_at_key(&key) {
554                        old_value.1.patch_field(
555                            new_value.1,
556                            &path,
557                            notify,
558                            keys,
559                        );
560                    } else {
561                        has_changed = true;
562                    }
563
564                    // and we'll insert it into the new map
565                    new.insert(new_value.0, old_value.1);
566                }
567            }
568        }
569
570        // update the value
571        *self = new;
572
573        has_changed
574    }
575}
576
577macro_rules! patch_tuple {
578	($($ty:ident),*) => {
579		impl<$($ty),*> PatchField for ($($ty,)*)
580		where
581			$($ty: PatchField),*,
582		{
583            fn patch_field(
584                &mut self,
585                new: Self,
586                path: &StorePath,
587                notify: &mut dyn FnMut(&StorePath),
588                keys: Option<&KeyMap>
589            ) {
590                let mut idx = 0;
591                let mut new_path = path.to_owned();
592                new_path.push(0);
593
594                paste::paste! {
595                    #[allow(non_snake_case)]
596                    let ($($ty,)*) = self;
597                    let ($([<new_ $ty:lower>],)*) = new;
598                    $(
599                        $ty.patch_field([<new_ $ty:lower>], &new_path, notify, keys);
600                        idx += 1;
601                        new_path.replace_last(idx);
602                    )*
603                }
604            }
605        }
606    }
607}
608
609impl PatchField for () {
610    fn patch_field(
611        &mut self,
612        _new: Self,
613        _path: &StorePath,
614        _notify: &mut dyn FnMut(&StorePath),
615        _keys: Option<&KeyMap>,
616    ) {
617    }
618}
619
620patch_tuple!(A);
621patch_tuple!(A, B);
622patch_tuple!(A, B, C);
623patch_tuple!(A, B, C, D);
624patch_tuple!(A, B, C, D, E);
625patch_tuple!(A, B, C, D, E, F);
626patch_tuple!(A, B, C, D, E, F, G);
627patch_tuple!(A, B, C, D, E, F, G, H);
628patch_tuple!(A, B, C, D, E, F, G, H, I);
629patch_tuple!(A, B, C, D, E, F, G, H, I, J);
630patch_tuple!(A, B, C, D, E, F, G, H, I, J, K);
631patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
632patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
633patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
634patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
635patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
636patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
637patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
638patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
639patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
640patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
641patch_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
642patch_tuple!(
643    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W
644);
645patch_tuple!(
646    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X
647);
648patch_tuple!(
649    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y
650);
651patch_tuple!(
652    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y,
653    Z
654);