/***************************************************************************** * 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 #include #include #include #include #include namespace OpenRCT2 { namespace Detail { namespace BitSet { static constexpr size_t BitsPerByte = std::numeric_limits>::digits; template 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 static constexpr size_t ComputeBlockSize() { constexpr size_t numBits = ByteAlignBits(); if constexpr (numBits >= std::numeric_limits::digits) { return sizeof(uintptr_t); } else { const auto numBytes = numBits / BitsPerByte; auto mask = 1u; while (mask < numBytes) { mask <<= 1u; } return mask; } } template 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 static constexpr size_t popcount(const T val) { size_t res = 0; auto x = static_cast>(val); while (x > 0u) { if (x & 1u) res++; x >>= 1u; } return res; } template 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 struct storage_block_type_aligned { using value_type = typename StorageBlockType()>::value_type; }; } // namespace BitSet } // namespace Detail template class BitSet { static constexpr size_t ByteAlignedBitSize = Detail::BitSet::ByteAlignBits(); using StorageBlockType = typename Detail::BitSet::storage_block_type_aligned::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(); 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(~BlockValueZero); static constexpr bool RequiresTrim = TBitSize != CapacityBits; public: using BlockType = StorageBlockType; using Storage = std::array; // Proxy object to access the bits as single value. template 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; using const_reference = reference_base; template 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; using const_iterator = iterator_base; private: Storage _data{}; public: constexpr BitSet() = default; constexpr BitSet(const StorageBlockType val) : _data{ val } { } constexpr BitSet(const std::initializer_list& indices) { for (auto idx : indices) { 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 TAlloc = std::allocator> std::basic_string to_string(TChar zero = TChar{ '0' }, TChar one = TChar{ '1' }) const { std::basic_string 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>(res, other, std::make_index_sequence{}); 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>(res, other, std::make_index_sequence{}); 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>(res, other, std::make_index_sequence{}); 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{}); } 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{}); } constexpr bool operator>(const BitSet& other) const noexcept { return std::lexicographical_compare( _data.begin(), _data.end(), other._data.begin(), other._data.end(), std::greater{}); } 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{}); } private: template void ApplyOp(BitSet& dst, const BitSet& src, std::index_sequence) 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