707 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			707 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Runtime.CompilerServices; | ||
|  | using Unity.Burst; | ||
|  | using Unity.Jobs; | ||
|  | using Unity.Jobs.LowLevel.Unsafe; | ||
|  | using UnityEngine.Assertions; | ||
|  | 
 | ||
|  | namespace Unity.Collections.LowLevel.Unsafe | ||
|  | { | ||
|  |     [GenerateTestsForBurstCompatibility] | ||
|  |     internal unsafe struct UnsafeStreamBlock | ||
|  |     { | ||
|  |         internal UnsafeStreamBlock* Next; | ||
|  |         internal fixed byte Data[1]; | ||
|  |     } | ||
|  | 
 | ||
|  |     [GenerateTestsForBurstCompatibility] | ||
|  |     internal unsafe struct UnsafeStreamRange | ||
|  |     { | ||
|  |         internal UnsafeStreamBlock* Block; | ||
|  |         internal int OffsetInFirstBlock; | ||
|  |         internal int ElementCount; | ||
|  | 
 | ||
|  |         /// One byte past the end of the last byte written | ||
|  |         internal int LastOffset; | ||
|  |         internal int NumberOfBlocks; | ||
|  |     } | ||
|  | 
 | ||
|  |     [GenerateTestsForBurstCompatibility] | ||
|  |     internal unsafe struct UnsafeStreamBlockData | ||
|  |     { | ||
|  |         internal const int AllocationSize = 4 * 1024; | ||
|  |         internal AllocatorManager.AllocatorHandle Allocator; | ||
|  | 
 | ||
|  |         internal UnsafeStreamBlock** Blocks; | ||
|  |         internal int BlockCount; | ||
|  | 
 | ||
|  |         internal AllocatorManager.Block Ranges; | ||
|  |         internal int RangeCount; | ||
|  | 
 | ||
|  |         internal UnsafeStreamBlock* Allocate(UnsafeStreamBlock* oldBlock, int threadIndex) | ||
|  |         { | ||
|  |             Assert.IsTrue(threadIndex < BlockCount && threadIndex >= 0); | ||
|  | 
 | ||
|  |             UnsafeStreamBlock* block = (UnsafeStreamBlock*)Memory.Unmanaged.Array.Resize(null, 0, AllocationSize, Allocator, 1, 16); | ||
|  |             block->Next = null; | ||
|  | 
 | ||
|  |             if (oldBlock == null) | ||
|  |             { | ||
|  |                 // Append our new block in front of the previous head. | ||
|  |                 block->Next = Blocks[threadIndex]; | ||
|  |                 Blocks[threadIndex] = block; | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 block->Next = oldBlock->Next; | ||
|  |                 oldBlock->Next = block; | ||
|  |             } | ||
|  | 
 | ||
|  |             return block; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void Free(UnsafeStreamBlock* oldBlock) | ||
|  |         { | ||
|  |             Memory.Unmanaged.Array.Resize(oldBlock, AllocationSize, 0, Allocator, 1, 16); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /// <summary> | ||
|  |     /// A set of untyped, append-only buffers. Allows for concurrent reading and concurrent writing without synchronization. | ||
|  |     /// </summary> | ||
|  |     /// <remarks> | ||
|  |     /// As long as each individual buffer is written in one thread and read in one thread, multiple | ||
|  |     /// threads can read and write the stream concurrently, *e.g.* | ||
|  |     /// while thread *A* reads from buffer *X* of a stream, thread *B* can read from | ||
|  |     /// buffer *Y* of the same stream. | ||
|  |     /// | ||
|  |     /// Each buffer is stored as a chain of blocks. When a write exceeds a buffer's current capacity, another block | ||
|  |     /// is allocated and added to the end of the chain. Effectively, expanding the buffer never requires copying the existing | ||
|  |     /// data (unlike, for example, with <see cref="NativeList{T}"/>). | ||
|  |     /// | ||
|  |     /// **All writing to a stream should be completed before the stream is first read. Do not write to a stream after the first read.** | ||
|  |     /// | ||
|  |     /// Writing is done with <see cref="NativeStream.Writer"/>, and reading is done with <see cref="NativeStream.Reader"/>. | ||
|  |     /// An individual reader or writer cannot be used concurrently across threads. Each thread must use its own. | ||
|  |     /// | ||
|  |     /// The data written to an individual buffer can be heterogeneous in type, and the data written | ||
|  |     /// to different buffers of a stream can be entirely different in type, number, and order. Just make sure | ||
|  |     /// that the code reading from a particular buffer knows what to expect to read from it. | ||
|  |     /// </remarks> | ||
|  |     [GenerateTestsForBurstCompatibility] | ||
|  |     public unsafe struct UnsafeStream | ||
|  |         : INativeDisposable | ||
|  |     { | ||
|  |         [NativeDisableUnsafePtrRestriction] | ||
|  |         internal AllocatorManager.Block m_BlockData; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Initializes and returns an instance of UnsafeStream. | ||
|  |         /// </summary> | ||
|  |         /// <param name="bufferCount">The number of buffers to give the stream. You usually want | ||
|  |         /// one buffer for each thread that will read or write the stream.</param> | ||
|  |         /// <param name="allocator">The allocator to use.</param> | ||
|  |         public UnsafeStream(int bufferCount, AllocatorManager.AllocatorHandle allocator) | ||
|  |         { | ||
|  |             AllocateBlock(out this, allocator); | ||
|  |             AllocateForEach(bufferCount); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Creates and schedules a job to allocate a new stream. | ||
|  |         /// </summary> | ||
|  |         /// <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. | ||
|  |         /// | ||
|  |         /// Using a job to allocate the buffers can be more efficient, particularly for a stream with many buffers. | ||
|  |         /// </remarks> | ||
|  |         /// <typeparam name="T">Ignored.</typeparam> | ||
|  |         /// <param name="stream">Outputs the new stream.</param> | ||
|  |         /// <param name="bufferCount">A list whose length determines the number of buffers in the stream.</param> | ||
|  |         /// <param name="dependency">A job handle. The new job will depend upon this handle.</param> | ||
|  |         /// <param name="allocator">The allocator to use.</param> | ||
|  |         /// <returns>The handle of the new job.</returns> | ||
|  |         [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |         public static JobHandle ScheduleConstruct<T>(out UnsafeStream stream, NativeList<T> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator) | ||
|  |             where T : unmanaged | ||
|  |         { | ||
|  |             AllocateBlock(out stream, allocator); | ||
|  |             var jobData = new ConstructJobList { List = (UntypedUnsafeList*)bufferCount.GetUnsafeList(), Container = stream }; | ||
|  |             return jobData.Schedule(dependency); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Creates and schedules a job to allocate a new stream. | ||
|  |         /// </summary> | ||
|  |         /// <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. | ||
|  |         /// | ||
|  |         /// Allocating the buffers in a job can be more efficient, particularly for a stream with many buffers. | ||
|  |         /// </remarks> | ||
|  |         /// <param name="stream">Outputs the new stream.</param> | ||
|  |         /// <param name="bufferCount">An array whose value at index 0 determines the number of buffers in the stream.</param> | ||
|  |         /// <param name="dependency">A job handle. The new job will depend upon this handle.</param> | ||
|  |         /// <param name="allocator">The allocator to use.</param> | ||
|  |         /// <returns>The handle of the new job.</returns> | ||
|  |         public static JobHandle ScheduleConstruct(out UnsafeStream stream, NativeArray<int> bufferCount, JobHandle dependency, AllocatorManager.AllocatorHandle allocator) | ||
|  |         { | ||
|  |             AllocateBlock(out stream, allocator); | ||
|  |             var jobData = new ConstructJob { Length = bufferCount, Container = stream }; | ||
|  |             return jobData.Schedule(dependency); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void AllocateBlock(out UnsafeStream stream, AllocatorManager.AllocatorHandle allocator) | ||
|  |         { | ||
|  | #if UNITY_2022_2_14F1_OR_NEWER | ||
|  |             int maxThreadCount = JobsUtility.ThreadIndexCount; | ||
|  | #else | ||
|  |             int maxThreadCount = JobsUtility.MaxJobThreadCount; | ||
|  | #endif | ||
|  | 
 | ||
|  |             int blockCount = maxThreadCount; | ||
|  | 
 | ||
|  |             int allocationSize = sizeof(UnsafeStreamBlockData) + sizeof(UnsafeStreamBlock*) * blockCount; | ||
|  | 
 | ||
|  |             AllocatorManager.Block blk = AllocatorManager.AllocateBlock(ref allocator, allocationSize, 16, 1); | ||
|  |             UnsafeUtility.MemClear( (void*)blk.Range.Pointer, blk.AllocatedBytes); | ||
|  | 
 | ||
|  |             stream.m_BlockData = blk; | ||
|  | 
 | ||
|  |             var blockData = (UnsafeStreamBlockData*)blk.Range.Pointer; | ||
|  |             blockData->Allocator = allocator; | ||
|  |             blockData->BlockCount = blockCount; | ||
|  |             blockData->Blocks = (UnsafeStreamBlock**)(blk.Range.Pointer + sizeof(UnsafeStreamBlockData)); | ||
|  | 
 | ||
|  |             blockData->Ranges = default; | ||
|  |             blockData->RangeCount = 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void AllocateForEach(int forEachCount) | ||
|  |         { | ||
|  |             long allocationSize = sizeof(UnsafeStreamRange) * forEachCount; | ||
|  | 
 | ||
|  |             var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  |             blockData->Ranges = AllocatorManager.AllocateBlock(ref m_BlockData.Range.Allocator, sizeof(UnsafeStreamRange), 16, forEachCount); | ||
|  |             blockData->RangeCount = forEachCount; | ||
|  |             UnsafeUtility.MemClear((void*)blockData->Ranges.Range.Pointer, blockData->Ranges.AllocatedBytes); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns true if this stream is empty. | ||
|  |         /// </summary> | ||
|  |         /// <returns>True if this stream is empty or the stream has not been constructed.</returns> | ||
|  |         public readonly bool IsEmpty() | ||
|  |         { | ||
|  |             if (!IsCreated) | ||
|  |             { | ||
|  |                 return true; | ||
|  |             } | ||
|  | 
 | ||
|  |             var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  |             var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; | ||
|  | 
 | ||
|  |             for (int i = 0; i != blockData->RangeCount; i++) | ||
|  |             { | ||
|  |                 if (ranges[i].ElementCount > 0) | ||
|  |                 { | ||
|  |                     return false; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Whether this stream has been allocated (and not yet deallocated). | ||
|  |         /// </summary> | ||
|  |         /// <remarks>Does not necessarily reflect whether the buffers of the stream have themselves been allocated.</remarks> | ||
|  |         /// <value>True if this stream has been allocated (and not yet deallocated).</value> | ||
|  |         public readonly bool IsCreated | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get => m_BlockData.Range.Pointer != IntPtr.Zero; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The number of buffers in this stream. | ||
|  |         /// </summary> | ||
|  |         /// <value>The number of buffers in this stream.</value> | ||
|  |         public readonly int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns a reader of this stream. | ||
|  |         /// </summary> | ||
|  |         /// <returns>A reader of this stream.</returns> | ||
|  |         public Reader AsReader() | ||
|  |         { | ||
|  |             return new Reader(ref this); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns a writer of this stream. | ||
|  |         /// </summary> | ||
|  |         /// <returns>A writer of this stream.</returns> | ||
|  |         public Writer AsWriter() | ||
|  |         { | ||
|  |             return new Writer(ref this); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns the total number of items in the buffers of this stream. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>Each <see cref="Writer.Write{T}"/> and <see cref="Writer.Allocate"/> call increments this number.</remarks> | ||
|  |         /// <returns>The total number of items in the buffers of this stream.</returns> | ||
|  |         public int Count() | ||
|  |         { | ||
|  |             int itemCount = 0; | ||
|  | 
 | ||
|  |             var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  |             var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; | ||
|  | 
 | ||
|  |             for (int i = 0; i != blockData->RangeCount; i++) | ||
|  |             { | ||
|  |                 itemCount += ranges[i].ElementCount; | ||
|  |             } | ||
|  | 
 | ||
|  |             return itemCount; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Returns a new NativeArray copy of this stream's data. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>The length of the array will equal the count of this stream. | ||
|  |         /// | ||
|  |         /// Each buffer of this stream is copied to the array, one after the other. | ||
|  |         /// </remarks> | ||
|  |         /// <typeparam name="T">The type of values in the array.</typeparam> | ||
|  |         /// <param name="allocator">The allocator to use.</param> | ||
|  |         /// <returns>A new NativeArray copy of this stream's data.</returns> | ||
|  |         [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |         public NativeArray<T> ToNativeArray<T>(AllocatorManager.AllocatorHandle allocator) where T : unmanaged | ||
|  |         { | ||
|  |             var array = CollectionHelper.CreateNativeArray<T>(Count(), allocator, NativeArrayOptions.UninitializedMemory); | ||
|  |             var reader = AsReader(); | ||
|  | 
 | ||
|  |             int offset = 0; | ||
|  |             for (int i = 0; i != reader.ForEachCount; i++) | ||
|  |             { | ||
|  |                 reader.BeginForEachIndex(i); | ||
|  |                 int rangeItemCount = reader.RemainingItemCount; | ||
|  |                 for (int j = 0; j < rangeItemCount; ++j) | ||
|  |                 { | ||
|  |                     array[offset] = reader.Read<T>(); | ||
|  |                     offset++; | ||
|  |                 } | ||
|  |                 reader.EndForEachIndex(); | ||
|  |             } | ||
|  | 
 | ||
|  |             return array; | ||
|  |         } | ||
|  | 
 | ||
|  |         void Deallocate() | ||
|  |         { | ||
|  |             if (!IsCreated) | ||
|  |             { | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  | 
 | ||
|  |             for (int i = 0; i != blockData->BlockCount; i++) | ||
|  |             { | ||
|  |                 UnsafeStreamBlock* block = blockData->Blocks[i]; | ||
|  |                 while (block != null) | ||
|  |                 { | ||
|  |                     UnsafeStreamBlock* next = block->Next; | ||
|  |                     blockData->Free(block); | ||
|  |                     block = next; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             blockData->Ranges.Dispose(); | ||
|  | 
 | ||
|  |             m_BlockData.Dispose(); | ||
|  |             m_BlockData = default; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Releases all resources (memory). | ||
|  |         /// </summary> | ||
|  |         public void Dispose() | ||
|  |         { | ||
|  |             if (!IsCreated) | ||
|  |             { | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             Deallocate(); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Creates and schedules a job that will release all resources (memory and safety handles) of this stream. | ||
|  |         /// </summary> | ||
|  |         /// <param name="inputDeps">A job handle which the newly scheduled job will depend upon.</param> | ||
|  |         /// <returns>The handle of a new job that will release all resources (memory and safety handles) of this stream.</returns> | ||
|  |         public JobHandle Dispose(JobHandle inputDeps) | ||
|  |         { | ||
|  |             if (!IsCreated) | ||
|  |             { | ||
|  |                 return inputDeps; | ||
|  |             } | ||
|  | 
 | ||
|  |             var jobHandle = new DisposeJob { Container = this }.Schedule(inputDeps); | ||
|  | 
 | ||
|  |             m_BlockData = default; | ||
|  | 
 | ||
|  |             return jobHandle; | ||
|  |         } | ||
|  | 
 | ||
|  |         [BurstCompile] | ||
|  |         struct DisposeJob : IJob | ||
|  |         { | ||
|  |             public UnsafeStream Container; | ||
|  | 
 | ||
|  |             public void Execute() | ||
|  |             { | ||
|  |                 Container.Deallocate(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [BurstCompile] | ||
|  |         struct ConstructJobList : IJob | ||
|  |         { | ||
|  |             public UnsafeStream Container; | ||
|  | 
 | ||
|  |             [ReadOnly] | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             public UntypedUnsafeList* List; | ||
|  | 
 | ||
|  |             public void Execute() | ||
|  |             { | ||
|  |                 Container.AllocateForEach(List->m_length); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [BurstCompile] | ||
|  |         struct ConstructJob : IJob | ||
|  |         { | ||
|  |             public UnsafeStream Container; | ||
|  | 
 | ||
|  |             [ReadOnly] | ||
|  |             public NativeArray<int> Length; | ||
|  | 
 | ||
|  |             public void Execute() | ||
|  |             { | ||
|  |                 Container.AllocateForEach(Length[0]); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Writes data into a buffer of an <see cref="UnsafeStream"/>. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>An individual writer can only be used for one buffer of one stream. | ||
|  |         /// Do not create more than one writer for an individual buffer.</remarks> | ||
|  |         [GenerateTestsForBurstCompatibility] | ||
|  |         public unsafe struct Writer | ||
|  |         { | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             internal AllocatorManager.Block m_BlockData; | ||
|  | 
 | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             UnsafeStreamBlock* m_CurrentBlock; | ||
|  | 
 | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             byte* m_CurrentPtr; | ||
|  | 
 | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             byte* m_CurrentBlockEnd; | ||
|  | 
 | ||
|  |             internal int m_ForeachIndex; | ||
|  |             int m_ElementCount; | ||
|  | 
 | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             UnsafeStreamBlock* m_FirstBlock; | ||
|  | 
 | ||
|  |             int m_FirstOffset; | ||
|  |             int m_NumberOfBlocks; | ||
|  | 
 | ||
|  |             [NativeSetThreadIndex] | ||
|  |             int m_ThreadIndex; | ||
|  | 
 | ||
|  |             internal Writer(ref UnsafeStream stream) | ||
|  |             { | ||
|  |                 m_BlockData = stream.m_BlockData; | ||
|  |                 m_ForeachIndex = int.MinValue; | ||
|  |                 m_ElementCount = -1; | ||
|  |                 m_CurrentBlock = null; | ||
|  |                 m_CurrentBlockEnd = null; | ||
|  |                 m_CurrentPtr = null; | ||
|  |                 m_FirstBlock = null; | ||
|  |                 m_NumberOfBlocks = 0; | ||
|  |                 m_FirstOffset = 0; | ||
|  |                 m_ThreadIndex = 0; | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// The number of buffers in the stream of this writer. | ||
|  |             /// </summary> | ||
|  |             /// <value>The number of buffers in the stream of this writer.</value> | ||
|  |             public int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount; | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Readies this writer to write to a particular buffer of the stream. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>Must be called before using this writer. For an individual writer, call this method only once. | ||
|  |             /// | ||
|  |             /// When done using this writer, you must call <see cref="EndForEachIndex"/>.</remarks> | ||
|  |             /// <param name="foreachIndex">The index of the buffer to write.</param> | ||
|  |             public void BeginForEachIndex(int foreachIndex) | ||
|  |             { | ||
|  |                 m_ForeachIndex = foreachIndex; | ||
|  |                 m_ElementCount = 0; | ||
|  |                 m_NumberOfBlocks = 0; | ||
|  |                 m_FirstBlock = m_CurrentBlock; | ||
|  |                 m_FirstOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock); | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Readies the buffer written by this writer for reading. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>Must be called before reading the buffer written by this writer.</remarks> | ||
|  |             public void EndForEachIndex() | ||
|  |             { | ||
|  |                 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  |                 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; | ||
|  | 
 | ||
|  |                 ranges[m_ForeachIndex].ElementCount = m_ElementCount; | ||
|  |                 ranges[m_ForeachIndex].OffsetInFirstBlock = m_FirstOffset; | ||
|  |                 ranges[m_ForeachIndex].Block = m_FirstBlock; | ||
|  | 
 | ||
|  |                 ranges[m_ForeachIndex].LastOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock); | ||
|  |                 ranges[m_ForeachIndex].NumberOfBlocks = m_NumberOfBlocks; | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Write a value to a buffer. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>The value is written to the buffer which was specified | ||
|  |             /// with <see cref="BeginForEachIndex"/>.</remarks> | ||
|  |             /// <typeparam name="T">The type of value to write.</typeparam> | ||
|  |             /// <param name="value">The value to write.</param> | ||
|  |             [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |             public void Write<T>(T value) where T : unmanaged | ||
|  |             { | ||
|  |                 ref T dst = ref Allocate<T>(); | ||
|  |                 dst = value; | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Allocate space in a buffer. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>The space is allocated in the buffer which was specified | ||
|  |             /// with <see cref="BeginForEachIndex"/>.</remarks> | ||
|  |             /// <typeparam name="T">The type of value to allocate space for.</typeparam> | ||
|  |             /// <returns>A reference to the allocation.</returns> | ||
|  |             [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |             public ref T Allocate<T>() where T : unmanaged | ||
|  |             { | ||
|  |                 int size = UnsafeUtility.SizeOf<T>(); | ||
|  |                 return ref UnsafeUtility.AsRef<T>(Allocate(size)); | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Allocate space in a buffer. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>The space is allocated in the buffer which was specified | ||
|  |             /// with <see cref="BeginForEachIndex"/>.</remarks> | ||
|  |             /// <param name="size">The number of bytes to allocate.</param> | ||
|  |             /// <returns>The allocation.</returns> | ||
|  |             public byte* Allocate(int size) | ||
|  |             { | ||
|  |                 byte* ptr = m_CurrentPtr; | ||
|  |                 m_CurrentPtr += size; | ||
|  | 
 | ||
|  |                 if (m_CurrentPtr > m_CurrentBlockEnd) | ||
|  |                 { | ||
|  |                     UnsafeStreamBlock* oldBlock = m_CurrentBlock; | ||
|  | 
 | ||
|  |                     var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  | 
 | ||
|  |                     m_CurrentBlock = blockData->Allocate(oldBlock, m_ThreadIndex); | ||
|  |                     m_CurrentPtr = m_CurrentBlock->Data; | ||
|  | 
 | ||
|  |                     if (m_FirstBlock == null) | ||
|  |                     { | ||
|  |                         m_FirstOffset = (int)(m_CurrentPtr - (byte*)m_CurrentBlock); | ||
|  |                         m_FirstBlock = m_CurrentBlock; | ||
|  |                     } | ||
|  |                     else | ||
|  |                     { | ||
|  |                         m_NumberOfBlocks++; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize; | ||
|  |                     ptr = m_CurrentPtr; | ||
|  |                     m_CurrentPtr += size; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 m_ElementCount++; | ||
|  | 
 | ||
|  |                 return ptr; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Reads data from a buffer of an <see cref="UnsafeStream"/>. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>An individual reader can only be used for one buffer of one stream. | ||
|  |         /// Do not create more than one reader for an individual buffer.</remarks> | ||
|  |         [GenerateTestsForBurstCompatibility] | ||
|  |         public unsafe struct Reader | ||
|  |         { | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             internal AllocatorManager.Block m_BlockData; | ||
|  | 
 | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             internal UnsafeStreamBlock* m_CurrentBlock; | ||
|  | 
 | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             internal byte* m_CurrentPtr; | ||
|  | 
 | ||
|  |             [NativeDisableUnsafePtrRestriction] | ||
|  |             internal byte* m_CurrentBlockEnd; | ||
|  | 
 | ||
|  |             internal int m_RemainingItemCount; | ||
|  |             internal int m_LastBlockSize; | ||
|  | 
 | ||
|  |             internal Reader(ref UnsafeStream stream) | ||
|  |             { | ||
|  |                 m_BlockData = stream.m_BlockData; | ||
|  |                 m_CurrentBlock = null; | ||
|  |                 m_CurrentPtr = null; | ||
|  |                 m_CurrentBlockEnd = null; | ||
|  |                 m_RemainingItemCount = 0; | ||
|  |                 m_LastBlockSize = 0; | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Readies this reader to read a particular buffer of the stream. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>Must be called before using this reader. For an individual reader, call this method only once. | ||
|  |             /// | ||
|  |             /// When done using this reader, you must call <see cref="EndForEachIndex"/>.</remarks> | ||
|  |             /// <param name="foreachIndex">The index of the buffer to read.</param> | ||
|  |             /// <returns>The number of remaining elements to read from the buffer.</returns> | ||
|  |             public int BeginForEachIndex(int foreachIndex) | ||
|  |             { | ||
|  |                 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  |                 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; | ||
|  | 
 | ||
|  |                 m_RemainingItemCount = ranges[foreachIndex].ElementCount; | ||
|  |                 m_LastBlockSize = ranges[foreachIndex].LastOffset; | ||
|  | 
 | ||
|  |                 m_CurrentBlock = ranges[foreachIndex].Block; | ||
|  |                 m_CurrentPtr = (byte*)m_CurrentBlock + ranges[foreachIndex].OffsetInFirstBlock; | ||
|  |                 m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize; | ||
|  | 
 | ||
|  |                 return m_RemainingItemCount; | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Does nothing. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>Included only for consistency with <see cref="NativeStream"/>.</remarks> | ||
|  |             public void EndForEachIndex() | ||
|  |             { | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// The number of buffers in the stream of this reader. | ||
|  |             /// </summary> | ||
|  |             /// <value>The number of buffers in the stream of this reader.</value> | ||
|  |             public int ForEachCount => ((UnsafeStreamBlockData*)m_BlockData.Range.Pointer)->RangeCount; | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// The number of items not yet read from the buffer. | ||
|  |             /// </summary> | ||
|  |             /// <value>The number of items not yet read from the buffer.</value> | ||
|  |             public int RemainingItemCount => m_RemainingItemCount; | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Returns a pointer to the next position to read from the buffer. Advances the reader some number of bytes. | ||
|  |             /// </summary> | ||
|  |             /// <param name="size">The number of bytes to advance the reader.</param> | ||
|  |             /// <returns>A pointer to the next position to read from the buffer.</returns> | ||
|  |             /// <exception cref="System.ArgumentException">Thrown if the reader has been advanced past the end of the buffer.</exception> | ||
|  |             public byte* ReadUnsafePtr(int size) | ||
|  |             { | ||
|  |                 m_RemainingItemCount--; | ||
|  | 
 | ||
|  |                 byte* ptr = m_CurrentPtr; | ||
|  |                 m_CurrentPtr += size; | ||
|  | 
 | ||
|  |                 if (m_CurrentPtr > m_CurrentBlockEnd) | ||
|  |                 { | ||
|  |                     m_CurrentBlock = m_CurrentBlock->Next; | ||
|  |                     m_CurrentPtr = m_CurrentBlock->Data; | ||
|  | 
 | ||
|  |                     m_CurrentBlockEnd = (byte*)m_CurrentBlock + UnsafeStreamBlockData.AllocationSize; | ||
|  | 
 | ||
|  |                     ptr = m_CurrentPtr; | ||
|  |                     m_CurrentPtr += size; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 return ptr; | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Reads the next value from the buffer. | ||
|  |             /// </summary> | ||
|  |             /// <remarks>Each read advances the reader to the next item in the buffer.</remarks> | ||
|  |             /// <typeparam name="T">The type of value to read.</typeparam> | ||
|  |             /// <returns>A reference to the next value from the buffer.</returns> | ||
|  |             [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |             public ref T Read<T>() where T : unmanaged | ||
|  |             { | ||
|  |                 int size = UnsafeUtility.SizeOf<T>(); | ||
|  |                 return ref UnsafeUtility.AsRef<T>(ReadUnsafePtr(size)); | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Reads the next value from the buffer. Does not advance the reader. | ||
|  |             /// </summary> | ||
|  |             /// <typeparam name="T">The type of value to read.</typeparam> | ||
|  |             /// <returns>A reference to the next value from the buffer.</returns> | ||
|  |             [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |             public ref T Peek<T>() where T : unmanaged | ||
|  |             { | ||
|  |                 int size = UnsafeUtility.SizeOf<T>(); | ||
|  | 
 | ||
|  |                 byte* ptr = m_CurrentPtr; | ||
|  |                 if (ptr + size > m_CurrentBlockEnd) | ||
|  |                 { | ||
|  |                     ptr = m_CurrentBlock->Next->Data; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 return ref UnsafeUtility.AsRef<T>(ptr); | ||
|  |             } | ||
|  | 
 | ||
|  |             /// <summary> | ||
|  |             /// Returns the total number of items in the buffers of the stream. | ||
|  |             /// </summary> | ||
|  |             /// <returns>The total number of items in the buffers of the stream.</returns> | ||
|  |             public int Count() | ||
|  |             { | ||
|  |                 var blockData = (UnsafeStreamBlockData*)m_BlockData.Range.Pointer; | ||
|  |                 var ranges = (UnsafeStreamRange*)blockData->Ranges.Range.Pointer; | ||
|  | 
 | ||
|  |                 int itemCount = 0; | ||
|  |                 for (int i = 0; i != blockData->RangeCount; i++) | ||
|  |                 { | ||
|  |                     itemCount += ranges[i].ElementCount; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 return itemCount; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } |