Sin descripción
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.

UnsafeAppendBuffer.cs 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. using System;
  2. using System.Diagnostics;
  3. using Unity.Jobs;
  4. using Unity.Mathematics;
  5. namespace Unity.Collections.LowLevel.Unsafe
  6. {
  7. /// <summary>
  8. /// An unmanaged, untyped, heterogeneous buffer.
  9. /// </summary>
  10. /// <remarks>
  11. /// The values written to an individual append buffer can be of different types.
  12. /// </remarks>
  13. [BurstCompatible]
  14. public unsafe struct UnsafeAppendBuffer
  15. : INativeDisposable
  16. {
  17. /// <summary>
  18. /// The internal buffer where the content is stored.
  19. /// </summary>
  20. /// <value>The internal buffer where the content is stored.</value>
  21. [NativeDisableUnsafePtrRestriction]
  22. public byte* Ptr;
  23. /// <summary>
  24. /// The size in bytes of the currently-used portion of the internal buffer.
  25. /// </summary>
  26. /// <value>The size in bytes of the currently-used portion of the internal buffer.</value>
  27. public int Length;
  28. /// <summary>
  29. /// The size in bytes of the internal buffer.
  30. /// </summary>
  31. /// <value>The size in bytes of the internal buffer.</value>
  32. public int Capacity;
  33. /// <summary>
  34. /// The allocator used to create the internal buffer.
  35. /// </summary>
  36. /// <value>The allocator used to create the internal buffer.</value>
  37. public AllocatorManager.AllocatorHandle Allocator;
  38. /// <summary>
  39. /// The byte alignment used when allocating the internal buffer.
  40. /// </summary>
  41. /// <value>The byte alignment used when allocating the internal buffer. Is always a non-zero power of 2.</value>
  42. public readonly int Alignment;
  43. /// <summary>
  44. /// Initializes and returns an instance of UnsafeAppendBuffer.
  45. /// </summary>
  46. /// <param name="initialCapacity">The initial allocation size in bytes of the internal buffer.</param>
  47. /// <param name="alignment">The byte alignment of the allocation. Must be a non-zero power of 2.</param>
  48. /// <param name="allocator">The allocator to use.</param>
  49. public UnsafeAppendBuffer(int initialCapacity, int alignment, AllocatorManager.AllocatorHandle allocator)
  50. {
  51. CheckAlignment(alignment);
  52. Alignment = alignment;
  53. Allocator = allocator;
  54. Ptr = null;
  55. Length = 0;
  56. Capacity = 0;
  57. SetCapacity(initialCapacity);
  58. }
  59. /// <summary>
  60. /// Initializes and returns an instance of UnsafeAppendBuffer that aliases an existing buffer.
  61. /// </summary>
  62. /// <remarks>The capacity will be set to `length`, and <see cref="Length"/> will be set to 0.
  63. /// </remarks>
  64. /// <param name="ptr">The buffer to alias.</param>
  65. /// <param name="length">The length in bytes of the buffer.</param>
  66. public UnsafeAppendBuffer(void* ptr, int length)
  67. {
  68. Alignment = 0;
  69. Allocator = AllocatorManager.None;
  70. Ptr = (byte*)ptr;
  71. Length = 0;
  72. Capacity = length;
  73. }
  74. /// <summary>
  75. /// Whether the append buffer is empty.
  76. /// </summary>
  77. /// <value>True if the append buffer is empty.</value>
  78. public bool IsEmpty => Length == 0;
  79. /// <summary>
  80. /// Whether this append buffer has been allocated (and not yet deallocated).
  81. /// </summary>
  82. /// <value>True if this append buffer has been allocated (and not yet deallocated).</value>
  83. public bool IsCreated => Ptr != null;
  84. /// <summary>
  85. /// Releases all resources (memory and safety handles).
  86. /// </summary>
  87. public void Dispose()
  88. {
  89. if (CollectionHelper.ShouldDeallocate(Allocator))
  90. {
  91. Memory.Unmanaged.Free(Ptr, Allocator);
  92. Allocator = AllocatorManager.Invalid;
  93. }
  94. Ptr = null;
  95. Length = 0;
  96. Capacity = 0;
  97. }
  98. /// <summary>
  99. /// Creates and schedules a job that will dispose this append buffer.
  100. /// </summary>
  101. /// <param name="inputDeps">The handle of a job which the new job will depend upon.</param>
  102. /// <returns>The handle of a new job that will dispose this append buffer. The new job depends upon inputDeps.</returns>
  103. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
  104. public JobHandle Dispose(JobHandle inputDeps)
  105. {
  106. if (CollectionHelper.ShouldDeallocate(Allocator))
  107. {
  108. var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps);
  109. Ptr = null;
  110. Allocator = AllocatorManager.Invalid;
  111. return jobHandle;
  112. }
  113. Ptr = null;
  114. return inputDeps;
  115. }
  116. /// <summary>
  117. /// Sets the length to 0.
  118. /// </summary>
  119. /// <remarks>Does not change the capacity.</remarks>
  120. public void Reset()
  121. {
  122. Length = 0;
  123. }
  124. /// <summary>
  125. /// Sets the size in bytes of the internal buffer.
  126. /// </summary>
  127. /// <remarks>Does nothing if the new capacity is less than or equal to the current capacity.</remarks>
  128. /// <param name="capacity">A new capacity in bytes.</param>
  129. public void SetCapacity(int capacity)
  130. {
  131. if (capacity <= Capacity)
  132. {
  133. return;
  134. }
  135. capacity = math.max(64, math.ceilpow2(capacity));
  136. var newPtr = (byte*)Memory.Unmanaged.Allocate(capacity, Alignment, Allocator);
  137. if (Ptr != null)
  138. {
  139. UnsafeUtility.MemCpy(newPtr, Ptr, Length);
  140. Memory.Unmanaged.Free(Ptr, Allocator);
  141. }
  142. Ptr = newPtr;
  143. Capacity = capacity;
  144. }
  145. /// <summary>
  146. /// Sets the length in bytes.
  147. /// </summary>
  148. /// <remarks>If the new length exceeds the capacity, capacity is expanded to the new length.</remarks>
  149. /// <param name="length">The new length.</param>
  150. public void ResizeUninitialized(int length)
  151. {
  152. SetCapacity(length);
  153. Length = length;
  154. }
  155. /// <summary>
  156. /// Appends an element to the end of this append buffer.
  157. /// </summary>
  158. /// <typeparam name="T">The type of the element.</typeparam>
  159. /// <param name="value">The value to be appended.</param>
  160. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  161. public void Add<T>(T value) where T : struct
  162. {
  163. var structSize = UnsafeUtility.SizeOf<T>();
  164. SetCapacity(Length + structSize);
  165. UnsafeUtility.CopyStructureToPtr(ref value, Ptr + Length);
  166. Length += structSize;
  167. }
  168. /// <summary>
  169. /// Appends an element to the end of this append buffer.
  170. /// </summary>
  171. /// <remarks>The value itself is stored, not the pointer.</remarks>
  172. /// <param name="ptr">A pointer to the value to be appended.</param>
  173. /// <param name="structSize">The size in bytes of the value to be appended.</param>
  174. public void Add(void* ptr, int structSize)
  175. {
  176. SetCapacity(Length + structSize);
  177. UnsafeUtility.MemCpy(Ptr + Length, ptr, structSize);
  178. Length += structSize;
  179. }
  180. /// <summary>
  181. /// Appends the elements of a buffer to the end of this append buffer.
  182. /// </summary>
  183. /// <typeparam name="T">The type of the buffer's elements.</typeparam>
  184. /// <remarks>The values themselves are stored, not their pointers.</remarks>
  185. /// <param name="ptr">A pointer to the buffer whose values will be appended.</param>
  186. /// <param name="length">The number of elements to append.</param>
  187. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  188. public void AddArray<T>(void* ptr, int length) where T : struct
  189. {
  190. Add(length);
  191. if (length != 0)
  192. Add(ptr, length * UnsafeUtility.SizeOf<T>());
  193. }
  194. /// <summary>
  195. /// Appends all elements of an array to the end of this append buffer.
  196. /// </summary>
  197. /// <typeparam name="T">The type of the elements.</typeparam>
  198. /// <param name="value">The array whose elements will all be appended.</param>
  199. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  200. public void Add<T>(NativeArray<T> value) where T : struct
  201. {
  202. Add(value.Length);
  203. Add(NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(value), UnsafeUtility.SizeOf<T>() * value.Length);
  204. }
  205. /// <summary>
  206. /// Appends the content of a string as UTF-16 to the end of this append buffer.
  207. /// </summary>
  208. /// <remarks>Because some Unicode characters require two chars in UTF-16, each character is written as one or two chars (two or four bytes).
  209. ///
  210. /// The length of the string is itself appended before adding the first character. If the string is null, appends the int `-1` but no character data.
  211. ///
  212. /// A null terminator is not appended after the character data.</remarks>
  213. /// <param name="value">The string to append.</param>
  214. [NotBurstCompatible /* Deprecated */]
  215. [Obsolete("Please use `AddNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)]
  216. public void Add(string value) => NotBurstCompatible.Extensions.AddNBC(ref this, value);
  217. /// <summary>
  218. /// Removes and returns the last element of this append buffer.
  219. /// </summary>
  220. /// <typeparam name="T">The type of the element to remove.</typeparam>
  221. /// <remarks>It is your responsibility to specify the correct type. Do not pop when the append buffer is empty.</remarks>
  222. /// <returns>The element removed from the end of this append buffer.</returns>
  223. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  224. public T Pop<T>() where T : struct
  225. {
  226. int structSize = UnsafeUtility.SizeOf<T>();
  227. long ptr = (long)Ptr;
  228. long size = Length;
  229. long addr = ptr + size - structSize;
  230. var data = UnsafeUtility.ReadArrayElement<T>((void*)addr, 0);
  231. Length -= structSize;
  232. return data;
  233. }
  234. /// <summary>
  235. /// Removes and copies the last element of this append buffer.
  236. /// </summary>
  237. /// <remarks>It is your responsibility to specify the correct `structSize`. Do not pop when the append buffer is empty.</remarks>
  238. /// <param name="ptr">The location to which the removed element will be copied.</param>
  239. /// <param name="structSize">The size of the element to remove and copy.</param>
  240. public void Pop(void* ptr, int structSize)
  241. {
  242. long data = (long)Ptr;
  243. long size = Length;
  244. long addr = data + size - structSize;
  245. UnsafeUtility.MemCpy(ptr, (void*)addr, structSize);
  246. Length -= structSize;
  247. }
  248. /// <summary>
  249. /// Copies this append buffer to a managed array of bytes.
  250. /// </summary>
  251. /// <returns>A managed array of bytes.</returns>
  252. [NotBurstCompatible /* Deprecated */]
  253. [Obsolete("Please use `ToBytesNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)]
  254. public byte[] ToBytes() => NotBurstCompatible.Extensions.ToBytesNBC(ref this);
  255. /// <summary>
  256. /// Returns a reader for this append buffer.
  257. /// </summary>
  258. /// <returns>A reader for the append buffer.</returns>
  259. public Reader AsReader()
  260. {
  261. return new Reader(ref this);
  262. }
  263. /// <summary>
  264. /// A reader for UnsafeAppendBuffer.
  265. /// </summary>
  266. [BurstCompatible]
  267. public unsafe struct Reader
  268. {
  269. /// <summary>
  270. /// The internal buffer where the content is stored.
  271. /// </summary>
  272. /// <value>The internal buffer where the content is stored.</value>
  273. public readonly byte* Ptr;
  274. /// <summary>
  275. /// The length in bytes of the append buffer's content.
  276. /// </summary>
  277. /// <value>The length in bytes of the append buffer's content.</value>
  278. public readonly int Size;
  279. /// <summary>
  280. /// The location of the next read (expressed as a byte offset from the start).
  281. /// </summary>
  282. /// <value>The location of the next read (expressed as a byte offset from the start).</value>
  283. public int Offset;
  284. /// <summary>
  285. /// Initializes and returns an instance of UnsafeAppendBuffer.Reader.
  286. /// </summary>
  287. /// <param name="buffer">A reference to the append buffer to read.</param>
  288. public Reader(ref UnsafeAppendBuffer buffer)
  289. {
  290. Ptr = buffer.Ptr;
  291. Size = buffer.Length;
  292. Offset = 0;
  293. }
  294. /// <summary>
  295. /// Initializes and returns an instance of UnsafeAppendBuffer.Reader that reads from a buffer.
  296. /// </summary>
  297. /// <remarks>The buffer will be read *as if* it is an UnsafeAppendBuffer whether it was originally allocated as one or not.</remarks>
  298. /// <param name="ptr">The buffer to read as an UnsafeAppendBuffer.</param>
  299. /// <param name="length">The length in bytes of the </param>
  300. public Reader(void* ptr, int length)
  301. {
  302. Ptr = (byte*)ptr;
  303. Size = length;
  304. Offset = 0;
  305. }
  306. /// <summary>
  307. /// Whether the offset has advanced past the last of the append buffer's content.
  308. /// </summary>
  309. /// <value>Whether the offset has advanced past the last of the append buffer's content.</value>
  310. public bool EndOfBuffer => Offset == Size;
  311. /// <summary>
  312. /// Reads an element from the append buffer.
  313. /// </summary>
  314. /// <remarks>Advances the reader's offset by the size of T.</remarks>
  315. /// <typeparam name="T">The type of element to read.</typeparam>
  316. /// <param name="value">Output for the element read.</param>
  317. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  318. public void ReadNext<T>(out T value) where T : struct
  319. {
  320. var structSize = UnsafeUtility.SizeOf<T>();
  321. CheckBounds(structSize);
  322. UnsafeUtility.CopyPtrToStructure<T>(Ptr + Offset, out value);
  323. Offset += structSize;
  324. }
  325. /// <summary>
  326. /// Reads an element from the append buffer.
  327. /// </summary>
  328. /// <remarks>Advances the reader's offset by the size of T.</remarks>
  329. /// <typeparam name="T">The type of element to read.</typeparam>
  330. /// <returns>The element read.</returns>
  331. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  332. public T ReadNext<T>() where T : struct
  333. {
  334. var structSize = UnsafeUtility.SizeOf<T>();
  335. CheckBounds(structSize);
  336. T value = UnsafeUtility.ReadArrayElement<T>(Ptr + Offset, 0);
  337. Offset += structSize;
  338. return value;
  339. }
  340. /// <summary>
  341. /// Reads an element from the append buffer.
  342. /// </summary>
  343. /// <remarks>Advances the reader's offset by `structSize`.</remarks>
  344. /// <param name="structSize">The size of the element to read.</param>
  345. /// <returns>A pointer to where the read element resides in the append buffer.</returns>
  346. public void* ReadNext(int structSize)
  347. {
  348. CheckBounds(structSize);
  349. var value = (void*)((IntPtr)Ptr + Offset);
  350. Offset += structSize;
  351. return value;
  352. }
  353. /// <summary>
  354. /// Reads an element from the append buffer.
  355. /// </summary>
  356. /// <remarks>Advances the reader's offset by the size of T.</remarks>
  357. /// <typeparam name="T">The type of element to read.</typeparam>
  358. /// <param name="value">Outputs a new array with length of 1. The read element is copied to the single index of this array.</param>
  359. /// <param name="allocator">The allocator to use.</param>
  360. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  361. public void ReadNext<T>(out NativeArray<T> value, AllocatorManager.AllocatorHandle allocator) where T : struct
  362. {
  363. var length = ReadNext<int>();
  364. value = CollectionHelper.CreateNativeArray<T>(length, allocator);
  365. var size = length * UnsafeUtility.SizeOf<T>();
  366. if (size > 0)
  367. {
  368. var ptr = ReadNext(size);
  369. UnsafeUtility.MemCpy(NativeArrayUnsafeUtility.GetUnsafePtr(value), ptr, size);
  370. }
  371. }
  372. /// <summary>
  373. /// Reads an array from the append buffer.
  374. /// </summary>
  375. /// <remarks>An array stored in the append buffer starts with an int specifying the number of values in the array.
  376. /// The first element of an array immediately follows this int.
  377. ///
  378. /// Advances the reader's offset by the size of the array (plus an int).</remarks>
  379. /// <typeparam name="T">The type of elements in the array to read.</typeparam>
  380. /// <param name="length">Output which is the number of elements in the read array.</param>
  381. /// <returns>A pointer to where the first element of the read array resides in the append buffer.</returns>
  382. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  383. public void* ReadNextArray<T>(out int length) where T : struct
  384. {
  385. length = ReadNext<int>();
  386. return (length == 0) ? null : ReadNext(length * UnsafeUtility.SizeOf<T>());
  387. }
  388. #if !NET_DOTS
  389. /// <summary>
  390. /// Reads a UTF-16 string from the append buffer.
  391. /// </summary>
  392. /// <remarks>Because some Unicode characters require two chars in UTF-16, each character is either one or two chars (two or four bytes).
  393. ///
  394. /// Assumes the string does not have a null terminator.
  395. ///
  396. /// Advances the reader's offset by the size of the string (in bytes).</remarks>
  397. /// <param name="value">Outputs the string read from the append buffer.</param>
  398. [NotBurstCompatible /* Deprecated */]
  399. [Obsolete("Please use `ReadNextNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)]
  400. public void ReadNext(out string value) => NotBurstCompatible.Extensions.ReadNextNBC(ref this, out value);
  401. #endif
  402. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  403. void CheckBounds(int structSize)
  404. {
  405. if (Offset + structSize > Size)
  406. {
  407. throw new ArgumentException($"Requested value outside bounds of UnsafeAppendOnlyBuffer. Remaining bytes: {Size - Offset} Requested: {structSize}");
  408. }
  409. }
  410. }
  411. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  412. static void CheckAlignment(int alignment)
  413. {
  414. var zeroAlignment = alignment == 0;
  415. var powTwoAlignment = ((alignment - 1) & alignment) == 0;
  416. var validAlignment = (!zeroAlignment) && powTwoAlignment;
  417. if (!validAlignment)
  418. {
  419. throw new ArgumentException($"Specified alignment must be non-zero positive power of two. Requested: {alignment}");
  420. }
  421. }
  422. }
  423. }