first commit

This commit is contained in:
2025-12-13 14:28:35 +01:00
commit 679c3c9a52
113 changed files with 715750 additions and 0 deletions

150
Riptide/Utils/Bitfield.cs Normal file
View File

@@ -0,0 +1,150 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
using System.Collections.Generic;
namespace Riptide.Utils
{
/// <summary>Provides functionality for managing and manipulating a collection of bits.</summary>
internal class Bitfield
{
/// <summary>The first 8 bits stored in the bitfield.</summary>
internal byte First8 => (byte)segments[0];
/// <summary>The first 16 bits stored in the bitfield.</summary>
internal ushort First16 => (ushort)segments[0];
/// <summary>The number of bits which fit into a single segment.</summary>
private const int SegmentSize = sizeof(uint) * 8;
/// <summary>The segments of the bitfield.</summary>
private readonly List<uint> segments;
/// <summary>Whether or not the bitfield's capacity should dynamically adjust when shifting.</summary>
private readonly bool isDynamicCapacity;
/// <summary>The current number of bits being stored.</summary>
private int count;
/// <summary>The current capacity.</summary>
private int capacity;
/// <summary>Creates a bitfield.</summary>
/// <param name="isDynamicCapacity">Whether or not the bitfield's capacity should dynamically adjust when shifting.</param>
internal Bitfield(bool isDynamicCapacity = true)
{
segments = new List<uint>(4) { 0 };
capacity = segments.Count * SegmentSize;
this.isDynamicCapacity = isDynamicCapacity;
}
/// <summary>Checks if the bitfield has capacity for the given number of bits.</summary>
/// <param name="amount">The number of bits for which to check if there is capacity.</param>
/// <param name="overflow">The number of bits from <paramref name="amount"/> which there is no capacity for.</param>
/// <returns>Whether or not there is sufficient capacity.</returns>
internal bool HasCapacityFor(int amount, out int overflow)
{
overflow = count + amount - capacity;
return overflow < 0;
}
/// <summary>Shifts the bitfield by the given amount.</summary>
/// <param name="amount">How much to shift by.</param>
internal void ShiftBy(int amount)
{
int segmentShift = amount / SegmentSize; // How many WHOLE segments we have to shift by
int bitShift = amount % SegmentSize; // How many bits we have to shift by
if (!isDynamicCapacity)
count = Math.Min(count + amount, SegmentSize);
else if (!HasCapacityFor(amount, out int _))
{
Trim();
count += amount;
if (count > capacity)
{
int increaseBy = segmentShift + 1;
for (int i = 0; i < increaseBy; i++)
segments.Add(0);
capacity = segments.Count * SegmentSize;
}
}
else
count += amount;
int s = segments.Count - 1;
segments[s] <<= bitShift;
s -= 1 + segmentShift;
while (s > -1)
{
ulong shiftedBits = (ulong)segments[s] << bitShift;
segments[s] = (uint)shiftedBits;
segments[s + 1 + segmentShift] |= (uint)(shiftedBits >> SegmentSize);
s--;
}
}
/// <summary>Checks the last bit in the bitfield, and trims it if it is set to 1.</summary>
/// <param name="checkedPosition">The checked bit's position in the bitfield.</param>
/// <returns>Whether or not the checked bit was set.</returns>
internal bool CheckAndTrimLast(out int checkedPosition)
{
checkedPosition = count;
uint bitToCheck = (uint)(1 << ((count - 1) % SegmentSize));
bool isSet = (segments[segments.Count - 1] & bitToCheck) != 0;
count--;
return isSet;
}
/// <summary>Trims all bits from the end of the bitfield until an unset bit is encountered.</summary>
private void Trim()
{
while (count > 0 && IsSet(count))
count--;
}
/// <summary>Sets the given bit to 1.</summary>
/// <param name="bit">The bit to set.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bit"/> is less than 1.</exception>
internal void Set(int bit)
{
if (bit < 1)
throw new ArgumentOutOfRangeException(nameof(bit), $"'{nameof(bit)}' must be greater than zero!");
bit--;
int s = bit / SegmentSize;
uint bitToSet = (uint)(1 << (bit % SegmentSize));
if (s < segments.Count)
segments[s] |= bitToSet;
}
/// <summary>Checks if the given bit is set to 1.</summary>
/// <param name="bit">The bit to check.</param>
/// <returns>Whether or not the bit is set.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bit"/> is less than 1.</exception>
internal bool IsSet(int bit)
{
if (bit > count)
return true;
if (bit < 1)
throw new ArgumentOutOfRangeException(nameof(bit), $"'{nameof(bit)}' must be greater than zero!");
bit--;
int s = bit / SegmentSize;
uint bitToCheck = (uint)(1 << (bit % SegmentSize));
if (s < segments.Count)
return (segments[s] & bitToCheck) != 0;
return true;
}
/// <summary>Combines this bitfield with the given bits.</summary>
/// <param name="other">The bits to OR into the bitfield.</param>
internal void Combine(ushort other)
{
segments[0] |= other;
}
}
}

View File

