using System; using System.Diagnostics; using Unity.Mathematics; namespace Unity.Collections { [BurstCompatible] internal unsafe struct Bitwise { internal static int AlignDown(int value, int alignPow2) { return value & ~(alignPow2 - 1); } internal static int AlignUp(int value, int alignPow2) { return AlignDown(value + alignPow2 - 1, alignPow2); } internal static int FromBool(bool value) { return value ? 1 : 0; } // 32-bit uint internal static uint ExtractBits(uint input, int pos, uint mask) { var tmp0 = input >> pos; return tmp0 & mask; } internal static uint ReplaceBits(uint input, int pos, uint mask, uint value) { var tmp0 = (value & mask) << pos; var tmp1 = input & ~(mask << pos); return tmp0 | tmp1; } internal static uint SetBits(uint input, int pos, uint mask, bool value) { return ReplaceBits(input, pos, mask, (uint)-FromBool(value)); } // 64-bit ulong internal static ulong ExtractBits(ulong input, int pos, ulong mask) { var tmp0 = input >> pos; return tmp0 & mask; } internal static ulong ReplaceBits(ulong input, int pos, ulong mask, ulong value) { var tmp0 = (value & mask) << pos; var tmp1 = input & ~(mask << pos); return tmp0 | tmp1; } internal static ulong SetBits(ulong input, int pos, ulong mask, bool value) { return ReplaceBits(input, pos, mask, (ulong)-(long)FromBool(value)); } internal static int lzcnt(byte value) { return math.lzcnt((uint)value) - 24; } internal static int tzcnt(byte value) { return math.min(8, math.tzcnt((uint)value)); } internal static int lzcnt(ushort value) { return math.lzcnt((uint)value) - 16; } internal static int tzcnt(ushort value) { return math.min(16, math.tzcnt((uint)value)); } static int FindUlong(ulong* ptr, int beginBit, int endBit, int numBits) { var bits = ptr; var numSteps = (numBits + 63) >> 6; var numBitsPerStep = 64; var maxBits = numSteps * numBitsPerStep; for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i) { if (bits[i] != 0) { continue; } var idx = i * numBitsPerStep; var num = math.min(idx + numBitsPerStep, endBit) - idx; if (idx != beginBit) { var test = bits[idx / numBitsPerStep - 1]; var newIdx = math.max(idx - math.lzcnt(test), beginBit); num += idx - newIdx; idx = newIdx; } for (++i; i < end; ++i) { if (num >= numBits) { return idx; } var test = bits[i]; var pos = i * numBitsPerStep; num += math.min(pos + math.tzcnt(test), endBit) - pos; if (test != 0) { break; } } if (num >= numBits) { return idx; } } return endBit; } static int FindUint(ulong* ptr, int beginBit, int endBit, int numBits) { var bits = (uint*)ptr; var numSteps = (numBits + 31) >> 5; var numBitsPerStep = 32; var maxBits = numSteps * numBitsPerStep; for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i) { if (bits[i] != 0) { continue; } var idx = i * numBitsPerStep; var num = math.min(idx + numBitsPerStep, endBit) - idx; if (idx != beginBit) { var test = bits[idx / numBitsPerStep - 1]; var newIdx = math.max(idx - math.lzcnt(test), beginBit); num += idx - newIdx; idx = newIdx; } for (++i; i < end; ++i) { if (num >= numBits) { return idx; } var test = bits[i]; var pos = i * numBitsPerStep; num += math.min(pos + math.tzcnt(test), endBit) - pos; if (test != 0) { break; } } if (num >= numBits) { return idx; } } return endBit; } static int FindUshort(ulong* ptr, int beginBit, int endBit, int numBits) { var bits = (ushort*)ptr; var numSteps = (numBits + 15) >> 4; var numBitsPerStep = 16; var maxBits = numSteps * numBitsPerStep; for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i) { if (bits[i] != 0) { continue; } var idx = i * numBitsPerStep; var num = math.min(idx + numBitsPerStep, endBit) - idx; if (idx != beginBit) { var test = bits[idx / numBitsPerStep - 1]; var newIdx = math.max(idx - lzcnt(test), beginBit); num += idx - newIdx; idx = newIdx; } for (++i; i < end; ++i) { if (num >= numBits) { return idx; } var test = bits[i]; var pos = i * numBitsPerStep; num += math.min(pos + tzcnt(test), endBit) - pos; if (test != 0) { break; } } if (num >= numBits) { return idx; } } return endBit; } static int FindByte(ulong* ptr, int beginBit, int endBit, int numBits) { var bits = (byte*)ptr; var numSteps = (numBits + 7) >> 3; var numBitsPerStep = 8; var maxBits = numSteps * numBitsPerStep; for (int i = beginBit / numBitsPerStep, end = AlignUp(endBit, numBitsPerStep) / numBitsPerStep; i < end; ++i) { if (bits[i] != 0) { continue; } var idx = i * numBitsPerStep; var num = math.min(idx + numBitsPerStep, endBit) - idx; if (idx != beginBit) { var test = bits[idx / numBitsPerStep - 1]; var newIdx = math.max(idx - lzcnt(test), beginBit); num += idx - newIdx; idx = newIdx; } for (++i; i < end; ++i) { if (num >= numBits) { return idx; } var test = bits[i]; var pos = i * numBitsPerStep; num += math.min(pos + tzcnt(test), endBit) - pos; if (test != 0) { break; } } if (num >= numBits) { return idx; } } return endBit; } static int FindUpto14bits(ulong* ptr, int beginBit, int endBit, int numBits) { var bits = (byte*)ptr; var bit = (byte)(beginBit & 7); byte beginMask = (byte)~(0xff << bit); var lz = 0; for (int begin = beginBit / 8, end = AlignUp(endBit, 8) / 8, i = begin; i < end; ++i) { var test = bits[i]; test |= i == begin ? beginMask : (byte)0; if (test == 0xff) { continue; } var pos = i * 8; var tz = math.min(pos + tzcnt(test), endBit) - pos; if (lz + tz >= numBits) { return pos - lz; } lz = lzcnt(test); var idx = pos + 8; var newIdx = math.max(idx - lz, beginBit); lz = math.min(idx, endBit) - newIdx; if (lz >= numBits) { return newIdx; } } return endBit; } static int FindUpto6bits(ulong* ptr, int beginBit, int endBit, int numBits) { var bits = (byte*)ptr; byte beginMask = (byte)~(0xff << (beginBit & 7)); byte endMask = (byte)~(0xff >> ((8-(endBit & 7) & 7))); var mask = 1 << numBits - 1; for (int begin = beginBit / 8, end = AlignUp(endBit, 8) / 8, i = begin; i < end; ++i) { var test = bits[i]; test |= i == begin ? beginMask : (byte)0; test |= i == end - 1 ? endMask : (byte)0; if (test == 0xff) { continue; } for (int pos = i * 8, posEnd = pos + 7; pos < posEnd; ++pos) { var tz = tzcnt((byte)(test^0xff)); test >>= tz; pos += tz; if ((test & mask) == 0) { return pos; } test >>= 1; } } return endBit; } internal static int FindWithBeginEnd(ulong* ptr, int beginBit, int endBit, int numBits) { int idx; if (numBits >= 127) { idx = FindUlong(ptr, beginBit, endBit, numBits); if (idx != endBit) { return idx; } } if (numBits >= 63) { idx = FindUint(ptr, beginBit, endBit, numBits); if (idx != endBit) { return idx; } } if (numBits >= 128) { // early out - no smaller step will find this gap return int.MaxValue; } if (numBits >= 31) { idx = FindUshort(ptr, beginBit, endBit, numBits); if (idx != endBit) { return idx; } } if (numBits >= 64) { // early out - no smaller step will find this gap return int.MaxValue; } idx = FindByte(ptr, beginBit, endBit, numBits); if (idx != endBit) { return idx; } if (numBits < 15) { idx = FindUpto14bits(ptr, beginBit, endBit, numBits); if (idx != endBit) { return idx; } if (numBits < 7) { // The worst case scenario when every byte boundary bit is set (pattern 0x81), // and we're looking for 6 or less bits. It will rescan byte-by-byte to find // any inner byte gap. idx = FindUpto6bits(ptr, beginBit, endBit, numBits); if (idx != endBit) { return idx; } } } return int.MaxValue; } internal static int Find(ulong* ptr, int pos, int count, int numBits) => FindWithBeginEnd(ptr, pos, pos + count, numBits); } /// /// A 32-bit array of bits. /// /// /// Stack allocated, so it does not require thread safety checks or disposal. /// [DebuggerTypeProxy(typeof(BitField32DebugView))] [BurstCompatible] public struct BitField32 { /// /// The 32 bits, stored as a uint. /// /// The 32 bits, stored as a uint. public uint Value; /// /// Initializes and returns an instance of BitField32. /// /// Initial value of the bit field. Default is 0. public BitField32(uint initialValue = 0u) { Value = initialValue; } /// /// Clears all the bits to 0. /// public void Clear() { Value = 0u; } /// /// Sets a single bit to 1 or 0. /// /// Position in this bit field to set (must be 0-31). /// If true, sets the bit to 1. If false, sets the bit to 0. /// Thrown if `pos`is out of range. public void SetBits(int pos, bool value) { CheckArgs(pos, 1); Value = Bitwise.SetBits(Value, pos, 1, value); } /// /// Sets one or more contiguous bits to 1 or 0. /// /// Position in the bit field of the first bit to set (must be 0-31). /// If true, sets the bits to 1. If false, sets the bits to 0. /// Number of bits to set (must be 1-32). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32. public void SetBits(int pos, bool value, int numBits) { CheckArgs(pos, numBits); var mask = 0xffffffffu >> (32 - numBits); Value = Bitwise.SetBits(Value, pos, mask, value); } /// /// Returns one or more contiguous bits from the bit field as the lower bits of a uint. /// /// Position in the bit field of the first bit to get (must be 0-31). /// Number of bits to get (must be 1-32). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32. /// The requested range of bits from the bit field stored in the least-significant bits of a uint. All other bits of the uint will be 0. public uint GetBits(int pos, int numBits = 1) { CheckArgs(pos, numBits); var mask = 0xffffffffu >> (32 - numBits); return Bitwise.ExtractBits(Value, pos, mask); } /// /// Returns true if the bit at a position is 1. /// /// Position in the bit field (must be 0-31). /// True if the bit at the position is 1. public bool IsSet(int pos) { return 0 != GetBits(pos); } /// /// Returns true if none of the bits in a contiguous range are 1. /// /// Position in the bit field (must be 0-31). /// Number of bits to test (must be 1-32). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32. /// True if none of the bits in the contiguous range are 1. public bool TestNone(int pos, int numBits = 1) { return 0u == GetBits(pos, numBits); } /// /// Returns true if any of the bits in a contiguous range are 1. /// /// Position in the bit field (must be 0-31). /// Number of bits to test (must be 1-32). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32. /// True if at least one bit in the contiguous range is 1. public bool TestAny(int pos, int numBits = 1) { return 0u != GetBits(pos, numBits); } /// /// Returns true if all of the bits in a contiguous range are 1. /// /// Position in the bit field (must be 0-31). /// Number of bits to test (must be 1-32). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 32. /// True if all bits in the contiguous range are 1. public bool TestAll(int pos, int numBits = 1) { CheckArgs(pos, numBits); var mask = 0xffffffffu >> (32 - numBits); return mask == Bitwise.ExtractBits(Value, pos, mask); } /// /// Returns the number of bits that are 1. /// /// The number of bits that are 1. public int CountBits() { return math.countbits(Value); } /// /// Returns the number of leading zeroes. /// /// The number of leading zeros. public int CountLeadingZeros() { return math.lzcnt(Value); } /// /// Returns the number of trailing zeros. /// /// The number of trailing zeros. public int CountTrailingZeros() { return math.tzcnt(Value); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void CheckArgs(int pos, int numBits) { if (pos > 31 || numBits == 0 || numBits > 32 || pos + numBits > 32) { throw new ArgumentException($"BitField32 invalid arguments: pos {pos} (must be 0-31), numBits {numBits} (must be 1-32)."); } } } sealed class BitField32DebugView { BitField32 BitField; public BitField32DebugView(BitField32 bitfield) { BitField = bitfield; } public bool[] Bits { get { var array = new bool[32]; for (int i = 0; i < 32; ++i) { array[i] = BitField.IsSet(i); } return array; } } } /// /// A 64-bit array of bits. /// /// /// Stack allocated, so it does not require thread safety checks or disposal. /// [DebuggerTypeProxy(typeof(BitField64DebugView))] [BurstCompatible] public struct BitField64 { /// /// The 64 bits, stored as a ulong. /// /// The 64 bits, stored as a uint. public ulong Value; /// /// Initializes and returns an instance of BitField64. /// /// Initial value of the bit field. Default is 0. public BitField64(ulong initialValue = 0ul) { Value = initialValue; } /// /// Clears all bits to 0. /// public void Clear() { Value = 0ul; } /// /// Sets a single bit to 1 or 0. /// /// Position in this bit field to set (must be 0-63). /// If true, sets the bit to 1. If false, sets the bit to 0. /// Thrown if `pos`is out of range. public void SetBits(int pos, bool value) { CheckArgs(pos, 1); Value = Bitwise.SetBits(Value, pos, 1, value); } /// /// Sets one or more contiguous bits to 1 or 0. /// /// Position in the bit field of the first bit to set (must be 0-63). /// If true, sets the bits to 1. If false, sets the bits to 0. /// Number of bits to set (must be 1-64). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64. public void SetBits(int pos, bool value, int numBits = 1) { CheckArgs(pos, numBits); var mask = 0xfffffffffffffffful >> (64 - numBits); Value = Bitwise.SetBits(Value, pos, mask, value); } /// /// Returns one or more contiguous bits from the bit field as the lower bits of a ulong. /// /// Position in the bit field of the first bit to get (must be 0-63). /// Number of bits to get (must be 1-64). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64. /// The requested range of bits from the bit field stored in the least-significant bits of a ulong. All other bits of the ulong will be 0. public ulong GetBits(int pos, int numBits = 1) { CheckArgs(pos, numBits); var mask = 0xfffffffffffffffful >> (64 - numBits); return Bitwise.ExtractBits(Value, pos, mask); } /// /// Returns true if the bit at a position is 1. /// /// Position in the bit field (must be 0-63). /// True if the bit at the position is 1. public bool IsSet(int pos) { return 0ul != GetBits(pos); } /// /// Returns true if none of the bits in a contiguous range are 1. /// /// Position in the bit field (must be 0-63). /// Number of bits to test (must be 1-64). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64. /// True if none of the bits in the contiguous range are 1. public bool TestNone(int pos, int numBits = 1) { return 0ul == GetBits(pos, numBits); } /// /// Returns true if any of the bits in a contiguous range are 1. /// /// Position in the bit field (must be 0-63). /// Number of bits to test (must be 1-64). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64. /// True if at least one bit in the contiguous range is 1. public bool TestAny(int pos, int numBits = 1) { return 0ul != GetBits(pos, numBits); } /// /// Returns true if all of the bits in a contiguous range are 1. /// /// Position in the bit field (must be 0-63). /// Number of bits to test (must be 1-64). /// Thrown if `pos` or `numBits` are out of bounds or if `pos + numBits` exceeds 64. /// True if all bits in the contiguous range are 1. public bool TestAll(int pos, int numBits = 1) { CheckArgs(pos, numBits); var mask = 0xfffffffffffffffful >> (64 - numBits); return mask == Bitwise.ExtractBits(Value, pos, mask); } /// /// Returns the number of bits that are 1. /// /// The number of bits that are 1. public int CountBits() { return math.countbits(Value); } /// /// Returns the number of leading zeroes. /// /// The number of leading zeros. public int CountLeadingZeros() { return math.lzcnt(Value); } /// /// Returns the number of trailing zeros. /// /// The number of trailing zeros. public int CountTrailingZeros() { return math.tzcnt(Value); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void CheckArgs(int pos, int numBits) { if (pos > 63 || numBits == 0 || numBits > 64 || pos + numBits > 64) { throw new ArgumentException($"BitField32 invalid arguments: pos {pos} (must be 0-63), numBits {numBits} (must be 1-64)."); } } } sealed class BitField64DebugView { BitField64 Data; public BitField64DebugView(BitField64 data) { Data = data; } public bool[] Bits { get { var array = new bool[64]; for (int i = 0; i < 64; ++i) { array[i] = Data.IsSet(i); } return array; } } } }