Nenhuma descrição
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

NativeStream.cs 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. using System;
  2. using System.Diagnostics;
  3. using Unity.Collections.LowLevel.Unsafe;
  4. using Unity.Burst;
  5. using Unity.Jobs;
  6. using UnityEngine.Assertions;
  7. namespace Unity.Collections
  8. {
  9. /// <summary>
  10. /// A set of untyped, append-only buffers. Allows for concurrent reading and concurrent writing without synchronization.
  11. /// </summary>
  12. /// <remarks>
  13. /// As long as each individual buffer is written in one thread and read in one thread, multiple
  14. /// threads can read and write the stream concurrently, *e.g.*
  15. /// while thread *A* reads from buffer *X* of a stream, thread *B* can read from
  16. /// buffer *Y* of the same stream.
  17. ///
  18. /// Each buffer is stored as a chain of blocks. When a write exceeds a buffer's current capacity, another block
  19. /// is allocated and added to the end of the chain. Effectively, expanding the buffer never requires copying the existing
  20. /// data (unlike with <see cref="NativeList{T}"/>, for example).
  21. ///
  22. /// **All writing to a stream should be completed before the stream is first read. Do not write to a stream after the first read.**
  23. /// Violating these rules won't *necessarily* cause any problems, but they are the intended usage pattern.
  24. ///
  25. /// Writing is done with <see cref="NativeStream.Writer"/>, and reading is done with <see cref="NativeStream.Reader"/>.
  26. /// An individual reader or writer cannot be used concurrently across threads: each thread must use its own.
  27. ///
  28. /// The data written to an individual buffer can be heterogeneous in type, and the data written
  29. /// to different buffers of a stream can be entirely different in type, number, and order. Just make sure
  30. /// that the code reading from a particular buffer knows what to expect to read from it.
  31. /// </remarks>
  32. [NativeContainer]
  33. [BurstCompatible]
  34. public unsafe struct NativeStream : IDisposable
  35. {
  36. UnsafeStream m_Stream;
  37. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  38. AtomicSafetyHandle m_Safety;
  39. internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeStream>();
  40. #if REMOVE_DISPOSE_SENTINEL
  41. #else
  42. [NativeSetClassTypeToNullOnSchedule]
  43. DisposeSentinel m_DisposeSentinel;
  44. #endif
  45. #endif
  46. /// <summary>
  47. /// Initializes and returns an instance of NativeStream.
  48. /// </summary>
  49. /// <param name="bufferCount">The number of buffers to give the stream. You usually want
  50. /// one buffer for each thread that will read or write the stream.</param>
  51. /// <param name="allocator">The allocator to use.</param>
  52. public NativeStream(int bufferCount, AllocatorManager.AllocatorHandle allocator)
  53. {
  54. AllocateBlock(out this, allocator);
  55. m_Stream.AllocateForEach(bufferCount);
  56. }
  57. /// <summary>
  58. /// Creates and schedules a job to allocate a new stream.
  59. /// </summary>
  60. /// <remarks>The stream can be used on the main thread after completing the returned job or used in other jobs that depend upon the returned job.
  61. ///
  62. /// Using a job to allocate the buffers can be more efficient, particularly for a stream with many buffers.
  63. /// </remarks>
  64. /// <typeparam name="T">Ignored.</typeparam>
  65. /// <param name="stream">Outputs the new stream.</param>
  66. /// <param name="bufferCount">A list whose length determines the number of buffers in the stream.</param>
  67. /// <param name="dependency">A job handle. The new job will depend upon this handle.</param>
  68. /// <param name="allocator">The allocator to use.</param>
  69. /// <returns>The handle of the new job.</returns>
  70. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
  71. public static JobHandle ScheduleConstruct<T>(out NativeStream stream, NativeList<T> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator)
  72. where T : unmanaged
  73. {
  74. AllocateBlock(out stream, allocator);
  75. var jobData = new ConstructJobList { List = (UntypedUnsafeList*)bufferCount.GetUnsafeList(), Container = stream };
  76. return jobData.Schedule(dependency);
  77. }
  78. /// <summary>
  79. /// Creates and schedules a job to allocate a new stream.
  80. /// </summary>
  81. /// <remarks>The stream can be used...
  82. /// - after completing the returned job
  83. /// - or in other jobs that depend upon the returned job.
  84. ///
  85. /// Allocating the buffers in a job can be more efficient, particularly for a stream with many buffers.
  86. /// </remarks>
  87. /// <param name="stream">Outputs the new stream.</param>
  88. /// <param name="bufferCount">An array whose value at index 0 determines the number of buffers in the stream.</param>
  89. /// <param name="dependency">A job handle. The new job will depend upon this handle.</param>
  90. /// <param name="allocator">The allocator to use.</param>
  91. /// <returns>The handle of the new job.</returns>
  92. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
  93. public static JobHandle ScheduleConstruct(out NativeStream stream, NativeArray<int> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator)
  94. {
  95. AllocateBlock(out stream, allocator);
  96. var jobData = new ConstructJob { Length = bufferCount, Container = stream };
  97. return jobData.Schedule(dependency);
  98. }
  99. /// <summary>
  100. /// Returns true if this stream is empty.
  101. /// </summary>
  102. /// <returns>True if this stream is empty or the stream has not been constructed.</returns>
  103. public bool IsEmpty()
  104. {
  105. CheckReadAccess();
  106. return m_Stream.IsEmpty();
  107. }
  108. /// <summary>
  109. /// Whether this stream has been allocated (and not yet deallocated).
  110. /// </summary>
  111. /// <remarks>Does not necessarily reflect whether the buffers of the stream have themselves been allocated.</remarks>
  112. /// <value>True if this stream has been allocated (and not yet deallocated).</value>
  113. public bool IsCreated => m_Stream.IsCreated;
  114. /// <summary>
  115. /// The number of buffers in this stream.
  116. /// </summary>
  117. /// <value>The number of buffers in this stream.</value>
  118. public int ForEachCount
  119. {
  120. get
  121. {
  122. CheckReadAccess();
  123. return m_Stream.ForEachCount;
  124. }
  125. }
  126. /// <summary>
  127. /// Returns a reader of this stream.
  128. /// </summary>
  129. /// <returns>A reader of this stream.</returns>
  130. public Reader AsReader()
  131. {
  132. return new Reader(ref this);
  133. }
  134. /// <summary>
  135. /// Returns a writer of this stream.
  136. /// </summary>
  137. /// <returns>A writer of this stream.</returns>
  138. public Writer AsWriter()
  139. {
  140. return new Writer(ref this);
  141. }
  142. /// <summary>
  143. /// Returns the total number of items in the buffers of this stream.
  144. /// </summary>
  145. /// <remarks>Each <see cref="Writer.Write{T}"/> and <see cref="Writer.Allocate"/> call increments this number.</remarks>
  146. /// <returns>The total number of items in the buffers of this stream.</returns>
  147. public int Count()
  148. {
  149. CheckReadAccess();
  150. return m_Stream.Count();
  151. }
  152. /// <summary>
  153. /// Returns a new NativeArray copy of this stream's data.
  154. /// </summary>
  155. /// <remarks>The length of the array will equal the count of this stream.
  156. ///
  157. /// Each buffer of this stream is copied to the array, one after the other.
  158. /// </remarks>
  159. /// <typeparam name="T">The type of values in the array.</typeparam>
  160. /// <param name="allocator">The allocator to use.</param>
  161. /// <returns>A new NativeArray copy of this stream's data.</returns>
  162. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  163. public NativeArray<T> ToNativeArray<T>(AllocatorManager.AllocatorHandle allocator) where T : struct
  164. {
  165. CheckReadAccess();
  166. return m_Stream.ToNativeArray<T>(allocator);
  167. }
  168. /// <summary>
  169. /// Releases all resources (memory and safety handles).
  170. /// </summary>
  171. public void Dispose()
  172. {
  173. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  174. #if REMOVE_DISPOSE_SENTINEL
  175. CollectionHelper.DisposeSafetyHandle(ref m_Safety);
  176. #else
  177. DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
  178. #endif
  179. #endif
  180. m_Stream.Dispose();
  181. }
  182. /// <summary>
  183. /// Creates and schedules a job that will release all resources (memory and safety handles) of this stream.
  184. /// </summary>
  185. /// <param name="inputDeps">A job handle which the newly scheduled job will depend upon.</param>
  186. /// <returns>The handle of a new job that will release all resources (memory and safety handles) of this stream.</returns>
  187. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */]
  188. public JobHandle Dispose(JobHandle inputDeps)
  189. {
  190. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  191. #if REMOVE_DISPOSE_SENTINEL
  192. #else
  193. // [DeallocateOnJobCompletion] is not supported, but we want the deallocation
  194. // to happen in a thread. DisposeSentinel needs to be cleared on main thread.
  195. // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling
  196. // will check that no jobs are writing to the container).
  197. DisposeSentinel.Clear(ref m_DisposeSentinel);
  198. #endif
  199. #endif
  200. var jobHandle = m_Stream.Dispose(inputDeps);
  201. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  202. AtomicSafetyHandle.Release(m_Safety);
  203. #endif
  204. return jobHandle;
  205. }
  206. [BurstCompile]
  207. struct ConstructJobList : IJob
  208. {
  209. public NativeStream Container;
  210. [ReadOnly]
  211. [NativeDisableUnsafePtrRestriction]
  212. public UntypedUnsafeList* List;
  213. public void Execute()
  214. {
  215. Container.AllocateForEach(List->m_length);
  216. }
  217. }
  218. [BurstCompile]
  219. struct ConstructJob : IJob
  220. {
  221. public NativeStream Container;
  222. [ReadOnly]
  223. public NativeArray<int> Length;
  224. public void Execute()
  225. {
  226. Container.AllocateForEach(Length[0]);
  227. }
  228. }
  229. static void AllocateBlock(out NativeStream stream, AllocatorManager.AllocatorHandle allocator)
  230. {
  231. CollectionHelper.CheckAllocator(allocator);
  232. UnsafeStream.AllocateBlock(out stream.m_Stream, allocator);
  233. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  234. #if REMOVE_DISPOSE_SENTINEL
  235. stream.m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
  236. #else
  237. if (allocator.IsCustomAllocator)
  238. {
  239. stream.m_Safety = AtomicSafetyHandle.Create();
  240. stream.m_DisposeSentinel = null;
  241. }
  242. else
  243. {
  244. DisposeSentinel.Create(out stream.m_Safety, out stream.m_DisposeSentinel, 0, allocator.ToAllocator);
  245. }
  246. #endif
  247. CollectionHelper.SetStaticSafetyId(ref stream.m_Safety, ref s_staticSafetyId.Data, "Unity.Collections.NativeStream");
  248. #endif
  249. }
  250. void AllocateForEach(int forEachCount)
  251. {
  252. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  253. CheckForEachCountGreaterThanZero(forEachCount);
  254. Assert.IsTrue(m_Stream.m_Block->Ranges == null);
  255. Assert.AreEqual(0, m_Stream.m_Block->RangeCount);
  256. Assert.AreNotEqual(0, m_Stream.m_Block->BlockCount);
  257. #endif
  258. m_Stream.AllocateForEach(forEachCount);
  259. }
  260. /// <summary>
  261. /// Writes data into a buffer of a <see cref="NativeStream"/>.
  262. /// </summary>
  263. /// <remarks>An individual writer can only be used for one buffer of one stream.
  264. /// Do not create more than one writer for an individual buffer.</remarks>
  265. [NativeContainer]
  266. [NativeContainerSupportsMinMaxWriteRestriction]
  267. [BurstCompatible]
  268. public unsafe struct Writer
  269. {
  270. UnsafeStream.Writer m_Writer;
  271. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  272. AtomicSafetyHandle m_Safety;
  273. internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<Writer>();
  274. #pragma warning disable CS0414 // warning CS0414: The field 'NativeStream.Writer.m_Length' is assigned but its value is never used
  275. int m_Length;
  276. #pragma warning restore CS0414
  277. int m_MinIndex;
  278. int m_MaxIndex;
  279. [NativeDisableUnsafePtrRestriction]
  280. void* m_PassByRefCheck;
  281. #endif
  282. internal Writer(ref NativeStream stream)
  283. {
  284. m_Writer = stream.m_Stream.AsWriter();
  285. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  286. m_Safety = stream.m_Safety;
  287. CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data, "Unity.Collections.NativeStream.Writer");
  288. m_Length = int.MaxValue;
  289. m_MinIndex = int.MinValue;
  290. m_MaxIndex = int.MinValue;
  291. m_PassByRefCheck = null;
  292. #endif
  293. }
  294. /// <summary>
  295. /// The number of buffers in the stream of this writer.
  296. /// </summary>
  297. /// <value>The number of buffers in the stream of this writer.</value>
  298. public int ForEachCount
  299. {
  300. get
  301. {
  302. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  303. AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
  304. #endif
  305. return m_Writer.ForEachCount;
  306. }
  307. }
  308. /// <summary>
  309. /// For internal use only.
  310. /// </summary>
  311. /// <param name="foreEachIndex"></param>
  312. public void PatchMinMaxRange(int foreEachIndex)
  313. {
  314. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  315. m_MinIndex = foreEachIndex;
  316. m_MaxIndex = foreEachIndex;
  317. #endif
  318. }
  319. /// <summary>
  320. /// Readies this writer to write to a particular buffer of the stream.
  321. /// </summary>
  322. /// <remarks>Must be called before using this writer. For an individual writer, call this method only once.
  323. ///
  324. /// When done using this writer, you must call <see cref="EndForEachIndex"/>.</remarks>
  325. /// <param name="foreachIndex">The index of the buffer to write.</param>
  326. public void BeginForEachIndex(int foreachIndex)
  327. {
  328. //@TODO: Check that no one writes to the same for each index multiple times...
  329. CheckBeginForEachIndex(foreachIndex);
  330. m_Writer.BeginForEachIndex(foreachIndex);
  331. }
  332. /// <summary>
  333. /// Readies the buffer written by this writer for reading.
  334. /// </summary>
  335. /// <remarks>Must be called before reading the buffer written by this writer.</remarks>
  336. public void EndForEachIndex()
  337. {
  338. CheckEndForEachIndex();
  339. m_Writer.EndForEachIndex();
  340. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  341. m_Writer.m_ForeachIndex = int.MinValue;
  342. #endif
  343. }
  344. /// <summary>
  345. /// Write a value to a buffer.
  346. /// </summary>
  347. /// <remarks>The value is written to the buffer which was specified
  348. /// with <see cref="BeginForEachIndex"/>.</remarks>
  349. /// <typeparam name="T">The type of value to write.</typeparam>
  350. /// <param name="value">The value to write.</param>
  351. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  352. public void Write<T>(T value) where T : struct
  353. {
  354. ref T dst = ref Allocate<T>();
  355. dst = value;
  356. }
  357. /// <summary>
  358. /// Allocate space in a buffer.
  359. /// </summary>
  360. /// <remarks>The space is allocated in the buffer which was specified
  361. /// with <see cref="BeginForEachIndex"/>.</remarks>
  362. /// <typeparam name="T">The type of value to allocate space for.</typeparam>
  363. /// <returns>A reference to the allocation.</returns>
  364. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  365. public ref T Allocate<T>() where T : struct
  366. {
  367. CollectionHelper.CheckIsUnmanaged<T>();
  368. int size = UnsafeUtility.SizeOf<T>();
  369. return ref UnsafeUtility.AsRef<T>(Allocate(size));
  370. }
  371. /// <summary>
  372. /// Allocate space in a buffer.
  373. /// </summary>
  374. /// <remarks>The space is allocated in the buffer which was specified
  375. /// with <see cref="BeginForEachIndex"/>.</remarks>
  376. /// <param name="size">The number of bytes to allocate.</param>
  377. /// <returns>The allocation.</returns>
  378. public byte* Allocate(int size)
  379. {
  380. CheckAllocateSize(size);
  381. return m_Writer.Allocate(size);
  382. }
  383. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  384. void CheckBeginForEachIndex(int foreachIndex)
  385. {
  386. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  387. AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
  388. if (m_PassByRefCheck == null)
  389. {
  390. m_PassByRefCheck = UnsafeUtility.AddressOf(ref this);
  391. }
  392. if (foreachIndex < m_MinIndex || foreachIndex > m_MaxIndex)
  393. {
  394. // When the code is not running through the job system no ParallelForRange patching will occur
  395. // We can't grab m_BlockStream->RangeCount on creation of the writer because the RangeCount can be initialized
  396. // in a job after creation of the writer
  397. if (m_MinIndex == int.MinValue && m_MaxIndex == int.MinValue)
  398. {
  399. m_MinIndex = 0;
  400. m_MaxIndex = m_Writer.m_BlockStream->RangeCount - 1;
  401. }
  402. if (foreachIndex < m_MinIndex || foreachIndex > m_MaxIndex)
  403. {
  404. throw new ArgumentException($"Index {foreachIndex} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in NativeStream.");
  405. }
  406. }
  407. if (m_Writer.m_ForeachIndex != int.MinValue)
  408. {
  409. throw new ArgumentException($"BeginForEachIndex must always be balanced by a EndForEachIndex call");
  410. }
  411. if (0 != m_Writer.m_BlockStream->Ranges[foreachIndex].ElementCount)
  412. {
  413. throw new ArgumentException($"BeginForEachIndex can only be called once for the same index ({foreachIndex}).");
  414. }
  415. Assert.IsTrue(foreachIndex >= 0 && foreachIndex < m_Writer.m_BlockStream->RangeCount);
  416. #endif
  417. }
  418. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  419. void CheckEndForEachIndex()
  420. {
  421. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  422. AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
  423. if (m_Writer.m_ForeachIndex == int.MinValue)
  424. {
  425. throw new System.ArgumentException("EndForEachIndex must always be called balanced by a BeginForEachIndex or AppendForEachIndex call");
  426. }
  427. #endif
  428. }
  429. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  430. void CheckAllocateSize(int size)
  431. {
  432. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  433. AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
  434. if (m_PassByRefCheck != UnsafeUtility.AddressOf(ref this))
  435. {
  436. throw new ArgumentException("NativeStream.Writer must be passed by ref once it is in use");
  437. }
  438. if (m_Writer.m_ForeachIndex == int.MinValue)
  439. {
  440. throw new ArgumentException("Allocate must be called within BeginForEachIndex / EndForEachIndex");
  441. }
  442. if (size > UnsafeStreamBlockData.AllocationSize - sizeof(void*))
  443. {
  444. throw new ArgumentException("Allocation size is too large");
  445. }
  446. #endif
  447. }
  448. }
  449. /// <summary>
  450. /// Reads data from a buffer of a <see cref="NativeStream"/>.
  451. /// </summary>
  452. /// <remarks>An individual reader can only be used for one buffer of one stream.
  453. /// Do not create more than one reader for an individual buffer.</remarks>
  454. [NativeContainer]
  455. [NativeContainerIsReadOnly]
  456. [BurstCompatible]
  457. public unsafe struct Reader
  458. {
  459. UnsafeStream.Reader m_Reader;
  460. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  461. int m_RemainingBlocks;
  462. internal AtomicSafetyHandle m_Safety;
  463. internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<Reader>();
  464. #endif
  465. internal Reader(ref NativeStream stream)
  466. {
  467. m_Reader = stream.m_Stream.AsReader();
  468. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  469. m_RemainingBlocks = 0;
  470. m_Safety = stream.m_Safety;
  471. CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data, "Unity.Collections.NativeStream.Reader");
  472. #endif
  473. }
  474. /// <summary>
  475. /// Readies this reader to read a particular buffer of the stream.
  476. /// </summary>
  477. /// <remarks>Must be called before using this reader. For an individual reader, call this method only once.
  478. ///
  479. /// When done using this reader, you must call <see cref="EndForEachIndex"/>.</remarks>
  480. /// <param name="foreachIndex">The index of the buffer to read.</param>
  481. /// <returns>The number of elements left to read from the buffer.</returns>
  482. public int BeginForEachIndex(int foreachIndex)
  483. {
  484. CheckBeginForEachIndex(foreachIndex);
  485. var remainingItemCount = m_Reader.BeginForEachIndex(foreachIndex);
  486. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  487. m_RemainingBlocks = m_Reader.m_BlockStream->Ranges[foreachIndex].NumberOfBlocks;
  488. if (m_RemainingBlocks == 0)
  489. {
  490. m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + m_Reader.m_LastBlockSize;
  491. }
  492. #endif
  493. return remainingItemCount;
  494. }
  495. /// <summary>
  496. /// Checks if all data has been read from the buffer.
  497. /// </summary>
  498. /// <remarks>If you intentionally don't want to read *all* the data in the buffer, don't call this method.
  499. /// Otherwise, calling this method is recommended, even though it's not strictly necessary.</remarks>
  500. /// <exception cref="ArgumentException">Thrown if not all the buffer's data has been read.</exception>
  501. public void EndForEachIndex()
  502. {
  503. m_Reader.EndForEachIndex();
  504. CheckEndForEachIndex();
  505. }
  506. /// <summary>
  507. /// The number of buffers in the stream of this reader.
  508. /// </summary>
  509. /// <value>The number of buffers in the stream of this reader.</value>
  510. public int ForEachCount
  511. {
  512. get
  513. {
  514. CheckRead();
  515. return m_Reader.ForEachCount;
  516. }
  517. }
  518. /// <summary>
  519. /// The number of items not yet read from the buffer.
  520. /// </summary>
  521. /// <value>The number of items not yet read from the buffer.</value>
  522. public int RemainingItemCount => m_Reader.RemainingItemCount;
  523. /// <summary>
  524. /// Returns a pointer to the next position to read from the buffer. Advances the reader some number of bytes.
  525. /// </summary>
  526. /// <param name="size">The number of bytes to advance the reader.</param>
  527. /// <returns>A pointer to the next position to read from the buffer.</returns>
  528. /// <exception cref="ArgumentException">Thrown if the reader would advance past the end of the buffer.</exception>
  529. public byte* ReadUnsafePtr(int size)
  530. {
  531. CheckReadSize(size);
  532. m_Reader.m_RemainingItemCount--;
  533. byte* ptr = m_Reader.m_CurrentPtr;
  534. m_Reader.m_CurrentPtr += size;
  535. if (m_Reader.m_CurrentPtr > m_Reader.m_CurrentBlockEnd)
  536. {
  537. /*
  538. * On netfw/mono/il2cpp, doing m_CurrentBlock->Data does not throw, because it knows that it can
  539. * just do pointer + 8. On netcore, doing that throws a NullReferenceException. So, first check for
  540. * out of bounds accesses, and only then update m_CurrentBlock and m_CurrentPtr.
  541. */
  542. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  543. m_RemainingBlocks--;
  544. CheckNotReadingOutOfBounds(size);
  545. #endif
  546. m_Reader.m_CurrentBlock = m_Reader.m_CurrentBlock->Next;
  547. m_Reader.m_CurrentPtr = m_Reader.m_CurrentBlock->Data;
  548. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  549. if (m_RemainingBlocks <= 0)
  550. {
  551. m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + m_Reader.m_LastBlockSize;
  552. }
  553. else
  554. {
  555. m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + UnsafeStreamBlockData.AllocationSize;
  556. }
  557. #else
  558. m_Reader.m_CurrentBlockEnd = (byte*)m_Reader.m_CurrentBlock + UnsafeStreamBlockData.AllocationSize;
  559. #endif
  560. ptr = m_Reader.m_CurrentPtr;
  561. m_Reader.m_CurrentPtr += size;
  562. }
  563. return ptr;
  564. }
  565. /// <summary>
  566. /// Reads the next value from the buffer.
  567. /// </summary>
  568. /// <remarks>Each read advances the reader to the next item in the buffer.</remarks>
  569. /// <typeparam name="T">The type of value to read.</typeparam>
  570. /// <returns>A reference to the next value from the buffer.</returns>
  571. /// <exception cref="ArgumentException">Thrown if the reader would advance past the end of the buffer.</exception>
  572. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  573. public ref T Read<T>() where T : struct
  574. {
  575. int size = UnsafeUtility.SizeOf<T>();
  576. return ref UnsafeUtility.AsRef<T>(ReadUnsafePtr(size));
  577. }
  578. /// <summary>
  579. /// Reads the next value from the buffer. Does not advance the reader.
  580. /// </summary>
  581. /// <typeparam name="T">The type of value to read.</typeparam>
  582. /// <returns>A reference to the next value from the buffer.</returns>
  583. /// <exception cref="ArgumentException">Thrown if the read would go past the end of the buffer.</exception>
  584. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  585. public ref T Peek<T>() where T : struct
  586. {
  587. int size = UnsafeUtility.SizeOf<T>();
  588. CheckReadSize(size);
  589. return ref m_Reader.Peek<T>();
  590. }
  591. /// <summary>
  592. /// Returns the total number of items in the buffers of the stream.
  593. /// </summary>
  594. /// <returns>The total number of items in the buffers of the stream.</returns>
  595. public int Count()
  596. {
  597. CheckRead();
  598. return m_Reader.Count();
  599. }
  600. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  601. void CheckNotReadingOutOfBounds(int size)
  602. {
  603. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  604. if (m_RemainingBlocks < 0)
  605. throw new System.ArgumentException("Reading out of bounds");
  606. if (m_RemainingBlocks == 0 && size + sizeof(void*) > m_Reader.m_LastBlockSize)
  607. throw new System.ArgumentException("Reading out of bounds");
  608. #endif
  609. }
  610. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  611. void CheckRead()
  612. {
  613. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  614. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  615. #endif
  616. }
  617. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  618. void CheckReadSize(int size)
  619. {
  620. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  621. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  622. Assert.IsTrue(size <= UnsafeStreamBlockData.AllocationSize - (sizeof(void*)));
  623. if (m_Reader.m_RemainingItemCount < 1)
  624. {
  625. throw new ArgumentException("There are no more items left to be read.");
  626. }
  627. #endif
  628. }
  629. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  630. void CheckBeginForEachIndex(int forEachIndex)
  631. {
  632. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  633. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  634. if ((uint)forEachIndex >= (uint)m_Reader.m_BlockStream->RangeCount)
  635. {
  636. throw new System.ArgumentOutOfRangeException(nameof(forEachIndex), $"foreachIndex: {forEachIndex} must be between 0 and ForEachCount: {m_Reader.m_BlockStream->RangeCount}");
  637. }
  638. #endif
  639. }
  640. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  641. void CheckEndForEachIndex()
  642. {
  643. if (m_Reader.m_RemainingItemCount != 0)
  644. {
  645. throw new System.ArgumentException("Not all elements (Count) have been read. If this is intentional, simply skip calling EndForEachIndex();");
  646. }
  647. if (m_Reader.m_CurrentBlockEnd != m_Reader.m_CurrentPtr)
  648. {
  649. throw new System.ArgumentException("Not all data (Data Size) has been read. If this is intentional, simply skip calling EndForEachIndex();");
  650. }
  651. }
  652. }
  653. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  654. static void CheckForEachCountGreaterThanZero(int forEachCount)
  655. {
  656. if (forEachCount <= 0)
  657. throw new ArgumentException("foreachCount must be > 0", "foreachCount");
  658. }
  659. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
  660. void CheckReadAccess()
  661. {
  662. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  663. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
  664. #endif
  665. }
  666. }
  667. }