Skip to content

Instantly share code, notes, and snippets.

@RonenNess
Created December 19, 2021 22:38
Show Gist options
  • Save RonenNess/e21828394c200fa58d48886edb22d081 to your computer and use it in GitHub Desktop.
Save RonenNess/e21828394c200fa58d48886edb22d081 to your computer and use it in GitHub Desktop.
Utility classes to pack and unpack data in bytes buffers in C#
/// <summary>
/// Utility to serialize data directly into a buffer.
/// </summary>
public class MessageSerializer
{
// the buffer containing the data.
private byte[] _buffer;
/// <summary>
/// Get the buffer itself that contains the data.
/// Be sure to use Size and not Buffer.Length as the buffer may contain padding bytes.
/// </summary>
public byte[] Buffer => _buffer;
/// <summary>
/// Number of used bytes.
/// </summary>
public int Size { get; private set; }
/// <summary>
/// Return how many padding bytes we have.
/// </summary>
public int PaddingBytes => _buffer.Length - Size;
/// <summary>
/// Create the message serializer.
/// </summary>
/// <param name="startingSize">Buffer starting size (grows dynamically).</param>
public MessageSerializer(int startingSize = 128)
{
_buffer = new byte[startingSize];
Size = 0;
}
/// <summary>
/// Clear the buffer.
/// </summary>
/// <param name="shrinkTo">If not 0, will also shrink internal buffer to given size to free actual space.</param>
public void Clear(int shrinkTo = 0)
{
Size = 0;
if (shrinkTo != 0 && Buffer.Length > shrinkTo)
{
_buffer = new byte[shrinkTo];
}
}
/// <summary>
/// Push bytes into buffer.
/// </summary>
/// <param name="data">Data to push.</param>
/// <param name="len">Bytes to copy from source buffer.</param>
public void PushBytes(byte[] data, int len = 0)
{
// default length
if (len == 0) { len = data.Length; }
// check if need to resize buffer
if (Size + len >= _buffer.Length)
{
Array.Resize(ref _buffer, _buffer.Length * 2 + len + 5);
}
// copy bytes into buffer (either single or array) and update used size
if (len == 1)
{
_buffer[Size] = data[0];
}
else
{
Array.Copy(data, 0, _buffer, Size, len);
}
Size += len;
}
/// <summary>
/// Push a string to message.
/// </summary>
/// <param name="data">String to push.</param>
public void PushString(string data)
{
var stringBytes = StringUtils.ToBytes(data);
PushUShort((ushort)stringBytes.Length);
PushBytes(stringBytes);
}
/// <summary>
/// Push a short string to message.
/// </summary>
/// <param name="data">String to push.</param>
public void PushStringShort(string data)
{
var stringBytes = StringUtils.ToBytes(data);
if (stringBytes.Length > byte.MaxValue) { throw new Exception("Short string too long!"); }
PushByte((byte)stringBytes.Length);
PushBytes(stringBytes);
}
/// <summary>
/// Push a vector to message as shorts.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushVectorShort(Vector3 data)
{
PushShort((short)(data.X * 10));
PushShort((short)(data.Y * 10));
PushShort((short)(data.Z * 10));
}
/// <summary>
/// Push a vector to message as shorts.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushVectorShortXZ(Vector3 data)
{
PushShort((short)(data.X * 10));
PushShort((short)(data.Z * 10));
}
/// <summary>
/// Push a byte to message.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushByte(byte data)
{
PushBytes(new byte[] { data });
}
/// <summary>
/// Push a boolean to message.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushBool(bool data)
{
PushBytes(new byte[] { (byte)(data ? 1 : 0) });
}
/// <summary>
/// Push a short to message.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushShort(short data)
{
PushBytes(new byte[] { (byte)(data & 0xff), (byte)((data >> 8) & 0xff) });
}
/// <summary>
/// Push a ushort to message.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushUShort(ushort data)
{
PushBytes(new byte[] { (byte)(data & 0xff), (byte)((data >> 8) & 0xff) });
}
/// <summary>
/// Push a ushort to message at offset.
/// </summary>
/// <param name="data">Data to push.</param>
/// <param name="offset">Offset to push ushort into.</param>
public void PushUShortInto(ushort data, int offset)
{
_buffer[offset] = (byte)(data & 0xff);
_buffer[offset + 1] = (byte)((data >> 8) & 0xff);
}
/// <summary>
/// Push a byte to message at offset.
/// </summary>
/// <param name="data">Data to push.</param>
/// <param name="offset">Offset to push byte into.</param>
public void PushByteInto(byte data, int offset)
{
_buffer[offset] = data;
}
/// <summary>
/// Push an int to message.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushInt(int data)
{
PushBytes(new byte[] { (byte)(data & 0xff), (byte)((data >> 8) & 0xff), (byte)((data >> 16) & 0xff), (byte)((data >> 24) & 0xff) });
}
/// <summary>
/// Push an uint to message.
/// </summary>
/// <param name="data">Data to push.</param>
public void PushUInt(UInt32 data)
{
PushBytes(new byte[] { (byte)(data & 0xff), (byte)((data >> 8) & 0xff), (byte)((data >> 16) & 0xff), (byte)((data >> 24) & 0xff) });
}
}
/// <summary>
/// Utility to deserialize data.
/// </summary>
public struct MessageDeserializer
{
// serialized data to deserialize
byte[] _data;
// offset in buffer
int _offset;
/// <summary>
/// Check if finished buffer.
/// </summary>
public bool IsDone => _offset >= _data.Length;
/// <summary>
/// Create the message deserializer.
/// </summary>
/// <param name="data">Data to deserialize.</param>
/// <param name="offset">Offset to start deserializing from.</param>
public MessageDeserializer(byte[] data, int offset = 0)
{
_data = data;
_offset = offset;
}
/// <summary>
/// Pop a string from message.
/// </summary>
public string PopString()
{
var len = PopUShort();
var asString = StringUtils.ToString(_data, _offset, len);
_offset += len;
return asString;
}
/// <summary>
/// Pop a string from message.
/// </summary>
public string PopStringShort()
{
var len = PopByte();
var asString = StringUtils.ToString(_data, _offset, len);
_offset += len;
return asString;
}
/// <summary>
/// Pop a vector that is stored by shorts.
/// </summary>
/// <returns>Vector instance.</returns>
public Vector3 PopVectorShort()
{
var x = PopShort() / 10.0f;
var y = PopShort() / 10.0f;
var z = PopShort() / 10.0f;
return new Vector3(x, y, z);
}
/// <summary>
/// Pop a vector that is stored by shorts, but only X and Z.
/// </summary>
/// <returns>Vector instance.</returns>
public Vector3 PopVectorShortXZ()
{
var x = PopShort() / 10.0f;
var z = PopShort() / 10.0f;
return new Vector3(x, 0, z);
}
/// <summary>
/// Pop a byte from message.
/// </summary>
public byte PopByte()
{
return _data[_offset++];
}
/// <summary>
/// Pop number of bytes from message.
/// </summary>
public byte[] PopBytes(int amount)
{
byte[] ret = new byte[amount];
Array.Copy(_data, _offset, ret, 0, amount);
_offset += amount;
return ret;
}
/// <summary>
/// Pop a boolean from message.
/// </summary>
public bool PopBool()
{
return _data[_offset++] != 0;
}
/// <summary>
/// Pop a short from message.
/// </summary>
public short PopShort()
{
var a = _data[_offset++];
var b = _data[_offset++];
return (short)((int)a | (int)(b << 8));
}
/// <summary>
/// Pop a ushort from message.
/// </summary>
public ushort PopUShort()
{
var a = _data[_offset++];
var b = _data[_offset++];
return (ushort)((int)a | (int)(b << 8));
}
/// <summary>
/// Pop an int from message.
/// </summary>
public int PopInt()
{
var a = _data[_offset++];
var b = _data[_offset++];
var c = _data[_offset++];
var d = _data[_offset++];
return ((int)a | (int)(b << 8) | (int)(c << 16) | (int)(d << 24));
}
/// <summary>
/// Pop an uint from message.
/// </summary>
public UInt32 PopUInt()
{
var a = _data[_offset++];
var b = _data[_offset++];
var c = _data[_offset++];
var d = _data[_offset++];
return (UInt32)((int)a | (int)(b << 8) | (int)(c << 16) | (int)(d << 24));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment