Skip to main content

ferroui_base/utilities/
ferro_property_dictionary.rs

1use crate::FerroProperty;
2
3/// Stores values with [`FerroProperty`] as key.
4///
5/// This struct is very similar to a sorted list, with the following
6/// differences:
7/// - The key is the id of the property, allowing very fast lookups.
8/// - The entries are stored in one array, which is the only heap allocation.
9/// - It is a plain value: the owner mutates it in place.
10pub struct FerroPropertyDictionary<TValue> {
11    entries: Vec<Entry<TValue>>,
12}
13
14struct Entry<TValue> {
15    id: u32,
16    value: TValue,
17}
18
19impl<TValue> Entry<TValue> {
20    fn new(property: &FerroProperty, value: TValue) -> Self {
21        Self { id: property.id(), value }
22    }
23}
24
25const DEFAULT_INITIAL_CAPACITY: usize = 4;
26
27impl<TValue> Default for FerroPropertyDictionary<TValue> {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl<TValue> FerroPropertyDictionary<TValue> {
34    /// Initializes a new instance.
35    pub fn new() -> Self {
36        Self { entries: Vec::new() }
37    }
38
39    /// Initializes a new instance with the specified initial capacity.
40    pub fn new_with_capacity(capacity: usize) -> Self {
41        Self { entries: Vec::with_capacity(capacity) }
42    }
43
44    /// Gets the number of items in the collection.
45    pub fn count(&self) -> usize {
46        self.entries.len()
47    }
48
49    /// Gets the value associated with the specified property (the getter of
50    /// the indexer by property).
51    ///
52    /// Panics when the property is not found.
53    pub fn get(&self, property: &FerroProperty) -> &TValue {
54        match self.find_entry(property.id()) {
55            Ok(index) => &self.entries[index].value,
56            Err(_) => throw_not_found(),
57        }
58    }
59
60    /// Sets the value associated with the specified property (the setter of
61    /// the indexer by property). An existing value is replaced.
62    pub fn set(&mut self, property: &FerroProperty, value: TValue) {
63        match self.find_entry(property.id()) {
64            Ok(index) => self.entries[index] = Entry::new(property, value),
65            Err(index) => self.insert_entry(Entry::new(property, value), index),
66        }
67    }
68
69    /// Gets the value at the specified index (the indexer by index).
70    ///
71    /// Panics when the index is not less than [`count`](Self::count).
72    pub fn get_at(&self, index: usize) -> &TValue {
73        if index >= self.entries.len() {
74            throw_out_of_range();
75        }
76        &self.entries[index].value
77    }
78
79    /// Adds the specified property and value to the dictionary.
80    ///
81    /// Panics when the dictionary has the property already.
82    pub fn add(&mut self, property: &FerroProperty, value: TValue) {
83        match self.find_entry(property.id()) {
84            Ok(_) => throw_duplicate(),
85            Err(index) => self.insert_entry(Entry::new(property, value), index),
86        }
87    }
88
89    /// Removes all property and values from the dictionary.
90    pub fn clear(&mut self) {
91        self.entries.clear();
92    }
93
94    /// Determines whether the dictionary contains the specified property.
95    pub fn contains_key(&self, property: &FerroProperty) -> bool {
96        self.find_entry(property.id()).is_ok()
97    }
98
99    /// Gets the value at the specified index.
100    ///
101    /// Panics when the index is not less than [`count`](Self::count).
102    pub fn get_value(&self, index: usize) -> &TValue {
103        if index >= self.entries.len() {
104            throw_out_of_range();
105        }
106        &self.entries[index].value
107    }
108
109    /// Removes the value with the specified property from the dictionary.
110    ///
111    /// Returns true if the property was found and removed; otherwise false.
112    pub fn remove(&mut self, property: &FerroProperty) -> bool {
113        match self.find_entry(property.id()) {
114            Ok(index) => {
115                self.remove_at(index);
116                true
117            }
118            Err(_) => false,
119        }
120    }
121
122    /// Removes the value with the specified property from the dictionary
123    /// and returns it, or `None` when the property was not found.
124    pub fn remove_value(&mut self, property: &FerroProperty) -> Option<TValue> {
125        match self.find_entry(property.id()) {
126            Ok(index) => Some(self.entries.remove(index).value),
127            Err(_) => None,
128        }
129    }
130
131    /// Removes the element at the specified index from the dictionary.
132    ///
133    /// Panics when the index is not less than [`count`](Self::count).
134    pub fn remove_at(&mut self, index: usize) {
135        if index >= self.entries.len() {
136            throw_out_of_range();
137        }
138
139        self.entries.remove(index);
140    }
141
142    /// Attempts to add the specified property and value to the dictionary.
143    ///
144    /// Returns false when the dictionary has the property already.
145    pub fn try_add(&mut self, property: &FerroProperty, value: TValue) -> bool {
146        match self.find_entry(property.id()) {
147            Ok(_) => false,
148            Err(index) => {
149                self.insert_entry(Entry::new(property, value), index);
150                true
151            }
152        }
153    }
154
155    /// Gets the value associated with the specified property, or `None`
156    /// when the dictionary does not contain it.
157    #[inline]
158    pub fn try_get_value(&self, property: &FerroProperty) -> Option<&TValue> {
159        match self.find_entry(property.id()) {
160            Ok(index) => Some(&self.entries[index].value),
161            Err(_) => None,
162        }
163    }
164
165    /// The index of the entry of a property id, or, as the error, the index
166    /// at which an entry for it is to be inserted (the original's
167    /// complement of that index).
168    #[inline]
169    fn find_entry(&self, property_id: u32) -> Result<usize, usize> {
170        let mut lo: isize = 0;
171        let mut hi: isize = self.entries.len() as isize - 1;
172
173        while lo <= hi {
174            // hi and lo are never negative here
175            let i = ((hi as usize) + (lo as usize)) >> 1;
176
177            let entry_id = self.entries[i].id;
178            if entry_id == property_id {
179                return Ok(i);
180            }
181
182            if entry_id < property_id {
183                lo = i as isize + 1;
184            } else {
185                hi = i as isize - 1;
186            }
187        }
188
189        Err(lo as usize)
190    }
191
192    /// Inserts an entry, growing the array as the original does: to the
193    /// default capacity first, from there to twice of it and then by half.
194    fn insert_entry(&mut self, entry: Entry<TValue>, entry_index: usize) {
195        let entry_count = self.entries.len();
196        if entry_count > 0 {
197            if entry_count == self.entries.capacity() {
198                let new_size = if entry_count == DEFAULT_INITIAL_CAPACITY {
199                    DEFAULT_INITIAL_CAPACITY * 2
200                } else {
201                    (entry_count as f64 * 1.5) as usize
202                };
203
204                // A capacity the original cannot grow from (one entry in an
205                // array of one) grows by one entry here.
206                self.entries.reserve_exact(new_size.max(entry_count + 1) - entry_count);
207            }
208        } else if self.entries.capacity() == 0 {
209            self.entries.reserve_exact(DEFAULT_INITIAL_CAPACITY);
210        }
211
212        self.entries.insert(entry_index, entry);
213    }
214}
215
216fn throw_out_of_range() -> ! {
217    panic!("Index was outside the bounds of the array.")
218}
219
220fn throw_duplicate() -> ! {
221    panic!("An item with the same key has already been added.")
222}
223
224fn throw_not_found() -> ! {
225    panic!("The given key was not present in the dictionary.")
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use crate::{FerroObject, StyledProperty, StyledPropertyMetadata};
232    use std::panic::{catch_unwind, AssertUnwindSafe};
233
234    crate::ferro_thread_local! {
235        static TEST_PROPERTIES: Vec<&'static FerroProperty> = create_test_properties();
236    }
237
238    fn create_test_properties() -> Vec<&'static FerroProperty> {
239        let mut result: Vec<&'static FerroProperty> = Vec::with_capacity(100);
240
241        for i in 0..100 {
242            let property: &'static StyledProperty<String> = StyledProperty::create(
243                &format!("Test{i}"),
244                FerroObject::TYPE,
245                FerroObject::TYPE,
246                StyledPropertyMetadata::new(Some(String::new())),
247                false,
248                None,
249                None,
250                false,
251            );
252            result.push(property.as_property());
253        }
254
255        shuffle(&mut result, 42);
256        result
257    }
258
259    fn test_property(index: usize) -> &'static FerroProperty {
260        TEST_PROPERTIES.with(|properties| properties[index])
261    }
262
263    const COUNTS: [usize; 6] = [0, 1, 10, 13, 50, 72];
264
265    fn value(index: usize) -> String {
266        format!("Value{index}")
267    }
268
269    fn create_target(items: usize) -> FerroPropertyDictionary<String> {
270        let mut result = FerroPropertyDictionary::<String>::new();
271
272        for i in 0..items {
273            result.add(test_property(i), value(i));
274        }
275
276        result
277    }
278
279    /// The original shuffles with the random numbers of its runtime; the
280    /// order here comes from a linear congruential generator.
281    fn shuffle<T>(array: &mut [T], seed: u64) {
282        let mut state = seed;
283        let mut n = array.len();
284        while n > 1 {
285            state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
286            let k = ((state >> 33) as usize) % n;
287            n -= 1;
288            array.swap(n, k);
289        }
290    }
291
292    fn throws<R>(f: impl FnOnce() -> R) -> bool {
293        catch_unwind(AssertUnwindSafe(f)).is_err()
294    }
295
296    #[test]
297    fn property_indexer_finds_value() {
298        for count in COUNTS {
299            if count == 0 {
300                continue;
301            }
302
303            let target = create_target(count);
304            let index = count / 2;
305            let property = test_property(index);
306            let result = target.get(property);
307
308            assert_eq!(&value(index), result);
309        }
310    }
311
312    #[test]
313    fn property_indexer_throws_if_value_not_found() {
314        for count in COUNTS {
315            let target = create_target(count);
316            let index = count;
317            let property = test_property(index);
318
319            assert!(throws(|| target.get(property).clone()));
320        }
321    }
322
323    #[test]
324    fn property_indexer_adds_new_value() {
325        for count in COUNTS {
326            let mut target = create_target(count);
327            let index = count;
328            let property = test_property(index);
329
330            target.set(property, String::from("new"));
331
332            assert_eq!("new", target.get(property));
333        }
334    }
335
336    #[test]
337    fn property_indexer_sets_existing_value() {
338        for count in COUNTS {
339            if count == 0 {
340                continue;
341            }
342
343            let mut target = create_target(count);
344            let index = count / 2;
345            let property = test_property(index);
346
347            assert_eq!(&value(index), target.get(property));
348
349            target.set(property, String::from("new"));
350
351            assert_eq!("new", target.get(property));
352        }
353    }
354
355    #[test]
356    fn int_indexer_finds_value() {
357        for count in COUNTS {
358            if count == 0 {
359                continue;
360            }
361
362            let target = create_target(count);
363            let index = count / 2;
364            let result = target.get_at(index);
365
366            assert!(result.starts_with("Value"));
367        }
368    }
369
370    #[test]
371    fn int_indexer_throws_if_index_out_of_range() {
372        for count in COUNTS {
373            let target = create_target(count);
374            let index = count;
375
376            assert!(throws(|| target.get_at(index).clone()));
377        }
378    }
379
380    #[test]
381    fn add_adds_new_value() {
382        for count in COUNTS {
383            let mut target = create_target(count);
384            let index = count;
385            let property = test_property(index);
386
387            target.add(property, String::from("new"));
388
389            assert_eq!("new", target.get(property));
390        }
391    }
392
393    #[test]
394    fn add_throws_if_key_exists() {
395        for count in COUNTS {
396            if count == 0 {
397                continue;
398            }
399
400            let mut target = create_target(count);
401            let index = count / 2;
402            let property = test_property(index);
403
404            assert!(throws(|| target.add(property, String::from("new"))));
405        }
406    }
407
408    #[test]
409    fn contains_key_returns_true_if_value_exists() {
410        for count in COUNTS {
411            if count == 0 {
412                continue;
413            }
414
415            let target = create_target(count);
416            let index = count / 2;
417            let property = test_property(index);
418
419            assert!(target.contains_key(property));
420        }
421    }
422
423    #[test]
424    fn contains_key_returns_false_if_value_does_not_exist() {
425        for count in COUNTS {
426            let target = create_target(count);
427            let index = count;
428            let property = test_property(index);
429
430            assert!(!target.contains_key(property));
431        }
432    }
433
434    #[test]
435    fn get_value_finds_value() {
436        for count in COUNTS {
437            if count == 0 {
438                continue;
439            }
440
441            let target = create_target(count);
442            let index = count / 2;
443
444            let value = target.get_value(index);
445
446            assert!(value.starts_with("Value"));
447        }
448    }
449
450    #[test]
451    fn get_value_throws_if_index_out_of_range() {
452        for count in COUNTS {
453            let target = create_target(count);
454            let index = count;
455
456            assert!(throws(|| target.get_value(index).clone()));
457        }
458    }
459
460    #[test]
461    fn remove_removes_value() {
462        for count in COUNTS {
463            if count == 0 {
464                continue;
465            }
466
467            let mut target = create_target(count);
468            let index = count / 2;
469            let property = test_property(index);
470
471            assert!(target.remove(property));
472            assert!(!target.contains_key(property));
473        }
474    }
475
476    #[test]
477    fn remove_returns_false_if_value_not_present() {
478        for count in COUNTS {
479            let mut target = create_target(count);
480            let index = count;
481            let property = test_property(index);
482
483            assert!(!target.remove(property));
484        }
485    }
486
487    #[test]
488    fn remove_returns_existing_value() {
489        for count in COUNTS {
490            if count == 0 {
491                continue;
492            }
493
494            let mut target = create_target(count);
495            let index = count / 2;
496            let property = test_property(index);
497
498            assert_eq!(Some(value(index)), target.remove_value(property));
499        }
500    }
501
502    #[test]
503    fn try_add_adds_new_value() {
504        for count in COUNTS {
505            let mut target = create_target(count);
506            let index = count;
507            let property = test_property(index);
508
509            assert!(target.try_add(property, String::from("new")));
510
511            assert_eq!("new", target.get(property));
512        }
513    }
514
515    #[test]
516    fn try_add_returns_false_if_key_exists() {
517        for count in COUNTS {
518            if count == 0 {
519                continue;
520            }
521
522            let mut target = create_target(count);
523            let index = count / 2;
524            let property = test_property(index);
525
526            assert!(!target.try_add(property, String::from("new")));
527        }
528    }
529
530    #[test]
531    fn try_get_value_finds_value() {
532        for count in COUNTS {
533            if count == 0 {
534                continue;
535            }
536
537            let target = create_target(count);
538            let index = count / 2;
539            let property = test_property(index);
540
541            assert_eq!(Some(&value(index)), target.try_get_value(property));
542        }
543    }
544
545    #[test]
546    fn try_get_value_returns_false_if_key_does_not_exist() {
547        for count in COUNTS {
548            if count == 0 {
549                continue;
550            }
551
552            let target = create_target(count);
553            let index = count;
554            let property = test_property(index);
555
556            assert_eq!(None, target.try_get_value(property));
557        }
558    }
559
560    // Not from upstream.
561    #[test]
562    fn entries_are_ordered_by_property_id_and_removed_by_index() {
563        let mut target = FerroPropertyDictionary::<u32>::new_with_capacity(1);
564        for index in 0..20 {
565            let property = test_property(index);
566            target.add(property, property.id());
567        }
568        assert_eq!(20, target.count());
569        for index in 1..target.count() {
570            assert!(target.get_at(index - 1) < target.get_at(index));
571        }
572
573        let first = *target.get_at(0);
574        target.remove_at(0);
575        assert_eq!(19, target.count());
576        assert!(*target.get_at(0) > first);
577
578        target.clear();
579        assert_eq!(0, target.count());
580    }
581}