Revert "perf: NetworkWriter/Reader Write/ReadBlittable<T> for 4-6x performance improvement! (#2441)" (#2526)

This reverts commit 1947f061ad and applies some patches to bring it back up to date without the DOTSNET feature.
This commit is contained in:
Coburn 2021-01-03 22:52:34 +10:00 committed by GitHub
parent eb6b7e73b9
commit dd4d344542
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 314 additions and 230 deletions

View File

@ -8,7 +8,7 @@
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,

View File

@ -1,8 +1,15 @@
// Custom NetworkReader that doesn't use C#'s built in MemoryStream in order to
// avoid allocations.
//
// Benchmark: 100kb byte[] passed to NetworkReader constructor 1000x
// before with MemoryStream
// 0.8% CPU time, 250KB memory, 3.82ms
// now:
// 0.0% CPU time, 32KB memory, 0.02ms
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
namespace Mirror
@ -68,59 +75,13 @@ public NetworkReader(ArraySegment<byte> segment)
buffer = segment;
}
// ReadBlittable<T> from DOTSNET
// Benchmark: see NetworkWriter.WriteBlittable!
/// <summary>
/// Read blittable type from buffer
/// <para>
/// this is extremely fast, but only works for blittable types.
/// </para>
/// <para>
/// Note:
/// ReadBlittable assumes same endianness for server and client.
/// All Unity 2018+ platforms are little endian.
/// </para>
/// </summary>
/// <remarks>
/// See <see href="https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types">Blittable and Non-Blittable Types</see>
/// for more info.
/// </remarks>
/// <typeparam name="T">Needs to be unmanaged, see <see href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/unmanaged-types">unmanaged types</see></typeparam>
/// <returns></returns>
public unsafe T ReadBlittable<T>()
where T : unmanaged
public byte ReadByte()
{
// check if blittable for safety
#if UNITY_EDITOR
if (!UnsafeUtility.IsBlittable(typeof(T)))
if (Position + 1 > buffer.Count)
{
throw new ArgumentException(typeof(T) + " is not blittable!");
throw new EndOfStreamException("ReadByte out of range:" + ToString());
}
#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])
{
// cast buffer to a T* pointer and then read from it.
value = *(T*)ptr;
}
Position += size;
return value;
return buffer.Array[buffer.Offset + Position++];
}
/// <summary>
@ -183,12 +144,9 @@ public T Read<T>()
}
}
/// <summary>
/// Built in Reader functions for Mirror
/// <para>
/// Weaver automatically detects all extension methods for NetworkWriter
/// </para>
/// </summary>
// Mirror's Weaver automatically detects all NetworkReader function types,
// but they do all need to be extensions.
public static class NetworkReaderExtensions
{
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkReaderExtensions));
@ -198,19 +156,61 @@ 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.ReadBlittable<byte>();
public static sbyte ReadSByte(this NetworkReader reader) => reader.ReadBlittable<sbyte>();
public static char ReadChar(this NetworkReader reader) => (char)reader.ReadBlittable<short>(); // char isn't blittable
public static bool ReadBoolean(this NetworkReader reader) => reader.ReadBlittable<byte>() != 0; // bool isn't blittable
public static short ReadInt16(this NetworkReader reader) => reader.ReadBlittable<short>();
public static ushort ReadUInt16(this NetworkReader reader) => reader.ReadBlittable<ushort>();
public static int ReadInt32(this NetworkReader reader) => reader.ReadBlittable<int>();
public static uint ReadUInt32(this NetworkReader reader) => reader.ReadBlittable<uint>();
public static long ReadInt64(this NetworkReader reader) => reader.ReadBlittable<long>();
public static ulong ReadUInt64(this NetworkReader reader) => reader.ReadBlittable<ulong>();
public static float ReadSingle(this NetworkReader reader) => reader.ReadBlittable<float>();
public static double ReadDouble(this NetworkReader reader) => reader.ReadBlittable<double>();
public static decimal ReadDecimal(this NetworkReader reader) => reader.ReadBlittable<decimal>();
public static byte ReadByte(this NetworkReader reader) => reader.ReadByte();
public static sbyte ReadSByte(this NetworkReader reader) => (sbyte)reader.ReadByte();
public static char ReadChar(this NetworkReader reader) => (char)reader.ReadUInt16();
public static bool ReadBoolean(this NetworkReader reader) => reader.ReadByte() != 0;
public static short ReadInt16(this NetworkReader reader) => (short)reader.ReadUInt16();
public static ushort ReadUInt16(this NetworkReader reader)
{
ushort value = 0;
value |= reader.ReadByte();
value |= (ushort)(reader.ReadByte() << 8);
return value;
}
public static int ReadInt32(this NetworkReader reader) => (int)reader.ReadUInt32();
public static uint ReadUInt32(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 long ReadInt64(this NetworkReader reader) => (long)reader.ReadUInt64();
public static ulong ReadUInt64(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 float ReadSingle(this NetworkReader reader)
{
UIntFloat converter = new UIntFloat();
converter.intValue = reader.ReadUInt32();
return converter.floatValue;
}
public static double ReadDouble(this NetworkReader reader)
{
UIntDouble converter = new UIntDouble();
converter.longValue = reader.ReadUInt64();
return converter.doubleValue;
}
public static decimal ReadDecimal(this NetworkReader reader)
{
UIntDecimal converter = new UIntDecimal();
converter.longValue1 = reader.ReadUInt64();
converter.longValue2 = reader.ReadUInt64();
return converter.decimalValue;
}
/// <exception cref="T:System.ArgumentException">if an invalid utf8 string is sent</exception>
public static string ReadString(this NetworkReader reader)
@ -256,18 +256,40 @@ 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) => reader.ReadBlittable<Vector2>();
public static Vector3 ReadVector3(this NetworkReader reader) => reader.ReadBlittable<Vector3>();
public static Vector4 ReadVector4(this NetworkReader reader) => reader.ReadBlittable<Vector4>();
public static Vector2Int ReadVector2Int(this NetworkReader reader) => reader.ReadBlittable<Vector2Int>();
public static Vector3Int ReadVector3Int(this NetworkReader reader) => reader.ReadBlittable<Vector3Int>();
public static Color ReadColor(this NetworkReader reader) => reader.ReadBlittable<Color>();
public static Color32 ReadColor32(this NetworkReader reader) => reader.ReadBlittable<Color32>();
public static Quaternion ReadQuaternion(this NetworkReader reader) => reader.ReadBlittable<Quaternion>();
public static Rect ReadRect(this NetworkReader reader) => reader.ReadBlittable<Rect>();
public static Plane ReadPlane(this NetworkReader reader) => reader.ReadBlittable<Plane>();
public static Ray ReadRay(this NetworkReader reader) => reader.ReadBlittable<Ray>();
public static Matrix4x4 ReadMatrix4x4(this NetworkReader reader) => reader.ReadBlittable<Matrix4x4>();
public static Vector2 ReadVector2(this NetworkReader reader) => new Vector2(reader.ReadSingle(), reader.ReadSingle());
public static Vector3 ReadVector3(this NetworkReader reader) => new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
public static Vector4 ReadVector4(this NetworkReader reader) => new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
public static Vector2Int ReadVector2Int(this NetworkReader reader) => new Vector2Int(reader.ReadInt32(), reader.ReadInt32());
public static Vector3Int ReadVector3Int(this NetworkReader reader) => new Vector3Int(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32());
public static Color ReadColor(this NetworkReader reader) => new Color(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
public static Color32 ReadColor32(this NetworkReader reader) => new Color32(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte());
public static Quaternion ReadQuaternion(this NetworkReader reader) => new Quaternion(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
public static Rect ReadRect(this NetworkReader reader) => new Rect(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
public static Plane ReadPlane(this NetworkReader reader) => new Plane(reader.ReadVector3(), reader.ReadSingle());
public static Ray ReadRay(this NetworkReader reader) => new Ray(reader.ReadVector3(), reader.ReadVector3());
public static Matrix4x4 ReadMatrix4x4(this NetworkReader reader)
{
return new Matrix4x4
{
m00 = reader.ReadSingle(),
m01 = reader.ReadSingle(),
m02 = reader.ReadSingle(),
m03 = reader.ReadSingle(),
m10 = reader.ReadSingle(),
m11 = reader.ReadSingle(),
m12 = reader.ReadSingle(),
m13 = reader.ReadSingle(),
m20 = reader.ReadSingle(),
m21 = reader.ReadSingle(),
m22 = reader.ReadSingle(),
m23 = reader.ReadSingle(),
m30 = reader.ReadSingle(),
m31 = reader.ReadSingle(),
m32 = reader.ReadSingle(),
m33 = reader.ReadSingle()
};
}
public static byte[] ReadBytes(this NetworkReader reader, int count)
{

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
namespace Mirror
@ -139,85 +138,14 @@ public ArraySegment<byte> ToArraySegment()
return new ArraySegment<byte>(buffer, 0, length);
}
// WriteBlittable<T> from DOTSNET.
// Benchmark:
// WriteQuaternion x 100k, Macbook Pro 2015 @ 2.2Ghz, Unity 2018 LTS (debug mode)
//
// | Median | Min | Max | Avg | Std | (ms)
// before | 30.35 | 29.86 | 48.99 | 32.54 | 4.93 |
// blittable* | 5.69 | 5.52 | 27.51 | 7.78 | 5.65 |
//
// * without IsBlittable check
// => 4-6x faster!
//
// WriteQuaternion x 100k, Macbook Pro 2015 @ 2.2Ghz, Unity 2020.1 (release mode)
//
// | Median | Min | Max | Avg | Std | (ms)
// before | 9.41 | 8.90 | 23.02 | 10.72 | 3.07 |
// blittable* | 1.48 | 1.40 | 16.03 | 2.60 | 2.71 |
//
// * without IsBlittable check
// => 6x faster!
/// <summary>
/// Copies blittable type to buffer
/// <para>
/// This is extremely fast, but only works for blittable types.
/// </para>
/// <para>
/// Note:
/// WriteBlittable assumes same endianness for server and client.
/// All Unity 2018+ platforms are little endian.<br/>
/// => run NetworkWriterTests.BlittableOnThisPlatform() to verify!
/// </para>
/// </summary>
/// <remarks>
/// See <see href="https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types">Blittable and Non-Blittable Types</see>
/// for more info.
/// </remarks>
/// <typeparam name="T">Needs to be unmanaged, see <see href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/unmanaged-types">unmanaged types</see></typeparam>
/// <param name="value"></param>
public unsafe void WriteBlittable<T>(T value)
where T : unmanaged
public void WriteByte(byte value)
{
// check if blittable for safety
#if UNITY_EDITOR
if (!UnsafeUtility.IsBlittable(typeof(T)))
{
Debug.LogError(typeof(T) + " is not blittable!");
return;
}
#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);
// ensure length
EnsureLength(position + size);
// write blittable
fixed (byte* ptr = &buffer[Position])
{
// cast buffer to T* pointer, then assign value to the area
*(T*)ptr = value;
}
Position += size;
EnsureLength(position + 1);
buffer[position++] = value;
}
/// <summary>
/// Copys bytes from given array to <see cref="buffer"/>
/// <para>
/// useful for byte arrays with consistent size, where the reader knows how many to read
/// (like a packet opcode that's always the same)
/// </para>
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
// for byte arrays with consistent size, where the reader knows how many to read
// (like a packet opcode that's always the same)
public void WriteBytes(byte[] buffer, int offset, int count)
{
EnsureLength(position + count);
@ -245,12 +173,8 @@ public void Write<T>(T value)
}
/// <summary>
/// Built in Writer functions for Mirror
/// <para>
/// Weaver automatically decects all extension methods for NetworkWriter
/// </para>
/// </summary>
// Mirror's Weaver automatically detects all NetworkWriter function types,
// but they do all need to be extensions.
public static class NetworkWriterExtensions
{
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkWriterExtensions));
@ -261,19 +185,76 @@ public static class NetworkWriterExtensions
static readonly UTF8Encoding encoding = new UTF8Encoding(false, true);
static readonly byte[] stringBuffer = new byte[NetworkWriter.MaxStringLength];
public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteBlittable(value);
public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteBlittable(value);
public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteBlittable((short)value); // char isn't blittable
public static void WriteBoolean(this NetworkWriter writer, bool value) => writer.WriteBlittable((byte)(value ? 1 : 0)); // bool isn't blittable
public static void WriteUInt16(this NetworkWriter writer, ushort value) => writer.WriteBlittable(value);
public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteByte(value);
public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteByte((byte)value);
public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteUInt16(value);
public static void WriteBoolean(this NetworkWriter writer, bool value) => writer.WriteByte((byte)(value ? 1 : 0));
public static void WriteUInt16(this NetworkWriter writer, ushort value)
{
writer.WriteByte((byte)value);
writer.WriteByte((byte)(value >> 8));
}
public static void WriteInt16(this NetworkWriter writer, short value) => writer.WriteUInt16((ushort)value);
public static void WriteUInt32(this NetworkWriter writer, uint value) => writer.WriteBlittable(value);
public static void WriteInt32(this NetworkWriter writer, int value) => writer.WriteBlittable(value);
public static void WriteUInt64(this NetworkWriter writer, ulong value) => writer.WriteBlittable(value);
public static void WriteInt64(this NetworkWriter writer, long value) => writer.WriteBlittable(value);
public static void WriteSingle(this NetworkWriter writer, float value) => writer.WriteBlittable(value);
public static void WriteDouble(this NetworkWriter writer, double value) => writer.WriteBlittable(value);
public static void WriteDecimal(this NetworkWriter writer, decimal value) => writer.WriteBlittable(value);
public static void WriteUInt32(this NetworkWriter writer, uint value)
{
writer.WriteByte((byte)value);
writer.WriteByte((byte)(value >> 8));
writer.WriteByte((byte)(value >> 16));
writer.WriteByte((byte)(value >> 24));
}
public static void WriteInt32(this NetworkWriter writer, int value) => writer.WriteUInt32((uint)value);
public static void WriteUInt64(this NetworkWriter writer, ulong value)
{
writer.WriteByte((byte)value);
writer.WriteByte((byte)(value >> 8));
writer.WriteByte((byte)(value >> 16));
writer.WriteByte((byte)(value >> 24));
writer.WriteByte((byte)(value >> 32));
writer.WriteByte((byte)(value >> 40));
writer.WriteByte((byte)(value >> 48));
writer.WriteByte((byte)(value >> 56));
}
public static void WriteInt64(this NetworkWriter writer, long value) => writer.WriteUInt64((ulong)value);
public static void WriteSingle(this NetworkWriter writer, float value)
{
UIntFloat converter = new UIntFloat
{
floatValue = value
};
writer.WriteUInt32(converter.intValue);
}
public static void WriteDouble(this NetworkWriter writer, double value)
{
UIntDouble converter = new UIntDouble
{
doubleValue = value
};
writer.WriteUInt64(converter.longValue);
}
public static void WriteDecimal(this NetworkWriter writer, decimal value)
{
// the only way to read it without allocations is to both read and
// write it with the FloatConverter (which is not binary compatible
// to writer.Write(decimal), hence why we use it here too)
UIntDecimal converter = new UIntDecimal
{
decimalValue = value
};
writer.WriteUInt64(converter.longValue1);
writer.WriteUInt64(converter.longValue2);
}
public static void WriteString(this NetworkWriter writer, string value)
{
@ -331,18 +312,103 @@ public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegm
writer.WriteBytesAndSize(buffer.Array, buffer.Offset, buffer.Count);
}
public static void WriteVector2(this NetworkWriter writer, Vector2 value) => writer.WriteBlittable(value);
public static void WriteVector3(this NetworkWriter writer, Vector3 value) => writer.WriteBlittable(value);
public static void WriteVector4(this NetworkWriter writer, Vector4 value) => writer.WriteBlittable(value);
public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value) => writer.WriteBlittable(value);
public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value) => writer.WriteBlittable(value);
public static void WriteColor(this NetworkWriter writer, Color value) => writer.WriteBlittable(value);
public static void WriteColor32(this NetworkWriter writer, Color32 value) => writer.WriteBlittable(value);
public static void WriteQuaternion(this NetworkWriter writer, Quaternion value) => writer.WriteBlittable(value);
public static void WriteRect(this NetworkWriter writer, Rect value) => writer.WriteBlittable(value);
public static void WritePlane(this NetworkWriter writer, Plane value) => writer.WriteBlittable(value);
public static void WriteRay(this NetworkWriter writer, Ray value) => writer.WriteBlittable(value);
public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value) => writer.WriteBlittable(value);
public static void WriteVector2(this NetworkWriter writer, Vector2 value)
{
writer.WriteSingle(value.x);
writer.WriteSingle(value.y);
}
public static void WriteVector3(this NetworkWriter writer, Vector3 value)
{
writer.WriteSingle(value.x);
writer.WriteSingle(value.y);
writer.WriteSingle(value.z);
}
public static void WriteVector4(this NetworkWriter writer, Vector4 value)
{
writer.WriteSingle(value.x);
writer.WriteSingle(value.y);
writer.WriteSingle(value.z);
writer.WriteSingle(value.w);
}
public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value)
{
writer.WriteInt32(value.x);
writer.WriteInt32(value.y);
}
public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value)
{
writer.WriteInt32(value.x);
writer.WriteInt32(value.y);
writer.WriteInt32(value.z);
}
public static void WriteColor(this NetworkWriter writer, Color value)
{
writer.WriteSingle(value.r);
writer.WriteSingle(value.g);
writer.WriteSingle(value.b);
writer.WriteSingle(value.a);
}
public static void WriteColor32(this NetworkWriter writer, Color32 value)
{
writer.WriteByte(value.r);
writer.WriteByte(value.g);
writer.WriteByte(value.b);
writer.WriteByte(value.a);
}
public static void WriteQuaternion(this NetworkWriter writer, Quaternion value)
{
writer.WriteSingle(value.x);
writer.WriteSingle(value.y);
writer.WriteSingle(value.z);
writer.WriteSingle(value.w);
}
public static void WriteRect(this NetworkWriter writer, Rect value)
{
writer.WriteSingle(value.xMin);
writer.WriteSingle(value.yMin);
writer.WriteSingle(value.width);
writer.WriteSingle(value.height);
}
public static void WritePlane(this NetworkWriter writer, Plane value)
{
writer.WriteVector3(value.normal);
writer.WriteSingle(value.distance);
}
public static void WriteRay(this NetworkWriter writer, Ray value)
{
writer.WriteVector3(value.origin);
writer.WriteVector3(value.direction);
}
public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value)
{
writer.WriteSingle(value.m00);
writer.WriteSingle(value.m01);
writer.WriteSingle(value.m02);
writer.WriteSingle(value.m03);
writer.WriteSingle(value.m10);
writer.WriteSingle(value.m11);
writer.WriteSingle(value.m12);
writer.WriteSingle(value.m13);
writer.WriteSingle(value.m20);
writer.WriteSingle(value.m21);
writer.WriteSingle(value.m22);
writer.WriteSingle(value.m23);
writer.WriteSingle(value.m30);
writer.WriteSingle(value.m31);
writer.WriteSingle(value.m32);
writer.WriteSingle(value.m33);
}
public static void WriteGuid(this NetworkWriter writer, Guid value)
{

View File

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Mirror
@ -31,4 +32,38 @@ public static class Channels
public const int DefaultReliable = 0;
public const int DefaultUnreliable = 1;
}
// -- helpers for float conversion without allocations --
[StructLayout(LayoutKind.Explicit)]
internal struct UIntFloat
{
[FieldOffset(0)]
public float floatValue;
[FieldOffset(0)]
public uint intValue;
}
[StructLayout(LayoutKind.Explicit)]
internal struct UIntDouble
{
[FieldOffset(0)]
public double doubleValue;
[FieldOffset(0)]
public ulong longValue;
}
[StructLayout(LayoutKind.Explicit)]
internal struct UIntDecimal
{
[FieldOffset(0)]
public ulong longValue1;
[FieldOffset(8)]
public ulong longValue2;
[FieldOffset(0)]
public decimal decimalValue;
}
}

