ferroui_base/utilities/
ferro_property_dictionary.rs1use crate::FerroProperty;
2
3pub 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 pub fn new() -> Self {
36 Self { entries: Vec::new() }
37 }
38
39 pub fn new_with_capacity(capacity: usize) -> Self {
41 Self { entries: Vec::with_capacity(capacity) }
42 }
43
44 pub fn count(&self) -> usize {
46 self.entries.len()
47 }
48
49 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 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 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 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 pub fn clear(&mut self) {
91 self.entries.clear();
92 }
93
94 pub fn contains_key(&self, property: &FerroProperty) -> bool {
96 self.find_entry(property.id()).is_ok()
97 }
98
99 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 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 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 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 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 #[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 #[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 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 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 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 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 #[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}