// 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 #include #ifdef NAZARA_COMPILER_MSVC // Bits tricks require us to disable some warnings under VS #pragma warning(disable: 4146) #pragma warning(disable: 4804) #endif 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"); if (++bit >= m_bitCount) return npos; // 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()); // On réinitialise nos blocs à zéro m_blocks.clear(); m_blocks.resize(minmax.second, 0U); 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 unsigned int lastBlockIndex = m_blocks.size() - 1; 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 m_blocks[lastBlockIndex] |= fullBitMask << remainingBits; m_bitCount = bitCount; ResetExtraBits(); } template void NzBitset::Reset() { Set(false); } template void NzBitset::Reset(unsigned int bit) { Set(bit, 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))) != 0; } template bool NzBitset::TestAll() const { // 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() const { 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() const { 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 T 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 void NzBitset::UnboundedReset(unsigned int bit) { UnboundedSet(bit, false); } template void NzBitset::UnboundedSet(unsigned int bit, bool val) { if (bit < m_bitCount) Set(bit, val); else if (val) { // On élargit le bitset seulement s'il y a un bit à marquer Resize(bit + 1, false); Set(bit, true); } } template bool NzBitset::UnboundedTest(unsigned int bit) const { if (bit < m_bitCount) return Test(bit); else return false; } 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; } // Est-ce qu'on a un bloc non-nul ? if (i == m_blocks.size()) return npos; 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 bool operator==(const NzBitset& lhs, const NzBitset& rhs) { // La comparaison part du principe que (uint8) 00001100 == (uint16) 00000000 00001100 // et conserve donc cette propriété const NzBitset& greater = (lhs.GetBlockCount() > rhs.GetBlockCount()) ? lhs : rhs; const NzBitset& lesser = (lhs.GetBlockCount() > rhs.GetBlockCount()) ? rhs : lhs; unsigned int maxBlockCount = greater.GetBlockCount(); unsigned int minBlockCount = lesser.GetBlockCount(); // Nous testons les blocs en commun pour vérifier l'égalité des bits for (unsigned int i = 0; i < minBlockCount; ++i) { if (lhs.GetBlock(i) != rhs.GetBlock(i)) return false; } // Nous vérifions maintenant les blocs que seul le plus grand bitset possède, pour prétendre à l'égalité // ils doivent tous être nuls for (unsigned int i = minBlockCount; i < maxBlockCount; ++i) if (greater.GetBlock(i)) return false; return true; } template bool operator!=(const NzBitset& lhs, const NzBitset& rhs) { return !(lhs == rhs); } template bool operator<(const NzBitset& lhs, const NzBitset& rhs) { const NzBitset& greater = (lhs.GetBlockCount() > rhs.GetBlockCount()) ? lhs : rhs; const NzBitset& lesser = (lhs.GetBlockCount() > rhs.GetBlockCount()) ? rhs : lhs; unsigned int maxBlockCount = greater.GetBlockCount(); unsigned int minBlockCount = lesser.GetBlockCount(); // If the greatest bitset has a single bit active in a block outside the lesser bitset range, then it is greater for (unsigned int i = maxBlockCount; i > minBlockCount; ++i) { if (greater.GetBlock(i)) return lhs.GetBlockCount() < rhs.GetBlockCount(); } // Compare the common blocks for (unsigned int i = 0; i < minBlockCount; ++i) { unsigned int index = (minBlockCount - i - 1); // Compare from the most significant block to the less significant block if (lhs.GetBlock(index) < rhs.GetBlock(index)) return true; } return false; // They are equal } template bool operator<=(const NzBitset& lhs, const NzBitset& rhs) { return lhs < rhs || lhs == rhs; } template bool operator>(const NzBitset& lhs, const NzBitset& rhs) { return rhs < lhs; } template bool operator>=(const NzBitset& lhs, const NzBitset& rhs) { return rhs <= lhs; } 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); } } #ifdef NAZARA_COMPILER_MSVC // Reenable those warnings #pragma warning(default: 4146) #pragma warning(default: 4804) #endif #include