Skip to content

Commit

Permalink
perf: NetworkWriter/Reader Write/ReadBlittable<T> for 4-6x performan…
Browse files Browse the repository at this point in the history
…ce improvement! (based on #2441, #3036). This time with Android fix. (#3047)

* NetworkWriter.WriteBlittable with Android support

* NetworkReader.ReadBlittable with Android support

* link issue

* credits

* don't need thos eanymore
  • Loading branch information
miwarnec committed Jan 16, 2022
1 parent edd06ec commit b54d086
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 470 deletions.
242 changes: 123 additions & 119 deletions Assets/Mirror/Runtime/NetworkReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;

namespace Mirror
Expand Down Expand Up @@ -60,18 +61,78 @@ public void SetBuffer(ArraySegment<byte> segment)
Position = 0;
}

// IMPORTANT: ReadBlittable<T> via fixed pinning WON'T WORK on android:
// https://github.com/vis2k/Mirror/issues/3044
// if we ever do it again, use NativeArray + .GetPtr()!
public byte ReadByte()
// ReadBlittable<T> from DOTSNET
// this is extremely fast, but only works for blittable types.
// => private to make sure nobody accidentally uses it for non-blittable
//
// Benchmark: see NetworkWriter.WriteBlittable!
//
// Note:
// ReadBlittable assumes same endianness for server & client.
// All Unity 2018+ platforms are little endian.
internal unsafe T ReadBlittable<T>()
where T : unmanaged
{
if (Position + 1 > buffer.Count)
// check if blittable for safety
#if UNITY_EDITOR
if (!UnsafeUtility.IsBlittable(typeof(T)))
{
throw new EndOfStreamException($"ReadByte out of range:{ToString()}");
throw new ArgumentException($"{typeof(T)} is not blittable!");
}
return buffer.Array[buffer.Offset + Position++];
#endif

// calculate size
// sizeof(T) gets the managed size at compile time.
// Marshal.SizeOf<T> gets the unmanaged size at runtime (slow).
// => our 1mio writes benchmark is 6x slower with Marshal.SizeOf<T>
// => for blittable types, sizeof(T) is even recommended:
// https://docs.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
int size = sizeof(T);

// enough data to read?
if (Position + size > buffer.Count)
{
throw new EndOfStreamException($"ReadBlittable<{typeof(T)}> out of range: {ToString()}");
}

// read blittable
T value;
fixed (byte* ptr = &buffer.Array[buffer.Offset + Position])
{
#if UNITY_ANDROID
// on some android systems, reading *(T*)ptr throws a NRE if
// the ptr isn't aligned (i.e. if Position is 1,2,3,5, etc.).
// here we have to use memcpy.
//
// => we can't get a pointer of a struct in C# without
// marshalling allocations
// => instead, we stack allocate an array of type T and use that
// => stackalloc avoids GC and is very fast. it only works for
// value types, but all blittable types are anyway.
//
// this way, we can still support blittable reads on android.
// see also: https://github.com/vis2k/Mirror/issues/3044
// (solution discovered by AIIO, FakeByte, mischa)
T* valueBuffer = stackalloc T[1];
UnsafeUtility.MemCpy(valueBuffer, ptr, size);
value = valueBuffer[0];
#else
// cast buffer to a T* pointer and then read from it.
value = *(T*)ptr;
#endif
}
Position += size;
return value;
}

// blittable'?' template for code reuse
// note: bool isn't blittable. need to read as byte.
internal T? ReadBlittableNullable<T>()
where T : unmanaged =>
ReadByte() != 0 ? ReadBlittable<T>() : default(T?);

public byte ReadByte() => ReadBlittable<byte>();

/// <summary>Read 'count' bytes into the bytes array</summary>
// TODO why does this also return bytes[]???
public byte[] ReadBytes(byte[] bytes, int count)
Expand Down Expand Up @@ -129,86 +190,50 @@ public static class NetworkReaderExtensions
// 1000 readers after: 0.8MB GC, 18ms
static readonly UTF8Encoding encoding = new UTF8Encoding(false, true);

public static byte ReadByte(this NetworkReader reader) => reader.ReadByte();
public static byte? ReadByteNullable(this NetworkReader reader) => reader.ReadBool() ? ReadByte(reader) : default(byte?);
public static byte ReadByte(this NetworkReader reader) => reader.ReadBlittable<byte>();
public static byte? ReadByteNullable(this NetworkReader reader) => reader.ReadBlittableNullable<byte>();

