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 : 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 m_allocated; ArrayOfArrays 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(1024 * 1024, backingAllocatorHandle); m_tofree = new ArrayOfArrays(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(); } /// /// Dispose the allocator. This must be called to free the memory blocks that were allocated from the system. /// public void Dispose() { FreeAll(); m_tofree.Dispose(); m_allocated.Dispose(); } /// /// The allocator function. It can allocate, deallocate, or reallocate. /// public AllocatorManager.TryFunction Function => Try; /// /// Invoke the allocator function. /// /// The block to allocate, deallocate, or reallocate. See /// 0 if successful. Otherwise, returns the error code from the allocator function. 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); } } /// /// This allocator. /// /// This allocator. public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } /// /// Cast the Allocator index into Allocator /// public Allocator ToAllocator { get { return m_handle.ToAllocator; } } /// /// Check whether an allocator is a custom allocator /// public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } /// /// Check whether this allocator will automatically dispose allocations. /// /// Allocations made by Auto free allocator are automatically disposed. /// Always true public bool IsAutoDispose { get { return true; } } } }