Skip to main content

ferroui_dynamic_data/cache/ops/
combine.rs

1//! `Combine` (`Cache/ObservableCacheEx.Combine.cs`): the private overloads behind `And`, `Or`,
2//! `Xor` and `Except`, and the kinds of sources they take.
3//!
4//! Deviation (DEVIATIONS.md, DynamicData): upstream's static overloads over a collection of
5//! sources are methods of extension traits on those collections, as for `MergeChangeSets`:
6//! - a sequence with others: provided methods of [`ObservableCacheEx`](crate::cache::ObservableCacheEx)
7//!   (`and_cache(others)`, `or(others)`, `xor(others)`, `except(others)`);
8//! - a collection of sources (`ICollection<IObservable<IChangeSet>>`): [`CacheSourcesCombineEx`],
9//!   for a slice or a `Vec`;
10//! - an observable list of sources (`IObservableList<IObservable<IChangeSet>>`,
11//!   `IObservableList<IObservableCache>`, `IObservableList<ISourceCache>`):
12//!   [`CacheSourceListCombineEx`], for a `SourceList` or an `ObservableList`.
13//!
14//! A source is any [`CacheChangeSetSource`]: a sequence of change sets, or a cache, whose
15//! `Connect()` is its sequence.
16//!
17//! Deviation (DEVIATIONS.md, DynamicData): the combiners keep the item they hold when a source
18//! gives the same object again (`ReferenceEquals`). The reference of an item is the object its
19//! pointer refers to (`T: Deref`, an `Rc<..>` for the classes of upstream), as
20//! `ignore_same_reference_update` does; the combiners of caches require it.
21//!
22//! Naming (DEVIATIONS.md, DynamicData): `And` is `and_cache` everywhere, as the method of
23//! `ObservableCacheEx` would hide the join pattern `ObservableExt::and`.
24
25use crate::cache::internal::{combiner, dynamic_combiner, CombineOperator, SameReference};
26use crate::cache::{ChangeSet, IConnectableCache, IObservableCache, ISourceCache, ObservableCache, SourceCache};
27use crate::list::{IObservableList, ObservableList, SourceList};
28use ferroui_system::IObservable;
29use std::hash::Hash;
30use std::ops::Deref;
31use std::rc::Rc;
32
33/// What `ReferenceEquals` is for an item: the same object behind the pointer.
34pub(crate) fn same_reference<T: Deref + 'static>() -> SameReference<T> {
35    Rc::new(|a: &T, b: &T| std::ptr::addr_eq(Deref::deref(a) as *const T::Target, Deref::deref(b) as *const T::Target))
36}
37
38/// `Combine(IObservable<IChangeSet>, CombineOperator, params IObservable<IChangeSet>[])`: the
39/// sequence first, then the others.
40pub(crate) fn combine_with_others<T: Clone + Deref + 'static, K: Eq + Hash + Clone + 'static>(
41    source: Rc<dyn IObservable<ChangeSet<T, K>>>,
42    kind: CombineOperator,
43    others: Vec<Rc<dyn IObservable<ChangeSet<T, K>>>>,
44) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
45    let mut sources = Vec::with_capacity(others.len() + 1);
46    sources.push(source);
47    sources.extend(others);
48    combiner(sources, kind, same_reference())
49}
50
51/// A source of the change sets of a cache: a sequence of change sets, or a cache whose `Connect()`
52/// is its sequence (the items of upstream's collections of sources).
53pub trait CacheChangeSetSource: 'static {
54    /// The type of the items.
55    type Item: Clone + 'static;
56    /// The type of the keys.
57    type Key: Eq + Hash + Clone + 'static;
58
59    /// The change sets of the source: the sequence itself, or `Connect()` of a cache.
60    fn source_change_sets(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>;
61}
62
63impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> CacheChangeSetSource for Rc<dyn IObservable<ChangeSet<T, K>>> {
64    type Item = T;
65    type Key = K;
66
67    fn source_change_sets(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
68        self.clone()
69    }
70}
71
72impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> CacheChangeSetSource for SourceCache<T, K> {
73    type Item = T;
74    type Key = K;
75
76    fn source_change_sets(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
77        self.connect()
78    }
79}
80
81impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> CacheChangeSetSource for ObservableCache<T, K> {
82    type Item = T;
83    type Key = K;
84
85    fn source_change_sets(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
86        self.connect()
87    }
88}
89
90impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> CacheChangeSetSource for Rc<dyn IObservableCache<T, K>> {
91    type Item = T;
92    type Key = K;
93
94    fn source_change_sets(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
95        self.connect()
96    }
97}
98
99impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> CacheChangeSetSource for Rc<dyn ISourceCache<T, K>> {
100    type Item = T;
101    type Key = K;
102
103    fn source_change_sets(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
104        self.connect()
105    }
106}
107
108/// Caches compare by identity (reference equality), so that they can be the items of a list.
109impl<T, K> PartialEq for dyn IObservableCache<T, K> {
110    fn eq(&self, other: &Self) -> bool {
111        std::ptr::addr_eq(self as *const Self, other as *const Self)
112    }
113}
114
115/// Caches compare by identity (reference equality), so that they can be the items of a list.
116impl<T, K> PartialEq for dyn ISourceCache<T, K> {
117    fn eq(&self, other: &Self) -> bool {
118        std::ptr::addr_eq(self as *const Self, other as *const Self)
119    }
120}
121
122/// `And`, `Or`, `Xor` and `Except` of a collection of sources (`And(this
123/// ICollection<IObservable<IChangeSet<TObject, TKey>>>)`, ..): the sources are subscribed in
124/// order when the result is subscribed.
125pub trait CacheSourcesCombineEx {
126    /// The type of the items.
127    type Item: Clone + 'static;
128    /// The type of the keys.
129    type Key: Eq + Hash + Clone + 'static;
130
131    /// The items that are in every source (`And(ICollection<..>)`).
132    fn and_cache(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
133    where
134        Self::Item: Deref;
135
136    /// The items that are in any source (`Or(ICollection<..>)`).
137    fn or(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
138    where
139        Self::Item: Deref;
140
141    /// The items that are in exactly one source (`Xor(ICollection<..>)`).
142    fn xor(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
143    where
144        Self::Item: Deref;
145
146    /// The items of the first source that are in none of the others (`Except(ICollection<..>)`).
147    fn except(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
148    where
149        Self::Item: Deref;
150}
151
152/// `Combine(ICollection<IObservable<IChangeSet>>, CombineOperator)`.
153fn combine_collection<S: CacheChangeSetSource>(sources: &[S], kind: CombineOperator) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
154where
155    S::Item: Deref,
156{
157    combiner(sources.iter().map(CacheChangeSetSource::source_change_sets).collect(), kind, same_reference())
158}
159
160impl<S: CacheChangeSetSource> CacheSourcesCombineEx for [S] {
161    type Item = S::Item;
162    type Key = S::Key;
163
164    fn and_cache(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
165    where
166        Self::Item: Deref,
167    {
168        combine_collection(self, CombineOperator::And)
169    }
170
171    fn or(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
172    where
173        Self::Item: Deref,
174    {
175        combine_collection(self, CombineOperator::Or)
176    }
177
178    fn xor(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
179    where
180        Self::Item: Deref,
181    {
182        combine_collection(self, CombineOperator::Xor)
183    }
184
185    fn except(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
186    where
187        Self::Item: Deref,
188    {
189        combine_collection(self, CombineOperator::Except)
190    }
191}
192
193/// `And`, `Or`, `Xor` and `Except` of an observable list of sources (`And(this
194/// IObservableList<IObservable<IChangeSet<TObject, TKey>>>)`, `And(this
195/// IObservableList<IObservableCache<TObject, TKey>>)`, `And(this
196/// IObservableList<ISourceCache<TObject, TKey>>)`, ..): the result follows the sources as they are
197/// added to the list and removed from it.
198pub trait CacheSourceListCombineEx {
199    /// The type of the items.
200    type Item: Clone + 'static;
201    /// The type of the keys.
202    type Key: Eq + Hash + Clone + 'static;
203
204    /// The items that are in every source of the list; none while the list is empty
205    /// (`And(IObservableList<..>)`).
206    fn and_cache(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
207    where
208        Self::Item: Deref;
209
210    /// The items that are in any source of the list (`Or(IObservableList<..>)`).
211    fn or(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
212    where
213        Self::Item: Deref;
214
215    /// The items that are in exactly one source of the list (`Xor(IObservableList<..>)`).
216    fn xor(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
217    where
218        Self::Item: Deref;
219
220    /// The items of the first source of the list that are in none of the others
221    /// (`Except(IObservableList<..>)`).
222    fn except(&self) -> Rc<dyn IObservable<ChangeSet<Self::Item, Self::Key>>>
223    where
224        Self::Item: Deref;
225}
226
227/// `Combine(IObservableList<..>, CombineOperator)`, with `new DynamicCombiner(..).Run()`.
228fn combine_list<S: CacheChangeSetSource + Clone + PartialEq>(list: &dyn IObservableList<S>, kind: CombineOperator) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
229where
230    S::Item: Deref,
231{
232    dynamic_combiner(list.connect(), Rc::new(|source: &S| source.source_change_sets()), kind, same_reference())
233}
234
235/// The forms of a list of sources, for one type of list.
236macro_rules! cache_source_list_combine_impl {
237    ($list:ident) => {
238        impl<S: CacheChangeSetSource + Clone + PartialEq> CacheSourceListCombineEx for $list<S> {
239            type Item = S::Item;
240            type Key = S::Key;
241
242            fn and_cache(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
243            where
244                Self::Item: Deref,
245            {
246                combine_list(self, CombineOperator::And)
247            }
248
249            fn or(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
250            where
251                Self::Item: Deref,
252            {
253                combine_list(self, CombineOperator::Or)
254            }
255
256            fn xor(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
257            where
258                Self::Item: Deref,
259            {
260                combine_list(self, CombineOperator::Xor)
261            }
262
263            fn except(&self) -> Rc<dyn IObservable<ChangeSet<S::Item, S::Key>>>
264            where
265                Self::Item: Deref,
266            {
267                combine_list(self, CombineOperator::Except)
268            }
269        }
270    };
271}
272
273cache_source_list_combine_impl!(SourceList);
274cache_source_list_combine_impl!(ObservableList);