FastLED 3.10.9
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/new.h" // placement new for the rehash scratch // IWYU pragma: keep
26#include "fl/system/sketch_macros.h" // FL_PLATFORM_HAS_LARGE_MEMORY
27#include "fl/stl/vector.h"
28#include "fl/stl/unordered_map_basic.h" // type-erased probing helper (#3235 Tier 1C)
29#include "fl/stl/initializer_list.h" // IWYU pragma: keep
31#include "fl/stl/noexcept.h"
32
35
36namespace fl {
37
38// // begin using declarations for stl compatibility
39// use fl::equal_to;
40// use fl::unordered_map;
41// // end using declarations for stl compatibility
42
45template <typename T> struct EqualTo {
46 bool operator()(const T &a, const T &b) const FL_NO_EXCEPT { return a == b; }
47};
49
50// -- unordered_map class
51// ------------------------------------------------------------- Begin unordered_map
52// class
53
54#ifndef FASTLED_HASHMAP_INLINED_COUNT
55#define FASTLED_HASHMAP_INLINED_COUNT 8
56#endif
57
58template <typename Key, typename T, typename Hash = Hash<Key>,
59 typename KeyEqual = EqualTo<Key>,
60 int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT>
62 public:
64 unordered_map(fl::size initial_capacity) FL_NO_EXCEPT : unordered_map(initial_capacity, 0.7f) {}
65
73
74 unordered_map(fl::size initial_capacity, float max_load)
75 FL_NO_EXCEPT : _buckets(next_power_of_two(initial_capacity)), _size(0),
76 _tombstones(0), _occupied(next_power_of_two(initial_capacity)),
77 _deleted(next_power_of_two(initial_capacity)) {
78 setLoadFactor(max_load);
79 }
80
81 // Copy constructor
83 : _buckets(other._buckets.size()), _size(0), _tombstones(0),
84 mLoadFactor(other.mLoadFactor), _occupied(other._buckets.size()),
85 _deleted(other._buckets.size()), _hash(other._hash), _equal(other._equal) {
86 // Copy all occupied entries using insert to properly hash them
87 for (fl::size i = 0; i < other._buckets.size(); ++i) {
88 if (other.is_occupied(i)) {
89 insert(other._buckets[i].key, other._buckets[i].value);
90 }
91 }
92 }
93
94 // Move constructor
96 : _buckets(fl::move(other._buckets)), _size(other._size),
97 _tombstones(other._tombstones), mLoadFactor(other.mLoadFactor),
98 _occupied(fl::move(other._occupied)), _deleted(fl::move(other._deleted)),
99 _hash(fl::move(other._hash)), _equal(fl::move(other._equal)) {
100 // Reset other to valid empty state
101 other._size = 0;
102 other._tombstones = 0;
103 }
104
105 // Range constructor
106 template<typename InputIt>
107 unordered_map(InputIt first, InputIt last)
109 insert(first, last);
110 }
111
112 // Initializer list constructor
117
118 // Constructor with hash/equal/allocator parameters
119 unordered_map(fl::size n, const Hash& hf, const KeyEqual& eq)
122 _hash(hf), _equal(eq) {
123 setLoadFactor(0.7f);
124 }
125
126 // Copy assignment operator
128 if (this != &other) {
129 // Clear current content
130 clear();
131
132 // Resize if necessary
133 if (_buckets.size() != other._buckets.size()) {
134 _buckets.clear();
135 _buckets.assign(other._buckets.size(), Entry{});
136 _occupied.reset();
137 _occupied.resize(other._buckets.size());
138 _deleted.reset();
139 _deleted.resize(other._buckets.size());
140 }
141
142 // Copy all occupied entries
143 for (fl::size i = 0; i < other._buckets.size(); ++i) {
144 if (other.is_occupied(i)) {
145 _buckets[i] = other._buckets[i];
146 mark_occupied(i);
147 ++_size;
148 }
149 }
150
151 mLoadFactor = other.mLoadFactor;
152 _hash = other._hash;
153 _equal = other._equal;
154 }
155 return *this;
156 }
157
158 // Move assignment operator
160 if (this != &other) {
161 _buckets = fl::move(other._buckets);
162 _size = other._size;
163 _tombstones = other._tombstones;
164 mLoadFactor = other.mLoadFactor;
165 _occupied = fl::move(other._occupied);
166 _deleted = fl::move(other._deleted);
167 _hash = fl::move(other._hash);
168 _equal = fl::move(other._equal);
169
170 // Reset other to valid empty state
171 other._size = 0;
172 other._tombstones = 0;
173 }
174 return *this;
175 }
176
177 // Initializer list assignment operator
179 clear();
180 insert(init);
181 return *this;
182 }
183
185 f = fl::clamp(f, 0.f, 1.f);
186 mLoadFactor = fl::map_range<float, u8>(f, 0.f, 1.f, 0, 255);
187 }
188
189 // Iterator support.
190 struct iterator {
191 // Standard iterator typedefs
192 // using difference_type = std::ptrdiff_t;
193 using value_type = pair<const Key, T>; // Keep const Key as per standard
197
198 iterator() FL_NO_EXCEPT : _map(nullptr), _idx(0) {}
199 iterator(unordered_map *m, fl::size idx) FL_NO_EXCEPT : _map(m), _idx(idx) {
201 }
202
204 auto &e = _map->_buckets[_idx];
205 // Entry and pair<const Key, T> have the same memory layout
206 // Safe to reinterpret since we only add const to Key
207 return *fl::bit_cast<value_type*>(&e);
208 }
209
211 return &(operator*());
212 }
213
215 ++_idx;
217 return *this;
218 }
219
221 iterator tmp = *this;
222 ++(*this);
223 return tmp;
224 }
225
226 bool operator==(const iterator &o) const FL_NO_EXCEPT {
227 return _map == o._map && _idx == o._idx;
228 }
229 bool operator!=(const iterator &o) const FL_NO_EXCEPT { return !(*this == o); }
230
232 if (!_map)
233 return;
234 fl::size cap = _map->_buckets.size();
235 while (_idx < cap && !_map->is_occupied(_idx))
236 ++_idx;
237 }
238
239 private:
241 fl::size _idx;
242 friend class unordered_map;
243 };
244
246 // Standard iterator typedefs
247 // using difference_type = std::ptrdiff_t;
249 using pointer = const value_type *;
250 using reference = const value_type &;
252
254 const_iterator(const unordered_map *m, fl::size idx) FL_NO_EXCEPT : _map(m), _idx(idx) {
256 }
257 const_iterator(const iterator &it) FL_NO_EXCEPT : _map(it._map), _idx(it._idx) {}
258
260 auto &e = _map->_buckets[_idx];
261 // Entry and pair<const Key, T> have the same memory layout
262 // Safe to reinterpret since we only add const to Key
264 }
265
267 return &(operator*());
268 }
269
271 ++_idx;
273 return *this;
274 }
275
277 const_iterator tmp = *this;
278 ++(*this);
279 return tmp;
280 }
281
283 return _map == o._map && _idx == o._idx;
284 }
285 bool operator!=(const const_iterator &o) const FL_NO_EXCEPT { return !(*this == o); }
286
288 if (!_map)
289 return;
290 fl::size cap = _map->_buckets.size();
291 while (_idx < cap && !_map->is_occupied(_idx))
292 ++_idx;
293 }
294
295 friend class unordered_map;
296
297 private:
299 fl::size _idx;
300 };
301
302 iterator begin() FL_NO_EXCEPT { return iterator(this, 0); }
303 iterator end() FL_NO_EXCEPT { return iterator(this, _buckets.size()); }
304 const_iterator begin() const FL_NO_EXCEPT { return const_iterator(this, 0); }
305 const_iterator end() const FL_NO_EXCEPT { return const_iterator(this, _buckets.size()); }
306 const_iterator cbegin() const FL_NO_EXCEPT { return const_iterator(this, 0); }
307 const_iterator cend() const FL_NO_EXCEPT {
308 return const_iterator(this, _buckets.size());
309 }
310
311 static bool NeedsRehash(fl::size size, fl::size bucket_size, fl::size tombstones,
313 // (size + tombstones) << 8 : multiply numerator by 256
314 // capacity * max_load : denominator * threshold
315 u32 lhs = (size + tombstones) << 8;
316 u32 rhs = (bucket_size * load_factor);
317 return lhs > rhs;
318 }
319
320 // returns true if (size + tombs)/capacity > _max_load/256
323 }
324
325 // insert or overwrite - returns pair<iterator, bool>
326 // iterator points to element, bool is true if inserted, false if updated
328 const bool will_rehash = needs_rehash();
329 if (will_rehash) {
330 // if half the buckets are tombstones, rehash inline to prevent
331 // memory spill over into the heap.
332 if (_tombstones > _size) {
334 } else {
335 rehash_internal(_buckets.size() * 2);
336 }
337 }
338 fl::size idx;
339 bool is_new;
341 idx = p.first;
342 is_new = p.second;
343 if (is_new) {
344 _buckets[idx].key = key;
345 _buckets[idx].value = value;
346 mark_occupied(idx);
347 ++_size;
348 } else {
349 FASTLED_ASSERT(idx != npos(), "unordered_map::insert: invalid index");
350 _buckets[idx].value = value;
351 }
352 return {iterator(this, idx), is_new};
353 }
354
355 // Move version of insert - returns pair<iterator, bool>
356 // iterator points to element, bool is true if inserted, false if updated
358 const bool will_rehash = needs_rehash();
359 if (will_rehash) {
360 // if half the buckets are tombstones, rehash inline to prevent
361 // memory spill over into the heap.
362 if (_tombstones > _size) {
364 } else {
365 rehash_internal(_buckets.size() * 2);
366 }
367 }
368 fl::size idx;
369 bool is_new;
371 idx = p.first;
372 is_new = p.second;
373 if (is_new) {
374 _buckets[idx].key = fl::move(key);
375 _buckets[idx].value = fl::move(value);
376 mark_occupied(idx);
377 ++_size;
378 } else {
379 FASTLED_ASSERT(idx != npos(), "unordered_map::insert: invalid index");
380 _buckets[idx].value = fl::move(value);
381 }
382 return {iterator(this, idx), is_new};
383 }
384
385 // Pair-based insert (const) - std::unordered_map compatible
386 // Insert a pair<const Key, T> or pair<Key, T>
388 return insert(kv.first, kv.second);
389 }
390
391 // Pair-based insert (move) - std::unordered_map compatible
392 // Insert a pair<const Key, T> or pair<Key, T> with move semantics
394 return insert(fl::move(kv.first), fl::move(kv.second));
395 }
396
397 // Range insert - insert elements from iterator range [first, last)
398 // std::unordered_map compatible
399 template<typename InputIt>
400 void insert(InputIt first, InputIt last) FL_NO_EXCEPT {
401 for (; first != last; ++first) {
402 insert(*first); // Uses pair-based insert
403 }
404 }
405
406 // Initializer list insert - std::unordered_map compatible
407 // Insert elements from an initializer list like: m.insert({{1, "a"}, {2, "b"}})
408 void insert(fl::initializer_list<pair<Key, T>> init) FL_NO_EXCEPT {
409 for (const auto& kv : init) {
410 insert(kv); // Uses pair-based insert
411 }
412 }
413
414 // insert_or_assign() - C++17: insert new element or update existing
415 // Returns pair<iterator, bool> where bool is true if inserted, false if updated
417 const bool will_rehash = needs_rehash();
418 if (will_rehash) {
419 if (_tombstones > _size) {
421 } else {
422 rehash_internal(_buckets.size() * 2);
423 }
424 }
425 fl::size idx;
426 bool is_new;
428 idx = p.first;
429 is_new = p.second;
430 if (is_new) {
431 _buckets[idx].key = key;
432 _buckets[idx].value = fl::move(value);
433 mark_occupied(idx);
434 ++_size;
435 } else {
436 FASTLED_ASSERT(idx != npos(), "unordered_map::insert_or_assign: invalid index");
437 _buckets[idx].value = fl::move(value);
438 }
439 return {iterator(this, idx), is_new};
440 }
441
442 // insert_or_assign() with move key - C++17
444 const bool will_rehash = needs_rehash();
445 if (will_rehash) {
446 if (_tombstones > _size) {
448 } else {
449 rehash_internal(_buckets.size() * 2);
450 }
451 }
452 fl::size idx;
453 bool is_new;
455 idx = p.first;
456 is_new = p.second;
457 if (is_new) {
458 _buckets[idx].key = fl::move(key);
459 _buckets[idx].value = fl::move(value);
460 mark_occupied(idx);
461 ++_size;
462 } else {
463 FASTLED_ASSERT(idx != npos(), "unordered_map::insert_or_assign: invalid index");
464 _buckets[idx].value = fl::move(value);
465 }
466 return {iterator(this, idx), is_new};
467 }
468
469 // emplace() - construct element in-place from arguments
470 // For pairs, accepts (key, value) arguments to construct pair<Key, T>
471 template<typename... Args>
473 // Construct a pair from the arguments
475 // Use move-based insert since we just constructed this pair
476 return insert(fl::move(kv));
477 }
478
479 // emplace_hint() - construct element in-place with hint
480 // Hint is ignored for hash maps (no spatial locality benefits)
481 template<typename... Args>
482 iterator emplace_hint(const_iterator hint, Args&&... args) FL_NO_EXCEPT {
483 (void)hint; // Hint is ignored in hash maps
484 return emplace(fl::forward<Args>(args)...).first;
485 }
486
487 // try_emplace() - C++17: emplace only if key doesn't exist
488 // Advantage: doesn't move key if element already exists
489 template<typename... Args>
491 const bool will_rehash = needs_rehash();
492 if (will_rehash) {
493 if (_tombstones > _size) {
495 } else {
496 rehash_internal(_buckets.size() * 2);
497 }
498 }
499 fl::size idx;
500 bool is_new;
502 idx = p.first;
503 is_new = p.second;
504 if (is_new) {
505 // Key doesn't exist, construct in place
506 _buckets[idx].key = k;
507 _buckets[idx].value = T(fl::forward<Args>(args)...);
508 mark_occupied(idx);
509 ++_size;
510 }
511 // If not new, key already exists - don't construct value, just return existing iterator
512 return {iterator(this, idx), is_new};
513 }
514
515 // try_emplace() with move key - C++17
516 template<typename... Args>
518 const bool will_rehash = needs_rehash();
519 if (will_rehash) {
520 if (_tombstones > _size) {
522 } else {
523 rehash_internal(_buckets.size() * 2);
524 }
525 }
526 fl::size idx;
527 bool is_new;
529 idx = p.first;
530 is_new = p.second;
531 if (is_new) {
532 // Key doesn't exist, construct in place with moved key
533 _buckets[idx].key = fl::move(k);
534 _buckets[idx].value = T(fl::forward<Args>(args)...);
535 mark_occupied(idx);
536 ++_size;
537 }
538 // If not new, key already exists - don't move key or construct value
539 return {iterator(this, idx), is_new};
540 }
541
542 // remove key; returns true if removed
543 bool remove(const Key &key) FL_NO_EXCEPT {
544 auto idx = find_index(key);
545 if (idx == npos())
546 return false;
547 mark_deleted(idx);
548 --_size;
549 ++_tombstones;
550 return true;
551 }
552
553 bool erase(const Key &key) FL_NO_EXCEPT { return remove(key); }
554
555 // Iterator-based erase - more efficient when you already have the iterator position
556 iterator erase(iterator it) FL_NO_EXCEPT {
557 if (it == end() || it._map != this) {
558 return end(); // Invalid iterator
559 }
560
561 // Mark the current position as deleted
562 mark_deleted(it._idx);
563 --_size;
564 ++_tombstones;
565
566 // Advance to next valid element and return iterator to it
567 ++it._idx;
568 it.advance_to_occupied();
569 return it;
570 }
571
572 // Range erase - erase elements in range [first, last)
573 iterator erase(const_iterator first, const_iterator last) FL_NO_EXCEPT {
574 if (first._map != this || last._map != this) {
575 return end(); // Invalid iterators
576 }
577
578 // Erase each element in the range
579 // We need to convert const_iterator to iterator for the return value
580 iterator current(this, first._idx);
581 current.advance_to_occupied();
582
583 while (current != end() && current._idx < last._idx) {
584 fl::size idx_to_erase = current._idx;
585 ++current;
586 current.advance_to_occupied();
587
588 // Erase the element at idx_to_erase
589 mark_deleted(idx_to_erase);
590 --_size;
591 ++_tombstones;
592 }
593
594 return current;
595 }
596
598 _buckets.assign(_buckets.size(), Entry{});
599 _occupied.reset();
600 _deleted.reset();
601 _size = _tombstones = 0;
602 }
603
604 // swap() - swap contents with another unordered_map
606 // Swap all member variables
607 _buckets.swap(other._buckets);
608 fl::swap(_size, other._size);
609 fl::swap(_tombstones, other._tombstones);
610 fl::swap(mLoadFactor, other.mLoadFactor);
611 fl::swap(_occupied, other._occupied);
612 fl::swap(_deleted, other._deleted);
613 fl::swap(_hash, other._hash);
614 fl::swap(_equal, other._equal);
615 }
616
617 // find pointer to value or nullptr
618 T *find_value(const Key &key) FL_NO_EXCEPT {
619 auto idx = find_index(key);
620 return idx == npos() ? nullptr : &_buckets[idx].value;
621 }
622
623 const T *find_value(const Key &key) const FL_NO_EXCEPT {
624 auto idx = find_index(key);
625 return idx == npos() ? nullptr : &_buckets[idx].value;
626 }
627
628 iterator find(const Key &key) FL_NO_EXCEPT {
629 auto idx = find_index(key);
630 return idx == npos() ? end() : iterator(this, idx);
631 }
632
633 const_iterator find(const Key &key) const FL_NO_EXCEPT {
634 auto idx = find_index(key);
635 return idx == npos() ? end() : const_iterator(this, idx);
636 }
637
638 bool contains(const Key &key) const FL_NO_EXCEPT {
639 auto idx = find_index(key);
640 return idx != npos();
641 }
642
643 // at() - bounds-checked access, asserts if key not found
644 T &at(const Key &key) FL_NO_EXCEPT {
645 T* value = find_value(key);
646 FASTLED_ASSERT(value != nullptr, "unordered_map::at: key not found");
647 return *value;
648 }
649
650 const T &at(const Key &key) const FL_NO_EXCEPT {
651 const T* value = find_value(key);
652 FASTLED_ASSERT(value != nullptr, "unordered_map::at: key not found");
653 return *value;
654 }
655
656 // count() - returns 0 or 1 (unordered_map has unique keys)
657 fl::size count(const Key &key) const FL_NO_EXCEPT {
658 return contains(key) ? 1 : 0;
659 }
660
661 // equal_range() - returns pair of iterators [it, it+1) or [end, end)
663 iterator it = find(key);
664 if (it == end()) {
665 return {end(), end()};
666 }
667 iterator next = it;
668 ++next;
669 return {it, next};
670 }
671
673 const_iterator it = find(key);
674 if (it == end()) {
675 return {end(), end()};
676 }
677 const_iterator next = it;
678 ++next;
679 return {it, next};
680 }
681
682 // access or default-construct
683 T &operator[](const Key &key) FL_NO_EXCEPT {
684 fl::size idx;
685 bool is_new;
686
688 idx = p.first;
689 is_new = p.second;
690
691 // Check if find_slot failed to find a valid slot (unordered_map is full)
692 if (idx == npos()) {
693 // Need to resize to make room
694 if (needs_rehash()) {
695 // if half the buckets are tombstones, rehash inline to prevent
696 // memory growth. Otherwise, double the size.
697 if (_tombstones >= _buckets.size() / 2) {
699 } else {
700 rehash_internal(_buckets.size() * 2);
701 }
702 } else {
703 // Force a rehash with double size if needs_rehash() doesn't detect the issue
704 rehash_internal(_buckets.size() * 2);
705 }
706
707 // Try find_slot again after resize
708 p = find_slot(key);
709 idx = p.first;
710 is_new = p.second;
711
712 // If still npos() after resize, allocation failed catastrophically
713 if (idx == npos()) {
714 // This should never happen after a successful resize
715 static T default_value{};
716 FASTLED_ASSERT(false, "unordered_map::operator[]: Failed to allocate after rehash");
717 return default_value;
718 }
719 }
720
721 if (is_new) {
722 _buckets[idx].key = key;
723 _buckets[idx].value = T{};
724 mark_occupied(idx);
725 ++_size;
726 }
727 return _buckets[idx].value;
728 }
729
730 fl::size size() const FL_NO_EXCEPT { return _size; }
731 bool empty() const FL_NO_EXCEPT { return _size == 0; }
732 fl::size capacity() const FL_NO_EXCEPT { return _buckets.size(); }
733
734 // max_size() - theoretical maximum size
735 fl::size max_size() const FL_NO_EXCEPT {
736 // Return the maximum value that fl::size can hold, divided by the size of Entry
737 // to be conservative about memory limits
738 return static_cast<fl::size>(-1) / sizeof(Entry);
739 }
740
741 // hash_function() - return the hash functor
743
744 // key_eq() - return the key equality predicate
745 KeyEqual key_eq() const FL_NO_EXCEPT { return _equal; }
746
747 // Hash Policy Interface
748
749 // load_factor() - current load factor (size / bucket_count)
750 float load_factor() const FL_NO_EXCEPT {
751 if (_buckets.size() == 0) {
752 return 0.0f;
753 }
754 return static_cast<float>(_size) / static_cast<float>(_buckets.size());
755 }
756
757 // max_load_factor() - get maximum load factor
759 // mLoadFactor is stored as u8 (0-255) representing 0.0-1.0
760 return static_cast<float>(mLoadFactor) / 255.0f;
761 }
762
763 // max_load_factor(float ml) - set maximum load factor
765 setLoadFactor(ml);
766 }
767
768 // bucket_count() - number of buckets
769 fl::size bucket_count() const FL_NO_EXCEPT {
770 return _buckets.size();
771 }
772
773 // rehash(size_type n) - change number of buckets to at least n
774 void rehash(fl::size n) FL_NO_EXCEPT {
775 // Ensure n is at least as large as the current number of elements
776 fl::size min_buckets = _size;
777 if (n < min_buckets) {
778 n = min_buckets;
779 }
780
781 // Only rehash if we need more buckets than we currently have
782 if (n > _buckets.size()) {
784 }
785 // If n <= current bucket count, do nothing (std::unordered_map behavior)
786 }
787
788 // reserve(size_type n) - reserve capacity for at least n elements
789 void reserve(fl::size n) FL_NO_EXCEPT {
790 // Calculate required buckets to hold n elements without exceeding load factor
791 float max_lf = max_load_factor();
792 if (max_lf <= 0.0f) {
793 max_lf = 0.7f; // Default if somehow zero
794 }
795 // Required buckets = ceil(n / max_load_factor)
796 fl::size required_buckets = static_cast<fl::size>(
797 static_cast<float>(n) / max_lf + 0.999999f // Add almost 1 to simulate ceil
798 );
799 if (required_buckets > _buckets.size()) {
800 rehash(required_buckets);
801 }
802 }
803
804 private:
805 static fl::size npos() FL_NO_EXCEPT {
806 return static_cast<fl::size>(-1);
807 }
808
809 // Helper methods to check entry state
810 bool is_occupied(fl::size idx) const FL_NO_EXCEPT { return _occupied.test(idx); }
811
812 bool is_deleted(fl::size idx) const FL_NO_EXCEPT { return _deleted.test(idx); }
813
814 bool is_empty(fl::size idx) const FL_NO_EXCEPT {
815 return !is_occupied(idx) && !is_deleted(idx);
816 }
817
818 void mark_occupied(fl::size idx) FL_NO_EXCEPT {
819 _occupied.set(idx);
820 _deleted.reset(idx);
821 }
822
823 void mark_deleted(fl::size idx) FL_NO_EXCEPT {
824 _occupied.reset(idx);
825 _deleted.set(idx);
826 }
827
828 void mark_empty(fl::size idx) FL_NO_EXCEPT {
829 _occupied.reset(idx);
830 _deleted.reset(idx);
831 }
832
834 struct FL_ALIGN_AS_T(EntryAlign::value) Entry {
835 Key key;
836 T value;
837 void swap(Entry &other) FL_NO_EXCEPT {
838 fl::swap(key, other.key);
839 fl::swap(value, other.value);
840 }
841 };
842
843 static fl::size next_power_of_two(fl::size n) FL_NO_EXCEPT {
844 fl::size p = 1;
845 while (p < n)
846 p <<= 1;
847 return p;
848 }
849
851 const fl::size cap = _buckets.size();
852 const fl::size mask = cap - 1;
853 const fl::size h = _hash(key) & mask;
854 fl::size first_tomb = npos();
855
856 // Probe sequence comes from the shared (non-template) helper -- avoids
857 // duplicating the (cap <= 8 ? linear : quadratic-then-linear)
858 // branch logic across every unordered_map<K, V, Hash, Equal>
859 // instantiation. See FastLED #3235 Tier 1C.
860 for (fl::size i = 0; i < cap; ++i) {
861 const fl::size idx = detail::unordered_map_probe_idx(h, i, mask, cap);
862
863 if (is_empty(idx))
864 return {first_tomb != npos() ? first_tomb : idx, true};
865 if (is_deleted(idx)) {
866 if (first_tomb == npos())
867 first_tomb = idx;
868 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
869 return {idx, false};
870 }
871 }
872
873 return {npos(), false};
874 }
875
876 // Re-export probing constants for backward compat (they used to live as
877 // a local enum here). Sourced from the shared helper.
878 enum {
881 };
882
883 fl::size find_index(const Key &key) const FL_NO_EXCEPT {
884 const fl::size cap = _buckets.size();
885 const fl::size mask = cap - 1;
886 const fl::size h = _hash(key) & mask;
887
888 // Probe via the shared helper -- see find_slot() comment above.
889 for (fl::size i = 0; i < cap; ++i) {
890 const fl::size idx = detail::unordered_map_probe_idx(h, i, mask, cap);
891 if (is_empty(idx))
892 return npos();
893 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
894 return idx;
895 }
896
897 return npos();
898 }
899
901 const Key &key, const fl::bitset<1024> &occupied_set) const FL_NO_EXCEPT {
902 const fl::size cap = _buckets.size();
903 const fl::size mask = cap - 1;
904 const fl::size h = _hash(key) & mask;
905
906 // Probe via the shared helper -- see find_slot() comment above.
907 for (fl::size i = 0; i < cap; ++i) {
908 const fl::size idx = detail::unordered_map_probe_idx(h, i, mask, cap);
909 if (!occupied_set.test(idx)) {
910 return idx;
911 }
912 }
913 return npos();
914 }
915
916 void rehash_internal(fl::size new_cap) FL_NO_EXCEPT {
917 new_cap = next_power_of_two(new_cap);
918#if FL_PLATFORM_HAS_LARGE_MEMORY
919 // Large stacks: keep the scratch local and skip a heap round trip.
921 fl::bitset<1024> old_occupied = _occupied;
922#else
923 // Small stacks: the old buckets (an inline Entry buffer) and the
924 // 1024-bit occupancy copy cost about 1 KB on the stack, which
925 // overflowed ESP8266's 4 KB loop stack when an RPC registry grew
926 // during setup(), so they come from the map's memory resource.
927 struct Scratch {
928 explicit Scratch(fl::memory_resource* r) FL_NO_EXCEPT : buckets(r) {}
930 fl::bitset<1024> occupied;
931 };
932 fl::memory_resource* resource = _buckets.get_resource();
933 if (resource == nullptr) {
934 resource = fl::default_memory_resource();
935 }
936 void* mem = resource->allocate(sizeof(Scratch));
937 if (mem == nullptr) {
938 // Out of memory: stay at the current size. The table is at most
939 // 70% full, so inserts still find a slot and retry the grow.
940 return;
941 }
942 Scratch* scratch = new (mem) Scratch(resource);
943 fl::vector_inlined<Entry, INLINED_COUNT>& old = scratch->buckets;
944 fl::bitset<1024>& old_occupied = scratch->occupied;
945 old_occupied = _occupied;
946#endif
947
948 _buckets.swap(old);
949 _buckets.clear();
950 _buckets.assign(new_cap, Entry{});
951
952 _occupied.reset();
953 _occupied.resize(new_cap);
954 _deleted.reset();
955 _deleted.resize(new_cap);
956
957 _size = _tombstones = 0;
958
959 for (fl::size i = 0; i < old.size(); i++) {
960 if (old_occupied.test(i))
961 insert(fl::move(old[i].key), fl::move(old[i].value));
962 }
963#if !FL_PLATFORM_HAS_LARGE_MEMORY
964 scratch->~Scratch();
965 resource->deallocate(mem, sizeof(Scratch));
966#endif
967 }
968
969 // Rehash the inline buckets without resizing
973 // filter out tombstones and compact
974 fl::size cap = _buckets.size();
975 fl::size pos = 0;
976 // compact live elements to the left.
977 for (fl::size i = 0; i < cap; ++i) {
978 if (is_occupied(i)) {
979 if (pos != i) {
981 mark_empty(i);
983 }
984 ++pos;
985 } else if (is_deleted(i)) {
986 mark_empty(i);
987 }
988 }
989
990 fl::bitset<1024> occupied; // Preallocate a bitset of size 1024
991 // swap the components, this will happen at most N times,
992 // use the occupied bitset to track which entries are occupied
993 // in the array rather than just copied in.
995 for (fl::size i = 0; i < _size; ++i) {
996 const bool already_finished = occupied.test(i);
997 if (already_finished) {
998 continue;
999 }
1000 auto &e = _buckets[i];
1001 // FASTLED_ASSERT(e.state == EntryState::Occupied,
1002 // "unordered_map::rehash_inline_no_resize: invalid
1003 // state");
1004
1005 fl::size idx = find_unoccupied_index_using_bitset(e.key, occupied);
1006 if (idx == npos()) {
1007 // no more space
1008 FASTLED_ASSERT(
1009 false, "unordered_map::rehash_inline_no_resize: invalid index");
1010 return;
1011 }
1012 // if idx < pos then we are moving the entry to a new location
1013 FASTLED_ASSERT(!tmp,
1014 "unordered_map::rehash_inline_no_resize: invalid tmp");
1015 if (idx >= _size) {
1016 // directly move it now
1017 _buckets[idx] = fl::move(e);
1018 continue;
1019 }
1020 tmp = fl::move(e);
1021 occupied.set(idx);
1022 _buckets[idx] = fl::move(*tmp.ptr());
1023 while (!tmp.empty()) {
1024 // we have to find a place for temp.
1025 // find new position for tmp.
1026 auto key = tmp.ptr()->key;
1027 fl::size new_idx =
1029 if (new_idx == npos()) {
1030 // no more space
1031 FASTLED_ASSERT(
1032 false,
1033 "unordered_map::rehash_inline_no_resize: invalid index");
1034 return;
1035 }
1036 occupied.set(new_idx);
1037 if (new_idx < _size) {
1038 // we have to swap the entry at new_idx with tmp
1039 fl::optional<Entry> tmp2 = fl::move(_buckets[new_idx]);
1040 _buckets[new_idx] = fl::move(*tmp.ptr());
1041 tmp = fl::move(tmp2);
1042 } else {
1043 // we can just move tmp to new_idx
1044 _buckets[new_idx] = fl::move(*tmp.ptr());
1045 tmp.reset();
1046 }
1047 }
1048 FASTLED_ASSERT(
1049 occupied.test(i),
1050 "unordered_map::rehash_inline_no_resize: invalid occupied");
1051 FASTLED_ASSERT(
1052 tmp.empty(), "unordered_map::rehash_inline_no_resize: invalid tmp");
1053 }
1054 // Reset tombstones count since we've cleared all deleted entries
1055 _tombstones = 0;
1056 }
1058
1059 public:
1061 bool operator==(const unordered_map& other) const FL_NO_EXCEPT {
1062 if (size() != other.size()) return false;
1063 for (const auto& kv : *this) {
1064 auto it = other.find(kv.first);
1065 if (it == other.end() || it->second != kv.second) return false;
1066 }
1067 return true;
1068 }
1069
1071 bool operator!=(const unordered_map& other) const FL_NO_EXCEPT {
1072 return !(*this == other);
1073 }
1074
1075 memory_resource* get_memory_resource() const FL_NO_EXCEPT { return _buckets.get_resource(); }
1076
1077 private:
1079 fl::size _size;
1080 fl::size _tombstones;
1082 fl::bitset<1024> _occupied;
1083 fl::bitset<1024> _deleted;
1085 KeyEqual _equal;
1086};
1087
1088// begin using declarations for stl compatibility
1089template <typename T> using equal_to = EqualTo<T>;
1090
1091// Legacy alias for backwards compatibility - use fl::unordered_map instead
1092template <typename Key, typename T, typename Hash = Hash<Key>,
1093 typename KeyEqual = equal_to<Key>>
1095// end using declarations for stl compatibility
1096
1097} // namespace fl
1098
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
void deallocate(void *p, fl::size bytes) FL_NO_EXCEPT
void * allocate(fl::size bytes) FL_NO_EXCEPT
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
PMR-style polymorphic memory resource for type-erased allocation.
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:30
@ 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
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition s16x16x4.h:30
constexpr T && forward(typename remove_reference< T >::type &t) FL_NO_EXCEPT
Definition s16x16x4.h:236
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
memory_resource * default_memory_resource() FL_NO_EXCEPT
Get the default memory resource (wraps fl::Malloc / fl::Free / fl::realloc).
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.
corkscrew_args args
Definition old.h:149
#define FL_DISABLE_WARNING_PUSH
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
#define FL_DISABLE_WARNING_POP
#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