Skip to main content

ferroui_dynamic_data/list/ops/
combine.rs

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