Refactor Dictionary and HashSet to use shared base class
Add const iterators
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
|
||||
/// <summary>
|
||||
/// Tells if the object is occupied, and if not, if the bucket is a subject of compaction.
|
||||
/// </summary>
|
||||
enum class BucketState : byte
|
||||
{
|
||||
Empty = 0,
|
||||
Deleted = 1,
|
||||
Occupied = 2,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,122 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Memory/Memory.h"
|
||||
#include "Engine/Core/Memory/Allocation.h"
|
||||
#include "Engine/Core/Memory/AllocationUtils.h"
|
||||
#include "Engine/Core/Collections/BucketState.h"
|
||||
#include "Engine/Core/Collections/HashFunctions.h"
|
||||
#include "Engine/Core/Collections/Config.h"
|
||||
#include "HashSetBase.h"
|
||||
|
||||
/// <summary>
|
||||
/// Describes single portion of space for the item in a hash set.
|
||||
/// </summary>
|
||||
template<typename T, typename AllocationType>
|
||||
struct HashSetBucket
|
||||
{
|
||||
friend Memory;
|
||||
friend HashSetBase<AllocationType, HashSetBucket>;
|
||||
friend HashSet<T, AllocationType>;
|
||||
|
||||
/// <summary>The item.</summary>
|
||||
T Item;
|
||||
|
||||
private:
|
||||
HashSetBucketState _state;
|
||||
|
||||
HashSetBucket()
|
||||
: _state(HashSetBucketState::Empty)
|
||||
{
|
||||
}
|
||||
|
||||
HashSetBucket(HashSetBucket&& other) noexcept
|
||||
{
|
||||
_state = other._state;
|
||||
if (other._state == HashSetBucketState::Occupied)
|
||||
{
|
||||
Memory::MoveItems(&Item, &other.Item, 1);
|
||||
other._state = HashSetBucketState::Empty;
|
||||
}
|
||||
}
|
||||
|
||||
HashSetBucket& operator=(HashSetBucket&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (_state == HashSetBucketState::Occupied)
|
||||
{
|
||||
Memory::DestructItem(&Item);
|
||||
}
|
||||
_state = other._state;
|
||||
if (other._state == HashSetBucketState::Occupied)
|
||||
{
|
||||
Memory::MoveItems(&Item, &other.Item, 1);
|
||||
other._state = HashSetBucketState::Empty;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// <summary>Copying a bucket is useless, because a key must be unique in the dictionary.</summary>
|
||||
HashSetBucket(const HashSetBucket&) = delete;
|
||||
|
||||
/// <summary>Copying a bucket is useless, because a key must be unique in the dictionary.</summary>
|
||||
HashSetBucket& operator=(const HashSetBucket&) = delete;
|
||||
|
||||
~HashSetBucket()
|
||||
{
|
||||
if (_state == HashSetBucketState::Occupied)
|
||||
Memory::DestructItem(&Item);
|
||||
}
|
||||
|
||||
FORCE_INLINE void Free()
|
||||
{
|
||||
if (_state == HashSetBucketState::Occupied)
|
||||
Memory::DestructItem(&Item);
|
||||
_state = HashSetBucketState::Empty;
|
||||
}
|
||||
|
||||
FORCE_INLINE void Delete()
|
||||
{
|
||||
ASSERT(IsOccupied());
|
||||
_state = HashSetBucketState::Deleted;
|
||||
Memory::DestructItem(&Item);
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
FORCE_INLINE void Occupy(const ItemType& item)
|
||||
{
|
||||
Memory::ConstructItems(&Item, &item, 1);
|
||||
_state = HashSetBucketState::Occupied;
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
FORCE_INLINE void Occupy(ItemType&& item)
|
||||
{
|
||||
Memory::MoveItems(&Item, &item, 1);
|
||||
_state = HashSetBucketState::Occupied;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsEmpty() const
|
||||
{
|
||||
return _state == HashSetBucketState::Empty;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsDeleted() const
|
||||
{
|
||||
return _state == HashSetBucketState::Deleted;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsOccupied() const
|
||||
{
|
||||
return _state == HashSetBucketState::Occupied;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsNotOccupied() const
|
||||
{
|
||||
return _state != HashSetBucketState::Occupied;
|
||||
}
|
||||
|
||||
FORCE_INLINE const T& GetKey() const
|
||||
{
|
||||
return Item;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Template for unordered set of values (without duplicates with O(1) lookup access).
|
||||
@@ -15,124 +125,12 @@
|
||||
/// <typeparam name="T">The type of elements in the set.</typeparam>
|
||||
/// <typeparam name="AllocationType">The type of memory allocator.</typeparam>
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
API_CLASS(InBuild) class HashSet
|
||||
API_CLASS(InBuild) class HashSet : public HashSetBase<AllocationType, HashSetBucket<T, AllocationType>>
|
||||
{
|
||||
friend HashSet;
|
||||
public:
|
||||
/// <summary>
|
||||
/// Describes single portion of space for the item in a hash map.
|
||||
/// </summary>
|
||||
struct Bucket
|
||||
{
|
||||
friend HashSet;
|
||||
friend Memory;
|
||||
|
||||
/// <summary>The item.</summary>
|
||||
T Item;
|
||||
|
||||
private:
|
||||
BucketState _state;
|
||||
|
||||
Bucket()
|
||||
: _state(BucketState::Empty)
|
||||
{
|
||||
}
|
||||
|
||||
Bucket(Bucket&& other) noexcept
|
||||
{
|
||||
_state = other._state;
|
||||
if (other._state == BucketState::Occupied)
|
||||
{
|
||||
Memory::MoveItems(&Item, &other.Item, 1);
|
||||
other._state = BucketState::Empty;
|
||||
}
|
||||
}
|
||||
|
||||
Bucket& operator=(Bucket&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (_state == BucketState::Occupied)
|
||||
{
|
||||
Memory::DestructItem(&Item);
|
||||
}
|
||||
_state = other._state;
|
||||
if (other._state == BucketState::Occupied)
|
||||
{
|
||||
Memory::MoveItems(&Item, &other.Item, 1);
|
||||
other._state = BucketState::Empty;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// <summary>Copying a bucket is useless, because a key must be unique in the dictionary.</summary>
|
||||
Bucket(const Bucket&) = delete;
|
||||
|
||||
/// <summary>Copying a bucket is useless, because a key must be unique in the dictionary.</summary>
|
||||
Bucket& operator=(const Bucket&) = delete;
|
||||
|
||||
~Bucket()
|
||||
{
|
||||
if (_state == BucketState::Occupied)
|
||||
Memory::DestructItem(&Item);
|
||||
}
|
||||
|
||||
FORCE_INLINE void Free()
|
||||
{
|
||||
if (_state == BucketState::Occupied)
|
||||
Memory::DestructItem(&Item);
|
||||
_state = BucketState::Empty;
|
||||
}
|
||||
|
||||
FORCE_INLINE void Delete()
|
||||
{
|
||||
_state = BucketState::Deleted;
|
||||
Memory::DestructItem(&Item);
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
FORCE_INLINE void Occupy(const ItemType& item)
|
||||
{
|
||||
Memory::ConstructItems(&Item, &item, 1);
|
||||
_state = BucketState::Occupied;
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
FORCE_INLINE void Occupy(ItemType&& item)
|
||||
{
|
||||
Memory::MoveItems(&Item, &item, 1);
|
||||
_state = BucketState::Occupied;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsEmpty() const
|
||||
{
|
||||
return _state == BucketState::Empty;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsDeleted() const
|
||||
{
|
||||
return _state == BucketState::Deleted;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsOccupied() const
|
||||
{
|
||||
return _state == BucketState::Occupied;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsNotOccupied() const
|
||||
{
|
||||
return _state != BucketState::Occupied;
|
||||
}
|
||||
};
|
||||
|
||||
using AllocationData = typename AllocationType::template Data<Bucket>;
|
||||
|
||||
private:
|
||||
int32 _elementsCount = 0;
|
||||
int32 _deletedCount = 0;
|
||||
int32 _size = 0;
|
||||
AllocationData _allocation;
|
||||
typedef HashSetBucket<T, AllocationType> Bucket;
|
||||
typedef HashSetBase<AllocationType, Bucket> Base;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -148,7 +146,7 @@ public:
|
||||
/// <param name="capacity">The number of elements that can be added without a need to allocate more memory.</param>
|
||||
FORCE_INLINE explicit HashSet(const int32 capacity)
|
||||
{
|
||||
SetCapacity(capacity);
|
||||
Base::SetCapacity(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -157,13 +155,7 @@ public:
|
||||
/// <param name="other">The other collection to move.</param>
|
||||
HashSet(HashSet&& other) noexcept
|
||||
{
|
||||
_elementsCount = other._elementsCount;
|
||||
_deletedCount = other._deletedCount;
|
||||
_size = other._size;
|
||||
other._elementsCount = 0;
|
||||
other._deletedCount = 0;
|
||||
other._size = 0;
|
||||
AllocationUtils::MoveToEmpty<Bucket, AllocationType>(_allocation, other._allocation, _size, _size);
|
||||
Base::MoveToEmpty(MoveTemp(other));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,15 +188,9 @@ public:
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
Clear();
|
||||
_allocation.Free();
|
||||
_elementsCount = other._elementsCount;
|
||||
_deletedCount = other._deletedCount;
|
||||
_size = other._size;
|
||||
other._elementsCount = 0;
|
||||
other._deletedCount = 0;
|
||||
other._size = 0;
|
||||
AllocationUtils::MoveToEmpty<Bucket, AllocationType>(_allocation, other._allocation, _size, _size);
|
||||
Base::Clear();
|
||||
Base::_allocation.Free();
|
||||
Base::MoveToEmpty(MoveTemp(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -214,113 +200,129 @@ public:
|
||||
/// </summary>
|
||||
~HashSet()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements in the collection.
|
||||
/// The read-only hash set collection iterator.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 Count() const
|
||||
{
|
||||
return _elementsCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements that can be contained by the collection.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 Capacity() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if collection is empty.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsEmpty() const
|
||||
{
|
||||
return _elementsCount == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if collection has one or more elements.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool HasItems() const
|
||||
{
|
||||
return _elementsCount != 0;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The hash set collection iterator.
|
||||
/// </summary>
|
||||
struct Iterator
|
||||
struct ConstIterator : Base::IteratorBase
|
||||
{
|
||||
friend HashSet;
|
||||
private:
|
||||
HashSet* _collection;
|
||||
int32 _index;
|
||||
|
||||
public:
|
||||
Iterator(HashSet* collection, const int32 index)
|
||||
: _collection(collection)
|
||||
, _index(index)
|
||||
ConstIterator(const HashSet* collection, const int32 index)
|
||||
: Base::IteratorBase(collection, index)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const HashSet* collection, const int32 index)
|
||||
: _collection(const_cast<HashSet*>(collection))
|
||||
, _index(index)
|
||||
ConstIterator()
|
||||
: Base::IteratorBase(nullptr, -1)
|
||||
{
|
||||
}
|
||||
|
||||
ConstIterator(const ConstIterator& i)
|
||||
: Base::IteratorBase(i._collection, i._index)
|
||||
{
|
||||
}
|
||||
|
||||
ConstIterator(ConstIterator&& i) noexcept
|
||||
: Base::IteratorBase(i._collection, i._index)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE bool operator!() const
|
||||
{
|
||||
return !(bool)*this;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator==(const ConstIterator& v) const
|
||||
{
|
||||
return this->_index == v._index && this->_collection == v._collection;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const ConstIterator& v) const
|
||||
{
|
||||
return this->_index != v._index || this->_collection != v._collection;
|
||||
}
|
||||
|
||||
ConstIterator& operator=(const ConstIterator& v)
|
||||
{
|
||||
this->_collection = v._collection;
|
||||
this->_index = v._index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator& operator=(ConstIterator&& v) noexcept
|
||||
{
|
||||
this->_collection = v._collection;
|
||||
this->_index = v._index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator& operator++()
|
||||
{
|
||||
this->Next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator operator++(int) const
|
||||
{
|
||||
ConstIterator i = *this;
|
||||
i.Next();
|
||||
return i;
|
||||
}
|
||||
|
||||
ConstIterator& operator--()
|
||||
{
|
||||
this->Prev();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator operator--(int) const
|
||||
{
|
||||
ConstIterator i = *this;
|
||||
i.Prev();
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The hash set collection iterator.
|
||||
/// </summary>
|
||||
struct Iterator : Base::IteratorBase
|
||||
{
|
||||
friend HashSet;
|
||||
public:
|
||||
Iterator(HashSet* collection, const int32 index)
|
||||
: Base::IteratorBase(collection, index)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator()
|
||||
: _collection(nullptr)
|
||||
, _index(-1)
|
||||
: Base::IteratorBase(nullptr, -1)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(const Iterator& i)
|
||||
: _collection(i._collection)
|
||||
, _index(i._index)
|
||||
: Base::IteratorBase(i._collection, i._index)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(Iterator&& i) noexcept
|
||||
: _collection(i._collection)
|
||||
, _index(i._index)
|
||||
: Base::IteratorBase(i._collection, i._index)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE int32 Index() const
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsEnd() const
|
||||
{
|
||||
return _index == _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsNotEnd() const
|
||||
{
|
||||
return _index != _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE Bucket& operator*() const
|
||||
{
|
||||
return _collection->_allocation.Get()[_index];
|
||||
return ((HashSet*)this->_collection)->_allocation.Get()[this->_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE Bucket* operator->() const
|
||||
{
|
||||
return &_collection->_allocation.Get()[_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE explicit operator bool() const
|
||||
{
|
||||
return _index >= 0 && _index < _collection->_size;
|
||||
return &((HashSet*)this->_collection)->_allocation.Get()[this->_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!() const
|
||||
@@ -330,87 +332,56 @@ public:
|
||||
|
||||
FORCE_INLINE bool operator==(const Iterator& v) const
|
||||
{
|
||||
return _index == v._index && _collection == v._collection;
|
||||
return this->_index == v._index && this->_collection == v._collection;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const Iterator& v) const
|
||||
{
|
||||
return _index != v._index || _collection != v._collection;
|
||||
return this->_index != v._index || this->_collection != v._collection;
|
||||
}
|
||||
|
||||
Iterator& operator=(const Iterator& v)
|
||||
{
|
||||
_collection = v._collection;
|
||||
_index = v._index;
|
||||
this->_collection = v._collection;
|
||||
this->_index = v._index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator& operator=(Iterator&& v) noexcept
|
||||
{
|
||||
_collection = v._collection;
|
||||
_index = v._index;
|
||||
this->_collection = v._collection;
|
||||
this->_index = v._index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
const int32 capacity = _collection->_size;
|
||||
if (_index != capacity)
|
||||
{
|
||||
const Bucket* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
++_index;
|
||||
}
|
||||
while (_index != capacity && data[_index].IsNotOccupied());
|
||||
}
|
||||
this->Next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int)
|
||||
Iterator operator++(int) const
|
||||
{
|
||||
Iterator i = *this;
|
||||
++i;
|
||||
i.Next();
|
||||
return i;
|
||||
}
|
||||
|
||||
Iterator& operator--()
|
||||
{
|
||||
if (_index > 0)
|
||||
{
|
||||
const Bucket* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
--_index;
|
||||
}
|
||||
while (_index > 0 && data[_index].IsNotOccupied());
|
||||
}
|
||||
this->Prev();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator--(int)
|
||||
{
|
||||
Iterator i = *this;
|
||||
--i;
|
||||
i.Prev();
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Removes all elements from the collection.
|
||||
/// </summary>
|
||||
void Clear()
|
||||
{
|
||||
if (_elementsCount + _deletedCount != 0)
|
||||
{
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
data[i].Free();
|
||||
_elementsCount = _deletedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the collection and delete value objects.
|
||||
/// Note: collection must contain pointers to the objects that have public destructor and be allocated using New method.
|
||||
@@ -425,93 +396,7 @@ public:
|
||||
if (i->Item)
|
||||
::Delete(i->Item);
|
||||
}
|
||||
Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes capacity of the collection
|
||||
/// </summary>
|
||||
/// <param name="capacity">New capacity</param>
|
||||
/// <param name="preserveContents">Enable/disable preserving collection contents during resizing</param>
|
||||
void SetCapacity(int32 capacity, const bool preserveContents = true)
|
||||
{
|
||||
if (capacity == _size)
|
||||
return;
|
||||
ASSERT(capacity >= 0);
|
||||
AllocationData oldAllocation;
|
||||
AllocationUtils::MoveToEmpty<Bucket, AllocationType>(oldAllocation, _allocation, _size, _size);
|
||||
const int32 oldSize = _size;
|
||||
const int32 oldElementsCount = _elementsCount;
|
||||
_deletedCount = _elementsCount = 0;
|
||||
if (capacity != 0 && (capacity & (capacity - 1)) != 0)
|
||||
capacity = AllocationUtils::AlignToPowerOf2(capacity);
|
||||
if (capacity)
|
||||
{
|
||||
_allocation.Allocate(capacity);
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < capacity; i++)
|
||||
data[i]._state = BucketState::Empty;
|
||||
}
|
||||
_size = capacity;
|
||||
Bucket* oldData = oldAllocation.Get();
|
||||
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
|
||||
{
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < oldSize; i++)
|
||||
{
|
||||
Bucket& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.Item, pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
||||
bucket = MoveTemp(oldBucket);
|
||||
_elementsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldElementsCount != 0)
|
||||
{
|
||||
for (int32 i = 0; i < oldSize; i++)
|
||||
oldData[i].Free();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that collection has given capacity.
|
||||
/// </summary>
|
||||
/// <param name="minCapacity">The minimum required capacity.</param>
|
||||
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
|
||||
void EnsureCapacity(int32 minCapacity, const bool preserveContents = true)
|
||||
{
|
||||
minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE;
|
||||
if (_size >= minCapacity)
|
||||
return;
|
||||
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
|
||||
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
|
||||
capacity = DICTIONARY_DEFAULT_CAPACITY;
|
||||
SetCapacity(capacity, preserveContents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
|
||||
/// </summary>
|
||||
/// <param name="other">The other collection.</param>
|
||||
void Swap(HashSet& other)
|
||||
{
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
{
|
||||
::Swap(_elementsCount, other._elementsCount);
|
||||
::Swap(_deletedCount, other._deletedCount);
|
||||
::Swap(_size, other._size);
|
||||
_allocation.Swap(other._allocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
HashSet tmp = MoveTemp(other);
|
||||
other = *this;
|
||||
*this = MoveTemp(tmp);
|
||||
}
|
||||
Base::Clear();
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -523,7 +408,7 @@ public:
|
||||
template<typename ItemType>
|
||||
bool Add(const ItemType& item)
|
||||
{
|
||||
Bucket* bucket = OnAdd(item);
|
||||
Bucket* bucket = Base::OnAdd(item, false);
|
||||
if (bucket)
|
||||
bucket->Occupy(item);
|
||||
return bucket != nullptr;
|
||||
@@ -536,7 +421,7 @@ public:
|
||||
/// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns>
|
||||
bool Add(T&& item)
|
||||
{
|
||||
Bucket* bucket = OnAdd(item);
|
||||
Bucket* bucket = Base::OnAdd(item, false);
|
||||
if (bucket)
|
||||
bucket->Occupy(MoveTemp(item));
|
||||
return bucket != nullptr;
|
||||
@@ -546,7 +431,7 @@ public:
|
||||
/// Add element at iterator to the collection
|
||||
/// </summary>
|
||||
/// <param name="i">Iterator with item to add</param>
|
||||
void Add(const Iterator& i)
|
||||
DEPRECATED("Use Add with separate Key and Value from iterator.") void Add(const Iterator& i)
|
||||
{
|
||||
ASSERT(i._collection != this && i);
|
||||
const Bucket& bucket = *i;
|
||||
@@ -561,15 +446,13 @@ public:
|
||||
template<typename ItemType>
|
||||
bool Remove(const ItemType& item)
|
||||
{
|
||||
if (IsEmpty())
|
||||
return false;
|
||||
FindPositionResult pos;
|
||||
FindPosition(item, pos);
|
||||
typename Base::FindPositionResult pos;
|
||||
Base::FindPosition(item, pos);
|
||||
if (pos.ObjectIndex != -1)
|
||||
{
|
||||
_allocation.Get()[pos.ObjectIndex].Delete();
|
||||
--_elementsCount;
|
||||
++_deletedCount;
|
||||
Base::_allocation.Get()[pos.ObjectIndex].Delete();
|
||||
--Base::_elementsCount;
|
||||
++Base::_deletedCount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -585,10 +468,9 @@ public:
|
||||
ASSERT(i._collection == this);
|
||||
if (i)
|
||||
{
|
||||
ASSERT(_allocation.Get()[i._index].IsOccupied());
|
||||
_allocation.Get()[i._index].Delete();
|
||||
--_elementsCount;
|
||||
++_deletedCount;
|
||||
Base::_allocation.Get()[i._index].Delete();
|
||||
--Base::_elementsCount;
|
||||
++Base::_deletedCount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -601,15 +483,26 @@ public:
|
||||
/// <param name="item">Item to find</param>
|
||||
/// <returns>Iterator for the found element or End if cannot find it</returns>
|
||||
template<typename ItemType>
|
||||
Iterator Find(const ItemType& item) const
|
||||
Iterator Find(const ItemType& item)
|
||||
{
|
||||
if (IsEmpty())
|
||||
return End();
|
||||
FindPositionResult pos;
|
||||
FindPosition(item, pos);
|
||||
typename Base::FindPositionResult pos;
|
||||
Base::FindPosition(item, pos);
|
||||
return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find element with given item in the collection
|
||||
/// </summary>
|
||||
/// <param name="item">Item to find</param>
|
||||
/// <returns>Iterator for the found element or End if cannot find it</returns>
|
||||
template<typename ItemType>
|
||||
ConstIterator Find(const ItemType& item) const
|
||||
{
|
||||
typename Base::FindPositionResult pos;
|
||||
Base::FindPosition(item, pos);
|
||||
return pos.ObjectIndex != -1 ? ConstIterator(this, pos.ObjectIndex) : End();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a collection contains the specified element.
|
||||
/// </summary>
|
||||
@@ -618,39 +511,60 @@ public:
|
||||
template<typename ItemType>
|
||||
bool Contains(const ItemType& item) const
|
||||
{
|
||||
if (IsEmpty())
|
||||
return false;
|
||||
FindPositionResult pos;
|
||||
FindPosition(item, pos);
|
||||
typename Base::FindPositionResult pos;
|
||||
Base::FindPosition(item, pos);
|
||||
return pos.ObjectIndex != -1;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Clones other collection into this
|
||||
/// Clones other collection into this.
|
||||
/// </summary>
|
||||
/// <param name="other">Other collection to clone</param>
|
||||
/// <param name="other">The other collection to clone.</param>
|
||||
void Clone(const HashSet& other)
|
||||
{
|
||||
Clear();
|
||||
SetCapacity(other.Capacity(), false);
|
||||
for (Iterator i = other.Begin(); i != other.End(); ++i)
|
||||
Add(i);
|
||||
ASSERT(Count() == other.Count());
|
||||
ASSERT(Capacity() == other.Capacity());
|
||||
Base::Clear();
|
||||
Base::SetCapacity(other.Capacity(), false);
|
||||
for (ConstIterator i = other.Begin(); i != other.End(); ++i)
|
||||
Add(i->Item);
|
||||
ASSERT(Base::Count() == other.Count());
|
||||
ASSERT(Base::Capacity() == other.Capacity());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items collection to the output array (will contain unique items).
|
||||
/// </summary>
|
||||
/// <param name="result">The result.</param>
|
||||
template<typename ArrayAllocation>
|
||||
void GetItems(Array<T, ArrayAllocation>& result) const
|
||||
{
|
||||
for (ConstIterator i = Begin(); i.IsNotEnd(); ++i)
|
||||
result.Add(i->Item);
|
||||
}
|
||||
|
||||
public:
|
||||
Iterator Begin() const
|
||||
Iterator Begin()
|
||||
{
|
||||
Iterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
Iterator End() const
|
||||
Iterator End()
|
||||
{
|
||||
return Iterator(this, _size);
|
||||
return Iterator(this, Base::_size);
|
||||
}
|
||||
|
||||
ConstIterator Begin() const
|
||||
{
|
||||
ConstIterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
ConstIterator End() const
|
||||
{
|
||||
return ConstIterator(this, Base::_size);
|
||||
}
|
||||
|
||||
Iterator begin()
|
||||
@@ -662,141 +576,18 @@ public:
|
||||
|
||||
FORCE_INLINE Iterator end()
|
||||
{
|
||||
return Iterator(this, _size);
|
||||
return Iterator(this, Base::_size);
|
||||
}
|
||||
|
||||
Iterator begin() const
|
||||
ConstIterator begin() const
|
||||
{
|
||||
Iterator i(this, -1);
|
||||
ConstIterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
FORCE_INLINE Iterator end() const
|
||||
FORCE_INLINE ConstIterator end() const
|
||||
{
|
||||
return Iterator(this, _size);
|
||||
}
|
||||
|
||||
private:
|
||||
/// <summary>
|
||||
/// The result container of the set item lookup searching.
|
||||
/// </summary>
|
||||
struct FindPositionResult
|
||||
{
|
||||
int32 ObjectIndex;
|
||||
int32 FreeSlotIndex;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pair of positions: 1st where the object is, 2nd where
|
||||
/// it would go if you wanted to insert it. 1st is -1
|
||||
/// if object is not found; 2nd is -1 if it is.
|
||||
/// Note: because of deletions where-to-insert is not trivial: it's the
|
||||
/// first deleted bucket we see, as long as we don't find the item later
|
||||
/// </summary>
|
||||
/// <param name="item">The item to find</param>
|
||||
/// <param name="result">Pair of values: where the object is and where it would go if you wanted to insert it</param>
|
||||
template<typename ItemType>
|
||||
void FindPosition(const ItemType& item, FindPositionResult& result) const
|
||||
{
|
||||
ASSERT(_size);
|
||||
const int32 tableSizeMinusOne = _size - 1;
|
||||
int32 bucketIndex = GetHash(item) & tableSizeMinusOne;
|
||||
int32 insertPos = -1;
|
||||
int32 numChecks = 0;
|
||||
const Bucket* data = _allocation.Get();
|
||||
result.FreeSlotIndex = -1;
|
||||
while (numChecks < _size)
|
||||
{
|
||||
// Empty bucket
|
||||
const Bucket& bucket = data[bucketIndex];
|
||||
if (bucket.IsEmpty())
|
||||
{
|
||||
// Found place to insert
|
||||
result.ObjectIndex = -1;
|
||||
result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos;
|
||||
return;
|
||||
}
|
||||
// Deleted bucket
|
||||
if (bucket.IsDeleted())
|
||||
{
|
||||
// Keep searching but mark to insert
|
||||
if (insertPos == -1)
|
||||
insertPos = bucketIndex;
|
||||
}
|
||||
// Occupied bucket by target item
|
||||
else if (bucket.Item == item)
|
||||
{
|
||||
// Found item
|
||||
result.ObjectIndex = bucketIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
numChecks++;
|
||||
bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, numChecks)) & tableSizeMinusOne;
|
||||
}
|
||||
result.ObjectIndex = -1;
|
||||
result.FreeSlotIndex = insertPos;
|
||||
}
|
||||
|
||||
template<typename ItemType>
|
||||
Bucket* OnAdd(const ItemType& key)
|
||||
{
|
||||
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
|
||||
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
|
||||
Compact();
|
||||
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE);
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
FindPosition(key, pos);
|
||||
|
||||
// Check if object has been already added
|
||||
if (pos.ObjectIndex != -1)
|
||||
return nullptr;
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
++_elementsCount;
|
||||
return &_allocation.Get()[pos.FreeSlotIndex];
|
||||
}
|
||||
|
||||
void Compact()
|
||||
{
|
||||
if (_elementsCount == 0)
|
||||
{
|
||||
// Fast path if it's empty
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
data[i]._state = BucketState::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rebuild entire table completely
|
||||
AllocationData oldAllocation;
|
||||
AllocationUtils::MoveToEmpty<Bucket, AllocationType>(oldAllocation, _allocation, _size, _size);
|
||||
_allocation.Allocate(_size);
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
data[i]._state = BucketState::Empty;
|
||||
Bucket* oldData = oldAllocation.Get();
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
{
|
||||
Bucket& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.Item, pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
||||
bucket = MoveTemp(oldBucket);
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
oldData[i].Free();
|
||||
}
|
||||
_deletedCount = 0;
|
||||
return ConstIterator(this, Base::_size);
|
||||
}
|
||||
};
|
||||
|
||||
400
Source/Engine/Core/Collections/HashSetBase.h
Normal file
400
Source/Engine/Core/Collections/HashSetBase.h
Normal file
@@ -0,0 +1,400 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Memory/Memory.h"
|
||||
#include "Engine/Core/Memory/Allocation.h"
|
||||
#include "Engine/Core/Memory/AllocationUtils.h"
|
||||
#include "Engine/Core/Collections/HashFunctions.h"
|
||||
#include "Engine/Core/Collections/Config.h"
|
||||
|
||||
/// <summary>
|
||||
/// Tells if the object is occupied, and if not, if the bucket is a subject of compaction.
|
||||
/// </summary>
|
||||
enum class HashSetBucketState : byte
|
||||
{
|
||||
Empty = 0,
|
||||
Deleted = 1,
|
||||
Occupied = 2,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Base class for unordered set of values (without duplicates with O(1) lookup access).
|
||||
/// </summary>
|
||||
/// <typeparam name="BucketType">The type of bucket structure that stores element data and state.</typeparam>
|
||||
/// <typeparam name="AllocationType">The type of memory allocator.</typeparam>
|
||||
template<typename AllocationType, typename BucketType>
|
||||
class HashSetBase
|
||||
{
|
||||
friend HashSetBase;
|
||||
|
||||
public:
|
||||
// Type of allocation data used to store hash set buckets.
|
||||
using AllocationData = typename AllocationType::template Data<BucketType>;
|
||||
|
||||
protected:
|
||||
int32 _elementsCount = 0;
|
||||
int32 _deletedCount = 0;
|
||||
int32 _size = 0;
|
||||
AllocationData _allocation;
|
||||
|
||||
HashSetBase()
|
||||
{
|
||||
}
|
||||
|
||||
void MoveToEmpty(HashSetBase&& other)
|
||||
{
|
||||
_elementsCount = other._elementsCount;
|
||||
_deletedCount = other._deletedCount;
|
||||
_size = other._size;
|
||||
other._elementsCount = 0;
|
||||
other._deletedCount = 0;
|
||||
other._size = 0;
|
||||
AllocationUtils::MoveToEmpty<BucketType, AllocationType>(_allocation, other._allocation, _size, _size);
|
||||
}
|
||||
|
||||
~HashSetBase()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements in the collection.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 Count() const
|
||||
{
|
||||
return _elementsCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements that can be contained by the collection.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 Capacity() const
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if collection is empty.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsEmpty() const
|
||||
{
|
||||
return _elementsCount == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if collection has one or more elements.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool HasItems() const
|
||||
{
|
||||
return _elementsCount != 0;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Removes all elements from the collection.
|
||||
/// </summary>
|
||||
void Clear()
|
||||
{
|
||||
if (_elementsCount + _deletedCount != 0)
|
||||
{
|
||||
BucketType* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
data[i].Free();
|
||||
_elementsCount = _deletedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the capacity of the collection.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The new capacity.</param>
|
||||
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
|
||||
void SetCapacity(int32 capacity, const bool preserveContents = true)
|
||||
{
|
||||
if (capacity == _size)
|
||||
return;
|
||||
ASSERT(capacity >= 0);
|
||||
AllocationData oldAllocation;
|
||||
AllocationUtils::MoveToEmpty<BucketType, AllocationType>(oldAllocation, _allocation, _size, _size);
|
||||
const int32 oldSize = _size;
|
||||
const int32 oldElementsCount = _elementsCount;
|
||||
_deletedCount = _elementsCount = 0;
|
||||
if (capacity != 0 && (capacity & (capacity - 1)) != 0)
|
||||
capacity = AllocationUtils::AlignToPowerOf2(capacity);
|
||||
if (capacity)
|
||||
{
|
||||
_allocation.Allocate(capacity);
|
||||
BucketType* data = _allocation.Get();
|
||||
for (int32 i = 0; i < capacity; i++)
|
||||
data[i]._state = HashSetBucketState::Empty;
|
||||
}
|
||||
_size = capacity;
|
||||
BucketType* oldData = oldAllocation.Get();
|
||||
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
|
||||
{
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < oldSize; i++)
|
||||
{
|
||||
BucketType& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.GetKey(), pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
||||
bucket = MoveTemp(oldBucket);
|
||||
_elementsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldElementsCount != 0)
|
||||
{
|
||||
for (int32 i = 0; i < oldSize; i++)
|
||||
oldData[i].Free();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that collection has given capacity.
|
||||
/// </summary>
|
||||
/// <param name="minCapacity">The minimum required capacity.</param>
|
||||
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
|
||||
void EnsureCapacity(int32 minCapacity, const bool preserveContents = true)
|
||||
{
|
||||
minCapacity *= DICTIONARY_DEFAULT_SLACK_SCALE;
|
||||
if (_size >= minCapacity)
|
||||
return;
|
||||
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
|
||||
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
|
||||
capacity = DICTIONARY_DEFAULT_CAPACITY;
|
||||
SetCapacity(capacity, preserveContents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
|
||||
/// </summary>
|
||||
/// <param name="other">The other collection.</param>
|
||||
void Swap(HashSetBase& other)
|
||||
{
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
{
|
||||
::Swap(_elementsCount, other._elementsCount);
|
||||
::Swap(_deletedCount, other._deletedCount);
|
||||
::Swap(_size, other._size);
|
||||
_allocation.Swap(other._allocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
::Swap(other, *this);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The collection iterator base implementation.
|
||||
/// </summary>
|
||||
struct IteratorBase
|
||||
{
|
||||
protected:
|
||||
const HashSetBase* _collection;
|
||||
int32 _index;
|
||||
|
||||
IteratorBase(const HashSetBase* collection, const int32 index)
|
||||
: _collection(collection)
|
||||
, _index(index)
|
||||
{
|
||||
}
|
||||
|
||||
void Next()
|
||||
{
|
||||
const int32 capacity = _collection->_size;
|
||||
if (_index != capacity)
|
||||
{
|
||||
const BucketType* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
++_index;
|
||||
}
|
||||
while (_index != capacity && data[_index].IsNotOccupied());
|
||||
}
|
||||
}
|
||||
|
||||
void Prev()
|
||||
{
|
||||
if (_index > 0)
|
||||
{
|
||||
const BucketType* data = _collection->_allocation.Get();
|
||||
do
|
||||
{
|
||||
--_index;
|
||||
}
|
||||
while (_index > 0 && data[_index].IsNotOccupied());
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE int32 Index() const
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsEnd() const
|
||||
{
|
||||
return _index == _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool IsNotEnd() const
|
||||
{
|
||||
return _index != _collection->_size;
|
||||
}
|
||||
|
||||
FORCE_INLINE const BucketType& operator*() const
|
||||
{
|
||||
return _collection->_allocation.Get()[_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE const BucketType* operator->() const
|
||||
{
|
||||
return &_collection->_allocation.Get()[_index];
|
||||
}
|
||||
|
||||
FORCE_INLINE explicit operator bool() const
|
||||
{
|
||||
return _index >= 0 && _index < _collection->_size;
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
/// <summary>
|
||||
/// The result container of the set item lookup searching.
|
||||
/// </summary>
|
||||
struct FindPositionResult
|
||||
{
|
||||
int32 ObjectIndex;
|
||||
int32 FreeSlotIndex;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pair of positions: 1st where the object is, 2nd where it would go if you wanted to insert it.
|
||||
/// 1st is -1 if object is not found; 2nd is -1 if it is.
|
||||
/// Because of deletions where-to-insert is not trivial: it's the first deleted bucket we see, as long as we don't find the item later.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to find</param>
|
||||
/// <param name="result">A pair of values: where the object is and where it would go if you wanted to insert it</param>
|
||||
template<typename KeyComparableType>
|
||||
void FindPosition(const KeyComparableType& key, FindPositionResult& result) const
|
||||
{
|
||||
result.FreeSlotIndex = -1;
|
||||
if (_size == 0)
|
||||
{
|
||||
result.ObjectIndex = -1;
|
||||
return;
|
||||
}
|
||||
const int32 tableSizeMinusOne = _size - 1;
|
||||
int32 bucketIndex = GetHash(key) & tableSizeMinusOne;
|
||||
int32 insertPos = -1;
|
||||
int32 checksCount = 0;
|
||||
const BucketType* data = _allocation.Get();
|
||||
while (checksCount < _size)
|
||||
{
|
||||
// Empty bucket
|
||||
const BucketType& bucket = data[bucketIndex];
|
||||
if (bucket.IsEmpty())
|
||||
{
|
||||
// Found place to insert
|
||||
result.ObjectIndex = -1;
|
||||
result.FreeSlotIndex = insertPos == -1 ? bucketIndex : insertPos;
|
||||
return;
|
||||
}
|
||||
// Deleted bucket
|
||||
if (bucket.IsDeleted())
|
||||
{
|
||||
// Keep searching but mark to insert
|
||||
if (insertPos == -1)
|
||||
insertPos = bucketIndex;
|
||||
}
|
||||
// Occupied bucket by target item
|
||||
else if (bucket.GetKey() == key)
|
||||
{
|
||||
// Found item
|
||||
result.ObjectIndex = bucketIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to the next bucket
|
||||
checksCount++;
|
||||
bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, checksCount)) & tableSizeMinusOne;
|
||||
}
|
||||
result.ObjectIndex = -1;
|
||||
result.FreeSlotIndex = insertPos;
|
||||
}
|
||||
|
||||
template<typename KeyComparableType>
|
||||
BucketType* OnAdd(const KeyComparableType& key, bool checkUnique = true)
|
||||
{
|
||||
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
|
||||
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
|
||||
Compact();
|
||||
|
||||
// Ensure to have enough memory for the next item (in case of new element insertion)
|
||||
EnsureCapacity(((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount) / DICTIONARY_DEFAULT_SLACK_SCALE);
|
||||
|
||||
// Find location of the item or place to insert it
|
||||
FindPositionResult pos;
|
||||
FindPosition(key, pos);
|
||||
|
||||
// Check if object has been already added
|
||||
if (pos.ObjectIndex != -1)
|
||||
{
|
||||
if (checkUnique)
|
||||
{
|
||||
Platform::CheckFailed("That key has been already added to the collection.", __FILE__, __LINE__);
|
||||
return nullptr;
|
||||
}
|
||||
return &_allocation.Get()[pos.ObjectIndex];
|
||||
}
|
||||
|
||||
// Insert
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
++_elementsCount;
|
||||
return &_allocation.Get()[pos.FreeSlotIndex];
|
||||
}
|
||||
|
||||
void Compact()
|
||||
{
|
||||
if (_elementsCount == 0)
|
||||
{
|
||||
// Fast path if it's empty
|
||||
BucketType* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
data[i]._state = HashSetBucketState::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rebuild entire table completely
|
||||
AllocationData oldAllocation;
|
||||
AllocationUtils::MoveToEmpty<BucketType, AllocationType>(oldAllocation, _allocation, _size, _size);
|
||||
_allocation.Allocate(_size);
|
||||
BucketType* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
data[i]._state = HashSetBucketState::Empty;
|
||||
BucketType* oldData = oldAllocation.Get();
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
{
|
||||
BucketType& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.GetKey(), pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
||||
bucket = MoveTemp(oldBucket);
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
oldData[i].Free();
|
||||
}
|
||||
_deletedCount = 0;
|
||||
}
|
||||
};
|
||||
@@ -83,6 +83,8 @@ template<typename T, typename U>
|
||||
class Pair;
|
||||
template<typename KeyType, typename ValueType, typename AllocationType>
|
||||
class Dictionary;
|
||||
template<typename T, typename AllocationType>
|
||||
class HashSet;
|
||||
template<typename>
|
||||
class Function;
|
||||
template<typename... Params>
|
||||
|
||||
Reference in New Issue
Block a user