View File

@ -15,7 +15,7 @@
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"NSubstitute.dll",

View File

@ -26,45 +26,6 @@ public void Benchmark()
}
*/
struct TestStruct
{
#pragma warning disable 649
public int data;
public byte data2;
#pragma warning restore 649
}
[Test]
public unsafe void BlittableOnThisPlatform()
{
// we assume NetworkWriter.WriteBlittable<T> to behave the same on
// all platforms:
// - need to be little endian (atm all Unity platforms are)
// - padded structs need to be same size across all platforms
// (C# int, byte, etc. should be same on all platforms, and
// C# should do the same padding on all platforms)
// https://kalapos.net/Blog/ShowPost/DotNetConceptOfTheWeek13_DotNetMemoryLayout
// => let's have a test that we can run on different platforms to
// be 100% sure
// let's assume little endian.
// it would also be ok if server and client are both big endian,
// but that's extremely unlikely.
Assert.That(BitConverter.IsLittleEndian, Is.True);
// TestStruct biggest member is 'int' = 4 bytes.
// so C# aligns it to 4 bytes, hence does padding for the byte:
// 0 int
// 1 int
// 2 int
// 3 int
// 4 byte
// 5 padding
// 6 padding
// 7 padding
Assert.That(sizeof(TestStruct), Is.EqualTo(8));
}
[Test]
public void TestWritingSmallMessage()
{