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 public unsafe struct UnsafeBitSet : IDisposable
{ {
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 public ref struct Iterator
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 UnsafeBitSet _bitSet;
private static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically 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 UnsafeArray<uint> _bits;
private int _highestBit; private int _highestBit;
@@ -97,6 +116,11 @@ public unsafe struct UnsafeBitSet : IDisposable
return (id >> _INDEX_SIZE) + int.Sign(id & _BIT_SIZE); return (id >> _INDEX_SIZE) + int.Sign(id & _BIT_SIZE);
} }
public readonly Iterator GetIterator()
{
return new Iterator(this);
}
/// <summary> /// <summary>
/// Checks whether a bit is set at the index. /// Checks whether a bit is set at the index.
/// </summary> /// </summary>
@@ -717,6 +741,25 @@ public unsafe struct UnsafeBitSet : IDisposable
/// </summary> /// </summary>
public readonly ref struct SpanBitSet 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 private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31
// NOTE: Is a byte not 8 bits? // NOTE: Is a byte not 8 bits?
private const int _BYTE_SIZE = 5; // log_2(BitSize + 1) private const int _BYTE_SIZE = 5; // log_2(BitSize + 1)
@@ -734,12 +777,16 @@ public readonly ref struct SpanBitSet
_bits = bits; _bits = bits;
} }
public readonly Iterator GetIterator()
{
return new Iterator(this);
}
/// <summary> /// <summary>
/// Checks whether a bit is set at the index. /// Checks whether a bit is set at the index.
/// </summary> /// </summary>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
/// <returns>True if it is, otherwise false</returns> /// <returns>True if it is, otherwise false</returns>
public bool IsSet(int index) public bool IsSet(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> _BYTE_SIZE;
@@ -756,7 +803,6 @@ public readonly ref struct SpanBitSet
/// Resizes its internal array if necessary. /// Resizes its internal array if necessary.
/// </summary> /// </summary>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
public void SetBit(int index) public void SetBit(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> _BYTE_SIZE;
@@ -772,7 +818,6 @@ public readonly ref struct SpanBitSet
/// Clears the bit at the given index. /// Clears the bit at the given index.
/// </summary> /// </summary>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
public void ClearBit(int index) public void ClearBit(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> _BYTE_SIZE;
@@ -787,7 +832,6 @@ public readonly ref struct SpanBitSet
/// <summary> /// <summary>
/// Sets all bits. /// Sets all bits.
/// </summary> /// </summary>
public void SetAll() public void SetAll()
{ {
var count = _bits.Length; var count = _bits.Length;
@@ -800,12 +844,40 @@ public readonly ref struct SpanBitSet
/// <summary> /// <summary>
/// Clears all set bits. /// Clears all set bits.
/// </summary> /// </summary>
public void ClearAll() public void ClearAll()
{ {
_bits.Clear(); _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> /// <summary>
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>. /// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// </summary> /// </summary>

View File

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

View File

@@ -94,6 +94,12 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption); _reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_freeSparse = new UnsafeStack<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; _count = 0;
_nextId = 0; _nextId = 0;
_capacity = capacity; _capacity = capacity;

View File

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