@@ -0,0 +1,202 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
namespace Riptide.Utils
{
/// <summary>Tracks and manages various metrics of a <see cref="Connection"/>.</summary>
public class ConnectionMetrics
{
/// <summary>The total number of bytes received across all send modes since the last <see cref="Reset"/> call, including those in duplicate and, in
/// the case of notify messages, out-of-order packets. Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int BytesIn => UnreliableBytesIn + NotifyBytesIn + ReliableBytesIn;
/// <summary>The total number of bytes sent across all send modes since the last <see cref="Reset"/> call, including those in automatic resends.
/// Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int BytesOut => UnreliableBytesOut + NotifyBytesOut + ReliableBytesOut;
/// <summary>The total number of messages received across all send modes since the last <see cref="Reset"/> call, including duplicate and out-of-order notify messages.</summary>
public int MessagesIn => UnreliableIn + NotifyIn + ReliableIn;
/// <summary>The total number of messages sent across all send modes since the last <see cref="Reset"/> call, including automatic resends.</summary>
public int MessagesOut => UnreliableOut + NotifyOut + ReliableOut;
/// <summary>The total number of bytes received in unreliable messages since the last <see cref="Reset"/> call. Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int UnreliableBytesIn { get; private set; }
/// <summary>The total number of bytes sent in unreliable messages since the last <see cref="Reset"/> call. Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int UnreliableBytesOut { get; internal set; }
/// <summary>The number of unreliable messages received since the last <see cref="Reset"/> call.</summary>
public int UnreliableIn { get; private set; }
/// <summary>The number of unreliable messages sent since the last <see cref="Reset"/> call.</summary>
public int UnreliableOut { get; internal set; }
/// <summary>The total number of bytes received in notify messages since the last <see cref="Reset"/> call, including those in duplicate and out-of-order packets.
/// Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int NotifyBytesIn { get; private set; }
/// <summary>The total number of bytes sent in notify messages since the last <see cref="Reset"/> call. Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int NotifyBytesOut { get; internal set; }
/// <summary>The number of notify messages received since the last <see cref="Reset"/> call, including duplicate and out-of-order ones.</summary>
public int NotifyIn { get; private set; }
/// <summary>The number of notify messages sent since the last <see cref="Reset"/> call.</summary>
public int NotifyOut { get; internal set; }
/// <summary>The number of duplicate or out-of-order notify messages which were received, but discarded (not handled) since the last <see cref="Reset"/> call.</summary>
public int NotifyDiscarded { get; internal set; }
/// <summary>The number of notify messages lost since the last <see cref="Reset"/> call.</summary>
public int NotifyLost { get; private set; }
/// <summary>The number of notify messages delivered since the last <see cref="Reset"/> call.</summary>
public int NotifyDelivered { get; private set; }
/// <summary>The number of notify messages lost of the last 64 notify messages to be lost or delivered.</summary>
public int RollingNotifyLost { get; private set; }
/// <summary>The number of notify messages delivered of the last 64 notify messages to be lost or delivered.</summary>
public int RollingNotifyDelivered { get; private set; }
/// <summary>The loss rate (0-1) among the last 64 notify messages.</summary>
public float RollingNotifyLossRate => RollingNotifyLost / 64f;
/// <summary>The total number of bytes received in reliable messages since the last <see cref="Reset"/> call, including those in duplicate packets.
/// Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int ReliableBytesIn { get; private set; }
/// <summary>The total number of bytes sent in reliable messages since the last <see cref="Reset"/> call, including those in automatic resends.
/// Does <i>not</i> include packet header bytes, which may vary by transport.</summary>
public int ReliableBytesOut { get; internal set; }
/// <summary>The number of reliable messages received since the last <see cref="Reset"/> call, including duplicates.</summary>
public int ReliableIn { get; private set; }
/// <summary>The number of reliable messages sent since the last <see cref="Reset"/> call, including automatic resends (each resend adds to this value).</summary>
public int ReliableOut { get; internal set; }
/// <summary>The number of duplicate reliable messages which were received, but discarded (and not handled) since the last <see cref="Reset"/> call.</summary>
public int ReliableDiscarded { get; internal set; }
/// <summary>The number of unique reliable messages sent since the last <see cref="Reset"/> call.
/// A message only counts towards this the first time it is sent—subsequent resends are not counted.</summary>
public int ReliableUniques { get; internal set; }
/// <summary>The number of send attempts that were required to deliver recent reliable messages.</summary>
public readonly RollingStat RollingReliableSends;
/// <summary>The left-most bit of a <see cref="ulong"/>, used to store the oldest value in the <see cref="notifyLossTracker"/>.</summary>
private const ulong ULongLeftBit = 1ul << 63;
/// <summary>Which recent notify messages were lost. Each bit corresponds to a message.</summary>
private ulong notifyLossTracker;
/// <summary>How many of the <see cref="notifyLossTracker"/>'s bits are in use.</summary>
private int notifyBufferCount;
/// <summary>Initializes metrics.</summary>
public ConnectionMetrics()
{
Reset();
RollingNotifyDelivered = 0;
RollingNotifyLost = 0;
notifyLossTracker = 0;
notifyBufferCount = 0;
RollingReliableSends = new RollingStat(64);
}
/// <summary>Resets all non-rolling metrics to 0.</summary>
public void Reset()
{
UnreliableBytesIn = 0;
UnreliableBytesOut = 0;
UnreliableIn = 0;
UnreliableOut = 0;
NotifyBytesIn = 0;
NotifyBytesOut = 0;
NotifyIn = 0;
NotifyOut = 0;
NotifyDiscarded = 0;
NotifyLost = 0;
NotifyDelivered = 0;
ReliableBytesIn = 0;
ReliableBytesOut = 0;
ReliableIn = 0;
ReliableOut = 0;
ReliableDiscarded = 0;
ReliableUniques = 0;
}
/// <summary>Updates the metrics associated with receiving an unreliable message.</summary>
/// <param name="byteCount">The number of bytes that were received.</param>
internal void ReceivedUnreliable(int byteCount)
{
UnreliableBytesIn += byteCount;
UnreliableIn++;
}
/// <summary>Updates the metrics associated with sending an unreliable message.</summary>
/// <param name="byteCount">The number of bytes that were sent.</param>
internal void SentUnreliable(int byteCount)
{
UnreliableBytesOut += byteCount;
UnreliableOut++;
}
/// <summary>Updates the metrics associated with receiving a notify message.</summary>
/// <param name="byteCount">The number of bytes that were received.</param>
internal void ReceivedNotify(int byteCount)
{
NotifyBytesIn += byteCount;
NotifyIn++;
}
/// <summary>Updates the metrics associated with sending a notify message.</summary>
/// <param name="byteCount">The number of bytes that were sent.</param>
internal void SentNotify(int byteCount)
{
NotifyBytesOut += byteCount;
NotifyOut++;
}
/// <summary>Updates the metrics associated with delivering a notify message.</summary>
internal void DeliveredNotify()
{
NotifyDelivered++;
if (notifyBufferCount < 64)
{
RollingNotifyDelivered++;
notifyBufferCount++;
}
else if ((notifyLossTracker & ULongLeftBit) == 0)
{
// The one being removed from the buffer was not delivered
RollingNotifyDelivered++;
RollingNotifyLost--;
}
notifyLossTracker <<= 1;
notifyLossTracker |= 1;
}
/// <summary>Updates the metrics associated with losing a notify message.</summary>
internal void LostNotify()
{
NotifyLost++;
if (notifyBufferCount < 64)
{
RollingNotifyLost++;
notifyBufferCount++;
}
else if ((notifyLossTracker & ULongLeftBit) != 0)
{
// The one being removed from the buffer was delivered
RollingNotifyDelivered--;
RollingNotifyLost++;
}
notifyLossTracker <<= 1;
}
/// <summary>Updates the metrics associated with receiving a reliable message.</summary>
/// <param name="byteCount">The number of bytes that were received.</param>
internal void ReceivedReliable(int byteCount)
{
ReliableBytesIn += byteCount;
ReliableIn++;
}
/// <summary>Updates the metrics associated with sending a reliable message.</summary>
/// <param name="byteCount">The number of bytes that were sent.</param>
internal void SentReliable(int byteCount)
{
ReliableBytesOut += byteCount;
ReliableOut++;
}
}
}

954
Riptide/Utils/Converter.cs Normal file
View File

