Skip to content

Instantly share code, notes, and snippets.

@nathan130200
Last active August 5, 2022 13:51
Show Gist options
  • Save nathan130200/77b6b462e264bd656dbe81754fa00ae4 to your computer and use it in GitHub Desktop.
Save nathan130200/77b6b462e264bd656dbe81754fa00ae4 to your computer and use it in GitHub Desktop.
T4 templates for stream I/O + Minecraft stream I/O utilities.
using System.Runtime.CompilerServices;
using System.Text;
namespace CraftDedicatedServer;
public static class Util
{
const int SEGMENT_BITS = 0x7f;
const int CONTINUE_BIT = 0x80;
const int MAX_STRING_LEN = short.MaxValue - 3;
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Rsh<S, U>(this ref S value, U bits)
where S : struct
where U : struct
{
dynamic _value = value;
dynamic _bits = (S)((dynamic)bits);
value = ((S)((U)_value >> _bits));
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Lsh<S, U>(this ref S value, U bits)
where S : struct
where U : struct
{
dynamic _value = value;
dynamic _bits = (S)((dynamic)bits);
value = ((S)((U)_value << _bits));
}
public static bool TryReadByte(this Stream s, out byte result)
{
result = default;
int current = s.ReadByte();
if (current != -1)
result = (byte)current;
return current != -1;
}
public static int ReadVarInt(this Stream stream)
{
int value = 0;
int position = 0;
while (true)
{
if (!stream.TryReadByte(out byte currentByte))
throw new IOException("End of stream.");
value |= (currentByte & SEGMENT_BITS) << position;
if ((currentByte & CONTINUE_BIT) == 0)
break;
position += 7;
if (position >= 32)
throw new IOException("VarInt is too big.");
}
return value;
}
public static void WriteVarInt(this Stream stream, int value)
{
while (true)
{
if ((value & ~SEGMENT_BITS) == 0)
{
stream.WriteByte((byte)value);
return;
}
stream.WriteByte((byte)((value & SEGMENT_BITS) | CONTINUE_BIT));
value.Rsh(7U);
}
}
public static long ReadVarLong(this Stream stream)
{
long value = 0;
int position = 0;
while (true)
{
if (!stream.TryReadByte(out byte currentByte))
throw new IOException("End of stream.");
value |= (long)(currentByte & SEGMENT_BITS) << position;
if ((currentByte & CONTINUE_BIT) == 0)
break;
position += 7;
if (position >= 64)
throw new IOException("VarLong is too big.");
}
return value;
}
public static void WriteVarLong(this Stream stream, long value)
{
while (true)
{
if ((value & ~((long)SEGMENT_BITS)) == 0)
{
stream.WriteByte((byte)value);
return;
}
stream.WriteByte((byte)((value & SEGMENT_BITS) | CONTINUE_BIT));
value.Rsh(7UL);
}
}
public static string ReadPrefixedString(this Stream stream)
{
int size = stream.ReadVarInt();
var buff = new byte[size];
if (stream.Read(buff) != size)
throw new IOException("Cannot read varsized string from stream.");
return Encoding.UTF8.GetString(buff, 0, size);
}
public static void WritePrefixedString(this Stream stream, string text)
{
if (text.Length > MAX_STRING_LEN)
text = text.Substring(0, MAX_STRING_LEN);
stream.WriteVarInt(text.Length);
stream.Write(Encoding.UTF8.GetBytes(text));
}
// ------------------------------------------------------------------------------------------------
// Async versions
// ------------------------------------------------------------------------------------------------
public static Task<int> ReadVarIntAsync(this Stream stream, CancellationToken token = default)
=> Task.Run(() => stream.ReadVarInt());
public static Task WriteVarIntAsync(this Stream stream, int value, CancellationToken token = default)
=> Task.Run(() => stream.WriteVarInt(value), token);
public static Task<long> ReadVarLongAsync(this Stream stream, CancellationToken token = default)
=> Task.Run(() => stream.ReadVarLong(), token);
public static Task WriteVarLongAsync(this Stream stream, long value, CancellationToken token = default)
=> Task.Run(() => stream.ReadVarLong(), token);
public static Task<string> ReadPrefixedStringAsync(this Stream stream, CancellationToken token = default)
=> Task.Run(() => stream.ReadPrefixedString(), token);
public static void WritePrefixedStringAsync(this Stream stream, string text, CancellationToken token = default)
=> Task.Run(() => stream.WritePrefixedString(text), token);
}
#pragma warning disable
using System;
namespace System.IO;
public static class StreamUtil
{
public static short ReadInt16(this Stream stream)
{
const int size = 2;
var buff = new byte[size];
if(stream.Read(buff) != size)
throw new IOException("Cannot read 'System.Int16' from stream.");
return BitConverter.ToInt16(buff, 0);
}
public static async Task<short> ReadInt16Async(this Stream stream, CancellationToken token = default)
{
const int size = 2;
var buff = new byte[size];
if(await stream.ReadAsync(buff, token) != size)
throw new IOException("Cannot read 'System.Int16' from stream.");
return BitConverter.ToInt16(buff, 0);
}
public static void WriteInt16(this Stream stream, short value)
=> stream.Write(BitConverter.GetBytes(value));
public static async Task WriteInt16Async(this Stream stream, short value, CancellationToken token = default)
=> await stream.WriteAsync(BitConverter.GetBytes(value), token);
public static int ReadInt32(this Stream stream)
{
const int size = 4;
var buff = new byte[size];
if(stream.Read(buff) != size)
throw new IOException("Cannot read 'System.Int32' from stream.");
return BitConverter.ToInt32(buff, 0);
}
public static async Task<int> ReadInt32Async(this Stream stream, CancellationToken token = default)
{
const int size = 4;
var buff = new byte[size];
if(await stream.ReadAsync(buff, token) != size)
throw new IOException("Cannot read 'System.Int32' from stream.");
return BitConverter.ToInt32(buff, 0);
}
public static void WriteInt32(this Stream stream, int value)
=> stream.Write(BitConverter.GetBytes(value));
public static async Task WriteInt32Async(this Stream stream, int value, CancellationToken token = default)
=> await stream.WriteAsync(BitConverter.GetBytes(value), token);
public static long ReadInt64(this Stream stream)
{
const int size = 8;
var buff = new byte[size];
if(stream.Read(buff) != size)
throw new IOException("Cannot read 'System.Int64' from stream.");
return BitConverter.ToInt64(buff, 0);
}
public static async Task<long> ReadInt64Async(this Stream stream, CancellationToken token = default)
{
const int size = 8;
var buff = new byte[size];
if(await stream.ReadAsync(buff, token) != size)
throw new IOException("Cannot read 'System.Int64' from stream.");
return BitConverter.ToInt64(buff, 0);
}
public static void WriteInt64(this Stream stream, long value)
=> stream.Write(BitConverter.GetBytes(value));
public static async Task WriteInt64Async(this Stream stream, long value, CancellationToken token = default)
=> await stream.WriteAsync(BitConverter.GetBytes(value), token);
public static ushort ReadUInt16(this Stream stream)
{
const int size = 2;
var buff = new byte[size];
if(stream.Read(buff) != size)
throw new IOException("Cannot read 'System.UInt16' from stream.");
return BitConverter.ToUInt16(buff, 0);
}
public static async Task<ushort> ReadUInt16Async(this Stream stream, CancellationToken token = default)
{
const int size = 2;
var buff = new byte[size];
if(await stream.ReadAsync(buff, token) != size)
throw new IOException("Cannot read 'System.UInt16' from stream.");
return BitConverter.ToUInt16(buff, 0);
}
public static void WriteUInt16(this Stream stream, ushort value)
=> stream.Write(BitConverter.GetBytes(value));
public static async Task WriteUInt16Async(this Stream stream, ushort value, CancellationToken token = default)
=> await stream.WriteAsync(BitConverter.GetBytes(value), token);
public static uint ReadUInt32(this Stream stream)
{
const int size = 4;
var buff = new byte[size];
if(stream.Read(buff) != size)
throw new IOException("Cannot read 'System.UInt32' from stream.");
return BitConverter.ToUInt32(buff, 0);
}
public static async Task<uint> ReadUInt32Async(this Stream stream, CancellationToken token = default)
{
const int size = 4;
var buff = new byte[size];
if(await stream.ReadAsync(buff, token) != size)
throw new IOException("Cannot read 'System.UInt32' from stream.");
return BitConverter.ToUInt32(buff, 0);
}
public static void WriteUInt32(this Stream stream, uint value)
=> stream.Write(BitConverter.GetBytes(value));
public static async Task WriteUInt32Async(this Stream stream, uint value, CancellationToken token = default)
=> await stream.WriteAsync(BitConverter.GetBytes(value), token);
public static ulong ReadUInt64(this Stream stream)
{
const int size = 8;
var buff = new byte[size];
if(stream.Read(buff) != size)
throw new IOException("Cannot read 'System.UInt64' from stream.");
return BitConverter.ToUInt64(buff, 0);
}
public static async Task<ulong> ReadUInt64Async(this Stream stream, CancellationToken token = default)
{
const int size = 8;
var buff = new byte[size];
if(await stream.ReadAsync(buff, token) != size)
throw new IOException("Cannot read 'System.UInt64' from stream.");
return BitConverter.ToUInt64(buff, 0);
}
public static void WriteUInt64(this Stream stream, ulong value)
=> stream.Write(BitConverter.GetBytes(value));
public static async Task WriteUInt64Async(this Stream stream, ulong value, CancellationToken token = default)
=> await stream.WriteAsync(BitConverter.GetBytes(value), token);
}
#pragma warning restore
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ import namespace="System" #>
#pragma warning disable
using System;
namespace System.IO;
public static class StreamUtil
{
<#
var Types = new(string alias, Type type, int len)[]
{
new("short", typeof(Int16), 2),
new("int", typeof(Int32), 4),
new("long", typeof(Int64), 8),
new("ushort", typeof(UInt16), 2),
new("uint", typeof(UInt32), 4),
new("ulong", typeof(UInt64), 8),
};
foreach(var t in Types){ #>
public static <#= t.alias #> Read<#= t.type.Name #>(this Stream stream)
{
const int size = <#= t.len #>;
var buff = new byte[size];
if(stream.Read(buff) != size)
throw new IOException("Cannot read '<#= t.type.FullName #>' from stream.");
return BitConverter.To<#= t.type.Name #>(buff, 0);
}
public static async Task<<#= t.alias #>> Read<#= t.type.Name #>Async(this Stream stream, CancellationToken token = default)
{
const int size = <#= t.len #>;
var buff = new byte[size];
if(await stream.ReadAsync(buff, token) != size)
throw new IOException("Cannot read '<#= t.type.FullName #>' from stream.");
return BitConverter.To<#= t.type.Name #>(buff, 0);
}
public static void Write<#= t.type.Name #>(this Stream stream, <#= t.alias #> value)
=> stream.Write(BitConverter.GetBytes(value));
public static async Task Write<#= t.type.Name #>Async(this Stream stream, <#= t.alias #> value, CancellationToken token = default)
=> await stream.WriteAsync(BitConverter.GetBytes(value), token);
<#
}
#>
}
#pragma warning restore
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment