311 lines
10 KiB
C#
311 lines
10 KiB
C#
|
using System;
|
|||
|
using System.Net;
|
|||
|
using System.Threading;
|
|||
|
|
|||
|
namespace LiteNetLib
|
|||
|
{
|
|||
|
//minimal hashset class from dotnet with some optimizations
|
|||
|
public partial class NetManager
|
|||
|
{
|
|||
|
private const int MaxPrimeArrayLength = 0x7FFFFFC3;
|
|||
|
private const int HashPrime = 101;
|
|||
|
private const int Lower31BitMask = 0x7FFFFFFF;
|
|||
|
private static readonly int[] Primes =
|
|||
|
{
|
|||
|
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
|
|||
|
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
|
|||
|
17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
|
|||
|
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
|
|||
|
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
|
|||
|
};
|
|||
|
|
|||
|
private static int HashSetGetPrime(int min)
|
|||
|
{
|
|||
|
foreach (int prime in Primes)
|
|||
|
{
|
|||
|
if (prime >= min)
|
|||
|
return prime;
|
|||
|
}
|
|||
|
|
|||
|
// Outside of our predefined table. Compute the hard way.
|
|||
|
for (int i = (min | 1); i < int.MaxValue; i += 2)
|
|||
|
{
|
|||
|
if (IsPrime(i) && ((i - 1) % HashPrime != 0))
|
|||
|
return i;
|
|||
|
}
|
|||
|
return min;
|
|||
|
|
|||
|
bool IsPrime(int candidate)
|
|||
|
{
|
|||
|
if ((candidate & 1) != 0)
|
|||
|
{
|
|||
|
int limit = (int)Math.Sqrt(candidate);
|
|||
|
for (int divisor = 3; divisor <= limit; divisor += 2)
|
|||
|
{
|
|||
|
if (candidate % divisor == 0)
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
return candidate == 2;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private struct Slot
|
|||
|
{
|
|||
|
internal int HashCode;
|
|||
|
internal int Next;
|
|||
|
internal NetPeer Value;
|
|||
|
}
|
|||
|
|
|||
|
private int[] _buckets;
|
|||
|
private Slot[] _slots;
|
|||
|
private int _count;
|
|||
|
private int _lastIndex;
|
|||
|
private int _freeList = -1;
|
|||
|
private NetPeer[] _peersArray = new NetPeer[32];
|
|||
|
private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
|||
|
private volatile NetPeer _headPeer;
|
|||
|
|
|||
|
private void ClearPeerSet()
|
|||
|
{
|
|||
|
_peersLock.EnterWriteLock();
|
|||
|
_headPeer = null;
|
|||
|
if (_lastIndex > 0)
|
|||
|
{
|
|||
|
Array.Clear(_slots, 0, _lastIndex);
|
|||
|
Array.Clear(_buckets, 0, _buckets.Length);
|
|||
|
_lastIndex = 0;
|
|||
|
_count = 0;
|
|||
|
_freeList = -1;
|
|||
|
}
|
|||
|
_peersArray = new NetPeer[32];
|
|||
|
_peersLock.ExitWriteLock();
|
|||
|
}
|
|||
|
|
|||
|
private bool ContainsPeer(NetPeer item)
|
|||
|
{
|
|||
|
if (_buckets != null)
|
|||
|
{
|
|||
|
int hashCode = item.GetHashCode() & Lower31BitMask;
|
|||
|
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
|
|||
|
{
|
|||
|
if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(item))
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets peer by peer id
|
|||
|
/// </summary>
|
|||
|
/// <param name="id">id of peer</param>
|
|||
|
/// <returns>Peer if peer with id exist, otherwise null</returns>
|
|||
|
public NetPeer GetPeerById(int id)
|
|||
|
{
|
|||
|
return id >= 0 && id < _peersArray.Length ? _peersArray[id] : null;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets peer by peer id
|
|||
|
/// </summary>
|
|||
|
/// <param name="id">id of peer</param>
|
|||
|
/// <param name="peer">resulting peer</param>
|
|||
|
/// <returns>True if peer with id exist, otherwise false</returns>
|
|||
|
public bool TryGetPeerById(int id, out NetPeer peer)
|
|||
|
{
|
|||
|
peer = GetPeerById(id);
|
|||
|
return peer != null;
|
|||
|
}
|
|||
|
|
|||
|
private void AddPeer(NetPeer peer)
|
|||
|
{
|
|||
|
_peersLock.EnterWriteLock();
|
|||
|
if (_headPeer != null)
|
|||
|
{
|
|||
|
peer.NextPeer = _headPeer;
|
|||
|
_headPeer.PrevPeer = peer;
|
|||
|
}
|
|||
|
_headPeer = peer;
|
|||
|
AddPeerToSet(peer);
|
|||
|
if (peer.Id >= _peersArray.Length)
|
|||
|
{
|
|||
|
int newSize = _peersArray.Length * 2;
|
|||
|
while (peer.Id >= newSize)
|
|||
|
newSize *= 2;
|
|||
|
Array.Resize(ref _peersArray, newSize);
|
|||
|
}
|
|||
|
_peersArray[peer.Id] = peer;
|
|||
|
_peersLock.ExitWriteLock();
|
|||
|
}
|
|||
|
|
|||
|
private void RemovePeer(NetPeer peer)
|
|||
|
{
|
|||
|
_peersLock.EnterWriteLock();
|
|||
|
RemovePeerInternal(peer);
|
|||
|
_peersLock.ExitWriteLock();
|
|||
|
}
|
|||
|
|
|||
|
private void RemovePeerInternal(NetPeer peer)
|
|||
|
{
|
|||
|
if (!RemovePeerFromSet(peer))
|
|||
|
return;
|
|||
|
if (peer == _headPeer)
|
|||
|
_headPeer = peer.NextPeer;
|
|||
|
|
|||
|
if (peer.PrevPeer != null)
|
|||
|
peer.PrevPeer.NextPeer = peer.NextPeer;
|
|||
|
if (peer.NextPeer != null)
|
|||
|
peer.NextPeer.PrevPeer = peer.PrevPeer;
|
|||
|
peer.PrevPeer = null;
|
|||
|
|
|||
|
_peersArray[peer.Id] = null;
|
|||
|
_peerIds.Enqueue(peer.Id);
|
|||
|
}
|
|||
|
|
|||
|
private bool RemovePeerFromSet(NetPeer peer)
|
|||
|
{
|
|||
|
if (_buckets == null)
|
|||
|
return false;
|
|||
|
int hashCode = peer.GetHashCode() & Lower31BitMask;
|
|||
|
int bucket = hashCode % _buckets.Length;
|
|||
|
int last = -1;
|
|||
|
for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i].Next)
|
|||
|
{
|
|||
|
if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(peer))
|
|||
|
{
|
|||
|
if (last < 0)
|
|||
|
_buckets[bucket] = _slots[i].Next + 1;
|
|||
|
else
|
|||
|
_slots[last].Next = _slots[i].Next;
|
|||
|
_slots[i].HashCode = -1;
|
|||
|
_slots[i].Value = null;
|
|||
|
_slots[i].Next = _freeList;
|
|||
|
|
|||
|
_count--;
|
|||
|
if (_count == 0)
|
|||
|
{
|
|||
|
_lastIndex = 0;
|
|||
|
_freeList = -1;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_freeList = i;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
private bool TryGetPeer(IPEndPoint endPoint, out NetPeer actualValue)
|
|||
|
{
|
|||
|
if (_buckets != null)
|
|||
|
{
|
|||
|
#if NET8_0_OR_GREATER
|
|||
|
//can be NetPeer or IPEndPoint
|
|||
|
int hashCode = (UseNativeSockets ? endPoint.GetHashCode() : endPoint.Serialize().GetHashCode()) & Lower31BitMask;
|
|||
|
#else
|
|||
|
int hashCode = endPoint.GetHashCode() & Lower31BitMask;
|
|||
|
#endif
|
|||
|
_peersLock.EnterReadLock();
|
|||
|
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
|
|||
|
{
|
|||
|
if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(endPoint))
|
|||
|
{
|
|||
|
actualValue = _slots[i].Value;
|
|||
|
_peersLock.ExitReadLock();
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
_peersLock.ExitReadLock();
|
|||
|
}
|
|||
|
actualValue = null;
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
//only used for NET8
|
|||
|
private bool TryGetPeer(SocketAddress saddr, out NetPeer actualValue)
|
|||
|
{
|
|||
|
if (_buckets != null)
|
|||
|
{
|
|||
|
int hashCode = saddr.GetHashCode() & Lower31BitMask;
|
|||
|
_peersLock.EnterReadLock();
|
|||
|
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
|
|||
|
{
|
|||
|
if (_slots[i].HashCode == hashCode && _slots[i].Value.Serialize().Equals(saddr))
|
|||
|
{
|
|||
|
actualValue = _slots[i].Value;
|
|||
|
_peersLock.ExitReadLock();
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
_peersLock.ExitReadLock();
|
|||
|
}
|
|||
|
actualValue = null;
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
private bool AddPeerToSet(NetPeer value)
|
|||
|
{
|
|||
|
if (_buckets == null)
|
|||
|
{
|
|||
|
int size = HashSetGetPrime(0);
|
|||
|
_buckets = new int[size];
|
|||
|
_slots = new Slot[size];
|
|||
|
}
|
|||
|
|
|||
|
int hashCode = value.GetHashCode() & Lower31BitMask;
|
|||
|
int bucket = hashCode % _buckets.Length;
|
|||
|
for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
|
|||
|
{
|
|||
|
if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(value))
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
int index;
|
|||
|
if (_freeList >= 0)
|
|||
|
{
|
|||
|
index = _freeList;
|
|||
|
_freeList = _slots[index].Next;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (_lastIndex == _slots.Length)
|
|||
|
{
|
|||
|
//increase capacity
|
|||
|
int newSize = 2 * _count;
|
|||
|
newSize = (uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > _count
|
|||
|
? MaxPrimeArrayLength
|
|||
|
: HashSetGetPrime(newSize);
|
|||
|
|
|||
|
// Able to increase capacity; copy elements to larger array and rehash
|
|||
|
Slot[] newSlots = new Slot[newSize];
|
|||
|
Array.Copy(_slots, 0, newSlots, 0, _lastIndex);
|
|||
|
_buckets = new int[newSize];
|
|||
|
for (int i = 0; i < _lastIndex; i++)
|
|||
|
{
|
|||
|
int b = newSlots[i].HashCode % newSize;
|
|||
|
newSlots[i].Next = _buckets[b] - 1;
|
|||
|
_buckets[b] = i + 1;
|
|||
|
}
|
|||
|
_slots = newSlots;
|
|||
|
// this will change during resize
|
|||
|
bucket = hashCode % _buckets.Length;
|
|||
|
}
|
|||
|
index = _lastIndex;
|
|||
|
_lastIndex++;
|
|||
|
}
|
|||
|
_slots[index].HashCode = hashCode;
|
|||
|
_slots[index].Value = value;
|
|||
|
_slots[index].Next = _buckets[bucket] - 1;
|
|||
|
_buckets[bucket] = index + 1;
|
|||
|
_count++;
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|