feat(memory): refactor allocation and add new queue
Refactored memory management by removing safety checks and introducing `MemoryHandle` for centralized tracking. Simplified allocation logic across allocators and enhanced `Dispose` methods for better resource cleanup. Added `UnsafeChunkedQueue<T>`, a lock-free, dynamically resizing queue with chunk-based memory management, supporting parallel producers and consumers. Updated unit tests to validate new queue functionality and ensure compatibility with refactored memory logic. Incremented assembly version to 1.6.12. BREAKING CHANGE: Removed `#if MHP_ENABLE_SAFETY_CHECKS` blocks, altering memory validation behavior.
This commit is contained in:
@@ -54,10 +54,6 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// This buffer has 4 parts: TValue, TKey, Next, Buckets.
|
||||
@@ -99,14 +95,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
if (_buffer != null)
|
||||
{
|
||||
if (_allocationHandle.IsValid != null)
|
||||
{
|
||||
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return _memoryHandle.IsValid;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -156,7 +145,13 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
|
||||
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||
|
||||
allocationOption &= ~AllocationOption.Clear;
|
||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle = MemoryHandle.Create(_buffer, (nuint)totalSize);
|
||||
#endif
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
@@ -256,22 +251,12 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
MemoryHandle memHandle;
|
||||
#endif
|
||||
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, &memHandle
|
||||
#endif
|
||||
);
|
||||
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption);
|
||||
|
||||
_buffer = buf;
|
||||
_keys = (TKey*)(_buffer + keyOffset);
|
||||
_next = (int*)(_buffer + nextOffset);
|
||||
_buckets = (int*)(_buffer + bucketOffset);
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle = memHandle;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ResizeExact(int newCapacity, int newBucketCapacity)
|
||||
@@ -284,9 +269,6 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
var oldNext = _next;
|
||||
var oldBuckets = _buckets;
|
||||
var oldBucketCapacity = _bucketCapacity;
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
var oldMemoryHandle = _memoryHandle;
|
||||
#endif
|
||||
|
||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
|
||||
_capacity = newCapacity;
|
||||
@@ -305,12 +287,12 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
|
||||
if (_allocationHandle.Free != null)
|
||||
{
|
||||
_allocationHandle.Free(_allocationHandle.State, oldBuffer
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, oldMemoryHandle
|
||||
#endif
|
||||
);
|
||||
_allocationHandle.Free(_allocationHandle.State, oldBuffer);
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle.Update(_buffer, (nuint)totalSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Resize(int newCapacity)
|
||||
@@ -719,42 +701,19 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
#if DEBUG
|
||||
if (_buffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = "The HashMapHelper is not created or already disposed.";
|
||||
#if MHP_ENABLE_STACKTRACE
|
||||
var stackTrace = new StackTrace(1, true);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var frame in stackTrace.GetFrames())
|
||||
{
|
||||
var fileName = frame?.GetFileName();
|
||||
if (frame != null)
|
||||
{
|
||||
var methodInfo = DiagnosticMethodInfo.Create(frame);
|
||||
sb.AppendLine($"File: {fileName}, Type: {methodInfo?.DeclaringTypeName}, Method: {methodInfo?.Name}, Line: {frame.GetFileLineNumber()}");
|
||||
}
|
||||
}
|
||||
|
||||
message += Environment.NewLine + sb.ToString();
|
||||
#endif
|
||||
Debug.WriteLine(message);
|
||||
#endif
|
||||
UnsafeCollectionUtility.ReportDoubleFree<HashMapHelper<TKey>>(_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_allocationHandle.Free != null)
|
||||
{
|
||||
_allocationHandle.Free(_allocationHandle.State, _buffer
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, _memoryHandle
|
||||
#endif
|
||||
);
|
||||
_allocationHandle.Free(_allocationHandle.State, _buffer);
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle.Dispose();
|
||||
#endif
|
||||
|
||||
_buffer = null;
|
||||
_keys = null;
|
||||
_next = null;
|
||||
|
||||
@@ -108,14 +108,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
if (_buffer != null)
|
||||
{
|
||||
if (_allocationHandle.IsValid != null)
|
||||
{
|
||||
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return _memoryHandle.IsValid;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -149,18 +142,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
||||
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||
}
|
||||
|
||||
_buffer = (T*)handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption);
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
MemoryHandle memHandle;
|
||||
#endif
|
||||
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, &memHandle
|
||||
#endif
|
||||
);
|
||||
|
||||
_buffer = (T*)buff;
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle = memHandle;
|
||||
_memoryHandle = MemoryHandle.Create(_buffer, (nuint)(count * sizeof(T)));
|
||||
#endif
|
||||
_allocationHandle = handle;
|
||||
_count = count;
|
||||
@@ -247,19 +231,12 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
||||
return;
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
MemoryHandle memHandle = _memoryHandle;
|
||||
#endif
|
||||
var elemSize = SizeOf<T>();
|
||||
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, &memHandle
|
||||
#endif
|
||||
);
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle = memHandle;
|
||||
#endif
|
||||
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option);
|
||||
_count = newSize;
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle.Update(_buffer, (nuint)newSize * elemSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -407,42 +384,19 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
#if DEBUG
|
||||
if (_buffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = "The UnsafeArray is not created or already disposed.";
|
||||
#if MHP_ENABLE_STACKTRACE
|
||||
var stackTrace = new StackTrace(1, true);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var frame in stackTrace.GetFrames())
|
||||
{
|
||||
var fileName = frame?.GetFileName();
|
||||
if (frame != null)
|
||||
{
|
||||
var methodInfo = DiagnosticMethodInfo.Create(frame);
|
||||
sb.AppendLine($"File: {fileName}, Type: {methodInfo?.DeclaringTypeName}, Method: {methodInfo?.Name}, Line: {frame.GetFileLineNumber()}");
|
||||
}
|
||||
}
|
||||
|
||||
message += Environment.NewLine + sb.ToString();
|
||||
#endif
|
||||
Debug.WriteLine(message);
|
||||
#endif
|
||||
UnsafeCollectionUtility.ReportDoubleFree<UnsafeArray<T>>(_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_allocationHandle.Free != null)
|
||||
{
|
||||
_allocationHandle.Free(_allocationHandle.State, _buffer
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, _memoryHandle
|
||||
#endif
|
||||
);
|
||||
_allocationHandle.Free(_allocationHandle.State, _buffer);
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle.Dispose();
|
||||
#endif
|
||||
|
||||
_buffer = null;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A dynamically resizing, parallel, lock-free queue using unmanaged chunks.
|
||||
/// Uses a very brief spin lock only during chunk allocation, alongside a lock-free segment cache.
|
||||
/// </summary>
|
||||
public unsafe struct UnsafeChunkedQueue<T> : IDisposable
|
||||
where T : unmanaged
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ChunkSlot
|
||||
{
|
||||
// 0 = Empty, 1 = Ready (Writer has finished writing)
|
||||
public int state;
|
||||
public T value;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ChunkHeader
|
||||
{
|
||||
public ChunkHeader* next;
|
||||
public ChunkHeader* nextFree;
|
||||
public int capacity;
|
||||
|
||||
// Cache line padding to avoid false sharing between atomic counters
|
||||
private readonly long _pad1, _pad2, _pad3;
|
||||
public int head;
|
||||
|
||||
private readonly long _pad4, _pad5, _pad6;
|
||||
public int tail;
|
||||
|
||||
private readonly long _pad7, _pad8, _pad9;
|
||||
public int consumedSlots;
|
||||
}
|
||||
|
||||
public readonly unsafe struct ParallelProducer
|
||||
{
|
||||
private readonly UnsafeChunkedQueue<T>* _queue;
|
||||
|
||||
internal ParallelProducer(UnsafeChunkedQueue<T>* queue)
|
||||
{
|
||||
_queue = queue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Enqueue(T item) => _queue->Enqueue(item);
|
||||
}
|
||||
|
||||
public readonly unsafe struct ParallelConsumer
|
||||
{
|
||||
private readonly UnsafeChunkedQueue<T>* _queue;
|
||||
|
||||
internal ParallelConsumer(UnsafeChunkedQueue<T>* queue)
|
||||
{
|
||||
_queue = queue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryDequeue(out T item) => _queue->TryDequeue(out item);
|
||||
}
|
||||
|
||||
// Pointer representations (nint utilized for straightforward Interlocked compatibility)
|
||||
private nint _head;
|
||||
private nint _tail;
|
||||
private nint _freeList;
|
||||
|
||||
private int _expandLock;
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private readonly MemoryHandle _memoryHandle;
|
||||
#endif
|
||||
private readonly AllocationHandle _allocHandle;
|
||||
private readonly AllocationOption _allocOption;
|
||||
private readonly int _chunkCapacity;
|
||||
|
||||
public readonly bool IsCreated => _head != 0;
|
||||
|
||||
public UnsafeChunkedQueue(int capacityPerChunk, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
||||
{
|
||||
_chunkCapacity = Math.Max(32, capacityPerChunk);
|
||||
_allocHandle = handle;
|
||||
_allocOption = allocationOption;
|
||||
_freeList = 0;
|
||||
_expandLock = 0;
|
||||
|
||||
// Preallocate the first chunk
|
||||
var initialChunk = AllocateNewChunk();
|
||||
_head = (nint)initialChunk;
|
||||
_tail = (nint)initialChunk;
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle = MemoryHandle.Create(initialChunk, (nuint)(_chunkCapacity * sizeof(ChunkSlot)));
|
||||
#endif
|
||||
}
|
||||
|
||||
public UnsafeChunkedQueue(int capacityPerChunk, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||
: this(capacityPerChunk, AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to enqueue an item. Expands automatically if the current chunk is full.
|
||||
/// </summary>
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
SpinWait spin = new SpinWait();
|
||||
|
||||
while (true)
|
||||
{
|
||||
ChunkHeader* tail = (ChunkHeader*)_tail;
|
||||
|
||||
// Reserve our slot
|
||||
int tailIdx = Interlocked.Increment(ref tail->tail) - 1;
|
||||
|
||||
if (tailIdx < tail->capacity)
|
||||
{
|
||||
// Slot secured. Let's write.
|
||||
var slot = (ChunkSlot*)(tail + 1) + tailIdx;
|
||||
slot->value = item;
|
||||
Volatile.Write(ref slot->state, 1); // Mark as readable
|
||||
return;
|
||||
}
|
||||
|
||||
// Chunk is full. Expand the queue.
|
||||
if (Interlocked.CompareExchange(ref _expandLock, 1, 0) == 0)
|
||||
{
|
||||
// Verify no other thread already expanded
|
||||
if (tail == (ChunkHeader*)_tail)
|
||||
{
|
||||
var newChunk = GetChunkFromPoolOrAllocate();
|
||||
|
||||
// Pre-write our object onto the new chunk's first spot safely
|
||||
newChunk->tail = 1;
|
||||
var slot = (ChunkSlot*)(newChunk + 1) + 0;
|
||||
slot->value = item;
|
||||
Volatile.Write(ref slot->state, 1);
|
||||
|
||||
// Attach new chunk
|
||||
Volatile.Write(ref *(nint*)&tail->next, (nint)newChunk);
|
||||
Volatile.Write(ref _tail, (nint)newChunk);
|
||||
Volatile.Write(ref _expandLock, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
Volatile.Write(ref _expandLock, 0); // Release if another thread expanded
|
||||
}
|
||||
|
||||
// Another thread is allocating the chunk. Spin and retry.
|
||||
spin.SpinOnce();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to dequeue an item.
|
||||
/// </summary>
|
||||
public bool TryDequeue(out T item)
|
||||
{
|
||||
SpinWait spin = new SpinWait();
|
||||
|
||||
while (true)
|
||||
{
|
||||
ChunkHeader* head = (ChunkHeader*)Volatile.Read(ref _head);
|
||||
if (head == null)
|
||||
{
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentHead = Volatile.Read(ref head->head);
|
||||
int currentTail = Volatile.Read(ref head->tail);
|
||||
|
||||
if (currentHead >= head->capacity)
|
||||
{
|
||||
// Current chunk exhausted. Advance _head to Next chunk.
|
||||
ChunkHeader* next = (ChunkHeader*)Volatile.Read(ref *(nint*)&head->next);
|
||||
if (next != null)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _head, (nint)next, (nint)head) == (nint)head)
|
||||
{
|
||||
// Successfully unlinked this chunk from _head.
|
||||
// If all slots have already been read, recycle it safely now!
|
||||
if (Volatile.Read(ref head->consumedSlots) >= head->capacity)
|
||||
{
|
||||
RecycleChunk(head);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We reached the end of the chunks, but a writer might be locking to expand right now.
|
||||
if (Volatile.Read(ref _expandLock) == 1)
|
||||
{
|
||||
spin.SpinOnce();
|
||||
continue;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent infinite loop: if head has caught up to tail, the queue chunk is empty.
|
||||
if (currentHead >= currentTail)
|
||||
{
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to acquire the slot at currentHead lock-free
|
||||
if (Interlocked.CompareExchange(ref head->head, currentHead + 1, currentHead) == currentHead)
|
||||
{
|
||||
var slot = (ChunkSlot*)(head + 1) + currentHead;
|
||||
|
||||
// Wait until the Enqueuing thread has finished writing (usually 0 spins)
|
||||
SpinWait innerWait = new SpinWait();
|
||||
while (Volatile.Read(ref slot->state) == 0)
|
||||
{
|
||||
innerWait.SpinOnce();
|
||||
}
|
||||
|
||||
item = slot->value;
|
||||
|
||||
// Track how many values have been permanently read
|
||||
int consumed = Interlocked.Increment(ref head->consumedSlots);
|
||||
|
||||
// We recycle only if all readers are done AND this chunk is already detached from _head
|
||||
// (prevents ABA object reuse crashes where _head still points to a recycled memory block).
|
||||
if (consumed >= head->capacity && Volatile.Read(ref _head) != (nint)head)
|
||||
{
|
||||
RecycleChunk(head);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ChunkHeader* GetChunkFromPoolOrAllocate()
|
||||
{
|
||||
// Pop lock-free from the free list
|
||||
while (true)
|
||||
{
|
||||
ChunkHeader* free = (ChunkHeader*)Volatile.Read(ref _freeList);
|
||||
if (free == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var nextFree = free->nextFree;
|
||||
if (Interlocked.CompareExchange(ref _freeList, (nint)nextFree, (nint)free) == (nint)free)
|
||||
{
|
||||
// Reset chunk
|
||||
free->next = null;
|
||||
free->nextFree = null;
|
||||
free->head = 0;
|
||||
free->tail = 0;
|
||||
free->consumedSlots = 0;
|
||||
|
||||
var slots = (ChunkSlot*)(free + 1);
|
||||
MemClear(slots, (uint)(_chunkCapacity * sizeof(ChunkSlot)));
|
||||
return free;
|
||||
}
|
||||
}
|
||||
|
||||
return AllocateNewChunk();
|
||||
}
|
||||
|
||||
private readonly ChunkHeader* AllocateNewChunk()
|
||||
{
|
||||
nuint byteSize = (nuint)sizeof(ChunkHeader) + (nuint)(_chunkCapacity * sizeof(ChunkSlot));
|
||||
ChunkHeader* block = (ChunkHeader*)_allocHandle.Alloc(_allocHandle.State, byteSize, AlignOf<int>(), _allocOption);
|
||||
|
||||
block->next = null;
|
||||
block->nextFree = null;
|
||||
block->capacity = _chunkCapacity;
|
||||
block->head = 0;
|
||||
block->tail = 0;
|
||||
block->consumedSlots = 0;
|
||||
|
||||
var slots = (ChunkSlot*)(block + 1);
|
||||
MemClear(slots, (uint)(_chunkCapacity * sizeof(ChunkSlot)));
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
private void RecycleChunk(ChunkHeader* chunk)
|
||||
{
|
||||
// Push lock-free to the free list
|
||||
while (true)
|
||||
{
|
||||
ChunkHeader* free = (ChunkHeader*)Volatile.Read(ref _freeList);
|
||||
chunk->nextFree = free;
|
||||
if (Interlocked.CompareExchange(ref _freeList, (nint)chunk, (nint)free) == (nint)free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a parallel producer for this queue. The returned struct contains a raw pointer
|
||||
/// to the queue and can be used from multiple threads as long as the queue struct itself
|
||||
/// remains alive and its address stable.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ParallelProducer AsParallelProducer()
|
||||
{
|
||||
return new ParallelProducer((UnsafeChunkedQueue<T>*)Unsafe.AsPointer(ref this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a parallel consumer for this queue. The returned struct contains a raw pointer
|
||||
/// to the queue and can be used from multiple threads as long as the queue struct itself
|
||||
/// remains alive and its address stable.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ParallelConsumer AsParallelConsumer()
|
||||
{
|
||||
return new ParallelConsumer((UnsafeChunkedQueue<T>*)Unsafe.AsPointer(ref this));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispose Active Chunks
|
||||
ChunkHeader* curr = (ChunkHeader*)_head;
|
||||
while (curr != null)
|
||||
{
|
||||
var next = curr->next;
|
||||
_allocHandle.Free(_allocHandle.State, curr);
|
||||
curr = next;
|
||||
}
|
||||
|
||||
// Dispose FreeList cache Chunks
|
||||
ChunkHeader* free = (ChunkHeader*)_freeList;
|
||||
while (free != null)
|
||||
{
|
||||
var next = free->nextFree;
|
||||
_allocHandle.Free(_allocHandle.State, free);
|
||||
free = next;
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_memoryHandle.Dispose();
|
||||
#endif
|
||||
|
||||
_head = 0;
|
||||
_tail = 0;
|
||||
_freeList = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user