// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Platform/Platform.h"
#include "Engine/Core/Memory/Memory.h"
#include "Engine/Core/Memory/Allocation.h"
///
/// Template for dynamic array with variable capacity.
///
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;
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 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(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(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(Get(), other.Get(), other._count);
Memory::ConstructItems(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._count;
_count = other._count;
if (_capacity > 0)
{
_allocation.Allocate(_capacity);
Memory::ConstructItems(Get(), other._data, other._count);
}
}
///
/// Initializes a new instance of the class.
///
/// The other collection to move.
FORCE_INLINE Array(Array&& other) noexcept
{
_count = other._count;
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
}
///
/// 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(Get(), _count);
if (_capacity < other._count)
{
_allocation.Free();
_capacity = other._count;
_allocation.Allocate(_capacity);
}
_count = other._count;
Memory::ConstructItems(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(Get(), _count);
_allocation.Free();
_count = other._count;
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
}
return *this;
}
///
/// Finalizes an instance of the class.
///
~Array()
{
Memory::DestructItems(Get(), _count);
}
public:
///
/// Gets the amount of the items in the collection.
///
/// The amount of items.
FORCE_INLINE int32 Count() const
{
return _count;
}
///
/// Gets the amount of the items that can be contained by collection without resizing.
///
/// The collection capacity.
FORCE_INLINE int32 Capacity() const
{
return _capacity;
}
///
/// Returns true if collection isn't empty.
///
/// True if collection isn't empty, otherwise false.
FORCE_INLINE bool HasItems() const
{
return _count != 0;
}
///
/// Returns true if collection is empty.
///
/// True if collection is empty, otherwise false.
FORCE_INLINE bool IsEmpty() const
{
return _count == 0;
}
///
/// Gets the pointer to the first item in the collection (linear allocation).
///
/// The data pointer.
FORCE_INLINE T* Get()
{
return _allocation.Get();
}
///
/// Gets the pointer to the first item in the collection (linear allocation).
///
/// The data pointer.
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 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 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 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 Get()[index];
}
///
/// Gets the last item.
///
/// The last item.
FORCE_INLINE T& Last()
{
ASSERT(_count > 0);
return Get()[_count - 1];
}
///
/// Gets the last item.
///
/// The last item.
FORCE_INLINE const T& Last() const
{
ASSERT(_count > 0);
return Get()[_count - 1];
}
///
/// Gets the first item.
///
/// The first item.
FORCE_INLINE T& First()
{
ASSERT(_count > 0);
return Get()[0];
}
///
/// Gets the first item.
///
/// The first item.
FORCE_INLINE const T& First() const
{
ASSERT(_count > 0);
return Get()[0];
}
public:
FORCE_INLINE T* begin()
{
return &Get()[0];
}
FORCE_INLINE T* end()
{
return &Get()[_count];
}
FORCE_INLINE const T* begin() const
{
return &Get()[0];
}
FORCE_INLINE const T* end() const
{
return &Get()[_count];
}
public:
///
/// Clear the collection without changing its capacity.
///
FORCE_INLINE void Clear()
{
Memory::DestructItems(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(Get() + size, _count - size);
}
else
{
EnsureCapacity(size, preserveContents);
Memory::ConstructItems(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 = 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(Get(), _count);
_count = count;
Memory::ConstructItems(Get(), data, _count);
}
///
/// Adds the specified item to the collection.
///
/// The item to add.
void Add(const T& item)
{
EnsureCapacity(_count + 1);
Memory::ConstructItems(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(Get() + _count, items, count);
_count += count;
}
///
/// Adds the other collection to the collection.
///
/// The other collection to add.
FORCE_INLINE void Add(const Array& other)
{
Add(other.Get(), other.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(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(Get() + _count, 1);
_count++;
return 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(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 = 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 = 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 = 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 (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 = 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 (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 = 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(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)
{
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
_allocation.Swap(other._allocation);
}
///
/// Reverses the order of the added items in the collection.
///
void Reverse()
{
T* data = Get();
const int32 count = static_cast(_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).
///
/// The item.
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).
///
/// The item.
FORCE_INLINE T& Peek()
{
return Last();
}
///
/// Peeks items which is at the top of the stack (stack grows at the end of the collection).
///
/// The item.
FORCE_INLINE const T& Peek() const
{
return Last();
}
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 = 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 = 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:
///
/// Compares collection contents with the other collection.
///
/// The other collection to compare with.
/// True if collection have the same set of items, otherwise false.
bool operator==(const Array& other) const
{
if (_count == other._count)
{
const T* data = Get();
const T* otherData = other.Get();
for (int32 i = 0; i < _count; i++)
{
if (!(data[i] == otherData[i]))
return false;
}
}
return true;
}
///
/// Compares collection contents with the other collection.
///
/// The other collection to compare with.
/// True if collection don't have the same set of items, otherwise false.
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)
{
}
public:
FORCE_INLINE Array* GetArray() const
{
return _array;
}
FORCE_INLINE int32 GetIndex() const
{
return _index;
}
public:
///
/// Checks if iterator is in the end of the collection.
///
FORCE_INLINE bool IsEnd() const
{
return _index == _array->Count();
}
///
/// Checks if iterator is not in the end of the collection.
///
FORCE_INLINE bool IsNotEnd() const
{
return _index != _array->Count();
}
public:
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++()
{
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.
///
/// Iterator for beginning of the collection.
FORCE_INLINE Iterator Begin() const
{
return Iterator(this, 0);
}
///
/// Gets iterator for ending of the collection.
///
/// 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];
}