No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

UnsafeBitArray.cs 36KB

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