// 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;
using Riptide.Utils;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
namespace Riptide
{
/// The send mode of a .
public enum MessageSendMode : byte
{
/// Guarantees order but not delivery. Notifies the sender of what happened via the and
/// events. The receiver must handle notify messages via the event, which is different from the other two send modes.
Notify = MessageHeader.Notify,
/// Guarantees neither delivery nor order.
Unreliable = MessageHeader.Unreliable,
/// Guarantees delivery but not order.
Reliable = MessageHeader.Reliable,
}
/// Provides functionality for converting data to bytes and vice versa.
public class Message
{
/// The maximum number of bits required for a message's header.
public const int MaxHeaderSize = NotifyHeaderBits;
/// The number of bits used by the .
internal const int HeaderBits = 4;
/// A bitmask that, when applied, only keeps the bits corresponding to the value.
internal const byte HeaderBitmask = (1 << HeaderBits) - 1;
/// The header size for unreliable messages. Does not count the 2 bytes used for the message ID.
/// 4 bits - header.
internal const int UnreliableHeaderBits = HeaderBits;
/// The header size for reliable messages. Does not count the 2 bytes used for the message ID.
/// 4 bits - header, 16 bits - sequence ID.
internal const int ReliableHeaderBits = HeaderBits + 2 * BitsPerByte;
/// The header size for notify messages.
/// 4 bits - header, 24 bits - ack, 16 bits - sequence ID.
internal const int NotifyHeaderBits = HeaderBits + 5 * BitsPerByte;
/// The minimum number of bytes contained in an unreliable message.
internal const int MinUnreliableBytes = UnreliableHeaderBits / BitsPerByte + (UnreliableHeaderBits % BitsPerByte == 0 ? 0 : 1);
/// The minimum number of bytes contained in a reliable message.
internal const int MinReliableBytes = ReliableHeaderBits / BitsPerByte + (ReliableHeaderBits % BitsPerByte == 0 ? 0 : 1);
/// The minimum number of bytes contained in a notify message.
internal const int MinNotifyBytes = NotifyHeaderBits / BitsPerByte + (NotifyHeaderBits % BitsPerByte == 0 ? 0 : 1);
/// The number of bits in a byte.
private const int BitsPerByte = Converter.BitsPerByte;
/// The number of bits in each data segment.
private const int BitsPerSegment = Converter.BitsPerULong;
/// The maximum number of bytes that a message can contain, including the .
public static int MaxSize { get; private set; }
/// The maximum number of bytes of payload data that a message can contain. This value represents how many bytes can be added to a message on top of the .
public static int MaxPayloadSize
{
get => MaxSize - (MaxHeaderSize / BitsPerByte + (MaxHeaderSize % BitsPerByte == 0 ? 0 : 1));
set
{
if (Peer.ActiveCount > 0)
throw new InvalidOperationException($"Changing the '{nameof(MaxPayloadSize)}' is not allowed while a {nameof(Server)} or {nameof(Client)} is running!");
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value), $"'{nameof(MaxPayloadSize)}' cannot be negative!");
MaxSize = MaxHeaderSize / BitsPerByte + (MaxHeaderSize % BitsPerByte == 0 ? 0 : 1) + value;
maxBitCount = MaxSize * BitsPerByte;
maxArraySize = MaxSize / sizeof(ulong) + (MaxSize % sizeof(ulong) == 0 ? 0 : 1);
ByteBuffer = new byte[MaxSize];
TrimPool(); // When ActiveSocketCount is 0, this clears the pool
PendingMessage.ClearPool();
}
}
/// An intermediary buffer to help convert to a byte array when sending.
internal static byte[] ByteBuffer;
/// The maximum number of bits a message can contain.
private static int maxBitCount;
/// The maximum size of the array.
private static int maxArraySize;
/// How many messages to add to the pool for each or instance that is started.
/// Changes will not affect and instances which are already running until they are restarted.
public static byte InstancesPerPeer { get; set; } = 4;
/// A pool of reusable message instances.
private static readonly List pool = new List(InstancesPerPeer * 2);
static Message()
{
MaxSize = MaxHeaderSize / BitsPerByte + (MaxHeaderSize % BitsPerByte == 0 ? 0 : 1) + 1225;
maxBitCount = MaxSize * BitsPerByte;
maxArraySize = MaxSize / sizeof(ulong) + (MaxSize % sizeof(ulong) == 0 ? 0 : 1);
ByteBuffer = new byte[MaxSize];
}
/// The message's send mode.
public MessageSendMode SendMode { get; private set; }
/// How many bits have been retrieved from the message.
public int ReadBits => readBit;
/// How many unretrieved bits remain in the message.
public int UnreadBits => writeBit - readBit;
/// How many bits have been added to the message.
public int WrittenBits => writeBit;
/// How many more bits can be added to the message.
public int UnwrittenBits => maxBitCount - writeBit;
/// How many of this message's bytes are in use. Rounds up to the next byte because only whole bytes can be sent.
public int BytesInUse => writeBit / BitsPerByte + (writeBit % BitsPerByte == 0 ? 0 : 1);
/// How many bytes have been retrieved from the message.
[Obsolete("Use ReadBits instead.")] public int ReadLength => ReadBits / BitsPerByte + (ReadBits % BitsPerByte == 0 ? 0 : 1);
/// How many more bytes can be retrieved from the message.
[Obsolete("Use UnreadBits instead.")] public int UnreadLength => UnreadBits / BitsPerByte + (UnreadBits % BitsPerByte == 0 ? 0 : 1);
/// How many bytes have been added to the message.
[Obsolete("Use WrittenBits instead.")] public int WrittenLength => WrittenBits / BitsPerByte + (WrittenBits % BitsPerByte == 0 ? 0 : 1);
///
internal ulong[] Data => data;
/// The message's data.
private readonly ulong[] data;
/// The next bit to be read.
private int readBit;
/// The next bit to be written.
private int writeBit;
/// Initializes a reusable instance.
private Message() => data = new ulong[maxArraySize];
/// Gets a completely empty message instance with no header.
/// An empty message instance.
public static Message Create()
{
Message message = RetrieveFromPool();
message.readBit = 0;
message.writeBit = 0;
return message;
}
/// Gets a message instance that can be used for sending.
/// The mode in which the message should be sent.
/// A message instance ready to be sent.
/// This method is primarily intended for use with as notify messages don't have a built-in message ID, and unlike
/// and , this overload does not add a message ID to the message.
public static Message Create(MessageSendMode sendMode)
{
return RetrieveFromPool().Init((MessageHeader)sendMode);
}
/// Gets a message instance that can be used for sending.
/// The mode in which the message should be sent.
/// The message ID.
/// A message instance ready to be sent.
public static Message Create(MessageSendMode sendMode, ushort id)
{
return RetrieveFromPool().Init((MessageHeader)sendMode).AddVarULong(id);
}
///
/// NOTE: will be cast to a . You should ensure that its value never exceeds that of , otherwise you'll encounter unexpected behaviour when handling messages.
public static Message Create(MessageSendMode sendMode, Enum id)
{
return Create(sendMode, (ushort)(object)id);
}
/// Gets a message instance that can be used for sending.
/// The message's header type.
/// A message instance ready to be sent.
internal static Message Create(MessageHeader header)
{
return RetrieveFromPool().Init(header);
}
#region Pooling
/// Trims the message pool to a more appropriate size for how many and/or instances are currently running.
public static void TrimPool()
{
if (Peer.ActiveCount == 0)
{
// No Servers or Clients are running, empty the list and reset the capacity
pool.Clear();
pool.Capacity = InstancesPerPeer * 2; // x2 so there's some buffer room for extra Message instances in the event that more are needed
}
else
{
// Reset the pool capacity and number of Message instances in the pool to what is appropriate for how many Servers & Clients are active
int idealInstanceAmount = Peer.ActiveCount * InstancesPerPeer;
if (pool.Count > idealInstanceAmount)
{
pool.RemoveRange(Peer.ActiveCount * InstancesPerPeer, pool.Count - idealInstanceAmount);
pool.Capacity = idealInstanceAmount * 2;
}
}
}
/// Retrieves a message instance from the pool. If none is available, a new instance is created.
/// A message instance ready to be used for sending or handling.
private static Message RetrieveFromPool()
{
Message message;
if (pool.Count > 0)
{
message = pool[0];
pool.RemoveAt(0);
}
else
message = new Message();
return message;
}
/// Returns the message instance to the internal pool so it can be reused.
public void Release()
{
if (pool.Count < pool.Capacity)
{
// Pool exists and there's room
if (!pool.Contains(this))
pool.Add(this); // Only add it if it's not already in the list, otherwise this method being called twice in a row for whatever reason could cause *serious* issues
}
}
#endregion
#region Functions
/// Initializes the message so that it can be used for sending.
/// The message's header type.
/// The message, ready to be used for sending.
private Message Init(MessageHeader header)
{
data[0] = (byte)header;
SetHeader(header);
return this;
}
/// Initializes the message so that it can be used for receiving/handling.
/// The first byte of the received data.
/// The message's header type.
/// The number of bytes which this message will contain.
/// The message, ready to be used for handling.
internal Message Init(byte firstByte, int contentLength, out MessageHeader header)
{
data[0] = firstByte;
header = (MessageHeader)(firstByte & HeaderBitmask);
SetHeader(header);
writeBit = contentLength * BitsPerByte;
return this;
}
/// Sets the message's header bits to the given and determines the appropriate and read/write positions.
/// The header to use for this message.
private void SetHeader(MessageHeader header)
{
if (header == MessageHeader.Notify)
{
readBit = NotifyHeaderBits;
writeBit = NotifyHeaderBits;
SendMode = MessageSendMode.Notify;
}
else if (header >= MessageHeader.Reliable)
{
readBit = ReliableHeaderBits;
writeBit = ReliableHeaderBits;
SendMode = MessageSendMode.Reliable;
}
else
{
readBit = UnreliableHeaderBits;
writeBit = UnreliableHeaderBits;
SendMode = MessageSendMode.Unreliable;
}
}
#endregion
#region Add & Retrieve Data
#region Message
/// Adds 's unread bits to the message.
/// The message whose unread bits to add.
/// The message that the bits were added to.
/// This method does not move 's internal read position!
public Message AddMessage(Message message) => AddMessage(message, message.UnreadBits, message.readBit);
/// Adds a range of bits from to the message.
/// The message whose bits to add.
/// The number of bits to add.
/// The position in from which to add the bits.
/// The message that the bits were added to.
/// This method does not move 's internal read position!
public Message AddMessage(Message message, int amount, int startBit)
{
if (UnwrittenBits < amount)
throw new InsufficientCapacityException(this, nameof(Message), amount);
int sourcePos = startBit / BitsPerSegment;
int sourceBit = startBit % BitsPerSegment;
int destPos = writeBit / BitsPerSegment;
int destBit = writeBit % BitsPerSegment;
int bitOffset = destBit - sourceBit;
int destSegments = (writeBit + amount) / BitsPerSegment - destPos + 1;
if (bitOffset == 0)
{
// Source doesn't need to be shifted, source and dest bits span the same number of segments
ulong firstSegment = message.data[sourcePos];
if (destBit == 0)
data[destPos] = firstSegment;
else
data[destPos] |= firstSegment & ~((1ul << sourceBit) - 1);
for (int i = 1; i < destSegments; i++)
data[destPos + i] = message.data[sourcePos + i];
}
else if (bitOffset > 0)
{
// Source needs to be shifted left, dest bits may span more segments than source bits
ulong firstSegment = message.data[sourcePos] & ~((1ul << sourceBit) - 1);
firstSegment <<= bitOffset;
if (destBit == 0)
data[destPos] = firstSegment;
else
data[destPos] |= firstSegment;
for (int i = 1; i < destSegments; i++)
data[destPos + i] = (message.data[sourcePos + i - 1] >> (BitsPerSegment - bitOffset)) | (message.data[sourcePos + i] << bitOffset);
}
else
{
// Source needs to be shifted right, source bits may span more segments than dest bits
bitOffset = -bitOffset;
ulong firstSegment = message.data[sourcePos] & ~((1ul << sourceBit) - 1);
firstSegment >>= bitOffset;
if (destBit == 0)
data[destPos] = firstSegment;
else
data[destPos] |= firstSegment;
int sourceSegments = (startBit + amount) / BitsPerSegment - sourcePos + 1;
for (int i = 1; i < sourceSegments; i++)
{
data[destPos + i - 1] |= message.data[sourcePos + i] << (BitsPerSegment - bitOffset);
data[destPos + i ] = message.data[sourcePos + i] >> bitOffset;
}
}
writeBit += amount;
data[destPos + destSegments - 1] &= (1ul << (writeBit % BitsPerSegment)) - 1;
return this;
}
#endregion
#region Bits
/// Moves the message's internal write position by the given of bits, reserving them so they can be set at a later time.
/// The number of bits to reserve.
/// The message instance.
public Message ReserveBits(int amount)
{
if (UnwrittenBits < amount)
throw new InsufficientCapacityException(this, amount);
int bit = writeBit % BitsPerSegment;
writeBit += amount;
// Reset the last segment that the reserved range touches, unless it's also the first one, in which case it may already contain data which we don't want to overwrite
if (bit + amount >= BitsPerSegment)
data[writeBit / BitsPerSegment] = 0;
return this;
}
/// Moves the message's internal read position by the given of bits, skipping over them.
/// The number of bits to skip.
/// The message instance.
public Message SkipBits(int amount)
{
if (UnreadBits < amount)
RiptideLogger.Log(LogType.Error, $"Message only contains {UnreadBits} unread {Helper.CorrectForm(UnreadBits, "bit")}, which is not enough to skip {amount}!");
readBit += amount;
return this;
}
/// Sets up to 64 bits at the specified position in the message.
/// The bits to write into the message.
/// The number of bits to set.
/// The bit position in the message at which to start writing.
/// The message instance.
/// This method can be used to directly set a range of bits anywhere in the message without moving its internal write position. Data which was previously added to
/// the message and which falls within the range of bits being set will be overwritten, meaning that improper use of this method will likely corrupt the message!
public Message SetBits(ulong bitfield, int amount, int startBit)
{
if (amount > sizeof(ulong) * BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"Cannot set more than {sizeof(ulong) * BitsPerByte} bits at a time!");
Converter.SetBits(bitfield, amount, data, startBit);
return this;
}
/// Retrieves up to 8 bits from the specified position in the message.
/// The number of bits to peek.
/// The bit position in the message at which to start peeking.
/// The bits that were retrieved.
/// The message instance.
/// This method can be used to retrieve a range of bits from anywhere in the message without moving its internal read position.
public Message PeekBits(int amount, int startBit, out byte bitfield)
{
if (amount > BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(PeekBits)}' overload cannot be used to peek more than {BitsPerByte} bits at a time!");
Converter.GetBits(amount, data, startBit, out bitfield);
return this;
}
/// Retrieves up to 16 bits from the specified position in the message.
///
public Message PeekBits(int amount, int startBit, out ushort bitfield)
{
if (amount > sizeof(ushort) * BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(PeekBits)}' overload cannot be used to peek more than {sizeof(ushort) * BitsPerByte} bits at a time!");
Converter.GetBits(amount, data, startBit, out bitfield);
return this;
}
/// Retrieves up to 32 bits from the specified position in the message.
///
public Message PeekBits(int amount, int startBit, out uint bitfield)
{
if (amount > sizeof(uint) * BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(PeekBits)}' overload cannot be used to peek more than {sizeof(uint) * BitsPerByte} bits at a time!");
Converter.GetBits(amount, data, startBit, out bitfield);
return this;
}
/// Retrieves up to 64 bits from the specified position in the message.
///
public Message PeekBits(int amount, int startBit, out ulong bitfield)
{
if (amount > sizeof(ulong) * BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(PeekBits)}' overload cannot be used to peek more than {sizeof(ulong) * BitsPerByte} bits at a time!");
Converter.GetBits(amount, data, startBit, out bitfield);
return this;
}
/// Adds up to 8 of the given bits to the message.
/// The bits to add.
/// The number of bits to add.
/// The message that the bits were added to.
public Message AddBits(byte bitfield, int amount)
{
if (amount > BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(AddBits)}' overload cannot be used to add more than {BitsPerByte} bits at a time!");
bitfield &= (byte)((1 << amount) - 1); // Discard any bits that are set beyond the ones we're setting
Converter.ByteToBits(bitfield, data, writeBit);
writeBit += amount;
return this;
}
/// Adds up to 16 of the given bits to the message.
///
public Message AddBits(ushort bitfield, int amount)
{
if (amount > sizeof(ushort) * BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(AddBits)}' overload cannot be used to add more than {sizeof(ushort) * BitsPerByte} bits at a time!");
bitfield &= (ushort)((1 << amount) - 1); // Discard any bits that are set beyond the ones we're adding
Converter.UShortToBits(bitfield, data, writeBit);
writeBit += amount;
return this;
}
/// Adds up to 32 of the given bits to the message.
///
public Message AddBits(uint bitfield, int amount)
{
if (amount > sizeof(uint) * BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(AddBits)}' overload cannot be used to add more than {sizeof(uint) * BitsPerByte} bits at a time!");
bitfield &= (1u << (amount - 1) << 1) - 1; // Discard any bits that are set beyond the ones we're adding
Converter.UIntToBits(bitfield, data, writeBit);
writeBit += amount;
return this;
}
/// Adds up to 64 of the given bits to the message.
///
public Message AddBits(ulong bitfield, int amount)
{
if (amount > sizeof(ulong) * BitsPerByte)
throw new ArgumentOutOfRangeException(nameof(amount), $"This '{nameof(AddBits)}' overload cannot be used to add more than {sizeof(ulong) * BitsPerByte} bits at a time!");
bitfield &= (1ul << (amount - 1) << 1) - 1; // Discard any bits that are set beyond the ones we're adding
Converter.ULongToBits(bitfield, data, writeBit);
writeBit += amount;
return this;
}
/// Retrieves the next bits (up to 8) from the message.
/// The number of bits to retrieve.
/// The bits that were retrieved.
/// The messages that the bits were retrieved from.
public Message GetBits(int amount, out byte bitfield)
{
PeekBits(amount, readBit, out bitfield);
readBit += amount;
return this;
}
/// Retrieves the next bits (up to 16) from the message.
///
public Message GetBits(int amount, out ushort bitfield)
{
PeekBits(amount, readBit, out bitfield);
readBit += amount;
return this;
}
/// Retrieves the next bits (up to 32) from the message.
///
public Message GetBits(int amount, out uint bitfield)
{
PeekBits(amount, readBit, out bitfield);
readBit += amount;
return this;
}
/// Retrieves the next bits (up to 64) from the message.
///
public Message GetBits(int amount, out ulong bitfield)
{
PeekBits(amount, readBit, out bitfield);
readBit += amount;
return this;
}
#endregion
#region Varint
/// Adds a positive or negative number to the message, using fewer bits for smaller values.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message AddVarLong(long value) => AddVarULong((ulong)Converter.ZigZagEncode(value));
/// Adds a positive number to the message, using fewer bits for smaller values.
/// The value to add.
/// The message that the value was added to.
/// The value is added in segments of 8 bits, 1 of which is used to indicate whether or not another segment follows. As a result, small values are
/// added to the message using fewer bits, while large values will require a few more bits than they would if they were added via ,
/// , , or (or their signed counterparts).
public Message AddVarULong(ulong value)
{
do
{
byte byteValue = (byte)(value & 0b01111111);
value >>= 7;
if (value != 0) // There's more to write
byteValue |= 0b10000000;
AddByte(byteValue);
}
while (value != 0);
return this;
}
/// Retrieves a positive or negative number from the message, using fewer bits for smaller values.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long GetVarLong() => Converter.ZigZagDecode((long)GetVarULong());
/// Retrieves a positive number from the message, using fewer bits for smaller values.
/// The value that was retrieved.
/// The value is retrieved in segments of 8 bits, 1 of which is used to indicate whether or not another segment follows. As a result, small values are
/// retrieved from the message using fewer bits, while large values will require a few more bits than they would if they were retrieved via ,
/// , , or (or their signed counterparts).
public ulong GetVarULong()
{
ulong byteValue;
ulong value = 0;
int shift = 0;
do
{
byteValue = GetByte();
value |= (byteValue & 0b01111111) << shift;
shift += 7;
}
while ((byteValue & 0b10000000) != 0);
return value;
}
#endregion
#region Byte & SByte
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddByte(byte value)
{
if (UnwrittenBits < BitsPerByte)
throw new InsufficientCapacityException(this, ByteName, BitsPerByte);
Converter.ByteToBits(value, data, writeBit);
writeBit += BitsPerByte;
return this;
}
/// Adds an to the message.
/// The to add.
/// The message that the was added to.
public Message AddSByte(sbyte value)
{
if (UnwrittenBits < BitsPerByte)
throw new InsufficientCapacityException(this, SByteName, BitsPerByte);
Converter.SByteToBits(value, data, writeBit);
writeBit += BitsPerByte;
return this;
}
/// Retrieves a from the message.
/// The that was retrieved.
public byte GetByte()
{
if (UnreadBits < BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(ByteName, $"{default(byte)}"));
return default(byte);
}
byte value = Converter.ByteFromBits(data, readBit);
readBit += BitsPerByte;
return value;
}
/// Retrieves an from the message.
/// The that was retrieved.
public sbyte GetSByte()
{
if (UnreadBits < BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(SByteName, $"{default(sbyte)}"));
return default(sbyte);
}
sbyte value = Converter.SByteFromBits(data, readBit);
readBit += BitsPerByte;
return value;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddBytes(byte[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, ByteName, BitsPerByte);
if (writeBit % BitsPerByte == 0)
{
Buffer.BlockCopy(array, 0, data, writeBit / BitsPerByte, array.Length);
writeBit += array.Length * BitsPerByte;
}
else
{
for (int i = 0; i < array.Length; i++)
{
Converter.ByteToBits(array[i], data, writeBit);
writeBit += BitsPerByte;
}
}
return this;
}
/// Adds an array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddSBytes(sbyte[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, SByteName, BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
Converter.SByteToBits(array[i], data, writeBit);
writeBit += BitsPerByte;
}
return this;
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public byte[] GetBytes() => GetBytes((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of bytes to retrieve.
/// The array that was retrieved.
public byte[] GetBytes(int amount)
{
byte[] array = new byte[amount];
ReadBytes(amount, array);
return array;
}
/// Populates a array with bytes retrieved from the message.
/// The amount of bytes to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetBytes(int amount, byte[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, ByteName));
ReadBytes(amount, intoArray, startIndex);
}
/// Retrieves an array from the message.
/// The array that was retrieved.
public sbyte[] GetSBytes() => GetSBytes((int)GetVarULong());
/// Retrieves an array from the message.
/// The amount of sbytes to retrieve.
/// The array that was retrieved.
public sbyte[] GetSBytes(int amount)
{
sbyte[] array = new sbyte[amount];
ReadSBytes(amount, array);
return array;
}
/// Populates a array with bytes retrieved from the message.
/// The amount of sbytes to retrieve.
/// The array to populate.
/// The position at which to start populating .
public void GetSBytes(int amount, sbyte[] intArray, int startIndex = 0)
{
if (startIndex + amount > intArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intArray.Length, startIndex, SByteName));
ReadSBytes(amount, intArray, startIndex);
}
/// Reads a number of bytes from the message and writes them into the given array.
/// The amount of bytes to read.
/// The array to write the bytes into.
/// The position at which to start writing into the array.
private void ReadBytes(int amount, byte[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, ByteName));
amount = UnreadBits / BitsPerByte;
}
if (readBit % BitsPerByte == 0)
{
Buffer.BlockCopy(data, readBit / BitsPerByte, intoArray, startIndex, amount);
readBit += amount * BitsPerByte;
}
else
{
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.ByteFromBits(data, readBit);
readBit += BitsPerByte;
}
}
}
/// Reads a number of sbytes from the message and writes them into the given array.
/// The amount of sbytes to read.
/// The array to write the sbytes into.
/// The position at which to start writing into the array.
private void ReadSBytes(int amount, sbyte[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, SByteName));
amount = UnreadBits / BitsPerByte;
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.SByteFromBits(data, readBit);
readBit += BitsPerByte;
}
}
#endregion
#region Bool
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddBool(bool value)
{
if (UnwrittenBits < 1)
throw new InsufficientCapacityException(this, BoolName, 1);
Converter.BoolToBit(value, data, writeBit++);
return this;
}
/// Retrieves a from the message.
/// The that was retrieved.
public bool GetBool()
{
if (UnreadBits < 1)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(BoolName, $"{default(bool)}"));
return default(bool);
}
return Converter.BoolFromBit(data, readBit++);
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddBools(bool[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length)
throw new InsufficientCapacityException(this, array.Length, BoolName, 1);
for (int i = 0; i < array.Length; i++)
Converter.BoolToBit(array[i], data, writeBit++);
return this;
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public bool[] GetBools() => GetBools((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of bools to retrieve.
/// The array that was retrieved.
public bool[] GetBools(int amount)
{
bool[] array = new bool[amount];
ReadBools(amount, array);
return array;
}
/// Populates a array with bools retrieved from the message.
/// The amount of bools to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetBools(int amount, bool[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, BoolName));
ReadBools(amount, intoArray, startIndex);
}
/// Reads a number of bools from the message and writes them into the given array.
/// The amount of bools to read.
/// The array to write the bools into.
/// The position at which to start writing into the array.
private void ReadBools(int amount, bool[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, BoolName));
amount = UnreadBits;
}
for (int i = 0; i < amount; i++)
intoArray[startIndex + i] = Converter.BoolFromBit(data, readBit++);
}
#endregion
#region Short & UShort
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddShort(short value)
{
if (UnwrittenBits < sizeof(short) * BitsPerByte)
throw new InsufficientCapacityException(this, ShortName, sizeof(short) * BitsPerByte);
Converter.ShortToBits(value, data, writeBit);
writeBit += sizeof(short) * BitsPerByte;
return this;
}
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddUShort(ushort value)
{
if (UnwrittenBits < sizeof(ushort) * BitsPerByte)
throw new InsufficientCapacityException(this, UShortName, sizeof(ushort) * BitsPerByte);
Converter.UShortToBits(value, data, writeBit);
writeBit += sizeof(ushort) * BitsPerByte;
return this;
}
/// Retrieves a from the message.
/// The that was retrieved.
public short GetShort()
{
if (UnreadBits < sizeof(short) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(ShortName, $"{default(short)}"));
return default(short);
}
short value = Converter.ShortFromBits(data, readBit);
readBit += sizeof(short) * BitsPerByte;
return value;
}
/// Retrieves a from the message.
/// The that was retrieved.
public ushort GetUShort()
{
if (UnreadBits < sizeof(ushort) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(UShortName, $"{default(ushort)}"));
return default(ushort);
}
ushort value = Converter.UShortFromBits(data, readBit);
readBit += sizeof(ushort) * BitsPerByte;
return value;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddShorts(short[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(short) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, ShortName, sizeof(short) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
array[i] = Converter.ShortFromBits(data, readBit);
readBit += sizeof(short) * BitsPerByte;
}
return this;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddUShorts(ushort[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(ushort) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, UShortName, sizeof(ushort) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
array[i] = Converter.UShortFromBits(data, readBit);
readBit += sizeof(ushort) * BitsPerByte;
}
return this;
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public short[] GetShorts() => GetShorts((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of shorts to retrieve.
/// The array that was retrieved.
public short[] GetShorts(int amount)
{
short[] array = new short[amount];
ReadShorts(amount, array);
return array;
}
/// Populates a array with shorts retrieved from the message.
/// The amount of shorts to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetShorts(int amount, short[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, ShortName));
ReadShorts(amount, intoArray, startIndex);
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public ushort[] GetUShorts() => GetUShorts((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of ushorts to retrieve.
/// The array that was retrieved.
public ushort[] GetUShorts(int amount)
{
ushort[] array = new ushort[amount];
ReadUShorts(amount, array);
return array;
}
/// Populates a array with ushorts retrieved from the message.
/// The amount of ushorts to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetUShorts(int amount, ushort[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, UShortName));
ReadUShorts(amount, intoArray, startIndex);
}
/// Reads a number of shorts from the message and writes them into the given array.
/// The amount of shorts to read.
/// The array to write the shorts into.
/// The position at which to start writing into the array.
private void ReadShorts(int amount, short[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(short) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, ShortName));
amount = UnreadBits / (sizeof(short) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.ShortFromBits(data, readBit);
readBit += sizeof(short) * BitsPerByte;
}
}
/// Reads a number of ushorts from the message and writes them into the given array.
/// The amount of ushorts to read.
/// The array to write the ushorts into.
/// The position at which to start writing into the array.
private void ReadUShorts(int amount, ushort[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(ushort) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, UShortName));
amount = UnreadBits / (sizeof(ushort) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.UShortFromBits(data, readBit);
readBit += sizeof(ushort) * BitsPerByte;
}
}
#endregion
#region Int & UInt
/// Adds an to the message.
/// The to add.
/// The message that the was added to.
public Message AddInt(int value)
{
if (UnwrittenBits < sizeof(int) * BitsPerByte)
throw new InsufficientCapacityException(this, IntName, sizeof(int) * BitsPerByte);
Converter.IntToBits(value, data, writeBit);
writeBit += sizeof(int) * BitsPerByte;
return this;
}
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddUInt(uint value)
{
if (UnwrittenBits < sizeof(uint) * BitsPerByte)
throw new InsufficientCapacityException(this, UIntName, sizeof(uint) * BitsPerByte);
Converter.UIntToBits(value, data, writeBit);
writeBit += sizeof(uint) * BitsPerByte;
return this;
}
/// Retrieves an from the message.
/// The that was retrieved.
public int GetInt()
{
if (UnreadBits < sizeof(int) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(IntName, $"{default(int)}"));
return default(int);
}
int value = Converter.IntFromBits(data, readBit);
readBit += sizeof(int) * BitsPerByte;
return value;
}
/// Retrieves a from the message.
/// The that was retrieved.
public uint GetUInt()
{
if (UnreadBits < sizeof(uint) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(UIntName, $"{default(uint)}"));
return default(uint);
}
uint value = Converter.UIntFromBits(data, readBit);
readBit += sizeof(uint) * BitsPerByte;
return value;
}
/// Adds an array message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddInts(int[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(int) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, IntName, sizeof(int) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
Converter.IntToBits(array[i], data, writeBit);
writeBit += sizeof(int) * BitsPerByte;
}
return this;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddUInts(uint[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(uint) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, UIntName, sizeof(uint) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
Converter.UIntToBits(array[i], data, writeBit);
writeBit += sizeof(uint) * BitsPerByte;
}
return this;
}
/// Retrieves an array from the message.
/// The array that was retrieved.
public int[] GetInts() => GetInts((int)GetVarULong());
/// Retrieves an array from the message.
/// The amount of ints to retrieve.
/// The array that was retrieved.
public int[] GetInts(int amount)
{
int[] array = new int[amount];
ReadInts(amount, array);
return array;
}
/// Populates an array with ints retrieved from the message.
/// The amount of ints to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetInts(int amount, int[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, IntName));
ReadInts(amount, intoArray, startIndex);
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public uint[] GetUInts() => GetUInts((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of uints to retrieve.
/// The array that was retrieved.
public uint[] GetUInts(int amount)
{
uint[] array = new uint[amount];
ReadUInts(amount, array);
return array;
}
/// Populates a array with uints retrieved from the message.
/// The amount of uints to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetUInts(int amount, uint[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, UIntName));
ReadUInts(amount, intoArray, startIndex);
}
/// Reads a number of ints from the message and writes them into the given array.
/// The amount of ints to read.
/// The array to write the ints into.
/// The position at which to start writing into the array.
private void ReadInts(int amount, int[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(int) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, IntName));
amount = UnreadBits / (sizeof(int) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.IntFromBits(data, readBit);
readBit += sizeof(int) * BitsPerByte;
}
}
/// Reads a number of uints from the message and writes them into the given array.
/// The amount of uints to read.
/// The array to write the uints into.
/// The position at which to start writing into the array.
private void ReadUInts(int amount, uint[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(uint) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, UIntName));
amount = UnreadBits / (sizeof(uint) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.UIntFromBits(data, readBit);
readBit += sizeof(uint) * BitsPerByte;
}
}
#endregion
#region Long & ULong
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddLong(long value)
{
if (UnwrittenBits < sizeof(long) * BitsPerByte)
throw new InsufficientCapacityException(this, LongName, sizeof(long) * BitsPerByte);
Converter.LongToBits(value, data, writeBit);
writeBit += sizeof(long) * BitsPerByte;
return this;
}
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddULong(ulong value)
{
if (UnwrittenBits < sizeof(ulong) * BitsPerByte)
throw new InsufficientCapacityException(this, ULongName, sizeof(ulong) * BitsPerByte);
Converter.ULongToBits(value, data, writeBit);
writeBit += sizeof(ulong) * BitsPerByte;
return this;
}
/// Retrieves a from the message.
/// The that was retrieved.
public long GetLong()
{
if (UnreadBits < sizeof(long) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(LongName, $"{default(long)}"));
return default(long);
}
long value = Converter.LongFromBits(data, readBit);
readBit += sizeof(long) * BitsPerByte;
return value;
}
/// Retrieves a from the message.
/// The that was retrieved.
public ulong GetULong()
{
if (UnreadBits < sizeof(ulong) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(ULongName, $"{default(ulong)}"));
return default(ulong);
}
ulong value = Converter.ULongFromBits(data, readBit);
readBit += sizeof(ulong) * BitsPerByte;
return value;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddLongs(long[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(long) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, LongName, sizeof(long) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
Converter.LongToBits(array[i], data, writeBit);
writeBit += sizeof(long) * BitsPerByte;
}
return this;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddULongs(ulong[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(ulong) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, ULongName, sizeof(ulong) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
Converter.ULongToBits(array[i], data, writeBit);
writeBit += sizeof(ulong) * BitsPerByte;
}
return this;
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public long[] GetLongs() => GetLongs((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of longs to retrieve.
/// The array that was retrieved.
public long[] GetLongs(int amount)
{
long[] array = new long[amount];
ReadLongs(amount, array);
return array;
}
/// Populates a array with longs retrieved from the message.
/// The amount of longs to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetLongs(int amount, long[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, LongName));
ReadLongs(amount, intoArray, startIndex);
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public ulong[] GetULongs() => GetULongs((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of ulongs to retrieve.
/// The array that was retrieved.
public ulong[] GetULongs(int amount)
{
ulong[] array = new ulong[amount];
ReadULongs(amount, array);
return array;
}
/// Populates a array with ulongs retrieved from the message.
/// The amount of ulongs to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetULongs(int amount, ulong[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, ULongName));
ReadULongs(amount, intoArray, startIndex);
}
/// Reads a number of longs from the message and writes them into the given array.
/// The amount of longs to read.
/// The array to write the longs into.
/// The position at which to start writing into the array.
private void ReadLongs(int amount, long[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(long) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, LongName));
amount = UnreadBits / (sizeof(long) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.LongFromBits(data, readBit);
readBit += sizeof(long) * BitsPerByte;
}
}
/// Reads a number of ulongs from the message and writes them into the given array.
/// The amount of ulongs to read.
/// The array to write the ulongs into.
/// The position at which to start writing into the array.
private void ReadULongs(int amount, ulong[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(ulong) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, ULongName));
amount = UnreadBits / (sizeof(ulong) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.ULongFromBits(data, readBit);
readBit += sizeof(ulong) * BitsPerByte;
}
}
#endregion
#region Float
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddFloat(float value)
{
if (UnwrittenBits < sizeof(float) * BitsPerByte)
throw new InsufficientCapacityException(this, FloatName, sizeof(float) * BitsPerByte);
Converter.FloatToBits(value, data, writeBit);
writeBit += sizeof(float) * BitsPerByte;
return this;
}
/// Retrieves a from the message.
/// The that was retrieved.
public float GetFloat()
{
if (UnreadBits < sizeof(float) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(FloatName, $"{default(float)}"));
return default(float);
}
float value = Converter.FloatFromBits(data, readBit);
readBit += sizeof(float) * BitsPerByte;
return value;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddFloats(float[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(float) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, FloatName, sizeof(float) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
Converter.FloatToBits(array[i], data, writeBit);
writeBit += sizeof(float) * BitsPerByte;
}
return this;
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public float[] GetFloats() => GetFloats((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of floats to retrieve.
/// The array that was retrieved.
public float[] GetFloats(int amount)
{
float[] array = new float[amount];
ReadFloats(amount, array);
return array;
}
/// Populates a array with floats retrieved from the message.
/// The amount of floats to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetFloats(int amount, float[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, FloatName));
ReadFloats(amount, intoArray, startIndex);
}
/// Reads a number of floats from the message and writes them into the given array.
/// The amount of floats to read.
/// The array to write the floats into.
/// The position at which to start writing into the array.
private void ReadFloats(int amount, float[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(float) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, FloatName));
amount = UnreadBits / (sizeof(float) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.FloatFromBits(data, readBit);
readBit += sizeof(float) * BitsPerByte;
}
}
#endregion
#region Double
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddDouble(double value)
{
if (UnwrittenBits < sizeof(double) * BitsPerByte)
throw new InsufficientCapacityException(this, DoubleName, sizeof(double) * BitsPerByte);
Converter.DoubleToBits(value, data, writeBit);
writeBit += sizeof(double) * BitsPerByte;
return this;
}
/// Retrieves a from the message.
/// The that was retrieved.
public double GetDouble()
{
if (UnreadBits < sizeof(double) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(DoubleName, $"{default(double)}"));
return default(double);
}
double value = Converter.DoubleFromBits(data, readBit);
readBit += sizeof(double) * BitsPerByte;
return value;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddDoubles(double[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
if (UnwrittenBits < array.Length * sizeof(double) * BitsPerByte)
throw new InsufficientCapacityException(this, array.Length, DoubleName, sizeof(double) * BitsPerByte);
for (int i = 0; i < array.Length; i++)
{
Converter.DoubleToBits(array[i], data, writeBit);
writeBit += sizeof(double) * BitsPerByte;
}
return this;
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public double[] GetDoubles() => GetDoubles((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of doubles to retrieve.
/// The array that was retrieved.
public double[] GetDoubles(int amount)
{
double[] array = new double[amount];
ReadDoubles(amount, array);
return array;
}
/// Populates a array with doubles retrieved from the message.
/// The amount of doubles to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetDoubles(int amount, double[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, DoubleName));
ReadDoubles(amount, intoArray, startIndex);
}
/// Reads a number of doubles from the message and writes them into the given array.
/// The amount of doubles to read.
/// The array to write the doubles into.
/// The position at which to start writing into the array.
private void ReadDoubles(int amount, double[] intoArray, int startIndex = 0)
{
if (UnreadBits < amount * sizeof(double) * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(amount, DoubleName));
amount = UnreadBits / (sizeof(double) * BitsPerByte);
}
for (int i = 0; i < amount; i++)
{
intoArray[startIndex + i] = Converter.DoubleFromBits(data, readBit);
readBit += sizeof(double) * BitsPerByte;
}
}
#endregion
#region String
/// Adds a to the message.
/// The to add.
/// The message that the was added to.
public Message AddString(string value)
{
AddBytes(Encoding.UTF8.GetBytes(value));
return this;
}
/// Retrieves a from the message.
/// The that was retrieved.
public string GetString()
{
int length = (int)GetVarULong(); // Get the length of the string (in bytes, NOT characters)
if (UnreadBits < length * BitsPerByte)
{
RiptideLogger.Log(LogType.Error, NotEnoughBitsError(StringName, "shortened string"));
length = UnreadBits / BitsPerByte;
}
string value = Encoding.UTF8.GetString(GetBytes(length), 0, length);
return value;
}
/// Adds a array to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddStrings(string[] array, bool includeLength = true)
{
if (includeLength)
AddVarULong((uint)array.Length);
// It'd be ideal to throw an exception here (instead of in AddString) if the entire array isn't going to fit, but since each string could
// be (and most likely is) a different length and some characters use more than a single byte, the only way of doing that would be to loop
// through the whole array here and convert each string to bytes ahead of time, just to get the required byte count. Then if they all fit
// into the message, they would all be converted again when actually being written into the byte array, which is obviously inefficient.
for (int i = 0; i < array.Length; i++)
AddString(array[i]);
return this;
}
/// Retrieves a array from the message.
/// The array that was retrieved.
public string[] GetStrings() => GetStrings((int)GetVarULong());
/// Retrieves a array from the message.
/// The amount of strings to retrieve.
/// The array that was retrieved.
public string[] GetStrings(int amount)
{
string[] array = new string[amount];
for (int i = 0; i < array.Length; i++)
array[i] = GetString();
return array;
}
/// Populates a array with strings retrieved from the message.
/// The amount of strings to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetStrings(int amount, string[] intoArray, int startIndex = 0)
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, StringName));
for (int i = 0; i < amount; i++)
intoArray[startIndex + i] = GetString();
}
#endregion
#region IMessageSerializable Types
/// Adds a serializable to the message.
/// The serializable to add.
/// The message that the serializable was added to.
public Message AddSerializable(T value) where T : IMessageSerializable
{
value.Serialize(this);
return this;
}
/// Retrieves a serializable from the message.
/// The serializable that was retrieved.
public T GetSerializable() where T : IMessageSerializable, new()
{
T t = new T();
t.Deserialize(this);
return t;
}
/// Adds an array of serializables to the message.
/// The array to add.
/// Whether or not to include the length of the array in the message.
/// The message that the array was added to.
public Message AddSerializables(T[] array, bool includeLength = true) where T : IMessageSerializable
{
if (includeLength)
AddVarULong((uint)array.Length);
for (int i = 0; i < array.Length; i++)
AddSerializable(array[i]);
return this;
}
/// Retrieves an array of serializables from the message.
/// The array that was retrieved.
public T[] GetSerializables() where T : IMessageSerializable, new() => GetSerializables((int)GetVarULong());
/// Retrieves an array of serializables from the message.
/// The amount of serializables to retrieve.
/// The array that was retrieved.
public T[] GetSerializables(int amount) where T : IMessageSerializable, new()
{
T[] array = new T[amount];
ReadSerializables(amount, array);
return array;
}
/// Populates an array of serializables retrieved from the message.
/// The amount of serializables to retrieve.
/// The array to populate.
/// The position at which to start populating the array.
public void GetSerializables(int amount, T[] intoArray, int startIndex = 0) where T : IMessageSerializable, new()
{
if (startIndex + amount > intoArray.Length)
throw new ArgumentException(nameof(amount), ArrayNotLongEnoughError(amount, intoArray.Length, startIndex, typeof(T).Name));
ReadSerializables(amount, intoArray, startIndex);
}
/// Reads a number of serializables from the message and writes them into the given array.
/// The amount of serializables to read.
/// The array to write the serializables into.
/// The position at which to start writing into .
private void ReadSerializables(int amount, T[] intArray, int startIndex = 0) where T : IMessageSerializable, new()
{
for (int i = 0; i < amount; i++)
intArray[startIndex + i] = GetSerializable();
}
#endregion
#region Overload Versions
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(byte value) => AddByte(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(sbyte value) => AddSByte(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(bool value) => AddBool(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(short value) => AddShort(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(ushort value) => AddUShort(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(int value) => AddInt(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(uint value) => AddUInt(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(long value) => AddLong(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(ulong value) => AddULong(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(float value) => AddFloat(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(double value) => AddDouble(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(string value) => AddString(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(T value) where T : IMessageSerializable => AddSerializable(value);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(byte[] array, bool includeLength = true) => AddBytes(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(sbyte[] array, bool includeLength = true) => AddSBytes(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(bool[] array, bool includeLength = true) => AddBools(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(short[] array, bool includeLength = true) => AddShorts(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(ushort[] array, bool includeLength = true) => AddUShorts(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(int[] array, bool includeLength = true) => AddInts(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(uint[] array, bool includeLength = true) => AddUInts(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(long[] array, bool includeLength = true) => AddLongs(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(ulong[] array, bool includeLength = true) => AddULongs(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(float[] array, bool includeLength = true) => AddFloats(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(double[] array, bool includeLength = true) => AddDoubles(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(string[] array, bool includeLength = true) => AddStrings(array, includeLength);
///
/// This method is simply an alternative way of calling .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Message Add(T[] array, bool includeLength = true) where T : IMessageSerializable, new() => AddSerializables(array, includeLength);
#endregion
#endregion
#region Error Messaging
/// The name of a value.
private const string ByteName = "byte";
/// The name of a value.
private const string SByteName = "sbyte";
/// The name of a value.
private const string BoolName = "bool";
/// The name of a value.
private const string ShortName = "short";
/// The name of a value.
private const string UShortName = "ushort";
/// The name of an value.
private const string IntName = "int";
/// The name of a value.
private const string UIntName = "uint";
/// The name of a value.
private const string LongName = "long";
/// The name of a value.
private const string ULongName = "ulong";
/// The name of a value.
private const string FloatName = "float";
/// The name of a value.
private const string DoubleName = "double";
/// The name of a value.
private const string StringName = "string";
/// The name of an array length value.
private const string ArrayLengthName = "array length";
/// Constructs an error message for when a message contains insufficient unread bits to retrieve a certain value.
/// The name of the value type for which the retrieval attempt failed.
/// Text describing the value which will be returned.
/// The error message.
private string NotEnoughBitsError(string valueName, string defaultReturn)
{
return $"Message only contains {UnreadBits} unread {Helper.CorrectForm(UnreadBits, "bit")}, which is not enough to retrieve a value of type '{valueName}'! Returning {defaultReturn}.";
}
/// Constructs an error message for when a message contains insufficient unread bits to retrieve an array of values.
/// The expected length of the array.
/// The name of the value type for which the retrieval attempt failed.
/// The error message.
private string NotEnoughBitsError(int arrayLength, string valueName)
{
return $"Message only contains {UnreadBits} unread {Helper.CorrectForm(UnreadBits, "bit")}, which is not enough to retrieve {arrayLength} {Helper.CorrectForm(arrayLength, valueName)}! Returned array will contain default elements.";
}
/// Constructs an error message for when a number of retrieved values do not fit inside the bounds of the provided array.
/// The number of values being retrieved.
/// The length of the provided array.
/// The position in the array at which to begin writing values.
/// The name of the value type which is being retrieved.
/// The name of the value type in plural form. If left empty, this will be set to with an s appended to it.
/// The error message.
private string ArrayNotLongEnoughError(int amount, int arrayLength, int startIndex, string valueName, string pluralValueName = "")
{
if (string.IsNullOrEmpty(pluralValueName))
pluralValueName = $"{valueName}s";
return $"The amount of {pluralValueName} to retrieve ({amount}) is greater than the number of elements from the start index ({startIndex}) to the end of the given array (length: {arrayLength})!";
}
#endregion
}
}