@@ -0,0 +1,954 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Riptide.Utils
{
/// <summary>Provides functionality for converting bits and bytes to various value types and vice versa.</summary>
public class Converter
{
/// <summary>The number of bits in a byte.</summary>
public const int BitsPerByte = 8;
/// <summary>The number of bits in a ulong.</summary>
public const int BitsPerULong = sizeof(ulong) * BitsPerByte;
#region Zig Zag Encoding
/// <summary>Zig zag encodes <paramref name="value"/>.</summary>
/// <param name="value">The value to encode.</param>
/// <returns>The zig zag-encoded value.</returns>
/// <remarks>Zig zag encoding allows small negative numbers to be represented as small positive numbers. All positive numbers are doubled and become even numbers,
/// while all negative numbers become positive odd numbers. In contrast, simply casting a negative value to its unsigned counterpart would result in a large positive
/// number which uses the high bit, rendering compression via <see cref="Message.AddVarULong(ulong)"/> and <see cref="Message.GetVarULong"/> ineffective.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ZigZagEncode(int value)
{
return (value >> 31) ^ (value << 1);
}
/// <inheritdoc cref="ZigZagEncode(int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long ZigZagEncode(long value)
{
return (value >> 63) ^ (value << 1);
}
/// <summary>Zig zag decodes <paramref name="value"/>.</summary>
/// <param name="value">The value to decode.</param>
/// <returns>The zig zag-decoded value.</returns>
/// <inheritdoc cref="ZigZagEncode(int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ZigZagDecode(int value)
{
return (value >> 1) ^ -(value & 1);
}
/// <inheritdoc cref="ZigZagDecode(int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long ZigZagDecode(long value)
{
return (value >> 1) ^ -(value & 1);
}
#endregion
#region Bits
/// <summary>Takes <paramref name="amount"/> bits from <paramref name="bitfield"/> and writes them into <paramref name="array"/>, starting at <paramref name="startBit"/>.</summary>
/// <param name="bitfield">The bitfield from which to write the bits into the array.</param>
/// <param name="amount">The number of bits to write.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The bit position in the array at which to start writing.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetBits(byte bitfield, int amount, byte[] array, int startBit)
{
byte mask = (byte)((1 << amount) - 1);
bitfield &= mask; // Discard any bits that are set beyond the ones we're setting
int inverseMask = ~mask;
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
array[pos] = (byte)(bitfield | (array[pos] & inverseMask));
else
{
array[pos ] = (byte)((bitfield << bit) | (array[pos] & ~(mask << bit)));
array[pos + 1] = (byte)((bitfield >> (8 - bit)) | (array[pos + 1] & (inverseMask >> (8 - bit))));
}
}
/// <inheritdoc cref="SetBits(byte, int, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetBits(ushort bitfield, int amount, byte[] array, int startBit)
{
ushort mask = (ushort)((1 << amount) - 1);
bitfield &= mask; // Discard any bits that are set beyond the ones we're setting
int inverseMask = ~mask;
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
{
array[pos ] = (byte)(bitfield | (array[pos] & inverseMask));
array[pos + 1] = (byte)((bitfield >> 8) | (array[pos + 1] & (inverseMask >> 8)));
}
else
{
array[pos ] = (byte)((bitfield << bit) | (array[pos] & ~(mask << bit)));
bitfield >>= 8 - bit;
inverseMask >>= 8 - bit;
array[pos + 1] = (byte)(bitfield | (array[pos + 1] & inverseMask));
array[pos + 2] = (byte)((bitfield >> 8) | (array[pos + 2] & (inverseMask >> 8)));
}
}
/// <inheritdoc cref="SetBits(byte, int, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetBits(uint bitfield, int amount, byte[] array, int startBit)
{
uint mask = (1u << (amount - 1) << 1) - 1; // Perform 2 shifts, doing it in 1 doesn't cause the value to wrap properly
bitfield &= mask; // Discard any bits that are set beyond the ones we're setting
uint inverseMask = ~mask;
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
{
array[pos ] = (byte)(bitfield | (array[pos] & inverseMask));
array[pos + 1] = (byte)((bitfield >> 8) | (array[pos + 1] & (inverseMask >> 8)));
array[pos + 2] = (byte)((bitfield >> 16) | (array[pos + 2] & (inverseMask >> 16)));
array[pos + 3] = (byte)((bitfield >> 24) | (array[pos + 3] & (inverseMask >> 24)));
}
else
{
array[pos ] = (byte)((bitfield << bit) | (array[pos] & ~(mask << bit)));
bitfield >>= 8 - bit;
inverseMask >>= 8 - bit;
array[pos + 1] = (byte)(bitfield | (array[pos + 1] & inverseMask));
array[pos + 2] = (byte)((bitfield >> 8) | (array[pos + 2] & (inverseMask >> 8)));
array[pos + 3] = (byte)((bitfield >> 16) | (array[pos + 3] & (inverseMask >> 16)));
array[pos + 4] = (byte)((bitfield >> 24) | (array[pos + 4] & ~(mask >> (32 - bit)))); // This one can't use inverseMask because it would have incorrectly zeroed bits
}
}
/// <inheritdoc cref="SetBits(byte, int, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetBits(ulong bitfield, int amount, byte[] array, int startBit)
{
ulong mask = (1ul << (amount - 1) << 1) - 1; // Perform 2 shifts, doing it in 1 doesn't cause the value to wrap properly
bitfield &= mask; // Discard any bits that are set beyond the ones we're setting
ulong inverseMask = ~mask;
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
{
array[pos ] = (byte)(bitfield | (array[pos] & inverseMask));
array[pos + 1] = (byte)((bitfield >> 8) | (array[pos + 1] & (inverseMask >> 8)));
array[pos + 2] = (byte)((bitfield >> 16) | (array[pos + 2] & (inverseMask >> 16)));
array[pos + 3] = (byte)((bitfield >> 24) | (array[pos + 3] & (inverseMask >> 24)));
array[pos + 4] = (byte)((bitfield >> 32) | (array[pos + 4] & (inverseMask >> 32)));
array[pos + 5] = (byte)((bitfield >> 40) | (array[pos + 5] & (inverseMask >> 40)));
array[pos + 6] = (byte)((bitfield >> 48) | (array[pos + 6] & (inverseMask >> 48)));
array[pos + 7] = (byte)((bitfield >> 56) | (array[pos + 7] & (inverseMask >> 56)));
}
else
{
array[pos ] = (byte)((bitfield << bit) | (array[pos] & ~(mask << bit)));
bitfield >>= 8 - bit;
inverseMask >>= 8 - bit;
array[pos + 1] = (byte)(bitfield | (array[pos + 1] & inverseMask));
array[pos + 2] = (byte)((bitfield >> 8) | (array[pos + 2] & (inverseMask >> 8)));
array[pos + 3] = (byte)((bitfield >> 16) | (array[pos + 3] & (inverseMask >> 16)));
array[pos + 4] = (byte)((bitfield >> 24) | (array[pos + 4] & (inverseMask >> 24)));
array[pos + 5] = (byte)((bitfield >> 32) | (array[pos + 5] & (inverseMask >> 32)));
array[pos + 6] = (byte)((bitfield >> 40) | (array[pos + 6] & (inverseMask >> 40)));
array[pos + 7] = (byte)((bitfield >> 48) | (array[pos + 7] & (inverseMask >> 48)));
array[pos + 8] = (byte)((bitfield >> 56) | (array[pos + 8] & ~(mask >> (64 - bit)))); // This one can't use inverseMask because it would have incorrectly zeroed bits
}
}
/// <inheritdoc cref="SetBits(byte, int, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetBits(ulong bitfield, int amount, ulong[] array, int startBit)
{
ulong mask = (1ul << (amount - 1) << 1) - 1; // Perform 2 shifts, doing it in 1 doesn't cause the value to wrap properly
bitfield &= mask; // Discard any bits that are set beyond the ones we're setting
int pos = startBit / BitsPerULong;
int bit = startBit % BitsPerULong;
if (bit == 0)
array[pos] = bitfield | array[pos] & ~mask;
else
{
array[pos] = (bitfield << bit) | (array[pos] & ~(mask << bit));
if (bit + amount >= BitsPerULong)
array[pos + 1] = (bitfield >> (64 - bit)) | (array[pos + 1] & ~(mask >> (64 - bit)));
}
}
/// <summary>Starting at <paramref name="startBit"/>, reads <paramref name="amount"/> bits from <paramref name="array"/> into <paramref name="bitfield"/>.</summary>
/// <param name="amount">The number of bits to read.</param>
/// <param name="array">The array to read the bits from.</param>
/// <param name="startBit">The bit position in the array at which to start reading.</param>
/// <param name="bitfield">The bitfield into which to write the bits from the array.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, byte[] array, int startBit, out byte bitfield)
{
bitfield = ByteFromBits(array, startBit);
bitfield &= (byte)((1 << amount) - 1); // Discard any bits that are set beyond the ones we're reading
}
/// <inheritdoc cref="GetBits(int, byte[], int, out byte)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, byte[] array, int startBit, out ushort bitfield)
{
bitfield = UShortFromBits(array, startBit);
bitfield &= (ushort)((1 << amount) - 1); // Discard any bits that are set beyond the ones we're reading
}
/// <inheritdoc cref="GetBits(int, byte[], int, out byte)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, byte[] array, int startBit, out uint bitfield)
{
bitfield = UIntFromBits(array, startBit);
bitfield &= (1u << (amount - 1) << 1) - 1; // Discard any bits that are set beyond the ones we're reading
}
/// <inheritdoc cref="GetBits(int, byte[], int, out byte)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, byte[] array, int startBit, out ulong bitfield)
{
bitfield = ULongFromBits(array, startBit);
bitfield &= (1ul << (amount - 1) << 1) - 1; // Discard any bits that are set beyond the ones we're reading
}
/// <inheritdoc cref="GetBits(int, byte[], int, out byte)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, ulong[] array, int startBit, out byte bitfield)
{
bitfield = ByteFromBits(array, startBit);
bitfield &= (byte)((1 << amount) - 1); // Discard any bits that are set beyond the ones we're reading
}
/// <inheritdoc cref="GetBits(int, byte[], int, out byte)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, ulong[] array, int startBit, out ushort bitfield)
{
bitfield = UShortFromBits(array, startBit);
bitfield &= (ushort)((1 << amount) - 1); // Discard any bits that are set beyond the ones we're reading
}
/// <inheritdoc cref="GetBits(int, byte[], int, out byte)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, ulong[] array, int startBit, out uint bitfield)
{
bitfield = UIntFromBits(array, startBit);
bitfield &= (1u << (amount - 1) << 1) - 1; // Discard any bits that are set beyond the ones we're reading
}
/// <inheritdoc cref="GetBits(int, byte[], int, out byte)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBits(int amount, ulong[] array, int startBit, out ulong bitfield)
{
bitfield = ULongFromBits(array, startBit);
bitfield &= (1ul << (amount - 1) << 1) - 1; // Discard any bits that are set beyond the ones we're reading
}
#endregion
#region Byte/SByte
/// <summary>Converts <paramref name="value"/> to 8 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="sbyte"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SByteToBits(sbyte value, byte[] array, int startBit) => ByteToBits((byte)value, array, startBit);
/// <inheritdoc cref="SByteToBits(sbyte, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SByteToBits(sbyte value, ulong[] array, int startBit) => ByteToBits((byte)value, array, startBit);
/// <summary>Converts <paramref name="value"/> to 8 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="byte"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ByteToBits(byte value, byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
array[pos] = value;
else
{
array[pos ] |= (byte)(value << bit);
array[pos + 1] = (byte)(value >> (8 - bit));
}
}
/// <inheritdoc cref="ByteToBits(byte, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ByteToBits(byte value, ulong[] array, int startBit) => ToBits(value, BitsPerByte, array, startBit);
/// <summary>Converts the 8 bits at <paramref name="startBit"/> in <paramref name="array"/> to an <see cref="sbyte"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="sbyte"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static sbyte SByteFromBits(byte[] array, int startBit) => (sbyte)ByteFromBits(array, startBit);
/// <inheritdoc cref="SByteFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static sbyte SByteFromBits(ulong[] array, int startBit) => (sbyte)ByteFromBits(array, startBit);
/// <summary>Converts the 8 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="byte"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="byte"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte ByteFromBits(byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
byte value = array[pos];
if (bit == 0)
return value;
value >>= bit;
return (byte)(value | (array[pos + 1] << (8 - bit)));
}
/// <inheritdoc cref="ByteFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte ByteFromBits(ulong[] array, int startBit) => (byte)FromBits(BitsPerByte, array, startBit);
#endregion
#region Bool
/// <summary>Converts <paramref name="value"/> to a bit and writes it into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="bool"/> to convert.</param>
/// <param name="array">The array to write the bit into.</param>
/// <param name="startBit">The position in the array at which to write the bit.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BoolToBit(bool value, byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
array[pos] = 0;
if (value)
array[pos] |= (byte)(1 << bit);
}
/// <inheritdoc cref="BoolToBit(bool, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BoolToBit(bool value, ulong[] array, int startBit)
{
int pos = startBit / BitsPerULong;
int bit = startBit % BitsPerULong;
if (bit == 0)
array[pos] = 0;
if (value)
array[pos] |= 1ul << bit;
}
/// <summary>Converts the bit at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="bool"/>.</summary>
/// <param name="array">The array to convert the bit from.</param>
/// <param name="startBit">The position in the array from which to read the bit.</param>
/// <returns>The converted <see cref="bool"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool BoolFromBit(byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
return (array[pos] & (1 << bit)) != 0;
}
/// <inheritdoc cref="BoolFromBit(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool BoolFromBit(ulong[] array, int startBit)
{
int pos = startBit / BitsPerULong;
int bit = startBit % BitsPerULong;
return (array[pos] & (1ul << bit)) != 0;
}
#endregion
#region Short/UShort
/// <summary>Converts a given <see cref="short"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="short"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromShort(short value, byte[] array, int startIndex) => FromUShort((ushort)value, array, startIndex);
/// <summary>Converts a given <see cref="ushort"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="ushort"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromUShort(ushort value, byte[] array, int startIndex)
{
#if BIG_ENDIAN
array[startIndex + 1] = (byte)value;
array[startIndex ] = (byte)(value >> 8);
#else
array[startIndex ] = (byte)value;
array[startIndex + 1] = (byte)(value >> 8);
#endif
}
/// <summary>Converts the 2 bytes in the array at <paramref name="startIndex"/> to a <see cref="short"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="short"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ToShort(byte[] array, int startIndex) => (short)ToUShort(array, startIndex);
/// <summary>Converts the 2 bytes in the array at <paramref name="startIndex"/> to a <see cref="ushort"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="ushort"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ToUShort(byte[] array, int startIndex)
{
#if BIG_ENDIAN
return (ushort)(array[startIndex + 1] | (array[startIndex ] << 8));
#else
return (ushort)(array[startIndex ] | (array[startIndex + 1] << 8));
#endif
}
/// <summary>Converts <paramref name="value"/> to 16 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="short"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ShortToBits(short value, byte[] array, int startBit) => UShortToBits((ushort)value, array, startBit);
/// <inheritdoc cref="ShortToBits(short, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ShortToBits(short value, ulong[] array, int startBit) => UShortToBits((ushort)value, array, startBit);
/// <summary>Converts <paramref name="value"/> to 16 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="ushort"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UShortToBits(ushort value, byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
{
array[pos] = (byte)value;
array[pos + 1] = (byte)(value >> 8);
}
else
{
array[pos ] |= (byte)(value << bit);
value >>= 8 - bit;
array[pos + 1] = (byte)value;
array[pos + 2] = (byte)(value >> 8);
}
}
/// <inheritdoc cref="UShortToBits(ushort, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UShortToBits(ushort value, ulong[] array, int startBit) => ToBits(value, sizeof(ushort) * BitsPerByte, array, startBit);
/// <summary>Converts the 16 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="short"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="short"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ShortFromBits(byte[] array, int startBit) => (short)UShortFromBits(array, startBit);
/// <inheritdoc cref="ShortFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ShortFromBits(ulong[] array, int startBit) => (short)UShortFromBits(array, startBit);
/// <summary>Converts the 16 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="ushort"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="ushort"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort UShortFromBits(byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
ushort value = (ushort)(array[pos] | (array[pos + 1] << 8));
if (bit == 0)
return value;
value >>= bit;
return (ushort)(value | (array[pos + 2] << (16 - bit)));
}
/// <inheritdoc cref="UShortFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort UShortFromBits(ulong[] array, int startBit) => (ushort)FromBits(sizeof(ushort) * BitsPerByte, array, startBit);
#endregion
#region Int/UInt
/// <summary>Converts a given <see cref="int"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="int"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromInt(int value, byte[] array, int startIndex) => FromUInt((uint)value, array, startIndex);
/// <summary>Converts a given <see cref="uint"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="uint"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromUInt(uint value, byte[] array, int startIndex)
{
#if BIG_ENDIAN
array[startIndex + 3] = (byte)value;
array[startIndex + 2] = (byte)(value >> 8);
array[startIndex + 1] = (byte)(value >> 16);
array[startIndex ] = (byte)(value >> 24);
#else
array[startIndex ] = (byte)value;
array[startIndex + 1] = (byte)(value >> 8);
array[startIndex + 2] = (byte)(value >> 16);
array[startIndex + 3] = (byte)(value >> 24);
#endif
}
/// <summary>Converts the 4 bytes in the array at <paramref name="startIndex"/> to a <see cref="int"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="int"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToInt(byte[] array, int startIndex) => (int)ToUInt(array, startIndex);
/// <summary>Converts the 4 bytes in the array at <paramref name="startIndex"/> to a <see cref="uint"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="uint"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ToUInt(byte[] array, int startIndex)
{
#if BIG_ENDIAN
return (uint)(array[startIndex + 3] | (array[startIndex + 2] << 8) | (array[startIndex + 1] << 16) | (array[startIndex ] << 24));
#else
return (uint)(array[startIndex ] | (array[startIndex + 1] << 8) | (array[startIndex + 2] << 16) | (array[startIndex + 3] << 24));
#endif
}
/// <summary>Converts <paramref name="value"/> to 32 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="int"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IntToBits(int value, byte[] array, int startBit) => UIntToBits((uint)value, array, startBit);
/// <inheritdoc cref="IntToBits(int, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IntToBits(int value, ulong[] array, int startBit) => UIntToBits((uint)value, array, startBit);
/// <summary>Converts <paramref name="value"/> to 32 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="uint"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UIntToBits(uint value, byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
{
array[pos ] = (byte)value;
array[pos + 1] = (byte)(value >> 8);
array[pos + 2] = (byte)(value >> 16);
array[pos + 3] = (byte)(value >> 24);
}
else
{
array[pos ] |= (byte)(value << bit);
value >>= 8 - bit;
array[pos + 1] = (byte)value;
array[pos + 2] = (byte)(value >> 8);
array[pos + 3] = (byte)(value >> 16);
array[pos + 4] = (byte)(value >> 24);
}
}
/// <inheritdoc cref="UIntToBits(uint, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UIntToBits(uint value, ulong[] array, int startBit) => ToBits(value, sizeof(uint) * BitsPerByte, array, startBit);
/// <summary>Converts the 32 bits at <paramref name="startBit"/> in <paramref name="array"/> to an <see cref="int"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="int"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IntFromBits(byte[] array, int startBit) => (int)UIntFromBits(array, startBit);
/// <inheritdoc cref="IntFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IntFromBits(ulong[] array, int startBit) => (int)UIntFromBits(array, startBit);
/// <summary>Converts the 32 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="uint"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="uint"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint UIntFromBits(byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
uint value = (uint)(array[pos] | (array[pos + 1] << 8) | (array[pos + 2] << 16) | (array[pos + 3] << 24));
if (bit == 0)
return value;
value >>= bit;
return value | (uint)(array[pos + 4] << (32 - bit));
}
/// <inheritdoc cref="UIntFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint UIntFromBits(ulong[] array, int startBit) => (uint)FromBits(sizeof(uint) * BitsPerByte, array, startBit);
#endregion
#region Long/ULong
/// <summary>Converts a given <see cref="long"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="long"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromLong(long value, byte[] array, int startIndex) => FromULong((ulong)value, array, startIndex);
/// <summary>Converts a given <see cref="ulong"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="ulong"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromULong(ulong value, byte[] array, int startIndex)
{
#if BIG_ENDIAN
array[startIndex + 7] = (byte)value;
array[startIndex + 6] = (byte)(value >> 8);
array[startIndex + 5] = (byte)(value >> 16);
array[startIndex + 4] = (byte)(value >> 24);
array[startIndex + 3] = (byte)(value >> 32);
array[startIndex + 2] = (byte)(value >> 40);
array[startIndex + 1] = (byte)(value >> 48);
array[startIndex ] = (byte)(value >> 56);
#else
array[startIndex ] = (byte)value;
array[startIndex + 1] = (byte)(value >> 8);
array[startIndex + 2] = (byte)(value >> 16);
array[startIndex + 3] = (byte)(value >> 24);
array[startIndex + 4] = (byte)(value >> 32);
array[startIndex + 5] = (byte)(value >> 40);
array[startIndex + 6] = (byte)(value >> 48);
array[startIndex + 7] = (byte)(value >> 56);
#endif
}
/// <summary>Converts the 8 bytes in the array at <paramref name="startIndex"/> to a <see cref="long"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="long"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long ToLong(byte[] array, int startIndex)
{
#if BIG_ENDIAN
Array.Reverse(array, startIndex, longLength);
#endif
return BitConverter.ToInt64(array, startIndex);
}
/// <summary>Converts the 8 bytes in the array at <paramref name="startIndex"/> to a <see cref="ulong"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="ulong"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ToULong(byte[] array, int startIndex)
{
#if BIG_ENDIAN
Array.Reverse(array, startIndex, ulongLength);
#endif
return BitConverter.ToUInt64(array, startIndex);
}
/// <summary>Converts <paramref name="value"/> to 64 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="long"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LongToBits(long value, byte[] array, int startBit) => ULongToBits((ulong)value, array, startBit);
/// <inheritdoc cref="LongToBits(long, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LongToBits(long value, ulong[] array, int startBit) => ULongToBits((ulong)value, array, startBit);
/// <summary>Converts <paramref name="value"/> to 64 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="ulong"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ULongToBits(ulong value, byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
if (bit == 0)
{
array[pos ] = (byte)value;
array[pos + 1] = (byte)(value >> 8);
array[pos + 2] = (byte)(value >> 16);
array[pos + 3] = (byte)(value >> 24);
array[pos + 4] = (byte)(value >> 32);
array[pos + 5] = (byte)(value >> 40);
array[pos + 6] = (byte)(value >> 48);
array[pos + 7] = (byte)(value >> 56);
}
else
{
array[pos ] |= (byte)(value << bit);
value >>= 8 - bit;
array[pos + 1] = (byte)value;
array[pos + 2] = (byte)(value >> 8);
array[pos + 3] = (byte)(value >> 16);
array[pos + 4] = (byte)(value >> 24);
array[pos + 5] = (byte)(value >> 32);
array[pos + 6] = (byte)(value >> 40);
array[pos + 7] = (byte)(value >> 48);
array[pos + 8] = (byte)(value >> 56);
}
}
/// <inheritdoc cref="ULongToBits(ulong, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ULongToBits(ulong value, ulong[] array, int startBit)
{
int pos = startBit / BitsPerULong;
int bit = startBit % BitsPerULong;
if (bit == 0)
array[pos] = value;
else
{
array[pos ] |= value << bit;
array[pos + 1] = value >> (BitsPerULong - bit);
}
}
/// <summary>Converts the 64 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="long"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="long"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long LongFromBits(byte[] array, int startBit) => (long)ULongFromBits(array, startBit);
/// <inheritdoc cref="LongFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long LongFromBits(ulong[] array, int startBit) => (long)ULongFromBits(array, startBit);
/// <summary>Converts the 64 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="ulong"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="ulong"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ULongFromBits(byte[] array, int startBit)
{
int pos = startBit / BitsPerByte;
int bit = startBit % BitsPerByte;
ulong value = BitConverter.ToUInt64(array, pos);
if (bit == 0)
return value;
value >>= bit;
return value | ((ulong)array[pos + 8] << (64 - bit));
}
/// <inheritdoc cref="ULongFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ULongFromBits(ulong[] array, int startBit)
{
int pos = startBit / BitsPerULong;
int bit = startBit % BitsPerULong;
ulong value = array[pos];
if (bit == 0)
return value;
value >>= bit;
return value | (array[pos + 1] << (BitsPerULong - bit));
}
/// <summary>Converts <paramref name="value"/> to <paramref name="valueSize"/> bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.
/// Meant for values which fit into a <see cref="ulong"/>, not for <see cref="ulong"/>s themselves.</summary>
/// <param name="value">The value to convert.</param>
/// <param name="valueSize">The size in bits of the value being converted.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ToBits(ulong value, int valueSize, ulong[] array, int startBit)
{
int pos = startBit / BitsPerULong;
int bit = startBit % BitsPerULong;
if (bit == 0)
array[pos] = value;
else if (bit + valueSize < BitsPerULong)
array[pos] |= value << bit;
else
{
array[pos] |= value << bit;
array[pos + 1] = value >> (BitsPerULong - bit);
}
}
/// <summary>Converts the <paramref name="valueSize"/> bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="ulong"/>.
/// Meant for values which fit into a <see cref="ulong"/>, not for <see cref="ulong"/>s themselves.</summary>
/// <param name="valueSize">The size in bits of the value being converted.</param>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="ulong"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong FromBits(int valueSize, ulong[] array, int startBit)
{
int pos = startBit / BitsPerULong;
int bit = startBit % BitsPerULong;
ulong value = array[pos];
if (bit == 0)
return value;
value >>= bit;
if (bit + valueSize < BitsPerULong)
return value;
return value | (array[pos + 1] << (BitsPerULong - bit));
}
#endregion
#region Float
/// <summary>Converts a given <see cref="float"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="float"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromFloat(float value, byte[] array, int startIndex)
{
FloatConverter converter = new FloatConverter { FloatValue = value };
#if BIG_ENDIAN
array[startIndex + 3] = converter.Byte0;
array[startIndex + 2] = converter.Byte1;
array[startIndex + 1] = converter.Byte2;
array[startIndex ] = converter.Byte3;
#else
array[startIndex ] = converter.Byte0;
array[startIndex + 1] = converter.Byte1;
array[startIndex + 2] = converter.Byte2;
array[startIndex + 3] = converter.Byte3;
#endif
}
/// <summary>Converts the 4 bytes in the array at <paramref name="startIndex"/> to a <see cref="float"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="float"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ToFloat(byte[] array, int startIndex)
{
#if BIG_ENDIAN
return new FloatConverter { Byte3 = array[startIndex], Byte2 = array[startIndex + 1], Byte1 = array[startIndex + 2], Byte0 = array[startIndex + 3] }.FloatValue;
#else
return new FloatConverter { Byte0 = array[startIndex], Byte1 = array[startIndex + 1], Byte2 = array[startIndex + 2], Byte3 = array[startIndex + 3] }.FloatValue;
#endif
}
/// <summary>Converts <paramref name="value"/> to 32 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="float"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FloatToBits(float value, byte[] array, int startBit)
{
UIntToBits(new FloatConverter { FloatValue = value }.UIntValue, array, startBit);
}
/// <inheritdoc cref="FloatToBits(float, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FloatToBits(float value, ulong[] array, int startBit)
{
UIntToBits(new FloatConverter { FloatValue = value }.UIntValue, array, startBit);
}
/// <summary>Converts the 32 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="float"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="float"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float FloatFromBits(byte[] array, int startBit)
{
return new FloatConverter { UIntValue = UIntFromBits(array, startBit) }.FloatValue;
}
/// <inheritdoc cref="FloatFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float FloatFromBits(ulong[] array, int startBit)
{
return new FloatConverter { UIntValue = UIntFromBits(array, startBit) }.FloatValue;
}
#endregion
#region Double
/// <summary>Converts a given <see cref="double"/> to bytes and writes them into the given array.</summary>
/// <param name="value">The <see cref="double"/> to convert.</param>
/// <param name="array">The array to write the bytes into.</param>
/// <param name="startIndex">The position in the array at which to write the bytes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromDouble(double value, byte[] array, int startIndex)
{
DoubleConverter converter = new DoubleConverter { DoubleValue = value };
#if BIG_ENDIAN
array[startIndex + 7] = converter.Byte0;
array[startIndex + 6] = converter.Byte1;
array[startIndex + 5] = converter.Byte2;
array[startIndex + 4] = converter.Byte3;
array[startIndex + 3] = converter.Byte4;
array[startIndex + 2] = converter.Byte5;
array[startIndex + 1] = converter.Byte6;
array[startIndex ] = converter.Byte7;
#else
array[startIndex ] = converter.Byte0;
array[startIndex + 1] = converter.Byte1;
array[startIndex + 2] = converter.Byte2;
array[startIndex + 3] = converter.Byte3;
array[startIndex + 4] = converter.Byte4;
array[startIndex + 5] = converter.Byte5;
array[startIndex + 6] = converter.Byte6;
array[startIndex + 7] = converter.Byte7;
#endif
}
/// <summary>Converts the 8 bytes in the array at <paramref name="startIndex"/> to a <see cref="double"/>.</summary>
/// <param name="array">The array to read the bytes from.</param>
/// <param name="startIndex">The position in the array at which to read the bytes.</param>
/// <returns>The converted <see cref="double"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double ToDouble(byte[] array, int startIndex)
{
#if BIG_ENDIAN
Array.Reverse(array, startIndex, doubleLength);
#endif
return BitConverter.ToDouble(array, startIndex);
}
/// <summary>Converts <paramref name="value"/> to 64 bits and writes them into <paramref name="array"/> at <paramref name="startBit"/>.</summary>
/// <param name="value">The <see cref="double"/> to convert.</param>
/// <param name="array">The array to write the bits into.</param>
/// <param name="startBit">The position in the array at which to write the bits.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DoubleToBits(double value, byte[] array, int startBit)
{
ULongToBits(new DoubleConverter { DoubleValue = value }.ULongValue, array, startBit);
}
/// <inheritdoc cref="DoubleToBits(double, byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DoubleToBits(double value, ulong[] array, int startBit)
{
ULongToBits(new DoubleConverter { DoubleValue = value }.ULongValue, array, startBit);
}
/// <summary>Converts the 64 bits at <paramref name="startBit"/> in <paramref name="array"/> to a <see cref="double"/>.</summary>
/// <param name="array">The array to convert the bits from.</param>
/// <param name="startBit">The position in the array from which to read the bits.</param>
/// <returns>The converted <see cref="double"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double DoubleFromBits(byte[] array, int startBit)
{
return new DoubleConverter { ULongValue = ULongFromBits(array, startBit) }.DoubleValue;
}
/// <inheritdoc cref="DoubleFromBits(byte[], int)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double DoubleFromBits(ulong[] array, int startBit)
{
return new DoubleConverter { ULongValue = ULongFromBits(array, startBit) }.DoubleValue;
}
#endregion
}
[StructLayout(LayoutKind.Explicit)]
internal struct FloatConverter
{
[FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3;
[FieldOffset(0)] public float FloatValue;
[FieldOffset(0)] public uint UIntValue;
}
[StructLayout(LayoutKind.Explicit)]
internal struct DoubleConverter
{
[FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3;
[FieldOffset(4)] public byte Byte4;
[FieldOffset(5)] public byte Byte5;
[FieldOffset(6)] public byte Byte6;
[FieldOffset(7)] public byte Byte7;
[FieldOffset(0)] public double DoubleValue;
[FieldOffset(0)] public ulong ULongValue;
}
}

View File

@@ -0,0 +1,61 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using Riptide.Transports;
namespace Riptide.Utils
{
/// <summary>Executes an action when invoked.</summary>
internal abstract class DelayedEvent
{
/// <summary>Executes the action.</summary>
public abstract void Invoke();
}
/// <summary>Resends a <see cref="PendingMessage"/> when invoked.</summary>
internal class ResendEvent : DelayedEvent
{
/// <summary>The message to resend.</summary>
private readonly PendingMessage message;
/// <summary>The time at which the resend event was queued.</summary>
private readonly long initiatedAtTime;
/// <summary>Initializes the event.</summary>
/// <param name="message">The message to resend.</param>
/// <param name="initiatedAtTime">The time at which the resend event was queued.</param>
public ResendEvent(PendingMessage message, long initiatedAtTime)
{
this.message = message;
this.initiatedAtTime = initiatedAtTime;
}
/// <inheritdoc/>
public override void Invoke()
{
if (initiatedAtTime == message.LastSendTime) // If this isn't the case then the message has been resent already
message.RetrySend();
}
}
/// <summary>Executes a heartbeat when invoked.</summary>
internal class HeartbeatEvent : DelayedEvent
{
/// <summary>The peer whose heart to beat.</summary>
private readonly Peer peer;
/// <summary>Initializes the event.</summary>
/// <param name="peer">The peer whose heart to beat.</param>
public HeartbeatEvent(Peer peer)
{
this.peer = peer;
}
/// <inheritdoc/>
public override void Invoke()
{
peer.Heartbeat();
}
}
}

View File

@@ -0,0 +1,23 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System.Net;
namespace Riptide.Utils
{
/// <summary>Contains extension methods for various classes.</summary>
public static class Extensions
{
/// <summary>Takes the <see cref="IPEndPoint"/>'s IP address and port number and converts it to a string, accounting for whether the address is an IPv4 or IPv6 address.</summary>
/// <returns>A string containing the IP address and port number of the endpoint.</returns>
public static string ToStringBasedOnIPFormat(this IPEndPoint endPoint)
{
if (endPoint.Address.IsIPv4MappedToIPv6)
return $"{endPoint.Address.MapToIPv4()}:{endPoint.Port}";
return endPoint.ToString();
}
}
}

113
Riptide/Utils/Helper.cs Normal file
View File

@@ -0,0 +1,113 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
namespace Riptide.Utils
{
/// <summary>Contains miscellaneous helper methods.</summary>
internal class Helper
{
/// <summary>The text to log when disconnected due to <see cref="DisconnectReason.NeverConnected"/>.</summary>
private const string DCNeverConnected = "Never connected";
/// <summary>The text to log when disconnected due to <see cref="DisconnectReason.TransportError"/>.</summary>
private const string DCTransportError = "Transport error";
/// <summary>The text to log when disconnected due to <see cref="DisconnectReason.TimedOut"/>.</summary>
private const string DCTimedOut = "Timed out";
/// <summary>The text to log when disconnected due to <see cref="DisconnectReason.Kicked"/>.</summary>
private const string DCKicked = "Kicked";
/// <summary>The text to log when disconnected due to <see cref="DisconnectReason.ServerStopped"/>.</summary>
private const string DCServerStopped = "Server stopped";
/// <summary>The text to log when disconnected due to <see cref="DisconnectReason.Disconnected"/>.</summary>
private const string DCDisconnected = "Disconnected";
/// <summary>The text to log when disconnected due to <see cref="DisconnectReason.PoorConnection"/>.</summary>
private const string DCPoorConnection = "Poor connection";
/// <summary>The text to log when disconnected or rejected due to an unknown reason.</summary>
private const string UnknownReason = "Unknown reason";
/// <summary>The text to log when the connection failed due to <see cref="RejectReason.NoConnection"/>.</summary>
private const string CRNoConnection = "No connection";
/// <summary>The text to log when the connection failed due to <see cref="RejectReason.AlreadyConnected"/>.</summary>
private const string CRAlreadyConnected = "This client is already connected";
/// <summary>The text to log when the connection failed due to <see cref="RejectReason.ServerFull"/>.</summary>
private const string CRServerFull = "Server is full";
/// <summary>The text to log when the connection failed due to <see cref="RejectReason.Rejected"/>.</summary>
private const string CRRejected = "Rejected";
/// <summary>The text to log when the connection failed due to <see cref="RejectReason.Custom"/>.</summary>
private const string CRCustom = "Rejected (with custom data)";
/// <summary>Determines whether <paramref name="singular"/> or <paramref name="plural"/> form should be used based on the <paramref name="amount"/>.</summary>
/// <param name="amount">The amount that <paramref name="singular"/> and <paramref name="plural"/> refer to.</param>
/// <param name="singular">The singular form.</param>
/// <param name="plural">The plural form.</param>
/// <returns><paramref name="singular"/> if <paramref name="amount"/> is 1; otherwise <paramref name="plural"/>.</returns>
internal static string CorrectForm(int amount, string singular, string plural = "")
{
if (string.IsNullOrEmpty(plural))
plural = $"{singular}s";
return amount == 1 ? singular : plural;
}
/// <summary>Calculates the signed gap between sequence IDs, accounting for wrapping.</summary>
/// <param name="seqId1">The new sequence ID.</param>
/// <param name="seqId2">The previous sequence ID.</param>
/// <returns>The signed gap between the two given sequence IDs. A positive gap means <paramref name="seqId1"/> is newer than <paramref name="seqId2"/>. A negative gap means <paramref name="seqId1"/> is older than <paramref name="seqId2"/>.</returns>
internal static int GetSequenceGap(ushort seqId1, ushort seqId2)
{
int gap = seqId1 - seqId2;
if (Math.Abs(gap) <= 32768) // Difference is small, meaning sequence IDs are close together
return gap;
else // Difference is big, meaning sequence IDs are far apart
return (seqId1 <= 32768 ? ushort.MaxValue + 1 + seqId1 : seqId1) - (seqId2 <= 32768 ? ushort.MaxValue + 1 + seqId2 : seqId2);
}
/// <summary>Retrieves the appropriate reason string for the given <see cref="DisconnectReason"/>.</summary>
/// <param name="forReason">The <see cref="DisconnectReason"/> to retrieve the string for.</param>
/// <returns>The appropriate reason string.</returns>
internal static string GetReasonString(DisconnectReason forReason)
{
switch (forReason)
{
case DisconnectReason.NeverConnected:
return DCNeverConnected;
case DisconnectReason.TransportError:
return DCTransportError;
case DisconnectReason.TimedOut:
return DCTimedOut;
case DisconnectReason.Kicked:
return DCKicked;
case DisconnectReason.ServerStopped:
return DCServerStopped;
case DisconnectReason.Disconnected:
return DCDisconnected;
case DisconnectReason.PoorConnection:
return DCPoorConnection;
default:
return $"{UnknownReason} '{forReason}'";
}
}
/// <summary>Retrieves the appropriate reason string for the given <see cref="RejectReason"/>.</summary>
/// <param name="forReason">The <see cref="RejectReason"/> to retrieve the string for.</param>
/// <returns>The appropriate reason string.</returns>
internal static string GetReasonString(RejectReason forReason)
{
switch (forReason)
{
case RejectReason.NoConnection:
return CRNoConnection;
case RejectReason.AlreadyConnected:
return CRAlreadyConnected;
case RejectReason.ServerFull:
return CRServerFull;
case RejectReason.Rejected:
return CRRejected;
case RejectReason.Custom:
return CRCustom;
default:
return $"{UnknownReason} '{forReason}'";
}
}
}
}

View File

@@ -0,0 +1,158 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
using System.Collections.Generic;
namespace Riptide.Utils
{
// PriorityQueue unfortunately doesn't exist in .NET Standard 2.1
/// <summary>Represents a collection of items that have a value and a priority. On dequeue, the item with the lowest priority value is removed.</summary>
/// <typeparam name="TElement">Specifies the type of elements in the queue.</typeparam>
/// <typeparam name="TPriority">Specifies the type of priority associated with enqueued elements.</typeparam>
public class PriorityQueue<TElement, TPriority>
{
/// <summary>Gets the number of elements contained in the <see cref="PriorityQueue{TElement, TPriority}"/>.</summary>
public int Count { get; private set; }
private const int DefaultCapacity = 8;
private Entry<TElement, TPriority>[] heap;
private readonly IComparer<TPriority> comparer;
/// <summary>Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class.</summary>
/// <param name="capacity">Initial capacity to allocate for the underlying heap array.</param>
public PriorityQueue(int capacity = DefaultCapacity)
{
heap = new Entry<TElement, TPriority>[capacity];
comparer = Comparer<TPriority>.Default;
}
/// <summary>Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}"/> class with the specified custom priority comparer.</summary>
/// <param name="comparer">Custom comparer dictating the ordering of elements.</param>
/// <param name="capacity">Initial capacity to allocate for the underlying heap array.</param>
public PriorityQueue(IComparer<TPriority> comparer, int capacity = DefaultCapacity)
{
heap = new Entry<TElement, TPriority>[capacity];
this.comparer = comparer;
}
/// <summary>Adds the specified element and associated priority to the <see cref="PriorityQueue{TElement, TPriority}"/>.</summary>
/// <param name="element">The element to add.</param>
/// <param name="priority">The priority with which to associate the new element.</param>
public void Enqueue(TElement element, TPriority priority)
{
if (Count == heap.Length)
{
// Resizing is necessary
Entry<TElement, TPriority>[] temp = new Entry<TElement, TPriority>[Count * 2];
Array.Copy(heap, temp, heap.Length);
heap = temp;
}
int index = Count;
while (index > 0)
{
int parentIndex = GetParentIndex(index);
if (comparer.Compare(priority, heap[parentIndex].Priority) < 0)
{
heap[index] = heap[parentIndex];
index = parentIndex;
}
else
break;
}
heap[index] = new Entry<TElement, TPriority>(element, priority);
Count++;
}
/// <summary>Removes and returns the lowest priority element.</summary>
public TElement Dequeue()
{
TElement returnValue = heap[0].Element;
if (Count > 1)
{
int parent = 0;
int leftChild = GetLeftChildIndex(parent);
while (leftChild < Count)
{
int rightChild = leftChild + 1;
int bestChild = (rightChild < Count && comparer.Compare(heap[rightChild].Priority, heap[leftChild].Priority) < 0) ? rightChild : leftChild;
heap[parent] = heap[bestChild];
parent = bestChild;
leftChild = GetLeftChildIndex(parent);
}
heap[parent] = heap[Count - 1];
}
Count--;
return returnValue;
}
/// <summary>Removes the lowest priority element from the <see cref="PriorityQueue{TElement, TPriority}"/> and copies it and its associated priority to the <paramref name="element"/> and <paramref name="priority"/> arguments.</summary>
/// <param name="element">When this method returns, contains the removed element.</param>
/// <param name="priority">When this method returns, contains the priority associated with the removed element.</param>
/// <returns>true if the element is successfully removed; false if the <see cref="PriorityQueue{TElement, TPriority}"/> is empty.</returns>
public bool TryDequeue(out TElement element, out TPriority priority)
{
if (Count > 0)
{
priority = heap[0].Priority;
element = Dequeue();
return true;
}
{
element = default(TElement);
priority = default(TPriority);
return false;
}
}
/// <summary>Returns the lowest priority element.</summary>
public TElement Peek()
{
return heap[0].Element;
}
/// <summary>Returns the priority of the lowest priority element.</summary>
public TPriority PeekPriority()
{
return heap[0].Priority;
}
/// <summary>Removes all elements from the <see cref="PriorityQueue{TElement, TPriority}"/>.</summary>
public void Clear()
{
Array.Clear(heap, 0, heap.Length);
Count = 0;
}
private static int GetParentIndex(int index)
{
return (index - 1) / 2;
}
private static int GetLeftChildIndex(int index)
{
return (index * 2) + 1;
}
private struct Entry<TEle, TPrio>
{
internal readonly TEle Element;
internal readonly TPrio Priority;
public Entry(TEle element, TPrio priority)
{
Element = element;
Priority = priority;
}
}
}
}

View File

@@ -0,0 +1,126 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
using System.Collections.Generic;
namespace Riptide.Utils
{
/// <summary>Defines log message types.</summary>
public enum LogType
{
/// <summary>Logs that are used for investigation during development.</summary>
Debug,
/// <summary>Logs that provide general information about application flow.</summary>
Info,
/// <summary>Logs that highlight abnormal or unexpected events in the application flow.</summary>
Warning,
/// <summary>Logs that highlight problematic events in the application flow which will cause unexpected behavior if not planned for.</summary>
Error
}
/// <summary>Provides functionality for logging messages.</summary>
public class RiptideLogger
{
/// <summary>Whether or not <see cref="LogType.Debug"/> messages will be logged.</summary>
public static bool IsDebugLoggingEnabled => logMethods.ContainsKey(LogType.Debug);
/// <summary>Whether or not <see cref="LogType.Info"/> messages will be logged.</summary>
public static bool IsInfoLoggingEnabled => logMethods.ContainsKey(LogType.Info);
/// <summary>Whether or not <see cref="LogType.Warning"/> messages will be logged.</summary>
public static bool IsWarningLoggingEnabled => logMethods.ContainsKey(LogType.Warning);
/// <summary>Whether or not <see cref="LogType.Error"/> messages will be logged.</summary>
public static bool IsErrorLoggingEnabled => logMethods.ContainsKey(LogType.Error);
/// <summary>Encapsulates a method used to log messages.</summary>
/// <param name="log">The message to log.</param>
public delegate void LogMethod(string log);
/// <summary>Log methods, accessible by their <see cref="LogType"/></summary>
private static readonly Dictionary<LogType, LogMethod> logMethods = new Dictionary<LogType, LogMethod>(4);
/// <summary>Whether or not to include timestamps when logging messages.</summary>
private static bool includeTimestamps;
/// <summary>The format to use for timestamps.</summary>
private static string timestampFormat;
/// <summary>Initializes <see cref="RiptideLogger"/> with all log types enabled.</summary>
/// <param name="logMethod">The method to use when logging all types of messages.</param>
/// <param name="includeTimestamps">Whether or not to include timestamps when logging messages.</param>
/// <param name="timestampFormat">The format to use for timestamps.</param>
public static void Initialize(LogMethod logMethod, bool includeTimestamps, string timestampFormat = "HH:mm:ss") => Initialize(logMethod, logMethod, logMethod, logMethod, includeTimestamps, timestampFormat);
/// <summary>Initializes <see cref="RiptideLogger"/> with the supplied log methods.</summary>
/// <param name="debugMethod">The method to use when logging debug messages. Set to <see langword="null"/> to disable debug logs.</param>
/// <param name="infoMethod">The method to use when logging info messages. Set to <see langword="null"/> to disable info logs.</param>
/// <param name="warningMethod">The method to use when logging warning messages. Set to <see langword="null"/> to disable warning logs.</param>
/// <param name="errorMethod">The method to use when logging error messages. Set to <see langword="null"/> to disable error logs.</param>
/// <param name="includeTimestamps">Whether or not to include timestamps when logging messages.</param>
/// <param name="timestampFormat">The format to use for timestamps.</param>
public static void Initialize(LogMethod debugMethod, LogMethod infoMethod, LogMethod warningMethod, LogMethod errorMethod, bool includeTimestamps, string timestampFormat = "HH:mm:ss")
{
logMethods.Clear();
if (debugMethod != null)
logMethods.Add(LogType.Debug, debugMethod);
if (infoMethod != null)
logMethods.Add(LogType.Info, infoMethod);
if (warningMethod != null)
logMethods.Add(LogType.Warning, warningMethod);
if (errorMethod != null)
logMethods.Add(LogType.Error, errorMethod);
RiptideLogger.includeTimestamps = includeTimestamps;
RiptideLogger.timestampFormat = timestampFormat;
}
/// <summary>Enables logging for messages of the given <see cref="LogType"/>.</summary>
/// <param name="logType">The type of message to enable logging for.</param>
/// <param name="logMethod">The method to use when logging this type of message.</param>
public static void EnableLoggingFor(LogType logType, LogMethod logMethod)
{
if (logMethods.ContainsKey(logType))
logMethods[logType] = logMethod;
else
logMethods.Add(logType, logMethod);
}
/// <summary>Disables logging for messages of the given <see cref="LogType"/>.</summary>
/// <param name="logType">The type of message to enable logging for.</param>
public static void DisableLoggingFor(LogType logType) => logMethods.Remove(logType);
/// <summary>Logs a message.</summary>
/// <param name="logType">The type of log message that is being logged.</param>
/// <param name="message">The message to log.</param>
public static void Log(LogType logType, string message)
{
if (logMethods.TryGetValue(logType, out LogMethod logMethod))
{
if (includeTimestamps)
logMethod($"[{GetTimestamp(DateTime.Now)}]: {message}");
else
logMethod(message);
}
}
/// <summary>Logs a message.</summary>
/// <param name="logType">The type of log message that is being logged.</param>
/// <param name="logName">Who is logging this message.</param>
/// <param name="message">The message to log.</param>
public static void Log(LogType logType, string logName, string message)
{
if (logMethods.TryGetValue(logType, out LogMethod logMethod))
{
if (includeTimestamps)
logMethod($"[{GetTimestamp(DateTime.Now)}] ({logName}): {message}");
else
logMethod($"({logName}): {message}");
}
}
/// <summary>Converts a <see cref="DateTime"/> object to a formatted timestamp string.</summary>
/// <param name="time">The time to format.</param>
/// <returns>The formatted timestamp.</returns>
private static string GetTimestamp(DateTime time)
{
return time.ToString(timestampFormat);
}
}
}

View File

@@ -0,0 +1,93 @@
// This file is provided under The MIT License as part of RiptideNetworking.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md
using System;
using System.Linq;
namespace Riptide.Utils
{
/// <summary>Represents a rolling series of numbers.</summary>
public class RollingStat
{
/// <summary>The position in the array of the latest item.</summary>
private int index;
/// <summary>How many of the array's slots are in use.</summary>
private int slotsFilled;
/// <inheritdoc cref="Mean"/>
private double mean;
/// <summary>The sum of the mean subtracted from each value in the array.</summary>
private double sumOfSquares;
/// <summary>The array used to store the values.</summary>
private readonly double[] array;
/// <summary>The mean of the stat's values.</summary>
public double Mean => mean;
/// <summary>The variance of the stat's values.</summary>
public double Variance => slotsFilled > 1 ? sumOfSquares / (slotsFilled - 1) : 0;
/// <summary>The standard deviation of the stat's values.</summary>
public double StandardDev
{
get
{
double variance = Variance;
if (variance >= double.Epsilon)
{
double root = Math.Sqrt(variance);
return double.IsNaN(root) ? 0 : root;
}
return 0;
}
}
/// <summary>Initializes the stat.</summary>
/// <param name="sampleSize">The number of values to store.</param>
public RollingStat(int sampleSize)
{
index = 0;
slotsFilled = 0;
mean = 0;
sumOfSquares = 0;
array = new double[sampleSize];
}
/// <summary>Adds a new value to the stat.</summary>
/// <param name="value">The value to add.</param>
public void Add(double value)
{
if (double.IsNaN(value) || double.IsInfinity(value))
return;
index %= array.Length;
double oldMean = mean;
double oldValue = array[index];
array[index] = value;
index++;
if (slotsFilled == array.Length)
{
double delta = value - oldValue;
mean += delta / slotsFilled;
sumOfSquares += delta * (value - mean + (oldValue - oldMean));
}
else
{
slotsFilled++;
double delta = value - oldMean;
mean += delta / slotsFilled;
sumOfSquares += delta * (value - mean);
}
}
/// <inheritdoc/>
public override string ToString()
{
if (slotsFilled == array.Length)
return string.Join(",", array);
return string.Join(",", array.Take(slotsFilled));
}
}
}