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