설명 없음
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 19KB

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