Blender V4.3
BLI_vector_set.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#pragma once
6
50#include "BLI_array.hh"
51#include "BLI_hash.hh"
52#include "BLI_hash_tables.hh"
55
56namespace blender {
57
58template<
63 typename Key,
67 typename ProbingStrategy = DefaultProbingStrategy,
72 typename Hash = DefaultHash<Key>,
77 typename IsEqual = DefaultEquality<Key>,
84 typename Slot = typename DefaultVectorSetSlot<Key>::type,
89 typename Allocator = GuardedAllocator>
90class VectorSet {
91 public:
92 using value_type = Key;
93 using pointer = Key *;
94 using const_pointer = const Key *;
95 using reference = Key &;
96 using const_reference = const Key &;
97 using iterator = Key *;
98 using const_iterator = const Key *;
100
101 private:
106 int64_t removed_slots_;
107 int64_t occupied_and_removed_slots_;
108
113 int64_t usable_slots_;
114
119 uint64_t slot_mask_;
120
122 BLI_NO_UNIQUE_ADDRESS Hash hash_;
123
125 BLI_NO_UNIQUE_ADDRESS IsEqual is_equal_;
126
128#define LOAD_FACTOR 1, 2
129 LoadFactor max_load_factor_ = LoadFactor(LOAD_FACTOR);
130 using SlotArray = Array<Slot, LoadFactor::compute_total_slots(4, LOAD_FACTOR), Allocator>;
131#undef LOAD_FACTOR
132
137 SlotArray slots_;
138
144 Key *keys_ = nullptr;
145
147#define VECTOR_SET_SLOT_PROBING_BEGIN(HASH, R_SLOT) \
148 SLOT_PROBING_BEGIN (ProbingStrategy, HASH, slot_mask_, SLOT_INDEX) \
149 auto &R_SLOT = slots_[SLOT_INDEX];
150#define VECTOR_SET_SLOT_PROBING_END() SLOT_PROBING_END()
151
152 public:
158 VectorSet(Allocator allocator = {}) noexcept
159 : removed_slots_(0),
160 occupied_and_removed_slots_(0),
161 usable_slots_(0),
162 slot_mask_(0),
163 slots_(1, allocator),
164 keys_(nullptr)
165 {
166 }
167
168 VectorSet(NoExceptConstructor, Allocator allocator = {}) : VectorSet(allocator) {}
169
170 VectorSet(Span<Key> keys, Allocator allocator = {}) : VectorSet(NoExceptConstructor(), allocator)
171 {
172 this->add_multiple(keys);
173 }
174
178 VectorSet(const std::initializer_list<Key> &keys, Allocator allocator = {})
179 : VectorSet(Span(keys), allocator)
180 {
181 }
182
184 {
185 destruct_n(keys_, this->size());
186 if (keys_ != nullptr) {
187 this->deallocate_keys_array(keys_);
188 }
189 }
190
191 VectorSet(const VectorSet &other) : slots_(other.slots_)
192 {
193 keys_ = this->allocate_keys_array(other.usable_slots_);
194 try {
195 uninitialized_copy_n(other.keys_, other.size(), keys_);
196 }
197 catch (...) {
198 this->deallocate_keys_array(keys_);
199 throw;
200 }
201
202 removed_slots_ = other.removed_slots_;
203 occupied_and_removed_slots_ = other.occupied_and_removed_slots_;
204 usable_slots_ = other.usable_slots_;
205 slot_mask_ = other.slot_mask_;
206 hash_ = other.hash_;
207 is_equal_ = other.is_equal_;
208 }
209
210 VectorSet(VectorSet &&other) noexcept
211 : removed_slots_(other.removed_slots_),
212 occupied_and_removed_slots_(other.occupied_and_removed_slots_),
213 usable_slots_(other.usable_slots_),
214 slot_mask_(other.slot_mask_),
215 slots_(std::move(other.slots_)),
216 keys_(other.keys_)
217 {
218 other.removed_slots_ = 0;
219 other.occupied_and_removed_slots_ = 0;
220 other.usable_slots_ = 0;
221 other.slot_mask_ = 0;
222 other.slots_ = SlotArray(1);
223 other.keys_ = nullptr;
224 }
225
227 {
228 return copy_assign_container(*this, other);
229 }
230
232 {
233 return move_assign_container(*this, std::move(other));
234 }
235
239 const Key &operator[](const int64_t index) const
240 {
241 BLI_assert(index >= 0);
242 BLI_assert(index <= this->size());
243 return keys_[index];
244 }
245
246 operator Span<Key>() const
247 {
248 return Span<Key>(keys_, this->size());
249 }
250
258 {
259 return *this;
260 }
261
267 void add_new(const Key &key)
268 {
269 this->add_new__impl(key, hash_(key));
270 }
271 void add_new(Key &&key)
272 {
273 this->add_new__impl(std::move(key), hash_(key));
274 }
275
282 bool add(const Key &key)
283 {
284 return this->add_as(key);
285 }
286 bool add(Key &&key)
287 {
288 return this->add_as(std::move(key));
289 }
290 template<typename ForwardKey> bool add_as(ForwardKey &&key)
291 {
292 return this->add__impl(std::forward<ForwardKey>(key), hash_(key));
293 }
294
303 {
304 for (const Key &key : keys) {
305 this->add(key);
306 }
307 }
308
314 bool contains(const Key &key) const
315 {
316 return this->contains_as(key);
317 }
318 template<typename ForwardKey> bool contains_as(const ForwardKey &key) const
319 {
320 return this->contains__impl(key, hash_(key));
321 }
322
329 bool remove(const Key &key)
330 {
331 return this->remove_as(key);
332 }
333 template<typename ForwardKey> bool remove_as(const ForwardKey &key)
334 {
335 return this->remove__impl(key, hash_(key));
336 }
337
342 void remove_contained(const Key &key)
343 {
344 this->remove_contained_as(key);
345 }
346 template<typename ForwardKey> void remove_contained_as(const ForwardKey &key)
347 {
348 this->remove_contained__impl(key, hash_(key));
349 }
350
357 template<typename Predicate> int64_t remove_if(Predicate &&predicate)
358 {
359 const int64_t prev_size = this->size();
360 for (Slot &slot : slots_) {
361 if (slot.is_occupied()) {
362 const int64_t index = slot.index();
363 const Key &key = keys_[index];
364 if (predicate(key)) {
365 this->remove_key_internal(slot);
366 }
367 }
368 }
369 return prev_size - this->size();
370 }
371
377 {
378 return this->pop__impl();
379 }
380
385 int64_t index_of(const Key &key) const
386 {
387 return this->index_of_as(key);
388 }
389 template<typename ForwardKey> int64_t index_of_as(const ForwardKey &key) const
390 {
391 return this->index_of__impl(key, hash_(key));
392 }
393
398 int64_t index_of_try(const Key &key) const
399 {
400 return this->index_of_try_as(key);
401 }
402 template<typename ForwardKey> int64_t index_of_try_as(const ForwardKey &key) const
403 {
404 return this->index_of_try__impl(key, hash_(key));
405 }
406
412 {
413 return this->index_of_or_add_as(key);
414 }
416 {
417 return this->index_of_or_add_as(std::move(key));
418 }
419 template<typename ForwardKey> int64_t index_of_or_add_as(ForwardKey &&key)
420 {
421 return this->index_of_or_add__impl(std::forward<ForwardKey>(key), hash_(key));
422 }
423
428 const Key &lookup_key(const Key &key) const
429 {
430 return this->lookup_key_as(key);
431 }
432 template<typename ForwardKey> const Key &lookup_key_as(const ForwardKey &key) const
433 {
434 const Key *key_ptr = this->lookup_key_ptr_as(key);
435 BLI_assert(key_ptr != nullptr);
436 return *key_ptr;
437 }
438
443 const Key *lookup_key_ptr(const Key &key) const
444 {
445 return this->lookup_key_ptr_as(key);
446 }
447 template<typename ForwardKey> const Key *lookup_key_ptr_as(const ForwardKey &key) const
448 {
449 const int64_t index = this->index_of_try__impl(key, hash_(key));
450 if (index >= 0) {
451 return keys_ + index;
452 }
453 return nullptr;
454 }
455
459 const Key *data() const
460 {
461 return keys_;
462 }
463
464 const Key *begin() const
465 {
466 return keys_;
467 }
468
469 const Key *end() const
470 {
471 return keys_ + this->size();
472 }
473
478 {
479 return IndexRange(this->size());
480 }
481
485 void print_stats(const char *name) const
486 {
487 HashTableStats stats(*this, this->as_span());
488 stats.print(name);
489 }
490
494 int64_t size() const
495 {
496 return occupied_and_removed_slots_ - removed_slots_;
497 }
498
502 bool is_empty() const
503 {
504 return occupied_and_removed_slots_ == removed_slots_;
505 }
506
511 {
512 return slots_.size();
513 }
514
519 {
520 return removed_slots_;
521 }
522
527 {
528 return sizeof(Slot) + sizeof(Key);
529 }
530
536 {
537 return int64_t(sizeof(Slot) * slots_.size() + sizeof(Key) * usable_slots_);
538 }
539
543 void reserve(const int64_t n)
544 {
545 if (usable_slots_ < n) {
546 this->realloc_and_reinsert(n);
547 }
548 }
549
553 void clear()
554 {
555 destruct_n(keys_, this->size());
556 for (Slot &slot : slots_) {
557 slot.~Slot();
558 new (&slot) Slot();
559 }
560
561 removed_slots_ = 0;
562 occupied_and_removed_slots_ = 0;
563 }
564
569 {
570 std::destroy_at(this);
571 new (this) VectorSet(NoExceptConstructor{});
572 }
573
578 int64_t count_collisions(const Key &key) const
579 {
580 return this->count_collisions__impl(key, hash_(key));
581 }
582
583 private:
584 BLI_NOINLINE void realloc_and_reinsert(const int64_t min_usable_slots)
585 {
586 int64_t total_slots, usable_slots;
587 max_load_factor_.compute_total_and_usable_slots(
588 SlotArray::inline_buffer_capacity(), min_usable_slots, &total_slots, &usable_slots);
589 BLI_assert(total_slots >= 1);
590 const uint64_t new_slot_mask = uint64_t(total_slots) - 1;
591
592 /* Optimize the case when the set was empty beforehand. We can avoid some copies here. */
593 if (this->size() == 0) {
594 try {
595 slots_.reinitialize(total_slots);
596 if (keys_ != nullptr) {
597 this->deallocate_keys_array(keys_);
598 keys_ = nullptr;
599 }
600 keys_ = this->allocate_keys_array(usable_slots);
601 }
602 catch (...) {
603 this->noexcept_reset();
604 throw;
605 }
606 removed_slots_ = 0;
607 occupied_and_removed_slots_ = 0;
608 usable_slots_ = usable_slots;
609 slot_mask_ = new_slot_mask;
610 return;
611 }
612
613 SlotArray new_slots(total_slots);
614
615 try {
616 for (Slot &slot : slots_) {
617 if (slot.is_occupied()) {
618 this->add_after_grow(slot, new_slots, new_slot_mask);
619 slot.remove();
620 }
621 }
622 slots_ = std::move(new_slots);
623 }
624 catch (...) {
625 this->noexcept_reset();
626 throw;
627 }
628
629 Key *new_keys = this->allocate_keys_array(usable_slots);
630 try {
631 uninitialized_relocate_n(keys_, this->size(), new_keys);
632 }
633 catch (...) {
634 this->deallocate_keys_array(new_keys);
635 this->noexcept_reset();
636 throw;
637 }
638 this->deallocate_keys_array(keys_);
639
640 keys_ = new_keys;
641 occupied_and_removed_slots_ -= removed_slots_;
642 usable_slots_ = usable_slots;
643 removed_slots_ = 0;
644 slot_mask_ = new_slot_mask;
645 }
646
647 void add_after_grow(Slot &old_slot, SlotArray &new_slots, const uint64_t new_slot_mask)
648 {
649 const Key &key = keys_[old_slot.index()];
650 const uint64_t hash = old_slot.get_hash(key, Hash());
651
652 SLOT_PROBING_BEGIN (ProbingStrategy, hash, new_slot_mask, slot_index) {
653 Slot &slot = new_slots[slot_index];
654 if (slot.is_empty()) {
655 slot.occupy(old_slot.index(), hash);
656 return;
657 }
658 }
660 }
661
662 void noexcept_reset() noexcept
663 {
664 Allocator allocator = slots_.allocator();
665 this->~VectorSet();
666 new (this) VectorSet(NoExceptConstructor(), allocator);
667 }
668
669 template<typename ForwardKey>
670 bool contains__impl(const ForwardKey &key, const uint64_t hash) const
671 {
673 if (slot.is_empty()) {
674 return false;
675 }
676 if (slot.contains(key, is_equal_, hash, keys_)) {
677 return true;
678 }
679 }
681 }
682
683 template<typename ForwardKey> void add_new__impl(ForwardKey &&key, const uint64_t hash)
684 {
685 BLI_assert(!this->contains_as(key));
686
687 this->ensure_can_add();
688
690 if (slot.is_empty()) {
691 int64_t index = this->size();
692 Key *dst = keys_ + index;
693 new (dst) Key(std::forward<ForwardKey>(key));
694 BLI_assert(hash_(*dst) == hash);
695 slot.occupy(index, hash);
696 occupied_and_removed_slots_++;
697 return;
698 }
699 }
701 }
702
703 template<typename ForwardKey> bool add__impl(ForwardKey &&key, const uint64_t hash)
704 {
705 this->ensure_can_add();
706
708 if (slot.is_empty()) {
709 int64_t index = this->size();
710 Key *dst = keys_ + index;
711 new (dst) Key(std::forward<ForwardKey>(key));
712 BLI_assert(hash_(*dst) == hash);
713 slot.occupy(index, hash);
714 occupied_and_removed_slots_++;
715 return true;
716 }
717 if (slot.contains(key, is_equal_, hash, keys_)) {
718 return false;
719 }
720 }
722 }
723
724 template<typename ForwardKey>
725 int64_t index_of__impl(const ForwardKey &key, const uint64_t hash) const
726 {
727 BLI_assert(this->contains_as(key));
728
730 if (slot.contains(key, is_equal_, hash, keys_)) {
731 return slot.index();
732 }
733 }
735 }
736
737 template<typename ForwardKey>
738 int64_t index_of_try__impl(const ForwardKey &key, const uint64_t hash) const
739 {
741 if (slot.contains(key, is_equal_, hash, keys_)) {
742 return slot.index();
743 }
744 if (slot.is_empty()) {
745 return -1;
746 }
747 }
749 }
750
751 template<typename ForwardKey>
752 int64_t index_of_or_add__impl(ForwardKey &&key, const uint64_t hash)
753 {
754 this->ensure_can_add();
755
757 if (slot.contains(key, is_equal_, hash, keys_)) {
758 return slot.index();
759 }
760 if (slot.is_empty()) {
761 const int64_t index = this->size();
762 Key *dst = keys_ + index;
763 new (dst) Key(std::forward<ForwardKey>(key));
764 BLI_assert(hash_(*dst) == hash);
765 slot.occupy(index, hash);
766 occupied_and_removed_slots_++;
767 return index;
768 }
769 }
771 }
772
773 Key pop__impl()
774 {
775 BLI_assert(this->size() > 0);
776
777 const int64_t index_to_pop = this->size() - 1;
778 Key key = std::move(keys_[index_to_pop]);
779 keys_[index_to_pop].~Key();
780 const uint64_t hash = hash_(key);
781
782 removed_slots_++;
783
785 if (slot.has_index(index_to_pop)) {
786 slot.remove();
787 return key;
788 }
789 }
791 }
792
793 template<typename ForwardKey> bool remove__impl(const ForwardKey &key, const uint64_t hash)
794 {
796 if (slot.contains(key, is_equal_, hash, keys_)) {
797 this->remove_key_internal(slot);
798 return true;
799 }
800 if (slot.is_empty()) {
801 return false;
802 }
803 }
805 }
806
807 template<typename ForwardKey>
808 void remove_contained__impl(const ForwardKey &key, const uint64_t hash)
809 {
810 BLI_assert(this->contains_as(key));
811
813 if (slot.contains(key, is_equal_, hash, keys_)) {
814 this->remove_key_internal(slot);
815 return;
816 }
817 }
819 }
820
821 void remove_key_internal(Slot &slot)
822 {
823 int64_t index_to_remove = slot.index();
824 int64_t size = this->size();
825 int64_t last_element_index = size - 1;
826
827 if (index_to_remove < last_element_index) {
828 keys_[index_to_remove] = std::move(keys_[last_element_index]);
829 this->update_slot_index(keys_[index_to_remove], last_element_index, index_to_remove);
830 }
831
832 keys_[last_element_index].~Key();
833 slot.remove();
834 removed_slots_++;
835 return;
836 }
837
838 void update_slot_index(const Key &key, const int64_t old_index, const int64_t new_index)
839 {
840 uint64_t hash = hash_(key);
842 if (slot.has_index(old_index)) {
843 slot.update_index(new_index);
844 return;
845 }
846 }
848 }
849
850 template<typename ForwardKey>
851 int64_t count_collisions__impl(const ForwardKey &key, const uint64_t hash) const
852 {
853 int64_t collisions = 0;
854
856 if (slot.contains(key, is_equal_, hash, keys_)) {
857 return collisions;
858 }
859 if (slot.is_empty()) {
860 return collisions;
861 }
862 collisions++;
863 }
865 }
866
867 void ensure_can_add()
868 {
869 if (occupied_and_removed_slots_ >= usable_slots_) {
870 this->realloc_and_reinsert(this->size() + 1);
871 BLI_assert(occupied_and_removed_slots_ < usable_slots_);
872 }
873 }
874
875 Key *allocate_keys_array(const int64_t size)
876 {
877 return static_cast<Key *>(
878 slots_.allocator().allocate(sizeof(Key) * size_t(size), alignof(Key), AT));
879 }
880
881 void deallocate_keys_array(Key *keys)
882 {
883 slots_.allocator().deallocate(keys);
884 }
885};
886
891template<typename Key,
892 typename ProbingStrategy = DefaultProbingStrategy,
893 typename Hash = DefaultHash<Key>,
894 typename IsEqual = DefaultEquality<Key>,
895 typename Slot = typename DefaultVectorSetSlot<Key>::type>
897
898} // namespace blender
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_NOINLINE
#define LOAD_FACTOR
Definition BLI_map.hh:162
#define SLOT_PROBING_BEGIN(PROBING_STRATEGY, HASH, MASK, R_SLOT_INDEX)
#define SLOT_PROBING_END()
#define BLI_NO_UNIQUE_ADDRESS
#define VECTOR_SET_SLOT_PROBING_BEGIN(HASH, R_SLOT)
#define VECTOR_SET_SLOT_PROBING_END()
struct Key Key
static int64_t inline_buffer_capacity()
Definition BLI_array.hh:379
void print(const char *name) const
static constexpr int64_t compute_total_slots(int64_t min_usable_slots, uint8_t numerator, uint8_t denominator)
VectorSet(VectorSet &&other) noexcept
int64_t index_of(const Key &key) const
void reserve(const int64_t n)
int64_t count_collisions(const Key &key) const
int64_t index_of_or_add_as(ForwardKey &&key)
bool add(const Key &key)
VectorSet & operator=(VectorSet &&other)
const Key & lookup_key(const Key &key) const
bool add(Key &&key)
int64_t index_of_try_as(const ForwardKey &key) const
void add_new(const Key &key)
int64_t index_of_as(const ForwardKey &key) const
bool add_as(ForwardKey &&key)
bool contains_as(const ForwardKey &key) const
int64_t size_per_element() const
VectorSet(Allocator allocator={}) noexcept
VectorSet(Span< Key > keys, Allocator allocator={})
VectorSet & operator=(const VectorSet &other)
const Key & lookup_key_as(const ForwardKey &key) const
int64_t index_of_try(const Key &key) const
const Key * lookup_key_ptr_as(const ForwardKey &key) const
IndexRange index_range() const
Span< Key > as_span() const
int64_t size_in_bytes() const
int64_t index_of_or_add(Key &&key)
int64_t size() const
void remove_contained_as(const ForwardKey &key)
const Key * data() const
VectorSet(NoExceptConstructor, Allocator allocator={})
const Key * lookup_key_ptr(const Key &key) const
VectorSet(const std::initializer_list< Key > &keys, Allocator allocator={})
void remove_contained(const Key &key)
void print_stats(const char *name) const
const Key * begin() const
int64_t removed_amount() const
int64_t capacity() const
const Key & operator[](const int64_t index) const
VectorSet(const VectorSet &other)
const Key * end() const
void add_new(Key &&key)
bool contains(const Key &key) const
bool remove_as(const ForwardKey &key)
int64_t index_of_or_add(const Key &key)
void add_multiple(Span< Key > keys)
bool remove(const Key &key)
int64_t remove_if(Predicate &&predicate)
Container & copy_assign_container(Container &dst, const Container &src)
Container & move_assign_container(Container &dst, Container &&src) noexcept(std::is_nothrow_move_constructible_v< Container >)
PythonProbingStrategy<> DefaultProbingStrategy
void uninitialized_relocate_n(T *src, int64_t n, T *dst)
void destruct_n(T *ptr, int64_t n)
void uninitialized_copy_n(const T *src, int64_t n, T *dst)
#define hash
Definition noise.c:154
__int64 int64_t
Definition stdint.h:89
unsigned __int64 uint64_t
Definition stdint.h:90
SimpleVectorSetSlot< Key > type