Skip to main content

ferroui_dynamic_data/cache/internal/
key_value_comparer.rs

1//! `KeyValueComparer<TObject, TKey>` (`Cache/Internal/KeyValueComparer.cs`).
2
3use std::any::Any;
4use std::cmp::Ordering;
5use std::fmt;
6use std::hash::{DefaultHasher, Hash, Hasher};
7use std::marker::PhantomData;
8use std::rc::Rc;
9
10/// A comparer of items (`IComparer<TObject>`).
11pub(crate) type Comparer<T> = Rc<dyn Fn(&T, &T) -> Ordering>;
12
13/// Compares the pairs of a key and an item of a sorted cache (`KeyValueComparer<TObject, TKey>`):
14/// by the items with the comparer when there is one, then by the keys, so that each pair has a
15/// unique position.
16pub struct KeyValueComparer<T, K> {
17    comparer: Option<Comparer<T>>,
18    _key: PhantomData<fn(&K)>,
19}
20
21impl<T, K> Clone for KeyValueComparer<T, K> {
22    fn clone(&self) -> Self {
23        Self { comparer: self.comparer.clone(), _key: PhantomData }
24    }
25}
26
27impl<T, K> Default for KeyValueComparer<T, K> {
28    /// A comparer of the keys only (`new KeyValueComparer<TObject, TKey>()`).
29    fn default() -> Self {
30        Self::new(None)
31    }
32}
33
34impl<T, K> fmt::Debug for KeyValueComparer<T, K> {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        f.debug_struct("KeyValueComparer").field("has_comparer", &self.comparer.is_some()).finish()
37    }
38}
39
40impl<T, K> KeyValueComparer<T, K> {
41    /// A comparer of pairs by `comparer`, then by the keys (`KeyValueComparer(IComparer<TObject>?)`).
42    pub fn new(comparer: Option<Rc<dyn Fn(&T, &T) -> Ordering>>) -> Self {
43        Self { comparer, _key: PhantomData }
44    }
45
46    /// The comparer of the items.
47    pub fn item_comparer(&self) -> Option<&Rc<dyn Fn(&T, &T) -> Ordering>> {
48        self.comparer.as_ref()
49    }
50}
51
52impl<T: 'static, K: Hash + 'static> KeyValueComparer<T, K> {
53    /// Compares two pairs (`Compare`).
54    pub fn compare(&self, x: &(K, T), y: &(K, T)) -> Ordering {
55        if let Some(comparer) = &self.comparer {
56            let result = comparer(&x.1, &y.1);
57            if result != Ordering::Equal {
58                return result;
59            }
60        }
61        compare_keys(&x.0, &y.0)
62    }
63
64    /// The comparer as a function.
65    pub fn to_fn(&self) -> Rc<dyn Fn(&(K, T), &(K, T)) -> Ordering> {
66        let comparer = self.clone();
67        Rc::new(move |x: &(K, T), y: &(K, T)| comparer.compare(x, y))
68    }
69}
70
71/// Compares two keys as upstream does when the items compare as equal: by `IComparable<TKey>`
72/// when the key has it, else by the hash codes.
73///
74/// Deviation (DEVIATIONS.md, DynamicData): the keys need no `Ord`, as upstream's need no
75/// `IComparable`. The standard types that have an order (the strings, the integers, `char` and
76/// `bool`) are compared by it, found at run time as upstream finds `IComparable<TKey>`; strings
77/// compare by their code points (an ordinal comparison, where `string.CompareTo` uses the
78/// culture). Other keys compare by the hash of a `DefaultHasher`, which differs from .NET's hash
79/// codes; this only decides the order of items that the comparer finds equal.
80pub(crate) fn compare_keys<K: Hash + 'static>(x: &K, y: &K) -> Ordering {
81    macro_rules! compare_as {
82        ($($ty:ty),* $(,)?) => {
83            $(
84                if let (Some(x), Some(y)) = ((x as &dyn Any).downcast_ref::<$ty>(), (y as &dyn Any).downcast_ref::<$ty>()) {
85                    return x.cmp(y);
86                }
87            )*
88        };
89    }
90    compare_as!(String, &'static str, Rc<str>, i32, i64, u32, u64, usize, isize, i8, i16, i128, u8, u16, u128, char, bool);
91    hash_code(x).cmp(&hash_code(y))
92}
93
94fn hash_code<K: Hash>(key: &K) -> u64 {
95    let mut hasher = DefaultHasher::new();
96    key.hash(&mut hasher);
97    hasher.finish()
98}