// Copyright (c) Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Platform/Platform.h"
#include "Engine/Core/Memory/Allocation.h"
#include "Engine/Core/Math/Math.h"
///
/// Template for dynamic array with variable capacity that stores the bit values.
///
template
API_CLASS(InBuild) class BitArray
{
friend BitArray;
public:
using ItemType = uint64;
using AllocationData = typename AllocationType::template Data;
private:
int32 _count;
int32 _capacity;
AllocationData _allocation;
FORCE_INLINE static int32 ToItemCount(const int32 size)
{
return Math::DivideAndRoundUp(size, sizeof(ItemType));
}
FORCE_INLINE static int32 ToItemCapacity(const int32 size)
{
return Math::Max(Math::DivideAndRoundUp(size, sizeof(ItemType)), 1);
}
public:
///
/// Initializes an empty without reserving any space.
///
FORCE_INLINE BitArray()
: _count(0)
, _capacity(0)
{
}
///
/// Initializes by reserving space.
///
/// The number of elements that can be added without a need to allocate more memory.
explicit BitArray(const int32 capacity)
: _count(0)
, _capacity(capacity)
{
if (capacity > 0)
_allocation.Allocate(ToItemCapacity(capacity));
}
///
/// Initializes by copying the elements from the other collection.
///
/// The other collection to copy.
BitArray(const BitArray& other) noexcept
{
_count = _capacity = other.Count();
if (_capacity > 0)
{
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
}
///
/// Initializes a new instance of the class.
///
/// The other collection to copy.
template
explicit BitArray(const BitArray& other) noexcept
{
_count = _capacity = other.Count();
if (_capacity > 0)
{
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
}
///
/// Initializes a new instance of the class.
///
/// The other collection to move.
FORCE_INLINE BitArray(BitArray&& other) noexcept
: _count(0)
, _capacity(0)
{
Swap(other);
}
///
/// 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.
BitArray& operator=(const BitArray& other) noexcept
{
if (this != &other)
{
if (_capacity < other._count)
{
_allocation.Free();
_capacity = other._count;
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
_count = other._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.
BitArray& operator=(BitArray&& other) noexcept
{
if (this != &other)
{
_allocation.Free();
_count = 0;
_capacity = 0;
Swap(other);
}
return *this;
}
///
/// Finalizes an instance of the class.
///
~BitArray()
{
}
public:
///
/// Gets the pointer to the bits storage data (linear allocation).
///
FORCE_INLINE ItemType* Get()
{
return _allocation.Get();
}
///
/// Gets the pointer to the bits storage data (linear allocation).
///
FORCE_INLINE const ItemType* Get() const
{
return _allocation.Get();
}
///
/// 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 item at the given index.
///
/// The index of the item.
/// The value of the item.
FORCE_INLINE bool operator[](const int32 index) const
{
return Get(index);
}
///
/// Gets the item at the given index.
///
/// The index of the item.
/// The value of the item.
bool Get(const int32 index) const
{
ASSERT(index >= 0 && index < _count);
const ItemType offset = index / 64;
const ItemType bitMask = 1ull << (index & 63ull);
const ItemType item = ((ItemType*)_allocation.Get())[offset];
return (item & bitMask) != 0;
}
///
/// Sets the item at the given index.
///
/// The index of the item.
/// The value to set.
void Set(const int32 index, const bool value)
{
ASSERT(index >= 0 && index < _count);
const ItemType offset = index / 64;
const ItemType bitMask = 1ull << (index & 63ull);
ItemType& item = ((ItemType*)_allocation.Get())[offset];
if (value)
item |= bitMask; // Set the bit
else
item &= ~bitMask; // Unset the bit
}
public:
///
/// Clear the collection without changing its capacity.
///
FORCE_INLINE void Clear()
{
_count = 0;
}
///
/// 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, const bool preserveContents = true)
{
if (capacity == _capacity)
return;
ASSERT(capacity >= 0);
const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0;
_allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(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(const int32 size, const bool preserveContents = true)
{
if (_count <= size)
EnsureCapacity(size, preserveContents);
_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(const int32 minCapacity, const bool preserveContents = true)
{
if (_capacity < minCapacity)
{
const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity);
SetCapacity(capacity, preserveContents);
}
}
///
/// Sets all items to the given value
///
/// The value to assign to all the collection items.
void SetAll(const bool value)
{
if (_count != 0)
Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0);
}
///
/// Adds the specified item to the collection.
///
/// The item to add.
void Add(const bool item)
{
EnsureCapacity(_count + 1);
++_count;
Set(_count - 1, item);
}
///
/// Adds the specified item to the collection.
///
/// The items to add.
/// The items count.
void Add(const bool* items, const int32 count)
{
EnsureCapacity(_count + count);
for (int32 i = 0; i < count; ++i)
Add(items[i]);
}
///
/// Adds the other collection to the collection.
///
/// The other collection to add.
void Add(const BitArray& other)
{
EnsureCapacity(_count, other.Count());
for (int32 i = 0; i < other.Count(); ++i)
Add(other[i]);
}
///
/// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
///
/// The other collection.
void Swap(BitArray& other)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
_allocation.Swap(other._allocation);
else
{
// Move to temp
const int32 oldItemsCapacity = ToItemCount(_capacity);
const int32 otherItemsCapacity = ToItemCount(other._capacity);
AllocationData oldAllocation;
if (oldItemsCapacity)
{
oldAllocation.Allocate(oldItemsCapacity);
Memory::MoveItems(oldAllocation.Get(), _allocation.Get(), oldItemsCapacity);
_allocation.Free();
}
// Move other to source
if (otherItemsCapacity)
{
_allocation.Allocate(otherItemsCapacity);
Memory::MoveItems(_allocation.Get(), other._allocation.Get(), otherItemsCapacity);
other._allocation.Free();
}
// Move temp to other
if (oldItemsCapacity)
{
other._allocation.Allocate(oldItemsCapacity);
Memory::MoveItems(other._allocation.Get(), oldAllocation.Get(), oldItemsCapacity);
}
}
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
}
public:
template
bool operator==(const BitArray& other) const
{
if (_count == other.Count())
{
for (int32 i = 0; i < _count; i++)
{
if (!(Get(i) == other.Get(i)))
return false;
}
return true;
}
return false;
}
template
bool operator!=(const BitArray& other) const
{
return !operator==(other);
}
};