using System;
using System.Diagnostics;
using Unity.Collections;
namespace Unity.Networking.Transport.Utilities
{
///
/// A NativeMultiQueue is a set of several FIFO queues split into buckets.
/// Each bucket has its own first and last item and each bucket can have
/// items pushed and popped individually.
///
public struct NativeMultiQueue : IDisposable where T : unmanaged
{
private NativeList m_Queue;
private NativeList m_QueueHeadTail;
private NativeArray m_MaxItems;
public bool IsCreated => m_Queue.IsCreated;
///
/// New NativeMultiQueue has a single bucket and the specified number
/// of items for that bucket. Accessing buckets out of range will grow
/// the number of buckets and pushing more items than the initial capacity
/// will increase the number of items for each bucket.
///
public NativeMultiQueue(int initialMessageCapacity)
{
m_MaxItems = new NativeArray(1, Allocator.Persistent);
m_MaxItems[0] = initialMessageCapacity;
m_Queue = new NativeList(initialMessageCapacity, Allocator.Persistent);
m_QueueHeadTail = new NativeList(2, Allocator.Persistent);
}
public void Dispose()
{
m_MaxItems.Dispose();
m_Queue.Dispose();
m_QueueHeadTail.Dispose();
}
///
/// Enqueue a new item to a specific bucket. If the bucket does not yet exist
/// the number of buckets will be increased and if the queue is full the number
/// of items for each bucket will be increased.
///
public void Enqueue(int bucket, T value)
{
// Grow number of buckets to fit specified index
if (bucket >= m_QueueHeadTail.Length / 2)
{
int oldSize = m_QueueHeadTail.Length;
m_QueueHeadTail.ResizeUninitialized((bucket+1)*2);
for (;oldSize < m_QueueHeadTail.Length; ++oldSize)
m_QueueHeadTail[oldSize] = 0;
m_Queue.ResizeUninitialized((m_QueueHeadTail.Length / 2) * m_MaxItems[0]);
}
int idx = m_QueueHeadTail[bucket * 2 + 1];
if (idx >= m_MaxItems[0])
{
// Grow number of items per bucket
int oldMax = m_MaxItems[0];
while (idx >= m_MaxItems[0])
m_MaxItems[0] = m_MaxItems[0]*2;
int maxBuckets = m_QueueHeadTail.Length / 2;
m_Queue.ResizeUninitialized(maxBuckets * m_MaxItems[0]);
for (int b = maxBuckets-1; b >= 0; --b)
{
for (int i = m_QueueHeadTail[b*2+1]-1; i >= m_QueueHeadTail[b * 2]; --i)
{
m_Queue[b * m_MaxItems[0] + i] = m_Queue[b * oldMax + i];
}
}
}
m_Queue[m_MaxItems[0] * bucket + idx] = value;
m_QueueHeadTail[bucket * 2 + 1] = idx + 1;
}
///
/// Dequeue an item from a specific bucket. If the bucket does not exist or if the
/// bucket is empty the call will fail and return false.
///
public bool Dequeue(int bucket, out T value)
{
if (bucket < 0 || bucket >= m_QueueHeadTail.Length / 2)
{
value = default(T);
return false;
}
int idx = m_QueueHeadTail[bucket * 2];
if (idx >= m_QueueHeadTail[bucket * 2 + 1])
{
m_QueueHeadTail[bucket * 2] = m_QueueHeadTail[bucket * 2 + 1] = 0;
value = default(T);
return false;
}
else if (idx + 1 == m_QueueHeadTail[bucket * 2 + 1])
{
m_QueueHeadTail[bucket * 2] = m_QueueHeadTail[bucket * 2 + 1] = 0;
}
else
{
m_QueueHeadTail[bucket * 2] = idx + 1;
}
value = m_Queue[m_MaxItems[0] * bucket + idx];
return true;
}
///
/// Peek the next item in a specific bucket. If the bucket does not exist or if the
/// bucket is empty the call will fail and return false.
///
public bool Peek(int bucket, out T value)
{
if (bucket < 0 || bucket >= m_QueueHeadTail.Length / 2)
{
value = default(T);
return false;
}
int idx = m_QueueHeadTail[bucket * 2];
if (idx >= m_QueueHeadTail[bucket * 2 + 1])
{
value = default(T);
return false;
}
value = m_Queue[m_MaxItems[0] * bucket + idx];
return true;
}
///
/// Remove all items from a specific bucket. If the bucket does not exist
/// the call will not do anything.
///
public void Clear(int bucket)
{
if (bucket < 0 || bucket >= m_QueueHeadTail.Length / 2)
return;
m_QueueHeadTail[bucket * 2] = 0;
m_QueueHeadTail[bucket * 2 + 1] = 0;
}
}
public struct SequenceHelpers
{
// Calculate difference between the sequence IDs taking into account wrapping, so when you go from 65535 to 0 the distance is 1
public static int AbsDistance(ushort lhs, ushort rhs)
{
int distance;
if (lhs < rhs)
distance = lhs + ushort.MaxValue + 1 - rhs;
else
distance = lhs - rhs;
return distance;
}
public static bool IsNewer(uint current, uint old)
{
// Invert the check so same does not count as newer
return !(old - current < (1u << 31));
}
public static bool GreaterThan16(ushort lhs, ushort rhs)
{
const uint max_sequence_divide_2 = 0x7FFF;
return lhs > rhs && lhs - rhs <= (ushort) max_sequence_divide_2 ||
lhs < rhs && rhs - lhs > (ushort) max_sequence_divide_2;
}
public static bool LessThan16(ushort lhs, ushort rhs)
{
return GreaterThan16(rhs, lhs);
}
public static bool StalePacket(ushort sequence, ushort oldSequence, ushort windowSize)
{
return LessThan16(sequence, (ushort) (oldSequence - windowSize));
}
public static string BitMaskToString(uint mask)
{
// 31 24 16 8 0
// |-------+-------+-------+--------|
// 00000000000000000000000000000000
const int bits = 4 * 8;
var sb = new char[bits];
for (var i = bits - 1; i >= 0; i--)
{
sb[i] = (mask & 1) != 0 ? '1' : '0';
mask >>= 1;
}
return new string(sb);
}
}
public static class RandomHelpers
{
// returns ushort in [1..ushort.MaxValue - 1] range
public static ushort GetRandomUShort()
{
var rnd = new Unity.Mathematics.Random((uint) Stopwatch.GetTimestamp());
return (ushort)rnd.NextUInt(1, 0xffff);
}
}
}