diff --git a/include/bx/bitarray.h b/include/bx/bitarray.h index 90141c4..74e8c12 100644 --- a/include/bx/bitarray.h +++ b/include/bx/bitarray.h @@ -12,6 +12,66 @@ namespace bx { + class BitArrayViewBase; + + /// Walks set bits of a mask, lowest first, by shifting the mask down to the next set bit. + /// + /// @tparam MaskT Unsigned integer type of the mask. + /// + template + struct BitMaskToIndexIteratorT + { + /// + BitMaskToIndexIteratorT(MaskT _mask); + + /// Advances to the next set bit. + void next(); + + /// Returns true when no set bits remain. + bool isDone() const; + + /// Returns index of the current set bit. + uint32_t getIndex() const; + + /// Returns remaining mask, shifted so that the current set bit is bit 0. + MaskT getMask() const; + + private: + MaskT m_mask; + uint32_t m_idx; + }; + + /// Range-for iterator over set bits of a bit array, in index order. Dereferences to the bit + /// index. The current word is cached and walked with BitMaskToIndexIteratorT; the next word is + /// looked up with findSet only when the cached one runs out. Clearing the bit under the + /// iterator keeps it valid. Other bits changed during iteration are seen only once the walk + /// reaches their word. + struct BitArrayIterator + { + /// + BitArrayIterator(const BitArrayViewBase& _view, uint32_t _idx); + + /// + uint32_t operator*() const; + + /// + bool operator!=(const BitArrayIterator& _rhs) const; + + /// + const BitArrayIterator& operator++(); + + /// Advances to the next set bit. Returns false at the end. + bool next(); + + private: + void load(uint32_t _idx); + + const BitArrayViewBase& m_view; + BitMaskToIndexIteratorT m_bits; + uint32_t m_word; + uint32_t m_idx; + }; + /// Base class for immutable bit array views providing read-only access to a contiguous bit set. class BitArrayViewBase { @@ -62,6 +122,12 @@ namespace bx /// Returns the maximum number of bits the bit set can hold. uint32_t getCapacity() const; + /// Returns iterator to the first set bit. + BitArrayIterator begin() const; + + /// Returns iterator past the last set bit. + BitArrayIterator end() const; + /// Finds a contiguous run of zero bits. /// /// @param[in] _numBits Number of contiguous zero bits to find. diff --git a/include/bx/inline/bitarray.inl b/include/bx/inline/bitarray.inl index da10f6a..ef90d83 100644 --- a/include/bx/inline/bitarray.inl +++ b/include/bx/inline/bitarray.inl @@ -53,6 +53,118 @@ namespace bx return m_sizeInBytes * 8; } + inline BitArrayIterator BitArrayViewBase::begin() const + { + return BitArrayIterator(*this, findSet(0, m_count) ); + } + + inline BitArrayIterator BitArrayViewBase::end() const + { + return BitArrayIterator(*this, kInvalid); + } + + template + inline BitMaskToIndexIteratorT::BitMaskToIndexIteratorT(MaskT _mask) + : m_mask(0) + , m_idx(0) + { + const uint8_t ntz = countTrailingZeros(_mask); + m_mask = 0 == _mask ? MaskT(0) : MaskT(_mask >> ntz); + m_idx = ntz; + } + + template + inline void BitMaskToIndexIteratorT::next() + { + m_mask = MaskT(m_mask >> 1); + m_idx += 1; + + const uint8_t ntz = countTrailingZeros(m_mask); + m_mask = 0 == m_mask ? MaskT(0) : MaskT(m_mask >> ntz); + m_idx += ntz; + } + + template + inline bool BitMaskToIndexIteratorT::isDone() const + { + return 0 == m_mask; + } + + template + inline uint32_t BitMaskToIndexIteratorT::getIndex() const + { + return m_idx; + } + + template + inline MaskT BitMaskToIndexIteratorT::getMask() const + { + return m_mask; + } + + inline BitArrayIterator::BitArrayIterator(const BitArrayViewBase& _view, uint32_t _idx) + : m_view(_view) + , m_bits(0) + , m_word(0) + , m_idx(kInvalid) + { + load(_idx); + } + + inline uint32_t BitArrayIterator::operator*() const + { + return m_idx; + } + + inline bool BitArrayIterator::operator!=(const BitArrayIterator& _rhs) const + { + return m_idx != _rhs.m_idx; + } + + inline const BitArrayIterator& BitArrayIterator::operator++() + { + next(); + return *this; + } + + inline void BitArrayIterator::load(uint32_t _idx) + { + m_idx = _idx; + + if (kInvalid != _idx) + { + m_word = _idx/64; + + const uint64_t word = reinterpret_cast(m_view.getPtr() )[m_word]; + m_bits = BitMaskToIndexIteratorT(word & (~UINT64_C(0) << (_idx & 63) ) ); + } + } + + inline bool BitArrayIterator::next() + { + if (kInvalid == m_idx) + { + return false; + } + + const uint32_t count = m_view.getCount(); + + m_bits.next(); + + if (!m_bits.isDone() ) + { + const uint32_t idx = m_word*64 + m_bits.getIndex(); + m_idx = idx < count ? idx : kInvalid; + } + else + { + const uint32_t begin = (m_word + 1)*64; + load(begin < count ? m_view.findSet(begin, count) : kInvalid); + } + + return kInvalid != m_idx; + } + inline uint32_t BitArrayViewBase::find(uint32_t _numBits) const { return find(0, m_count, _numBits); diff --git a/tests/bitarray_test.cpp b/tests/bitarray_test.cpp index b1d4f84..93acb9a 100644 --- a/tests/bitarray_test.cpp +++ b/tests/bitarray_test.cpp @@ -1512,3 +1512,143 @@ TEST_CASE("MippedBitArray-random", "[container]") REQUIRE(expected == array.findClear(findBegin, kNumBits) ); } } + +TEST_CASE("BitArray-iterator", "[container]") +{ + constexpr uint32_t kNumWords = 40; + constexpr uint32_t kNumBits = kNumWords*64; + + uint64_t bits[kNumWords]; + bx::MutableBitArrayView bs(bits, sizeof(bits) ); + + bs.clear(); + + { + uint32_t numVisited = 0; + + for (const uint32_t idx : bs) + { + BX_UNUSED(idx); + ++numVisited; + } + + REQUIRE(0 == numVisited); + } + + bs.set(0, true); + bs.set(63, true); + bs.set(64, true); + bs.set(1000, true); + bs.set(kNumBits - 1, true); + + { + const uint32_t expected[] = { 0, 63, 64, 1000, kNumBits - 1 }; + + uint32_t numVisited = 0; + + for (const uint32_t idx : bs) + { + REQUIRE(numVisited < BX_COUNTOF(expected) ); + REQUIRE(expected[numVisited] == idx); + ++numVisited; + } + + REQUIRE(BX_COUNTOF(expected) == numVisited); + } + + bx::RngMwc rng; + + for (uint32_t iter = 0; iter < 1000; ++iter) + { + fillRandom(rng, bits, kNumWords); + + const uint32_t count = 1 + rng.gen() % kNumBits; + bx::MutableBitArrayView view(bits, sizeof(bits), count); + + uint32_t expected = view.findSet(0, count); + + for (const uint32_t idx : view) + { + REQUIRE(expected == idx); + REQUIRE(view.get(idx) ); + expected = view.findSet(idx + 1, count); + } + + REQUIRE(bx::kInvalid == expected); + } + + fillRandom(rng, bits, kNumWords); + + { + const uint32_t numSet = bs.countBits(); + + uint32_t numVisited = 0; + + for (const uint32_t idx : bs) + { + ++numVisited; + + if (0 == (idx & 1) ) + { + bs.set(idx, false); + } + } + + REQUIRE(numSet == numVisited); + + for (const uint32_t idx : bs) + { + REQUIRE(1 == (idx & 1) ); + bs.set(idx, false); + } + + REQUIRE(bs.testNone() ); + } +} + +template +static void testBitMaskToIndexIterator(MaskT _mask) +{ + uint32_t numVisited = 0; + uint32_t prevIdx = 0; + + for (bx::BitMaskToIndexIteratorT it(_mask); !it.isDone(); it.next() ) + { + const uint32_t idx = it.getIndex(); + + REQUIRE(idx < sizeof(MaskT)*8); + REQUIRE( (0 == numVisited || prevIdx < idx) ); + REQUIRE(0 != ( (_mask >> idx) & 1) ); + REQUIRE(1 == (it.getMask() & 1) ); + + prevIdx = idx; + ++numVisited; + } + + REQUIRE(bx::countBits(uint64_t(_mask) ) == numVisited); +} + +TEST_CASE("BitMaskToIndexIteratorT", "[container]") +{ + testBitMaskToIndexIterator(0); + testBitMaskToIndexIterator(1); + testBitMaskToIndexIterator(0x80); + testBitMaskToIndexIterator(0xff); + testBitMaskToIndexIterator(0xa5); + testBitMaskToIndexIterator(0x8001); + testBitMaskToIndexIterator(0); + testBitMaskToIndexIterator(0x80000001); + testBitMaskToIndexIterator(UINT32_MAX); + testBitMaskToIndexIterator(0); + testBitMaskToIndexIterator(1); + testBitMaskToIndexIterator(UINT64_C(0x8000000000000000) ); + testBitMaskToIndexIterator(UINT64_MAX); + + bx::RngMwc rng; + + for (uint32_t ii = 0; ii < 1000; ++ii) + { + testBitMaskToIndexIterator(rng.gen() ); + testBitMaskToIndexIterator( (uint64_t(rng.gen() ) << 32) | rng.gen() ); + } +}