// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Platform/Platform.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" /// /// Template for dynamic array with variable capacity that support concurrent elements appending (atomic add). /// /// The type of elements in the array. /// The type of memory allocator. template class ConcurrentArray { friend ConcurrentArray; public: typedef T ItemType; typedef typename AllocationType::template Data AllocationData; private: volatile int64 _count; volatile int64 _capacity; AllocationData _allocation; CriticalSection _locker; public: /// /// Initializes a new instance of the class. /// FORCE_INLINE ConcurrentArray() : _count(0) , _capacity(0) { } /// /// Initializes a new instance of the class. /// /// The initial capacity. ConcurrentArray(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. ConcurrentArray(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. ConcurrentArray(const ConcurrentArray& other) { _count = _capacity = other._count; if (_capacity > 0) { _allocation.Allocate(_capacity); Memory::ConstructItems(_allocation.Get(), other.Get(), (int32)other._count); } } /// /// Initializes a new instance of the class. /// /// The other collection to move. ConcurrentArray(ConcurrentArray&& 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. ConcurrentArray& operator=(const ConcurrentArray& other) noexcept { if (this != &other) { Memory::DestructItems(_allocation.Get(), (int32)_count); if (_capacity < other.Count()) { _allocation.Free(); _capacity = other.Count(); _allocation.Allocate(_capacity); } _count = other.Count(); Memory::ConstructItems(_allocation.Get(), other.Get(), (int32)_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. ConcurrentArray& operator=(ConcurrentArray&& other) noexcept { if (this != &other) { Memory::DestructItems(_allocation.Get(), (int32)_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. /// ~ConcurrentArray() { Memory::DestructItems(_allocation.Get(), (int32)_count); } public: /// /// Gets the amount of the items in the collection. /// FORCE_INLINE int32 Count() const { return (int32)Platform::AtomicRead((volatile int64*)&_count); } /// /// Gets the amount of the items that can be contained by collection without resizing. /// FORCE_INLINE int32 Capacity() const { return (int32)Platform::AtomicRead((volatile int64*)&_capacity); } /// /// Gets the critical section locking the collection during resizing. /// FORCE_INLINE const CriticalSection& Locker() const { return _locker; } /// /// 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 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]; } 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. /// void Clear() { _locker.Lock(); Memory::DestructItems(_allocation.Get(), (int32)_count); _count = 0; _locker.Unlock(); } /// /// 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) { _locker.Lock(); if (capacity == _capacity) return; ASSERT(capacity >= 0); const int32 count = preserveContents ? ((int32)_count < capacity ? (int32)_count : capacity) : 0; _allocation.Relocate(capacity, (int32)_count, count); _capacity = capacity; _count = count; _locker.Unlock(); } /// /// 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) { _locker.Lock(); if (_count > size) { Memory::DestructItems(_allocation.Get() + size, (int32)_count - size); } else { EnsureCapacity(size, preserveContents); Memory::ConstructItems(_allocation.Get() + _count, size - (int32)_count); } _count = size; _locker.Unlock(); } /// /// 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) { _locker.Lock(); if (_capacity < minCapacity) { const int32 capacity = _allocation.CalculateCapacityGrow((int32)_capacity, minCapacity); SetCapacity(capacity, preserveContents); } _locker.Unlock(); } /// /// Adds the specified item to the collection. /// /// The item to add. /// Index of the added element. int32 Add(const T& item) { const int32 count = (int32)Platform::AtomicRead(&_count); const int32 capacity = (int32)Platform::AtomicRead(&_capacity); const int32 minCapacity = count + PLATFORM_THREADS_LIMIT; // Ensure there is a room for all threads (eg. all possible threads add item at once) if (minCapacity >= capacity) EnsureCapacity(minCapacity); const int32 index = (int32)Platform::InterlockedIncrement(&_count) - 1; Memory::ConstructItems(_allocation.Get() + index, &item, 1); return index; } };