// Code from https://github.com/genaray/Arch/blob/master/src/Arch/Core/Utils/BitSet.cs
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
namespace Ghost.Entities.Helpers;
// NOTE: Can this be replaced with `System.Collections.BitArray`?
// NOTE: If not, can it at least mirror that type's API?
///
/// The class
/// represents a resizable collection of bits.
///
public sealed class BitSet
{
private const int _BIT_SIZE = (sizeof(uint) * 8) - 1; // 31
private const int _INDEX_SIZE = 5; // log_2(BitSize + 1)
private static readonly int _padding = Vector.Count; // The padding used for vectorisation, the amount of uints required for being vectorized basically
///
/// Determines the required length of an to hold the passed id or bit.
///
/// The id or bit.
/// A size of required s for the bitset.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RequiredLength(int id)
{
return (id >> 5) + int.Sign(id & _BIT_SIZE);
}
///
/// The bits from the bitset.
///
private uint[] _bits;
/// TODO: Update on ClearBit, however clearbit is only used in tests so its fine for now.
///
/// The highest bit set.
///
private int _highestBit;
/// TODO: Update on ClearBit, probably remove in favor?
///
/// The maximum -index current in use.
///
private int _max;
///
/// Initializes a new instance of the class.
///
public BitSet()
{
_bits = new uint[_padding];
}
///
/// Initializes a new instance of the class.
///
public BitSet(params uint[] bits)
{
_bits = bits;
}
///
/// The highest uint index in use inside the -array.
///
public int HighestIndex
{
get => _max;
}
///
/// The highest bit set.
///
public int HighestBit
{
get => _highestBit;
}
///
/// Returns the length of the bitset, how many ints it consists of.
///
public int Length
{
get => _bits.Length;
}
///
/// 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 >> _INDEX_SIZE;
if (b >= _bits.Length)
{
return false;
}
return (_bits[b] & (1 << (index & _BIT_SIZE))) != 0;
}
///
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
///
/// The index.
public void SetBit(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
{
Array.Resize(ref _bits, (b + _padding) / _padding * _padding); // Round up to a multiply of Padding
}
// Track highest set bit
_highestBit = Math.Max(_highestBit, index);
_max = (_highestBit / (_BIT_SIZE + 1)) + 1;
_bits[b] |= 1u << (index & _BIT_SIZE);
}
///
/// Clears the bit at the given index.
///
/// The index.
public void ClearBit(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
{
return;
}
_bits[b] &= ~(1u << (index & _BIT_SIZE));
}
///
/// Sets all bits.
///
public void SetAll()
{
var count = _bits.Length;
for (var i = 0; i < count; i++)
{
_bits[i] = 0xffffffff;
}
_highestBit = (_bits.Length * (_BIT_SIZE + 1)) - 1;
_max = (_highestBit / (_BIT_SIZE + 1)) + 1;
}
///
/// Clears all set bits.
///
public void ClearAll()
{
Array.Clear(_bits, 0, _bits.Length);
}
///
/// Checks if all bits from this instance match those of the other instance.
///
/// The other .
/// True if they match, false if not.
[SkipLocalsInit]
public bool All(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < _padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise and
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit & otherBits[i]) != bit)
{
return false;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i++)
{
if (bits[i] != 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise and
for (var i = 0; i < min; i += _padding)
{
var vector = new Vector(_bits, i);
var otherVector = new Vector(other._bits, i);
var resultVector = Vector.BitwiseAnd(vector, otherVector);
if (!Vector.EqualsAll(resultVector, vector))
{
return false;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i += _padding)
{
var vector = new Vector(_bits, i);
if (!Vector.EqualsAll(vector, Vector.Zero)) // Vectors are not zero bits[0] != 0 basically
{
return false;
}
}
}
return true;
}
///
/// Checks if any bits from this instance match those of the other instance.
///
/// The other .
/// True if they match, false if not.
public bool Any(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < _padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise and, return true since any is met
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit & otherBits[i]) > 0)
{
return true;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i++)
{
if (bits[i] > 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise and, return true since any is met
for (var i = 0; i < min; i += _padding)
{
var vector = new Vector(_bits, i);
var otherVector = new Vector(other._bits, i);
var resultVector = Vector.BitwiseAnd(vector, otherVector);
if (!Vector.EqualsAll(resultVector, Vector.Zero))
{
return true;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i += _padding)
{
var vector = new Vector(_bits, i);
if (!Vector.EqualsAll(vector, Vector.Zero)) // Vectors are not zero bits[0] != 0 basically
{
return false;
}
}
}
return _highestBit <= 0;
}
///
/// Checks if none bits from this instance match those of the other instance.
///
/// The other .
/// True if none match, false if not.
public bool None(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < _padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise and, return true since any is met
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit & otherBits[i]) != 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise and, return true since any is met
for (var i = 0; i < min; i += _padding)
{
var vector = new Vector(_bits, i);
var otherVector = new Vector(other._bits, i);
var resultVector = Vector.BitwiseAnd(vector, otherVector);
if (!Vector.EqualsAll(resultVector, Vector.Zero))
{
return false;
}
}
}
return true;
}
///
/// Checks if exactly all bits from this instance match those of the other instance.
///
/// The other .
/// True if they match, false if not.
public bool Exclusive(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < _padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise xor, if both are not totally equal, return false
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit ^ otherBits[i]) != 0)
{
return false;
}
}
// handle extra bits on our side that might just be all zero
for (var i = min; i < _max; i++)
{
if (bits[i] != 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise xor, return true since any is met
for (var i = 0; i < min; i += _padding)
{
var vector = new Vector(_bits, i);
var otherVector = new Vector(other._bits, i);
var resultVector = Vector.Xor(vector, otherVector);
if (!Vector.EqualsAll(resultVector, Vector.Zero))
{
return false;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i += _padding)
{
var vector = new Vector(_bits, i);
if (!Vector.EqualsAll(vector, Vector.Zero)) // Vectors are not zero bits[0] != 0 basically
{
return false;
}
}
}
return true;
}
///
/// Creates a to access the .
///
/// The hash.
public Span AsSpan()
{
var max = (_highestBit / (_BIT_SIZE + 1)) + 1;
return _bits.AsSpan()[0..max];
}
///
/// Copies the bits into a and returns a slice containing the copied .
///
/// The to copy into.
/// If true, it will zero the unused space from the .
/// The .
public Span AsSpan(Span span, bool zero = true)
{
// Copy everything thats possible from one to another
var length = Math.Min(Length, span.Length);
for (var index = 0; index < length; index++)
{
span[index] = _bits[index];
}
// Zero the rest space which was not overriden due to the copy.
for (var index = length; zero && index < span.Length; index++)
{
span[index] = 0;
}
return span[0..length];
}
///
/// Calculates the hash, this is unique for the set bits. Two with the same set bits, result in the same hash.
///
/// The hash.
public override int GetHashCode()
{
return Component.GetHashCode(AsSpan());
}
///
/// Prints the content of this instance.
///
/// The string.
public override string ToString()
{
// Convert uint to binary form for pretty printing
var binaryBuilder = new StringBuilder();
foreach (var bit in _bits)
{
binaryBuilder.Append(Convert.ToString((uint)bit, 2).PadLeft(32, '0')).Append(',');
}
binaryBuilder.Length--;
return $"{nameof(_bits)}: {binaryBuilder}, {nameof(Length)}: {Length}";
}
}
///
/// The struct
/// represents a non resizable collection of bits.
/// Used to set, check and clear bits on a allocated or on the stack.
///
public readonly ref struct SpanBitSet
{
private const int BitSize = (sizeof(uint) * 8) - 1; // 31
// NOTE: Is a byte not 8 bits?
private const int ByteSize = 5; // log_2(BitSize + 1)
///
/// The bits from the bitset.
///
private readonly Span _bits;
///
/// Initializes a new instance of the class.
///
public SpanBitSet(Span bits)
{
_bits = bits;
}
///
/// 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 >> ByteSize;
if (b >= _bits.Length)
{
return false;
}
return (_bits[b] & (1 << (index & BitSize))) != 0;
}
///
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
///
/// The index.
public void SetBit(int index)
{
var b = index >> ByteSize;
if (b >= _bits.Length)
{
return;
}
_bits[b] |= 1u << (index & BitSize);
}
///
/// Clears the bit at the given index.
///
/// The index.
public void ClearBit(int index)
{
var b = index >> ByteSize;
if (b >= _bits.Length)
{
return;
}
_bits[b] &= ~(1u << (index & BitSize));
}
///
///
///
public void SetAll()
{
var count = _bits.Length;
for (var i = 0; i < count; i++)
{
_bits[i] = 0xffffffff;
}
}
///
/// Clears all set bits.
///
public void ClearAll()
{
_bits.Clear();
}
///
/// Creates a to access the .
///
/// The hash.
public Span AsSpan()
{
return _bits;
}
///
/// Copies the bits into a and returns a slice containing the copied .
///
///
/// The hash.
public Span AsSpan(Span span, bool zero = true)
{
// Prevent exception because target array is to small for copy operation
var length = Math.Min(this._bits.Length, span.Length);
for (var index = 0; index < length; index++)
{
span[index] = _bits[index];
}
// Zero the rest space which was not overriden due to the copy.
for (var index = length; zero && index < span.Length; index++)
{
span[index] = 0;
}
return span[.._bits.Length];
}
///
/// Calculates the hash, this is unique for the set bits. Two with the same set bits, result in the same hash.
///
/// The hash.
public override int GetHashCode()
{
return Component.GetHashCode(AsSpan());
}
///
/// Prints the content of this instance.
///
/// The string.
public override string ToString()
{
// Convert uint to binary form for pretty printing
var binaryBuilder = new StringBuilder();
foreach (var bit in _bits)
{
binaryBuilder.Append(Convert.ToString((uint)bit, 2).PadLeft(32, '0')).Append(',');
}
binaryBuilder.Length--;
return $"{nameof(_bits)}: {string.Join(",", binaryBuilder)}";
}
}