public static sbyte ReadSByte(this NetworkReader reader) => (sbyte)reader.ReadByte();
public static sbyte? ReadSByteNullable(this NetworkReader reader) => reader.ReadBool() ? ReadSByte(reader) : default(sbyte?);
public static sbyte ReadSByte(this NetworkReader reader) => reader.ReadBlittable<sbyte>();
public static sbyte? ReadSByteNullable(this NetworkReader reader) => reader.ReadBlittableNullable<sbyte>();

public static char ReadChar(this NetworkReader reader) => (char)reader.ReadUShort();
public static char? ReadCharNullable(this NetworkReader reader) => reader.ReadBool() ? ReadChar(reader) : default(char?);
// bool is not blittable. read as ushort.
public static char ReadChar(this NetworkReader reader) => (char)reader.ReadBlittable<ushort>();
public static char? ReadCharNullable(this NetworkReader reader) => (char?)reader.ReadBlittableNullable<ushort>();

public static bool ReadBool(this NetworkReader reader) => reader.ReadByte() != 0;
public static bool? ReadBoolNullable(this NetworkReader reader) => reader.ReadBool() ? ReadBool(reader) : default(bool?);
// bool is not blittable. read as byte.
public static bool ReadBool(this NetworkReader reader) => reader.ReadBlittable<byte>() != 0;
public static bool? ReadBoolNullable(this NetworkReader reader)
{
byte? value = reader.ReadBlittableNullable<byte>();
return value.HasValue ? (value.Value != 0) : default(bool?);
}

public static short ReadShort(this NetworkReader reader) => (short)reader.ReadUShort();
public static short? ReadShortNullable(this NetworkReader reader) => reader.ReadBool() ? ReadShort(reader) : default(short?);
public static short? ReadShortNullable(this NetworkReader reader) => reader.ReadBlittableNullable<short>();

public static ushort ReadUShort(this NetworkReader reader)
{
ushort value = 0;
value |= reader.ReadByte();
value |= (ushort)(reader.ReadByte() << 8);
return value;
}
public static ushort? ReadUShortNullable(this NetworkReader reader) => reader.ReadBool() ? ReadUShort(reader) : default(ushort?);
public static ushort ReadUShort(this NetworkReader reader) => reader.ReadBlittable<ushort>();
public static ushort? ReadUShortNullable(this NetworkReader reader) => reader.ReadBlittableNullable<ushort>();

public static int ReadInt(this NetworkReader reader) => (int)reader.ReadUInt();
public static int? ReadIntNullable(this NetworkReader reader) => reader.ReadBool() ? ReadInt(reader) : default(int?);
public static int ReadInt(this NetworkReader reader) => reader.ReadBlittable<int>();
public static int? ReadIntNullable(this NetworkReader reader) => reader.ReadBlittableNullable<int>();

public static uint ReadUInt(this NetworkReader reader)
{
uint value = 0;
value |= reader.ReadByte();
value |= (uint)(reader.ReadByte() << 8);
value |= (uint)(reader.ReadByte() << 16);
value |= (uint)(reader.ReadByte() << 24);
return value;
}
public static uint? ReadUIntNullable(this NetworkReader reader) => reader.ReadBool() ? ReadUInt(reader) : default(uint?);
public static uint ReadUInt(this NetworkReader reader) => reader.ReadBlittable<uint>();
public static uint? ReadUIntNullable(this NetworkReader reader) => reader.ReadBlittableNullable<uint>();

public static long ReadLong(this NetworkReader reader) => (long)reader.ReadULong();
public static long? ReadLongNullable(this NetworkReader reader) => reader.ReadBool() ? ReadLong(reader) : default(long?);
public static long ReadLong(this NetworkReader reader) => reader.ReadBlittable<long>();
public static long? ReadLongNullable(this NetworkReader reader) => reader.ReadBlittableNullable<long>();

public static ulong ReadULong(this NetworkReader reader)
{
ulong value = 0;
value |= reader.ReadByte();
value |= ((ulong)reader.ReadByte()) << 8;
value |= ((ulong)reader.ReadByte()) << 16;
value |= ((ulong)reader.ReadByte()) << 24;
value |= ((ulong)reader.ReadByte()) << 32;
value |= ((ulong)reader.ReadByte()) << 40;
value |= ((ulong)reader.ReadByte()) << 48;
value |= ((ulong)reader.ReadByte()) << 56;
return value;
}
public static ulong? ReadULongNullable(this NetworkReader reader) => reader.ReadBool() ? ReadULong(reader) : default(ulong?);
public static ulong ReadULong(this NetworkReader reader) => reader.ReadBlittable<ulong>();
public static ulong? ReadULongNullable(this NetworkReader reader) => reader.ReadBlittableNullable<ulong>();

