A game about forced loneliness, made by TACStudios
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}