Blender  V2.93
BLI_vector_set.hh
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
17 #pragma once
18 
62 #include "BLI_array.hh"
63 #include "BLI_hash.hh"
64 #include "BLI_hash_tables.hh"
66 #include "BLI_vector_set_slots.hh"
67 
68 namespace blender {
69 
70 template<
75  typename Key,
79  typename ProbingStrategy = DefaultProbingStrategy,
84  typename Hash = DefaultHash<Key>,
89  typename IsEqual = DefaultEquality,
96  typename Slot = typename DefaultVectorSetSlot<Key>::type,
101  typename Allocator = GuardedAllocator>
102 class VectorSet {
103  public:
104  using value_type = Key;
105  using pointer = Key *;
106  using const_pointer = const Key *;
107  using reference = Key &;
108  using const_reference = const Key &;
109  using iterator = Key *;
110  using const_iterator = const Key *;
112 
113  private:
118  int64_t removed_slots_;
119  int64_t occupied_and_removed_slots_;
120 
125  int64_t usable_slots_;
126 
131  uint64_t slot_mask_;
132 
134  Hash hash_;
135 
137  IsEqual is_equal_;
138 
140 #define LOAD_FACTOR 1, 2
141  LoadFactor max_load_factor_ = LoadFactor(LOAD_FACTOR);
142  using SlotArray = Array<Slot, LoadFactor::compute_total_slots(4, LOAD_FACTOR), Allocator>;
143 #undef LOAD_FACTOR
144 
149  SlotArray slots_;
150 
156  Key *keys_ = nullptr;
157 
159 #define VECTOR_SET_SLOT_PROBING_BEGIN(HASH, R_SLOT) \
160  SLOT_PROBING_BEGIN (ProbingStrategy, HASH, slot_mask_, SLOT_INDEX) \
161  auto &R_SLOT = slots_[SLOT_INDEX];
162 #define VECTOR_SET_SLOT_PROBING_END() SLOT_PROBING_END()
163 
164  public:
170  VectorSet(Allocator allocator = {}) noexcept
171  : removed_slots_(0),
172  occupied_and_removed_slots_(0),
173  usable_slots_(0),
174  slot_mask_(0),
175  slots_(1, allocator),
176  keys_(nullptr)
177  {
178  }
179 
180  VectorSet(NoExceptConstructor, Allocator allocator = {}) : VectorSet(allocator)
181  {
182  }
183 
184  VectorSet(Span<Key> keys, Allocator allocator = {}) : VectorSet(NoExceptConstructor(), allocator)
185  {
186  this->add_multiple(keys);
187  }
188 
192  VectorSet(const std::initializer_list<Key> &keys, Allocator allocator = {})
193  : VectorSet(Span(keys), allocator)
194  {
195  }
196 
198  {
199  destruct_n(keys_, this->size());
200  if (keys_ != nullptr) {
201  this->deallocate_keys_array(keys_);
202  }
203  }
204 
205  VectorSet(const VectorSet &other) : slots_(other.slots_)
206  {
207  keys_ = this->allocate_keys_array(other.usable_slots_);
208  try {
209  uninitialized_copy_n(other.keys_, other.size(), keys_);
210  }
211  catch (...) {
212  this->deallocate_keys_array(keys_);
213  throw;
214  }
215 
216  removed_slots_ = other.removed_slots_;
217  occupied_and_removed_slots_ = other.occupied_and_removed_slots_;
218  usable_slots_ = other.usable_slots_;
219  slot_mask_ = other.slot_mask_;
220  hash_ = other.hash_;
221  is_equal_ = other.is_equal_;
222  }
223 
224  VectorSet(VectorSet &&other) noexcept
225  : removed_slots_(other.removed_slots_),
226  occupied_and_removed_slots_(other.occupied_and_removed_slots_),
227  usable_slots_(other.usable_slots_),
228  slot_mask_(other.slot_mask_),
229  slots_(std::move(other.slots_)),
230  keys_(other.keys_)
231  {
232  other.removed_slots_ = 0;
233  other.occupied_and_removed_slots_ = 0;
234  other.usable_slots_ = 0;
235  other.slot_mask_ = 0;
236  other.slots_ = SlotArray(1);
237  other.keys_ = nullptr;
238  }
239 
241  {
242  return copy_assign_container(*this, other);
243  }
244 
246  {
247  return move_assign_container(*this, std::move(other));
248  }
249 
253  const Key &operator[](const int64_t index) const
254  {
255  BLI_assert(index >= 0);
256  BLI_assert(index <= this->size());
257  return keys_[index];
258  }
259 
260  operator Span<Key>() const
261  {
262  return Span<Key>(keys_, this->size());
263  }
264 
272  {
273  return *this;
274  }
275 
281  void add_new(const Key &key)
282  {
283  this->add_new__impl(key, hash_(key));
284  }
285  void add_new(Key &&key)
286  {
287  this->add_new__impl(std::move(key), hash_(key));
288  }
289 
296  bool add(const Key &key)
297  {
298  return this->add_as(key);
299  }
300  bool add(Key &&key)
301  {
302  return this->add_as(std::move(key));
303  }
304  template<typename ForwardKey> bool add_as(ForwardKey &&key)
305  {
306  return this->add__impl(std::forward<ForwardKey>(key), hash_(key));
307  }
308 
317  {
318  for (const Key &key : keys) {
319  this->add(key);
320  }
321  }
322 
328  bool contains(const Key &key) const
329  {
330  return this->contains_as(key);
331  }
332  template<typename ForwardKey> bool contains_as(const ForwardKey &key) const
333  {
334  return this->contains__impl(key, hash_(key));
335  }
336 
343  bool remove(const Key &key)
344  {
345  return this->remove_as(key);
346  }
347  template<typename ForwardKey> bool remove_as(const ForwardKey &key)
348  {
349  return this->remove__impl(key, hash_(key));
350  }
351 
356  void remove_contained(const Key &key)
357  {
358  this->remove_contained_as(key);
359  }
360  template<typename ForwardKey> void remove_contained_as(const ForwardKey &key)
361  {
362  this->remove_contained__impl(key, hash_(key));
363  }
364 
370  {
371  return this->pop__impl();
372  }
373 
378  int64_t index_of(const Key &key) const
379  {
380  return this->index_of_as(key);
381  }
382  template<typename ForwardKey> int64_t index_of_as(const ForwardKey &key) const
383  {
384  return this->index_of__impl(key, hash_(key));
385  }
386 
391  int64_t index_of_try(const Key &key) const
392  {
393  return this->index_of_try_as(key);
394  }
395  template<typename ForwardKey> int64_t index_of_try_as(const ForwardKey &key) const
396  {
397  return this->index_of_try__impl(key, hash_(key));
398  }
399 
403  const Key *data() const
404  {
405  return keys_;
406  }
407 
408  const Key *begin() const
409  {
410  return keys_;
411  }
412 
413  const Key *end() const
414  {
415  return keys_ + this->size();
416  }
417 
421  void print_stats(StringRef name = "") const
422  {
423  HashTableStats stats(*this, this->as_span());
424  stats.print(name);
425  }
426 
430  int64_t size() const
431  {
432  return occupied_and_removed_slots_ - removed_slots_;
433  }
434 
438  bool is_empty() const
439  {
440  return occupied_and_removed_slots_ == removed_slots_;
441  }
442 
447  {
448  return slots_.size();
449  }
450 
455  {
456  return removed_slots_;
457  }
458 
463  {
464  return sizeof(Slot) + sizeof(Key);
465  }
466 
472  {
473  return static_cast<int64_t>(sizeof(Slot) * slots_.size() + sizeof(Key) * usable_slots_);
474  }
475 
479  void reserve(const int64_t n)
480  {
481  if (usable_slots_ < n) {
482  this->realloc_and_reinsert(n);
483  }
484  }
485 
490  int64_t count_collisions(const Key &key) const
491  {
492  return this->count_collisions__impl(key, hash_(key));
493  }
494 
495  private:
496  BLI_NOINLINE void realloc_and_reinsert(const int64_t min_usable_slots)
497  {
498  int64_t total_slots, usable_slots;
499  max_load_factor_.compute_total_and_usable_slots(
500  SlotArray::inline_buffer_capacity(), min_usable_slots, &total_slots, &usable_slots);
501  BLI_assert(total_slots >= 1);
502  const uint64_t new_slot_mask = static_cast<uint64_t>(total_slots) - 1;
503 
504  /* Optimize the case when the set was empty beforehand. We can avoid some copies here. */
505  if (this->size() == 0) {
506  try {
507  slots_.reinitialize(total_slots);
508  keys_ = this->allocate_keys_array(usable_slots);
509  }
510  catch (...) {
511  this->noexcept_reset();
512  throw;
513  }
514  removed_slots_ = 0;
515  occupied_and_removed_slots_ = 0;
516  usable_slots_ = usable_slots;
517  slot_mask_ = new_slot_mask;
518  return;
519  }
520 
521  SlotArray new_slots(total_slots);
522 
523  try {
524  for (Slot &slot : slots_) {
525  if (slot.is_occupied()) {
526  this->add_after_grow(slot, new_slots, new_slot_mask);
527  slot.remove();
528  }
529  }
530  slots_ = std::move(new_slots);
531  }
532  catch (...) {
533  this->noexcept_reset();
534  throw;
535  }
536 
537  Key *new_keys = this->allocate_keys_array(usable_slots);
538  try {
539  uninitialized_relocate_n(keys_, this->size(), new_keys);
540  }
541  catch (...) {
542  this->deallocate_keys_array(new_keys);
543  this->noexcept_reset();
544  throw;
545  }
546  this->deallocate_keys_array(keys_);
547 
548  keys_ = new_keys;
549  occupied_and_removed_slots_ -= removed_slots_;
550  usable_slots_ = usable_slots;
551  removed_slots_ = 0;
552  slot_mask_ = new_slot_mask;
553  }
554 
555  void add_after_grow(Slot &old_slot, SlotArray &new_slots, const uint64_t new_slot_mask)
556  {
557  const Key &key = keys_[old_slot.index()];
558  const uint64_t hash = old_slot.get_hash(key, Hash());
559 
560  SLOT_PROBING_BEGIN (ProbingStrategy, hash, new_slot_mask, slot_index) {
561  Slot &slot = new_slots[slot_index];
562  if (slot.is_empty()) {
563  slot.occupy(old_slot.index(), hash);
564  return;
565  }
566  }
568  }
569 
570  void noexcept_reset() noexcept
571  {
572  Allocator allocator = slots_.allocator();
573  this->~VectorSet();
574  new (this) VectorSet(NoExceptConstructor(), allocator);
575  }
576 
577  template<typename ForwardKey>
578  bool contains__impl(const ForwardKey &key, const uint64_t hash) const
579  {
581  if (slot.is_empty()) {
582  return false;
583  }
584  if (slot.contains(key, is_equal_, hash, keys_)) {
585  return true;
586  }
587  }
589  }
590 
591  template<typename ForwardKey> void add_new__impl(ForwardKey &&key, const uint64_t hash)
592  {
593  BLI_assert(!this->contains_as(key));
594 
595  this->ensure_can_add();
596 
598  if (slot.is_empty()) {
599  int64_t index = this->size();
600  new (keys_ + index) Key(std::forward<ForwardKey>(key));
601  slot.occupy(index, hash);
602  occupied_and_removed_slots_++;
603  return;
604  }
605  }
607  }
608 
609  template<typename ForwardKey> bool add__impl(ForwardKey &&key, const uint64_t hash)
610  {
611  this->ensure_can_add();
612 
614  if (slot.is_empty()) {
615  int64_t index = this->size();
616  new (keys_ + index) Key(std::forward<ForwardKey>(key));
617  slot.occupy(index, hash);
618  occupied_and_removed_slots_++;
619  return true;
620  }
621  if (slot.contains(key, is_equal_, hash, keys_)) {
622  return false;
623  }
624  }
626  }
627 
628  template<typename ForwardKey>
629  int64_t index_of__impl(const ForwardKey &key, const uint64_t hash) const
630  {
631  BLI_assert(this->contains_as(key));
632 
634  if (slot.contains(key, is_equal_, hash, keys_)) {
635  return slot.index();
636  }
637  }
639  }
640 
641  template<typename ForwardKey>
642  int64_t index_of_try__impl(const ForwardKey &key, const uint64_t hash) const
643  {
645  if (slot.contains(key, is_equal_, hash, keys_)) {
646  return slot.index();
647  }
648  if (slot.is_empty()) {
649  return -1;
650  }
651  }
653  }
654 
655  Key pop__impl()
656  {
657  BLI_assert(this->size() > 0);
658 
659  const int64_t index_to_pop = this->size() - 1;
660  Key key = std::move(keys_[index_to_pop]);
661  keys_[index_to_pop].~Key();
662  const uint64_t hash = hash_(key);
663 
664  removed_slots_++;
665 
667  if (slot.has_index(index_to_pop)) {
668  slot.remove();
669  return key;
670  }
671  }
673  }
674 
675  template<typename ForwardKey> bool remove__impl(const ForwardKey &key, const uint64_t hash)
676  {
678  if (slot.contains(key, is_equal_, hash, keys_)) {
679  this->remove_key_internal(slot);
680  return true;
681  }
682  if (slot.is_empty()) {
683  return false;
684  }
685  }
687  }
688 
689  template<typename ForwardKey>
690  void remove_contained__impl(const ForwardKey &key, const uint64_t hash)
691  {
692  BLI_assert(this->contains_as(key));
693 
695  if (slot.contains(key, is_equal_, hash, keys_)) {
696  this->remove_key_internal(slot);
697  return;
698  }
699  }
701  }
702 
703  void remove_key_internal(Slot &slot)
704  {
705  int64_t index_to_remove = slot.index();
706  int64_t size = this->size();
707  int64_t last_element_index = size - 1;
708 
709  if (index_to_remove < last_element_index) {
710  keys_[index_to_remove] = std::move(keys_[last_element_index]);
711  this->update_slot_index(keys_[index_to_remove], last_element_index, index_to_remove);
712  }
713 
714  keys_[last_element_index].~Key();
715  slot.remove();
716  removed_slots_++;
717  return;
718  }
719 
720  void update_slot_index(const Key &key, const int64_t old_index, const int64_t new_index)
721  {
722  uint64_t hash = hash_(key);
724  if (slot.has_index(old_index)) {
725  slot.update_index(new_index);
726  return;
727  }
728  }
730  }
731 
732  template<typename ForwardKey>
733  int64_t count_collisions__impl(const ForwardKey &key, const uint64_t hash) const
734  {
735  int64_t collisions = 0;
736 
738  if (slot.contains(key, is_equal_, hash, keys_)) {
739  return collisions;
740  }
741  if (slot.is_empty()) {
742  return collisions;
743  }
744  collisions++;
745  }
747  }
748 
749  void ensure_can_add()
750  {
751  if (occupied_and_removed_slots_ >= usable_slots_) {
752  this->realloc_and_reinsert(this->size() + 1);
753  BLI_assert(occupied_and_removed_slots_ < usable_slots_);
754  }
755  }
756 
757  Key *allocate_keys_array(const int64_t size)
758  {
759  return static_cast<Key *>(
760  slots_.allocator().allocate(sizeof(Key) * static_cast<size_t>(size), alignof(Key), AT));
761  }
762 
763  void deallocate_keys_array(Key *keys)
764  {
765  slots_.allocator().deallocate(keys);
766  }
767 };
768 
773 template<typename Key,
774  typename ProbingStrategy = DefaultProbingStrategy,
775  typename Hash = DefaultHash<Key>,
776  typename IsEqual = DefaultEquality,
777  typename Slot = typename DefaultVectorSetSlot<Key>::type>
779 
780 } // namespace blender
#define BLI_assert(a)
Definition: BLI_assert.h:58
#define BLI_NOINLINE
#define SLOT_PROBING_BEGIN(PROBING_STRATEGY, HASH, MASK, R_SLOT_INDEX)
#define SLOT_PROBING_END()
#define AT
#define VECTOR_SET_SLOT_PROBING_BEGIN(HASH, R_SLOT)
#define LOAD_FACTOR
#define VECTOR_SET_SLOT_PROBING_END()
struct Key Key
static int64_t inline_buffer_capacity()
Definition: BLI_array.hh:375
void print(StringRef name="")
static constexpr int64_t compute_total_slots(int64_t min_usable_slots, uint8_t numerator, uint8_t denominator)
Span< Key > as_span() const
VectorSet & operator=(const VectorSet &other)
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
bool add(const Key &key)
bool add(Key &&key)
const Key * begin() const
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)
const Key * data() const
bool contains_as(const ForwardKey &key) const
int64_t size_per_element() const
VectorSet(Allocator allocator={}) noexcept
VectorSet(Span< Key > keys, Allocator allocator={})
void print_stats(StringRef name="") const
int64_t index_of_try(const Key &key) const
int64_t size_in_bytes() const
int64_t size() const
void remove_contained_as(const ForwardKey &key)
VectorSet(NoExceptConstructor, Allocator allocator={})
VectorSet(const std::initializer_list< Key > &keys, Allocator allocator={})
void remove_contained(const Key &key)
const Key * end() const
bool is_empty() const
VectorSet & operator=(VectorSet &&other)
int64_t removed_amount() const
int64_t capacity() const
VectorSet(const VectorSet &other)
const Key & operator[](const int64_t index) const
void add_new(Key &&key)
bool contains(const Key &key) const
bool remove_as(const ForwardKey &key)
void add_multiple(Span< Key > keys)
bool remove(const Key &key)
Container & move_assign_container(Container &dst, Container &&src) noexcept(std::is_nothrow_move_constructible_v< Container >)
Container & copy_assign_container(Container &dst, const Container &src)
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:169
__int64 int64_t
Definition: stdint.h:92
unsigned __int64 uint64_t
Definition: stdint.h:93
SimpleVectorSetSlot< Key > type