Path: blob/master/thirdparty/jolt_physics/Jolt/Core/HashTable.h
21362 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2024 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56#include <Jolt/Math/BVec16.h>78JPH_NAMESPACE_BEGIN910/// Helper class for implementing an UnorderedSet or UnorderedMap11/// Based on CppCon 2017: Matt Kulukundis "Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step"12/// See: https://www.youtube.com/watch?v=ncHmEUmJZf413template <class Key, class KeyValue, class HashTableDetail, class Hash, class KeyEqual>14class HashTable15{16public:17/// Properties18using value_type = KeyValue;19using size_type = uint32;20using difference_type = ptrdiff_t;2122private:23/// Base class for iterators24template <class Table, class Iterator>25class IteratorBase26{27public:28/// Properties29using difference_type = typename Table::difference_type;30using value_type = typename Table::value_type;31using iterator_category = std::forward_iterator_tag;3233/// Copy constructor34IteratorBase(const IteratorBase &inRHS) = default;3536/// Assignment operator37IteratorBase & operator = (const IteratorBase &inRHS) = default;3839/// Iterator at start of table40explicit IteratorBase(Table *inTable) :41mTable(inTable),42mIndex(0)43{44while (mIndex < mTable->mMaxSize && (mTable->mControl[mIndex] & cBucketUsed) == 0)45++mIndex;46}4748/// Iterator at specific index49IteratorBase(Table *inTable, size_type inIndex) :50mTable(inTable),51mIndex(inIndex)52{53}5455/// Prefix increment56Iterator & operator ++ ()57{58JPH_ASSERT(IsValid());5960do61{62++mIndex;63}64while (mIndex < mTable->mMaxSize && (mTable->mControl[mIndex] & cBucketUsed) == 0);6566return static_cast<Iterator &>(*this);67}6869/// Postfix increment70Iterator operator ++ (int)71{72Iterator result(mTable, mIndex);73++(*this);74return result;75}7677/// Access to key value pair78const KeyValue & operator * () const79{80JPH_ASSERT(IsValid());81return mTable->mData[mIndex];82}8384/// Access to key value pair85const KeyValue * operator -> () const86{87JPH_ASSERT(IsValid());88return mTable->mData + mIndex;89}9091/// Equality operator92bool operator == (const Iterator &inRHS) const93{94return mIndex == inRHS.mIndex && mTable == inRHS.mTable;95}9697/// Inequality operator98bool operator != (const Iterator &inRHS) const99{100return !(*this == inRHS);101}102103/// Check that the iterator is valid104bool IsValid() const105{106return mIndex < mTable->mMaxSize107&& (mTable->mControl[mIndex] & cBucketUsed) != 0;108}109110Table * mTable;111size_type mIndex;112};113114/// Get the maximum number of elements that we can support given a number of buckets115static constexpr size_type sGetMaxLoad(size_type inBucketCount)116{117return uint32((cMaxLoadFactorNumerator * inBucketCount) / cMaxLoadFactorDenominator);118}119120/// Update the control value for a bucket121JPH_INLINE void SetControlValue(size_type inIndex, uint8 inValue)122{123JPH_ASSERT(inIndex < mMaxSize);124mControl[inIndex] = inValue;125126// Mirror the first 15 bytes to the 15 bytes beyond mMaxSize127// Note that this is equivalent to:128// if (inIndex < 15)129// mControl[inIndex + mMaxSize] = inValue130// else131// mControl[inIndex] = inValue132// Which performs a needless write if inIndex >= 15 but at least it is branch-less133mControl[((inIndex - 15) & (mMaxSize - 1)) + 15] = inValue;134}135136/// Get the index and control value for a particular key137JPH_INLINE void GetIndexAndControlValue(const Key &inKey, size_type &outIndex, uint8 &outControl) const138{139// Calculate hash140uint64 hash_value = Hash { } (inKey);141142// Split hash into index and control value143outIndex = size_type(hash_value >> 7) & (mMaxSize - 1);144outControl = cBucketUsed | uint8(hash_value);145}146147/// Allocate space for the hash table148void AllocateTable(size_type inMaxSize)149{150JPH_ASSERT(mData == nullptr);151152mMaxSize = inMaxSize;153mLoadLeft = sGetMaxLoad(inMaxSize);154size_t required_size = size_t(mMaxSize) * (sizeof(KeyValue) + 1) + 15; // Add 15 bytes to mirror the first 15 bytes of the control values155if constexpr (cNeedsAlignedAllocate)156mData = reinterpret_cast<KeyValue *>(AlignedAllocate(required_size, alignof(KeyValue)));157else158mData = reinterpret_cast<KeyValue *>(Allocate(required_size));159mControl = reinterpret_cast<uint8 *>(mData + mMaxSize);160}161162/// Copy the contents of another hash table163void CopyTable(const HashTable &inRHS)164{165if (inRHS.empty())166return;167168AllocateTable(inRHS.mMaxSize);169170// Copy control bytes171memcpy(mControl, inRHS.mControl, mMaxSize + 15);172173// Copy elements174uint index = 0;175for (const uint8 *control = mControl, *control_end = mControl + mMaxSize; control != control_end; ++control, ++index)176if (*control & cBucketUsed)177new (mData + index) KeyValue(inRHS.mData[index]);178mSize = inRHS.mSize;179}180181/// Grow the table to a new size182void GrowTable(size_type inNewMaxSize)183{184// Move the old table to a temporary structure185size_type old_max_size = mMaxSize;186KeyValue *old_data = mData;187const uint8 *old_control = mControl;188mData = nullptr;189mControl = nullptr;190mSize = 0;191mMaxSize = 0;192mLoadLeft = 0;193194// Allocate new table195AllocateTable(inNewMaxSize);196197// Reset all control bytes198memset(mControl, cBucketEmpty, mMaxSize + 15);199200if (old_data != nullptr)201{202// Copy all elements from the old table203for (size_type i = 0; i < old_max_size; ++i)204if (old_control[i] & cBucketUsed)205{206size_type index;207KeyValue *element = old_data + i;208JPH_IF_ENABLE_ASSERTS(bool inserted =) InsertKey</* InsertAfterGrow= */ true>(HashTableDetail::sGetKey(*element), index);209JPH_ASSERT(inserted);210new (mData + index) KeyValue(std::move(*element));211element->~KeyValue();212}213214// Free memory215if constexpr (cNeedsAlignedAllocate)216AlignedFree(old_data);217else218Free(old_data);219}220}221222protected:223/// Get an element by index224KeyValue & GetElement(size_type inIndex) const225{226return mData[inIndex];227}228229/// Insert a key into the map, returns true if the element was inserted, false if it already existed.230/// outIndex is the index at which the element should be constructed / where it is located.231template <bool InsertAfterGrow = false>232bool InsertKey(const Key &inKey, size_type &outIndex)233{234// Ensure we have enough space235if (mLoadLeft == 0)236{237// Should not be growing if we're already growing!238if constexpr (InsertAfterGrow)239JPH_ASSERT(false);240241// Decide if we need to clean up all tombstones or if we need to grow the map242size_type num_deleted = sGetMaxLoad(mMaxSize) - mSize;243if (num_deleted * cMaxDeletedElementsDenominator > mMaxSize * cMaxDeletedElementsNumerator)244rehash(0);245else246{247// Grow by a power of 2248size_type new_max_size = max<size_type>(mMaxSize << 1, 16);249if (new_max_size < mMaxSize)250{251JPH_ASSERT(false, "Overflow in hash table size, can't grow!");252return false;253}254GrowTable(new_max_size);255}256}257258// Split hash into index and control value259size_type index;260uint8 control;261GetIndexAndControlValue(inKey, index, control);262263// Keeps track of the index of the first deleted bucket we found264constexpr size_type cNoDeleted = ~size_type(0);265size_type first_deleted_index = cNoDeleted;266267// Linear probing268KeyEqual equal;269size_type bucket_mask = mMaxSize - 1;270BVec16 control16 = BVec16::sReplicate(control);271BVec16 bucket_empty = BVec16::sZero();272BVec16 bucket_deleted = BVec16::sReplicate(cBucketDeleted);273for (;;)274{275// Read 16 control values (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)276BVec16 control_bytes = BVec16::sLoadByte16(mControl + index);277278// Check if we must find the element before we can insert279if constexpr (!InsertAfterGrow)280{281// Check for the control value we're looking for282// Note that when deleting we can create empty buckets instead of deleted buckets.283// This means we must unconditionally check all buckets in this batch for equality284// (also beyond the first empty bucket).285uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues());286287// Index within the 16 buckets288size_type local_index = index;289290// Loop while there's still buckets to process291while (control_equal != 0)292{293// Get the first equal bucket294uint first_equal = CountTrailingZeros(control_equal);295296// Skip to the bucket297local_index += first_equal;298299// Make sure that our index is not beyond the end of the table300local_index &= bucket_mask;301302// We found a bucket with same control value303if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey))304{305// Element already exists306outIndex = local_index;307return false;308}309310// Skip past this bucket311control_equal >>= first_equal + 1;312local_index++;313}314315// Check if we're still scanning for deleted buckets316if (first_deleted_index == cNoDeleted)317{318// Check if any buckets have been deleted, if so store the first one319uint32 control_deleted = uint32(BVec16::sEquals(control_bytes, bucket_deleted).GetTrues());320if (control_deleted != 0)321first_deleted_index = index + CountTrailingZeros(control_deleted);322}323}324325// Check for empty buckets326uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());327if (control_empty != 0)328{329// If we found a deleted bucket, use it.330// It doesn't matter if it is before or after the first empty bucket we found331// since we will always be scanning in batches of 16 buckets.332if (first_deleted_index == cNoDeleted || InsertAfterGrow)333{334index += CountTrailingZeros(control_empty);335--mLoadLeft; // Using an empty bucket decreases the load left336}337else338{339index = first_deleted_index;340}341342// Make sure that our index is not beyond the end of the table343index &= bucket_mask;344345// Update control byte346SetControlValue(index, control);347++mSize;348349// Return index to newly allocated bucket350outIndex = index;351return true;352}353354// Move to next batch of 16 buckets355index = (index + 16) & bucket_mask;356}357}358359public:360/// Non-const iterator361class iterator : public IteratorBase<HashTable, iterator>362{363using Base = IteratorBase<HashTable, iterator>;364365public:366using IteratorBase<HashTable, iterator>::operator ==;367368/// Properties369using reference = typename Base::value_type &;370using pointer = typename Base::value_type *;371372/// Constructors373explicit iterator(HashTable *inTable) : Base(inTable) { }374iterator(HashTable *inTable, size_type inIndex) : Base(inTable, inIndex) { }375iterator(const iterator &inIterator) : Base(inIterator) { }376377/// Assignment378iterator & operator = (const iterator &inRHS) { Base::operator = (inRHS); return *this; }379380using Base::operator *;381382/// Non-const access to key value pair383KeyValue & operator * ()384{385JPH_ASSERT(this->IsValid());386return this->mTable->mData[this->mIndex];387}388389using Base::operator ->;390391/// Non-const access to key value pair392KeyValue * operator -> ()393{394JPH_ASSERT(this->IsValid());395return this->mTable->mData + this->mIndex;396}397};398399/// Const iterator400class const_iterator : public IteratorBase<const HashTable, const_iterator>401{402using Base = IteratorBase<const HashTable, const_iterator>;403404public:405using IteratorBase<const HashTable, const_iterator>::operator ==;406407/// Properties408using reference = const typename Base::value_type &;409using pointer = const typename Base::value_type *;410411/// Constructors412explicit const_iterator(const HashTable *inTable) : Base(inTable) { }413const_iterator(const HashTable *inTable, size_type inIndex) : Base(inTable, inIndex) { }414const_iterator(const const_iterator &inRHS) : Base(inRHS) { }415const_iterator(const iterator &inIterator) : Base(inIterator.mTable, inIterator.mIndex) { }416417/// Assignment418const_iterator & operator = (const iterator &inRHS) { this->mTable = inRHS.mTable; this->mIndex = inRHS.mIndex; return *this; }419const_iterator & operator = (const const_iterator &inRHS) { Base::operator = (inRHS); return *this; }420};421422/// Default constructor423HashTable() = default;424425/// Copy constructor426HashTable(const HashTable &inRHS)427{428CopyTable(inRHS);429}430431/// Move constructor432HashTable(HashTable &&ioRHS) noexcept :433mData(ioRHS.mData),434mControl(ioRHS.mControl),435mSize(ioRHS.mSize),436mMaxSize(ioRHS.mMaxSize),437mLoadLeft(ioRHS.mLoadLeft)438{439ioRHS.mData = nullptr;440ioRHS.mControl = nullptr;441ioRHS.mSize = 0;442ioRHS.mMaxSize = 0;443ioRHS.mLoadLeft = 0;444}445446/// Assignment operator447HashTable & operator = (const HashTable &inRHS)448{449if (this != &inRHS)450{451clear();452453CopyTable(inRHS);454}455456return *this;457}458459/// Move assignment operator460HashTable & operator = (HashTable &&ioRHS) noexcept461{462if (this != &ioRHS)463{464clear();465466mData = ioRHS.mData;467mControl = ioRHS.mControl;468mSize = ioRHS.mSize;469mMaxSize = ioRHS.mMaxSize;470mLoadLeft = ioRHS.mLoadLeft;471472ioRHS.mData = nullptr;473ioRHS.mControl = nullptr;474ioRHS.mSize = 0;475ioRHS.mMaxSize = 0;476ioRHS.mLoadLeft = 0;477}478479return *this;480}481482/// Destructor483~HashTable()484{485clear();486}487488/// Reserve memory for a certain number of elements489void reserve(size_type inMaxSize)490{491// Calculate max size based on load factor492size_type max_size = GetNextPowerOf2(max<uint32>((cMaxLoadFactorDenominator * inMaxSize) / cMaxLoadFactorNumerator, 16));493if (max_size <= mMaxSize)494return;495496GrowTable(max_size);497}498499/// Destroy the entire hash table500void clear()501{502// Delete all elements503if constexpr (!std::is_trivially_destructible<KeyValue>())504if (!empty())505for (size_type i = 0; i < mMaxSize; ++i)506if (mControl[i] & cBucketUsed)507mData[i].~KeyValue();508509if (mData != nullptr)510{511// Free memory512if constexpr (cNeedsAlignedAllocate)513AlignedFree(mData);514else515Free(mData);516517// Reset members518mData = nullptr;519mControl = nullptr;520mSize = 0;521mMaxSize = 0;522mLoadLeft = 0;523}524}525526/// Destroy the entire hash table but keeps the memory allocated527void ClearAndKeepMemory()528{529// Destruct elements530if constexpr (!std::is_trivially_destructible<KeyValue>())531if (!empty())532for (size_type i = 0; i < mMaxSize; ++i)533if (mControl[i] & cBucketUsed)534mData[i].~KeyValue();535mSize = 0;536537// If there are elements that are not marked cBucketEmpty, we reset them538size_type max_load = sGetMaxLoad(mMaxSize);539if (mLoadLeft != max_load)540{541// Reset all control bytes542memset(mControl, cBucketEmpty, mMaxSize + 15);543mLoadLeft = max_load;544}545}546547/// Iterator to first element548iterator begin()549{550return iterator(this);551}552553/// Iterator to one beyond last element554iterator end()555{556return iterator(this, mMaxSize);557}558559/// Iterator to first element560const_iterator begin() const561{562return const_iterator(this);563}564565/// Iterator to one beyond last element566const_iterator end() const567{568return const_iterator(this, mMaxSize);569}570571/// Iterator to first element572const_iterator cbegin() const573{574return const_iterator(this);575}576577/// Iterator to one beyond last element578const_iterator cend() const579{580return const_iterator(this, mMaxSize);581}582583/// Number of buckets in the table584size_type bucket_count() const585{586return mMaxSize;587}588589/// Max number of buckets that the table can have590constexpr size_type max_bucket_count() const591{592return size_type(1) << (sizeof(size_type) * 8 - 1);593}594595/// Check if there are no elements in the table596bool empty() const597{598return mSize == 0;599}600601/// Number of elements in the table602size_type size() const603{604return mSize;605}606607/// Max number of elements that the table can hold608constexpr size_type max_size() const609{610return size_type((uint64(max_bucket_count()) * cMaxLoadFactorNumerator) / cMaxLoadFactorDenominator);611}612613/// Get the max load factor for this table (max number of elements / number of buckets)614constexpr float max_load_factor() const615{616return float(cMaxLoadFactorNumerator) / float(cMaxLoadFactorDenominator);617}618619/// Insert a new element, returns iterator and if the element was inserted620std::pair<iterator, bool> insert(const value_type &inValue)621{622size_type index;623bool inserted = InsertKey(HashTableDetail::sGetKey(inValue), index);624if (inserted)625new (mData + index) KeyValue(inValue);626return std::make_pair(iterator(this, index), inserted);627}628629/// Find an element, returns iterator to element or end() if not found630const_iterator find(const Key &inKey) const631{632// Check if we have any data633if (empty())634return cend();635636// Split hash into index and control value637size_type index;638uint8 control;639GetIndexAndControlValue(inKey, index, control);640641// Linear probing642KeyEqual equal;643size_type bucket_mask = mMaxSize - 1;644BVec16 control16 = BVec16::sReplicate(control);645BVec16 bucket_empty = BVec16::sZero();646for (;;)647{648// Read 16 control values649// (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)650BVec16 control_bytes = BVec16::sLoadByte16(mControl + index);651652// Check for the control value we're looking for653// Note that when deleting we can create empty buckets instead of deleted buckets.654// This means we must unconditionally check all buckets in this batch for equality655// (also beyond the first empty bucket).656uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues());657658// Index within the 16 buckets659size_type local_index = index;660661// Loop while there's still buckets to process662while (control_equal != 0)663{664// Get the first equal bucket665uint first_equal = CountTrailingZeros(control_equal);666667// Skip to the bucket668local_index += first_equal;669670// Make sure that our index is not beyond the end of the table671local_index &= bucket_mask;672673// We found a bucket with same control value674if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey))675{676// Element found677return const_iterator(this, local_index);678}679680// Skip past this bucket681control_equal >>= first_equal + 1;682local_index++;683}684685// Check for empty buckets686uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());687if (control_empty != 0)688{689// An empty bucket was found, we didn't find the element690return cend();691}692693// Move to next batch of 16 buckets694index = (index + 16) & bucket_mask;695}696}697698/// @brief Erase an element by iterator699void erase(const const_iterator &inIterator)700{701JPH_ASSERT(inIterator.IsValid());702703// Read 16 control values before and after the current index704// (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)705BVec16 control_bytes_before = BVec16::sLoadByte16(mControl + ((inIterator.mIndex - 16) & (mMaxSize - 1)));706BVec16 control_bytes_after = BVec16::sLoadByte16(mControl + inIterator.mIndex);707BVec16 bucket_empty = BVec16::sZero();708uint32 control_empty_before = uint32(BVec16::sEquals(control_bytes_before, bucket_empty).GetTrues());709uint32 control_empty_after = uint32(BVec16::sEquals(control_bytes_after, bucket_empty).GetTrues());710711// If (this index including) there exist 16 consecutive non-empty slots (represented by a bit being 0) then712// a probe looking for some element needs to continue probing so we cannot mark the bucket as empty713// but must mark it as deleted instead.714// Note that we use: CountLeadingZeros(uint16) = CountLeadingZeros(uint32) - 16.715uint8 control_value = CountLeadingZeros(control_empty_before) - 16 + CountTrailingZeros(control_empty_after) < 16? cBucketEmpty : cBucketDeleted;716717// Mark the bucket as empty/deleted718SetControlValue(inIterator.mIndex, control_value);719720// Destruct the element721mData[inIterator.mIndex].~KeyValue();722723// If we marked the bucket as empty we can increase the load left724if (control_value == cBucketEmpty)725++mLoadLeft;726727// Decrease size728--mSize;729}730731/// @brief Erase an element by key732size_type erase(const Key &inKey)733{734const_iterator it = find(inKey);735if (it == cend())736return 0;737738erase(it);739return 1;740}741742/// Swap the contents of two hash tables743void swap(HashTable &ioRHS) noexcept744{745std::swap(mData, ioRHS.mData);746std::swap(mControl, ioRHS.mControl);747std::swap(mSize, ioRHS.mSize);748std::swap(mMaxSize, ioRHS.mMaxSize);749std::swap(mLoadLeft, ioRHS.mLoadLeft);750}751752/// In place re-hashing of all elements in the table. Removes all cBucketDeleted elements753/// The std version takes a bucket count, but we just re-hash to the same size.754void rehash(size_type)755{756// Update the control value for all buckets757for (size_type i = 0; i < mMaxSize; ++i)758{759uint8 &control = mControl[i];760switch (control)761{762case cBucketDeleted:763// Deleted buckets become empty764control = cBucketEmpty;765break;766case cBucketEmpty:767// Remains empty768break;769default:770// Mark all occupied as deleted, to indicate it needs to move to the correct place771control = cBucketDeleted;772break;773}774}775776// Replicate control values to the last 15 entries777for (size_type i = 0; i < 15; ++i)778mControl[mMaxSize + i] = mControl[i];779780// Loop over all elements that have been 'deleted' and move them to their new spot781BVec16 bucket_used = BVec16::sReplicate(cBucketUsed);782size_type bucket_mask = mMaxSize - 1;783uint32 probe_mask = bucket_mask & ~uint32(0b1111); // Mask out lower 4 bits because we test 16 buckets at a time784for (size_type src = 0; src < mMaxSize; ++src)785if (mControl[src] == cBucketDeleted)786for (;;)787{788// Split hash into index and control value789size_type src_index;790uint8 src_control;791GetIndexAndControlValue(HashTableDetail::sGetKey(mData[src]), src_index, src_control);792793// Linear probing794size_type dst = src_index;795for (;;)796{797// Check if any buckets are free798BVec16 control_bytes = BVec16::sLoadByte16(mControl + dst);799uint32 control_free = uint32(BVec16::sAnd(control_bytes, bucket_used).GetTrues()) ^ 0xffff;800if (control_free != 0)801{802// Select this bucket as destination803dst += CountTrailingZeros(control_free);804dst &= bucket_mask;805break;806}807808// Move to next batch of 16 buckets809dst = (dst + 16) & bucket_mask;810}811812// Check if we stay in the same probe group813if (((dst - src_index) & probe_mask) == ((src - src_index) & probe_mask))814{815// We stay in the same group, we can stay where we are816SetControlValue(src, src_control);817break;818}819else if (mControl[dst] == cBucketEmpty)820{821// There's an empty bucket, move us there822SetControlValue(dst, src_control);823SetControlValue(src, cBucketEmpty);824new (mData + dst) KeyValue(std::move(mData[src]));825mData[src].~KeyValue();826break;827}828else829{830// There's an element in the bucket we want to move to, swap them831JPH_ASSERT(mControl[dst] == cBucketDeleted);832SetControlValue(dst, src_control);833std::swap(mData[src], mData[dst]);834// Iterate again with the same source bucket835}836}837838// Reinitialize load left839mLoadLeft = sGetMaxLoad(mMaxSize) - mSize;840}841842private:843/// If this allocator needs to fall back to aligned allocations because the type requires it844static constexpr bool cNeedsAlignedAllocate = alignof(KeyValue) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16);845846/// Max load factor is cMaxLoadFactorNumerator / cMaxLoadFactorDenominator847static constexpr uint64 cMaxLoadFactorNumerator = 7;848static constexpr uint64 cMaxLoadFactorDenominator = 8;849850/// If we can recover this fraction of deleted elements, we'll reshuffle the buckets in place rather than growing the table851static constexpr uint64 cMaxDeletedElementsNumerator = 1;852static constexpr uint64 cMaxDeletedElementsDenominator = 8;853854/// Values that the control bytes can have855static constexpr uint8 cBucketEmpty = 0;856static constexpr uint8 cBucketDeleted = 0x7f;857static constexpr uint8 cBucketUsed = 0x80; // Lowest 7 bits are lowest 7 bits of the hash value858859/// The buckets, an array of size mMaxSize860KeyValue * mData = nullptr;861862/// Control bytes, an array of size mMaxSize + 15863uint8 * mControl = nullptr;864865/// Number of elements in the table866size_type mSize = 0;867868/// Max number of elements that can be stored in the table869size_type mMaxSize = 0;870871/// Number of elements we can add to the table before we need to grow872size_type mLoadLeft = 0;873};874875JPH_NAMESPACE_END876877878