From f5280eab740b1a0a58fb276c457bac8d5548c36c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 23 Jan 2025 14:44:11 +0100 Subject: [PATCH] Refactor and improve collections code #3043 --- Source/Editor/Editor.cpp | 2 +- Source/Editor/Scripting/ScriptsBuilder.cpp | 2 +- Source/Engine/Core/Collections/Array.h | 82 ++++--------- Source/Engine/Core/Collections/BitArray.h | 8 +- Source/Engine/Core/Collections/Dictionary.h | 125 +++++++++++--------- Source/Engine/Core/Collections/HashSet.h | 113 ++++++++++-------- Source/Engine/Core/Memory/Allocation.h | 111 +++++++++-------- Source/Engine/Core/Memory/AllocationUtils.h | 24 ++++ Source/Engine/Core/Memory/Memory.h | 35 +++--- Source/Engine/Core/Templates.h | 6 + Source/Engine/Debug/DebugCommands.cpp | 2 +- Source/Engine/Renderer/RenderListBuffer.h | 29 +---- Source/Engine/Scripting/Runtime/DotNet.cpp | 6 +- 13 files changed, 277 insertions(+), 268 deletions(-) create mode 100644 Source/Engine/Core/Memory/AllocationUtils.h diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index f915bfde6..a62799097 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -608,7 +608,7 @@ int32 Editor::LoadProduct() // Validate project min supported version (older engine may try to load newer project) // Special check if project specifies only build number, then major/minor fields are set to 0 const auto engineVersion = FLAXENGINE_VERSION; - for (auto e : projects) + for (const auto& e : projects) { const auto project = e.Item; if (project->MinEngineVersion > engineVersion || diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index ac0f5438d..404add5f3 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -582,7 +582,7 @@ bool ScriptsBuilderService::Init() auto project = Editor::Project; HashSet projects; project->GetAllProjects(projects); - for (auto e : projects) + for (const auto& e : projects) { ProjectInfo* project = e.Item; if (project->Name == TEXT("Flax")) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 4b9511671..b8e86bb19 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -6,6 +6,7 @@ #include "Engine/Platform/Platform.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Memory/AllocationUtils.h" /// /// Template for dynamic array with variable capacity. @@ -25,22 +26,9 @@ private: int32 _capacity; AllocationData _allocation; - FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, const int32 fromCount, const int32 fromCapacity) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - to.Swap(from); - else - { - to.Allocate(fromCapacity); - Memory::MoveItems(to.Get(), from.Get(), fromCount); - Memory::DestructItems(from.Get(), fromCount); - from.Free(); - } - } - public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// FORCE_INLINE Array() : _count(0) @@ -49,10 +37,10 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. - explicit Array(const int32 capacity) + /// The number of elements that can be added without a need to allocate more memory. + FORCE_INLINE explicit Array(const int32 capacity) : _count(0) , _capacity(capacity) { @@ -61,21 +49,7 @@ public: } /// - /// Initializes a new instance of the class. - /// - /// The initial values defined in the array. - Array(std::initializer_list initList) - { - _count = _capacity = static_cast(initList.size()); - if (_count > 0) - { - _allocation.Allocate(_count); - Memory::ConstructItems(_allocation.Get(), initList.begin(), _count); - } - } - - /// - /// Initializes a new instance of the class. + /// Initializes by copying elements. /// /// The initial data. /// The amount of items. @@ -91,38 +65,25 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by copying listed elements. /// - /// The other collection to copy. - Array(const Array& other) + /// The initial values defined in the array. + FORCE_INLINE Array(std::initializer_list initList) + : Array(initList.begin(), (int32)initList.size()) { - _count = _capacity = other._count; - if (_capacity > 0) - { - _allocation.Allocate(_capacity); - Memory::ConstructItems(_allocation.Get(), other.Get(), other._count); - } } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// The other collection to copy. - /// The additionally amount of items to add to the add. - Array(const Array& other, int32 extraSize) + FORCE_INLINE Array(const Array& other) + : Array(other.Get(), other.Count()) { - ASSERT(extraSize >= 0); - _count = _capacity = other._count + extraSize; - if (_capacity > 0) - { - _allocation.Allocate(_capacity); - Memory::ConstructItems(_allocation.Get(), other.Get(), other._count); - Memory::ConstructItems(_allocation.Get() + other._count, extraSize); - } } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// The other collection to copy. template @@ -138,7 +99,7 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by moving the content of the other collection. /// /// The other collection to move. Array(Array&& other) noexcept @@ -147,7 +108,7 @@ public: _capacity = other._capacity; other._count = 0; other._capacity = 0; - MoveToEmpty(_allocation, other._allocation, _count, _capacity); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _count, _capacity); } /// @@ -204,7 +165,7 @@ public: _capacity = other._capacity; other._count = 0; other._capacity = 0; - MoveToEmpty(_allocation, other._allocation, _count, _capacity); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _count, _capacity); } return *this; } @@ -377,10 +338,13 @@ public: /// /// Clear the collection without changing its capacity. /// - FORCE_INLINE void Clear() + void Clear() { - Memory::DestructItems(_allocation.Get(), _count); - _count = 0; + if (_count != 0) + { + Memory::DestructItems(_allocation.Get(), _count); + _count = 0; + } } /// diff --git a/Source/Engine/Core/Collections/BitArray.h b/Source/Engine/Core/Collections/BitArray.h index 0a748f4bd..799feb64d 100644 --- a/Source/Engine/Core/Collections/BitArray.h +++ b/Source/Engine/Core/Collections/BitArray.h @@ -34,7 +34,7 @@ private: public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// FORCE_INLINE BitArray() : _count(0) @@ -43,9 +43,9 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. + /// The number of elements that can be added without a need to allocate more memory. explicit BitArray(const int32 capacity) : _count(0) , _capacity(capacity) @@ -55,7 +55,7 @@ public: } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// The other collection to copy. BitArray(const BitArray& other) noexcept diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 01b33cf2d..0b717dc52 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -4,6 +4,7 @@ #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" @@ -25,6 +26,7 @@ public: struct Bucket { friend Dictionary; + friend Memory; /// The key. KeyType Key; @@ -34,6 +36,57 @@ public: 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) @@ -104,46 +157,19 @@ private: int32 _size = 0; AllocationData _allocation; - FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, const int32 fromSize) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - to.Swap(from); - else - { - to.Allocate(fromSize); - Bucket* toData = to.Get(); - Bucket* fromData = from.Get(); - for (int32 i = 0; i < fromSize; i++) - { - Bucket& fromBucket = fromData[i]; - if (fromBucket.IsOccupied()) - { - Bucket& toBucket = toData[i]; - Memory::MoveItems(&toBucket.Key, &fromBucket.Key, 1); - Memory::MoveItems(&toBucket.Value, &fromBucket.Value, 1); - toBucket._state = BucketState::Occupied; - Memory::DestructItem(&fromBucket.Key); - Memory::DestructItem(&fromBucket.Value); - fromBucket._state = BucketState::Empty; - } - } - from.Free(); - } - } - public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// Dictionary() { } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. - explicit Dictionary(const int32 capacity) + /// The number of elements that can be added without a need to allocate more memory. + FORCE_INLINE explicit Dictionary(const int32 capacity) { SetCapacity(capacity); } @@ -160,11 +186,11 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// Other collection to copy Dictionary(const Dictionary& other) @@ -201,7 +227,7 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } return *this; } @@ -536,25 +562,16 @@ public: /// Enables preserving collection contents during resizing. void SetCapacity(int32 capacity, const bool preserveContents = true) { - if (capacity == Capacity()) + if (capacity == _size) return; ASSERT(capacity >= 0); AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); const int32 oldSize = _size; const int32 oldElementsCount = _elementsCount; _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) - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - capacity++; - } + capacity = AllocationUtils::AlignToPowerOf2(capacity); if (capacity) { _allocation.Allocate(capacity); @@ -574,11 +591,9 @@ public: { FindPosition(oldBucket.Key, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1); - Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1); - bucket->_state = BucketState::Occupied; - ++_elementsCount; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); + _elementsCount++; } } } @@ -968,7 +983,7 @@ private: { // Rebuild entire table completely AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); _allocation.Allocate(_size); Bucket* data = _allocation.Get(); for (int32 i = 0; i < _size; i++) @@ -982,10 +997,8 @@ private: { FindPosition(oldBucket.Key, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1); - Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1); - bucket->_state = BucketState::Occupied; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); } } for (int32 i = 0; i < _size; i++) diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index 838db7a35..711e65dca 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -4,6 +4,7 @@ #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" @@ -24,6 +25,7 @@ public: struct Bucket { friend HashSet; + friend Memory; /// The item. T Item; @@ -31,6 +33,51 @@ public: 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) @@ -87,44 +134,19 @@ private: int32 _size = 0; AllocationData _allocation; - FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, const int32 fromSize) - { - if IF_CONSTEXPR (AllocationType::HasSwap) - to.Swap(from); - else - { - to.Allocate(fromSize); - Bucket* toData = to.Get(); - Bucket* fromData = from.Get(); - for (int32 i = 0; i < fromSize; ++i) - { - Bucket& fromBucket = fromData[i]; - if (fromBucket.IsOccupied()) - { - Bucket& toBucket = toData[i]; - Memory::MoveItems(&toBucket.Item, &fromBucket.Item, 1); - toBucket._state = BucketState::Occupied; - Memory::DestructItem(&fromBucket.Item); - fromBucket._state = BucketState::Empty; - } - } - from.Free(); - } - } - public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// HashSet() { } /// - /// Initializes a new instance of the class. + /// Initializes by reserving space. /// - /// The initial capacity. - explicit HashSet(const int32 capacity) + /// The number of elements that can be added without a need to allocate more memory. + FORCE_INLINE explicit HashSet(const int32 capacity) { SetCapacity(capacity); } @@ -141,11 +163,11 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } /// - /// Initializes a new instance of the class. + /// Initializes by copying the elements from the other collection. /// /// Other collection to copy HashSet(const HashSet& other) @@ -182,7 +204,7 @@ public: other._elementsCount = 0; other._deletedCount = 0; other._size = 0; - MoveToEmpty(_allocation, other._allocation, _size); + AllocationUtils::MoveToEmpty(_allocation, other._allocation, _size, _size); } return *this; } @@ -413,25 +435,16 @@ public: /// Enable/disable preserving collection contents during resizing void SetCapacity(int32 capacity, const bool preserveContents = true) { - if (capacity == Capacity()) + if (capacity == _size) return; ASSERT(capacity >= 0); AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); const int32 oldSize = _size; const int32 oldElementsCount = _elementsCount; _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) - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - capacity++; - } + capacity = AllocationUtils::AlignToPowerOf2(capacity); if (capacity) { _allocation.Allocate(capacity); @@ -451,9 +464,8 @@ public: { FindPosition(oldBucket.Item, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1); - bucket->_state = BucketState::Occupied; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); _elementsCount++; } } @@ -764,7 +776,7 @@ private: { // Rebuild entire table completely AllocationData oldAllocation; - MoveToEmpty(oldAllocation, _allocation, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); _allocation.Allocate(_size); Bucket* data = _allocation.Get(); for (int32 i = 0; i < _size; ++i) @@ -778,9 +790,8 @@ private: { FindPosition(oldBucket.Item, pos); ASSERT(pos.FreeSlotIndex != -1); - Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex]; - Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1); - bucket->_state = BucketState::Occupied; + Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex]; + bucket = MoveTemp(oldBucket); } } for (int32 i = 0; i < _size; ++i) diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h index 5fce2ad73..7ce02ca90 100644 --- a/Source/Engine/Core/Memory/Allocation.h +++ b/Source/Engine/Core/Memory/Allocation.h @@ -5,6 +5,39 @@ #include "Memory.h" #include "Engine/Core/Core.h" +namespace AllocationUtils +{ + // Rounds up the input value to the next power of 2 to be used as bigger memory allocation block. Handles overflow. + inline int32 RoundUpToPowerOf2(int32 capacity) + { + // Reference: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + capacity--; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + uint64 capacity64 = (uint64)(capacity + 1) * 2; + if (capacity64 > MAX_int32) + capacity64 = MAX_int32; + return (int32)capacity64; + } + + // Aligns the input value to the next power of 2 to be used as bigger memory allocation block. + inline int32 AlignToPowerOf2(int32 capacity) + { + // Reference: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + capacity--; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + capacity++; + return capacity; + } +} + /// /// The memory allocation policy that uses inlined memory of the fixed size (no resize support, does not use heap allocations at all). /// @@ -47,16 +80,12 @@ public: FORCE_INLINE void Allocate(const int32 capacity) { -#if ENABLE_ASSERTION_LOW_LAYERS - ASSERT(capacity <= Capacity); -#endif + ASSERT_LOW_LAYER(capacity <= Capacity); } FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount) { -#if ENABLE_ASSERTION_LOW_LAYERS - ASSERT(capacity <= Capacity); -#endif + ASSERT_LOW_LAYER(capacity <= Capacity); } FORCE_INLINE void Free() @@ -71,7 +100,7 @@ public: }; /// -/// The memory allocation policy that uses default heap allocator. +/// The memory allocation policy that uses default heap allocation. /// class HeapAllocation { @@ -109,33 +138,17 @@ public: if (capacity < minCapacity) capacity = minCapacity; if (capacity < 8) - { capacity = 8; - } else - { - // Round up to the next power of 2 and multiply by 2 (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - uint64 capacity64 = (uint64)(capacity + 1) * 2; - if (capacity64 > MAX_int32) - capacity64 = MAX_int32; - capacity = (int32)capacity64; - } + capacity = AllocationUtils::RoundUpToPowerOf2(capacity); return capacity; } FORCE_INLINE void Allocate(const int32 capacity) { -#if ENABLE_ASSERTION_LOW_LAYERS - ASSERT(!_data); -#endif + ASSERT_LOW_LAYER(!_data); _data = static_cast(Allocator::Allocate(capacity * sizeof(T))); -#if !BUILD_RELEASE +#if ENABLE_ASSERTION if (!_data) OUT_OF_MEMORY; #endif @@ -144,7 +157,7 @@ public: FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount) { T* newData = capacity != 0 ? static_cast(Allocator::Allocate(capacity * sizeof(T))) : nullptr; -#if !BUILD_RELEASE +#if ENABLE_ASSERTION if (!newData && capacity != 0) OUT_OF_MEMORY; #endif @@ -176,7 +189,7 @@ public: /// /// The memory allocation policy that uses inlined memory of the fixed size and supports using additional allocation to increase its capacity (eg. via heap allocation). /// -template +template class InlinedAllocation { public: @@ -186,11 +199,11 @@ public: class alignas(sizeof(void*)) Data { private: - typedef typename OtherAllocator::template Data OtherData; + typedef typename FallbackAllocation::template Data FallbackData; - bool _useOther = false; + bool _useFallback = false; byte _data[Capacity * sizeof(T)]; - OtherData _other; + FallbackData _fallback; public: FORCE_INLINE Data() @@ -203,25 +216,25 @@ public: FORCE_INLINE T* Get() { - return _useOther ? _other.Get() : reinterpret_cast(_data); + return _useFallback ? _fallback.Get() : reinterpret_cast(_data); } FORCE_INLINE const T* Get() const { - return _useOther ? _other.Get() : reinterpret_cast(_data); + return _useFallback ? _fallback.Get() : reinterpret_cast(_data); } FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity) const { - return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity); + return minCapacity <= Capacity ? Capacity : _fallback.CalculateCapacityGrow(capacity, minCapacity); } FORCE_INLINE void Allocate(int32 capacity) { if (capacity > Capacity) { - _useOther = true; - _other.Allocate(capacity); + _useFallback = true; + _fallback.Allocate(capacity); } } @@ -232,32 +245,32 @@ public: // Check if the new allocation will fit into inlined storage if (capacity <= Capacity) { - if (_useOther) + if (_useFallback) { // Move the items from other allocation to the inlined storage - Memory::MoveItems(data, _other.Get(), newCount); + Memory::MoveItems(data, _fallback.Get(), newCount); // Free the other allocation - Memory::DestructItems(_other.Get(), oldCount); - _other.Free(); - _useOther = false; + Memory::DestructItems(_fallback.Get(), oldCount); + _fallback.Free(); + _useFallback = false; } } else { - if (_useOther) + if (_useFallback) { // Resize other allocation - _other.Relocate(capacity, oldCount, newCount); + _fallback.Relocate(capacity, oldCount, newCount); } else { // Allocate other allocation - _other.Allocate(capacity); - _useOther = true; + _fallback.Allocate(capacity); + _useFallback = true; // Move the items from the inlined storage to the other allocation - Memory::MoveItems(_other.Get(), data, newCount); + Memory::MoveItems(_fallback.Get(), data, newCount); Memory::DestructItems(data, oldCount); } } @@ -265,10 +278,10 @@ public: FORCE_INLINE void Free() { - if (_useOther) + if (_useFallback) { - _useOther = false; - _other.Free(); + _useFallback = false; + _fallback.Free(); } } diff --git a/Source/Engine/Core/Memory/AllocationUtils.h b/Source/Engine/Core/Memory/AllocationUtils.h new file mode 100644 index 000000000..236d7bae1 --- /dev/null +++ b/Source/Engine/Core/Memory/AllocationUtils.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Allocation.h" +#include "Engine/Core/Templates.h" + +namespace AllocationUtils +{ + // Moves the data from the source allocation to the destination allocation. + template + inline void MoveToEmpty(typename AllocationType::template Data& to, typename AllocationType::template Data& from, const int32 fromCount, const int32 fromCapacity) + { + if IF_CONSTEXPR (AllocationType::HasSwap) + to.Swap(from); + else + { + to.Allocate(fromCapacity); + Memory::MoveItems(to.Get(), from.Get(), fromCount); + Memory::DestructItems(from.Get(), fromCount); + from.Free(); + } + } +} diff --git a/Source/Engine/Core/Memory/Memory.h b/Source/Engine/Core/Memory/Memory.h index 484731c70..c8ac9edb4 100644 --- a/Source/Engine/Core/Memory/Memory.h +++ b/Source/Engine/Core/Memory/Memory.h @@ -91,15 +91,16 @@ namespace AllocatorExt } } -namespace Memory +class Memory { +public: /// /// Constructs the item in the memory. /// /// The optimized version is noop. /// The address of the memory location to construct. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItem(T* dst) { new(dst) T(); } @@ -110,7 +111,7 @@ namespace Memory /// The optimized version is noop. /// The address of the memory location to construct. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItem(T* dst) { // More undefined behavior! No more clean memory! More performance! Yay! //Platform::MemoryClear(dst, sizeof(T)); @@ -123,7 +124,7 @@ namespace Memory /// The address of the first memory location to construct. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) { while (count--) { @@ -139,7 +140,7 @@ namespace Memory /// The address of the first memory location to construct. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, int32 count) { // More undefined behavior! No more clean memory! More performance! Yay! //Platform::MemoryClear(dst, count * sizeof(T)); @@ -153,7 +154,7 @@ namespace Memory /// The address of the first memory location to pass to the constructor. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) { while (count--) { @@ -171,7 +172,7 @@ namespace Memory /// The address of the first memory location to pass to the constructor. /// The number of element to construct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type ConstructItems(T* dst, const U* src, int32 count) { Platform::MemoryCopy(dst, src, count * sizeof(U)); } @@ -182,7 +183,7 @@ namespace Memory /// The optimized version is noop. /// The address of the memory location to destruct. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItem(T* dst) { dst->~T(); } @@ -193,7 +194,7 @@ namespace Memory /// The optimized version is noop. /// The address of the memory location to destruct. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItem(T* dst) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItem(T* dst) { } @@ -204,7 +205,7 @@ namespace Memory /// The address of the first memory location to destruct. /// The number of element to destruct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) { while (count--) { @@ -220,7 +221,7 @@ namespace Memory /// The address of the first memory location to destruct. /// The number of element to destruct. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type DestructItems(T* dst, int32 count) { } @@ -232,7 +233,7 @@ namespace Memory /// The address of the first memory location to assign from. /// The number of element to assign. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) { while (count--) { @@ -250,7 +251,7 @@ namespace Memory /// The address of the first memory location to assign from. /// The number of element to assign. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type CopyItems(T* dst, const T* src, int32 count) { Platform::MemoryCopy(dst, src, count * sizeof(T)); } @@ -263,11 +264,11 @@ namespace Memory /// The address of the first memory location to pass to the move constructor. /// The number of element to move. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type MoveItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type MoveItems(T* dst, U* src, int32 count) { while (count--) { - new(dst) T((T&&)*src); + new(dst) T((U&&)*src); ++(T*&)dst; ++src; } @@ -281,11 +282,11 @@ namespace Memory /// The address of the first memory location to pass to the move constructor. /// The number of element to move. Can be equal 0. template - FORCE_INLINE typename TEnableIf::Value>::Type MoveItems(T* dst, const U* src, int32 count) + FORCE_INLINE static typename TEnableIf::Value>::Type MoveItems(T* dst, U* src, int32 count) { Platform::MemoryCopy(dst, src, count * sizeof(U)); } -} +}; /// /// Creates a new object of the given type. diff --git a/Source/Engine/Core/Templates.h b/Source/Engine/Core/Templates.h index 462fa5024..a87166e1d 100644 --- a/Source/Engine/Core/Templates.h +++ b/Source/Engine/Core/Templates.h @@ -234,6 +234,12 @@ struct TIsCopyConstructible enum { Value = __is_constructible(T, typename TAddLValueReference::Type>::Type) }; }; +template +struct TIsMoveConstructible +{ + enum { Value = __is_constructible(T, typename TAddRValueReference::Type) }; +}; + //////////////////////////////////////////////////////////////////////////////////// // Checks if a type has a trivial copy constructor. diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index 1dabec081..a871dc813 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -198,7 +198,7 @@ namespace const MClass* attribute = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEngine.DebugCommand"); ASSERT_LOW_LAYER(attribute); const auto& classes = managedModule->Assembly->GetClasses(); - for (auto e : classes) + for (const auto& e : classes) { MClass* mclass = e.Value; if (mclass->IsGeneric() || diff --git a/Source/Engine/Renderer/RenderListBuffer.h b/Source/Engine/Renderer/RenderListBuffer.h index e6a22949a..955da31dd 100644 --- a/Source/Engine/Renderer/RenderListBuffer.h +++ b/Source/Engine/Renderer/RenderListBuffer.h @@ -30,7 +30,7 @@ private: public: /// - /// Initializes a new instance of the class. + /// Initializes an empty without reserving any space. /// FORCE_INLINE RenderListBuffer() : _count(0) @@ -39,19 +39,7 @@ public: } /// - /// Initializes a new instance of the class. - /// - /// The initial capacity. - explicit RenderListBuffer(const int32 capacity) - : _count(0) - , _capacity(capacity) - { - if (capacity > 0) - _allocation.Allocate(capacity); - } - - /// - /// Initializes a new instance of the class. + /// Initializes by copying elements. /// /// The initial data. /// The amount of items. @@ -369,17 +357,6 @@ private: { // Ensure there is a slack for others threads to reduce resize counts in highly multi-threaded environment constexpr int32 slack = PLATFORM_THREADS_LIMIT * 8; - int32 capacity = count + slack; - { - // Round up to the next power of 2 and multiply by 2 - capacity--; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - capacity |= capacity >> 8; - capacity |= capacity >> 16; - capacity = (capacity + 1) * 2; - } - return capacity; + return AllocationUtils::RoundUpToPowerOf2(count + slack); } }; diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index ddafab614..30b26760f 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -335,17 +335,17 @@ void MCore::UnloadEngine() void MCore::ReloadScriptingAssemblyLoadContext() { // Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108) - for (auto e : CachedClassHandles) + for (const auto& e : CachedClassHandles) { e.Value->_hasCachedAttributes = false; e.Value->_attributes.Clear(); } - for (auto e : CachedAssemblyHandles) + for (const auto& e : CachedAssemblyHandles) { MAssembly* a = e.Value; if (!a->IsLoaded() || !a->_hasCachedClasses) continue; - for (auto q : a->GetClasses()) + for (const auto& q : a->GetClasses()) { MClass* c = q.Value; c->_hasCachedAttributes = false;