A game about forced loneliness, made by TACStudios
at master 876 lines 37 kB view raw
1using System; 2using System.Diagnostics; 3using Unity.Jobs; 4using Unity.Mathematics; 5using System.Runtime.InteropServices; 6 7namespace Unity.Collections.LowLevel.Unsafe 8{ 9 /// <summary> 10 /// An arbitrarily-sized array of bits. 11 /// </summary> 12 /// <remarks> 13 /// The number of allocated bytes is always a multiple of 8. For example, a 65-bit array could fit in 9 bytes, but its allocation is actually 16 bytes. 14 /// </remarks> 15 [DebuggerDisplay("Length = {Length}, IsCreated = {IsCreated}")] 16 [DebuggerTypeProxy(typeof(UnsafeBitArrayDebugView))] 17 [GenerateTestsForBurstCompatibility] 18 [StructLayout(LayoutKind.Sequential)] 19 public unsafe struct UnsafeBitArray 20 : INativeDisposable 21 { 22 /// <summary> 23 /// Pointer to the data. 24 /// </summary> 25 /// <value>Pointer to the data.</value> 26 [NativeDisableUnsafePtrRestriction] 27 public ulong* Ptr; 28 29 /// <summary> 30 /// The number of bits. 31 /// </summary> 32 /// <value>The number of bits.</value> 33 public int Length; 34 35 /// <summary> 36 /// The capacity number of bits. 37 /// </summary> 38 /// <value>The capacity number of bits.</value> 39 public int Capacity; 40 41 /// <summary> 42 /// The allocator to use. 43 /// </summary> 44 /// <value>The allocator to use.</value> 45 public AllocatorManager.AllocatorHandle Allocator; 46 47 /// <summary> 48 /// Initializes and returns an instance of UnsafeBitArray which aliases an existing buffer. 49 /// </summary> 50 /// <param name="ptr">An existing buffer.</param> 51 /// <param name="allocator">The allocator that was used to allocate the bytes. Needed to dispose this array.</param> 52 /// <param name="sizeInBytes">The number of bytes. The length will be `sizeInBytes * 8`.</param> 53 public unsafe UnsafeBitArray(void* ptr, int sizeInBytes, AllocatorManager.AllocatorHandle allocator = new AllocatorManager.AllocatorHandle()) 54 { 55 CheckSizeMultipleOf8(sizeInBytes); 56 Ptr = (ulong*)ptr; 57 Length = sizeInBytes * 8; 58 Capacity = sizeInBytes * 8; 59 Allocator = allocator; 60 } 61 62 /// <summary> 63 /// Initializes and returns an instance of UnsafeBitArray. 64 /// </summary> 65 /// <param name="numBits">Number of bits.</param> 66 /// <param name="allocator">The allocator to use.</param> 67 /// <param name="options">Whether newly allocated bytes should be zeroed out.</param> 68 public UnsafeBitArray(int numBits, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) 69 { 70 CollectionHelper.CheckAllocator(allocator); 71 Allocator = allocator; 72 73 Ptr = null; 74 Length = 0; 75 Capacity = 0; 76 77 Resize(numBits, options); 78 } 79 80 internal static UnsafeBitArray* Alloc(AllocatorManager.AllocatorHandle allocator) 81 { 82 UnsafeBitArray* data = (UnsafeBitArray*)Memory.Unmanaged.Allocate(sizeof(UnsafeBitArray), UnsafeUtility.AlignOf<UnsafeBitArray>(), allocator); 83 return data; 84 } 85 86 internal static void Free(UnsafeBitArray* data, AllocatorManager.AllocatorHandle allocator) 87 { 88 if (data == null) 89 { 90 throw new InvalidOperationException("UnsafeBitArray has yet to be created or has been destroyed!"); 91 } 92 data->Dispose(); 93 Memory.Unmanaged.Free(data, allocator); 94 } 95 96 /// <summary> 97 /// Whether this array has been allocated (and not yet deallocated). 98 /// </summary> 99 /// <value>True if this array has been allocated (and not yet deallocated).</value> 100 public readonly bool IsCreated => Ptr != null; 101 102 /// <summary> 103 /// Whether the container is empty. 104 /// </summary> 105 /// <value>True if the container is empty or the container has not been constructed.</value> 106 public readonly bool IsEmpty => !IsCreated || Length == 0; 107 108 void Realloc(int capacityInBits) 109 { 110 var newCapacity = Bitwise.AlignUp(capacityInBits, 64); 111 var sizeInBytes = newCapacity / 8; 112 113 ulong* newPointer = null; 114 115 if (sizeInBytes > 0) 116 { 117 newPointer = (ulong*)Memory.Unmanaged.Allocate(sizeInBytes, 16, Allocator); 118 119 if (Capacity > 0) 120 { 121 var itemsToCopy = math.min(newCapacity, Capacity); 122 var bytesToCopy = itemsToCopy / 8; 123 UnsafeUtility.MemCpy(newPointer, Ptr, bytesToCopy); 124 } 125 } 126 127 Memory.Unmanaged.Free(Ptr, Allocator); 128 129 Ptr = newPointer; 130 Capacity = newCapacity; 131 Length = math.min(Length, newCapacity); 132 } 133 134 /// <summary> 135 /// Sets the length, expanding the capacity if necessary. 136 /// </summary> 137 /// <param name="numBits">The new length in bits.</param> 138 /// <param name="options">Whether newly allocated data should be zeroed out.</param> 139 public void Resize(int numBits, NativeArrayOptions options = NativeArrayOptions.UninitializedMemory) 140 { 141 CollectionHelper.CheckAllocator(Allocator); 142 143 var minCapacity = math.max(numBits, 1); 144 145 if (minCapacity > Capacity) 146 { 147 SetCapacity(minCapacity); 148 } 149 150 var oldLength = Length; 151 Length = numBits; 152 153 if (options == NativeArrayOptions.ClearMemory && oldLength < Length) 154 { 155 SetBits(oldLength, false, Length - oldLength); 156 } 157 } 158 159 /// <summary> 160 /// Sets the capacity. 161 /// </summary> 162 /// <param name="capacityInBits">The new capacity.</param> 163 public void SetCapacity(int capacityInBits) 164 { 165 CollectionHelper.CheckCapacityInRange(capacityInBits, Length); 166 167 if (Capacity == capacityInBits) 168 { 169 return; 170 } 171 172 Realloc(capacityInBits); 173 } 174 175 /// <summary> 176 /// Sets the capacity to match what it would be if it had been originally initialized with all its entries. 177 /// </summary> 178 public void TrimExcess() 179 { 180 SetCapacity(Length); 181 } 182 183 /// <summary> 184 /// Releases all resources (memory and safety handles). 185 /// </summary> 186 public void Dispose() 187 { 188 if (!IsCreated) 189 { 190 return; 191 } 192 193 if (CollectionHelper.ShouldDeallocate(Allocator)) 194 { 195 Memory.Unmanaged.Free(Ptr, Allocator); 196 Allocator = AllocatorManager.Invalid; 197 } 198 199 Ptr = null; 200 Length = 0; 201 } 202 203 /// <summary> 204 /// Creates and schedules a job that will dispose this array. 205 /// </summary> 206 /// <param name="inputDeps">The handle of a job which the new job will depend upon.</param> 207 /// <returns>The handle of a new job that will dispose this array. The new job depends upon inputDeps.</returns> 208 public JobHandle Dispose(JobHandle inputDeps) 209 { 210 if (!IsCreated) 211 { 212 return inputDeps; 213 } 214 215 if (CollectionHelper.ShouldDeallocate(Allocator)) 216 { 217 var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps); 218 219 Ptr = null; 220 Allocator = AllocatorManager.Invalid; 221 222 return jobHandle; 223 } 224 225 Ptr = null; 226 227 return inputDeps; 228 } 229 230 /// <summary> 231 /// Sets all the bits to 0. 232 /// </summary> 233 public void Clear() 234 { 235 var sizeInBytes = Bitwise.AlignUp(Length, 64) / 8; 236 UnsafeUtility.MemClear(Ptr, sizeInBytes); 237 } 238 239 /// <summary> 240 /// Sets the bit at an index to 0 or 1. 241 /// </summary> 242 /// <param name="ptr">pointer to the bit buffer</param> 243 /// <param name="pos">Index of the bit to set.</param> 244 /// <param name="value">True for 1, false for 0.</param> 245 public static void Set(ulong* ptr, int pos, bool value) 246 { 247 var idx = pos >> 6; 248 var shift = pos & 0x3f; 249 var mask = 1ul << shift; 250 var bits = (ptr[idx] & ~mask) | ((ulong)-Bitwise.FromBool(value) & mask); 251 ptr[idx] = bits; 252 } 253 254 /// <summary> 255 /// Sets the bit at an index to 0 or 1. 256 /// </summary> 257 /// <param name="pos">Index of the bit to set.</param> 258 /// <param name="value">True for 1, false for 0.</param> 259 public void Set(int pos, bool value) 260 { 261 CheckArgs(pos, 1); 262 Set(Ptr, pos, value); 263 } 264 265 /// <summary> 266 /// Sets a range of bits to 0 or 1. 267 /// </summary> 268 /// <remarks> 269 /// The range runs from index `pos` up to (but not including) `pos + numBits`. 270 /// No exception is thrown if `pos + numBits` exceeds the length. 271 /// </remarks> 272 /// <param name="pos">Index of the first bit to set.</param> 273 /// <param name="value">True for 1, false for 0.</param> 274 /// <param name="numBits">Number of bits to set.</param> 275 /// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is less than 1.</exception> 276 public void SetBits(int pos, bool value, int numBits) 277 { 278 CheckArgs(pos, numBits); 279 280 var end = math.min(pos + numBits, Length); 281 var idxB = pos >> 6; 282 var shiftB = pos & 0x3f; 283 var idxE = (end - 1) >> 6; 284 var shiftE = end & 0x3f; 285 var maskB = 0xfffffffffffffffful << shiftB; 286 var maskE = 0xfffffffffffffffful >> (64 - shiftE); 287 var orBits = (ulong)-Bitwise.FromBool(value); 288 var orBitsB = maskB & orBits; 289 var orBitsE = maskE & orBits; 290 var cmaskB = ~maskB; 291 var cmaskE = ~maskE; 292 293 if (idxB == idxE) 294 { 295 var maskBE = maskB & maskE; 296 var cmaskBE = ~maskBE; 297 var orBitsBE = orBitsB & orBitsE; 298 Ptr[idxB] = (Ptr[idxB] & cmaskBE) | orBitsBE; 299 return; 300 } 301 302 Ptr[idxB] = (Ptr[idxB] & cmaskB) | orBitsB; 303 304 for (var idx = idxB + 1; idx < idxE; ++idx) 305 { 306 Ptr[idx] = orBits; 307 } 308 309 Ptr[idxE] = (Ptr[idxE] & cmaskE) | orBitsE; 310 } 311 312 /// <summary> 313 /// Copies bits of a ulong to bits in this array. 314 /// </summary> 315 /// <remarks> 316 /// The destination bits in this array run from index `pos` up to (but not including) `pos + numBits`. 317 /// No exception is thrown if `pos + numBits` exceeds the length. 318 /// 319 /// The lowest bit of the ulong is copied to the first destination bit; the second-lowest bit of the ulong is 320 /// copied to the second destination bit; and so forth. 321 /// </remarks> 322 /// <param name="pos">Index of the first bit to set.</param> 323 /// <param name="value">Unsigned long from which to copy bits.</param> 324 /// <param name="numBits">Number of bits to set (must be between 1 and 64).</param> 325 /// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is not between 1 and 64.</exception> 326 public void SetBits(int pos, ulong value, int numBits = 1) 327 { 328 CheckArgsUlong(pos, numBits); 329 330 var idxB = pos >> 6; 331 var shiftB = pos & 0x3f; 332 333 if (shiftB + numBits <= 64) 334 { 335 var mask = 0xfffffffffffffffful >> (64 - numBits); 336 Ptr[idxB] = Bitwise.ReplaceBits(Ptr[idxB], shiftB, mask, value); 337 338 return; 339 } 340 341 var end = math.min(pos + numBits, Length); 342 var idxE = (end - 1) >> 6; 343 var shiftE = end & 0x3f; 344 345 var maskB = 0xfffffffffffffffful >> shiftB; 346 Ptr[idxB] = Bitwise.ReplaceBits(Ptr[idxB], shiftB, maskB, value); 347 348 var valueE = value >> (64 - shiftB); 349 var maskE = 0xfffffffffffffffful >> (64 - shiftE); 350 Ptr[idxE] = Bitwise.ReplaceBits(Ptr[idxE], 0, maskE, valueE); 351 } 352 353 /// <summary> 354 /// Returns a ulong which has bits copied from this array. 355 /// </summary> 356 /// <remarks> 357 /// The source bits in this array run from index `pos` up to (but not including) `pos + numBits`. 358 /// No exception is thrown if `pos + numBits` exceeds the length. 359 /// 360 /// The first source bit is copied to the lowest bit of the ulong; the second source bit is copied to the second-lowest bit of the ulong; and so forth. Any remaining bits in the ulong will be 0. 361 /// </remarks> 362 /// <param name="pos">Index of the first bit to get.</param> 363 /// <param name="numBits">Number of bits to get (must be between 1 and 64).</param> 364 /// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is not between 1 and 64.</exception> 365 /// <returns>A ulong which has bits copied from this array.</returns> 366 public ulong GetBits(int pos, int numBits = 1) 367 { 368 CheckArgsUlong(pos, numBits); 369 return Bitwise.GetBits(Ptr, Length, pos, numBits); 370 } 371 372 /// <summary> 373 /// Returns true if the bit at an index is 1. 374 /// </summary> 375 /// <param name="pos">Index of the bit to test.</param> 376 /// <returns>True if the bit at the index is 1.</returns> 377 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds.</exception> 378 public bool IsSet(int pos) 379 { 380 CheckArgs(pos, 1); 381 return Bitwise.IsSet(Ptr, pos); 382 } 383 384 internal void CopyUlong(int dstPos, ref UnsafeBitArray srcBitArray, int srcPos, int numBits) => SetBits(dstPos, srcBitArray.GetBits(srcPos, numBits), numBits); 385 386 /// <summary> 387 /// Copies a range of bits from this array to another range in this array. 388 /// </summary> 389 /// <remarks> 390 /// The bits to copy run from index `srcPos` up to (but not including) `srcPos + numBits`. 391 /// The bits to set run from index `dstPos` up to (but not including) `dstPos + numBits`. 392 /// 393 /// The ranges may overlap, but the result in the overlapping region is undefined. 394 /// </remarks> 395 /// <param name="dstPos">Index of the first bit to set.</param> 396 /// <param name="srcPos">Index of the first bit to copy.</param> 397 /// <param name="numBits">Number of bits to copy.</param> 398 /// <exception cref="ArgumentException">Thrown if either `dstPos + numBits` or `srcPos + numBits` exceed the length of this array.</exception> 399 public void Copy(int dstPos, int srcPos, int numBits) 400 { 401 if (dstPos == srcPos) 402 { 403 return; 404 } 405 406 Copy(dstPos, ref this, srcPos, numBits); 407 } 408 409 /// <summary> 410 /// Copies a range of bits from an array to a range of bits in this array. 411 /// </summary> 412 /// <remarks> 413 /// The bits to copy in the source array run from index srcPos up to (but not including) `srcPos + numBits`. 414 /// The bits to set in the destination array run from index dstPos up to (but not including) `dstPos + numBits`. 415 /// 416 /// It's fine if source and destination array are one and the same, even if the ranges overlap, but the result in the overlapping region is undefined. 417 /// </remarks> 418 /// <param name="dstPos">Index of the first bit to set.</param> 419 /// <param name="srcBitArray">The source array.</param> 420 /// <param name="srcPos">Index of the first bit to copy.</param> 421 /// <param name="numBits">The number of bits to copy.</param> 422 /// <exception cref="ArgumentException">Thrown if either `dstPos + numBits` or `srcBitArray + numBits` exceed the length of this array.</exception> 423 public void Copy(int dstPos, ref UnsafeBitArray srcBitArray, int srcPos, int numBits) 424 { 425 if (numBits == 0) 426 { 427 return; 428 } 429 430 CheckArgsCopy(ref this, dstPos, ref srcBitArray, srcPos, numBits); 431 432 if (numBits <= 64) // 1x CopyUlong 433 { 434 CopyUlong(dstPos, ref srcBitArray, srcPos, numBits); 435 } 436 else if (numBits <= 128) // 2x CopyUlong 437 { 438 CopyUlong(dstPos, ref srcBitArray, srcPos, 64); 439 numBits -= 64; 440 441 if (numBits > 0) 442 { 443 CopyUlong(dstPos + 64, ref srcBitArray, srcPos + 64, numBits); 444 } 445 } 446 else if ((dstPos & 7) == (srcPos & 7)) // aligned copy 447 { 448 var dstPosInBytes = CollectionHelper.Align(dstPos, 8) >> 3; 449 var srcPosInBytes = CollectionHelper.Align(srcPos, 8) >> 3; 450 var numPreBits = dstPosInBytes * 8 - dstPos; 451 452 if (numPreBits > 0) 453 { 454 CopyUlong(dstPos, ref srcBitArray, srcPos, numPreBits); 455 } 456 457 var numBitsLeft = numBits - numPreBits; 458 var numBytes = numBitsLeft / 8; 459 460 if (numBytes > 0) 461 { 462 unsafe 463 { 464 UnsafeUtility.MemMove((byte*)Ptr + dstPosInBytes, (byte*)srcBitArray.Ptr + srcPosInBytes, numBytes); 465 } 466 } 467 468 var numPostBits = numBitsLeft & 7; 469 470 if (numPostBits > 0) 471 { 472 CopyUlong((dstPosInBytes + numBytes) * 8, ref srcBitArray, (srcPosInBytes + numBytes) * 8, numPostBits); 473 } 474 } 475 else // unaligned copy 476 { 477 var dstPosAligned = CollectionHelper.Align(dstPos, 64); 478 var numPreBits = dstPosAligned - dstPos; 479 480 if (numPreBits > 0) 481 { 482 CopyUlong(dstPos, ref srcBitArray, srcPos, numPreBits); 483 numBits -= numPreBits; 484 dstPos += numPreBits; 485 srcPos += numPreBits; 486 } 487 488 for (; numBits >= 64; numBits -= 64, dstPos += 64, srcPos += 64) 489 { 490 Ptr[dstPos >> 6] = srcBitArray.GetBits(srcPos, 64); 491 } 492 493 if (numBits > 0) 494 { 495 CopyUlong(dstPos, ref srcBitArray, srcPos, numBits); 496 } 497 } 498 } 499 500 /// <summary> 501 /// Returns the index of the first occurrence in this array of *N* contiguous 0 bits. 502 /// </summary> 503 /// <remarks>The search is linear.</remarks> 504 /// <param name="pos">Index of the bit at which to start searching.</param> 505 /// <param name="numBits">Number of contiguous 0 bits to look for.</param> 506 /// <returns>The index of the first occurrence in this array of `numBits` contiguous 0 bits. Range is pos up to (but not including) the length of this array. Returns -1 if no occurrence is found.</returns> 507 public int Find(int pos, int numBits) 508 { 509 var count = Length - pos; 510 CheckArgsPosCount(pos, count, numBits); 511 return Bitwise.Find(Ptr, pos, count, numBits); 512 } 513 514 /// <summary> 515 /// Returns the index of the first occurrence in this array of a given number of contiguous 0 bits. 516 /// </summary> 517 /// <remarks>The search is linear.</remarks> 518 /// <param name="pos">Index of the bit at which to start searching.</param> 519 /// <param name="numBits">Number of contiguous 0 bits to look for.</param> 520 /// <param name="count">Number of indexes to consider as the return value.</param> 521 /// <returns>The index of the first occurrence in this array of `numBits` contiguous 0 bits. Range is pos up to (but not including) `pos + count`. Returns -1 if no occurrence is found.</returns> 522 public int Find(int pos, int count, int numBits) 523 { 524 CheckArgsPosCount(pos, count, numBits); 525 return Bitwise.Find(Ptr, pos, count, numBits); 526 } 527 528 /// <summary> 529 /// Returns true if none of the bits in a range are 1 (*i.e.* all bits in the range are 0). 530 /// </summary> 531 /// <param name="pos">Index of the bit at which to start searching.</param> 532 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 533 /// <returns>Returns true if none of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns> 534 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 535 public bool TestNone(int pos, int numBits = 1) 536 { 537 CheckArgs(pos, numBits); 538 return Bitwise.TestNone(Ptr, Length, pos, numBits); 539 } 540 541 /// <summary> 542 /// Returns true if at least one of the bits in a range is 1. 543 /// </summary> 544 /// <param name="pos">Index of the bit at which to start searching.</param> 545 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 546 /// <returns>True if one or more of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns> 547 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 548 public bool TestAny(int pos, int numBits = 1) 549 { 550 CheckArgs(pos, numBits); 551 return Bitwise.TestAny(Ptr, Length, pos, numBits); 552 } 553 554 /// <summary> 555 /// Returns true if all of the bits in a range are 1. 556 /// </summary> 557 /// <param name="pos">Index of the bit at which to start searching.</param> 558 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 559 /// <returns>True if all of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns> 560 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 561 public bool TestAll(int pos, int numBits = 1) 562 { 563 CheckArgs(pos, numBits); 564 return Bitwise.TestAll(Ptr, Length, pos, numBits); 565 } 566 567 /// <summary> 568 /// Returns the number of bits in a range that are 1. 569 /// </summary> 570 /// <param name="pos">Index of the bit at which to start searching.</param> 571 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 572 /// <returns>The number of bits in a range of bits that are 1.</returns> 573 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 574 public int CountBits(int pos, int numBits = 1) 575 { 576 CheckArgs(pos, numBits); 577 return Bitwise.CountBits(Ptr, Length, pos, numBits); 578 } 579 580 /// <summary> 581 /// Returns a readonly version of this UnsafeBitArray instance. 582 /// </summary> 583 /// <remarks>ReadOnly containers point to the same underlying data as the UnsafeBitArray it is made from.</remarks> 584 /// <returns>ReadOnly instance for this.</returns> 585 public ReadOnly AsReadOnly() 586 { 587 return new ReadOnly(Ptr, Length); 588 } 589 590 /// <summary> 591 /// A read-only alias for the value of a UnsafeBitArray. Does not have its own allocated storage. 592 /// </summary> 593 public struct ReadOnly 594 { 595 /// <summary> 596 /// Pointer to the data. 597 /// </summary> 598 /// <value>Pointer to the data.</value> 599 [NativeDisableUnsafePtrRestriction] 600 public readonly ulong* Ptr; 601 602 /// <summary> 603 /// The number of bits. 604 /// </summary> 605 /// <value>The number of bits.</value> 606 public readonly int Length; 607 608 /// <summary> 609 /// Whether this array has been allocated (and not yet deallocated). 610 /// </summary> 611 /// <value>True if this array has been allocated (and not yet deallocated).</value> 612 public readonly bool IsCreated => Ptr != null; 613 614 /// <summary> 615 /// Whether the container is empty. 616 /// </summary> 617 /// <value>True if the container is empty or the container has not been constructed.</value> 618 public readonly bool IsEmpty => !IsCreated || Length == 0; 619 620 internal ReadOnly(ulong* ptr, int length) 621 { 622 Ptr = ptr; 623 Length = length; 624 } 625 626 /// <summary> 627 /// Returns a ulong which has bits copied from this array. 628 /// </summary> 629 /// <remarks> 630 /// The source bits in this array run from index `pos` up to (but not including) `pos + numBits`. 631 /// No exception is thrown if `pos + numBits` exceeds the length. 632 /// 633 /// The first source bit is copied to the lowest bit of the ulong; the second source bit is copied to the second-lowest bit of the ulong; and so forth. Any remaining bits in the ulong will be 0. 634 /// </remarks> 635 /// <param name="pos">Index of the first bit to get.</param> 636 /// <param name="numBits">Number of bits to get (must be between 1 and 64).</param> 637 /// <exception cref="ArgumentException">Thrown if pos is out of bounds or if numBits is not between 1 and 64.</exception> 638 /// <returns>A ulong which has bits copied from this array.</returns> 639 public readonly ulong GetBits(int pos, int numBits = 1) 640 { 641 CheckArgsUlong(pos, numBits); 642 return Bitwise.GetBits(Ptr, Length, pos, numBits); 643 } 644 645 /// <summary> 646 /// Returns true if the bit at an index is 1. 647 /// </summary> 648 /// <param name="pos">Index of the bit to test.</param> 649 /// <returns>True if the bit at the index is 1.</returns> 650 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds.</exception> 651 public readonly bool IsSet(int pos) 652 { 653 CheckArgs(pos, 1); 654 return Bitwise.IsSet(Ptr, pos); 655 } 656 657 /// <summary> 658 /// Returns the index of the first occurrence in this array of *N* contiguous 0 bits. 659 /// </summary> 660 /// <remarks>The search is linear.</remarks> 661 /// <param name="pos">Index of the bit at which to start searching.</param> 662 /// <param name="numBits">Number of contiguous 0 bits to look for.</param> 663 /// <returns>The index of the first occurrence in this array of `numBits` contiguous 0 bits. Range is pos up to (but not including) the length of this array. Returns -1 if no occurrence is found.</returns> 664 public readonly int Find(int pos, int numBits) 665 { 666 var count = Length - pos; 667 CheckArgsPosCount(pos, count, numBits); 668 return Bitwise.Find(Ptr, pos, count, numBits); 669 } 670 671 /// <summary> 672 /// Returns the index of the first occurrence in this array of a given number of contiguous 0 bits. 673 /// </summary> 674 /// <remarks>The search is linear.</remarks> 675 /// <param name="pos">Index of the bit at which to start searching.</param> 676 /// <param name="numBits">Number of contiguous 0 bits to look for.</param> 677 /// <param name="count">Number of indexes to consider as the return value.</param> 678 /// <returns>The index of the first occurrence in this array of `numBits` contiguous 0 bits. Range is pos up to (but not including) `pos + count`. Returns -1 if no occurrence is found.</returns> 679 public readonly int Find(int pos, int count, int numBits) 680 { 681 CheckArgsPosCount(pos, count, numBits); 682 return Bitwise.Find(Ptr, pos, count, numBits); 683 } 684 685 /// <summary> 686 /// Returns true if none of the bits in a range are 1 (*i.e.* all bits in the range are 0). 687 /// </summary> 688 /// <param name="pos">Index of the bit at which to start searching.</param> 689 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 690 /// <returns>Returns true if none of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns> 691 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 692 public readonly bool TestNone(int pos, int numBits = 1) 693 { 694 CheckArgs(pos, numBits); 695 return Bitwise.TestNone(Ptr, pos, numBits); 696 } 697 698 /// <summary> 699 /// Returns true if at least one of the bits in a range is 1. 700 /// </summary> 701 /// <param name="pos">Index of the bit at which to start searching.</param> 702 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 703 /// <returns>True if one or more of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns> 704 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 705 public readonly bool TestAny(int pos, int numBits = 1) 706 { 707 CheckArgs(pos, numBits); 708 return Bitwise.TestAny(Ptr, Length, pos, numBits); 709 } 710 711 /// <summary> 712 /// Returns true if all of the bits in a range are 1. 713 /// </summary> 714 /// <param name="pos">Index of the bit at which to start searching.</param> 715 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 716 /// <returns>True if all of the bits in range `pos` up to (but not including) `pos + numBits` are 1.</returns> 717 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 718 public readonly bool TestAll(int pos, int numBits = 1) 719 { 720 CheckArgs(pos, numBits); 721 return Bitwise.TestAll(Ptr, Length, pos, numBits); 722 } 723 724 /// <summary> 725 /// Returns the number of bits in a range that are 1. 726 /// </summary> 727 /// <param name="pos">Index of the bit at which to start searching.</param> 728 /// <param name="numBits">Number of bits to test. Defaults to 1.</param> 729 /// <returns>The number of bits in a range of bits that are 1.</returns> 730 /// <exception cref="ArgumentException">Thrown if `pos` is out of bounds or `numBits` is less than 1.</exception> 731 public readonly int CountBits(int pos, int numBits = 1) 732 { 733 CheckArgs(pos, numBits); 734 return Bitwise.CountBits(Ptr, Length, pos, numBits); 735 } 736 737 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 738 readonly void CheckArgs(int pos, int numBits) 739 { 740 if (pos < 0 741 || pos >= Length 742 || numBits < 1) 743 { 744 throw new ArgumentException($"BitArray invalid arguments: pos {pos} (must be 0-{Length - 1}), numBits {numBits} (must be greater than 0)."); 745 } 746 } 747 748 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 749 readonly void CheckArgsPosCount(int begin, int count, int numBits) 750 { 751 if (begin < 0 || begin >= Length) 752 { 753 throw new ArgumentException($"BitArray invalid argument: begin {begin} (must be 0-{Length - 1})."); 754 } 755 756 if (count < 0 || count > Length) 757 { 758 throw new ArgumentException($"BitArray invalid argument: count {count} (must be 0-{Length})."); 759 } 760 761 if (numBits < 1 || count < numBits) 762 { 763 throw new ArgumentException($"BitArray invalid argument: numBits {numBits} (must be greater than 0)."); 764 } 765 } 766 767 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 768 readonly void CheckArgsUlong(int pos, int numBits) 769 { 770 CheckArgs(pos, numBits); 771 772 if (numBits < 1 || numBits > 64) 773 { 774 throw new ArgumentException($"BitArray invalid arguments: numBits {numBits} (must be 1-64)."); 775 } 776 777 if (pos + numBits > Length) 778 { 779 throw new ArgumentException($"BitArray invalid arguments: Out of bounds pos {pos}, numBits {numBits}, Length {Length}."); 780 } 781 } 782 } 783 784 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 785 static void CheckSizeMultipleOf8(int sizeInBytes) 786 { 787 if ((sizeInBytes & 7) != 0) 788 { 789 throw new ArgumentException($"BitArray invalid arguments: sizeInBytes {sizeInBytes} (must be multiple of 8-bytes, sizeInBytes: {sizeInBytes})."); 790 } 791 } 792 793 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 794 void CheckArgs(int pos, int numBits) 795 { 796 if (pos < 0 797 || pos >= Length 798 || numBits < 1) 799 { 800 throw new ArgumentException($"BitArray invalid arguments: pos {pos} (must be 0-{Length - 1}), numBits {numBits} (must be greater than 0)."); 801 } 802 } 803 804 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 805 void CheckArgsPosCount(int begin, int count, int numBits) 806 { 807 if (begin < 0 || begin >= Length) 808 { 809 throw new ArgumentException($"BitArray invalid argument: begin {begin} (must be 0-{Length - 1})."); 810 } 811 812 if (count < 0 || count > Length) 813 { 814 throw new ArgumentException($"BitArray invalid argument: count {count} (must be 0-{Length})."); 815 } 816 817 if (numBits < 1 || count < numBits) 818 { 819 throw new ArgumentException($"BitArray invalid argument: numBits {numBits} (must be greater than 0)."); 820 } 821 } 822 823 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 824 void CheckArgsUlong(int pos, int numBits) 825 { 826 CheckArgs(pos, numBits); 827 828 if (numBits < 1 || numBits > 64) 829 { 830 throw new ArgumentException($"BitArray invalid arguments: numBits {numBits} (must be 1-64)."); 831 } 832 833 if (pos + numBits > Length) 834 { 835 throw new ArgumentException($"BitArray invalid arguments: Out of bounds pos {pos}, numBits {numBits}, Length {Length}."); 836 } 837 } 838 839 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 840 static void CheckArgsCopy(ref UnsafeBitArray dstBitArray, int dstPos, ref UnsafeBitArray srcBitArray, int srcPos, int numBits) 841 { 842 if (srcPos + numBits > srcBitArray.Length) 843 { 844 throw new ArgumentException($"BitArray invalid arguments: Out of bounds - source position {srcPos}, numBits {numBits}, source bit array Length {srcBitArray.Length}."); 845 } 846 847 if (dstPos + numBits > dstBitArray.Length) 848 { 849 throw new ArgumentException($"BitArray invalid arguments: Out of bounds - destination position {dstPos}, numBits {numBits}, destination bit array Length {dstBitArray.Length}."); 850 } 851 } 852 } 853 854 sealed class UnsafeBitArrayDebugView 855 { 856 UnsafeBitArray Data; 857 858 public UnsafeBitArrayDebugView(UnsafeBitArray data) 859 { 860 Data = data; 861 } 862 863 public bool[] Bits 864 { 865 get 866 { 867 var array = new bool[Data.Length]; 868 for (int i = 0; i < Data.Length; ++i) 869 { 870 array[i] = Data.IsSet(i); 871 } 872 return array; 873 } 874 } 875 } 876}