A game about forced loneliness, made by TACStudios
1using System;
2using System.Runtime.CompilerServices;
3using Unity.Burst;
4using Unity.Jobs;
5using Unity.Jobs.LowLevel.Unsafe;
6using UnityEngine.Assertions;
7
8namespace Unity.Collections.LowLevel.Unsafe
9{
10 [GenerateTestsForBurstCompatibility]
11 internal unsafe struct UnsafeStreamBlock
12 {
13 internal UnsafeStreamBlock* Next;
14 internal fixed byte Data[1];
15 }
16
17 [GenerateTestsForBurstCompatibility]
18 internal unsafe struct UnsafeStreamRange
19 {
20 internal UnsafeStreamBlock* Block;
21 internal int OffsetInFirstBlock;
22 internal int ElementCount;
23
24 /// One byte past the end of the last byte written
25 internal int LastOffset;
26 internal int NumberOfBlocks;
27 }
28
29 [GenerateTestsForBurstCompatibility]
30 internal unsafe struct UnsafeStreamBlockData
31 {
32 internal const int AllocationSize = 4 * 1024;
33 internal AllocatorManager.AllocatorHandle Allocator;
34
35 internal UnsafeStreamBlock** Blocks;
36 internal int BlockCount;
37
38 internal AllocatorManager.Block Ranges;
39 internal int RangeCount;
40
41 internal UnsafeStreamBlock* Allocate(UnsafeStreamBlock* oldBlock, int threadIndex)
42 {
43 Assert.IsTrue(threadIndex < BlockCount && threadIndex >= 0);
44
45 UnsafeStreamBlock* block = (UnsafeStreamBlock*)Memory.Unmanaged.Array.Resize(null, 0, AllocationSize, Allocator, 1, 16);
46 block->Next = null;
47
48 if (oldBlock == null)
49 {
50 // Append our new block in front of the previous head.
51 block->Next = Blocks[threadIndex];
52 Blocks[threadIndex] = block;
53 }
54 else
55 {
56 block->Next = oldBlock->Next;
57 oldBlock->Next = block;
58 }
59
60 return block;
61 }
62
63 internal void Free(UnsafeStreamBlock* oldBlock)
64 {
65 Memory.Unmanaged.Array.Resize(oldBlock, AllocationSize, 0, Allocator, 1, 16);
66 }
67 }
68
69 /// <summary>
70 /// A set of untyped, append-only buffers. Allows for concurrent reading and concurrent writing without synchronization.
71 /// </summary>
72 /// <remarks>
73 /// As long as each individual buffer is written in one thread and read in one thread, multiple
74 /// threads can read and write the stream concurrently, *e.g.*
75 /// while thread *A* reads from buffer *X* of a stream, thread *B* can read from
76 /// buffer *Y* of the same stream.
77 ///
78 /// Each buffer is stored as a chain of blocks. When a write exceeds a buffer's current capacity, another block
79 /// is allocated and added to the end of the chain. Effectively, expanding the buffer never requires copying the existing
80 /// data (unlike, for example, with <see cref="NativeList{T}"/>).
81 ///
82 /// **All writing to a stream should be completed before the stream is first read. Do not write to a stream after the first read.**
83 ///
84 /// Writing is done with <see cref="NativeStream.Writer"/>, and reading is done with <see cref="NativeStream.Reader"/>.
85 /// An individual reader or writer cannot be used concurrently across threads. Each thread must use its own.
86 ///
87 /// The data written to an individual buffer can be heterogeneous in type, and the data written
88 /// to different buffers of a stream can be entirely different in type, number, and order. Just make sure
89 /// that the code reading from a particular buffer knows what to expect to read from it.
90 /// </remarks>
91 [GenerateTestsForBurstCompatibility]
92 public unsafe struct UnsafeStream
93 : INativeDisposable
94 {
95 [NativeDisableUnsafePtrRestriction]
96 internal AllocatorManager.Block m_BlockData;
97
98 /// <summary>
99 /// Initializes and returns an instance of UnsafeStream.
100 /// </summary>
101 /// <param name="bufferCount">The number of buffers to give the stream. You usually want
102 /// one buffer for each thread that will read or write the stream.</param>
103 /// <param name="allocator">The allocator to use.</param>
104 public UnsafeStream(int bufferCount, AllocatorManager.AllocatorHandle allocator)
105 {
106 AllocateBlock(out this, allocator);
107 AllocateForEach(bufferCount);
108 }
109
110 /// <summary>
111 /// Creates and schedules a job to allocate a new stream.
112 /// </summary>
113 /// <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.
114 ///
115 /// Using a job to allocate the buffers can be more efficient, particularly for a stream with many buffers.
116 /// </remarks>
117 /// <typeparam name="T">Ignored.</typeparam>
118 /// <param name="stream">Outputs the new stream.</param>
119 /// <param name="bufferCount">A list whose length determines the number of buffers in the stream.</param>
120 /// <param name="dependency">A job handle. The new job will depend upon this handle.</param>
121 /// <param name="allocator">The allocator to use.</param>
122 /// <returns>The handle of the new job.</returns>
123 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
124 public static JobHandle ScheduleConstruct<T>(out UnsafeStream stream, NativeList<T> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator)
125 where T : unmanaged
126 {
127 AllocateBlock(out stream, allocator);
128 var jobData = new ConstructJobList { List = (UntypedUnsafeList*)bufferCount.GetUnsafeList(), Container = stream };
129 return jobData.Schedule(dependency);
130 }
131
132 /// <summary>
133 /// Creates and schedules a job to allocate a new stream.
134 /// </summary>
135 /// <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.
136 ///
137 /// Allocating the buffers in a job can be more efficient, particularly for a stream with many buffers.
138 /// </remarks>
139 /// <param name="stream">Outputs the new stream.</param>
140 /// <param name="bufferCount">An array whose value at index 0 determines the number of buffers in the stream.</param>
141 /// <param name="dependency">A job handle. The new job will depend upon this handle.</param>
142 /// <param name="allocator">The allocator to use.</param>
143 /// <returns>The handle of the new job.</returns>
144 public static JobHandle ScheduleConstruct(out UnsafeStream stream, NativeArray<int> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator)
145 {
146 AllocateBlock(out stream, allocator);
147 var jobData = new ConstructJob { Length = bufferCount, Container = stream };
148 return jobData.Schedule(dependency);
149 }
150
151 internal static void AllocateBlock(out UnsafeStream stream, AllocatorManager.AllocatorHandle allocator)
152 {
153#if UNITY_2022_2_14F1_OR_NEWER
154 int maxThreadCount = JobsUtility.ThreadIndexCount;
155#else
156 int maxThreadCount = JobsUtility.MaxJobThreadCount;
157#endif
158
159 int blockCount = maxThreadCount;
160
161 int allocationSize = sizeof(UnsafeStreamBlockData) + sizeof(UnsafeStreamBlock*) * blockCount;
162
163 AllocatorManager.Block blk = AllocatorManager.AllocateBlock(ref allocator, allocationSize, 16, 1);
164 UnsafeUtility.MemClear( (void*)blk.Range.Pointer, blk.AllocatedBytes);
165
166 stream.m_BlockData = blk;
167
168 var blockData = (UnsafeStreamBlockData*)blk.Range.Pointer;
169 blockData->Allocator = allocator;
170 blockData->BlockCount = blockCount;
171 blockData->Blocks = (UnsafeStreamBlock**)(blk.Range.Pointer + sizeof(UnsafeStreamBlockData));
172
173 blockData->Ranges = default;
174 blockData->RangeCount = 0;
175 }
176
177 internal void AllocateForEach(int forEachCount)
178 {
179 long allocationSize = sizeof(UnsafeStreamRange) * forEachCount;
180
181 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
182 blockData->Ranges = AllocatorManager.AllocateBlock(ref m_BlockData.Range.Allocator, sizeof(UnsafeStreamRange), 16, forEachCount);
183 blockData->RangeCount = forEachCount;
184 UnsafeUtility.MemClear((void*)blockData->Ranges.Range.Pointer, blockData->Ranges.AllocatedBytes);
185 }
186
187 /// <summary>
188 /// Returns true if this stream is empty.
189 /// </summary>
190 /// <returns>True if this stream is empty or the stream has not been constructed.</returns>
191 public readonly bool IsEmpty()
192 {
193 if (!IsCreated)
194 {
195 return true;
196 }
197
198 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
199 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer;
200
201 for (int i = 0; i != blockData->RangeCount; i++)
202 {
203 if (ranges[i].ElementCount > 0)
204 {
205 return false;
206 }
207 }
208
209 return true;
210 }
211
212 /// <summary>
213 /// Whether this stream has been allocated (and not yet deallocated).
214 /// </summary>
215 /// <remarks>Does not necessarily reflect whether the buffers of the stream have themselves been allocated.</remarks>
216 /// <value>True if this stream has been allocated (and not yet deallocated).</value>
217 public readonly bool IsCreated
218 {
219 [MethodImpl(MethodImplOptions.AggressiveInlining)]
220 get => m_BlockData.Range.Pointer != IntPtr.Zero;
221 }
222
223 /// <summary>
224 /// The number of buffers in this stream.
225 /// </summary>
226 /// <value>The number of buffers in this stream.</value>
227 public readonly int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount;
228
229 /// <summary>
230 /// Returns a reader of this stream.
231 /// </summary>
232 /// <returns>A reader of this stream.</returns>
233 public Reader AsReader()
234 {
235 return new Reader(ref this);
236 }
237
238 /// <summary>
239 /// Returns a writer of this stream.
240 /// </summary>
241 /// <returns>A writer of this stream.</returns>
242 public Writer AsWriter()
243 {
244 return new Writer(ref this);
245 }
246
247 /// <summary>
248 /// Returns the total number of items in the buffers of this stream.
249 /// </summary>
250 /// <remarks>Each <see cref="Writer.Write{T}"/> and <see cref="Writer.Allocate"/> call increments this number.</remarks>
251 /// <returns>The total number of items in the buffers of this stream.</returns>
252 public int Count()
253 {
254 int itemCount = 0;
255
256 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
257 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer;
258
259 for (int i = 0; i != blockData->RangeCount; i++)
260 {
261 itemCount += ranges[i].ElementCount;
262 }
263
264 return itemCount;
265 }
266
267 /// <summary>
268 /// Returns a new NativeArray copy of this stream's data.
269 /// </summary>
270 /// <remarks>The length of the array will equal the count of this stream.
271 ///
272 /// Each buffer of this stream is copied to the array, one after the other.
273 /// </remarks>
274 /// <typeparam name="T">The type of values in the array.</typeparam>
275 /// <param name="allocator">The allocator to use.</param>
276 /// <returns>A new NativeArray copy of this stream's data.</returns>
277 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
278 public NativeArray<T> ToNativeArray<T>(AllocatorManager.AllocatorHandle allocator) where T : unmanaged
279 {
280 var array = CollectionHelper.CreateNativeArray<T>(Count(), allocator, NativeArrayOptions.UninitializedMemory);
281 var reader = AsReader();
282
283 int offset = 0;
284 for (int i = 0; i != reader.ForEachCount; i++)
285 {
286 reader.BeginForEachIndex(i);
287 int rangeItemCount = reader.RemainingItemCount;
288 for (int j = 0; j < rangeItemCount; ++j)
289 {
290 array[offset] = reader.Read<T>();
291 offset++;
292 }
293 reader.EndForEachIndex();
294 }
295
296 return array;
297 }
298
299 void Deallocate()
300 {
301 if (!IsCreated)
302 {
303 return;
304 }
305
306 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
307
308 for (int i = 0; i != blockData->BlockCount; i++)
309 {
310 UnsafeStreamBlock* block = blockData->Blocks[i];
311 while (block != null)
312 {
313 UnsafeStreamBlock* next = block->Next;
314 blockData->Free(block);
315 block = next;
316 }
317 }
318
319 blockData->Ranges.Dispose();
320
321 m_BlockData.Dispose();
322 m_BlockData = default;
323 }
324
325 /// <summary>
326 /// Releases all resources (memory).
327 /// </summary>
328 public void Dispose()
329 {
330 if (!IsCreated)
331 {
332 return;
333 }
334
335 Deallocate();
336 }
337
338 /// <summary>
339 /// Creates and schedules a job that will release all resources (memory and safety handles) of this stream.
340 /// </summary>
341 /// <param name="inputDeps">A job handle which the newly scheduled job will depend upon.</param>
342 /// <returns>The handle of a new job that will release all resources (memory and safety handles) of this stream.</returns>
343 public JobHandle Dispose(JobHandle inputDeps)
344 {
345 if (!IsCreated)
346 {
347 return inputDeps;
348 }
349
350 var jobHandle = new DisposeJob { Container = this }.Schedule(inputDeps);
351
352 m_BlockData = default;
353
354 return jobHandle;
355 }
356
357 [BurstCompile]
358 struct DisposeJob : IJob
359 {
360 public UnsafeStream Container;
361
362 public void Execute()
363 {
364 Container.Deallocate();
365 }
366 }
367
368 [BurstCompile]
369 struct ConstructJobList : IJob
370 {
371 public UnsafeStream Container;
372
373 [ReadOnly]
374 [NativeDisableUnsafePtrRestriction]
375 public UntypedUnsafeList* List;
376
377 public void Execute()
378 {
379 Container.AllocateForEach(List->m_length);
380 }
381 }
382
383 [BurstCompile]
384 struct ConstructJob : IJob
385 {
386 public UnsafeStream Container;
387
388 [ReadOnly]
389 public NativeArray<int> Length;
390
391 public void Execute()
392 {
393 Container.AllocateForEach(Length[0]);
394 }
395 }
396
397 /// <summary>
398 /// Writes data into a buffer of an <see cref="UnsafeStream"/>.
399 /// </summary>
400 /// <remarks>An individual writer can only be used for one buffer of one stream.
401 /// Do not create more than one writer for an individual buffer.</remarks>
402 [GenerateTestsForBurstCompatibility]
403 public unsafe struct Writer
404 {
405 [NativeDisableUnsafePtrRestriction]
406 internal AllocatorManager.Block m_BlockData;
407
408 [NativeDisableUnsafePtrRestriction]
409 UnsafeStreamBlock* m_CurrentBlock;
410
411 [NativeDisableUnsafePtrRestriction]
412 byte* m_CurrentPtr;
413
414 [NativeDisableUnsafePtrRestriction]
415 byte* m_CurrentBlockEnd;
416
417 internal int m_ForeachIndex;
418 int m_ElementCount;
419
420 [NativeDisableUnsafePtrRestriction]
421 UnsafeStreamBlock* m_FirstBlock;
422
423 int m_FirstOffset;
424 int m_NumberOfBlocks;
425
426 [NativeSetThreadIndex]
427 int m_ThreadIndex;
428
429 internal Writer(ref UnsafeStream stream)
430 {
431 m_BlockData = stream.m_BlockData;
432 m_ForeachIndex = int.MinValue;
433 m_ElementCount = -1;
434 m_CurrentBlock = null;
435 m_CurrentBlockEnd = null;
436 m_CurrentPtr = null;
437 m_FirstBlock = null;
438 m_NumberOfBlocks = 0;
439 m_FirstOffset = 0;
440 m_ThreadIndex = 0;
441 }
442
443 /// <summary>
444 /// The number of buffers in the stream of this writer.
445 /// </summary>
446 /// <value>The number of buffers in the stream of this writer.</value>
447 public int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount;
448
449 /// <summary>
450 /// Readies this writer to write to a particular buffer of the stream.
451 /// </summary>
452 /// <remarks>Must be called before using this writer. For an individual writer, call this method only once.
453 ///
454 /// When done using this writer, you must call <see cref="EndForEachIndex"/>.</remarks>
455 /// <param name="foreachIndex">The index of the buffer to write.</param>
456 public void BeginForEachIndex(int foreachIndex)
457 {
458 m_ForeachIndex = foreachIndex;
459 m_ElementCount = 0;
460 m_NumberOfBlocks = 0;
461 m_FirstBlock = m_CurrentBlock;
462 m_FirstOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock);
463 }
464
465 /// <summary>
466 /// Readies the buffer written by this writer for reading.
467 /// </summary>
468 /// <remarks>Must be called before reading the buffer written by this writer.</remarks>
469 public void EndForEachIndex()
470 {
471 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
472 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer;
473
474 ranges[m_ForeachIndex].ElementCount = m_ElementCount;
475 ranges[m_ForeachIndex].OffsetInFirstBlock = m_FirstOffset;
476 ranges[m_ForeachIndex].Block = m_FirstBlock;
477
478 ranges[m_ForeachIndex].LastOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock);
479 ranges[m_ForeachIndex].NumberOfBlocks = m_NumberOfBlocks;
480 }
481
482 /// <summary>
483 /// Write a value to a buffer.
484 /// </summary>
485 /// <remarks>The value is written to the buffer which was specified
486 /// with <see cref="BeginForEachIndex"/>.</remarks>
487 /// <typeparam name="T">The type of value to write.</typeparam>
488 /// <param name="value">The value to write.</param>
489 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
490 public void Write<T>(T value) where T : unmanaged
491 {
492 ref T dst = ref Allocate<T>();
493 dst = value;
494 }
495
496 /// <summary>
497 /// Allocate space in a buffer.
498 /// </summary>
499 /// <remarks>The space is allocated in the buffer which was specified
500 /// with <see cref="BeginForEachIndex"/>.</remarks>
501 /// <typeparam name="T">The type of value to allocate space for.</typeparam>
502 /// <returns>A reference to the allocation.</returns>
503 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
504 public ref T Allocate<T>() where T : unmanaged
505 {
506 int size = UnsafeUtility.SizeOf<T>();
507 return ref UnsafeUtility.AsRef<T>(Allocate(size));
508 }
509
510 /// <summary>
511 /// Allocate space in a buffer.
512 /// </summary>
513 /// <remarks>The space is allocated in the buffer which was specified
514 /// with <see cref="BeginForEachIndex"/>.</remarks>
515 /// <param name="size">The number of bytes to allocate.</param>
516 /// <returns>The allocation.</returns>
517 public byte* Allocate(int size)
518 {
519 byte* ptr = m_CurrentPtr;
520 m_CurrentPtr += size;
521
522 if (m_CurrentPtr > m_CurrentBlockEnd)
523 {
524 UnsafeStreamBlock* oldBlock = m_CurrentBlock;
525
526 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
527
528 m_CurrentBlock = blockData->Allocate(oldBlock, m_ThreadIndex);
529 m_CurrentPtr = m_CurrentBlock->Data;
530
531 if (m_FirstBlock == null)
532 {
533 m_FirstOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock);
534 m_FirstBlock = m_CurrentBlock;
535 }
536 else
537 {
538 m_NumberOfBlocks++;
539 }
540
541 m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize;
542 ptr = m_CurrentPtr;
543 m_CurrentPtr += size;
544 }
545
546 m_ElementCount++;
547
548 return ptr;
549 }
550 }
551
552 /// <summary>
553 /// Reads data from a buffer of an <see cref="UnsafeStream"/>.
554 /// </summary>
555 /// <remarks>An individual reader can only be used for one buffer of one stream.
556 /// Do not create more than one reader for an individual buffer.</remarks>
557 [GenerateTestsForBurstCompatibility]
558 public unsafe struct Reader
559 {
560 [NativeDisableUnsafePtrRestriction]
561 internal AllocatorManager.Block m_BlockData;
562
563 [NativeDisableUnsafePtrRestriction]
564 internal UnsafeStreamBlock* m_CurrentBlock;
565
566 [NativeDisableUnsafePtrRestriction]
567 internal byte* m_CurrentPtr;
568
569 [NativeDisableUnsafePtrRestriction]
570 internal byte* m_CurrentBlockEnd;
571
572 internal int m_RemainingItemCount;
573 internal int m_LastBlockSize;
574
575 internal Reader(ref UnsafeStream stream)
576 {
577 m_BlockData = stream.m_BlockData;
578 m_CurrentBlock = null;
579 m_CurrentPtr = null;
580 m_CurrentBlockEnd = null;
581 m_RemainingItemCount = 0;
582 m_LastBlockSize = 0;
583 }
584
585 /// <summary>
586 /// Readies this reader to read a particular buffer of the stream.
587 /// </summary>
588 /// <remarks>Must be called before using this reader. For an individual reader, call this method only once.
589 ///
590 /// When done using this reader, you must call <see cref="EndForEachIndex"/>.</remarks>
591 /// <param name="foreachIndex">The index of the buffer to read.</param>
592 /// <returns>The number of remaining elements to read from the buffer.</returns>
593 public int BeginForEachIndex(int foreachIndex)
594 {
595 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
596 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer;
597
598 m_RemainingItemCount = ranges[foreachIndex].ElementCount;
599 m_LastBlockSize = ranges[foreachIndex].LastOffset;
600
601 m_CurrentBlock = ranges[foreachIndex].Block;
602 m_CurrentPtr = (byte*)m_CurrentBlock + ranges[foreachIndex].OffsetInFirstBlock;
603 m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize;
604
605 return m_RemainingItemCount;
606 }
607
608 /// <summary>
609 /// Does nothing.
610 /// </summary>
611 /// <remarks>Included only for consistency with <see cref="NativeStream"/>.</remarks>
612 public void EndForEachIndex()
613 {
614 }
615
616 /// <summary>
617 /// The number of buffers in the stream of this reader.
618 /// </summary>
619 /// <value>The number of buffers in the stream of this reader.</value>
620 public int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount;
621
622 /// <summary>
623 /// The number of items not yet read from the buffer.
624 /// </summary>
625 /// <value>The number of items not yet read from the buffer.</value>
626 public int RemainingItemCount => m_RemainingItemCount;
627
628 /// <summary>
629 /// Returns a pointer to the next position to read from the buffer. Advances the reader some number of bytes.
630 /// </summary>
631 /// <param name="size">The number of bytes to advance the reader.</param>
632 /// <returns>A pointer to the next position to read from the buffer.</returns>
633 /// <exception cref="System.ArgumentException">Thrown if the reader has been advanced past the end of the buffer.</exception>
634 public byte* ReadUnsafePtr(int size)
635 {
636 m_RemainingItemCount--;
637
638 byte* ptr = m_CurrentPtr;
639 m_CurrentPtr += size;
640
641 if (m_CurrentPtr > m_CurrentBlockEnd)
642 {
643 m_CurrentBlock = m_CurrentBlock->Next;
644 m_CurrentPtr = m_CurrentBlock->Data;
645
646 m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize;
647
648 ptr = m_CurrentPtr;
649 m_CurrentPtr += size;
650 }
651
652 return ptr;
653 }
654
655 /// <summary>
656 /// Reads the next value from the buffer.
657 /// </summary>
658 /// <remarks>Each read advances the reader to the next item in the buffer.</remarks>
659 /// <typeparam name="T">The type of value to read.</typeparam>
660 /// <returns>A reference to the next value from the buffer.</returns>
661 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
662 public ref T Read<T>() where T : unmanaged
663 {
664 int size = UnsafeUtility.SizeOf<T>();
665 return ref UnsafeUtility.AsRef<T>(ReadUnsafePtr(size));
666 }
667
668 /// <summary>
669 /// Reads the next value from the buffer. Does not advance the reader.
670 /// </summary>
671 /// <typeparam name="T">The type of value to read.</typeparam>
672 /// <returns>A reference to the next value from the buffer.</returns>
673 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
674 public ref T Peek<T>() where T : unmanaged
675 {
676 int size = UnsafeUtility.SizeOf<T>();
677
678 byte* ptr = m_CurrentPtr;
679 if (ptr + size > m_CurrentBlockEnd)
680 {
681 ptr = m_CurrentBlock->Next->Data;
682 }
683
684 return ref UnsafeUtility.AsRef<T>(ptr);
685 }
686
687 /// <summary>
688 /// Returns the total number of items in the buffers of the stream.
689 /// </summary>
690 /// <returns>The total number of items in the buffers of the stream.</returns>
691 public int Count()
692 {
693 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer;
694 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer;
695
696 int itemCount = 0;
697 for (int i = 0; i != blockData->RangeCount; i++)
698 {
699 itemCount += ranges[i].ElementCount;
700 }
701
702 return itemCount;
703 }
704 }
705 }
706}