// 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)}"; } }