From 4dd5d6f1c60346c3d75794c0835c5235fdfcb796 Mon Sep 17 00:00:00 2001 From: Misaki Date: Fri, 5 Dec 2025 16:36:02 +0900 Subject: [PATCH] Add iterators to UnsafeBitSet and SpanBitSet Introduced `Iterator` structs in `UnsafeBitSet` and `SpanBitSet` to enable efficient traversal of set bits. Added `GetIterator` methods to both structs to return iterator instances. Implemented `NextSetBit` in `SpanBitSet` to support iterator functionality. Changed constants in `UnsafeBitSet` from `private` to `internal` for broader assembly access. Removed redundant methods from `SpanBitSet` to streamline the API in favor of iterator-based operations. Updated constructors in `UnsafeSlotMap` and `UnsafeSparseSet` to conditionally clear arrays based on `AllocationOption.Clear`. Incremented assembly version to 1.2.7 to reflect these updates. --- .../Collections/UnsafeBitSet.cs | 90 +++++++++++++++++-- .../Collections/UnsafeSlotMap.cs | 4 + .../Collections/UnsafeSparseSet.cs | 6 ++ .../Misaki.HighPerformance.LowLevel.csproj | 2 +- 4 files changed, 92 insertions(+), 10 deletions(-) diff --git a/Misaki.HighPerformance.LowLevel/Collections/UnsafeBitSet.cs b/Misaki.HighPerformance.LowLevel/Collections/UnsafeBitSet.cs index d71e89a..915ccf4 100644 --- a/Misaki.HighPerformance.LowLevel/Collections/UnsafeBitSet.cs +++ b/Misaki.HighPerformance.LowLevel/Collections/UnsafeBitSet.cs @@ -10,10 +10,29 @@ namespace Misaki.HighPerformance.LowLevel.Collections; public unsafe struct UnsafeBitSet : IDisposable { - private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 - private const int _INDEX_SIZE = 5; // log_2(BitSize + 1) - private const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint - private static readonly int s_padding = Vector.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically + public ref struct Iterator + { + private UnsafeBitSet _bitSet; + private int _currentBit; + + public Iterator (UnsafeBitSet bitSet) + { + _bitSet = bitSet; + _currentBit = -1; + } + + public bool Next(out int bitIndex) + { + _currentBit = _bitSet.NextSetBit(_currentBit + 1); + bitIndex = _currentBit; + return _currentBit != -1; + } + } + + internal const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 + internal const int _INDEX_SIZE = 5; // log_2(BitSize + 1) + internal const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint + internal static readonly int s_padding = Vector.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically private UnsafeArray _bits; private int _highestBit; @@ -97,6 +116,11 @@ public unsafe struct UnsafeBitSet : IDisposable return (id >> _INDEX_SIZE) + int.Sign(id & _BIT_SIZE); } + public readonly Iterator GetIterator() + { + return new Iterator(this); + } + /// /// Checks whether a bit is set at the index. /// @@ -717,6 +741,25 @@ public unsafe struct UnsafeBitSet : IDisposable /// public readonly ref struct SpanBitSet { + public ref struct Iterator + { + private SpanBitSet _bitSet; + private int _currentBit; + + public Iterator(SpanBitSet bitSet) + { + _bitSet = bitSet; + _currentBit = -1; + } + + public bool Next(out int bitIndex) + { + _currentBit = _bitSet.NextSetBit(_currentBit + 1); + bitIndex = _currentBit; + return _currentBit != -1; + } + } + private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 // NOTE: Is a byte not 8 bits? private const int _BYTE_SIZE = 5; // log_2(BitSize + 1) @@ -734,12 +777,16 @@ public readonly ref struct SpanBitSet _bits = bits; } + public readonly Iterator GetIterator() + { + return new Iterator(this); + } + /// /// Checks whether a bit is set at the index. /// /// The index. /// True if it is, otherwise false - public bool IsSet(int index) { var b = index >> _BYTE_SIZE; @@ -756,7 +803,6 @@ public readonly ref struct SpanBitSet /// Resizes its internal array if necessary. /// /// The index. - public void SetBit(int index) { var b = index >> _BYTE_SIZE; @@ -772,7 +818,6 @@ public readonly ref struct SpanBitSet /// Clears the bit at the given index. /// /// The index. - public void ClearBit(int index) { var b = index >> _BYTE_SIZE; @@ -787,7 +832,6 @@ public readonly ref struct SpanBitSet /// /// Sets all bits. /// - public void SetAll() { var count = _bits.Length; @@ -800,12 +844,40 @@ public readonly ref struct SpanBitSet /// /// Clears all set bits. /// - public void ClearAll() { _bits.Clear(); } + public int NextSetBit(int startIndex) + { + var wordIndex = startIndex >> _BIT_SIZE; + if (wordIndex >= _bits.Length) + { + return -1; + } + + // Mask off bits below startIndex in the first word: + var word = _bits[wordIndex] & ~0u << (startIndex & UnsafeBitSet._MASK); + while (true) + { + if (word != 0) + { + // get the least-significant set bit + var bit = BitOperations.TrailingZeroCount(word); + return (wordIndex << _BIT_SIZE) + bit; + } + + wordIndex++; + if (wordIndex >= _bits.Length) + { + return -1; + } + + word = _bits[wordIndex]; + } + } + /// /// Creates a to access the . /// diff --git a/Misaki.HighPerformance.LowLevel/Collections/UnsafeSlotMap.cs b/Misaki.HighPerformance.LowLevel/Collections/UnsafeSlotMap.cs index 025f672..a7bab81 100644 --- a/Misaki.HighPerformance.LowLevel/Collections/UnsafeSlotMap.cs +++ b/Misaki.HighPerformance.LowLevel/Collections/UnsafeSlotMap.cs @@ -91,6 +91,10 @@ public unsafe struct UnsafeSlotMap : IUnsafeCollection _freeSlots = new UnsafeQueue(capacity, ref handle, allocationOption); _validBits = new UnsafeBitSet(GetBitSetCapacity(capacity), ref handle, allocationOption); + if (!allocationOption.HasFlag(AllocationOption.Clear)) + { + _generations.AsSpan().Clear(); + } _validBits.ClearAll(); _count = 0; diff --git a/Misaki.HighPerformance.LowLevel/Collections/UnsafeSparseSet.cs b/Misaki.HighPerformance.LowLevel/Collections/UnsafeSparseSet.cs index 8abcacd..b3feba9 100644 --- a/Misaki.HighPerformance.LowLevel/Collections/UnsafeSparseSet.cs +++ b/Misaki.HighPerformance.LowLevel/Collections/UnsafeSparseSet.cs @@ -94,6 +94,12 @@ public unsafe struct UnsafeSparseSet : IUnsafeCollection _reverse = new UnsafeArray(capacity, ref handle, allocationOption); _freeSparse = new UnsafeStack(capacity, ref handle, allocationOption); + if (!allocationOption.HasFlag(AllocationOption.Clear)) + { + _generations.AsSpan().Clear(); + _sparse.AsSpan().Clear(); + } + _count = 0; _nextId = 0; _capacity = capacity; diff --git a/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj b/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj index b0e80cc..21ba561 100644 --- a/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj +++ b/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj @@ -6,7 +6,7 @@ enable True Misaki - 1.2.6 + 1.2.7 $(AssemblyVersion) True https://git.personalnas.com/Misaki/Misaki.HighPerformance.git