public static float ReadFloat(this NetworkReader reader)
{
UIntFloat converter = new UIntFloat();
converter.intValue = reader.ReadUInt();
return converter.floatValue;
}
public static float? ReadFloatNullable(this NetworkReader reader) => reader.ReadBool() ? ReadFloat(reader) : default(float?);
public static float ReadFloat(this NetworkReader reader) => reader.ReadBlittable<float>();
public static float? ReadFloatNullable(this NetworkReader reader) => reader.ReadBlittableNullable<float>();

public static double ReadDouble(this NetworkReader reader)
{
UIntDouble converter = new UIntDouble();
converter.longValue = reader.ReadULong();
return converter.doubleValue;
}
public static double? ReadDoubleNullable(this NetworkReader reader) => reader.ReadBool() ? ReadDouble(reader) : default(double?);
public static double ReadDouble(this NetworkReader reader) => reader.ReadBlittable<double>();
public static double? ReadDoubleNullable(this NetworkReader reader) => reader.ReadBlittableNullable<double>();

public static decimal ReadDecimal(this NetworkReader reader)
{
UIntDecimal converter = new UIntDecimal();
converter.longValue1 = reader.ReadULong();
converter.longValue2 = reader.ReadULong();
return converter.decimalValue;
}
public static decimal? ReadDecimalNullable(this NetworkReader reader) => reader.ReadBool() ? ReadDecimal(reader) : default(decimal?);
public static decimal ReadDecimal(this NetworkReader reader) => reader.ReadBlittable<decimal>();
public static decimal? ReadDecimalNullable(this NetworkReader reader) => reader.ReadBlittableNullable<decimal>();

/// <exception cref="T:System.ArgumentException">if an invalid utf8 string is sent</exception>
public static string ReadString(this NetworkReader reader)
Expand Down Expand Up @@ -261,62 +286,41 @@ public static ArraySegment<byte> ReadBytesAndSizeSegment(this NetworkReader read
return count == 0 ? default : reader.ReadBytesSegment(checked((int)(count - 1u)));
}

public static Vector2 ReadVector2(this NetworkReader reader) => new Vector2(reader.ReadFloat(), reader.ReadFloat());
public static Vector2? ReadVector2Nullable(this NetworkReader reader) => reader.ReadBool() ? ReadVector2(reader) : default(Vector2?);
public static Vector2 ReadVector2(this NetworkReader reader) => reader.ReadBlittable<Vector2>();
public static Vector2? ReadVector2Nullable(this NetworkReader reader) => reader.ReadBlittableNullable<Vector2>();

public static Vector3 ReadVector3(this NetworkReader reader) => new Vector3(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat());
public static Vector3? ReadVector3Nullable(this NetworkReader reader) => reader.ReadBool() ? ReadVector3(reader) : default(Vector3?);
public static Vector3 ReadVector3(this NetworkReader reader) => reader.ReadBlittable<Vector3>();
public static Vector3? ReadVector3Nullable(this NetworkReader reader) => reader.ReadBlittableNullable<Vector3>();

public static Vector4 ReadVector4(this NetworkReader reader) => new Vector4(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat());
public static Vector4? ReadVector4Nullable(this NetworkReader reader) => reader.ReadBool() ? ReadVector4(reader) : default(Vector4?);
public static Vector4 ReadVector4(this NetworkReader reader) => reader.ReadBlittable<Vector4>();
public static Vector4? ReadVector4Nullable(this NetworkReader reader) => reader.ReadBlittableNullable<Vector4>();

public static Vector2Int ReadVector2Int(this NetworkReader reader) => new Vector2Int(reader.ReadInt(), reader.ReadInt());
public static Vector2Int? ReadVector2IntNullable(this NetworkReader reader) => reader.ReadBool() ? ReadVector2Int(reader) : default(Vector2Int?);
public static Vector2Int ReadVector2Int(this NetworkReader reader) => reader.ReadBlittable<Vector2Int>();
public static Vector2Int? ReadVector2IntNullable(this NetworkReader reader) => reader.ReadBlittableNullable<Vector2Int>();

public static Vector3Int ReadVector3Int(this NetworkReader reader) => new Vector3Int(reader.ReadInt(), reader.ReadInt(), reader.ReadInt());
public static Vector3Int? ReadVector3IntNullable(this NetworkReader reader) => reader.ReadBool() ? ReadVector3Int(reader) : default(Vector3Int?);
public static Vector3Int ReadVector3Int(this NetworkReader reader) => reader.ReadBlittable<Vector3Int>();
public static Vector3Int? ReadVector3IntNullable(this NetworkReader reader) => reader.ReadBlittableNullable<Vector3Int>();

