diff --git a/include/Nazara/Core/Bitset.hpp b/include/Nazara/Core/Bitset.hpp index 5aca6ef39..a3aea9c0d 100644 --- a/include/Nazara/Core/Bitset.hpp +++ b/include/Nazara/Core/Bitset.hpp @@ -24,6 +24,7 @@ namespace Nz public: class Bit; + using PointerSequence = std::pair; //< Start pointer, bit offset Bitset(); explicit Bitset(std::size_t bitCount, bool val); @@ -49,6 +50,9 @@ namespace Nz std::size_t GetCapacity() const; std::size_t GetSize() const; + PointerSequence Read(const void* ptr, std::size_t bitCount); + PointerSequence Read(const PointerSequence& sequence, std::size_t bitCount); + void PerformsAND(const Bitset& a, const Bitset& b); void PerformsNOT(const Bitset& a); void PerformsOR(const Bitset& a, const Bitset& b); @@ -107,6 +111,8 @@ namespace Nz static constexpr std::size_t bitsPerBlock = std::numeric_limits::digits; static constexpr std::size_t npos = std::numeric_limits::max(); + static Bitset FromPointer(const void* ptr, std::size_t bitCount, PointerSequence* sequence = nullptr); + private: std::size_t FindFirstFrom(std::size_t blockIndex) const; Block GetLastBlockMask() const; diff --git a/include/Nazara/Core/Bitset.inl b/include/Nazara/Core/Bitset.inl index 7a7015059..3cfeec62b 100644 --- a/include/Nazara/Core/Bitset.inl +++ b/include/Nazara/Core/Bitset.inl @@ -323,6 +323,76 @@ namespace Nz return m_bitCount; } + /*! + * \brief Read a byte sequence into a bitset + * + * This function expand the bitset with bits read from a byte sequence + * + * \param ptr A pointer to the start of the byte sequence + * \param bitCount Number of bits to read from the byte sequence + * + * \returns A pointer to the next byte to read along with the next bit index (useful when reading multiple times) + * + * \remark For technical reasons, ceil(bitCount / 8) bytes from the sequence will always be read (even with non-multiple-of-8 bitCount) + * + * \see AppendBits + * \see Read + */ + template + typename Bitset::PointerSequence Bitset::Read(const void* ptr, std::size_t bitCount) + { + return Read(PointerSequence(ptr, 0U), bitCount); + } + + /*! + * \brief Read a byte sequence into a bitset + * + * This function expand the bitset with bits read from a pointer sequence (made of a pointer and a bit index) + * + * \param sequence A pointer sequence to the start of the byte sequence + * \param bitCount Number of bits to read from the byte sequence + * + * \returns A pointer to the next byte to read along with the next bit index (useful when reading multiple times) + * + * \remark For technical reasons, ceil(bitCount / 8) bytes from the sequence will always be read (even with non-multiple-of-8 bitCount) + * + * \see AppendBits + * \see Read + */ + template + typename Bitset::PointerSequence Bitset::Read(const PointerSequence& sequence, std::size_t bitCount) + { + NazaraAssert(sequence.first, "Invalid pointer sequence"); + NazaraAssert(sequence.second < 8, "Invalid next bit index (must be < 8)"); + + std::size_t totalBitCount = sequence.second + bitCount; + + const UInt8* u8Ptr = static_cast(sequence.first); + const UInt8* endPtr = u8Ptr + ((totalBitCount != 0) ? (totalBitCount - 1) / 8 : 0); + const UInt8* nextPtr = endPtr + ((totalBitCount % 8 != 0) ? 0 : 1); + + // Read the first block apart to apply a mask on the first byte if necessary + if (sequence.second != 0) + { + UInt8 mask = ~((1U << sequence.second) - 1U); + + std::size_t readCount = std::min(bitCount, 8 - sequence.second); + AppendBits(Block(*u8Ptr++ & mask) >> sequence.second, readCount); + bitCount -= readCount; + } + + // And then read the remaining bytes + while (u8Ptr <= endPtr) + { + std::size_t bitToRead = std::min(bitCount, 8); + AppendBits(*u8Ptr++, bitToRead); + bitCount -= bitToRead; + } + + // Returns informations to continue reading + return PointerSequence(nextPtr, totalBitCount % 8); + } + /*! * \brief Performs the "AND" operator between two bitsets * @@ -1039,6 +1109,35 @@ namespace Nz return *this; } + /*! + * \brief Builds a bitset from a byte sequence + * + * This function builds a bitset using a byte sequence by reading bitCount bits from it + * + * \param ptr A pointer to the start of the byte sequence + * \param bitCount Number of bits to read from the byte sequence + * \param sequence Optional data to pass to a next call to Read + * + * \return The constructed bitset + * + * \remark For technical reasons, ceil(bitCount / 8) bytes from the sequence will always be read (even with non-multiple-of-8 bitCount) + * + * \see AppendBits + * \see Read + */ + template + Bitset Bitset::FromPointer(const void* ptr, std::size_t bitCount, PointerSequence* sequence) + { + Bitset bitset; + + if (sequence) + *sequence = bitset.Read(ptr, bitCount); + else + bitset.Read(ptr, bitCount); + + return bitset; + } + /*! * \brief Finds the position of the first bit set to true after the blockIndex * \return The position of the bit diff --git a/tests/Engine/Core/Bitset.cpp b/tests/Engine/Core/Bitset.cpp index c8b98288c..a5410da3c 100644 --- a/tests/Engine/Core/Bitset.cpp +++ b/tests/Engine/Core/Bitset.cpp @@ -10,6 +10,7 @@ template void CheckAppend(const char* title); template void CheckBitOps(const char* title); template void CheckConstructor(const char* title); template void CheckCopyMoveSwap(const char* title); +template void CheckRead(const char* title); SCENARIO("Bitset", "[CORE][BITSET]") { @@ -28,6 +29,7 @@ void Check(const char* title) CheckBitOps(title); CheckAppend(title); + CheckRead(title); } template @@ -217,3 +219,55 @@ void CheckCopyMoveSwap(const char* title) } } +template +void CheckRead(const char* title) +{ + SECTION(title) + { + GIVEN("An empty bitset filled by reading") + { + #define BitVal1 10010101 + #define BitVal2 11010010 + #define BitVal3 01101010 + std::array data = {NazaraPrefixMacro(BitVal1, 0b), NazaraPrefixMacro(BitVal2, 0b), NazaraPrefixMacro(BitVal3, 0b)}; + const char result[] = NazaraStringifyMacro(BitVal3) NazaraStringifyMacro(BitVal2) NazaraStringifyMacro(BitVal1); + std::size_t resultLength = Nz::CountOf(result) - 1; + std::size_t bitCount = data.size() * 8; + #undef BitVal1 + #undef BitVal2 + #undef BitVal3 + + std::array, 8> tests = { + { + {"We read bits one by one", 1}, + {"We read bits two by two", 2}, + {"We read bits three by three", 3}, + {"We read bits four by four", 4}, + {"We read bits six by six", 6}, + {"We read bits byte by byte", 8}, + {"We read bits twelve by twelve", 12}, + {"We read bits all at once", 24} + } + }; + + for (auto& pair : tests) + { + WHEN(pair.first) + { + Nz::Bitset bitset; + + auto seq = bitset.Read(data.data(), pair.second); + for (std::size_t i = pair.second; i < bitCount; i += pair.second) + seq = bitset.Read(seq, pair.second); + + REQUIRE(bitset.GetSize() == bitCount); + + Nz::Bitset expectedBitset(result); + + CHECK(bitset == expectedBitset); + CHECK(bitset.GetBlockCount() == (bitCount / bitset.bitsPerBlock + std::min(1, bitCount % bitset.bitsPerBlock))); + } + } + } + } +}