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.
This commit is contained in:
2025-12-05 16:36:02 +09:00
parent c152e4383d
commit 4dd5d6f1c6
4 changed files with 92 additions and 10 deletions

View File

@@ -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<uint>.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<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically
private UnsafeArray<uint> _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);
}
/// <summary>
/// Checks whether a bit is set at the index.
/// </summary>
@@ -717,6 +741,25 @@ public unsafe struct UnsafeBitSet : IDisposable
/// </summary>
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);
}
/// <summary>
/// Checks whether a bit is set at the index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>True if it is, otherwise false</returns>
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.
/// </summary>
/// <param name="index">The index.</param>
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.
/// </summary>
/// <param name="index">The index.</param>
public void ClearBit(int index)
{
var b = index >> _BYTE_SIZE;
@@ -787,7 +832,6 @@ public readonly ref struct SpanBitSet
/// <summary>
/// Sets all bits.
/// </summary>
public void SetAll()
{
var count = _bits.Length;
@@ -800,12 +844,40 @@ public readonly ref struct SpanBitSet
/// <summary>
/// Clears all set bits.
/// </summary>
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];
}
}
/// <summary>
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// </summary>

View File

@@ -91,6 +91,10 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
_freeSlots = new UnsafeQueue<int>(capacity, ref handle, allocationOption);
_validBits = new UnsafeBitSet(GetBitSetCapacity(capacity), ref handle, allocationOption);
if (!allocationOption.HasFlag(AllocationOption.Clear))
{
_generations.AsSpan().Clear();
}
_validBits.ClearAll();
_count = 0;

View File

@@ -94,6 +94,12 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_freeSparse = new UnsafeStack<int>(capacity, ref handle, allocationOption);
if (!allocationOption.HasFlag(AllocationOption.Clear))
{
_generations.AsSpan().Clear();
_sparse.AsSpan().Clear();
}
_count = 0;
_nextId = 0;
_capacity = capacity;

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Authors>Misaki</Authors>
<AssemblyVersion>1.2.6</AssemblyVersion>
<AssemblyVersion>1.2.7</AssemblyVersion>
<Version>$(AssemblyVersion)</Version>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>