265 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			265 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Diagnostics; | ||
|  | using System.Threading; | ||
|  | using AOT; | ||
|  | using Unity.Burst; | ||
|  | using Unity.Collections.LowLevel.Unsafe; | ||
|  | using Unity.Mathematics; | ||
|  | 
 | ||
|  | namespace Unity.Collections | ||
|  | { | ||
|  |     unsafe internal struct ArrayOfArrays<T> : IDisposable where  T : unmanaged | ||
|  |     { | ||
|  |         AllocatorManager.AllocatorHandle m_backingAllocatorHandle; | ||
|  |         int m_lengthInElements; | ||
|  |         int m_capacityInElements; | ||
|  |         int m_log2BlockSizeInElements; | ||
|  |         int m_blocks; | ||
|  |         IntPtr* m_block; | ||
|  | 
 | ||
|  |         int BlockSizeInElements => 1 << m_log2BlockSizeInElements; | ||
|  |         int BlockSizeInBytes => BlockSizeInElements * sizeof(T); | ||
|  |         int BlockMask => BlockSizeInElements - 1; | ||
|  | 
 | ||
|  |         public int Length => m_lengthInElements; | ||
|  |         public int Capacity => m_capacityInElements; | ||
|  | 
 | ||
|  |         public ArrayOfArrays(int capacityInElements, AllocatorManager.AllocatorHandle backingAllocatorHandle, int log2BlockSizeInElements = 12) | ||
|  |         { | ||
|  |             this = default; | ||
|  |             m_backingAllocatorHandle = backingAllocatorHandle; | ||
|  |             m_lengthInElements = 0; | ||
|  |             m_capacityInElements = capacityInElements; | ||
|  |             m_log2BlockSizeInElements = log2BlockSizeInElements; | ||
|  |             m_blocks = (capacityInElements + BlockMask) >> m_log2BlockSizeInElements; | ||
|  |             m_block = (IntPtr*)Memory.Unmanaged.Allocate(sizeof(IntPtr) * m_blocks, 16, m_backingAllocatorHandle); | ||
|  |             UnsafeUtility.MemSet(m_block, 0, sizeof(IntPtr) * m_blocks); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void LockfreeAdd(T t) | ||
|  |         { | ||
|  |             var elementIndex = Interlocked.Increment(ref m_lengthInElements) - 1; | ||
|  |             var blockIndex = BlockIndexOfElement(elementIndex); | ||
|  |             CheckBlockIndex(blockIndex); | ||
|  |             if(m_block[blockIndex] == IntPtr.Zero) | ||
|  |             { | ||
|  |                 void* pointer = Memory.Unmanaged.Allocate(BlockSizeInBytes, 16, m_backingAllocatorHandle); // $$$! | ||
|  |                 var lastBlock = math.min(m_blocks, blockIndex + 4); // don't overgrow too fast, simply to avoid a $$$ free | ||
|  |                 for(; blockIndex < lastBlock; ++blockIndex) | ||
|  |                     if(IntPtr.Zero == Interlocked.CompareExchange(ref m_block[blockIndex], (IntPtr)pointer, IntPtr.Zero)) | ||
|  |                         break; // install the new block, into *any* empty slot available, to avoid wasting the time we spent on malloc | ||
|  |                 if(blockIndex == lastBlock) | ||
|  |                     Memory.Unmanaged.Free(pointer, m_backingAllocatorHandle); // $$$, only if absolutely necessary | ||
|  |             } | ||
|  |             this[elementIndex] = t; | ||
|  |         } | ||
|  | 
 | ||
|  |         public ref T this[int elementIndex] | ||
|  |         { | ||
|  |             get | ||
|  |             { | ||
|  |                 CheckElementIndex(elementIndex); | ||
|  |                 var blockIndex = BlockIndexOfElement(elementIndex); | ||
|  |                 CheckBlockIndex(blockIndex); | ||
|  |                 CheckBlockIsNotNull(blockIndex); | ||
|  |                 IntPtr blockIntPtr = m_block[blockIndex]; | ||
|  |                 var elementIndexInBlock = elementIndex & BlockMask; | ||
|  |                 T* blockPointer = (T*)blockIntPtr; | ||
|  |                 return ref blockPointer[elementIndexInBlock]; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Rewind() | ||
|  |         { | ||
|  |             m_lengthInElements = 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Clear() | ||
|  |         { | ||
|  |             Rewind(); | ||
|  |             for(var i = 0; i < m_blocks; ++i) | ||
|  |                 if(m_block[i] != IntPtr.Zero) | ||
|  |                 { | ||
|  |                     Memory.Unmanaged.Free((void*)m_block[i], m_backingAllocatorHandle); | ||
|  |                     m_block[i] = IntPtr.Zero; | ||
|  |                 } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Dispose() | ||
|  |         { | ||
|  |             Clear(); | ||
|  |             Memory.Unmanaged.Free(m_block, m_backingAllocatorHandle); | ||
|  |         } | ||
|  | 
 | ||
|  |         [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] | ||
|  |         void CheckElementIndex(int elementIndex) | ||
|  |         { | ||
|  |             if (elementIndex >= m_lengthInElements) | ||
|  |                 throw new ArgumentException($"Element index {elementIndex} must be less than length in elements {m_lengthInElements}."); | ||
|  |         } | ||
|  | 
 | ||
|  |         [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] | ||
|  |         void CheckBlockIndex(int blockIndex) | ||
|  |         { | ||
|  |             if (blockIndex >= m_blocks) | ||
|  |                 throw new ArgumentException($"Block index {blockIndex} must be less than number of blocks {m_blocks}."); | ||
|  |         } | ||
|  | 
 | ||
|  |         [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] | ||
|  |         void CheckBlockIsNotNull(int blockIndex) | ||
|  |         { | ||
|  |             if(m_block[blockIndex] == IntPtr.Zero) | ||
|  |                 throw new ArgumentException($"Block index {blockIndex} is a null pointer."); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void RemoveAtSwapBack(int elementIndex) | ||
|  |         { | ||
|  |             this[elementIndex] = this[Length-1]; | ||
|  |             --m_lengthInElements; | ||
|  |         } | ||
|  | 
 | ||
|  |         int BlockIndexOfElement(int elementIndex) | ||
|  |         { | ||
|  |             return elementIndex >> m_log2BlockSizeInElements; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void TrimExcess() | ||
|  |         { | ||
|  |             for(var blockIndex = BlockIndexOfElement(m_lengthInElements + BlockMask); blockIndex < m_blocks; ++blockIndex) | ||
|  |             { | ||
|  |                 CheckBlockIndex(blockIndex); | ||
|  |                 if(m_block[blockIndex] != IntPtr.Zero) | ||
|  |                 { | ||
|  |                     var blockIntPtr = m_block[blockIndex]; | ||
|  |                     void* blockPointer = (void*)blockIntPtr; | ||
|  |                     Memory.Unmanaged.Free(blockPointer, m_backingAllocatorHandle); | ||
|  |                     m_block[blockIndex] = IntPtr.Zero; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     [BurstCompile] | ||
|  |     internal struct AutoFreeAllocator : AllocatorManager.IAllocator | ||
|  |     { | ||
|  |         ArrayOfArrays<IntPtr> m_allocated; | ||
|  |         ArrayOfArrays<IntPtr> m_tofree; | ||
|  |         AllocatorManager.AllocatorHandle m_handle; | ||
|  |         AllocatorManager.AllocatorHandle m_backingAllocatorHandle; | ||
|  | 
 | ||
|  |         unsafe public void Update() | ||
|  |         { | ||
|  |             for(var i = m_tofree.Length; i --> 0;) | ||
|  |                 for(var j = m_allocated.Length; j --> 0;) | ||
|  |                     if(m_allocated[j] == m_tofree[i]) | ||
|  |                     { | ||
|  |                         Memory.Unmanaged.Free((void*)m_tofree[i], m_backingAllocatorHandle); | ||
|  |                         m_allocated.RemoveAtSwapBack(j); | ||
|  |                         break; | ||
|  |                     } | ||
|  |             m_tofree.Rewind(); | ||
|  |             m_allocated.TrimExcess(); | ||
|  |         } | ||
|  | 
 | ||
|  |         unsafe public void Initialize(AllocatorManager.AllocatorHandle backingAllocatorHandle) | ||
|  |         { | ||
|  |             m_allocated = new ArrayOfArrays<IntPtr>(1024 * 1024, backingAllocatorHandle); | ||
|  |             m_tofree = new ArrayOfArrays<IntPtr>(128 * 1024, backingAllocatorHandle); | ||
|  |             m_backingAllocatorHandle = backingAllocatorHandle; | ||
|  |         } | ||
|  | 
 | ||
|  |         unsafe public void FreeAll() | ||
|  |         { | ||
|  |             Update(); | ||
|  |             m_handle.Rewind(); | ||
|  |             for(var i = 0; i < m_allocated.Length; ++i) | ||
|  |                 Memory.Unmanaged.Free((void*) m_allocated[i], m_backingAllocatorHandle); | ||
|  |             m_allocated.Rewind(); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Dispose the allocator. This must be called to free the memory blocks that were allocated from the system. | ||
|  |         /// </summary> | ||
|  |         public void Dispose() | ||
|  |         { | ||
|  |             FreeAll(); | ||
|  |             m_tofree.Dispose(); | ||
|  |             m_allocated.Dispose(); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The allocator function. It can allocate, deallocate, or reallocate. | ||
|  |         /// </summary> | ||
|  |         public AllocatorManager.TryFunction Function => Try; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Invoke the allocator function. | ||
|  |         /// </summary> | ||
|  |         /// <param name="block">The block to allocate, deallocate, or reallocate. See <see cref="AllocatorManager.Try"/></param> | ||
|  |         /// <returns>0 if successful. Otherwise, returns the error code from the allocator function.</returns> | ||
|  |         public int Try(ref AllocatorManager.Block block) | ||
|  |         { | ||
|  |             unsafe | ||
|  |             { | ||
|  |                 if (block.Range.Pointer == IntPtr.Zero) | ||
|  |                 { | ||
|  |                     if (block.Bytes == 0) | ||
|  |                     { | ||
|  |                         return 0; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     var ptr = (byte*)Memory.Unmanaged.Allocate(block.Bytes, block.Alignment, m_backingAllocatorHandle); | ||
|  |                     block.Range.Pointer = (IntPtr)ptr; | ||
|  |                     block.AllocatedItems = block.Range.Items; | ||
|  | 
 | ||
|  |                     m_allocated.LockfreeAdd(block.Range.Pointer); | ||
|  | 
 | ||
|  |                     return 0; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (block.Range.Items == 0) | ||
|  |                 { | ||
|  |                     m_tofree.LockfreeAdd(block.Range.Pointer); | ||
|  | 
 | ||
|  |                     block.Range.Pointer = IntPtr.Zero; | ||
|  |                     block.AllocatedItems = 0; | ||
|  | 
 | ||
|  |                     return 0; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 return -1; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         [BurstCompile] | ||
|  |         [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))] | ||
|  |         internal static int Try(IntPtr state, ref AllocatorManager.Block block) | ||
|  |         { | ||
|  |             unsafe { return ((AutoFreeAllocator*)state)->Try(ref block); } | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// This allocator. | ||
|  |         /// </summary> | ||
|  |         /// <value>This allocator.</value> | ||
|  |         public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Cast the Allocator index into Allocator | ||
|  |         /// </summary> | ||
|  |         public Allocator ToAllocator { get { return m_handle.ToAllocator; } } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Check whether an allocator is a custom allocator | ||
|  |         /// </summary> | ||
|  |         public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Check whether this allocator will automatically dispose allocations. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>Allocations made by Auto free allocator are automatically disposed.</remarks> | ||
|  |         /// <value>Always true</value> | ||
|  |         public bool IsAutoDispose { get { return true; } } | ||
|  |     } | ||
|  | } |