FastLED 3.10.8
Loading...
Searching...
No Matches
unordered_map.h
Go to the documentation of this file.
1#pragma once
2
3/*
4unordered_map that is optimized for embedded devices. The unordered_map
5will store upto N elements inline, and will spill over to a heap.
6This unordered_map will try not to grow by detecting during rehash that
7the number of tombstones is greater than the number of elements.
8This will keep the memory from growing during multiple inserts
9and removals.
10*/
11
12#include "fl/stl/align.h"
14#include "fl/stl/private.h" // IWYU pragma: keep
15#include "fl/stl/hash.h"
16#include "fl/stl/int.h"
17#include "fl/math/math.h"
18#include "fl/stl/assert.h" // IWYU pragma: keep
19#include "fl/stl/bit_cast.h"
20#include "fl/stl/bitset.h"
21#include "fl/stl/move.h"
22#include "fl/stl/optional.h"
23#include "fl/stl/pair.h"
24#include "fl/stl/type_traits.h"
25#include "fl/stl/vector.h"
26#include "fl/stl/unordered_map_basic.h" // type-erased probing helper (#3235 Tier 1C)
27#include "fl/stl/initializer_list.h" // IWYU pragma: keep
29#include "fl/stl/noexcept.h"
30
33
34namespace fl {
35
36// // begin using declarations for stl compatibility
37// use fl::equal_to;
38// use fl::unordered_map;
39// // end using declarations for stl compatibility
40
43template <typename T> struct EqualTo {
44 bool operator()(const T &a, const T &b) const FL_NO_EXCEPT { return a == b; }
45};
47
48// -- unordered_map class
49// ------------------------------------------------------------- Begin unordered_map
50// class
51
52#ifndef FASTLED_HASHMAP_INLINED_COUNT
53#define FASTLED_HASHMAP_INLINED_COUNT 8
54#endif
55
56template <typename Key, typename T, typename Hash = Hash<Key>,
57 typename KeyEqual = EqualTo<Key>,
58 int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT>
60 public:
62 unordered_map(fl::size initial_capacity) FL_NO_EXCEPT : unordered_map(initial_capacity, 0.7f) {}
63
71
72 unordered_map(fl::size initial_capacity, float max_load)
73 FL_NO_EXCEPT : _buckets(next_power_of_two(initial_capacity)), _size(0),
74 _tombstones(0), _occupied(next_power_of_two(initial_capacity)),
75 _deleted(next_power_of_two(initial_capacity)) {
76 setLoadFactor(max_load);
77 }
78
79 // Copy constructor
81 : _buckets(other._buckets.size()), _size(0), _tombstones(0),
82 mLoadFactor(other.mLoadFactor), _occupied(other._buckets.size()),
83 _deleted(other._buckets.size()), _hash(other._hash), _equal(other._equal) {
84 // Copy all occupied entries using insert to properly hash them
85 for (fl::size i = 0; i < other._buckets.size(); ++i) {
86 if (other.is_occupied(i)) {
87 insert(other._buckets[i].key, other._buckets[i].value);
88 }
89 }
90 }
91
92 // Move constructor
94 : _buckets(fl::move(other._buckets)), _size(other._size),
95 _tombstones(other._tombstones), mLoadFactor(other.mLoadFactor),
96 _occupied(fl::move(other._occupied)), _deleted(fl::move(other._deleted)),
97 _hash(fl::move(other._hash)), _equal(fl::move(other._equal)) {
98 // Reset other to valid empty state
99 other._size = 0;
100 other._tombstones = 0;
101 }
102
103 // Range constructor
104 template<typename InputIt>
105 unordered_map(InputIt first, InputIt last)
107 insert(first, last);
108 }
109
110 // Initializer list constructor
115
116 // Constructor with hash/equal/allocator parameters
117 unordered_map(fl::size n, const Hash& hf, const KeyEqual& eq)
120 _hash(hf), _equal(eq) {
121 setLoadFactor(0.7f);
122 }
123
124 // Copy assignment operator
126 if (this != &other) {
127 // Clear current content
128 clear();
129
130 // Resize if necessary
131 if (_buckets.size() != other._buckets.size()) {
132 _buckets.clear();
133 _buckets.assign(other._buckets.size(), Entry{});
134 _occupied.reset();
135 _occupied.resize(other._buckets.size());
136 _deleted.reset();
137 _deleted.resize(other._buckets.size());
138 }
139
140 // Copy all occupied entries
141 for (fl::size i = 0; i < other._buckets.size(); ++i) {
142 if (other.is_occupied(i)) {
143 _buckets[i] = other._buckets[i];
144 mark_occupied(i);
145 ++_size;
146 }
147 }
148
149 mLoadFactor = other.mLoadFactor;
150 _hash = other._hash;
151 _equal = other._equal;
152 }
153 return *this;
154 }
155
156 // Move assignment operator
158 if (this != &other) {
159 _buckets = fl::move(other._buckets);
160 _size = other._size;
161 _tombstones = other._tombstones;
162 mLoadFactor = other.mLoadFactor;
163 _occupied = fl::move(other._occupied);
164 _deleted = fl::move(other._deleted);
165 _hash = fl::move(other._hash);
166 _equal = fl::move(other._equal);
167
168 // Reset other to valid empty state
169 other._size = 0;
170 other._tombstones = 0;
171 }
172 return *this;
173 }
174
175 // Initializer list assignment operator
177 clear();
178 insert(init);
179 return *this;
180 }
181
183 f = fl::clamp(f, 0.f, 1.f);
184 mLoadFactor = fl::map_range<float, u8>(f, 0.f, 1.f, 0, 255);
185 }
186
187 // Iterator support.
188 struct iterator {
189 // Standard iterator typedefs
190 // using difference_type = std::ptrdiff_t;
191 using value_type = pair<const Key, T>; // Keep const Key as per standard
195
196 iterator() FL_NO_EXCEPT : _map(nullptr), _idx(0) {}
197 iterator(unordered_map *m, fl::size idx) FL_NO_EXCEPT : _map(m), _idx(idx) {
199 }
200
202 auto &e = _map->_buckets[_idx];
203 // Entry and pair<const Key, T> have the same memory layout
204 // Safe to reinterpret since we only add const to Key
205 return *fl::bit_cast<value_type*>(&e);
206 }
207
209 return &(operator*());
210 }
211
213 ++_idx;
215 return *this;
216 }
217
219 iterator tmp = *this;
220 ++(*this);
221 return tmp;
222 }
223
224 bool operator==(const iterator &o) const FL_NO_EXCEPT {
225 return _map == o._map && _idx == o._idx;
226 }
227 bool operator!=(const iterator &o) const FL_NO_EXCEPT { return !(*this == o); }
228
230 if (!_map)
231 return;
232 fl::size cap = _map->_buckets.size();
233 while (_idx < cap && !_map->is_occupied(_idx))
234 ++_idx;
235 }
236
237 private:
239 fl::size _idx;
240 friend class unordered_map;
241 };
242
244 // Standard iterator typedefs
245 // using difference_type = std::ptrdiff_t;
247 using pointer = const value_type *;
248 using reference = const value_type &;
250
252 const_iterator(const unordered_map *m, fl::size idx) FL_NO_EXCEPT : _map(m), _idx(idx) {
254 }
255 const_iterator(const iterator &it) FL_NO_EXCEPT : _map(it._map), _idx(it._idx) {}
256
258 auto &e = _map->_buckets[_idx];
259 // Entry and pair<const Key, T> have the same memory layout
260 // Safe to reinterpret since we only add const to Key
262 }
263
265 return &(operator*());
266 }
267
269 ++_idx;
271 return *this;
272 }
273
275 const_iterator tmp = *this;
276 ++(*this);
277 return tmp;
278 }
279
281 return _map == o._map && _idx == o._idx;
282 }
283 bool operator!=(const const_iterator &o) const FL_NO_EXCEPT { return !(*this == o); }
284
286 if (!_map)
287 return;
288 fl::size cap = _map->_buckets.size();
289 while (_idx < cap && !_map->is_occupied(_idx))
290 ++_idx;
291 }
292
293 friend class unordered_map;
294
295 private:
297 fl::size _idx;
298 };
299
300 iterator begin() FL_NO_EXCEPT { return iterator(this, 0); }
301 iterator end() FL_NO_EXCEPT { return iterator(this, _buckets.size()); }
302 const_iterator begin() const FL_NO_EXCEPT { return const_iterator(this, 0); }
303 const_iterator end() const FL_NO_EXCEPT { return const_iterator(this, _buckets.size()); }
304 const_iterator cbegin() const FL_NO_EXCEPT { return const_iterator(this, 0); }
305 const_iterator cend() const FL_NO_EXCEPT {
306 return const_iterator(this, _buckets.size());
307 }
308
309 static bool NeedsRehash(fl::size size, fl::size bucket_size, fl::size tombstones,
311 // (size + tombstones) << 8 : multiply numerator by 256
312 // capacity * max_load : denominator * threshold
313 u32 lhs = (size + tombstones) << 8;
314 u32 rhs = (bucket_size * load_factor);
315 return lhs > rhs;
316 }
317
318 // returns true if (size + tombs)/capacity > _max_load/256
321 }
322
323 // insert or overwrite - returns pair<iterator, bool>
324 // iterator points to element, bool is true if inserted, false if updated
326 const bool will_rehash = needs_rehash();
327 if (will_rehash) {
328 // if half the buckets are tombstones, rehash inline to prevent
329 // memory spill over into the heap.
330 if (_tombstones > _size) {
332 } else {
333 rehash_internal(_buckets.size() * 2);
334 }
335 }
336 fl::size idx;
337 bool is_new;
339 idx = p.first;
340 is_new = p.second;
341 if (is_new) {
342 _buckets[idx].key = key;
343 _buckets[idx].value = value;
344 mark_occupied(idx);
345 ++_size;
346 } else {
347 FASTLED_ASSERT(idx != npos(), "unordered_map::insert: invalid index");
348 _buckets[idx].value = value;
349 }
350 return {iterator(this, idx), is_new};
351 }
352
353 // Move version of insert - returns pair<iterator, bool>
354 // iterator points to element, bool is true if inserted, false if updated
356 const bool will_rehash = needs_rehash();
357 if (will_rehash) {
358 // if half the buckets are tombstones, rehash inline to prevent
359 // memory spill over into the heap.
360 if (_tombstones > _size) {
362 } else {
363 rehash_internal(_buckets.size() * 2);
364 }
365 }
366 fl::size idx;
367 bool is_new;
369 idx = p.first;
370 is_new = p.second;
371 if (is_new) {
372 _buckets[idx].key = fl::move(key);
373 _buckets[idx].value = fl::move(value);
374 mark_occupied(idx);
375 ++_size;
376 } else {
377 FASTLED_ASSERT(idx != npos(), "unordered_map::insert: invalid index");
378 _buckets[idx].value = fl::move(value);
379 }
380 return {iterator(this, idx), is_new};
381 }
382
383 // Pair-based insert (const) - std::unordered_map compatible
384 // Insert a pair<const Key, T> or pair<Key, T>
386 return insert(kv.first, kv.second);
387 }
388
389 // Pair-based insert (move) - std::unordered_map compatible
390 // Insert a pair<const Key, T> or pair<Key, T> with move semantics
392 return insert(fl::move(kv.first), fl::move(kv.second));
393 }
394
395 // Range insert - insert elements from iterator range [first, last)
396 // std::unordered_map compatible
397 template<typename InputIt>
398 void insert(InputIt first, InputIt last) FL_NO_EXCEPT {
399 for (; first != last; ++first) {
400 insert(*first); // Uses pair-based insert
401 }
402 }
403
404 // Initializer list insert - std::unordered_map compatible
405 // Insert elements from an initializer list like: m.insert({{1, "a"}, {2, "b"}})
406 void insert(fl::initializer_list<pair<Key, T>> init) FL_NO_EXCEPT {
407 for (const auto& kv : init) {
408 insert(kv); // Uses pair-based insert
409 }
410 }
411
412 // insert_or_assign() - C++17: insert new element or update existing
413 // Returns pair<iterator, bool> where bool is true if inserted, false if updated
415 const bool will_rehash = needs_rehash();
416 if (will_rehash) {
417 if (_tombstones > _size) {
419 } else {
420 rehash_internal(_buckets.size() * 2);
421 }
422 }
423 fl::size idx;
424 bool is_new;
426 idx = p.first;
427 is_new = p.second;
428 if (is_new) {
429 _buckets[idx].key = key;
430 _buckets[idx].value = fl::move(value);
431 mark_occupied(idx);
432 ++_size;
433 } else {
434 FASTLED_ASSERT(idx != npos(), "unordered_map::insert_or_assign: invalid index");
435 _buckets[idx].value = fl::move(value);
436 }
437 return {iterator(this, idx), is_new};
438 }
439
440 // insert_or_assign() with move key - C++17
442 const bool will_rehash = needs_rehash();
443 if (will_rehash) {
444 if (_tombstones > _size) {
446 } else {
447 rehash_internal(_buckets.size() * 2);
448 }
449 }
450 fl::size idx;
451 bool is_new;
453 idx = p.first;
454 is_new = p.second;
455 if (is_new) {
456 _buckets[idx].key = fl::move(key);
457 _buckets[idx].value = fl::move(value);
458 mark_occupied(idx);
459 ++_size;
460 } else {
461 FASTLED_ASSERT(idx != npos(), "unordered_map::insert_or_assign: invalid index");
462 _buckets[idx].value = fl::move(value);
463 }
464 return {iterator(this, idx), is_new};
465 }
466
467 // emplace() - construct element in-place from arguments
468 // For pairs, accepts (key, value) arguments to construct pair<Key, T>
469 template<typename... Args>
471 // Construct a pair from the arguments
473 // Use move-based insert since we just constructed this pair
474 return insert(fl::move(kv));
475 }
476
477 // emplace_hint() - construct element in-place with hint
478 // Hint is ignored for hash maps (no spatial locality benefits)
479 template<typename... Args>
480 iterator emplace_hint(const_iterator hint, Args&&... args) FL_NO_EXCEPT {
481 (void)hint; // Hint is ignored in hash maps
482 return emplace(fl::forward<Args>(args)...).first;
483 }
484
485 // try_emplace() - C++17: emplace only if key doesn't exist
486 // Advantage: doesn't move key if element already exists
487 template<typename... Args>
489 const bool will_rehash = needs_rehash();
490 if (will_rehash) {
491 if (_tombstones > _size) {
493 } else {
494 rehash_internal(_buckets.size() * 2);
495 }
496 }
497 fl::size idx;
498 bool is_new;
500 idx = p.first;
501 is_new = p.second;
502 if (is_new) {
503 // Key doesn't exist, construct in place
504 _buckets[idx].key = k;
505 _buckets[idx].value = T(fl::forward<Args>(args)...);
506 mark_occupied(idx);
507 ++_size;
508 }
509 // If not new, key already exists - don't construct value, just return existing iterator
510 return {iterator(this, idx), is_new};
511 }
512
513 // try_emplace() with move key - C++17
514 template<typename... Args>
516 const bool will_rehash = needs_rehash();
517 if (will_rehash) {
518 if (_tombstones > _size) {
520 } else {
521 rehash_internal(_buckets.size() * 2);
522 }
523 }
524 fl::size idx;
525 bool is_new;
527 idx = p.first;
528 is_new = p.second;
529 if (is_new) {
530 // Key doesn't exist, construct in place with moved key
531 _buckets[idx].key = fl::move(k);
532 _buckets[idx].value = T(fl::forward<Args>(args)...);
533 mark_occupied(idx);
534 ++_size;
535 }
536 // If not new, key already exists - don't move key or construct value
537 return {iterator(this, idx), is_new};
538 }
539
540 // remove key; returns true if removed
541 bool remove(const Key &key) FL_NO_EXCEPT {
542 auto idx = find_index(key);
543 if (idx == npos())
544 return false;
545 mark_deleted(idx);
546 --_size;
547 ++_tombstones;
548 return true;
549 }
550
551 bool erase(const Key &key) FL_NO_EXCEPT { return remove(key); }
552
553 // Iterator-based erase - more efficient when you already have the iterator position
554 iterator erase(iterator it) FL_NO_EXCEPT {
555 if (it == end() || it._map != this) {
556 return end(); // Invalid iterator
557 }
558
559 // Mark the current position as deleted
560 mark_deleted(it._idx);
561 --_size;
562 ++_tombstones;
563
564 // Advance to next valid element and return iterator to it
565 ++it._idx;
566 it.advance_to_occupied();
567 return it;
568 }
569
570 // Range erase - erase elements in range [first, last)
571 iterator erase(const_iterator first, const_iterator last) FL_NO_EXCEPT {
572 if (first._map != this || last._map != this) {
573 return end(); // Invalid iterators
574 }
575
576 // Erase each element in the range
577 // We need to convert const_iterator to iterator for the return value
578 iterator current(this, first._idx);
579 current.advance_to_occupied();
580
581 while (current != end() && current._idx < last._idx) {
582 fl::size idx_to_erase = current._idx;
583 ++current;
584 current.advance_to_occupied();
585
586 // Erase the element at idx_to_erase
587 mark_deleted(idx_to_erase);
588 --_size;
589 ++_tombstones;
590 }
591
592 return current;
593 }
594
596 _buckets.assign(_buckets.size(), Entry{});
597 _occupied.reset();
598 _deleted.reset();
599 _size = _tombstones = 0;
600 }
601
602 // swap() - swap contents with another unordered_map
604 // Swap all member variables
605 _buckets.swap(other._buckets);
606 fl::swap(_size, other._size);
607 fl::swap(_tombstones, other._tombstones);
608 fl::swap(mLoadFactor, other.mLoadFactor);
609 fl::swap(_occupied, other._occupied);
610 fl::swap(_deleted, other._deleted);
611 fl::swap(_hash, other._hash);
612 fl::swap(_equal, other._equal);
613 }
614
615 // find pointer to value or nullptr
616 T *find_value(const Key &key) FL_NO_EXCEPT {
617 auto idx = find_index(key);
618 return idx == npos() ? nullptr : &_buckets[idx].value;
619 }
620
621 const T *find_value(const Key &key) const FL_NO_EXCEPT {
622 auto idx = find_index(key);
623 return idx == npos() ? nullptr : &_buckets[idx].value;
624 }
625
626 iterator find(const Key &key) FL_NO_EXCEPT {
627 auto idx = find_index(key);
628 return idx == npos() ? end() : iterator(this, idx);
629 }
630
631 const_iterator find(const Key &key) const FL_NO_EXCEPT {
632 auto idx = find_index(key);
633 return idx == npos() ? end() : const_iterator(this, idx);
634 }
635
636 bool contains(const Key &key) const FL_NO_EXCEPT {
637 auto idx = find_index(key);
638 return idx != npos();
639 }
640
641 // at() - bounds-checked access, asserts if key not found
642 T &at(const Key &key) FL_NO_EXCEPT {
643 T* value = find_value(key);
644 FASTLED_ASSERT(value != nullptr, "unordered_map::at: key not found");
645 return *value;
646 }
647
648 const T &at(const Key &key) const FL_NO_EXCEPT {
649 const T* value = find_value(key);
650 FASTLED_ASSERT(value != nullptr, "unordered_map::at: key not found");
651 return *value;
652 }
653
654 // count() - returns 0 or 1 (unordered_map has unique keys)
655 fl::size count(const Key &key) const FL_NO_EXCEPT {
656 return contains(key) ? 1 : 0;
657 }
658
659 // equal_range() - returns pair of iterators [it, it+1) or [end, end)
661 iterator it = find(key);
662 if (it == end()) {
663 return {end(), end()};
664 }
665 iterator next = it;
666 ++next;
667 return {it, next};
668 }
669
671 const_iterator it = find(key);
672 if (it == end()) {
673 return {end(), end()};
674 }
675 const_iterator next = it;
676 ++next;
677 return {it, next};
678 }
679
680 // access or default-construct
681 T &operator[](const Key &key) FL_NO_EXCEPT {
682 fl::size idx;
683 bool is_new;
684
686 idx = p.first;
687 is_new = p.second;
688
689 // Check if find_slot failed to find a valid slot (unordered_map is full)
690 if (idx == npos()) {
691 // Need to resize to make room
692 if (needs_rehash()) {
693 // if half the buckets are tombstones, rehash inline to prevent
694 // memory growth. Otherwise, double the size.
695 if (_tombstones >= _buckets.size() / 2) {
697 } else {
698 rehash_internal(_buckets.size() * 2);
699 }
700 } else {
701 // Force a rehash with double size if needs_rehash() doesn't detect the issue
702 rehash_internal(_buckets.size() * 2);
703 }
704
705 // Try find_slot again after resize
706 p = find_slot(key);
707 idx = p.first;
708 is_new = p.second;
709
710 // If still npos() after resize, allocation failed catastrophically
711 if (idx == npos()) {
712 // This should never happen after a successful resize
713 static T default_value{};
714 FASTLED_ASSERT(false, "unordered_map::operator[]: Failed to allocate after rehash");
715 return default_value;
716 }
717 }
718
719 if (is_new) {
720 _buckets[idx].key = key;
721 _buckets[idx].value = T{};
722 mark_occupied(idx);
723 ++_size;
724 }
725 return _buckets[idx].value;
726 }
727
728 fl::size size() const FL_NO_EXCEPT { return _size; }
729 bool empty() const FL_NO_EXCEPT { return _size == 0; }
730 fl::size capacity() const FL_NO_EXCEPT { return _buckets.size(); }
731
732 // max_size() - theoretical maximum size
733 fl::size max_size() const FL_NO_EXCEPT {
734 // Return the maximum value that fl::size can hold, divided by the size of Entry
735 // to be conservative about memory limits
736 return static_cast<fl::size>(-1) / sizeof(Entry);
737 }
738
739 // hash_function() - return the hash functor
741
742 // key_eq() - return the key equality predicate
743 KeyEqual key_eq() const FL_NO_EXCEPT { return _equal; }
744
745 // Hash Policy Interface
746
747 // load_factor() - current load factor (size / bucket_count)
748 float load_factor() const FL_NO_EXCEPT {
749 if (_buckets.size() == 0) {
750 return 0.0f;
751 }
752 return static_cast<float>(_size) / static_cast<float>(_buckets.size());
753 }
754
755 // max_load_factor() - get maximum load factor
757 // mLoadFactor is stored as u8 (0-255) representing 0.0-1.0
758 return static_cast<float>(mLoadFactor) / 255.0f;
759 }
760
761 // max_load_factor(float ml) - set maximum load factor
763 setLoadFactor(ml);
764 }
765
766 // bucket_count() - number of buckets
767 fl::size bucket_count() const FL_NO_EXCEPT {
768 return _buckets.size();
769 }
770
771 // rehash(size_type n) - change number of buckets to at least n
772 void rehash(fl::size n) FL_NO_EXCEPT {
773 // Ensure n is at least as large as the current number of elements
774 fl::size min_buckets = _size;
775 if (n < min_buckets) {
776 n = min_buckets;
777 }
778
779 // Only rehash if we need more buckets than we currently have
780 if (n > _buckets.size()) {
782 }
783 // If n <= current bucket count, do nothing (std::unordered_map behavior)
784 }
785
786 // reserve(size_type n) - reserve capacity for at least n elements
787 void reserve(fl::size n) FL_NO_EXCEPT {
788 // Calculate required buckets to hold n elements without exceeding load factor
789 float max_lf = max_load_factor();
790 if (max_lf <= 0.0f) {
791 max_lf = 0.7f; // Default if somehow zero
792 }
793 // Required buckets = ceil(n / max_load_factor)
794 fl::size required_buckets = static_cast<fl::size>(
795 static_cast<float>(n) / max_lf + 0.999999f // Add almost 1 to simulate ceil
796 );
797 if (required_buckets > _buckets.size()) {
798 rehash(required_buckets);
799 }
800 }
801
802 private:
803 static fl::size npos() FL_NO_EXCEPT {
804 return static_cast<fl::size>(-1);
805 }
806
807 // Helper methods to check entry state
808 bool is_occupied(fl::size idx) const FL_NO_EXCEPT { return _occupied.test(idx); }
809
810 bool is_deleted(fl::size idx) const FL_NO_EXCEPT { return _deleted.test(idx); }
811
812 bool is_empty(fl::size idx) const FL_NO_EXCEPT {
813 return !is_occupied(idx) && !is_deleted(idx);
814 }
815
816 void mark_occupied(fl::size idx) FL_NO_EXCEPT {
817 _occupied.set(idx);
818 _deleted.reset(idx);
819 }
820
821 void mark_deleted(fl::size idx) FL_NO_EXCEPT {
822 _occupied.reset(idx);
823 _deleted.set(idx);
824 }
825
826 void mark_empty(fl::size idx) FL_NO_EXCEPT {
827 _occupied.reset(idx);
828 _deleted.reset(idx);
829 }
830
832 struct FL_ALIGN_AS_T(EntryAlign::value) Entry {
833 Key key;
834 T value;
835 void swap(Entry &other) FL_NO_EXCEPT {
836 fl::swap(key, other.key);
837 fl::swap(value, other.value);
838 }
839 };
840
841 static fl::size next_power_of_two(fl::size n) FL_NO_EXCEPT {
842 fl::size p = 1;
843 while (p < n)
844 p <<= 1;
845 return p;
846 }
847
849 const fl::size cap = _buckets.size();
850 const fl::size mask = cap - 1;
851 const fl::size h = _hash(key) & mask;
852 fl::size first_tomb = npos();
853
854 // Probe sequence comes from the shared (non-template) helper -- avoids
855 // duplicating the (cap <= 8 ? linear : quadratic-then-linear)
856 // branch logic across every unordered_map<K, V, Hash, Equal>
857 // instantiation. See FastLED #3235 Tier 1C.
858 for (fl::size i = 0; i < cap; ++i) {
859 const fl::size idx = detail::unordered_map_probe_idx(h, i, mask, cap);
860
861 if (is_empty(idx))
862 return {first_tomb != npos() ? first_tomb : idx, true};
863 if (is_deleted(idx)) {
864 if (first_tomb == npos())
865 first_tomb = idx;
866 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
867 return {idx, false};
868 }
869 }
870
871 return {npos(), false};
872 }
873
874 // Re-export probing constants for backward compat (they used to live as
875 // a local enum here). Sourced from the shared helper.
876 enum {
879 };
880
881 fl::size find_index(const Key &key) const FL_NO_EXCEPT {
882 const fl::size cap = _buckets.size();
883 const fl::size mask = cap - 1;
884 const fl::size h = _hash(key) & mask;
885
886 // Probe via the shared helper -- see find_slot() comment above.
887 for (fl::size i = 0; i < cap; ++i) {
888 const fl::size idx = detail::unordered_map_probe_idx(h, i, mask, cap);
889 if (is_empty(idx))
890 return npos();
891 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
892 return idx;
893 }
894
895 return npos();
896 }
897
899 const Key &key, const fl::bitset<1024> &occupied_set) const FL_NO_EXCEPT {
900 const fl::size cap = _buckets.size();
901 const fl::size mask = cap - 1;
902 const fl::size h = _hash(key) & mask;
903
904 // Probe via the shared helper -- see find_slot() comment above.
905 for (fl::size i = 0; i < cap; ++i) {
906 const fl::size idx = detail::unordered_map_probe_idx(h, i, mask, cap);
907 if (!occupied_set.test(idx)) {
908 return idx;
909 }
910 }
911 return npos();
912 }
913
914 void rehash_internal(fl::size new_cap) FL_NO_EXCEPT {
915 new_cap = next_power_of_two(new_cap);
917 fl::bitset<1024> old_occupied = _occupied;
918
919 _buckets.swap(old);
920 _buckets.clear();
921 _buckets.assign(new_cap, Entry{});
922
923 _occupied.reset();
924 _occupied.resize(new_cap);
925 _deleted.reset();
926 _deleted.resize(new_cap);
927
928 _size = _tombstones = 0;
929
930 for (fl::size i = 0; i < old.size(); i++) {
931 if (old_occupied.test(i))
932 insert(fl::move(old[i].key), fl::move(old[i].value));
933 }
934 }
935
936 // Rehash the inline buckets without resizing
940 // filter out tombstones and compact
941 fl::size cap = _buckets.size();
942 fl::size pos = 0;
943 // compact live elements to the left.
944 for (fl::size i = 0; i < cap; ++i) {
945 if (is_occupied(i)) {
946 if (pos != i) {
948 mark_empty(i);
950 }
951 ++pos;
952 } else if (is_deleted(i)) {
953 mark_empty(i);
954 }
955 }
956
957 fl::bitset<1024> occupied; // Preallocate a bitset of size 1024
958 // swap the components, this will happen at most N times,
959 // use the occupied bitset to track which entries are occupied
960 // in the array rather than just copied in.
962 for (fl::size i = 0; i < _size; ++i) {
963 const bool already_finished = occupied.test(i);
964 if (already_finished) {
965 continue;
966 }
967 auto &e = _buckets[i];
968 // FASTLED_ASSERT(e.state == EntryState::Occupied,
969 // "unordered_map::rehash_inline_no_resize: invalid
970 // state");
971
972 fl::size idx = find_unoccupied_index_using_bitset(e.key, occupied);
973 if (idx == npos()) {
974 // no more space
975 FASTLED_ASSERT(
976 false, "unordered_map::rehash_inline_no_resize: invalid index");
977 return;
978 }
979 // if idx < pos then we are moving the entry to a new location
980 FASTLED_ASSERT(!tmp,
981 "unordered_map::rehash_inline_no_resize: invalid tmp");
982 if (idx >= _size) {
983 // directly move it now
984 _buckets[idx] = fl::move(e);
985 continue;
986 }
987 tmp = fl::move(e);
988 occupied.set(idx);
989 _buckets[idx] = fl::move(*tmp.ptr());
990 while (!tmp.empty()) {
991 // we have to find a place for temp.
992 // find new position for tmp.
993 auto key = tmp.ptr()->key;
994 fl::size new_idx =
996 if (new_idx == npos()) {
997 // no more space
998 FASTLED_ASSERT(
999 false,
1000 "unordered_map::rehash_inline_no_resize: invalid index");
1001 return;
1002 }
1003 occupied.set(new_idx);
1004 if (new_idx < _size) {
1005 // we have to swap the entry at new_idx with tmp
1006 fl::optional<Entry> tmp2 = fl::move(_buckets[new_idx]);
1007 _buckets[new_idx] = fl::move(*tmp.ptr());
1008 tmp = fl::move(tmp2);
1009 } else {
1010 // we can just move tmp to new_idx
1011 _buckets[new_idx] = fl::move(*tmp.ptr());
1012 tmp.reset();
1013 }
1014 }
1015 FASTLED_ASSERT(
1016 occupied.test(i),
1017 "unordered_map::rehash_inline_no_resize: invalid occupied");
1018 FASTLED_ASSERT(
1019 tmp.empty(), "unordered_map::rehash_inline_no_resize: invalid tmp");
1020 }
1021 // Reset tombstones count since we've cleared all deleted entries
1022 _tombstones = 0;
1023 }
1025
1026 public:
1028 bool operator==(const unordered_map& other) const FL_NO_EXCEPT {
1029 if (size() != other.size()) return false;
1030 for (const auto& kv : *this) {
1031 auto it = other.find(kv.first);
1032 if (it == other.end() || it->second != kv.second) return false;
1033 }
1034 return true;
1035 }
1036
1038 bool operator!=(const unordered_map& other) const FL_NO_EXCEPT {
1039 return !(*this == other);
1040 }
1041
1042 memory_resource* get_memory_resource() const FL_NO_EXCEPT { return _buckets.get_resource(); }
1043
1044 private:
1046 fl::size _size;
1047 fl::size _tombstones;
1049 fl::bitset<1024> _occupied;
1050 fl::bitset<1024> _deleted;
1052 KeyEqual _equal;
1053};
1054
1055// begin using declarations for stl compatibility
1056template <typename T> using equal_to = EqualTo<T>;
1057
1058// Legacy alias for backwards compatibility - use fl::unordered_map instead
1059template <typename Key, typename T, typename Hash = Hash<Key>,
1060 typename KeyEqual = equal_to<Key>>
1062// end using declarations for stl compatibility
1063
1064} // namespace fl
1065
uint8_t pos
Definition Blur.ino:11
Alignment macros and utilities for FastLED.
void reset() FL_NO_EXCEPT
Definition optional.h:46
T * ptr() FL_NO_EXCEPT
Definition optional.h:43
bool empty() const FL_NO_EXCEPT
Definition optional.h:41
Polymorphic memory resource base class (PMR-style).
iterator find(const Key &key) FL_NO_EXCEPT
void reserve(fl::size n) FL_NO_EXCEPT
bool erase(const Key &key) FL_NO_EXCEPT
iterator emplace_hint(const_iterator hint, Args &&... args) FL_NO_EXCEPT
unordered_map & operator=(fl::initializer_list< pair< Key, T > > init) FL_NO_EXCEPT
const T * find_value(const Key &key) const FL_NO_EXCEPT
Hash< vec2< u16 > > _hash
fl::bitset< 1024 > _occupied
unordered_map(InputIt first, InputIt last) FL_NO_EXCEPT
bool is_occupied(fl::size idx) const FL_NO_EXCEPT
fl::size max_size() const FL_NO_EXCEPT
unordered_map(fl::size n, const Hash &hf, const KeyEqual &eq) FL_NO_EXCEPT
const_iterator find(const Key &key) const FL_NO_EXCEPT
bool operator!=(const unordered_map &other) const FL_NO_EXCEPT
Inequality comparison.
bool is_deleted(fl::size idx) const FL_NO_EXCEPT
Hash hash_function() const FL_NO_EXCEPT
fl::size size() const FL_NO_EXCEPT
void swap(unordered_map &other) FL_NO_EXCEPT
unordered_map & operator=(unordered_map &&other) FL_NO_EXCEPT
bool contains(const Key &key) const FL_NO_EXCEPT
unordered_map() FL_NO_EXCEPT
unordered_map & operator=(const unordered_map &other) FL_NO_EXCEPT
bool remove(const Key &key) FL_NO_EXCEPT
void rehash_internal(fl::size new_cap) FL_NO_EXCEPT
void clear() FL_NO_EXCEPT
pair< iterator, iterator > equal_range(const Key &key) FL_NO_EXCEPT
pair< iterator, bool > insert_or_assign(const Key &key, T &&value) FL_NO_EXCEPT
bool needs_rehash() const FL_NO_EXCEPT
EqualTo< vec2< u16 > > _equal
float max_load_factor() const FL_NO_EXCEPT
static fl::size npos() FL_NO_EXCEPT
static bool NeedsRehash(fl::size size, fl::size bucket_size, fl::size tombstones, u8 load_factor) FL_NO_EXCEPT
fl::vector_inlined< Entry, FASTLED_HASHMAP_INLINED_COUNT > _buckets
float load_factor() const FL_NO_EXCEPT
pair< iterator, bool > insert(const vec2< u16 > &key, const u8 &value) FL_NO_EXCEPT
const T & at(const Key &key) const FL_NO_EXCEPT
pair< iterator, bool > insert(const pair< Key, T > &kv) FL_NO_EXCEPT
void insert(InputIt first, InputIt last) FL_NO_EXCEPT
void mark_occupied(fl::size idx) FL_NO_EXCEPT
pair< iterator, bool > emplace(Args &&... args) FL_NO_EXCEPT
iterator end() FL_NO_EXCEPT
KeyEqual key_eq() const FL_NO_EXCEPT
pair< iterator, bool > insert_or_assign(Key &&key, T &&value) FL_NO_EXCEPT
bool empty() const FL_NO_EXCEPT
const_iterator cend() const FL_NO_EXCEPT
unordered_map(fl::size initial_capacity, float max_load) FL_NO_EXCEPT
pair< iterator, bool > insert(pair< Key, T > &&kv) FL_NO_EXCEPT
iterator erase(iterator it) FL_NO_EXCEPT
unordered_map(memory_resource *resource) FL_NO_EXCEPT
pair< iterator, bool > try_emplace(const Key &k, Args &&... args) FL_NO_EXCEPT
void mark_empty(fl::size idx) FL_NO_EXCEPT
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_NULL_DEREFERENCE void rehash_inline_no_resize() FL_NO_EXCEPT
bool operator==(const unordered_map &other) const FL_NO_EXCEPT
Equality comparison (map equality: same key-value pairs, order-independent)
bool is_empty(fl::size idx) const FL_NO_EXCEPT
T * find_value(const Key &key) FL_NO_EXCEPT
struct FL_ALIGN_AS_T(EntryAlign::value) Entry
void mark_deleted(fl::size idx) FL_NO_EXCEPT
void setLoadFactor(float f) FL_NO_EXCEPT
void max_load_factor(float ml) FL_NO_EXCEPT
memory_resource * get_memory_resource() const FL_NO_EXCEPT
fl::size find_unoccupied_index_using_bitset(const Key &key, const fl::bitset< 1024 > &occupied_set) const FL_NO_EXCEPT
unordered_map(fl::initializer_list< pair< Key, T > > init) FL_NO_EXCEPT
max_align< vec2< u16 >, u8 > EntryAlign
const_iterator begin() const FL_NO_EXCEPT
const_iterator cbegin() const FL_NO_EXCEPT
iterator begin() FL_NO_EXCEPT
pair< iterator, bool > insert(Key &&key, T &&value) FL_NO_EXCEPT
fl::size bucket_count() const FL_NO_EXCEPT
pair< fl::size, bool > find_slot(const vec2< u16 > &key) const FL_NO_EXCEPT
void insert(fl::initializer_list< pair< Key, T > > init) FL_NO_EXCEPT
pair< iterator, bool > try_emplace(Key &&k, Args &&... args) FL_NO_EXCEPT
T & at(const Key &key) FL_NO_EXCEPT
void rehash(fl::size n) FL_NO_EXCEPT
fl::size _tombstones
T & operator[](const Key &key) FL_NO_EXCEPT
pair< const_iterator, const_iterator > equal_range(const Key &key) const FL_NO_EXCEPT
fl::size capacity() const FL_NO_EXCEPT
iterator erase(const_iterator first, const_iterator last) FL_NO_EXCEPT
fl::bitset< 1024 > _deleted
unordered_map(const unordered_map &other) FL_NO_EXCEPT
static fl::size next_power_of_two(fl::size n) FL_NO_EXCEPT
fl::size find_index(const vec2< u16 > &key) const FL_NO_EXCEPT
const_iterator end() const FL_NO_EXCEPT
unordered_map(fl::size initial_capacity) FL_NO_EXCEPT
unordered_map(unordered_map &&other) FL_NO_EXCEPT
fl::size count(const Key &key) const FL_NO_EXCEPT
fl::size size() const FL_NO_EXCEPT
#define FL_DISABLE_WARNING_PUSH
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
#define FL_DISABLE_WARNING_POP
PMR-style polymorphic memory resource for type-erased allocation.
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:30
constexpr T && forward(typename remove_reference< T >::type &t) FL_NO_EXCEPT
@ kUnorderedMapQuadraticProbingTries
@ kUnorderedMapLinearProbeOnlyThreshold
FL_NO_INLINE fl::size unordered_map_probe_idx(fl::size h, fl::size i, fl::size mask, fl::size cap) FL_NO_EXCEPT
unsigned char u8
Definition stdint.h:131
void swap(array< T, N > &lhs, array< T, N > &rhs) FL_NO_EXCEPT
Definition array.h:209
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
constexpr int type_rank< T >::value
unordered_map< Key, T, Hash, KeyEqual > hash_map
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NO_EXCEPT
Optional< T > optional
Definition optional.h:16
FASTLED_FORCE_INLINE U map_range(T value, T in_min, T in_max, U out_min, U out_max) FL_NO_EXCEPT
Definition math.h:182
void init(Context &ctx, int w, int h) FL_NO_EXCEPT
Definition engine.h:133
EqualTo< T > equal_to
To bit_cast(const From &from) FL_NO_EXCEPT
Definition bit_cast.h:48
VectorN< T, INLINED_SIZE > vector_inlined
Definition vector.h:1137
Base definition for an LED controller.
Definition crgb.hpp:179
corkscrew_args args
Definition old.h:149
#define FL_ALIGN
Definition Keyboard.h:22
bool operator()(const T &a, const T &b) const FL_NO_EXCEPT
T1 first
Definition pair.h:17
T2 second
Definition pair.h:18
void advance_to_occupied() FL_NO_EXCEPT
fl::forward_iterator_tag iterator_category
const_iterator(const unordered_map *m, fl::size idx) FL_NO_EXCEPT
reference operator*() const FL_NO_EXCEPT
bool operator!=(const const_iterator &o) const FL_NO_EXCEPT
bool operator==(const const_iterator &o) const FL_NO_EXCEPT
pointer operator->() const FL_NO_EXCEPT
const_iterator(const iterator &it) FL_NO_EXCEPT
const_iterator & operator++() FL_NO_EXCEPT
const_iterator operator++(int) FL_NO_EXCEPT
iterator & operator++() FL_NO_EXCEPT
bool operator==(const iterator &o) const FL_NO_EXCEPT
fl::forward_iterator_tag iterator_category
pointer operator->() const FL_NO_EXCEPT
iterator operator++(int) FL_NO_EXCEPT
reference operator*() const FL_NO_EXCEPT
bool operator!=(const iterator &o) const FL_NO_EXCEPT
void advance_to_occupied() FL_NO_EXCEPT
pair< const Key, T > value_type
iterator(unordered_map *m, fl::size idx) FL_NO_EXCEPT
#define FASTLED_HASHMAP_INLINED_COUNT