using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace BF
{
///
/// Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst()
///
[DebuggerDisplay("Count={Count} Capacity={Capacity}")]
internal sealed class NetSafeQueue
{
private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim();
// Example:
// m_capacity = 8
// m_size = 6
// m_head = 4
//
// [0] item
// [1] item (tail = ((head + size - 1) % capacity)
// [2]
// [3]
// [4] item (head)
// [5] item
// [6] item
// [7] item
//
private T[] m_items;
private int m_size;
private int m_head;
///
/// Gets the number of items in the queue
///
public int Count {
get
{
m_lock.EnterReadLock();
int count = m_size;
m_lock.ExitReadLock();
return count;
}
}
///
/// Gets the current capacity for the queue
///
public int Capacity
{
get
{
m_lock.EnterReadLock();
int capacity = m_items.Length;
m_lock.ExitReadLock();
return capacity;
}
}
///
/// NetQueue constructor
///
public NetSafeQueue(int initialCapacity)
{
m_items = new T[initialCapacity];
}
///
/// Adds an item last/tail of the queue
///
public void Enqueue(T item)
{
m_lock.EnterWriteLock();
try
{
if (m_size == m_items.Length)
SetCapacity(m_items.Length + 8);
int slot = (m_head + m_size) % m_items.Length;
m_items[slot] = item;
m_size++;
}
finally
{
m_lock.ExitWriteLock();
}
}
///
/// Adds an item last/tail of the queue
///
public void Enqueue(IEnumerable items)
{
m_lock.EnterWriteLock();
try
{
foreach (var item in items)
{
if (m_size == m_items.Length)
SetCapacity(m_items.Length + 8); // @TODO move this out of loop
int slot = (m_head + m_size) % m_items.Length;
m_items[slot] = item;
m_size++;
}
}
finally
{
m_lock.ExitWriteLock();
}
}
///
/// Places an item first, at the head of the queue
///
public void EnqueueFirst(T item)
{
m_lock.EnterWriteLock();
try
{
if (m_size >= m_items.Length)
SetCapacity(m_items.Length + 8);
m_head--;
if (m_head < 0)
m_head = m_items.Length - 1;
m_items[m_head] = item;
m_size++;
}
finally
{
m_lock.ExitWriteLock();
}
}
// must be called from within a write locked m_lock!
private void SetCapacity(int newCapacity)
{
if (m_size == 0)
{
if (m_size == 0)
{
m_items = new T[newCapacity];
m_head = 0;
return;
}
}
T[] newItems = new T[newCapacity];
if (m_head + m_size - 1 < m_items.Length)
{
Array.Copy(m_items, m_head, newItems, 0, m_size);
}
else
{
Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head);
Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head)));
}
m_items = newItems;
m_head = 0;
}
///
/// Gets an item from the head of the queue, or returns default(T) if empty
///
public bool TryDequeue(out T item)
{
if (m_size == 0)
{
item = default(T);
return false;
}
m_lock.EnterWriteLock();
try
{
if (m_size == 0)
{
item = default(T);
return false;
}
item = m_items[m_head];
m_items[m_head] = default(T);
m_head = (m_head + 1) % m_items.Length;
m_size--;
return true;
}
catch
{
#if DEBUG
throw;
#else
item = default(T);
return false;
#endif
}
finally
{
m_lock.ExitWriteLock();
}
}
///
/// Gets all items from the head of the queue, or returns number of items popped
///
public int TryDrain(IList addTo)
{
if (m_size == 0)
return 0;
m_lock.EnterWriteLock();
try
{
int added = m_size;
while (m_size > 0)
{
var item = m_items[m_head];
addTo.Add(item);
m_items[m_head] = default(T);
m_head = (m_head + 1) % m_items.Length;
m_size--;
}
return added;
}
finally
{
m_lock.ExitWriteLock();
}
}
///
/// Returns default(T) if queue is empty
///
public T TryPeek(int offset)
{
if (m_size == 0)
return default(T);
m_lock.EnterReadLock();
try
{
if (m_size == 0)
return default(T);
return m_items[(m_head + offset) % m_items.Length];
}
finally
{
m_lock.ExitReadLock();
}
}
///
/// Determines whether an item is in the queue
///
public bool Contains(T item)
{
m_lock.EnterReadLock();
try
{
int ptr = m_head;
for (int i = 0; i < m_size; i++)
{
if (m_items[ptr] == null)
{
if (item == null)
return true;
}
else
{
if (m_items[ptr].Equals(item))
return true;
}
ptr = (ptr + 1) % m_items.Length;
}
return false;
}
finally
{
m_lock.ExitReadLock();
}
}
///
/// Copies the queue items to a new array
///
public T[] ToArray()
{
m_lock.EnterReadLock();
try
{
T[] retval = new T[m_size];
int ptr = m_head;
for (int i = 0; i < m_size; i++)
{
retval[i] = m_items[ptr++];
if (ptr >= m_items.Length)
ptr = 0;
}
return retval;
}
finally
{
m_lock.ExitReadLock();
}
}
///
/// Removes all objects from the queue
///
public void Clear()
{
m_lock.EnterWriteLock();
try
{
for (int i = 0; i < m_items.Length; i++)
m_items[i] = default(T);
m_head = 0;
m_size = 0;
}
finally
{
m_lock.ExitWriteLock();
}
}
}
}