public static Color ReadColor(this NetworkReader reader) => new Color(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat());
public static Color? ReadColorNullable(this NetworkReader reader) => reader.ReadBool() ? new Color(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat()) : default(Color?);
public static Color ReadColor(this NetworkReader reader) => reader.ReadBlittable<Color>();
public static Color? ReadColorNullable(this NetworkReader reader) => reader.ReadBlittableNullable<Color>();

public static Color32 ReadColor32(this NetworkReader reader) => new Color32(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte());
public static Color32? ReadColor32Nullable(this NetworkReader reader) => reader.ReadBool() ? new Color32(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte()) : default(Color32?);
public static Color32 ReadColor32(this NetworkReader reader) => reader.ReadBlittable<Color32>();
public static Color32? ReadColor32Nullable(this NetworkReader reader) => reader.ReadBlittableNullable<Color32>();

public static Quaternion ReadQuaternion(this NetworkReader reader) => new Quaternion(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat());
public static Quaternion? ReadQuaternionNullable(this NetworkReader reader) => reader.ReadBool() ? ReadQuaternion(reader) : default(Quaternion?);
public static Quaternion ReadQuaternion(this NetworkReader reader) => reader.ReadBlittable<Quaternion>();
public static Quaternion? ReadQuaternionNullable(this NetworkReader reader) => reader.ReadBlittableNullable<Quaternion>();

public static Rect ReadRect(this NetworkReader reader) => new Rect(reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat(), reader.ReadFloat());
public static Rect? ReadRectNullable(this NetworkReader reader) => reader.ReadBool() ? ReadRect(reader) : default(Rect?);
public static Rect ReadRect(this NetworkReader reader) => reader.ReadBlittable<Rect>();
public static Rect? ReadRectNullable(this NetworkReader reader) => reader.ReadBlittableNullable<Rect>();

public static Plane ReadPlane(this NetworkReader reader) => new Plane(reader.ReadVector3(), reader.ReadFloat());
public static Plane? ReadPlaneNullable(this NetworkReader reader) => reader.ReadBool() ? ReadPlane(reader) : default(Plane?);
public static Plane ReadPlane(this NetworkReader reader) => reader.ReadBlittable<Plane>();
public static Plane? ReadPlaneNullable(this NetworkReader reader) => reader.ReadBlittableNullable<Plane>();

public static Ray ReadRay(this NetworkReader reader) => new Ray(reader.ReadVector3(), reader.ReadVector3());
public static Ray? ReadRayNullable(this NetworkReader reader) => reader.ReadBool() ? ReadRay(reader) : default(Ray?);
public static Ray ReadRay(this NetworkReader reader) => reader.ReadBlittable<Ray>();
public static Ray? ReadRayNullable(this NetworkReader reader) => reader.ReadBlittableNullable<Ray>();

public static Matrix4x4 ReadMatrix4x4(this NetworkReader reader)
{
return new Matrix4x4
{
m00 = reader.ReadFloat(),
m01 = reader.ReadFloat(),
m02 = reader.ReadFloat(),
m03 = reader.ReadFloat(),
m10 = reader.ReadFloat(),
m11 = reader.ReadFloat(),
m12 = reader.ReadFloat(),
m13 = reader.ReadFloat(),
m20 = reader.ReadFloat(),
m21 = reader.ReadFloat(),
m22 = reader.ReadFloat(),
m23 = reader.ReadFloat(),
m30 = reader.ReadFloat(),
m31 = reader.ReadFloat(),
m32 = reader.ReadFloat(),
m33 = reader.ReadFloat()
};
}
public static Matrix4x4? ReadMatrix4x4Nullable(this NetworkReader reader) => reader.ReadBool() ? ReadMatrix4x4(reader) : default(Matrix4x4?);
public static Matrix4x4 ReadMatrix4x4(this NetworkReader reader)=> reader.ReadBlittable<Matrix4x4>();
public static Matrix4x4? ReadMatrix4x4Nullable(this NetworkReader reader) => reader.ReadBlittableNullable<Matrix4x4>();

public static Guid ReadGuid(this NetworkReader reader) => new Guid(reader.ReadBytes(16));
public static Guid? ReadGuidNullable(this NetworkReader reader) => reader.ReadBool() ? ReadGuid(reader) : default(Guid?);
Expand Down

0 comments on commit b54d086

Please sign in to comment.