1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-04 13:42:55 +01:00
Files
OpenRCT2/src/openrct2/core/BitSet.hpp
2024-10-03 10:49:25 +10:00

538 lines
17 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "../util/Util.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <string>
namespace OpenRCT2
{
namespace Detail
{
namespace BitSet
{
static constexpr size_t BitsPerByte = std::numeric_limits<std::underlying_type_t<std::byte>>::digits;
template<size_t TNumBits> static constexpr size_t ByteAlignBits()
{
const auto reminder = TNumBits % BitsPerByte;
if constexpr (reminder == 0u)
{
return TNumBits;
}
else
{
return TNumBits + (BitsPerByte - (TNumBits % BitsPerByte));
}
}
static_assert(ByteAlignBits<1>() == 8);
static_assert(ByteAlignBits<4>() == 8);
static_assert(ByteAlignBits<8>() == 8);
static_assert(ByteAlignBits<9>() == 16);
static_assert(ByteAlignBits<9>() == 16);
static_assert(ByteAlignBits<17>() == 24);
static_assert(ByteAlignBits<24>() == 24);
static_assert(ByteAlignBits<31>() == 32);
// Returns the amount of bytes required for a single block.
template<size_t TNumBits> static constexpr size_t ComputeBlockSize()
{
constexpr size_t numBits = ByteAlignBits<TNumBits>();
if constexpr (numBits >= std::numeric_limits<uintptr_t>::digits)
{
return sizeof(uintptr_t);
}
else
{
const auto numBytes = numBits / BitsPerByte;
auto mask = 1u;
while (mask < numBytes)
{
mask <<= 1u;
}
return mask;
}
}
template<size_t TNumBits, size_t TBlockSizeBytes> static constexpr size_t ComputeBlockCount()
{
size_t numBits = TNumBits;
size_t numBlocks = 0;
while (numBits > 0)
{
numBlocks++;
numBits -= std::min(TBlockSizeBytes * BitsPerByte, numBits);
}
return numBlocks;
}
static_assert(ComputeBlockSize<1>() == sizeof(uint8_t));
static_assert(ComputeBlockSize<4>() == sizeof(uint8_t));
static_assert(ComputeBlockSize<8>() == sizeof(uint8_t));
static_assert(ComputeBlockSize<9>() == sizeof(uint16_t));
static_assert(ComputeBlockSize<14>() == sizeof(uint16_t));
static_assert(ComputeBlockSize<16>() == sizeof(uint16_t));
static_assert(ComputeBlockSize<18>() == sizeof(uint32_t));
static_assert(ComputeBlockSize<31>() == sizeof(uint32_t));
static_assert(ComputeBlockSize<33>() == sizeof(uintptr_t));
// TODO: Replace with std::popcount when C++20 is enabled.
template<typename T> static constexpr size_t popcount(const T val)
{
size_t res = 0;
auto x = static_cast<std::make_unsigned_t<T>>(val);
while (x > 0u)
{
if (x & 1u)
res++;
x >>= 1u;
}
return res;
}
template<size_t TByteSize> struct StorageBlockType;
template<> struct StorageBlockType<1>
{
using value_type = uint8_t;
};
template<> struct StorageBlockType<2>
{
using value_type = uint16_t;
};
template<> struct StorageBlockType<4>
{
using value_type = uint32_t;
};
template<> struct StorageBlockType<8>
{
using value_type = uint64_t;
};
template<size_t TBitSize> struct storage_block_type_aligned
{
using value_type = typename StorageBlockType<ComputeBlockSize<TBitSize>()>::value_type;
};
} // namespace BitSet
} // namespace Detail
template<size_t TBitSize> class BitSet
{
static constexpr size_t ByteAlignedBitSize = Detail::BitSet::ByteAlignBits<TBitSize>();
using StorageBlockType = typename Detail::BitSet::storage_block_type_aligned<ByteAlignedBitSize>::value_type;
static constexpr size_t BlockByteSize = sizeof(StorageBlockType);
static constexpr size_t BlockBitSize = BlockByteSize * Detail::BitSet::BitsPerByte;
static constexpr size_t BlockCount = Detail::BitSet::ComputeBlockCount<ByteAlignedBitSize, BlockByteSize>();
static constexpr size_t CapacityBits = BlockCount * BlockBitSize;
static constexpr StorageBlockType BlockValueZero = StorageBlockType{ 0u };
static constexpr StorageBlockType BlockValueOne = StorageBlockType{ 1u };
static constexpr StorageBlockType BlockValueMask = static_cast<StorageBlockType>(~BlockValueZero);
static constexpr bool RequiresTrim = TBitSize != CapacityBits;
public:
using BlockType = StorageBlockType;
using Storage = std::array<BlockType, BlockCount>;
// Proxy object to access the bits as single value.
template<typename T> class reference_base
{
T& _storage;
const size_t _blockIndex;
const size_t _blockOffset;
public:
constexpr reference_base(T& data, size_t blockIndex, size_t blockOffset) noexcept
: _storage(data)
, _blockIndex(blockIndex)
, _blockOffset(blockOffset)
{
}
constexpr reference_base& operator=(const bool value) noexcept
{
if (!value)
_storage[_blockIndex] &= ~(BlockValueOne << _blockOffset);
else
_storage[_blockIndex] |= (BlockValueOne << _blockOffset);
return *this;
}
constexpr reference_base& operator=(const reference_base& value) noexcept
{
return reference_base::operator=(value.value());
}
constexpr bool value() const noexcept
{
return (_storage[_blockIndex] & (BlockValueOne << _blockOffset)) != BlockValueZero;
}
constexpr operator bool() const noexcept
{
return value();
}
};
using reference = reference_base<Storage>;
using const_reference = reference_base<const Storage>;
template<typename T, typename TValue> class iterator_base
{
T* _bitset{};
size_t _pos{};
public:
constexpr iterator_base() = default;
constexpr iterator_base(T* bset, size_t pos)
: _bitset(bset)
, _pos(pos)
{
}
constexpr auto operator*() const
{
const auto blockIndex = ComputeBlockIndex(_pos);
const auto blockOffset = ComputeBlockOffset(_pos);
return TValue(_bitset->data(), blockIndex, blockOffset);
}
constexpr bool operator==(iterator_base other) const
{
return _bitset == other._bitset && _pos == other._pos;
}
constexpr bool operator!=(iterator_base other) const
{
return !(*this == other);
}
constexpr iterator_base& operator++()
{
_pos++;
return *this;
}
constexpr iterator_base operator++(int)
{
iterator_base res = *this;
++(*this);
return res;
}
constexpr iterator_base& operator--()
{
_pos--;
return *this;
}
constexpr iterator_base operator--(int)
{
iterator_base res = *this;
--(*this);
return res;
}
public:
using difference_type = std::size_t;
using value_type = TValue;
using pointer = const TValue*;
using reference = const TValue&;
using iterator_category = std::forward_iterator_tag;
};
using iterator = iterator_base<BitSet, reference>;
using const_iterator = iterator_base<const BitSet, const_reference>;
private:
Storage _data{};
public:
constexpr BitSet() = default;
constexpr BitSet(const StorageBlockType val)
: _data{ val }
{
}
template<typename T> constexpr BitSet(const std::initializer_list<T>& indices)
{
for (auto idx : indices)
{
if constexpr (std::is_enum_v<T>)
set(EnumValue(idx), true);
else
set(idx, true);
}
}
constexpr size_t size() const noexcept
{
return TBitSize;
}
constexpr size_t count() const noexcept
{
size_t numBits = 0;
for (auto& data : _data)
{
numBits += Detail::BitSet::popcount(data);
}
return numBits;
}
constexpr size_t capacity() const noexcept
{
return CapacityBits;
}
constexpr Storage& data() noexcept
{
return _data;
}
constexpr const Storage& data() const noexcept
{
return _data;
}
constexpr BitSet& set(size_t index, bool value) noexcept
{
const auto blockIndex = ComputeBlockIndex(index);
const auto blockOffset = ComputeBlockOffset(index);
if (!value)
_data[blockIndex] &= ~(BlockValueOne << blockOffset);
else
_data[blockIndex] |= (BlockValueOne << blockOffset);
return *this;
}
constexpr bool get(size_t index) const noexcept
{
const auto blockIndex = ComputeBlockIndex(index);
const auto blockOffset = ComputeBlockOffset(index);
return (_data[blockIndex] & (BlockValueOne << blockOffset)) != BlockValueZero;
}
constexpr bool operator[](const size_t index) const noexcept
{
const auto blockIndex = ComputeBlockIndex(index);
const auto blockOffset = ComputeBlockOffset(index);
const_reference ref(_data, blockIndex, blockOffset);
return ref.value();
}
constexpr reference operator[](const size_t index) noexcept
{
const auto blockIndex = ComputeBlockIndex(index);
const auto blockOffset = ComputeBlockOffset(index);
return reference(_data, blockIndex, blockOffset);
}
constexpr BitSet& flip() noexcept
{
for (auto& data : _data)
{
data ^= BlockValueMask;
}
if constexpr (RequiresTrim)
{
Trim();
}
return *this;
}
constexpr BitSet& reset() noexcept
{
std::fill(_data.begin(), _data.end(), BlockValueZero);
if constexpr (RequiresTrim)
{
Trim();
}
return *this;
}
constexpr const_iterator begin() const noexcept
{
return const_iterator(this, 0);
}
constexpr const_iterator end() const noexcept
{
return const_iterator(this, size());
}
constexpr iterator begin() noexcept
{
return iterator(this, 0);
}
constexpr iterator end() noexcept
{
return iterator(this, size());
}
template<class TChar = char, class TTraits = std::char_traits<TChar>, class TAlloc = std::allocator<TChar>>
std::basic_string<TChar, TTraits, TAlloc> to_string(TChar zero = TChar{ '0' }, TChar one = TChar{ '1' }) const
{
std::basic_string<TChar, TTraits, TAlloc> res;
res.resize(TBitSize);
for (size_t i = 0; i < TBitSize; ++i)
{
// Printed as little-endian.
res[TBitSize - i - 1] = get(i) ? one : zero;
}
return res;
}
constexpr BitSet operator^(const BitSet& other) const noexcept
{
BitSet res = *this;
ApplyOp<std::bit_xor<BlockType>>(res, other, std::make_index_sequence<BlockCount>{});
return res;
}
constexpr BitSet& operator^=(const BitSet& other) noexcept
{
*this = *this ^ other;
return *this;
}
constexpr BitSet operator|(const BitSet& other) const noexcept
{
BitSet res = *this;
ApplyOp<std::bit_or<BlockType>>(res, other, std::make_index_sequence<BlockCount>{});
return res;
}
constexpr BitSet& operator|=(const BitSet& other) noexcept
{
*this = *this | other;
return *this;
}
constexpr BitSet operator&(const BitSet& other) const noexcept
{
BitSet res = *this;
ApplyOp<std::bit_and<BlockType>>(res, other, std::make_index_sequence<BlockCount>{});
return res;
}
constexpr BitSet& operator&=(const BitSet& other) noexcept
{
*this = *this & other;
return *this;
}
constexpr BitSet operator~() const noexcept
{
BitSet res = *this;
for (size_t i = 0; i < _data.size(); i++)
{
res._data[i] = ~res._data[i];
}
if constexpr (RequiresTrim)
{
res.Trim();
}
return res;
}
constexpr bool operator<(const BitSet& other) const noexcept
{
return std::lexicographical_compare(
_data.begin(), _data.end(), other._data.begin(), other._data.end(), std::less<StorageBlockType>{});
}
constexpr bool operator<=(const BitSet& other) const noexcept
{
return std::lexicographical_compare(
_data.begin(), _data.end(), other._data.begin(), other._data.end(), std::less_equal<StorageBlockType>{});
}
constexpr bool operator>(const BitSet& other) const noexcept
{
return std::lexicographical_compare(
_data.begin(), _data.end(), other._data.begin(), other._data.end(), std::greater<StorageBlockType>{});
}
constexpr bool operator>=(const BitSet& other) const noexcept
{
return std::lexicographical_compare(
_data.begin(), _data.end(), other._data.begin(), other._data.end(), std::greater_equal<StorageBlockType>{});
}
private:
template<typename TOperator, size_t... TIndex>
void ApplyOp(BitSet& dst, const BitSet& src, std::index_sequence<TIndex...>) const
{
TOperator op{};
((dst._data[TIndex] = op(dst._data[TIndex], src._data[TIndex])), ...);
if constexpr (RequiresTrim)
{
dst.Trim();
}
}
static constexpr size_t ComputeBlockIndex(size_t idx) noexcept
{
if constexpr (BlockCount == 1)
{
return 0;
}
else
{
return idx / BlockBitSize;
}
}
static constexpr size_t ComputeBlockOffset(size_t idx) noexcept
{
if constexpr (BlockCount == 1)
{
return idx;
}
else
{
return idx % BlockBitSize;
}
}
// Some operations require to trim of the excess.
constexpr void Trim() noexcept
{
const auto byteIdx = TBitSize / BlockBitSize;
const auto bitIdx = TBitSize % BlockBitSize;
if constexpr (bitIdx == 0)
return;
auto trimMask = BlockValueMask;
trimMask <<= (BlockBitSize - bitIdx);
trimMask >>= (BlockBitSize - bitIdx);
_data[byteIdx] &= trimMask;
}
};
} // namespace OpenRCT2