// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #pragma once #include #include "Engine/Platform/Platform.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" /// /// Template for dynamic array with variable capacity. /// /// The type of elements in the array. /// The type of memory allocator. template API_CLASS(InBuild) class Array { friend Array; public: typedef T ItemType; typedef typename AllocationType::template Data AllocationData; private: int32 _count; int32 _capacity; AllocationData _allocation; FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromCount, 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. /// FORCE_INLINE Array() : _count(0) , _capacity(0) { } /// /// Initializes a new instance of the class. /// /// The initial capacity. Array(int32 capacity) : _count(0) , _capacity(capacity) { if (capacity > 0) _allocation.Allocate(capacity); } /// /// Initializes a new instance of the class. /// /// The initial values defined in the array. Array(std::initializer_list initList) { _count = _capacity = (int32)initList.size(); if (_count > 0) { _allocation.Allocate(_count); Memory::ConstructItems(_allocation.Get(), initList.begin(), _count); } } /// /// Initializes a new instance of the class. /// /// The initial data. /// The amount of items. Array(const T* data, int32 length) { ASSERT(length >= 0); _count = _capacity = length; if (length > 0) { _allocation.Allocate(length); Memory::ConstructItems(_allocation.Get(), data, length); } } /// /// Initializes a new instance of the class. /// /// The other collection to copy. Array(const Array& other) { _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. /// /// The other collection to copy. /// The additionally amount of items to add to the add. Array(const Array& other, int32 extraSize) { 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. /// /// The other collection to copy. template explicit Array(const Array& other) noexcept { _capacity = other.Capacity(); _count = other.Count(); if (_capacity > 0) { _allocation.Allocate(_capacity); Memory::ConstructItems(_allocation.Get(), other.Get(), _count); } } /// /// Initializes a new instance of the class. /// /// The other collection to move. Array(Array&& other) noexcept { _count = other._count; _capacity = other._capacity; other._count = 0; other._capacity = 0; MoveToEmpty(_allocation, other._allocation, _count, _capacity); } /// /// The assignment operator that deletes the current collection of items and the copies items from the initializer list. /// /// The other collection to copy. /// The reference to this. Array& operator=(std::initializer_list initList) noexcept { Clear(); if (initList.size() > 0) { EnsureCapacity((int32)initList.size()); _count = (int32)initList.size(); Memory::ConstructItems(_allocation.Get(), initList.begin(), _count); } return *this; } /// /// The assignment operator that deletes the current collection of items and the copies items from the other array. /// /// The other collection to copy. /// The reference to this. Array& operator=(const Array& other) noexcept { if (this != &other) { Memory::DestructItems(_allocation.Get(), _count); if (_capacity < other.Count()) { _allocation.Free(); _capacity = other.Count(); _allocation.Allocate(_capacity); } _count = other.Count(); Memory::ConstructItems(_allocation.Get(), other.Get(), _count); } return *this; } /// /// The move assignment operator that deletes the current collection of items and the moves items from the other array. /// /// The other collection to move. /// The reference to this. Array& operator=(Array&& other) noexcept { if (this != &other) { Memory::DestructItems(_allocation.Get(), _count); _allocation.Free(); _count = other._count; _capacity = other._capacity; other._count = 0; other._capacity = 0; MoveToEmpty(_allocation, other._allocation, _count, _capacity); } return *this; } /// /// Finalizes an instance of the class. /// ~Array() { Memory::DestructItems(_allocation.Get(), _count); } public: /// /// Gets the amount of the items in the collection. /// FORCE_INLINE int32 Count() const { return _count; } /// /// Gets the amount of the items that can be contained by collection without resizing. /// FORCE_INLINE int32 Capacity() const { return _capacity; } /// /// Returns true if collection isn't empty. /// FORCE_INLINE bool HasItems() const { return _count != 0; } /// /// Returns true if collection is empty. /// FORCE_INLINE bool IsEmpty() const { return _count == 0; } /// /// Gets the pointer to the first item in the collection (linear allocation). /// FORCE_INLINE T* Get() { return _allocation.Get(); } /// /// Gets the pointer to the first item in the collection (linear allocation). /// FORCE_INLINE const T* Get() const { return _allocation.Get(); } /// /// Gets item at the given index. /// /// The reference to the item. FORCE_INLINE T& At(int32 index) { ASSERT(index >= 0 && index < _count); return _allocation.Get()[index]; } /// /// Gets item at the given index. /// /// The reference to the item. FORCE_INLINE const T& At(int32 index) const { ASSERT(index >= 0 && index < _count); return _allocation.Get()[index]; } /// /// Gets or sets the item at the given index. /// /// The reference to the item. FORCE_INLINE T& operator[](int32 index) { ASSERT(index >= 0 && index < _count); return _allocation.Get()[index]; } /// /// Gets the item at the given index. /// /// The reference to the item. FORCE_INLINE const T& operator[](int32 index) const { ASSERT(index >= 0 && index < _count); return _allocation.Get()[index]; } /// /// Gets the last item. /// FORCE_INLINE T& Last() { ASSERT(_count > 0); return _allocation.Get()[_count - 1]; } /// /// Gets the last item. /// FORCE_INLINE const T& Last() const { ASSERT(_count > 0); return _allocation.Get()[_count - 1]; } /// /// Gets the first item. /// FORCE_INLINE T& First() { ASSERT(_count > 0); return _allocation.Get()[0]; } /// /// Gets the first item. /// FORCE_INLINE const T& First() const { ASSERT(_count > 0); return _allocation.Get()[0]; } public: FORCE_INLINE T* begin() { return &_allocation.Get()[0]; } FORCE_INLINE T* end() { return &_allocation.Get()[_count]; } FORCE_INLINE const T* begin() const { return &_allocation.Get()[0]; } FORCE_INLINE const T* end() const { return &_allocation.Get()[_count]; } public: /// /// Clear the collection without changing its capacity. /// FORCE_INLINE void Clear() { Memory::DestructItems(_allocation.Get(), _count); _count = 0; } /// /// Clears the collection without changing its capacity. Deletes all not null items. /// Note: collection must contain pointers to the objects that have public destructor and be allocated using New method. /// #if defined(_MSC_VER) template::Value>::Type> #endif void ClearDelete() { T* data = Get(); for (int32 i = 0; i < _count; i++) { if (data[i]) Delete(data[i]); } Clear(); } /// /// 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(const int32 capacity, bool preserveContents = true) { if (capacity == _capacity) return; ASSERT(capacity >= 0); const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0; _allocation.Relocate(capacity, _count, count); _capacity = capacity; _count = count; } /// /// Resizes the collection to the specified size. If the size is equal or less to the current capacity no additional memory reallocation in performed. /// /// The new collection size. /// True if preserve collection data when changing its size, otherwise collection after resize might not contain the previous data. void Resize(int32 size, bool preserveContents = true) { if (_count > size) { Memory::DestructItems(_allocation.Get() + size, _count - size); } else { EnsureCapacity(size, preserveContents); Memory::ConstructItems(_allocation.Get() + _count, size - _count); } _count = size; } /// /// Ensures the collection has given capacity (or more). /// /// The minimum capacity. /// 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) { const int32 capacity = _allocation.CalculateCapacityGrow(_capacity, minCapacity); SetCapacity(capacity, preserveContents); } } /// /// Sets all items to the given value /// /// The value to assign to all the collection items. void SetAll(const T& value) { T* data = _allocation.Get(); for (int32 i = 0; i < _count; i++) data[i] = value; } /// /// Sets the collection data. /// /// The data. /// The amount of items. void Set(const T* data, int32 count) { EnsureCapacity(count, false); Memory::DestructItems(_allocation.Get(), _count); _count = count; Memory::ConstructItems(_allocation.Get(), data, _count); } /// /// Adds the specified item to the collection. /// /// The item to add. void Add(const T& item) { EnsureCapacity(_count + 1); Memory::ConstructItems(_allocation.Get() + _count, &item, 1); _count++; } /// /// Adds the specified item to the collection. /// /// The item to add. void Add(T&& item) { EnsureCapacity(_count + 1); Memory::MoveItems(_allocation.Get() + _count, &item, 1); _count++; } /// /// Adds the specified item to the collection. /// /// The items to add. /// The items count. void Add(const T* items, int32 count) { EnsureCapacity(_count + count); Memory::ConstructItems(_allocation.Get() + _count, items, count); _count += count; } /// /// Adds the other collection to the collection. /// /// The other collection to add. template FORCE_INLINE void Add(const Array& other) { Add(other.Get(), other.Count()); } /// /// Adds the unique item to the collection if it doesn't exist. /// /// The item to add. FORCE_INLINE void AddUnique(const T& item) { if (!Contains(item)) Add(item); } /// /// Adds the given amount of items to the collection. /// /// The items count. FORCE_INLINE void AddDefault(int32 count = 1) { EnsureCapacity(_count + count); Memory::ConstructItems(_allocation.Get() + _count, count); _count += count; } /// /// Adds the given amount of uninitialized items to the collection without calling the constructor. /// /// The items count. FORCE_INLINE void AddUninitialized(int32 count = 1) { EnsureCapacity(_count + count); _count += count; } /// /// Adds the one item to the collection and returns the reference to it. /// /// The reference to the added item. FORCE_INLINE T& AddOne() { EnsureCapacity(_count + 1); Memory::ConstructItems(_allocation.Get() + _count, 1); _count++; return _allocation.Get()[_count - 1]; } /// /// Adds the new items to the end of the collection, possibly reallocating the whole collection to fit. The new items will be zeroed. /// /// /// Warning! AddZeroed() will create items without calling the constructor and this is not appropriate for item types that require a constructor to function properly. /// /// The number of new items to add. void AddZeroed(int32 count = 1) { EnsureCapacity(_count + count); Platform::MemoryClear(_allocation.Get() + _count, count * sizeof(T)); _count += count; } /// /// Insert the given item at specified index with keeping items order. /// /// The zero-based index at which item should be inserted. /// The item to insert. void Insert(int32 index, const T& item) { ASSERT(index >= 0 && index <= _count); EnsureCapacity(_count + 1); T* data = _allocation.Get(); Memory::ConstructItems(data + _count, 1); for (int32 i = _count - 1; i >= index; i--) data[i + 1] = data[i]; _count++; data[index] = item; } /// /// Insert the given item at specified index with keeping items order. /// /// The zero-based index at which item should be inserted. void Insert(int32 index) { ASSERT(index >= 0 && index <= _count); EnsureCapacity(_count + 1); T* data = _allocation.Get(); Memory::ConstructItems(data + _count, 1); for (int32 i = _count - 1; i >= index; i--) data[i + 1] = data[i]; _count++; } /// /// Determines whether the collection contains the specified item. /// /// The item to check. /// True if item has been found in the collection, otherwise false. template bool Contains(const TComparableType& item) const { const T* data = _allocation.Get(); for (int32 i = 0; i < _count; i++) { if (data[i] == item) return true; } return false; } /// /// Removes the first occurrence of a specific object from the collection and keeps order of the items in the collection. /// /// The item to remove. /// True if cannot remove item from the collection because cannot find it, otherwise false. bool RemoveKeepOrder(const T& item) { const int32 index = Find(item); if (index == -1) return true; RemoveAtKeepOrder(index); return false; } /// /// Removes all occurrence of a specific object from the collection and keeps order of the items in the collection. /// /// The item to remove. void RemoveAllKeepOrder(const T& item) { for (int32 i = Count() - 1; i >= 0; i--) { if (_allocation.Get()[i] == item) { RemoveAtKeepOrder(i); if (IsEmpty()) break; } } } /// /// Removes the item at the specified index of the collection and keeps order of the items in the collection. /// /// The zero-based index of the item to remove. void RemoveAtKeepOrder(const int32 index) { ASSERT(index < _count && index >= 0); _count--; T* data = _allocation.Get(); if (index < _count) { T* dst = data + index; T* src = data + (index + 1); const int32 count = _count - index; for (int32 i = 0; i < count; i++) dst[i] = MoveTemp(src[i]); } Memory::DestructItems(data + _count, 1); } /// /// Removes the first occurrence of a specific object from the collection. /// /// The item to remove. /// True if cannot remove item from the collection because cannot find it, otherwise false. bool Remove(const T& item) { const int32 index = Find(item); if (index == -1) return true; RemoveAt(index); return false; } /// /// Removes all occurrence of a specific object from the collection. /// /// The item to remove. void RemoveAll(const T& item) { for (int32 i = Count() - 1; i >= 0; i--) { if (_allocation.Get()[i] == item) { RemoveAt(i); if (IsEmpty()) break; } } } /// /// Removes the item at the specified index of the collection. /// /// The zero-based index of the item to remove. void RemoveAt(const int32 index) { ASSERT(index < _count && index >= 0); _count--; T* data = _allocation.Get(); if (_count) data[index] = data[_count]; Memory::DestructItems(data + _count, 1); } /// /// Removes the last items from the collection. /// void RemoveLast() { ASSERT(_count > 0); _count--; Memory::DestructItems(_allocation.Get() + _count, 1); } /// /// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange. /// /// The other collection. void Swap(Array& other) { if IF_CONSTEXPR (AllocationType::HasSwap) { _allocation.Swap(other._allocation); ::Swap(_count, other._count); ::Swap(_capacity, other._capacity); } else { Array tmp = MoveTemp(other); other = *this; *this = MoveTemp(tmp); } } /// /// Reverses the order of the added items in the collection. /// void Reverse() { T* data = _allocation.Get(); const int32 count = _count / 2; for (int32 i = 0; i < count; i++) ::Swap(data[i], data[_count - i - 1]); } public: /// /// Performs push on stack operation (stack grows at the end of the collection). /// /// The item to append. FORCE_INLINE void Push(const T& item) { Add(item); } /// /// Performs pop from stack operation (stack grows at the end of the collection). /// T Pop() { T item(Last()); RemoveLast(); return item; } /// /// Peeks items which is at the top of the stack (stack grows at the end of the collection). /// FORCE_INLINE T& Peek() { ASSERT(_count > 0); return _allocation.Get()[_count - 1]; } /// /// Peeks items which is at the top of the stack (stack grows at the end of the collection). /// FORCE_INLINE const T& Peek() const { ASSERT(_count > 0); return _allocation.Get()[_count - 1]; } public: /// /// Performs enqueue to queue operation (queue head is in the beginning of queue). /// /// The item to append. void Enqueue(const T& item) { Add(item); } /// /// Performs dequeue from queue operation (queue head is in the beginning of queue). /// /// The item. T Dequeue() { ASSERT(HasItems()); T item(First()); RemoveAtKeepOrder(0); return item; } public: /// /// Searches for the given item within the entire collection. /// /// The item to look for. /// The found item index, -1 if missing. /// True if found, otherwise false. template FORCE_INLINE bool Find(const ComparableType& item, int32& index) const { index = Find(item); return index != -1; } /// /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire collection. /// /// The item to find. /// The zero-based index of the first occurrence of itm within the entire collection, if found; otherwise, -1. template int32 Find(const ComparableType& item) const { if (_count > 0) { const T* RESTRICT start = _allocation.Get(); for (const T * RESTRICT data = start, *RESTRICT dataEnd = data + _count; data != dataEnd; ++data) { if (*data == item) return static_cast(data - start); } } return -1; } /// /// Searches for the given item within the entire collection starting from the end. /// /// The item to look for. /// The found item index, -1 if missing. /// True if found, otherwise false. template FORCE_INLINE bool FindLast(const ComparableType& item, int& index) const { index = FindLast(item); return index != -1; } /// /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire collection starting from the end. /// /// The item to find. /// The zero-based index of the first occurrence of itm within the entire collection, if found; otherwise, -1. template int32 FindLast(const ComparableType& item) const { if (_count > 0) { const T* RESTRICT end = _allocation.Get() + _count; for (const T * RESTRICT data = end, *RESTRICT dataStart = data - _count; data != dataStart;) { --data; if (*data == item) return static_cast(data - dataStart); } } return -1; } public: template bool operator==(const Array& other) const { if (_count == other.Count()) { const T* data = _allocation.Get(); const T* otherData = other.Get(); for (int32 i = 0; i < _count; i++) { if (!(data[i] == otherData[i])) return false; } return true; } return false; } template bool operator!=(const Array& other) const { return !operator==(other); } public: /// /// The collection iterator. /// struct Iterator { friend Array; private: Array* _array; int32 _index; Iterator(Array* array, const int32 index) : _array(array) , _index(index) { } Iterator(Array const* array, const int32 index) : _array(const_cast(array)) , _index(index) { } public: Iterator() : _array(nullptr) , _index(-1) { } Iterator(const Iterator& i) : _array(i._array) , _index(i._index) { } Iterator(Iterator&& i) : _array(i._array) , _index(i._index) { } public: FORCE_INLINE Array* GetArray() const { return _array; } FORCE_INLINE int32 GetIndex() const { return _index; } FORCE_INLINE bool IsEnd() const { return _index == _array->_count; } FORCE_INLINE bool IsNotEnd() const { return _index != _array->_count; } FORCE_INLINE T& operator*() const { return _array->Get()[_index]; } FORCE_INLINE T* operator->() const { return &_array->Get()[_index]; } FORCE_INLINE bool operator==(const Iterator& v) const { return _array == v._array && _index == v._index; } FORCE_INLINE bool operator!=(const Iterator& v) const { return _array != v._array || _index != v._index; } Iterator& operator=(const Iterator& v) { _array = v._array; _index = v._index; return *this; } Iterator& operator++() { if (_index != _array->_count) _index++; return *this; } Iterator operator++(int) { Iterator temp = *this; if (_index != _array->_count) _index++; return temp; } Iterator& operator--() { if (_index > 0) _index--; return *this; } Iterator operator--(int) { Iterator temp = *this; if (_index > 0) _index--; return temp; } }; public: /// /// Gets iterator for beginning of the collection. /// FORCE_INLINE Iterator Begin() const { return Iterator(this, 0); } /// /// Gets iterator for ending of the collection. /// FORCE_INLINE Iterator End() const { return Iterator(this, _count); } }; template void* operator new(size_t size, Array& array) { ASSERT(size == sizeof(T)); const int32 index = array.Count(); array.AddUninitialized(1); return &array[index]; } template void* operator new(size_t size, Array& array, int32 index) { ASSERT(size == sizeof(T)); array.Insert(index); return &array[index]; }