public static bool TryPeek <T>(UnsafeMPSCQueue *queue, out T result) where T : unmanaged { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); UDebug.Assert(typeof(T).TypeHandle.Value == queue->_typeHandle); // Temp copy of inner buffer var items = queue->_items; int head = Volatile.Read(ref queue->_headAndTail.Head); int index = head & queue->_mask; int offset = queue->_slotOffset; int seq = Volatile.Read(ref *(int *)items.Element(index, offset)); int dif = seq - (head + 1); if (dif == 0) { result = *items.Element <T>(index); return(true); } result = default; return(false); }
private static void SplitQueue(UnsafeMPSCQueue *q) { //Wrap tail back to 0 for (int i = 0; i < 5; i++) { UnsafeMPSCQueue.TryEnqueue(q, 111); } //First half for (int i = 0; i < 5; i++) { UnsafeMPSCQueue.TryEnqueue(q, i); } //Move head by 5 for (int i = 0; i < 5; i++) { UnsafeMPSCQueue.Dequeue <int>(q); } //Second half (head and tail are now both 5) for (int i = 5; i < 10; i++) { UnsafeMPSCQueue.TryEnqueue(q, i); } //Circular buffer now "ends" in the middle of the underlying array }
/// <summary> /// Dequeues an item from the queue. Blocks the thread until there is space in the queue. /// </summary> public static T Dequeue <T>(UnsafeMPSCQueue *queue) where T : unmanaged { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); UDebug.Assert(typeof(T).TypeHandle.Value == queue->_typeHandle); SpinWait spinner = new SpinWait(); // Temp copy of inner buffer var items = queue->_items; int head = Volatile.Read(ref queue->_headAndTail.Head); int index = head & queue->_mask; int offset = queue->_slotOffset; while (true) { int seq = Volatile.Read(ref *(int *)items.Element(index, offset)); int dif = seq - (head + 1); if (dif == 0) { // Update head Volatile.Write(ref queue->_headAndTail.Head, head + 1); var item = *items.Element <T>(index); // Update slot after reading Volatile.Write(ref *(int *)items.Element(index, offset), head + items.Length); return(item); } spinner.SpinOnce(); } }
public static int GetCapacity(UnsafeMPSCQueue *queue) { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); return(queue->_items.Length); }
/// <summary> /// Creates an enumerator for the current snapshot of the queue. /// </summary> public static Enumerator <T> GetEnumerator <T>(UnsafeMPSCQueue *queue) where T : unmanaged { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); UDebug.Assert(typeof(T).TypeHandle.Value == queue->_typeHandle); return(new Enumerator <T>(queue)); }
internal Enumerator(UnsafeMPSCQueue *queue) { _queue = queue; _index = -1; _current = default; _headStart = Volatile.Read(ref queue->_headAndTail.Head); _mask = queue->_mask; _seqOffset = queue->_slotOffset; }
public static bool IsEmpty <T>(UnsafeMPSCQueue *queue) where T : unmanaged { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); UDebug.Assert(typeof(T).TypeHandle.Value == queue->_typeHandle); var nextHead = Volatile.Read(ref queue->_headAndTail.Head) + 1; return(Volatile.Read(ref queue->_headAndTail.Tail) < nextHead); }
public static void Free(UnsafeMPSCQueue *queue) { if (queue == null) { return; } // clear queue memory (just in case) *queue = default; // free queue memory, if this is a fixed queue it frees the items memory at the same time Memory.Free(queue); }
public static void Clear(UnsafeMPSCQueue *queue) { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); queue->_headAndTail = new HeadAndTail(); // Initialize the sequence number for each slot. // This is used to synchronize between consumer and producer threads. var offset = queue->_slotOffset; var items = queue->_items; for (int i = 0; i < queue->_items.Length; i++) { *(int *)items.Element(i, offset) = i; } }
/// <summary> /// Gets the current count of the queue. /// Value becomes stale if enqueue/dequeue operations happen. /// </summary> public static int GetCount(UnsafeMPSCQueue *queue) { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); var head = Volatile.Read(ref queue->_headAndTail.Head); var tail = Volatile.Read(ref queue->_headAndTail.Tail); int mask = queue->_mask; if (head != tail) { head &= mask; tail &= mask; return(head < tail ? tail - head : queue->_items.Length - head + tail); } return(0); }
/// <summary> /// Returns a snapshot of the elements. /// </summary> /// <returns></returns> internal static T[] ToArray <T>(UnsafeMPSCQueue *queue) where T : unmanaged { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); UDebug.Assert(typeof(T).TypeHandle.Value == queue->_typeHandle); int count = GetCount(queue); var arr = new T[count]; var enumerator = GetEnumerator <T>(queue); int i = 0; while (enumerator.MoveNext() && i < count) { arr[i++] = enumerator.Current; } return(arr); }
/// <summary> /// Tries to enqueue an item in the queue. Returns false if there's no space in the queue. /// </summary> public static bool TryEnqueue <T>(UnsafeMPSCQueue *queue, T item) where T : unmanaged { UDebug.Assert(queue != null); UDebug.Assert(queue->_items.Ptr != null); UDebug.Assert(typeof(T).TypeHandle.Value == queue->_typeHandle); SpinWait spinner = default; // Temp copy of inner buffer var items = queue->_items; var offset = queue->_slotOffset; while (true) { int tail = Volatile.Read(ref queue->_headAndTail.Tail); int index = tail & queue->_mask; int seq = Volatile.Read(ref *(int *)items.Element(index, offset)); int dif = seq - tail; if (dif == 0) { // Reserve the slot if (Interlocked.CompareExchange(ref queue->_headAndTail.Tail, tail + 1, tail) == tail) { // Write the value and update the seq *items.Element <T>(index) = item; Volatile.Write(ref *(int *)items.Element(index, offset), tail + 1); return(true); } } else if (dif < 0) { // Slot was full return(false); } // Lost the race, try again spinner.SpinOnce(); } }
int _slotOffset; // Readonly /// <summary> /// Allocates a new SPSCRingbuffer. Capacity will be set to a power of 2. /// </summary> public static UnsafeMPSCQueue *Allocate <T>(int capacity) where T : unmanaged { if (capacity < 1) { throw new ArgumentOutOfRangeException(nameof(capacity), string.Format(ThrowHelper.ArgumentOutOfRange_MustBePositive, nameof(capacity))); } capacity = Memory.RoundUpToPowerOf2(capacity); // Required to get the memory size of the Slot + Value int slotStride = Marshal.SizeOf(new QueueSlot <T>()); int slotAlign = Memory.GetMaxAlignment(sizeof(T), sizeof(int)); int slotOffset = Memory.RoundToAlignment(sizeof(T), slotAlign); int alignment = Memory.GetAlignment(slotStride); var sizeOfQueue = Memory.RoundToAlignment(sizeof(UnsafeMPSCQueue), alignment); var sizeOfArray = slotStride * capacity; var ptr = Memory.MallocAndZero(sizeOfQueue + sizeOfArray, alignment); UnsafeMPSCQueue *queue = (UnsafeMPSCQueue *)ptr; // initialize fixed buffer from same block of memory as the stack UnsafeBuffer.InitFixed(&queue->_items, (byte *)ptr + sizeOfQueue, capacity, slotStride); // Read-only values queue->_mask = capacity - 1; queue->_slotOffset = slotOffset; queue->_typeHandle = typeof(T).TypeHandle.Value; // Reset the queue for use. Clear(queue); return(queue); }
public NativeMPSCQueue(int capacity) { m_inner = UnsafeMPSCQueue.Allocate <T>(capacity); }
public void Dispose() { UnsafeMPSCQueue.Free(m_inner); m_inner = null; }