From 17961f42fd13b6f46001ab98e1d688d4750cae54 Mon Sep 17 00:00:00 2001 From: Lynix Date: Mon, 16 Mar 2015 17:23:55 +0100 Subject: [PATCH] Added Bitset class It's like std::bitset with a dynamic size Former-commit-id: 704352d954c88e9cf829b41448d7761f89f59786 --- include/Nazara/Core/Bitset.hpp | 155 ++++++++ include/Nazara/Core/Bitset.inl | 650 +++++++++++++++++++++++++++++++++ 2 files changed, 805 insertions(+) create mode 100644 include/Nazara/Core/Bitset.hpp create mode 100644 include/Nazara/Core/Bitset.inl diff --git a/include/Nazara/Core/Bitset.hpp b/include/Nazara/Core/Bitset.hpp new file mode 100644 index 000000000..85ec7e459 --- /dev/null +++ b/include/Nazara/Core/Bitset.hpp @@ -0,0 +1,155 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_BITSET_HPP +#define NAZARA_BITSET_HPP + +#include +#include +#include +#include + +class NzAbstractHash; + +template> +class NzBitset +{ + static_assert(std::is_integral() && std::is_unsigned(), "Block must be a unsigned integral type"); + + public: + class Bit; + + NzBitset(); + explicit NzBitset(unsigned int bitCount, bool val = false); + explicit NzBitset(const char* bits); + NzBitset(const char* bits, unsigned int bitCount); + NzBitset(const NzBitset& bitset) = default; + explicit NzBitset(const NzString& bits); + NzBitset(NzBitset&& bitset) noexcept = default; + ~NzBitset() = default; + + void Clear(); + unsigned int Count() const; + void Flip(); + + unsigned int FindFirst() const; + unsigned int FindNext(unsigned int bit) const; + + Block GetBlock(unsigned int i) const; + unsigned int GetBlockCount() const; + unsigned int GetCapacity() const; + unsigned int GetSize() const; + + void PerformsAND(const NzBitset& a, const NzBitset& b); + void PerformsNOT(const NzBitset& a); + void PerformsOR(const NzBitset& a, const NzBitset& b); + void PerformsXOR(const NzBitset& a, const NzBitset& b); + + bool Intersects(const NzBitset& bitset) const; + + void Reserve(unsigned int bitCount); + void Resize(unsigned int bitCount, bool defaultVal = false); + + void Reset(); + void Reset(unsigned int bit); + + void Set(bool val = true); + void Set(unsigned int bit, bool val = true); + void SetBlock(unsigned int i, Block block); + + void Swap(NzBitset& bitset); + + bool Test(unsigned int bit) const; + bool TestAll(); + bool TestAny(); + bool TestNone(); + + template T To() const; + NzString ToString() const; + + Bit operator[](int index); + bool operator[](int index) const; + + NzBitset operator~() const; + + NzBitset& operator=(const NzBitset& bitset) = default; + NzBitset& operator=(const NzString& bits); + NzBitset& operator=(NzBitset&& bitset) noexcept = default; + + NzBitset& operator&=(const NzBitset& bitset); + NzBitset& operator|=(const NzBitset& bitset); + NzBitset& operator^=(const NzBitset& bitset); + + static Block fullBitMask; + static unsigned int bitsPerBlock; + static unsigned int npos; + + private: + unsigned int FindFirstFrom(unsigned int blockIndex) const; + Block GetLastBlockMask() const; + void ResetExtraBits(); + static unsigned int ComputeBlockCount(unsigned int bitCount); + static unsigned int GetBitIndex(unsigned int bit); + static unsigned int GetBlockIndex(unsigned int bit); + + std::vector m_blocks; + unsigned int m_bitCount; +}; + +template +NzBitset operator&(const NzBitset& lhs, const NzBitset& rhs); + +template +NzBitset operator|(const NzBitset& lhs, const NzBitset& rhs); + +template +NzBitset operator^(const NzBitset& lhs, const NzBitset& rhs); + +template +class NzBitset::Bit +{ + friend NzBitset; + + public: + Bit(const Bit& bit) = default; + + Bit& Flip(); + Bit& Reset(); + Bit& Set(bool val = true); + bool Test() const; + + template + void* operator&() const; + + operator bool() const; + Bit& operator=(bool val); + Bit& operator=(const Bit& bit); + + Bit& operator|=(bool val); + Bit& operator&=(bool val); + Bit& operator^=(bool val); + Bit& operator-=(bool val); + + private: + Bit(Block& block, Block mask) : + m_block(block), + m_mask(mask) + { + } + + Block& m_block; + Block m_mask; +}; + +namespace std +{ + template + void swap(NzBitset& lhs, NzBitset& rhs); +} + +#include + +#endif // NAZARA_BITSET_HPP diff --git a/include/Nazara/Core/Bitset.inl b/include/Nazara/Core/Bitset.inl new file mode 100644 index 000000000..2c00daf5e --- /dev/null +++ b/include/Nazara/Core/Bitset.inl @@ -0,0 +1,650 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +template +NzBitset::NzBitset() : +m_bitCount(0) +{ +} + +template +NzBitset::NzBitset(unsigned int bitCount, bool val) : +NzBitset() +{ + Resize(bitCount, val); +} + +template +NzBitset::NzBitset(const char* bits) : +NzBitset(bits, std::strlen(bits)) +{ +} + +template +NzBitset::NzBitset(const char* bits, unsigned int bitCount) : +m_blocks(ComputeBlockCount(bitCount), 0U), +m_bitCount(bitCount) +{ + for (unsigned int i = 0; i < bitCount; ++i) + { + switch (*bits++) + { + case '1': + // On adapte l'indice (inversion par rapport à la chaîne) + Set(m_bitCount - i - 1, true); + break; + + case '0': + // Tous les blocs ont été initialisés à zéro, rien à faire ici + break; + + default: + NazaraAssert(false, "Unexpected char (neither 1 nor 0)"); + break; + } + } +} + +template +NzBitset::NzBitset(const NzString& bits) : +NzBitset(bits.GetConstBuffer(), bits.GetSize()) +{ +} + +template +void NzBitset::Clear() +{ + m_bitCount = 0; + m_blocks.clear(); +} + +template +unsigned int NzBitset::Count() const +{ + if (m_blocks.empty()) + return 0; + + unsigned int count = 0; + for (unsigned int i = 0; i < m_blocks.size(); ++i) + count += NzCountBits(m_blocks[i]); + + return count; +} + +template +void NzBitset::Flip() +{ + for (Block& block : m_blocks) + block ^= fullBitMask; + + ResetExtraBits(); +} + +template +unsigned int NzBitset::FindFirst() const +{ + return FindFirstFrom(0); +} + +template +unsigned int NzBitset::FindNext(unsigned int bit) const +{ + NazaraAssert(bit < m_bitCount, "Bit index out of range"); + + bit++; + + // Le bloc du bit, l'indice du bit + unsigned int blockIndex = GetBlockIndex(bit); + unsigned int bitIndex = GetBitIndex(bit); + + // Récupération du bloc + Block block = m_blocks[blockIndex]; + + // On ignore les X premiers bits + block >>= bitIndex; + + // Si le bloc n'est pas nul, c'est bon, sinon on doit chercher à partir du prochain bloc + if (block) + return NzIntegralLog2Pot(block & -block) + bit; + else + return FindFirstFrom(blockIndex + 1); +} + +template +Block NzBitset::GetBlock(unsigned int i) const +{ + NazaraAssert(i < m_blocks.size(), "Block index out of range"); + + return m_blocks[i]; +} + +template +unsigned int NzBitset::GetBlockCount() const +{ + return m_blocks.size(); +} + +template +unsigned int NzBitset::GetCapacity() const +{ + return m_blocks.capacity()*bitsPerBlock; +} + +template +unsigned int NzBitset::GetSize() const +{ + return m_bitCount; +} + +template +void NzBitset::PerformsAND(const NzBitset& a, const NzBitset& b) +{ + std::pair minmax = std::minmax(a.GetBlockCount(), b.GetBlockCount()); + + m_blocks.resize(minmax.second); + m_bitCount = std::max(a.GetSize(), b.GetSize()); + + // Dans le cas du AND, nous pouvons nous arrêter à la plus petite taille (car x & 0 = 0) + for (unsigned int i = 0; i < minmax.first; ++i) + m_blocks[i] = a.GetBlock(i) & b.GetBlock(i); + + ResetExtraBits(); +} + +template +void NzBitset::PerformsNOT(const NzBitset& a) +{ + m_blocks.resize(a.GetBlockCount()); + m_bitCount = a.GetSize(); + + for (unsigned int i = 0; i < m_blocks.size(); ++i) + m_blocks[i] = ~a.GetBlock(i); + + ResetExtraBits(); +} + +template +void NzBitset::PerformsOR(const NzBitset& a, const NzBitset& b) +{ + const NzBitset& greater = (a.GetBlockCount() > b.GetBlockCount()) ? a : b; + const NzBitset& lesser = (a.GetBlockCount() > b.GetBlockCount()) ? b : a; + + unsigned int maxBlockCount = greater.GetBlockCount(); + unsigned int minBlockCount = lesser.GetBlockCount(); + m_blocks.resize(maxBlockCount); + m_bitCount = greater.GetSize(); + + for (unsigned int i = 0; i < minBlockCount; ++i) + m_blocks[i] = a.GetBlock(i) | b.GetBlock(i); + + for (unsigned int i = minBlockCount; i < maxBlockCount; ++i) + m_blocks[i] = greater.GetBlock(i); // (x | 0 = x) + + ResetExtraBits(); +} + +template +void NzBitset::PerformsXOR(const NzBitset& a, const NzBitset& b) +{ + const NzBitset& greater = (a.GetBlockCount() > b.GetBlockCount()) ? a : b; + const NzBitset& lesser = (a.GetBlockCount() > b.GetBlockCount()) ? b : a; + + unsigned int maxBlockCount = greater.GetBlockCount(); + unsigned int minBlockCount = lesser.GetBlockCount(); + m_blocks.resize(maxBlockCount); + m_bitCount = greater.GetSize(); + + for (unsigned int i = 0; i < minBlockCount; ++i) + m_blocks[i] = a.GetBlock(i) ^ b.GetBlock(i); + + for (unsigned int i = minBlockCount; i < maxBlockCount; ++i) + m_blocks[i] = greater.GetBlock(i); // (x ^ 0 = x) + + ResetExtraBits(); +} + +template +bool NzBitset::Intersects(const NzBitset& bitset) const +{ + // On ne testera que les blocs en commun + unsigned int sharedBlocks = std::min(GetBlockCount(), bitset.GetBlockCount()); + for (unsigned int i = 0; i < sharedBlocks; ++i) + { + Block a = GetBlock(i); + Block b = bitset.GetBlock(i); + if (a & b) + return true; + } + + return false; +} + +template +void NzBitset::Reserve(unsigned int bitCount) +{ + m_blocks.reserve(ComputeBlockCount(bitCount)); +} + +template +void NzBitset::Resize(unsigned int bitCount, bool defaultVal) +{ + // On commence par changer la taille du conteneur, avec la valeur correcte d'initialisation + auto oldSize = m_blocks.size(); + m_blocks.resize(ComputeBlockCount(bitCount), (defaultVal) ? fullBitMask : 0U); + + unsigned int remainingBits = GetBitIndex(m_bitCount); + if (bitCount > m_bitCount && remainingBits > 0 && defaultVal) + { + // Initialisation des bits non-utilisés du dernier bloc avant le changement de taille + Block& block = m_blocks[oldSize-1]; // Le bloc à corriger + Block mask = fullBitMask << remainingBits; // Le masque sur les bits en question + + // Set/Reset des bits + /* + if (defaultVal) + block |= mask; + else + block &= ~mask; + */ + // https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching + block = (block & ~mask) | (-defaultVal & mask); + } + + m_bitCount = bitCount; + ResetExtraBits(); +} + +template +void NzBitset::Reset() +{ + Set(false); +} + +template +void NzBitset::Set(bool val) +{ + std::fill(m_blocks.begin(), m_blocks.end(), (val) ? fullBitMask : Block(0U)); + if (val) + ResetExtraBits(); +} + +template +void NzBitset::Set(unsigned int bit, bool val) +{ + NazaraAssert(bit < m_bitCount, "Bit index out of range"); + + Block& block = m_blocks[GetBlockIndex(bit)]; + Block mask = Block(1U) << GetBitIndex(bit); + + // Activation du bit sans branching + // https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching + block = (block & ~mask) | (-val & mask); +} + +template +void NzBitset::SetBlock(unsigned int i, Block block) +{ + NazaraAssert(i < m_blocks.size(), "Block index out of range"); + + m_blocks[i] = block; + if (i == m_blocks.size()-1) + ResetExtraBits(); +} + +template +void NzBitset::Swap(NzBitset& bitset) +{ + std::swap(m_bitCount, bitset.m_bitCount); + std::swap(m_blocks, bitset.m_blocks); +} + +template +bool NzBitset::Test(unsigned int bit) const +{ + NazaraAssert(bit < m_bitCount, "Bit index out of range"); + + return m_blocks[GetBlockIndex(bit)] & (Block(1U) << GetBitIndex(bit)); +} + +template +bool NzBitset::TestAll() +{ + // Cas particulier du dernier bloc + Block lastBlockMask = GetLastBlockMask(); + + for (unsigned int i = 0; i < m_blocks.size(); ++i) + { + Block mask = (i == m_blocks.size() - 1) ? lastBlockMask : fullBitMask; + if (m_blocks[i] == mask) // Les extra bits sont à zéro, on peut donc tester sans procéder à un masquage + return false; + } + + return true; +} + +template +bool NzBitset::TestAny() +{ + if (m_blocks.empty()) + return false; + + for (unsigned int i = 0; i < m_blocks.size(); ++i) + { + if (m_blocks[i]) + return true; + } + + return false; +} + +template +bool NzBitset::TestNone() +{ + return !TestAny(); +} + +template +template +T NzBitset::To() const +{ + static_assert(std::is_integral() && std::is_unsigned(), "T must be a unsigned integral type"); + + NazaraAssert(m_bitCount <= std::numeric_limits::digits, "Bit count cannot be greater than UInt32 bit count"); + + T value = 0; + for (unsigned int i = 0; i < m_blocks.size(); ++i) + value |= static_cast(m_blocks[i]) << i*bitsPerBlock; + + return value; +} + +template +NzString NzBitset::ToString() const +{ + NzString str(m_bitCount, '0'); + + for (unsigned int i = 0; i < m_bitCount; ++i) + { + if (Test(i)) + str[m_bitCount - i - 1] = '1'; // Inversion de l'indice + } + + return str; +} + +template +typename NzBitset::Bit NzBitset::operator[](int index) +{ + return Bit(m_blocks[GetBlockIndex(index)], Block(1U) << GetBitIndex(index)); +} + +template +bool NzBitset::operator[](int index) const +{ + return Test(index); +} + +template +NzBitset NzBitset::operator~() const +{ + NzBitset bitset; + bitset.PerformsNOT(*this); + + return bitset; +} + +template +NzBitset& NzBitset::operator=(const NzString& bits) +{ + NzBitset bitset(bits); + std::swap(*this, bitset); + + return *this; +} + +template +NzBitset& NzBitset::operator&=(const NzBitset& bitset) +{ + PerformsAND(*this, bitset); + + return *this; +} + +template +NzBitset& NzBitset::operator|=(const NzBitset& bitset) +{ + PerformsOR(*this, bitset); + + return *this; +} + +template +NzBitset& NzBitset::operator^=(const NzBitset& bitset) +{ + PerformsXOR(*this, bitset); + + return *this; +} + +template +unsigned int NzBitset::FindFirstFrom(unsigned int blockIndex) const +{ + if (blockIndex >= m_blocks.size()) + return npos; + + // On cherche le premier bloc non-nul + unsigned int i = blockIndex; + for (; i < m_blocks.size(); ++i) + { + if (m_blocks[i]) + break; + } + Block block = m_blocks[i]; + + // Calcul de la position du LSB dans le bloc (et ajustement de la position) + return NzIntegralLog2Pot(block & -block) + i*bitsPerBlock; +} + +template +Block NzBitset::GetLastBlockMask() const +{ + return (Block(1U) << GetBitIndex(m_bitCount)) - 1U; +} + +template +void NzBitset::ResetExtraBits() +{ + Block mask = GetLastBlockMask(); + if (mask) + m_blocks.back() &= mask; +} + +template +unsigned int NzBitset::ComputeBlockCount(unsigned int bitCount) +{ + return GetBlockIndex(bitCount) + ((GetBitIndex(bitCount) != 0U) ? 1U : 0U); +} + +template +unsigned int NzBitset::GetBitIndex(unsigned int bit) +{ + return bit & (bitsPerBlock - 1U); // bit % bitsPerBlock +} + +template +unsigned int NzBitset::GetBlockIndex(unsigned int bit) +{ + return bit / bitsPerBlock; +} + +template +Block NzBitset::fullBitMask = std::numeric_limits::max(); + +template +unsigned int NzBitset::bitsPerBlock = std::numeric_limits::digits; + +template +unsigned int NzBitset::npos = std::numeric_limits::max(); + + +template +NzBitset operator&(const NzBitset& lhs, const NzBitset& rhs) +{ + NzBitset bitset; + bitset.PerformsAND(lhs, rhs); + + return bitset; +} + +template +NzBitset operator|(const NzBitset& lhs, const NzBitset& rhs) +{ + NzBitset bitset; + bitset.PerformsOR(lhs, rhs); + + return bitset; +} + +template +NzBitset operator^(const NzBitset& lhs, const NzBitset& rhs) +{ + NzBitset bitset; + bitset.PerformsXOR(lhs, rhs); + + return bitset; +} + + +template +typename NzBitset::Bit& NzBitset::Bit::Flip() +{ + m_block ^= m_mask; + + return *this; +} + +template +typename NzBitset::Bit& NzBitset::Bit::Reset() +{ + return Set(false); +} + +template +typename NzBitset::Bit& NzBitset::Bit::Set(bool val) +{ + // https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching + m_block = (m_block & ~m_mask) | (-val & m_mask); + + return *this; +} + +template +bool NzBitset::Bit::Test() const +{ + return m_block & m_mask; +} + +template +template +void* NzBitset::Bit::operator&() const +{ + // Le template est nécessaire pour ne planter la compilation qu'à l'utilisation + static_assert(!BadCall, "It is impossible to take the address of a bit in a bitset"); + + return nullptr; +} + +template +NzBitset::Bit::operator bool() const +{ + return Test(); +} + +template +typename NzBitset::Bit& NzBitset::Bit::operator=(bool val) +{ + return Set(val); +} + +template +typename NzBitset::Bit& NzBitset::Bit::operator=(const Bit& bit) +{ + return Set(bit); +} + +template +typename NzBitset::Bit& NzBitset::Bit::operator|=(bool val) +{ + // Version sans branching: + Set((val) ? true : Test()); + + // Avec branching: + /* + if (val) + Set(); + */ + + return *this; +} + +template +typename NzBitset::Bit& NzBitset::Bit::operator&=(bool val) +{ + // Version sans branching: + Set((val) ? Test() : false); + + // Avec branching: + /* + if (!val) + Reset(); + */ + + return *this; +} + +template +typename NzBitset::Bit& NzBitset::Bit::operator^=(bool val) +{ + // Version sans branching: + Set((val) ? !Test() : Test()); + + // Avec branching: + /* + if (val) + Flip(); + */ + + return *this; +} + +template +typename NzBitset::Bit& NzBitset::Bit::operator-=(bool val) +{ + // Version sans branching: + Set((val) ? false : Test()); + + // Avec branching: + /* + if (val) + Reset(); + */ + + return *this; +} + +namespace std +{ + template + void swap(NzBitset& lhs, NzBitset& rhs) + { + lhs.Swap(rhs); + } +} + +#include