diff --git a/Source/Engine/Core/Collections/Config.h b/Source/Engine/Core/Collections/Config.h
index 792ae57c8..ce7656dcd 100644
--- a/Source/Engine/Core/Collections/Config.h
+++ b/Source/Engine/Core/Collections/Config.h
@@ -2,13 +2,26 @@
#pragma once
-///
-/// Default capacity for the dictionaries (amount of space for the elements)
-///
-#define DICTIONARY_DEFAULT_CAPACITY 256
+#include "Engine/Platform/Defines.h"
///
-/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size)
+/// Default capacity for the dictionaries (amount of space for the elements).
+///
+#ifndef DICTIONARY_DEFAULT_CAPACITY
+#if PLATFORM_DESKTOP
+#define DICTIONARY_DEFAULT_CAPACITY 256
+#else
+#define DICTIONARY_DEFAULT_CAPACITY 64
+#endif
+#endif
+
+///
+/// Default slack space divider for the dictionaries.
+///
+#define DICTIONARY_DEFAULT_SLACK_SCALE 3
+
+///
+/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size).
///
#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks)
//#define DICTIONARY_PROB_FUNC(size, numChecks) (1)
diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h
index 575863dc9..729bc80aa 100644
--- a/Source/Engine/Core/Collections/Dictionary.h
+++ b/Source/Engine/Core/Collections/Dictionary.h
@@ -40,7 +40,7 @@ public:
private:
State _state;
- void Free()
+ FORCE_INLINE void Free()
{
if (_state == Occupied)
{
@@ -50,7 +50,7 @@ public:
_state = Empty;
}
- void Delete()
+ FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Key);
@@ -58,7 +58,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key)
+ FORCE_INLINE void Occupy(const KeyComparableType& key)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItem(&Value);
@@ -66,7 +66,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key, const ValueType& value)
+ FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItems(&Value, &value, 1);
@@ -74,7 +74,7 @@ public:
}
template
- void Occupy(const KeyComparableType& key, ValueType&& value)
+ FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::MoveItems(&Value, &value, 1);
@@ -132,9 +132,6 @@ public:
///
/// The other collection to move.
Dictionary(Dictionary&& other) noexcept
- : _elementsCount(other._elementsCount)
- , _deletedCount(other._deletedCount)
- , _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -375,8 +372,12 @@ 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 + _deletedCount + 1);
+ EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
@@ -388,9 +389,9 @@ public:
// Insert
ASSERT(pos.FreeSlotIndex != -1);
+ _elementsCount++;
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
bucket.Occupy(key);
- _elementsCount++;
return bucket.Value;
}
@@ -493,7 +494,7 @@ public:
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
- Delete(i->Value);
+ ::Delete(i->Value);
}
Clear();
}
@@ -535,11 +536,19 @@ public:
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
{
- // TODO; move keys and values on realloc
+ FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
- if (oldData[i].IsOccupied())
- Add(oldData[i].Key, MoveTemp(oldData[i].Value));
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Key, pos);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
+ Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
+ bucket->_state = Bucket::Occupied;
+ _elementsCount++;
+ }
}
}
if (oldElementsCount != 0)
@@ -558,9 +567,9 @@ public:
{
if (_size >= minCapacity)
return;
- if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
- minCapacity = DICTIONARY_DEFAULT_CAPACITY;
- const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ if (capacity < DICTIONARY_DEFAULT_CAPACITY)
+ capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
@@ -584,24 +593,10 @@ public:
/// The value.
/// Weak reference to the stored bucket.
template
- Bucket* Add(const KeyComparableType& key, const ValueType& value)
+ FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
-
- // 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);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Bucket* bucket = OnAdd(key);
bucket->Occupy(key, value);
- _elementsCount++;
-
return bucket;
}
@@ -612,24 +607,10 @@ public:
/// The value.
/// Weak reference to the stored bucket.
template
- Bucket* Add(const KeyComparableType& key, ValueType&& value)
+ FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
-
- // 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);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Bucket* bucket = OnAdd(key);
bucket->Occupy(key, MoveTemp(value));
- _elementsCount++;
-
return bucket;
}
@@ -851,7 +832,7 @@ public:
return Iterator(this, _size);
}
-protected:
+private:
///
/// The result container of the dictionary item lookup searching.
///
@@ -911,4 +892,65 @@ protected:
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);
+
+ // 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 = Bucket::Empty;
+ }
+ else
+ {
+ // Rebuild entire table completely
+ AllocationData oldAllocation;
+ oldAllocation.Swap(_allocation);
+ _allocation.Allocate(_size);
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::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);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
+ Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
+ bucket->_state = Bucket::Occupied;
+ }
+ }
+ for (int32 i = 0; i < _size; i++)
+ oldData[i].Free();
+ }
+ _deletedCount = 0;
+ }
};
diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h
index fba8ca823..166ca5cc3 100644
--- a/Source/Engine/Core/Collections/HashSet.h
+++ b/Source/Engine/Core/Collections/HashSet.h
@@ -37,26 +37,33 @@ public:
private:
State _state;
- void Free()
+ FORCE_INLINE void Free()
{
if (_state == Occupied)
Memory::DestructItem(&Item);
_state = Empty;
}
- void Delete()
+ FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Item);
}
template
- void Occupy(const ItemType& item)
+ FORCE_INLINE void Occupy(const ItemType& item)
{
Memory::ConstructItems(&Item, &item, 1);
_state = Occupied;
}
+ template
+ FORCE_INLINE void Occupy(ItemType& item)
+ {
+ Memory::MoveItems(&Item, &item, 1);
+ _state = Occupied;
+ }
+
FORCE_INLINE bool IsEmpty() const
{
return _state == Empty;
@@ -82,6 +89,7 @@ public:
private:
int32 _elementsCount = 0;
+ int32 _deletedCount = 0;
int32 _size = 0;
AllocationData _allocation;
@@ -107,12 +115,12 @@ public:
///
/// The other collection to move.
HashSet(HashSet&& other) noexcept
- : _elementsCount(other._elementsCount)
- , _size(other._size)
{
_elementsCount = other._elementsCount;
+ _deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
+ other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
@@ -150,8 +158,10 @@ public:
Clear();
_allocation.Free();
_elementsCount = other._elementsCount;
+ _deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
+ other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
@@ -163,7 +173,7 @@ public:
///
~HashSet()
{
- SetCapacity(0, false);
+ Clear();
}
public:
@@ -210,6 +220,7 @@ public:
HashSet* _collection;
int32 _index;
+ public:
Iterator(HashSet* collection, const int32 index)
: _collection(collection)
, _index(index)
@@ -222,7 +233,12 @@ public:
{
}
- public:
+ Iterator()
+ : _collection(nullptr)
+ , _index(-1)
+ {
+ }
+
Iterator(const Iterator& i)
: _collection(i._collection)
, _index(i._index)
@@ -236,6 +252,11 @@ public:
}
public:
+ FORCE_INLINE int32 Index() const
+ {
+ return _index;
+ }
+
FORCE_INLINE bool IsEnd() const
{
return _index == _collection->_size;
@@ -331,12 +352,12 @@ public:
///
void Clear()
{
- if (_elementsCount != 0)
+ if (_elementsCount + _deletedCount != 0)
{
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i].Free();
- _elementsCount = 0;
+ _elementsCount = _deletedCount = 0;
}
}
@@ -371,7 +392,7 @@ public:
oldAllocation.Swap(_allocation);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
- _elementsCount = 0;
+ _deletedCount = _elementsCount = 0;
if (capacity != 0 && (capacity & (capacity - 1)) != 0)
{
// Align capacity value to the next power of two (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2)
@@ -394,11 +415,18 @@ public:
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
{
- // TODO; move keys and values on realloc
+ FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
- if (oldData[i].IsOccupied())
- Add(oldData[i].Item);
+ Bucket& oldBucket = oldData[i];
+ if (oldBucket.IsOccupied())
+ {
+ FindPosition(oldBucket.Item, pos);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
+ bucket->_state = Bucket::Occupied;
+ _elementsCount++;
+ }
}
}
if (oldElementsCount != 0)
@@ -415,14 +443,26 @@ public:
/// True if preserve collection data when changing its size, otherwise collection after resize will be empty.
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
- if (Capacity() >= minCapacity)
+ if (_size >= minCapacity)
return;
- if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
- minCapacity = DICTIONARY_DEFAULT_CAPACITY;
- const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
+ 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)
+ {
+ ::Swap(_elementsCount, other._elementsCount);
+ ::Swap(_deletedCount, other._deletedCount);
+ ::Swap(_size, other._size);
+ _allocation.Swap(other._allocation);
+ }
+
public:
///
/// Add element to the collection.
@@ -432,24 +472,23 @@ public:
template
bool Add(const ItemType& item)
{
- // Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + 1);
+ Bucket* bucket = OnAdd(item);
+ if (bucket)
+ bucket->Occupy(item);
+ return bucket != nullptr;
+ }
- // Find location of the item or place to insert it
- FindPositionResult pos;
- FindPosition(item, pos);
-
- // Check if object has been already added
- if (pos.ObjectIndex != -1)
- return false;
-
- // Insert
- ASSERT(pos.FreeSlotIndex != -1);
- Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
- bucket->Occupy(item);
- _elementsCount++;
-
- return true;
+ ///
+ /// Add element to the collection.
+ ///
+ /// The element to add to the set.
+ /// 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);
+ if (bucket)
+ bucket->Occupy(MoveTemp(item));
+ return bucket != nullptr;
}
///
@@ -479,6 +518,7 @@ public:
{
_allocation.Get()[pos.ObjectIndex].Delete();
_elementsCount--;
+ _deletedCount++;
return true;
}
return false;
@@ -497,6 +537,7 @@ public:
ASSERT(_allocation.Get()[i._index].IsOccupied());
_allocation.Get()[i._index].Delete();
_elementsCount--;
+ _deletedCount++;
return true;
}
return false;
@@ -585,7 +626,7 @@ public:
return Iterator(this, _size);
}
-protected:
+private:
///
/// The result container of the set item lookup searching.
///
@@ -646,4 +687,64 @@ protected:
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);
+
+ // 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 = Bucket::Empty;
+ }
+ else
+ {
+ // Rebuild entire table completely
+ AllocationData oldAllocation;
+ oldAllocation.Swap(_allocation);
+ _allocation.Allocate(_size);
+ Bucket* data = _allocation.Get();
+ for (int32 i = 0; i < _size; i++)
+ data[i]._state = Bucket::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);
+ Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
+ Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
+ bucket->_state = Bucket::Occupied;
+ }
+ }
+ for (int32 i = 0; i < _size; i++)
+ oldData[i].Free();
+ }
+ _deletedCount = 0;
+ }
};