diff --git a/Source/Engine/Core/Collections/BucketState.h b/Source/Engine/Core/Collections/BucketState.h
deleted file mode 100644
index 16cbdb845..000000000
--- a/Source/Engine/Core/Collections/BucketState.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
-
-#pragma once
-
-#include "Engine/Core/Types/BaseTypes.h"
-
-///
-/// Tells if the object is occupied, and if not, if the bucket is a subject of compaction.
-///
-enum class BucketState : byte
-{
- Empty = 0,
- Deleted = 1,
- Occupied = 2,
-};
diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h
index 0b717dc52..b70ac85e8 100644
--- a/Source/Engine/Core/Collections/Dictionary.h
+++ b/Source/Engine/Core/Collections/Dictionary.h
@@ -2,12 +2,144 @@
#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"
+
+///
+/// Describes single portion of space for the key and value pair in a hash map.
+///
+template
+struct DictionaryBucket
+{
+ friend Memory;
+ friend HashSetBase;
+ friend Dictionary;
+
+ /// The key.
+ KeyType Key;
+ /// The value.
+ ValueType Value;
+
+private:
+ HashSetBucketState _state;
+
+ DictionaryBucket()
+ : _state(HashSetBucketState::Empty)
+ {
+ }
+
+ DictionaryBucket(DictionaryBucket&& other) noexcept
+ {
+ _state = other._state;
+ if (other._state == HashSetBucketState::Occupied)
+ {
+ Memory::MoveItems(&Key, &other.Key, 1);
+ Memory::MoveItems(&Value, &other.Value, 1);
+ other._state = HashSetBucketState::Empty;
+ }
+ }
+
+ DictionaryBucket& operator=(DictionaryBucket&& other) noexcept
+ {
+ if (this != &other)
+ {
+ if (_state == HashSetBucketState::Occupied)
+ {
+ Memory::DestructItem(&Key);
+ Memory::DestructItem(&Value);
+ }
+ _state = other._state;
+ if (other._state == HashSetBucketState::Occupied)
+ {
+ Memory::MoveItems(&Key, &other.Key, 1);
+ Memory::MoveItems(&Value, &other.Value, 1);
+ other._state = HashSetBucketState::Empty;
+ }
+ }
+ return *this;
+ }
+
+ /// Copying a bucket is useless, because a key must be unique in the dictionary.
+ DictionaryBucket(const DictionaryBucket&) = delete;
+
+ /// Copying a bucket is useless, because a key must be unique in the dictionary.
+ DictionaryBucket& operator=(const DictionaryBucket&) = delete;
+
+ ~DictionaryBucket()
+ {
+ if (_state == HashSetBucketState::Occupied)
+ {
+ Memory::DestructItem(&Key);
+ Memory::DestructItem(&Value);
+ }
+ }
+
+ FORCE_INLINE void Free()
+ {
+ if (_state == HashSetBucketState::Occupied)
+ {
+ Memory::DestructItem(&Key);
+ Memory::DestructItem(&Value);
+ }
+ _state = HashSetBucketState::Empty;
+ }
+
+ FORCE_INLINE void Delete()
+ {
+ ASSERT(IsOccupied());
+ _state = HashSetBucketState::Deleted;
+ Memory::DestructItem(&Key);
+ Memory::DestructItem(&Value);
+ }
+
+ template
+ FORCE_INLINE void Occupy(const KeyComparableType& key)
+ {
+ Memory::ConstructItems(&Key, &key, 1);
+ Memory::ConstructItem(&Value);
+ _state = HashSetBucketState::Occupied;
+ }
+
+ template
+ FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
+ {
+ Memory::ConstructItems(&Key, &key, 1);
+ Memory::ConstructItems(&Value, &value, 1);
+ _state = HashSetBucketState::Occupied;
+ }
+
+ template
+ FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
+ {
+ Memory::ConstructItems(&Key, &key, 1);
+ Memory::MoveItems(&Value, &value, 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 KeyType& GetKey() const
+ {
+ return Key;
+ }
+};
///
/// Template for unordered dictionary with mapped key with value pairs.
@@ -16,146 +148,12 @@
/// The type of the values in the dictionary.
/// The type of memory allocator.
template
-API_CLASS(InBuild) class Dictionary
+API_CLASS(InBuild) class Dictionary : public HashSetBase>
{
friend Dictionary;
public:
- ///
- /// Describes single portion of space for the key and value pair in a hash map.
- ///
- struct Bucket
- {
- friend Dictionary;
- friend Memory;
-
- /// The key.
- KeyType Key;
- /// The value.
- ValueType Value;
-
- private:
- BucketState _state;
-
- Bucket()
- : _state(BucketState::Empty)
- {
- }
-
- Bucket(Bucket&& other) noexcept
- {
- _state = other._state;
- if (other._state == BucketState::Occupied)
- {
- Memory::MoveItems(&Key, &other.Key, 1);
- Memory::MoveItems(&Value, &other.Value, 1);
- other._state = BucketState::Empty;
- }
- }
-
- Bucket& operator=(Bucket&& other) noexcept
- {
- if (this != &other)
- {
- if (_state == BucketState::Occupied)
- {
- Memory::DestructItem(&Key);
- Memory::DestructItem(&Value);
- }
- _state = other._state;
- if (other._state == BucketState::Occupied)
- {
- Memory::MoveItems(&Key, &other.Key, 1);
- Memory::MoveItems(&Value, &other.Value, 1);
- other._state = BucketState::Empty;
- }
- }
- return *this;
- }
-
- /// Copying a bucket is useless, because a key must be unique in the dictionary.
- Bucket(const Bucket&) = delete;
-
- /// Copying a bucket is useless, because a key must be unique in the dictionary.
- Bucket& operator=(const Bucket&) = delete;
-
- ~Bucket()
- {
- if (_state == BucketState::Occupied)
- {
- Memory::DestructItem(&Key);
- Memory::DestructItem(&Value);
- }
- }
-
- FORCE_INLINE void Free()
- {
- if (_state == BucketState::Occupied)
- {
- Memory::DestructItem(&Key);
- Memory::DestructItem(&Value);
- }
- _state = BucketState::Empty;
- }
-
- FORCE_INLINE void Delete()
- {
- _state = BucketState::Deleted;
- Memory::DestructItem(&Key);
- Memory::DestructItem(&Value);
- }
-
- template
- FORCE_INLINE void Occupy(const KeyComparableType& key)
- {
- Memory::ConstructItems(&Key, &key, 1);
- Memory::ConstructItem(&Value);
- _state = BucketState::Occupied;
- }
-
- template
- FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
- {
- Memory::ConstructItems(&Key, &key, 1);
- Memory::ConstructItems(&Value, &value, 1);
- _state = BucketState::Occupied;
- }
-
- template
- FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
- {
- Memory::ConstructItems(&Key, &key, 1);
- Memory::MoveItems(&Value, &value, 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;
-
-private:
- int32 _elementsCount = 0;
- int32 _deletedCount = 0;
- int32 _size = 0;
- AllocationData _allocation;
+ typedef DictionaryBucket Bucket;
+ typedef HashSetBase Base;
public:
///
@@ -171,7 +169,7 @@ public:
/// The number of elements that can be added without a need to allocate more memory.
FORCE_INLINE explicit Dictionary(const int32 capacity)
{
- SetCapacity(capacity);
+ Base::SetCapacity(capacity);
}
///
@@ -180,13 +178,7 @@ public:
/// The other collection to move.
Dictionary(Dictionary&& other) noexcept
{
- _elementsCount = other._elementsCount;
- _deletedCount = other._deletedCount;
- _size = other._size;
- other._elementsCount = 0;
- other._deletedCount = 0;
- other._size = 0;
- AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size);
+ Base::MoveToEmpty(MoveTemp(other));
}
///
@@ -219,15 +211,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(_allocation, other._allocation, _size, _size);
+ Base::Clear();
+ Base::_allocation.Free();
+ Base::MoveToEmpty(MoveTemp(other));
}
return *this;
}
@@ -237,113 +223,129 @@ public:
///
~Dictionary()
{
- Clear();
}
public:
///
- /// Gets the amount of the elements in the collection.
+ /// The read-only dictionary collection iterator.
///
- FORCE_INLINE int32 Count() const
- {
- return _elementsCount;
- }
-
- ///
- /// Gets the amount of the elements that can be contained by the collection.
- ///
- FORCE_INLINE int32 Capacity() const
- {
- return _size;
- }
-
- ///
- /// Returns true if collection is empty.
- ///
- FORCE_INLINE bool IsEmpty() const
- {
- return _elementsCount == 0;
- }
-
- ///
- /// Returns true if collection has one or more elements.
- ///
- FORCE_INLINE bool HasItems() const
- {
- return _elementsCount != 0;
- }
-
-public:
- ///
- /// The Dictionary collection iterator.
- ///
- struct Iterator
+ struct ConstIterator : Base::IteratorBase
{
friend Dictionary;
- private:
- Dictionary* _collection;
- int32 _index;
-
public:
- Iterator(Dictionary* collection, const int32 index)
- : _collection(collection)
- , _index(index)
+ ConstIterator(const Dictionary* collection, const int32 index)
+ : Base::IteratorBase(collection, index)
{
}
- Iterator(Dictionary const* collection, const int32 index)
- : _collection(const_cast(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;
+ }
+ };
+
+ ///
+ /// The dictionary collection iterator.
+ ///
+ struct Iterator : Base::IteratorBase
+ {
+ friend Dictionary;
+ public:
+ Iterator(Dictionary* 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 ((Dictionary*)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 &((Dictionary*)this->_collection)->_allocation.Get()[this->_index];
}
FORCE_INLINE bool operator!() const
@@ -353,68 +355,51 @@ 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) 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) const
{
Iterator i = *this;
- --i;
+ i.Prev();
return i;
}
};
@@ -428,27 +413,10 @@ public:
template
ValueType& At(const KeyComparableType& 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 that key has been already added
- if (pos.ObjectIndex != -1)
- return _allocation.Get()[pos.ObjectIndex].Value;
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- ++_elementsCount;
- Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
- bucket.Occupy(key);
- return bucket.Value;
+ Bucket* bucket = Base::OnAdd(key, false);
+ if (bucket->_state != HashSetBucketState::Occupied)
+ bucket->Occupy(key);
+ return bucket->Value;
}
///
@@ -459,10 +427,10 @@ public:
template
const ValueType& At(const KeyComparableType& key) const
{
- FindPositionResult pos;
- FindPosition(key, pos);
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(key, pos);
ASSERT(pos.ObjectIndex != -1);
- return _allocation.Get()[pos.ObjectIndex].Value;
+ return Base::_allocation.Get()[pos.ObjectIndex].Value;
}
///
@@ -496,13 +464,11 @@ public:
template
bool TryGet(const KeyComparableType& key, ValueType& result) const
{
- if (IsEmpty())
- return false;
- FindPositionResult pos;
- FindPosition(key, pos);
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(key, pos);
if (pos.ObjectIndex == -1)
return false;
- result = _allocation.Get()[pos.ObjectIndex].Value;
+ result = Base::_allocation.Get()[pos.ObjectIndex].Value;
return true;
}
@@ -514,30 +480,14 @@ public:
template
ValueType* TryGet(const KeyComparableType& key) const
{
- if (IsEmpty())
- return nullptr;
- FindPositionResult pos;
- FindPosition(key, pos);
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(key, pos);
if (pos.ObjectIndex == -1)
return nullptr;
- return const_cast(&_allocation.Get()[pos.ObjectIndex].Value); //TODO This one is problematic. I think this entire method should be removed.
+ return const_cast(&Base::_allocation.Get()[pos.ObjectIndex].Value);
}
public:
- ///
- /// Clears the collection but without changing its capacity (all inserted elements: keys and values will be removed).
- ///
- void Clear()
- {
- if (_elementsCount + _deletedCount != 0)
- {
- Bucket* data = _allocation.Get();
- for (int32 i = 0; i < _size; i++)
- data[i].Free();
- _elementsCount = _deletedCount = 0;
- }
- }
-
///
/// 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.
@@ -552,91 +502,7 @@ public:
if (i->Value)
::Delete(i->Value);
}
- Clear();
- }
-
- ///
- /// Changes the capacity of the collection.
- ///
- /// The new capacity.
- /// Enables preserving collection contents during resizing.
- void SetCapacity(int32 capacity, const bool preserveContents = true)
- {
- if (capacity == _size)
- return;
- ASSERT(capacity >= 0);
- AllocationData oldAllocation;
- AllocationUtils::MoveToEmpty(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.Key, 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();
- }
- }
-
- ///
- /// Ensures that collection has given capacity.
- ///
- /// The minimum required items capacity.
- /// True if preserve collection data when changing its size, otherwise collection after resize will be empty.
- 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);
- }
-
- ///
- /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
- ///
- /// The other collection.
- void Swap(Dictionary& 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);
- }
+ Base::Clear();
}
public:
@@ -649,7 +515,7 @@ public:
template
FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
{
- Bucket* bucket = OnAdd(key);
+ Bucket* bucket = Base::OnAdd(key);
bucket->Occupy(key, value);
return bucket;
}
@@ -663,7 +529,7 @@ public:
template
FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
{
- Bucket* bucket = OnAdd(key);
+ Bucket* bucket = Base::OnAdd(key);
bucket->Occupy(key, MoveTemp(value));
return bucket;
}
@@ -672,7 +538,7 @@ public:
/// Add pair element to the collection.
///
/// Iterator with key and value.
- 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;
@@ -687,15 +553,13 @@ public:
template
bool Remove(const KeyComparableType& key)
{
- if (IsEmpty())
- return false;
- FindPositionResult pos;
- FindPosition(key, pos);
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(key, 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;
@@ -711,10 +575,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;
@@ -746,15 +609,28 @@ public:
/// The key to find.
/// The iterator for the found element or End if cannot find it.
template
- Iterator Find(const KeyComparableType& key) const
+ Iterator Find(const KeyComparableType& key)
{
- if (IsEmpty())
+ if (Base::IsEmpty())
return End();
- FindPositionResult pos;
- FindPosition(key, pos);
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(key, pos);
return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End();
}
+ ///
+ /// Finds the element with given key in the collection.
+ ///
+ /// The key to find.
+ /// The iterator for the found element or End if cannot find it.
+ template
+ ConstIterator Find(const KeyComparableType& key) const
+ {
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(key, pos);
+ return pos.ObjectIndex != -1 ? ConstIterator(this, pos.ObjectIndex) : End();
+ }
+
///
/// Checks if given key is in a collection.
///
@@ -763,10 +639,8 @@ public:
template
bool ContainsKey(const KeyComparableType& key) const
{
- if (IsEmpty())
- return false;
- FindPositionResult pos;
- FindPosition(key, pos);
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(key, pos);
return pos.ObjectIndex != -1;
}
@@ -777,10 +651,10 @@ public:
/// True if value has been found in a collection, otherwise false.
bool ContainsValue(const ValueType& value) const
{
- if (HasItems())
+ if (Base::HasItems())
{
- const Bucket* data = _allocation.Get();
- for (int32 i = 0; i < _size; ++i)
+ const Bucket* data = Base::_allocation.Get();
+ for (int32 i = 0; i < Base::_size; ++i)
{
if (data[i].IsOccupied() && data[i].Value == value)
return true;
@@ -797,10 +671,10 @@ public:
/// True if value has been found, otherwise false.
bool KeyOf(const ValueType& value, KeyType* key) const
{
- if (HasItems())
+ if (Base::HasItems())
{
- const Bucket* data = _allocation.Get();
- for (int32 i = 0; i < _size; ++i)
+ const Bucket* data = Base::_allocation.Get();
+ for (int32 i = 0; i < Base::_size; ++i)
{
if (data[i].IsOccupied() && data[i].Value == value)
{
@@ -821,10 +695,15 @@ public:
void Clone(const Dictionary& other)
{
// TODO: if both key and value are POD types then use raw memory copy for buckets
- Clear();
- SetCapacity(other.Capacity(), false);
- for (Iterator i = other.Begin(); i != other.End(); ++i)
- Add(i);
+ Base::Clear();
+ Base::SetCapacity(other.Capacity(), false);
+ for (ConstIterator i = other.Begin(); i != other.End(); ++i)
+ {
+ const Bucket& bucket = *i;
+ Add(bucket.Key, bucket.Value);
+ }
+ ASSERT(Base::Count() == other.Count());
+ ASSERT(Base::Capacity() == other.Capacity());
}
///
@@ -834,7 +713,7 @@ public:
template
void GetKeys(Array& result) const
{
- for (Iterator i = Begin(); i.IsNotEnd(); ++i)
+ for (ConstIterator i = Begin(); i.IsNotEnd(); ++i)
result.Add(i->Key);
}
@@ -845,21 +724,33 @@ public:
template
void GetValues(Array& result) const
{
- for (Iterator i = Begin(); i.IsNotEnd(); ++i)
+ for (ConstIterator i = Begin(); i.IsNotEnd(); ++i)
result.Add(i->Value);
}
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()
@@ -871,139 +762,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:
- ///
- /// The result container of the dictionary item lookup searching.
- ///
- struct FindPositionResult
- {
- int32 ObjectIndex;
- int32 FreeSlotIndex;
- };
-
- ///
- /// 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 key later
- ///
- /// The ky to find.
- /// The pair of values: where the object is and where it would go if you wanted to insert it.
- template
- void FindPosition(const KeyComparableType& key, FindPositionResult& result) const
- {
- ASSERT(_size);
- const int32 tableSizeMinusOne = _size - 1;
- int32 bucketIndex = GetHash(key) & tableSizeMinusOne;
- int32 insertPos = -1;
- int32 checksCount = 0;
- const Bucket* data = _allocation.Get();
- result.FreeSlotIndex = -1;
- while (checksCount < _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 key
- else if (bucket.Key == key)
- {
- // Found key
- result.ObjectIndex = bucketIndex;
- return;
- }
- ++checksCount;
- bucketIndex = (bucketIndex + DICTIONARY_PROB_FUNC(_size, checksCount)) & tableSizeMinusOne;
- }
- result.ObjectIndex = -1;
- result.FreeSlotIndex = insertPos;
- }
-
- template
- Bucket* OnAdd(const KeyComparableType& 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);
-
- // Ensure key is unknown
- ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
-
- // 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(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.Key, 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);
}
};
diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h
index 711e65dca..51886d843 100644
--- a/Source/Engine/Core/Collections/HashSet.h
+++ b/Source/Engine/Core/Collections/HashSet.h
@@ -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"
+
+///
+/// Describes single portion of space for the item in a hash set.
+///
+template
+struct HashSetBucket
+{
+ friend Memory;
+ friend HashSetBase;
+ friend HashSet;
+
+ /// The item.
+ 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;
+ }
+
+ /// Copying a bucket is useless, because a key must be unique in the dictionary.
+ HashSetBucket(const HashSetBucket&) = delete;
+
+ /// Copying a bucket is useless, because a key must be unique in the dictionary.
+ 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
+ FORCE_INLINE void Occupy(const ItemType& item)
+ {
+ Memory::ConstructItems(&Item, &item, 1);
+ _state = HashSetBucketState::Occupied;
+ }
+
+ template
+ 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;
+ }
+};
///
/// Template for unordered set of values (without duplicates with O(1) lookup access).
@@ -15,124 +125,12 @@
/// The type of elements in the set.
/// The type of memory allocator.
template
-API_CLASS(InBuild) class HashSet
+API_CLASS(InBuild) class HashSet : public HashSetBase>
{
friend HashSet;
public:
- ///
- /// Describes single portion of space for the item in a hash map.
- ///
- struct Bucket
- {
- friend HashSet;
- friend Memory;
-
- /// The item.
- 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;
- }
-
- /// Copying a bucket is useless, because a key must be unique in the dictionary.
- Bucket(const Bucket&) = delete;
-
- /// Copying a bucket is useless, because a key must be unique in the dictionary.
- 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
- FORCE_INLINE void Occupy(const ItemType& item)
- {
- Memory::ConstructItems(&Item, &item, 1);
- _state = BucketState::Occupied;
- }
-
- template
- 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;
-
-private:
- int32 _elementsCount = 0;
- int32 _deletedCount = 0;
- int32 _size = 0;
- AllocationData _allocation;
+ typedef HashSetBucket Bucket;
+ typedef HashSetBase Base;
public:
///
@@ -148,7 +146,7 @@ public:
/// The number of elements that can be added without a need to allocate more memory.
FORCE_INLINE explicit HashSet(const int32 capacity)
{
- SetCapacity(capacity);
+ Base::SetCapacity(capacity);
}
///
@@ -157,13 +155,7 @@ public:
/// The other collection to move.
HashSet(HashSet&& other) noexcept
{
- _elementsCount = other._elementsCount;
- _deletedCount = other._deletedCount;
- _size = other._size;
- other._elementsCount = 0;
- other._deletedCount = 0;
- other._size = 0;
- AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size);
+ Base::MoveToEmpty(MoveTemp(other));
}
///
@@ -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(_allocation, other._allocation, _size, _size);
+ Base::Clear();
+ Base::_allocation.Free();
+ Base::MoveToEmpty(MoveTemp(other));
}
return *this;
}
@@ -214,113 +200,129 @@ public:
///
~HashSet()
{
- Clear();
}
public:
///
- /// Gets the amount of the elements in the collection.
+ /// The read-only hash set collection iterator.
///
- FORCE_INLINE int32 Count() const
- {
- return _elementsCount;
- }
-
- ///
- /// Gets the amount of the elements that can be contained by the collection.
- ///
- FORCE_INLINE int32 Capacity() const
- {
- return _size;
- }
-
- ///
- /// Returns true if collection is empty.
- ///
- FORCE_INLINE bool IsEmpty() const
- {
- return _elementsCount == 0;
- }
-
- ///
- /// Returns true if collection has one or more elements.
- ///
- FORCE_INLINE bool HasItems() const
- {
- return _elementsCount != 0;
- }
-
-public:
- ///
- /// The hash set collection iterator.
- ///
- 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(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;
+ }
+ };
+
+ ///
+ /// The hash set collection iterator.
+ ///
+ 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:
- ///
- /// Removes all elements from the collection.
- ///
- void Clear()
- {
- if (_elementsCount + _deletedCount != 0)
- {
- Bucket* data = _allocation.Get();
- for (int32 i = 0; i < _size; i++)
- data[i].Free();
- _elementsCount = _deletedCount = 0;
- }
- }
-
///
/// 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();
- }
-
- ///
- /// Changes capacity of the collection
- ///
- /// New capacity
- /// Enable/disable preserving collection contents during resizing
- void SetCapacity(int32 capacity, const bool preserveContents = true)
- {
- if (capacity == _size)
- return;
- ASSERT(capacity >= 0);
- AllocationData oldAllocation;
- AllocationUtils::MoveToEmpty(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();
- }
- }
-
- ///
- /// Ensures that collection has given capacity.
- ///
- /// The minimum required capacity.
- /// True if preserve collection data when changing its size, otherwise collection after resize will be empty.
- 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);
- }
-
- ///
- /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
- ///
- /// The other collection.
- 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
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:
/// True if element has been added to the collection, otherwise false if the element is already present.
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
///
/// Iterator with item to add
- 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
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:
/// Item to find
/// Iterator for the found element or End if cannot find it
template
- 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();
}
+ ///
+ /// Find element with given item in the collection
+ ///
+ /// Item to find
+ /// Iterator for the found element or End if cannot find it
+ template
+ ConstIterator Find(const ItemType& item) const
+ {
+ typename Base::FindPositionResult pos;
+ Base::FindPosition(item, pos);
+ return pos.ObjectIndex != -1 ? ConstIterator(this, pos.ObjectIndex) : End();
+ }
+
///
/// Determines whether a collection contains the specified element.
///
@@ -618,39 +511,60 @@ public:
template
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:
///
- /// Clones other collection into this
+ /// Clones other collection into this.
///
- /// Other collection to clone
+ /// The other collection to clone.
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());
+ }
+
+ ///
+ /// Gets the items collection to the output array (will contain unique items).
+ ///
+ /// The result.
+ template
+ void GetItems(Array& 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:
- ///
- /// The result container of the set item lookup searching.
- ///
- struct FindPositionResult
- {
- int32 ObjectIndex;
- int32 FreeSlotIndex;
- };
-
- ///
- /// 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
- ///
- /// The item to find
- /// Pair of values: where the object is and where it would go if you wanted to insert it
- template
- 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
- 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(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);
}
};
diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h
new file mode 100644
index 000000000..0ad2d7035
--- /dev/null
+++ b/Source/Engine/Core/Collections/HashSetBase.h
@@ -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"
+
+///
+/// Tells if the object is occupied, and if not, if the bucket is a subject of compaction.
+///
+enum class HashSetBucketState : byte
+{
+ Empty = 0,
+ Deleted = 1,
+ Occupied = 2,
+};
+
+///
+/// Base class for unordered set of values (without duplicates with O(1) lookup access).
+///
+/// The type of bucket structure that stores element data and state.
+/// The type of memory allocator.
+template
+class HashSetBase
+{
+ friend HashSetBase;
+
+public:
+ // Type of allocation data used to store hash set buckets.
+ using AllocationData = typename AllocationType::template Data;
+
+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(_allocation, other._allocation, _size, _size);
+ }
+
+ ~HashSetBase()
+ {
+ Clear();
+ }
+
+public:
+ ///
+ /// Gets the amount of the elements in the collection.
+ ///
+ FORCE_INLINE int32 Count() const
+ {
+ return _elementsCount;
+ }
+
+ ///
+ /// Gets the amount of the elements that can be contained by the collection.
+ ///
+ FORCE_INLINE int32 Capacity() const
+ {
+ return _size;
+ }
+
+ ///
+ /// Returns true if collection is empty.
+ ///
+ FORCE_INLINE bool IsEmpty() const
+ {
+ return _elementsCount == 0;
+ }
+
+ ///
+ /// Returns true if collection has one or more elements.
+ ///
+ FORCE_INLINE bool HasItems() const
+ {
+ return _elementsCount != 0;
+ }
+
+public:
+ ///
+ /// Removes all elements from the collection.
+ ///
+ void Clear()
+ {
+ if (_elementsCount + _deletedCount != 0)
+ {
+ BucketType* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i].Free();
+ _elementsCount = _deletedCount = 0;
+ }
+ }
+
+ ///
+ /// Changes the capacity of the collection.
+ ///
+ /// The new capacity.
+ /// True if preserve collection data when changing its size, otherwise collection after resize will be empty.
+ void SetCapacity(int32 capacity, const bool preserveContents = true)
+ {
+ if (capacity == _size)
+ return;
+ ASSERT(capacity >= 0);
+ AllocationData oldAllocation;
+ AllocationUtils::MoveToEmpty(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();
+ }
+ }
+
+ ///
+ /// Ensures that collection has given capacity.
+ ///
+ /// The minimum required capacity.
+ /// True if preserve collection data when changing its size, otherwise collection after resize will be empty.
+ 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);
+ }
+
+ ///
+ /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
+ ///
+ /// The other collection.
+ 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:
+ ///
+ /// The collection iterator base implementation.
+ ///
+ 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:
+ ///
+ /// The result container of the set item lookup searching.
+ ///
+ struct FindPositionResult
+ {
+ int32 ObjectIndex;
+ int32 FreeSlotIndex;
+ };
+
+ ///
+ /// 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.
+ ///
+ /// The key to find
+ /// A pair of values: where the object is and where it would go if you wanted to insert it
+ template
+ 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
+ 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(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;
+ }
+};
diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h
index 64db93be9..3273abeeb 100644
--- a/Source/Engine/Core/Types/BaseTypes.h
+++ b/Source/Engine/Core/Types/BaseTypes.h
@@ -83,6 +83,8 @@ template
class Pair;
template
class Dictionary;
+template
+class HashSet;
template
class Function;
template