using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine.Assertions;
namespace Pathfinding.Util {
///
/// Thread-safe hierarchical bitset.
///
/// Stores an array of bits. Each bit can be set or cleared individually from any thread.
///
/// Note: Setting the capacity is not thread-safe, nor is iterating over the bitset while it is being modified.
///
[BurstCompile]
public struct HierarchicalBitset {
UnsafeSpan l1;
UnsafeSpan l2;
UnsafeSpan l3;
Allocator allocator;
const int Log64 = 6;
public HierarchicalBitset (int size, Allocator allocator) {
this.allocator = allocator;
l1 = new UnsafeSpan(allocator, (size + 64 - 1) >> Log64);
l2 = new UnsafeSpan(allocator, (size + (64*64 - 1)) >> Log64 >> Log64);
l3 = new UnsafeSpan(allocator, (size + (64*64*64 - 1)) >> Log64 >> Log64 >> Log64);
l1.FillZeros();
l2.FillZeros();
l3.FillZeros();
}
public bool IsCreated => Capacity > 0;
public void Dispose () {
l1.Free(allocator);
l2.Free(allocator);
l3.Free(allocator);
this = default;
}
public int Capacity {
get {
return l1.Length << Log64;
}
set {
if (value < Capacity) throw new System.ArgumentException("Shrinking the bitset is not supported");
if (value == Capacity) return;
var b = new HierarchicalBitset(value, allocator);
// Copy the old data
l1.CopyTo(b.l1);
l2.CopyTo(b.l2);
l3.CopyTo(b.l3);
Dispose();
this = b;
}
}
/// Number of set bits in the bitset
public int Count () {
int count = 0;
for (int i = 0; i < l1.Length; i++) {
count += math.countbits(l1[i]);
}
return count;
}
/// True if the bitset is empty
public bool IsEmpty {
get {
for (int i = 0; i < l3.Length; i++) {
if (l3[i] != 0) return false;
}
return true;
}
}
/// Clear all bits
public void Clear () {
// TODO: Optimize?
l1.FillZeros();
l2.FillZeros();
l3.FillZeros();
}
public void GetIndices (NativeList result) {
var buffer = new NativeArray(256, Allocator.Temp);
var iter = GetIterator(buffer.AsUnsafeSpan());
while (iter.MoveNext()) {
var span = iter.Current;
for (int i = 0; i < span.Length; i++) {
result.Add(span[i]);
}
}
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
static bool SetAtomic (ref UnsafeSpan span, int index) {
var cellIndex = index >> Log64;
var currentValue = span[cellIndex];
// Note: 1 << index will only use the lower 6 bits of index
if ((currentValue & (1UL << index)) != 0) {
// Bit already set
return true;
}
// TODO: Use Interlocked.Or in newer .net versions
while (true) {
var actualValue = (ulong)System.Threading.Interlocked.CompareExchange(ref UnsafeUtility.As(ref span[cellIndex]), (long)(currentValue | (1UL << index)), (long)currentValue);
if (actualValue != currentValue) currentValue = actualValue;
else break;
}
return false;
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
static bool ResetAtomic (ref UnsafeSpan span, int index) {
var cellIndex = index >> Log64;
var currentValue = span[cellIndex];
// Note: 1 << index will only use the lower 6 bits of index
if ((currentValue & (1UL << index)) == 0) {
// Bit already cleared
return true;
}
// TODO: Use Interlocked.Or in newer .net versions
while (true) {
var actualValue = (ulong)System.Threading.Interlocked.CompareExchange(ref UnsafeUtility.As(ref span[cellIndex]), (long)(currentValue & ~(1UL << index)), (long)currentValue);
if (actualValue != currentValue) currentValue = actualValue;
else break;
}
return false;
}
/// Get the value of a bit
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool Get (int index) {
// Note: 1 << index will only use the lower 6 bits of index
return (l1[index >> Log64] & (1UL << index)) != 0;
}
/// Set a given bit to 1
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Set (int index) {
if (SetAtomic(ref l1, index)) return;
SetAtomic(ref l2, index >> Log64);
SetAtomic(ref l3, index >> (2*Log64));
}
/// Set a given bit to 0
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void Reset (int index) {
if (ResetAtomic(ref l1, index)) return;
if (l1[index >> Log64] == 0) ResetAtomic(ref l2, index >> Log64);
if (l2[index >> (2*Log64)] == 0) ResetAtomic(ref l3, index >> (2*Log64));
}
/// Get an iterator over all set bits.
/// A buffer to use for temporary storage. A slice of this buffer will be returned on each iteration, filled with the indices of the set bits.
public Iterator GetIterator (UnsafeSpan scratchBuffer) {
return new Iterator(this, scratchBuffer);
}
[BurstCompile]
public struct Iterator : IEnumerator >, IEnumerable > {
HierarchicalBitset bitSet;
UnsafeSpan result;
int resultCount;
int l3index;
int l3bitIndex;
int l2bitIndex;
public UnsafeSpan Current => result.Slice(0, resultCount);
object IEnumerator.Current => throw new System.NotImplementedException();
public void Reset() => throw new System.NotImplementedException();
public void Dispose () {}
public IEnumerator > GetEnumerator() => this;
IEnumerator IEnumerable.GetEnumerator() => throw new System.NotImplementedException();
static int l2index(int l3index, int l3bitIndex) => (l3index << Log64) + l3bitIndex;
static int l1index(int l2index, int l2bitIndex) => (l2index << Log64) + l2bitIndex;
public Iterator (HierarchicalBitset bitSet, UnsafeSpan result) {
this.bitSet = bitSet;
this.result = result;
resultCount = 0;
l3index = 0;
l3bitIndex = 0;
l2bitIndex = 0;
if (result.Length < 128) {
// Minimum is actually 64, but that can be very inefficient
throw new System.ArgumentException("Result array must be at least 128 elements long");
}
}
public bool MoveNext () {
return MoveNextBurst(ref this);
}
[BurstCompile]
public static bool MoveNextBurst (ref Iterator iter) {
return iter.MoveNextInternal();
}
// Inline
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
bool MoveNextInternal () {
// Store various data in local variables to avoid writing them to memory every time they are updated
uint resultCount = 0;
int l3index = this.l3index;
int l3bitIndex = this.l3bitIndex;
int l2bitIndex = this.l2bitIndex;
Assert.IsTrue(l2bitIndex < 64 && l3bitIndex < 64);
for (; l3index < bitSet.l3.length; l3index++) {
// Get the L3 cell, and mask out all bits we have already visited
var l3cell = bitSet.l3[l3index] & (~0UL << l3bitIndex);
if (l3cell == 0) continue;
while (l3cell != 0) {
// Find the next set bit in the L3 cell
l3bitIndex = math.tzcnt(l3cell);
// Nest check for level 2
int l2index = Iterator.l2index(l3index, l3bitIndex);
// The l2 cell is guaranteed to be non-zero, even after masking out the bits we have already visited
var l2cell = bitSet.l2[l2index] & (~0UL << l2bitIndex);
Assert.AreNotEqual(0, l2cell);
while (l2cell != 0) {
l2bitIndex = math.tzcnt(l2cell);
// Stop the loop if we have almost filled the result array
// Each L1 cell may contain up to 64 set bits
if (resultCount + 64 > result.Length) {
this.resultCount = (int)resultCount;
this.l3index = l3index;
this.l3bitIndex = l3bitIndex;
this.l2bitIndex = l2bitIndex;
return true;
}
int l1index = Iterator.l1index(l2index, l2bitIndex);
var l1cell = bitSet.l1[l1index];
int l1indexStart = l1index << Log64;
Assert.AreNotEqual(0, l1cell);
while (l1cell != 0) {
var l1bitIndex = math.tzcnt(l1cell);
l1cell &= l1cell - 1UL; // clear lowest bit
int index = l1indexStart + l1bitIndex;
Unity.Burst.CompilerServices.Hint.Assume(resultCount < (uint)result.Length);
result[resultCount++] = index;
}
l2cell &= l2cell - 1UL;
}
// Skip a bit at the L3 level
l3cell &= l3cell - 1UL; // clear lowest bit
// Enter new L2 level
l2bitIndex = 0;
}
l2bitIndex = 0;
l3bitIndex = 0;
}
this.resultCount = (int)resultCount;
this.l3index = l3index;
this.l3bitIndex = l3bitIndex;
this.l2bitIndex = l2bitIndex;
return resultCount > 0;
}
}
}
}