510 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			510 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using AOT; | ||
|  | using System; | ||
|  | using System.Runtime.CompilerServices; | ||
|  | using System.Threading; | ||
|  | using UnityEngine.Assertions; | ||
|  | using Unity.Burst; | ||
|  | using Unity.Collections.LowLevel.Unsafe; | ||
|  | using Unity.Jobs.LowLevel.Unsafe; | ||
|  | using Unity.Mathematics; | ||
|  | 
 | ||
|  | namespace Unity.Collections | ||
|  | { | ||
|  |     internal struct UnmanagedArray<T> : IDisposable where T : unmanaged | ||
|  |     { | ||
|  |         IntPtr m_pointer; | ||
|  |         int m_length; | ||
|  |         public int Length => m_length; | ||
|  |         AllocatorManager.AllocatorHandle m_allocator; | ||
|  |         public UnmanagedArray(int length, AllocatorManager.AllocatorHandle allocator) | ||
|  |         { | ||
|  |             unsafe | ||
|  |             { | ||
|  |                 m_pointer = (IntPtr)Memory.Unmanaged.Array.Allocate<T>(length, allocator); | ||
|  |             } | ||
|  |             m_length = length; | ||
|  |             m_allocator = allocator; | ||
|  |         } | ||
|  |         public void Dispose() | ||
|  |         { | ||
|  |             unsafe | ||
|  |             { | ||
|  |                 Memory.Unmanaged.Free((T*)m_pointer, Allocator.Persistent); | ||
|  |             } | ||
|  |         } | ||
|  |         public unsafe T* GetUnsafePointer() | ||
|  |         { | ||
|  |             return (T*)m_pointer; | ||
|  |         } | ||
|  |         public ref T this[int index] | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get { unsafe { return ref ((T*)m_pointer)[index]; } } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /// <summary> | ||
|  |     /// An allocator that is fast like a linear allocator, is threadsafe, and automatically invalidates | ||
|  |     /// all allocations made from it, when "rewound" by the user. | ||
|  |     /// </summary> | ||
|  |     [BurstCompile] | ||
|  |     public struct RewindableAllocator : AllocatorManager.IAllocator | ||
|  |     { | ||
|  |         internal struct Union | ||
|  |         { | ||
|  |             internal long m_long; | ||
|  | 
 | ||
|  |             // Number of bits used to store current position in a block to give out memory. | ||
|  |             // This limits the maximum block size to 1TB (2^40). | ||
|  |             const int currentBits = 40; | ||
|  |             // Offset of current position in m_long | ||
|  |             const int currentOffset = 0; | ||
|  |             // Number of bits used to store the allocation count in a block | ||
|  |             const long currentMask = (1L << currentBits) - 1; | ||
|  | 
 | ||
|  |             // Number of bits used to store allocation count in a block. | ||
|  |             // This limits the maximum number of allocations per block to 16 millions (2^24) | ||
|  |             const int allocCountBits = 24; | ||
|  |             // Offset of allocation count in m_long | ||
|  |             const int allocCountOffset = currentOffset + currentBits; | ||
|  |             const long allocCountMask = (1L << allocCountBits) - 1; | ||
|  | 
 | ||
|  |             // Current position in a block to give out memory | ||
|  |             internal long m_current | ||
|  |             { | ||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |                 get | ||
|  |                 { | ||
|  |                     return (m_long >> currentOffset) & currentMask; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |                 set | ||
|  |                 { | ||
|  |                     m_long &= ~(currentMask << currentOffset); | ||
|  |                     m_long |= (value & currentMask) << currentOffset; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             // The number of allocations in a block | ||
|  |             internal long m_allocCount | ||
|  |             { | ||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |                 get | ||
|  |                 { | ||
|  |                     return (m_long >> allocCountOffset) & allocCountMask; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |                 set | ||
|  |                 { | ||
|  |                     m_long &= ~(allocCountMask << allocCountOffset); | ||
|  |                     m_long |= (value & allocCountMask) << allocCountOffset; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         [GenerateTestsForBurstCompatibility] | ||
|  |         internal unsafe struct MemoryBlock : IDisposable | ||
|  |         { | ||
|  |             // can't align any coarser than this many bytes | ||
|  |             public const int kMaximumAlignment = 16384; | ||
|  |             // pointer to contiguous memory | ||
|  |             public byte* m_pointer; | ||
|  |             // how many bytes of contiguous memory it points to | ||
|  |             public long m_bytes; | ||
|  |             // Union of current position to give out memory and allocation counts | ||
|  |             public Union m_union; | ||
|  | 
 | ||
|  |             public MemoryBlock(long bytes) | ||
|  |             { | ||
|  |                 m_pointer = (byte*)Memory.Unmanaged.Allocate(bytes, kMaximumAlignment, Allocator.Persistent); | ||
|  |                 Assert.IsTrue(m_pointer != null, "Memory block allocation failed, system out of memory"); | ||
|  |                 m_bytes = bytes; | ||
|  |                 m_union = default; | ||
|  |             } | ||
|  | 
 | ||
|  |             public void Rewind() | ||
|  |             { | ||
|  |                 m_union = default; | ||
|  |             } | ||
|  | 
 | ||
|  |             public void Dispose() | ||
|  |             { | ||
|  |                 Memory.Unmanaged.Free(m_pointer, Allocator.Persistent); | ||
|  |                 m_pointer = null; | ||
|  |                 m_bytes = 0; | ||
|  |                 m_union = default; | ||
|  |             } | ||
|  | 
 | ||
|  |             public bool Contains(IntPtr ptr) | ||
|  |             { | ||
|  |                 unsafe | ||
|  |                 { | ||
|  |                     void* pointer = (void*)ptr; | ||
|  |                     return (pointer >= m_pointer) && (pointer < m_pointer + m_union.m_current); | ||
|  |                 } | ||
|  |             } | ||
|  |         }; | ||
|  | 
 | ||
|  |         // Log2 of Maximum memory block size.  Cannot exceed MemoryBlock.Union.currentBits. | ||
|  |         const int kLog2MaxMemoryBlockSize = 26; | ||
|  | 
 | ||
|  |         // Maximum memory block size.  Can exceed maximum memory block size if user requested more. | ||
|  |         const long kMaxMemoryBlockSize = 1L << kLog2MaxMemoryBlockSize;  // 64MB | ||
|  | 
 | ||
|  |         /// Minimum memory block size, 128KB. | ||
|  |         const long kMinMemoryBlockSize = 128 * 1024; | ||
|  | 
 | ||
|  |         /// Maximum number of memory blocks. | ||
|  |         const int kMaxNumBlocks = 64; | ||
|  | 
 | ||
|  |         // Bit mask (bit 31) of the memory block busy flag indicating whether the block is busy rewinding. | ||
|  |         const int kBlockBusyRewindMask = 0x1 << 31; | ||
|  | 
 | ||
|  |         // Bit mask of the memory block busy flag indicating whether the block is busy allocating. | ||
|  |         const int kBlockBusyAllocateMask = ~kBlockBusyRewindMask; | ||
|  | 
 | ||
|  |         Spinner m_spinner; | ||
|  |         AllocatorManager.AllocatorHandle m_handle; | ||
|  |         UnmanagedArray<MemoryBlock> m_block; | ||
|  |         int m_last;                 // highest-index block that has memory to allocate from | ||
|  |         int m_used;                 // highest-index block that we actually allocated from, since last rewind | ||
|  |         byte m_enableBlockFree;     // flag indicating if allocator enables individual block free | ||
|  |         byte m_reachMaxBlockSize;   // flag indicating if reach maximum block size | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Initializes the allocator. Must be called before first use. | ||
|  |         /// </summary> | ||
|  |         /// <param name="initialSizeInBytes">The initial capacity of the allocator, in bytes</param> | ||
|  |         /// <param name="enableBlockFree">A flag indicating if allocator enables individual block free</param> | ||
|  |         public void Initialize(int initialSizeInBytes, bool enableBlockFree = false) | ||
|  |         { | ||
|  |             m_spinner = default; | ||
|  |             m_block = new UnmanagedArray<MemoryBlock>(kMaxNumBlocks, Allocator.Persistent); | ||
|  |             // Initial block size should be larger than min block size | ||
|  |             var blockSize = initialSizeInBytes > kMinMemoryBlockSize ? initialSizeInBytes : kMinMemoryBlockSize; | ||
|  |             m_block[0] = new MemoryBlock(blockSize); | ||
|  |             m_last = m_used = 0; | ||
|  |             m_enableBlockFree = enableBlockFree ? (byte)1 : (byte)0; | ||
|  |             m_reachMaxBlockSize = (initialSizeInBytes >= kMaxMemoryBlockSize) ? (byte)1 : (byte)0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Property to get and set enable block free flag, a flag indicating whether the allocator should enable individual block to be freed. | ||
|  |         /// </summary> | ||
|  |         public bool EnableBlockFree | ||
|  |         { | ||
|  |             get => m_enableBlockFree != 0; | ||
|  |             set => m_enableBlockFree = value ? (byte)1 : (byte)0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Retrieves the number of memory blocks that the allocator has requested from the system. | ||
|  |         /// </summary> | ||
|  |         public int BlocksAllocated => (int)(m_last + 1); | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Retrieves the size of the initial memory block, as requested in the Initialize function. | ||
|  |         /// </summary> | ||
|  |         public int InitialSizeInBytes => (int)(m_block[0].m_bytes); | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Retrieves the maximum memory block size. | ||
|  |         /// </summary> | ||
|  |         internal long MaxMemoryBlockSize => kMaxMemoryBlockSize; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Retrieves the total bytes of the memory blocks allocated by this allocator. | ||
|  |         /// </summary> | ||
|  |         internal long BytesAllocated | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 long totalBytes = 0; | ||
|  |                 for(int i = 0; i <= m_last; i++) | ||
|  |                 { | ||
|  |                     totalBytes += m_block[i].m_bytes; | ||
|  |                 } | ||
|  |                 return totalBytes; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Rewind the allocator; invalidate all allocations made from it, and potentially also free memory blocks | ||
|  |         /// it has allocated from the system. | ||
|  |         /// </summary> | ||
|  |         public void Rewind() | ||
|  |         { | ||
|  |             if (JobsUtility.IsExecutingJob) | ||
|  |                 throw new InvalidOperationException("You cannot Rewind a RewindableAllocator from a Job."); | ||
|  |             m_handle.Rewind(); // bump the allocator handle version, invalidate all dependents | ||
|  |             while (m_last > m_used) // *delete* all blocks we didn't even allocate from this time around. | ||
|  |                 m_block[m_last--].Dispose(); | ||
|  |             while (m_used > 0) // simply *rewind* all blocks we used in this update, to avoid allocating again, every update. | ||
|  |                 m_block[m_used--].Rewind(); | ||
|  |             m_block[0].Rewind(); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Dispose the allocator. This must be called to free the memory blocks that were allocated from the system. | ||
|  |         /// </summary> | ||
|  |         public void Dispose() | ||
|  |         { | ||
|  |             if (JobsUtility.IsExecutingJob) | ||
|  |                 throw new InvalidOperationException("You cannot Dispose a RewindableAllocator from a Job."); | ||
|  |             m_used = 0; // so that we delete all blocks in Rewind() on the next line | ||
|  |             Rewind(); | ||
|  |             m_block[0].Dispose(); | ||
|  |             m_block.Dispose(); | ||
|  |             m_last = m_used = 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// All allocators must implement this property, in order to be installed in the custom allocator table. | ||
|  |         /// </summary> | ||
|  |         [ExcludeFromBurstCompatTesting("Uses managed delegate")] | ||
|  |         public AllocatorManager.TryFunction Function => Try; | ||
|  | 
 | ||
|  |         unsafe int TryAllocate(ref AllocatorManager.Block block, int startIndex, int lastIndex, long alignedSize, long alignmentMask) | ||
|  |         { | ||
|  |             for (int best = startIndex; best <= lastIndex; best++) | ||
|  |             { | ||
|  |                 Union oldUnion; | ||
|  |                 Union readUnion = default; | ||
|  |                 long begin = 0; | ||
|  |                 bool skip = false; | ||
|  |                 readUnion.m_long = Interlocked.Read(ref m_block[best].m_union.m_long); | ||
|  |                 do | ||
|  |                 { | ||
|  |                     begin = (readUnion.m_current + alignmentMask) & ~alignmentMask; | ||
|  |                     if (begin + block.Bytes > m_block[best].m_bytes) | ||
|  |                     { | ||
|  |                         skip = true; | ||
|  |                         break; | ||
|  |                     } | ||
|  |                     oldUnion = readUnion; | ||
|  |                     Union newUnion = default; | ||
|  |                     newUnion.m_current = (begin + alignedSize) > m_block[best].m_bytes ? m_block[best].m_bytes : (begin + alignedSize); | ||
|  |                     newUnion.m_allocCount = readUnion.m_allocCount + 1; | ||
|  |                     readUnion.m_long = Interlocked.CompareExchange(ref m_block[best].m_union.m_long, newUnion.m_long, oldUnion.m_long); | ||
|  |                 } while (readUnion.m_long != oldUnion.m_long); | ||
|  | 
 | ||
|  |                 if(skip) | ||
|  |                 { | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 block.Range.Pointer = (IntPtr)(m_block[best].m_pointer + begin); | ||
|  |                 block.AllocatedItems = block.Range.Items; | ||
|  | 
 | ||
|  |                 Interlocked.MemoryBarrier(); | ||
|  |                 int oldUsed; | ||
|  |                 int readUsed; | ||
|  |                 int newUsed; | ||
|  |                 readUsed = m_used; | ||
|  |                 do | ||
|  |                 { | ||
|  |                     oldUsed = readUsed; | ||
|  |                     newUsed = best > oldUsed ? best : oldUsed; | ||
|  |                     readUsed = Interlocked.CompareExchange(ref m_used, newUsed, oldUsed); | ||
|  |                 } while (newUsed != oldUsed); | ||
|  | 
 | ||
|  |                 return AllocatorManager.kErrorNone; | ||
|  |             } | ||
|  | 
 | ||
|  |             return AllocatorManager.kErrorBufferOverflow; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Try to allocate, free, or reallocate a block of memory. This is an internal function, and | ||
|  |         /// is not generally called by the user. | ||
|  |         /// </summary> | ||
|  |         /// <param name="block">The memory block to allocate, free, or reallocate</param> | ||
|  |         /// <returns>0 if successful. Otherwise, returns the error code from the allocator function.</returns> | ||
|  |         public int Try(ref AllocatorManager.Block block) | ||
|  |         { | ||
|  |             if (block.Range.Pointer == IntPtr.Zero) | ||
|  |             { | ||
|  |                 // Make the alignment multiple of cacheline size | ||
|  |                 var alignment = math.max(JobsUtility.CacheLineSize, block.Alignment); | ||
|  |                 var extra = alignment != JobsUtility.CacheLineSize ? 1 : 0; | ||
|  |                 var cachelineMask = JobsUtility.CacheLineSize - 1; | ||
|  |                 if (extra == 1) | ||
|  |                 { | ||
|  |                     alignment = (alignment + cachelineMask) & ~cachelineMask; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // Adjust the size to be multiple of alignment, add extra alignment | ||
|  |                 // to size if alignment is more than cacheline size | ||
|  |                 var mask = alignment - 1L; | ||
|  |                 var size = (block.Bytes + extra * alignment + mask) & ~mask; | ||
|  | 
 | ||
|  |                 // Check all the blocks to see if any of them have enough memory | ||
|  |                 var last = m_last; | ||
|  |                 int error = TryAllocate(ref block, 0, m_last, size, mask); | ||
|  |                 if (error == AllocatorManager.kErrorNone) | ||
|  |                 { | ||
|  |                     return error; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // If that fails, allocate another block that's guaranteed big enough, and allocate from it. | ||
|  |                 // Allocate twice as much as last time until it reaches MaxMemoryBlockSize, after that, increase | ||
|  |                 // the block size by MaxMemoryBlockSize. | ||
|  |                 m_spinner.Acquire(); | ||
|  | 
 | ||
|  |                 // After getting the lock, we must try to allocate again, because if many threads waited at | ||
|  |                 // the lock, the first one allocates and when it unlocks, it's likely that there's space for the | ||
|  |                 // other threads' allocations in the first thread's block. | ||
|  |                 error = TryAllocate(ref block, last, m_last, size, mask); | ||
|  |                 if (error == AllocatorManager.kErrorNone) | ||
|  |                 { | ||
|  |                     m_spinner.Release(); | ||
|  |                     return error; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 long bytes; | ||
|  |                 if (m_reachMaxBlockSize == 0) | ||
|  |                 { | ||
|  |                     bytes = m_block[m_last].m_bytes << 1; | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     bytes = m_block[m_last].m_bytes + kMaxMemoryBlockSize; | ||
|  |                 } | ||
|  |                 // if user asks more, skip smaller sizes | ||
|  |                 bytes = math.max(bytes, size); | ||
|  |                 m_reachMaxBlockSize = (bytes >= kMaxMemoryBlockSize) ? (byte)1 : (byte)0; | ||
|  |                 m_block[m_last + 1] = new MemoryBlock(bytes); | ||
|  |                 Interlocked.Increment(ref m_last); | ||
|  |                 error = TryAllocate(ref block, m_last, m_last, size, mask); | ||
|  |                 m_spinner.Release(); | ||
|  |                 return error; | ||
|  |             } | ||
|  | 
 | ||
|  |             // To free memory, no-op unless allocator enables individual block to be freed | ||
|  |             if (block.Range.Items == 0) | ||
|  |             { | ||
|  |                 if (m_enableBlockFree != 0) | ||
|  |                 { | ||
|  |                     for (int blockIndex = 0; blockIndex <= m_last; ++blockIndex) | ||
|  |                     { | ||
|  |                         if (m_block[blockIndex].Contains(block.Range.Pointer)) | ||
|  |                         { | ||
|  |                             Union oldUnion; | ||
|  |                             Union readUnion = default; | ||
|  |                             readUnion.m_long = Interlocked.Read(ref m_block[blockIndex].m_union.m_long); | ||
|  |                             do | ||
|  |                             { | ||
|  |                                 oldUnion = readUnion; | ||
|  |                                 Union newUnion = readUnion; | ||
|  |                                 newUnion.m_allocCount--; | ||
|  |                                 if (newUnion.m_allocCount == 0) | ||
|  |                                 { | ||
|  |                                     newUnion.m_current = 0; | ||
|  |                                 } | ||
|  |                                 readUnion.m_long = Interlocked.CompareExchange(ref m_block[blockIndex].m_union.m_long, newUnion.m_long, oldUnion.m_long); | ||
|  |                             } while (readUnion.m_long != oldUnion.m_long); | ||
|  |                         } | ||
|  |                     } | ||
|  |                 } | ||
|  |                 return 0; // we could check to see if the pointer belongs to us, if we want to be strict about it. | ||
|  |             } | ||
|  | 
 | ||
|  |             return -1; | ||
|  |         } | ||
|  | 
 | ||
|  |         [BurstCompile] | ||
|  |         [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))] | ||
|  |         internal static int Try(IntPtr state, ref AllocatorManager.Block block) | ||
|  |         { | ||
|  |             unsafe { return ((RewindableAllocator*)state)->Try(ref block); } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Retrieve the AllocatorHandle associated with this allocator. The handle is used as an index into a | ||
|  |         /// global table, for times when a reference to the allocator object isn't available. | ||
|  |         /// </summary> | ||
|  |         /// <value>The AllocatorHandle retrieved.</value> | ||
|  |         public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Retrieve the Allocator associated with this allocator. | ||
|  |         /// </summary> | ||
|  |         /// <value>The Allocator retrieved.</value> | ||
|  |         public Allocator ToAllocator { get { return m_handle.ToAllocator; } } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Check whether this AllocatorHandle is a custom allocator. | ||
|  |         /// </summary> | ||
|  |         /// <value>True if this AllocatorHandle is a custom allocator.</value> | ||
|  |         public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Check whether this allocator will automatically dispose allocations. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>Allocations made by Rewindable allocator are automatically disposed.</remarks> | ||
|  |         /// <value>Always true</value> | ||
|  |         public bool IsAutoDispose { get { return true; } } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Allocate a NativeArray of type T from memory that is guaranteed to remain valid until the end of the | ||
|  |         /// next Update of this World. There is no need to Dispose the NativeArray so allocated. It is not possible | ||
|  |         /// to free the memory by Disposing it - it is automatically freed after the end of the next Update for this | ||
|  |         /// World. | ||
|  |         /// </summary> | ||
|  |         /// <typeparam name="T">The element type of the NativeArray to allocate.</typeparam> | ||
|  |         /// <param name="length">The length of the NativeArray to allocate, measured in elements.</param> | ||
|  |         /// <returns>The NativeArray allocated by this function.</returns> | ||
|  |         [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |         public NativeArray<T> AllocateNativeArray<T>(int length) where T : unmanaged | ||
|  |         { | ||
|  |             var container = new NativeArray<T>(); | ||
|  |             unsafe | ||
|  |             { | ||
|  |                 container.m_Buffer = this.AllocateStruct(default(T), length); | ||
|  |             } | ||
|  |             container.m_Length = length; | ||
|  |             container.m_AllocatorLabel = Allocator.None; | ||
|  | #if ENABLE_UNITY_COLLECTIONS_CHECKS | ||
|  |             container.m_MinIndex = 0; | ||
|  |             container.m_MaxIndex = length - 1; | ||
|  |             container.m_Safety = CollectionHelper.CreateSafetyHandle(ToAllocator); | ||
|  |             CollectionHelper.SetStaticSafetyId<NativeArray<T>>(ref container.m_Safety, ref NativeArrayExtensions.NativeArrayStaticId<T>.s_staticSafetyId.Data); | ||
|  |             Handle.AddSafetyHandle(container.m_Safety); | ||
|  | #endif | ||
|  |             return container; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Allocate a NativeList of type T from memory that is guaranteed to remain valid until the end of the | ||
|  |         /// next Update of this World. There is no need to Dispose the NativeList so allocated. It is not possible | ||
|  |         /// to free the memory by Disposing it - it is automatically freed after the end of the next Update for this | ||
|  |         /// World. The NativeList must be initialized with its maximum capacity; if it were to dynamically resize, | ||
|  |         /// up to 1/2 of the total final capacity would be wasted, because the memory can't be dynamically freed. | ||
|  |         /// </summary> | ||
|  |         /// <typeparam name="T">The element type of the NativeList to allocate.</typeparam> | ||
|  |         /// <param name="capacity">The capacity of the NativeList to allocate, measured in elements.</param> | ||
|  |         /// <returns>The NativeList allocated by this function.</returns> | ||
|  |         [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] | ||
|  |         public NativeList<T> AllocateNativeList<T>(int capacity) where T : unmanaged | ||
|  |         { | ||
|  |             var container = new NativeList<T>(); | ||
|  |             unsafe | ||
|  |             { | ||
|  |                 container.m_ListData = this.Allocate(default(UnsafeList<T>), 1); | ||
|  |                 container.m_ListData->Ptr = this.Allocate(default(T), capacity); | ||
|  |                 container.m_ListData->m_length = 0; | ||
|  |                 container.m_ListData->m_capacity = capacity; | ||
|  |                 container.m_ListData->Allocator = Allocator.None; | ||
|  |             } | ||
|  | #if ENABLE_UNITY_COLLECTIONS_CHECKS | ||
|  |             container.m_Safety = CollectionHelper.CreateSafetyHandle(ToAllocator); | ||
|  |             CollectionHelper.SetStaticSafetyId<NativeList<T>>(ref container.m_Safety, ref NativeList<T>.s_staticSafetyId.Data); | ||
|  |             AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(container.m_Safety, true); | ||
|  |             Handle.AddSafetyHandle(container.m_Safety); | ||
|  | #endif | ||
|  |             return container; | ||
|  |         } | ||
|  |     } | ||
|  | } |