From 215e75ce36919c5103bf9896874e2c48862a53c1 Mon Sep 17 00:00:00 2001 From: Karim Rizk Date: Fri, 22 Dec 2023 16:29:34 +0200 Subject: [PATCH 1/3] Update serializer to match LiteNetLib 1.1.0 - Added nebula's custom methods to a partial class that extends the library. - Extracted interfaces to match the update. - Refactored some classes to match the new updates --- NebulaAPI/Interfaces/INetDataReader.cs | 151 ++ NebulaAPI/Interfaces/INetDataWriter.cs | 85 ++ NebulaAPI/Interfaces/INetSerializable.cs | 138 -- NebulaModel/Networking/PacketUtils.cs | 19 +- .../Serialization/FastBitConverter.cs | 258 ++-- .../Networking/Serialization/NetDataReader.cs | 1048 +++++++------- .../Serialization/NetDataReaderExtension.cs | 19 + .../Networking/Serialization/NetDataWriter.cs | 616 ++++---- .../Serialization/NetPacketProcessor.cs | 572 +++----- .../NetPacketProcessorExtension.cs | 121 ++ .../Networking/Serialization/NetSerializer.cs | 1275 +++++++---------- 11 files changed, 2084 insertions(+), 2218 deletions(-) create mode 100644 NebulaAPI/Interfaces/INetDataReader.cs create mode 100644 NebulaAPI/Interfaces/INetDataWriter.cs create mode 100644 NebulaModel/Networking/Serialization/NetDataReaderExtension.cs create mode 100644 NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs diff --git a/NebulaAPI/Interfaces/INetDataReader.cs b/NebulaAPI/Interfaces/INetDataReader.cs new file mode 100644 index 000000000..a37979e59 --- /dev/null +++ b/NebulaAPI/Interfaces/INetDataReader.cs @@ -0,0 +1,151 @@ +// #pragma once +// #ifndef INetDataReader.cs_H_ +// #define INetDataReader.cs_H_ +// +// #endif + +using System; +using System.Net; +using System.Runtime.CompilerServices; + +namespace NebulaAPI.Interfaces; + +public interface INetDataReader +{ + byte[] RawData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int RawDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int UserDataOffset + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int UserDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + bool EndOfData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int AvailableBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + void SkipBytes(int count); + void SetPosition(int position); + void SetSource(INetDataWriter dataWriter); + void SetSource(byte[] source); + void SetSource(byte[] source, int offset, int maxSize); + IPEndPoint GetNetEndPoint(); + byte GetByte(); + sbyte GetSByte(); + T[] GetArray(ushort size); + bool[] GetBoolArray(); + ushort[] GetUShortArray(); + short[] GetShortArray(); + int[] GetIntArray(); + uint[] GetUIntArray(); + float[] GetFloatArray(); + double[] GetDoubleArray(); + long[] GetLongArray(); + ulong[] GetULongArray(); + string[] GetStringArray(); + + /// + /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. + /// Strings that exceed this parameter are returned as empty + /// + string[] GetStringArray(int maxStringLength); + + bool GetBool(); + char GetChar(); + ushort GetUShort(); + short GetShort(); + long GetLong(); + ulong GetULong(); + int GetInt(); + uint GetUInt(); + float GetFloat(); + double GetDouble(); + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + /// "string.Empty" if value > "maxLength" + string GetString(int maxLength); + + string GetString(); + ArraySegment GetBytesSegment(int count); + ArraySegment GetRemainingBytesSegment(); + T Get() where T : struct, INetSerializable; + T Get(Func constructor) where T : class, INetSerializable; + byte[] GetRemainingBytes(); + void GetBytes(byte[] destination, int start, int count); + void GetBytes(byte[] destination, int count); + sbyte[] GetSBytesWithLength(); + byte[] GetBytesWithLength(); + byte PeekByte(); + sbyte PeekSByte(); + bool PeekBool(); + char PeekChar(); + ushort PeekUShort(); + short PeekShort(); + long PeekLong(); + ulong PeekULong(); + int PeekInt(); + uint PeekUInt(); + float PeekFloat(); + double PeekDouble(); + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + string PeekString(int maxLength); + + string PeekString(); + bool TryGetByte(out byte result); + bool TryGetSByte(out sbyte result); + bool TryGetBool(out bool result); + bool TryGetChar(out char result); + bool TryGetShort(out short result); + bool TryGetUShort(out ushort result); + bool TryGetInt(out int result); + bool TryGetUInt(out uint result); + bool TryGetLong(out long result); + bool TryGetULong(out ulong result); + bool TryGetFloat(out float result); + bool TryGetDouble(out double result); + bool TryGetString(out string result); + bool TryGetStringArray(out string[] result); + bool TryGetBytesWithLength(out byte[] result); + void Clear(); +} diff --git a/NebulaAPI/Interfaces/INetDataWriter.cs b/NebulaAPI/Interfaces/INetDataWriter.cs new file mode 100644 index 000000000..c405cc401 --- /dev/null +++ b/NebulaAPI/Interfaces/INetDataWriter.cs @@ -0,0 +1,85 @@ +// #pragma once +// #ifndef INetDataWriter.cs_H_ +// #define INetDataWriter.cs_H_ +// +// #endif + +using System; +using System.Net; +using System.Runtime.CompilerServices; + +namespace NebulaAPI.Interfaces; + +public interface INetDataWriter +{ + int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + byte[] Data + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + void ResizeIfNeed(int newSize); + void EnsureFit(int additionalSize); + void Reset(int size); + void Reset(); + byte[] CopyData(); + + /// + /// Sets position of NetDataWriter to rewrite previous values + /// + /// new byte position + /// previous position of data writer + int SetPosition(int position); + + void Put(float value); + void Put(double value); + void Put(long value); + void Put(ulong value); + void Put(int value); + void Put(uint value); + void Put(char value); + void Put(ushort value); + void Put(short value); + void Put(sbyte value); + void Put(byte value); + void Put(byte[] data, int offset, int length); + void Put(byte[] data); + void Put(bool value); + void Put(IPEndPoint endPoint); + void Put(string value); + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + void Put(string value, int maxLength); + + void Put(T obj) where T : INetSerializable; + void PutSBytesWithLength(sbyte[] data, int offset, ushort length); + void PutSBytesWithLength(sbyte[] data); + void PutBytesWithLength(byte[] data, int offset, ushort length); + void PutBytesWithLength(byte[] data); + void PutArray(Array arr, int sz); + void PutArray(float[] value); + void PutArray(double[] value); + void PutArray(long[] value); + void PutArray(ulong[] value); + void PutArray(int[] value); + void PutArray(uint[] value); + void PutArray(ushort[] value); + void PutArray(short[] value); + void PutArray(bool[] value); + void PutArray(string[] value); + void PutArray(string[] value, int strMaxLength); +} diff --git a/NebulaAPI/Interfaces/INetSerializable.cs b/NebulaAPI/Interfaces/INetSerializable.cs index 777597aaa..0c55052df 100644 --- a/NebulaAPI/Interfaces/INetSerializable.cs +++ b/NebulaAPI/Interfaces/INetSerializable.cs @@ -13,141 +13,3 @@ public interface INetSerializable void Deserialize(INetDataReader reader); } - -public interface INetDataWriter -{ - void Put(float value); - - void Put(double value); - - void Put(long value); - - void Put(ulong value); - - void Put(int value); - - void Put(uint value); - - void Put(char value); - - void Put(ushort value); - - void Put(short value); - - void Put(sbyte value); - - void Put(byte value); - - void Put(byte[] data, int offset, int length); - - void Put(byte[] data); - - void PutSBytesWithLength(sbyte[] data, int offset, int length); - - void PutSBytesWithLength(sbyte[] data); - - void PutBytesWithLength(byte[] data, int offset, int length); - - void PutBytesWithLength(byte[] data); - - void Put(bool value); - - void PutArray(float[] value); - - void PutArray(double[] value); - - void PutArray(long[] value); - - void PutArray(ulong[] value); - - void PutArray(int[] value); - - void PutArray(uint[] value); - - void PutArray(ushort[] value); - - void PutArray(short[] value); - - void PutArray(bool[] value); - - void PutArray(string[] value); - - void PutArray(string[] value, int maxLength); - - void Put(IPEndPoint endPoint); - - void Put(string value); - - void Put(string value, int maxLength); - - void Put(T obj) where T : INetSerializable; -} - -public interface INetDataReader -{ - IPEndPoint GetNetEndPoint(); - - byte GetByte(); - - sbyte GetSByte(); - - bool[] GetBoolArray(); - - ushort[] GetUShortArray(); - - short[] GetShortArray(); - - long[] GetLongArray(); - - ulong[] GetULongArray(); - - int[] GetIntArray(); - - uint[] GetUIntArray(); - - float[] GetFloatArray(); - - double[] GetDoubleArray(); - - string[] GetStringArray(); - - string[] GetStringArray(int maxStringLength); - - bool GetBool(); - - char GetChar(); - - ushort GetUShort(); - - short GetShort(); - - long GetLong(); - - ulong GetULong(); - - int GetInt(); - - uint GetUInt(); - - float GetFloat(); - - double GetDouble(); - - string GetString(int maxLength); - - string GetString(); - - ArraySegment GetRemainingBytesSegment(); - - T Get() where T : INetSerializable, new(); - - byte[] GetRemainingBytes(); - - void GetBytes(byte[] destination, int start, int count); - - void GetBytes(byte[] destination, int count); - - sbyte[] GetSBytesWithLength(); - - byte[] GetBytesWithLength(); -} diff --git a/NebulaModel/Networking/PacketUtils.cs b/NebulaModel/Networking/PacketUtils.cs index 32cf724fe..1837b57c8 100644 --- a/NebulaModel/Networking/PacketUtils.cs +++ b/NebulaModel/Networking/PacketUtils.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using NebulaAPI; using NebulaAPI.Packets; @@ -29,19 +30,27 @@ public static void RegisterAllPacketNestedTypesInAssembly(Assembly assembly, Net { Log.Debug($"Registering Nested Type: {type.Name}"); } + if (type.IsClass) { var registerMethod = packetProcessor.GetType().GetMethods() .Where(m => m.Name == nameof(NetPacketProcessor.RegisterNestedType)) .FirstOrDefault(m => - m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Name.Equals(typeof(Func<>).Name)) + m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Name.Equals(typeof(Func<>).Name))! .MakeGenericMethod(type); - var delegateMethod = packetProcessor.GetType().GetMethod(nameof(NetPacketProcessor.CreateNestedClassInstance)) + var constructorMethod = typeof(Activator) + .GetMethods() + .Where((info => info.GetParameters().Length == 0)) + .FirstOrDefault()! .MakeGenericMethod(type); + + // create a Func delegate from the object's constructor to pass into NetPacketProcessor.RegisterNestedType var funcType = typeof(Func<>).MakeGenericType(type); - var callback = Delegate.CreateDelegate(funcType, packetProcessor, delegateMethod); - registerMethod.Invoke(packetProcessor, new object[] { callback }); + var constructorDelegate = Delegate.CreateDelegate(funcType, constructorMethod); + + // Invoke NetPacketProcessor.RegisterNestedType(Func constructor) + registerMethod.Invoke(packetProcessor, new object[] { constructorDelegate }); } else if (type.IsValueType) { @@ -66,8 +75,10 @@ private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) { return true; } + toCheck = toCheck.BaseType; } + return false; } diff --git a/NebulaModel/Networking/Serialization/FastBitConverter.cs b/NebulaModel/Networking/Serialization/FastBitConverter.cs index 3081d4d7c..7bf53c652 100644 --- a/NebulaModel/Networking/Serialization/FastBitConverter.cs +++ b/NebulaModel/Networking/Serialization/FastBitConverter.cs @@ -1,118 +1,174 @@ -#region - -using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#endregion - -namespace NebulaModel.Networking.Serialization; - -public static class FastBitConverter +namespace NebulaModel.Networking.Serialization { - private static void WriteLittleEndian(IList buffer, int offset, ulong data) + public static class FastBitConverter { -#if BIGENDIAN - buffer[offset + 7] = (byte)(data); - buffer[offset + 6] = (byte)(data >> 8); - buffer[offset + 5] = (byte)(data >> 16); - buffer[offset + 4] = (byte)(data >> 24); - buffer[offset + 3] = (byte)(data >> 32); - buffer[offset + 2] = (byte)(data >> 40); - buffer[offset + 1] = (byte)(data >> 48); - buffer[offset] = (byte)(data >> 56); +#if (LITENETLIB_UNSAFE || LITENETLIB_UNSAFELIB || NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER) && !BIGENDIAN +#if LITENETLIB_UNSAFE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged + { + int size = sizeof(T); + if (bytes.Length < startIndex + size) + ThrowIndexOutOfRangeException(); +#if LITENETLIB_UNSAFELIB || NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER + Unsafe.As(ref bytes[startIndex]) = value; +#else + fixed (byte* ptr = &bytes[startIndex]) + { +#if UNITY_ANDROID + // On some android systems, assigning *(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. + T* valueBuffer = stackalloc T[1] { value }; + UnsafeUtility.MemCpy(ptr, valueBuffer, size); #else - buffer[offset] = (byte)data; - buffer[offset + 1] = (byte)(data >> 8); - buffer[offset + 2] = (byte)(data >> 16); - buffer[offset + 3] = (byte)(data >> 24); - buffer[offset + 4] = (byte)(data >> 32); - buffer[offset + 5] = (byte)(data >> 40); - buffer[offset + 6] = (byte)(data >> 48); - buffer[offset + 7] = (byte)(data >> 56); + *(T*)ptr = value; +#endif + } +#endif + } +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged + { + if (bytes.Length < startIndex + Unsafe.SizeOf()) + ThrowIndexOutOfRangeException(); + Unsafe.As(ref bytes[startIndex]) = value; + } #endif - } - private static void WriteLittleEndian(IList buffer, int offset, int data) - { + private static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException(); +#else + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperDouble + { + [FieldOffset(0)] + public ulong Along; + + [FieldOffset(0)] + public double Adouble; + } + + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperFloat + { + [FieldOffset(0)] + public int Aint; + + [FieldOffset(0)] + public float Afloat; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLittleEndian(byte[] buffer, int offset, ulong data) + { #if BIGENDIAN - buffer[offset + 3] = (byte)(data); - buffer[offset + 2] = (byte)(data >> 8); - buffer[offset + 1] = (byte)(data >> 16); - buffer[offset] = (byte)(data >> 24); + buffer[offset + 7] = (byte)(data); + buffer[offset + 6] = (byte)(data >> 8); + buffer[offset + 5] = (byte)(data >> 16); + buffer[offset + 4] = (byte)(data >> 24); + buffer[offset + 3] = (byte)(data >> 32); + buffer[offset + 2] = (byte)(data >> 40); + buffer[offset + 1] = (byte)(data >> 48); + buffer[offset ] = (byte)(data >> 56); #else - buffer[offset] = (byte)data; - buffer[offset + 1] = (byte)(data >> 8); - buffer[offset + 2] = (byte)(data >> 16); - buffer[offset + 3] = (byte)(data >> 24); + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); + buffer[offset + 4] = (byte)(data >> 32); + buffer[offset + 5] = (byte)(data >> 40); + buffer[offset + 6] = (byte)(data >> 48); + buffer[offset + 7] = (byte)(data >> 56); #endif - } + } - private static void WriteLittleEndian(IList buffer, int offset, short data) - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLittleEndian(byte[] buffer, int offset, int data) + { #if BIGENDIAN - buffer[offset + 1] = (byte)(data); - buffer[offset] = (byte)(data >> 8); + buffer[offset + 3] = (byte)(data); + buffer[offset + 2] = (byte)(data >> 8); + buffer[offset + 1] = (byte)(data >> 16); + buffer[offset ] = (byte)(data >> 24); #else - buffer[offset] = (byte)data; - buffer[offset + 1] = (byte)(data >> 8); + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); #endif - } - - public static void GetBytes(byte[] bytes, int startIndex, double value) - { - var ch = new ConverterHelperDouble { Adouble = value }; - WriteLittleEndian(bytes, startIndex, ch.Along); - } - - public static void GetBytes(byte[] bytes, int startIndex, float value) - { - var ch = new ConverterHelperFloat { Afloat = value }; - WriteLittleEndian(bytes, startIndex, ch.Aint); - } - - public static void GetBytes(IEnumerable bytes, int startIndex, short value) - { - WriteLittleEndian(bytes as IList, startIndex, value); - } - - public static void GetBytes(IEnumerable bytes, int startIndex, ushort value) - { - WriteLittleEndian(bytes as IList, startIndex, (short)value); - } - - public static void GetBytes(byte[] bytes, int startIndex, int value) - { - WriteLittleEndian(bytes, startIndex, value); - } - - public static void GetBytes(byte[] bytes, int startIndex, uint value) - { - WriteLittleEndian(bytes, startIndex, (int)value); - } - - public static void GetBytes(byte[] bytes, int startIndex, long value) - { - WriteLittleEndian(bytes, startIndex, (ulong)value); - } - - public static void GetBytes(byte[] bytes, int startIndex, ulong value) - { - WriteLittleEndian(bytes, startIndex, value); - } - - [StructLayout(LayoutKind.Explicit)] - private struct ConverterHelperDouble - { - [FieldOffset(0)] public ulong Along; - - [FieldOffset(0)] public double Adouble; - } + } - [StructLayout(LayoutKind.Explicit)] - private struct ConverterHelperFloat - { - [FieldOffset(0)] public int Aint; - - [FieldOffset(0)] public float Afloat; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLittleEndian(byte[] buffer, int offset, short data) + { +#if BIGENDIAN + buffer[offset + 1] = (byte)(data); + buffer[offset ] = (byte)(data >> 8); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, double value) + { + ConverterHelperDouble ch = new ConverterHelperDouble { Adouble = value }; + WriteLittleEndian(bytes, startIndex, ch.Along); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, float value) + { + ConverterHelperFloat ch = new ConverterHelperFloat { Afloat = value }; + WriteLittleEndian(bytes, startIndex, ch.Aint); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, short value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, ushort value) + { + WriteLittleEndian(bytes, startIndex, (short)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, int value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, uint value) + { + WriteLittleEndian(bytes, startIndex, (int)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, long value) + { + WriteLittleEndian(bytes, startIndex, (ulong)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, ulong value) + { + WriteLittleEndian(bytes, startIndex, value); + } +#endif } } diff --git a/NebulaModel/Networking/Serialization/NetDataReader.cs b/NebulaModel/Networking/Serialization/NetDataReader.cs index 3d0af0d39..ee986813b 100644 --- a/NebulaModel/Networking/Serialization/NetDataReader.cs +++ b/NebulaModel/Networking/Serialization/NetDataReader.cs @@ -1,672 +1,674 @@ -#region - -using System; +using System; using System.Net; -using System.Text; +using System.Runtime.CompilerServices; using NebulaAPI.Interfaces; -#endregion - -namespace NebulaModel.Networking.Serialization; - -public class NetDataReader : INetDataReader +namespace NebulaModel.Networking.Serialization { - public NetDataReader() + public class NetDataReader : INetDataReader { - } + protected byte[] _data; + protected int _position; + protected int _dataSize; + private int _offset; - public NetDataReader(NetDataWriter writer) - { - SetSource(writer); - } + public byte[] RawData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; + } + public int RawDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize; + } + public int UserDataOffset + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _offset; + } + public int UserDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _offset; + } + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data == null; + } + public int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; + } + public bool EndOfData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position == _dataSize; + } + public int AvailableBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _position; + } - public NetDataReader(byte[] source) - { - SetSource(source); - } + public void SkipBytes(int count) + { + _position += count; + } - public NetDataReader(byte[] source, int offset) - { - SetSource(source, offset); - } + public void SetPosition(int position) + { + _position = position; + } - public NetDataReader(byte[] source, int offset, int maxSize) - { - SetSource(source, offset, maxSize); - } + public void SetSource(INetDataWriter dataWriter) + { + _data = dataWriter.Data; + _position = 0; + _offset = 0; + _dataSize = dataWriter.Length; + } - private byte[] RawData { get; set; } + public void SetSource(byte[] source) + { + _data = source; + _position = 0; + _offset = 0; + _dataSize = source.Length; + } - private int RawDataSize { get; set; } + public void SetSource(byte[] source, int offset, int maxSize) + { + _data = source; + _position = offset; + _offset = offset; + _dataSize = maxSize; + } - private int UserDataOffset { get; set; } + public NetDataReader() + { - public int UserDataSize => RawDataSize - UserDataOffset; + } - public bool IsNull => RawData == null; + public NetDataReader(INetDataWriter writer) + { + SetSource(writer); + } - private int Position { get; set; } + public NetDataReader(byte[] source) + { + SetSource(source); + } - public bool EndOfData => Position == RawDataSize; + public NetDataReader(byte[] source, int offset, int maxSize) + { + SetSource(source, offset, maxSize); + } - public int AvailableBytes => RawDataSize - Position; + #region GetMethods + public IPEndPoint GetNetEndPoint() + { + string host = GetString(1000); + int port = GetInt(); + return NetUtils.MakeEndPoint(host, port); + } - public void SkipBytes(int count) - { - Position += count; - } + public byte GetByte() + { + byte res = _data[_position]; + _position++; + return res; + } - private void SetSource(NetDataWriter dataWriter) - { - RawData = dataWriter.Data; - Position = 0; - UserDataOffset = 0; - RawDataSize = dataWriter.Length; - } + public sbyte GetSByte() + { + return (sbyte)GetByte(); + } - private void SetSource(byte[] source) - { - RawData = source; - Position = 0; - UserDataOffset = 0; - RawDataSize = source.Length; - } + public T[] GetArray(ushort size) + { + ushort length = BitConverter.ToUInt16(_data, _position); + _position += 2; + T[] result = new T[length]; + length *= size; + Buffer.BlockCopy(_data, _position, result, 0, length); + _position += length; + return result; + } - private void SetSource(byte[] source, int offset) - { - RawData = source; - Position = offset; - UserDataOffset = offset; - RawDataSize = source.Length; - } + public bool[] GetBoolArray() + { + return GetArray(1); + } - private void SetSource(byte[] source, int offset, int maxSize) - { - RawData = source; - Position = offset; - UserDataOffset = offset; - RawDataSize = maxSize; - } + public ushort[] GetUShortArray() + { + return GetArray(2); + } - public void Clear() - { - Position = 0; - RawDataSize = 0; - RawData = null; - } + public short[] GetShortArray() + { + return GetArray(2); + } - #region GetMethods + public int[] GetIntArray() + { + return GetArray(4); + } - public IPEndPoint GetNetEndPoint() - { - var host = GetString(1000); - var port = GetInt(); - return NetUtils.MakeEndPoint(host, port); - } + public uint[] GetUIntArray() + { + return GetArray(4); + } - public byte GetByte() - { - var res = RawData[Position]; - Position += 1; - return res; - } + public float[] GetFloatArray() + { + return GetArray(4); + } - public sbyte GetSByte() - { - var b = (sbyte)RawData[Position]; - Position++; - return b; - } + public double[] GetDoubleArray() + { + return GetArray(8); + } - public bool[] GetBoolArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new bool[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size); - Position += size; - return arr; - } + public long[] GetLongArray() + { + return GetArray(8); + } - public ushort[] GetUShortArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new ushort[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 2); - Position += size * 2; - return arr; - } + public ulong[] GetULongArray() + { + return GetArray(8); + } - public short[] GetShortArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new short[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 2); - Position += size * 2; - return arr; - } + public string[] GetStringArray() + { + ushort length = GetUShort(); + string[] arr = new string[length]; + for (int i = 0; i < length; i++) + { + arr[i] = GetString(); + } + return arr; + } - public long[] GetLongArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new long[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 8); - Position += size * 8; - return arr; - } + /// + /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. + /// Strings that exceed this parameter are returned as empty + /// + public string[] GetStringArray(int maxStringLength) + { + ushort length = GetUShort(); + string[] arr = new string[length]; + for (int i = 0; i < length; i++) + { + arr[i] = GetString(maxStringLength); + } + return arr; + } - public ulong[] GetULongArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new ulong[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 8); - Position += size * 8; - return arr; - } + public bool GetBool() + { + return GetByte() == 1; + } - public int[] GetIntArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new int[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 4); - Position += size * 4; - return arr; - } + public char GetChar() + { + return (char)GetUShort(); + } - public uint[] GetUIntArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new uint[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 4); - Position += size * 4; - return arr; - } + public ushort GetUShort() + { + ushort result = BitConverter.ToUInt16(_data, _position); + _position += 2; + return result; + } - public float[] GetFloatArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new float[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 4); - Position += size * 4; - return arr; - } + public short GetShort() + { + short result = BitConverter.ToInt16(_data, _position); + _position += 2; + return result; + } - public double[] GetDoubleArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new double[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 8); - Position += size * 8; - return arr; - } + public long GetLong() + { + long result = BitConverter.ToInt64(_data, _position); + _position += 8; + return result; + } - public string[] GetStringArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new string[size]; - for (var i = 0; i < size; i++) + public ulong GetULong() { - arr[i] = GetString(); + ulong result = BitConverter.ToUInt64(_data, _position); + _position += 8; + return result; } - return arr; - } - public string[] GetStringArray(int maxStringLength) - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new string[size]; - for (var i = 0; i < size; i++) + public int GetInt() { - arr[i] = GetString(maxStringLength); + int result = BitConverter.ToInt32(_data, _position); + _position += 4; + return result; } - return arr; - } - public bool GetBool() - { - var res = RawData[Position] > 0; - Position += 1; - return res; - } + public uint GetUInt() + { + uint result = BitConverter.ToUInt32(_data, _position); + _position += 4; + return result; + } - public char GetChar() - { - var result = BitConverter.ToChar(RawData, Position); - Position += 2; - return result; - } + public float GetFloat() + { + float result = BitConverter.ToSingle(_data, _position); + _position += 4; + return result; + } - public ushort GetUShort() - { - var result = BitConverter.ToUInt16(RawData, Position); - Position += 2; - return result; - } + public double GetDouble() + { + double result = BitConverter.ToDouble(_data, _position); + _position += 8; + return result; + } - public short GetShort() - { - var result = BitConverter.ToInt16(RawData, Position); - Position += 2; - return result; - } + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + /// "string.Empty" if value > "maxLength" + public string GetString(int maxLength) + { + ushort size = GetUShort(); + if (size == 0) + { + return string.Empty; + } - public long GetLong() - { - var result = BitConverter.ToInt64(RawData, Position); - Position += 8; - return result; - } + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } - public ulong GetULong() - { - var result = BitConverter.ToUInt64(RawData, Position); - Position += 8; - return result; - } + ArraySegment data = GetBytesSegment(actualSize); - public int GetInt() - { - var result = BitConverter.ToInt32(RawData, Position); - Position += 4; - return result; - } + return (maxLength > 0 && NetDataWriter.uTF8Encoding.Value.GetCharCount(data.Array, data.Offset, data.Count) > maxLength) ? + string.Empty : + NetDataWriter.uTF8Encoding.Value.GetString(data.Array, data.Offset, data.Count); + } - public uint GetUInt() - { - var result = BitConverter.ToUInt32(RawData, Position); - Position += 4; - return result; - } + public string GetString() + { + ushort size = GetUShort(); + if (size == 0) + { + return string.Empty; + } - public float GetFloat() - { - var result = BitConverter.ToSingle(RawData, Position); - Position += 4; - return result; - } + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } - public double GetDouble() - { - var result = BitConverter.ToDouble(RawData, Position); - Position += 8; - return result; - } + ArraySegment data = GetBytesSegment(actualSize); - public string GetString(int maxLength) - { - var bytesCount = GetInt(); - if (bytesCount <= 0 || bytesCount > maxLength * 2) - { - return string.Empty; + return NetDataWriter.uTF8Encoding.Value.GetString(data.Array, data.Offset, data.Count); } - var charCount = Encoding.UTF8.GetCharCount(RawData, Position, bytesCount); - if (charCount > maxLength) + public ArraySegment GetBytesSegment(int count) { - return string.Empty; + ArraySegment segment = new ArraySegment(_data, _position, count); + _position += count; + return segment; } - var result = Encoding.UTF8.GetString(RawData, Position, bytesCount); - Position += bytesCount; - return result; - } - - public string GetString() - { - var bytesCount = GetInt(); - if (bytesCount <= 0) + public ArraySegment GetRemainingBytesSegment() { - return string.Empty; + ArraySegment segment = new ArraySegment(_data, _position, AvailableBytes); + _position = _data.Length; + return segment; } - var result = Encoding.UTF8.GetString(RawData, Position, bytesCount); - Position += bytesCount; - return result; - } - - public ArraySegment GetRemainingBytesSegment() - { - var segment = new ArraySegment(RawData, Position, AvailableBytes); - Position = RawData.Length; - return segment; - } - - public T Get() where T : INetSerializable, new() - { - var obj = new T(); - obj.Deserialize(this); - return obj; - } - - public byte[] GetRemainingBytes() - { - var outgoingData = new byte[AvailableBytes]; - Buffer.BlockCopy(RawData, Position, outgoingData, 0, AvailableBytes); - Position = RawData.Length; - return outgoingData; - } - - public void GetBytes(byte[] destination, int start, int count) - { - Buffer.BlockCopy(RawData, Position, destination, start, count); - Position += count; - } - - public void GetBytes(byte[] destination, int count) - { - Buffer.BlockCopy(RawData, Position, destination, 0, count); - Position += count; - } - - public sbyte[] GetSBytesWithLength() - { - var length = GetInt(); - var outgoingData = new sbyte[length]; - Buffer.BlockCopy(RawData, Position, outgoingData, 0, length); - Position += length; - return outgoingData; - } - - public byte[] GetBytesWithLength() - { - var length = GetInt(); - var outgoingData = new byte[length]; - Buffer.BlockCopy(RawData, Position, outgoingData, 0, length); - Position += length; - return outgoingData; - } + public T Get() where T : struct, INetSerializable + { + var obj = default(T); + obj.Deserialize(this); + return obj; + } - #endregion + public T Get(Func constructor) where T : class, INetSerializable + { + var obj = constructor(); + obj.Deserialize(this); + return obj; + } - #region PeekMethods + public byte[] GetRemainingBytes() + { + byte[] outgoingData = new byte[AvailableBytes]; + Buffer.BlockCopy(_data, _position, outgoingData, 0, AvailableBytes); + _position = _data.Length; + return outgoingData; + } - public byte PeekByte() - { - return RawData[Position]; - } + public void GetBytes(byte[] destination, int start, int count) + { + Buffer.BlockCopy(_data, _position, destination, start, count); + _position += count; + } - public sbyte PeekSByte() - { - return (sbyte)RawData[Position]; - } + public void GetBytes(byte[] destination, int count) + { + Buffer.BlockCopy(_data, _position, destination, 0, count); + _position += count; + } - public bool PeekBool() - { - return RawData[Position] > 0; - } + public sbyte[] GetSBytesWithLength() + { + return GetArray(1); + } - public char PeekChar() - { - return BitConverter.ToChar(RawData, Position); - } + public byte[] GetBytesWithLength() + { + return GetArray(1); + } + #endregion - public ushort PeekUShort() - { - return BitConverter.ToUInt16(RawData, Position); - } + #region PeekMethods - public short PeekShort() - { - return BitConverter.ToInt16(RawData, Position); - } + public byte PeekByte() + { + return _data[_position]; + } - public long PeekLong() - { - return BitConverter.ToInt64(RawData, Position); - } + public sbyte PeekSByte() + { + return (sbyte)_data[_position]; + } - public ulong PeekULong() - { - return BitConverter.ToUInt64(RawData, Position); - } + public bool PeekBool() + { + return _data[_position] == 1; + } - private int PeekInt() - { - return BitConverter.ToInt32(RawData, Position); - } + public char PeekChar() + { + return (char)PeekUShort(); + } - public uint PeekUInt() - { - return BitConverter.ToUInt32(RawData, Position); - } + public ushort PeekUShort() + { + return BitConverter.ToUInt16(_data, _position); + } - public float PeekFloat() - { - return BitConverter.ToSingle(RawData, Position); - } + public short PeekShort() + { + return BitConverter.ToInt16(_data, _position); + } - public double PeekDouble() - { - return BitConverter.ToDouble(RawData, Position); - } + public long PeekLong() + { + return BitConverter.ToInt64(_data, _position); + } - public string PeekString(int maxLength) - { - var bytesCount = BitConverter.ToInt32(RawData, Position); - if (bytesCount <= 0 || bytesCount > maxLength * 2) + public ulong PeekULong() { - return string.Empty; + return BitConverter.ToUInt64(_data, _position); } - var charCount = Encoding.UTF8.GetCharCount(RawData, Position + 4, bytesCount); - if (charCount > maxLength) + public int PeekInt() { - return string.Empty; + return BitConverter.ToInt32(_data, _position); } - var result = Encoding.UTF8.GetString(RawData, Position + 4, bytesCount); - return result; - } + public uint PeekUInt() + { + return BitConverter.ToUInt32(_data, _position); + } - public string PeekString() - { - var bytesCount = BitConverter.ToInt32(RawData, Position); - if (bytesCount <= 0) + public float PeekFloat() { - return string.Empty; + return BitConverter.ToSingle(_data, _position); } - var result = Encoding.UTF8.GetString(RawData, Position + 4, bytesCount); - return result; - } + public double PeekDouble() + { + return BitConverter.ToDouble(_data, _position); + } - #endregion + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + public string PeekString(int maxLength) + { + ushort size = PeekUShort(); + if (size == 0) + { + return string.Empty; + } - #region TryGetMethods + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } - public bool TryGetByte(out byte result) - { - if (AvailableBytes >= 1) - { - result = GetByte(); - return true; + return (maxLength > 0 && NetDataWriter.uTF8Encoding.Value.GetCharCount(_data, _position + 2, actualSize) > maxLength) ? + string.Empty : + NetDataWriter.uTF8Encoding.Value.GetString(_data, _position + 2, actualSize); } - result = 0; - return false; - } - public bool TryGetSByte(out sbyte result) - { - if (AvailableBytes >= 1) + public string PeekString() { - result = GetSByte(); - return true; + ushort size = PeekUShort(); + if (size == 0) + { + return string.Empty; + } + + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } + + return NetDataWriter.uTF8Encoding.Value.GetString(_data, _position + 2, actualSize); } - result = 0; - return false; - } + #endregion - public bool TryGetBool(out bool result) - { - if (AvailableBytes >= 1) + #region TryGetMethods + public bool TryGetByte(out byte result) { - result = GetBool(); - return true; + if (AvailableBytes >= 1) + { + result = GetByte(); + return true; + } + result = 0; + return false; } - result = false; - return false; - } - public bool TryGetChar(out char result) - { - if (AvailableBytes >= 2) + public bool TryGetSByte(out sbyte result) { - result = GetChar(); - return true; + if (AvailableBytes >= 1) + { + result = GetSByte(); + return true; + } + result = 0; + return false; } - result = '\0'; - return false; - } - public bool TryGetShort(out short result) - { - if (AvailableBytes >= 2) + public bool TryGetBool(out bool result) { - result = GetShort(); - return true; + if (AvailableBytes >= 1) + { + result = GetBool(); + return true; + } + result = false; + return false; } - result = 0; - return false; - } - private bool TryGetUShort(out ushort result) - { - if (AvailableBytes >= 2) + public bool TryGetChar(out char result) { - result = GetUShort(); + if (!TryGetUShort(out ushort uShortValue)) + { + result = '\0'; + return false; + } + result = (char)uShortValue; return true; } - result = 0; - return false; - } - public bool TryGetInt(out int result) - { - if (AvailableBytes >= 4) + public bool TryGetShort(out short result) { - result = GetInt(); - return true; + if (AvailableBytes >= 2) + { + result = GetShort(); + return true; + } + result = 0; + return false; } - result = 0; - return false; - } - public bool TryGetUInt(out uint result) - { - if (AvailableBytes >= 4) + public bool TryGetUShort(out ushort result) { - result = GetUInt(); - return true; + if (AvailableBytes >= 2) + { + result = GetUShort(); + return true; + } + result = 0; + return false; } - result = 0; - return false; - } - public bool TryGetLong(out long result) - { - if (AvailableBytes >= 8) + public bool TryGetInt(out int result) { - result = GetLong(); - return true; + if (AvailableBytes >= 4) + { + result = GetInt(); + return true; + } + result = 0; + return false; } - result = 0; - return false; - } - public bool TryGetULong(out ulong result) - { - if (AvailableBytes >= 8) + public bool TryGetUInt(out uint result) { - result = GetULong(); - return true; + if (AvailableBytes >= 4) + { + result = GetUInt(); + return true; + } + result = 0; + return false; } - result = 0; - return false; - } - public bool TryGetFloat(out float result) - { - if (AvailableBytes >= 4) + public bool TryGetLong(out long result) { - result = GetFloat(); - return true; + if (AvailableBytes >= 8) + { + result = GetLong(); + return true; + } + result = 0; + return false; } - result = 0; - return false; - } - public bool TryGetDouble(out double result) - { - if (AvailableBytes >= 8) + public bool TryGetULong(out ulong result) { - result = GetDouble(); - return true; + if (AvailableBytes >= 8) + { + result = GetULong(); + return true; + } + result = 0; + return false; } - result = 0; - return false; - } - private bool TryGetString(out string result) - { - if (AvailableBytes >= 4) + public bool TryGetFloat(out float result) { - var bytesCount = PeekInt(); - if (AvailableBytes >= bytesCount + 4) + if (AvailableBytes >= 4) { - result = GetString(); + result = GetFloat(); return true; } + result = 0; + return false; } - result = null; - return false; - } - public bool TryGetStringArray(out string[] result) - { - if (!TryGetUShort(out var size)) + public bool TryGetDouble(out double result) { - result = null; + if (AvailableBytes >= 8) + { + result = GetDouble(); + return true; + } + result = 0; return false; } - result = new string[size]; - for (var i = 0; i < size; i++) + public bool TryGetString(out string result) { - if (TryGetString(out result[i])) + if (AvailableBytes >= 2) { - continue; + ushort strSize = PeekUShort(); + if (AvailableBytes >= strSize + 1) + { + result = GetString(); + return true; + } } result = null; return false; } - return true; - } + public bool TryGetStringArray(out string[] result) + { + if (!TryGetUShort(out ushort strArrayLength)) { + result = null; + return false; + } - public bool TryGetBytesWithLength(out byte[] result) - { - if (AvailableBytes >= 4) + result = new string[strArrayLength]; + for (int i = 0; i < strArrayLength; i++) + { + if (!TryGetString(out result[i])) + { + result = null; + return false; + } + } + + return true; + } + + public bool TryGetBytesWithLength(out byte[] result) { - var length = PeekInt(); - if (length >= 0 && AvailableBytes >= length + 4) + if (AvailableBytes >= 2) { - result = GetBytesWithLength(); - return true; + ushort length = PeekUShort(); + if (length >= 0 && AvailableBytes >= 2 + length) + { + result = GetBytesWithLength(); + return true; + } } + result = null; + return false; } - result = null; - return false; - } + #endregion - #endregion + public void Clear() + { + _position = 0; + _dataSize = 0; + _data = null; + } + } } diff --git a/NebulaModel/Networking/Serialization/NetDataReaderExtension.cs b/NebulaModel/Networking/Serialization/NetDataReaderExtension.cs new file mode 100644 index 000000000..9d78e1bd6 --- /dev/null +++ b/NebulaModel/Networking/Serialization/NetDataReaderExtension.cs @@ -0,0 +1,19 @@ + +using NebulaAPI.Interfaces; + +namespace NebulaModel.Networking.Serialization; + +public static class NetDataReaderExtension +{ + /// + /// https://github.com/RevenantX/LiteNetLib/commit/2f9eefcbcec9d3f8243d2d8d5e757d2133aafcbe + /// This was removed in above commit for what seems to be performance reasons. Currently only used by `NebulaModel.DataStructures.PlayerData` + /// @TODO: Investigate if it proves necessary. + /// + public static T Get(this NetDataReader reader) where T : class, INetSerializable, new() + { + var obj = new T(); + obj.Deserialize(reader); + return obj; + } +} diff --git a/NebulaModel/Networking/Serialization/NetDataWriter.cs b/NebulaModel/Networking/Serialization/NetDataWriter.cs index 74af34c20..c0acc0409 100644 --- a/NebulaModel/Networking/Serialization/NetDataWriter.cs +++ b/NebulaModel/Networking/Serialization/NetDataWriter.cs @@ -1,464 +1,382 @@ -#region - -using System; -using System.Collections.Generic; +using System; using System.Net; +using System.Runtime.CompilerServices; using System.Text; +using System.Threading; using NebulaAPI.Interfaces; -#endregion - -namespace NebulaModel.Networking.Serialization; - -public class NetDataWriter : INetDataWriter +namespace NebulaModel.Networking.Serialization { - private const int InitialSize = 64; - private readonly bool _autoResize; - private byte[] _data; - - public NetDataWriter() : this(true) + public class NetDataWriter : INetDataWriter { - } - - private NetDataWriter(bool autoResize, int initialSize = InitialSize) - { - _data = new byte[initialSize]; - _autoResize = autoResize; - } - - public int Capacity => _data.Length; - - public byte[] Data => _data; - - public int Length { get; set; } + protected byte[] _data; + protected int _position; + private const int InitialSize = 64; + private readonly bool _autoResize; - public void Put(float value) - { - if (_autoResize) + public int Capacity { - ResizeIfNeed(Length + 4); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data.Length; } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 4; - } - - public void Put(double value) - { - if (_autoResize) + public byte[] Data { - ResizeIfNeed(Length + 8); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 8; - } - - public void Put(long value) - { - if (_autoResize) + public int Length { - ResizeIfNeed(Length + 8); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; } - FastBitConverter.GetBytes(_data, Length, value); - Length += 8; - } + public static readonly ThreadLocal uTF8Encoding = new ThreadLocal(() => new UTF8Encoding(false, true)); + public const int StringBufferMaxLength = 65535; + private readonly byte[] _stringBuffer = new byte[StringBufferMaxLength]; - public void Put(ulong value) - { - if (_autoResize) + public NetDataWriter() : this(true, InitialSize) { - ResizeIfNeed(Length + 8); } - FastBitConverter.GetBytes(_data, Length, value); - Length += 8; - } - - public void Put(int value) - { - if (_autoResize) + public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) { - ResizeIfNeed(Length + 4); } - FastBitConverter.GetBytes(_data, Length, value); - Length += 4; - } - - public void Put(uint value) - { - if (_autoResize) + public NetDataWriter(bool autoResize, int initialSize) { - ResizeIfNeed(Length + 4); + _data = new byte[initialSize]; + _autoResize = autoResize; } - FastBitConverter.GetBytes(_data, Length, value); - Length += 4; - } - - public void Put(char value) - { - if (_autoResize) + /// + /// Creates NetDataWriter from existing ByteArray + /// + /// Source byte array + /// Copy array to new location or use existing + public static NetDataWriter FromBytes(byte[] bytes, bool copy) { - ResizeIfNeed(Length + 2); + if (copy) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes); + return netDataWriter; + } + return new NetDataWriter(true, 0) {_data = bytes, _position = bytes.Length}; } - FastBitConverter.GetBytes(_data as IEnumerable, Length, value); - Length += 2; - } - - public void Put(ushort value) - { - if (_autoResize) + /// + /// Creates NetDataWriter from existing ByteArray (always copied data) + /// + /// Source byte array + /// Offset of array + /// Length of array + public static NetDataWriter FromBytes(byte[] bytes, int offset, int length) { - ResizeIfNeed(Length + 2); + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes, offset, length); + return netDataWriter; } - FastBitConverter.GetBytes(_data as IEnumerable, Length, value); - Length += 2; - } - - public void Put(short value) - { - if (_autoResize) + public static NetDataWriter FromString(string value) { - ResizeIfNeed(Length + 2); + var netDataWriter = new NetDataWriter(); + netDataWriter.Put(value); + return netDataWriter; } - FastBitConverter.GetBytes(_data as IEnumerable, Length, value); - Length += 2; - } - - public void Put(sbyte value) - { - if (_autoResize) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResizeIfNeed(int newSize) { - ResizeIfNeed(Length + 1); + if (_data.Length < newSize) + { + Array.Resize(ref _data, Math.Max(newSize, _data.Length * 2)); + } } - _data[Length] = (byte)value; - Length++; - } - - public void Put(byte value) - { - if (_autoResize) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EnsureFit(int additionalSize) { - ResizeIfNeed(Length + 1); + if (_data.Length < _position + additionalSize) + { + Array.Resize(ref _data, Math.Max(_position + additionalSize, _data.Length * 2)); + } } - _data[Length] = value; - Length++; - } - - public void Put(byte[] data, int offset, int length) - { - if (_autoResize) + public void Reset(int size) { - ResizeIfNeed(Length + length); + ResizeIfNeed(size); + _position = 0; } - Buffer.BlockCopy(data, offset, _data, Length, length); - Length += length; - } - - public void Put(byte[] data) - { - if (_autoResize) + public void Reset() { - ResizeIfNeed(Length + data.Length); + _position = 0; } - Buffer.BlockCopy(data, 0, _data, Length, data.Length); - Length += data.Length; - } - - public void PutSBytesWithLength(sbyte[] data, int offset, int length) - { - if (_autoResize) + public byte[] CopyData() { - ResizeIfNeed(Length + length + 4); + byte[] resultData = new byte[_position]; + Buffer.BlockCopy(_data, 0, resultData, 0, _position); + return resultData; } - FastBitConverter.GetBytes(_data, Length, length); - Buffer.BlockCopy(data, offset, _data, Length + 4, length); - Length += length + 4; - } - - public void PutSBytesWithLength(sbyte[] data) - { - if (_autoResize) + /// + /// Sets position of NetDataWriter to rewrite previous values + /// + /// new byte position + /// previous position of data writer + public int SetPosition(int position) { - ResizeIfNeed(Length + data.Length + 4); + int prevPosition = _position; + _position = position; + return prevPosition; } - FastBitConverter.GetBytes(_data, Length, data.Length); - Buffer.BlockCopy(data, 0, _data, Length + 4, data.Length); - Length += data.Length + 4; - } - - public void PutBytesWithLength(byte[] data, int offset, int length) - { - if (_autoResize) + public void Put(float value) { - ResizeIfNeed(Length + length + 4); + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; } - FastBitConverter.GetBytes(_data, Length, length); - Buffer.BlockCopy(data, offset, _data, Length + 4, length); - Length += length + 4; - } - - public void PutBytesWithLength(byte[] data) - { - if (_autoResize) + public void Put(double value) { - ResizeIfNeed(Length + data.Length + 4); + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; } - FastBitConverter.GetBytes(_data, Length, data.Length); - Buffer.BlockCopy(data, 0, _data, Length + 4, data.Length); - Length += data.Length + 4; - } - - public void Put(bool value) - { - if (_autoResize) + public void Put(long value) { - ResizeIfNeed(Length + 1); + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; } - _data[Length] = (byte)(value ? 1 : 0); - Length++; - } - - public void PutArray(float[] value) - { - PutArray(value, 4); - } + public void Put(ulong value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } - public void PutArray(double[] value) - { - PutArray(value, 8); - } + public void Put(int value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } - public void PutArray(long[] value) - { - PutArray(value, 8); - } + public void Put(uint value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } - public void PutArray(ulong[] value) - { - PutArray(value, 8); - } + public void Put(char value) + { + Put((ushort)value); + } - public void PutArray(int[] value) - { - PutArray(value, 4); - } + public void Put(ushort value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } - public void PutArray(uint[] value) - { - PutArray(value, 4); - } + public void Put(short value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } - public void PutArray(ushort[] value) - { - PutArray(value, 2); - } + public void Put(sbyte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = (byte)value; + _position++; + } - public void PutArray(short[] value) - { - PutArray(value, 2); - } + public void Put(byte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = value; + _position++; + } - public void PutArray(bool[] value) - { - PutArray(value, 1); - } + public void Put(byte[] data, int offset, int length) + { + if (_autoResize) + ResizeIfNeed(_position + length); + Buffer.BlockCopy(data, offset, _data, _position, length); + _position += length; + } - public void PutArray(string[] value) - { - var len = value == null ? (ushort)0 : (ushort)value.Length; - Put(len); - for (var i = 0; i < len; i++) + public void Put(byte[] data) { - if (value != null) - { - Put(value[i]); - } + if (_autoResize) + ResizeIfNeed(_position + data.Length); + Buffer.BlockCopy(data, 0, _data, _position, data.Length); + _position += data.Length; } - } - public void PutArray(string[] value, int maxLength) - { - var len = value == null ? (ushort)0 : (ushort)value.Length; - Put(len); - for (var i = 0; i < len; i++) + public void PutSBytesWithLength(sbyte[] data, int offset, ushort length) { - if (value != null) - { - Put(value[i], maxLength); - } + if (_autoResize) + ResizeIfNeed(_position + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + 2, length); + _position += 2 + length; } - } - public void Put(IPEndPoint endPoint) - { - Put(endPoint.Address.ToString()); - Put(endPoint.Port); - } + public void PutSBytesWithLength(sbyte[] data) + { + PutArray(data, 1); + } - public void Put(string value) - { - if (string.IsNullOrEmpty(value)) + public void PutBytesWithLength(byte[] data, int offset, ushort length) { - Put(0); - return; + if (_autoResize) + ResizeIfNeed(_position + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + 2, length); + _position += 2 + length; } - //put bytes count - var bytesCount = Encoding.UTF8.GetByteCount(value); - if (_autoResize) + public void PutBytesWithLength(byte[] data) { - ResizeIfNeed(Length + bytesCount + 4); + PutArray(data, 1); } - Put(bytesCount); + public void Put(bool value) + { + Put((byte)(value ? 1 : 0)); + } - //put string - Encoding.UTF8.GetBytes(value, 0, value.Length, _data, Length); - Length += bytesCount; - } + public void PutArray(Array arr, int sz) + { + ushort length = arr == null ? (ushort) 0 : (ushort)arr.Length; + sz *= length; + if (_autoResize) + ResizeIfNeed(_position + sz + 2); + FastBitConverter.GetBytes(_data, _position, length); + if (arr != null) + Buffer.BlockCopy(arr, 0, _data, _position + 2, sz); + _position += sz + 2; + } - public void Put(string value, int maxLength) - { - if (string.IsNullOrEmpty(value)) + public void PutArray(float[] value) { - Put(0); - return; + PutArray(value, 4); } - var length = value.Length > maxLength ? maxLength : value.Length; - //calculate max count - var bytesCount = Encoding.UTF8.GetByteCount(value); - if (_autoResize) + public void PutArray(double[] value) { - ResizeIfNeed(Length + bytesCount + 4); + PutArray(value, 8); } - //put bytes count - Put(bytesCount); + public void PutArray(long[] value) + { + PutArray(value, 8); + } - //put string - Encoding.UTF8.GetBytes(value, 0, length, _data, Length); + public void PutArray(ulong[] value) + { + PutArray(value, 8); + } - Length += bytesCount; - } + public void PutArray(int[] value) + { + PutArray(value, 4); + } - public void Put(T obj) where T : INetSerializable - { - obj.Serialize(this); - } + public void PutArray(uint[] value) + { + PutArray(value, 4); + } - /// - /// Creates NetDataWriter from existing ByteArray - /// - /// Source byte array - /// Copy array to new location or use existing - public static NetDataWriter FromBytes(byte[] bytes, bool copy) - { - if (!copy) + public void PutArray(ushort[] value) { - return new NetDataWriter(true, 0) { _data = bytes, Length = bytes.Length }; + PutArray(value, 2); } - var netDataWriter = new NetDataWriter(true, bytes.Length); - netDataWriter.Put(bytes); - return netDataWriter; - } - /// - /// Creates NetDataWriter from existing ByteArray (always copied data) - /// - /// Source byte array - /// Offset of array - /// Length of array - public static NetDataWriter FromBytes(byte[] bytes, int offset, int length) - { - var netDataWriter = new NetDataWriter(true, bytes.Length); - netDataWriter.Put(bytes, offset, length); - return netDataWriter; - } + public void PutArray(short[] value) + { + PutArray(value, 2); + } - public static NetDataWriter FromString(string value) - { - var netDataWriter = new NetDataWriter(); - netDataWriter.Put(value); - return netDataWriter; - } + public void PutArray(bool[] value) + { + PutArray(value, 1); + } - private void ResizeIfNeed(int newSize) - { - var len = _data.Length; - if (len >= newSize) + public void PutArray(string[] value) { - return; + ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + Put(value[i]); } - while (len < newSize) + + public void PutArray(string[] value, int strMaxLength) { - len *= 2; + ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + Put(value[i], strMaxLength); } - Array.Resize(ref _data, len); - } + public void Put(IPEndPoint endPoint) + { + Put(endPoint.Address.ToString()); + Put(endPoint.Port); + } - public void Reset(int size) - { - ResizeIfNeed(size); - Length = 0; - } + public void Put(string value) + { + Put(value, 0); + } - public void Reset() - { - Length = 0; - } + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + public void Put(string value, int maxLength) + { + if (string.IsNullOrEmpty(value)) + { + Put((ushort)0); + return; + } - public byte[] CopyData() - { - var resultData = new byte[Length]; - Buffer.BlockCopy(_data, 0, resultData, 0, Length); - return resultData; - } + int length = maxLength > 0 && value.Length > maxLength ? maxLength : value.Length; + int size = uTF8Encoding.Value.GetBytes(value, 0, length, _stringBuffer, 0); - /// - /// Sets position of NetDataWriter to rewrite previous values - /// - /// new byte position - /// previous position of data writer - public int SetPosition(int position) - { - var prevPosition = Length; - Length = position; - return prevPosition; - } + if (size == 0 || size >= StringBufferMaxLength) + { + Put((ushort)0); + return; + } - private void PutArray(Array arr, int sz) - { - var length = arr == null ? (ushort)0 : (ushort)arr.Length; - sz *= length; - if (_autoResize) - { - ResizeIfNeed(Length + sz + 2); + Put(checked((ushort)(size + 1))); + Put(_stringBuffer, 0, size); } - FastBitConverter.GetBytes(_data as IEnumerable, Length, length); - if (arr != null) + public void Put(T obj) where T : INetSerializable { - Buffer.BlockCopy(arr, 0, _data, Length + 2, sz); + obj.Serialize(this); } - - Length += sz + 2; } } diff --git a/NebulaModel/Networking/Serialization/NetPacketProcessor.cs b/NebulaModel/Networking/Serialization/NetPacketProcessor.cs index 327a66d67..7bdc9653b 100644 --- a/NebulaModel/Networking/Serialization/NetPacketProcessor.cs +++ b/NebulaModel/Networking/Serialization/NetPacketProcessor.cs @@ -1,434 +1,268 @@ -#region - -using System; +using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using NebulaAPI.Interfaces; -using NebulaAPI.Packets; -using NebulaModel.Logger; - -#endregion - -namespace NebulaModel.Networking.Serialization; -public class NetPacketProcessor +namespace NebulaModel.Networking.Serialization { - private readonly Dictionary _callbacks = []; - private readonly Dictionary _callbacksDebugInfo = []; - private readonly NetDataWriter _netDataWriter = new(); - private readonly NetSerializer _netSerializer; - private readonly List delayedPackets = []; - private readonly Queue pendingPackets = new(); - - private readonly Random simulationRandom = new(); - private readonly int SimulatedMaxLatency = 50; - private readonly int SimulatedMinLatency = 20; - - public bool SimulateLatency = false; - - public NetPacketProcessor() - { - _netSerializer = new NetSerializer(); - } - - public NetPacketProcessor(int maxStringLength) + public partial class NetPacketProcessor { - _netSerializer = new NetSerializer(maxStringLength); - } - - public bool Enable { get; set; } = true; - - public void EnqueuePacketForProcessing(byte[] rawData, object userData) - { -#if DEBUG - if (SimulateLatency) + private static class HashCache { - lock (delayedPackets) + public static readonly ulong Id; + + //FNV-1 64 bit hash + static HashCache() { - var packet = new PendingPacket(rawData, userData); - var dueTime = DateTime.UtcNow.AddMilliseconds(simulationRandom.Next(SimulatedMinLatency, SimulatedMaxLatency)); - delayedPackets.Add(new DelayedPacket(packet, dueTime)); + ulong hash = 14695981039346656037UL; //offset + string typeName = typeof(T).ToString(); + for (var i = 0; i < typeName.Length; i++) + { + hash ^= typeName[i]; + hash *= 1099511628211UL; //prime + } + Id = hash; } } - else + + protected delegate void SubscribeDelegate(NetDataReader reader, object userData); + private readonly NetSerializer _netSerializer; + private readonly Dictionary _callbacks = new Dictionary(); + + public NetPacketProcessor() { - lock (pendingPackets) - { - pendingPackets.Enqueue(new PendingPacket(rawData, userData)); - } + _netSerializer = new NetSerializer(); } -#else - lock (pendingPackets) + + public NetPacketProcessor(int maxStringLength) { - pendingPackets.Enqueue(new PendingPacket(rawData, userData)); + _netSerializer = new NetSerializer(maxStringLength); } -#endif - } - public void ProcessPacketQueue() - { - lock (pendingPackets) + protected virtual ulong GetHash() { - ProcessDelayedPackets(); - - while (pendingPackets.Count > 0 && Enable) - { - var packet = pendingPackets.Dequeue(); - ReadPacket(new NetDataReader(packet.Data), packet.UserData); - } + return HashCache.Id; } - } - [Conditional("DEBUG")] - private void ProcessDelayedPackets() - { - lock (delayedPackets) + protected virtual SubscribeDelegate GetCallbackFromData(NetDataReader reader) { - var now = DateTime.UtcNow; - var deleteCount = 0; - - for (var i = 0; i < delayedPackets.Count; ++i) - { - if (now >= delayedPackets[i].DueTime) - { - pendingPackets.Enqueue(delayedPackets[i].Packet); - deleteCount = i + 1; - } - else - { - // We need to break to avoid messing up the order of the packets. - break; - } - } - - if (deleteCount > 0) + ulong hash = reader.GetULong(); + if (!_callbacks.TryGetValue(hash, out var action)) { - delayedPackets.RemoveRange(0, deleteCount); + throw new ParseException("Undefined packet in NetDataReader"); } + return action; } - } - //FNV-1 64 bit hash - protected virtual ulong GetHash() - { - if (HashCache.Initialized) + protected virtual void WriteHash(NetDataWriter writer) { - return HashCache.Id; + writer.Put(GetHash()); } - var hash = 14695981039346656037UL; //offset - var typeName = typeof(T).FullName; - if (typeName != null) + /// + /// Register nested property type + /// + /// INetSerializable structure + public void RegisterNestedType() where T : struct, INetSerializable { - foreach (var t in typeName) - { - hash ^= t; - hash *= 1099511628211UL; //prime - } + _netSerializer.RegisterNestedType(); } - HashCache.Initialized = true; - HashCache.Id = hash; - return hash; - } - protected virtual SubscribeDelegate GetCallbackFromData(NetDataReader reader) - { - var hash = reader.GetULong(); - if (!_callbacks.TryGetValue(hash, out var action)) + /// + /// Register nested property type + /// + /// + /// + public void RegisterNestedType(Action writeDelegate, Func readDelegate) { - Log.Warn($"Unknown packet hash: {hash}"); - throw new Exception("Undefined packet in NetDataReader"); + _netSerializer.RegisterNestedType(writeDelegate, readDelegate); } -#if DEBUG - if (_callbacksDebugInfo.TryGetValue(hash, out var packetType)) + /// + /// Register nested property type + /// + /// INetSerializable class + public void RegisterNestedType(Func constructor) where T : class, INetSerializable { - if (!packetType.IsDefined(typeof(HidePacketInDebugLogsAttribute), false)) - { - Log.Debug($"Packet Received: {packetType.Name}"); - } + _netSerializer.RegisterNestedType(constructor); } - else + + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + public void ReadAllPackets(NetDataReader reader) { - Log.Warn($"Packet not registered: {hash}"); + while (reader.AvailableBytes > 0) + ReadPacket(reader); } -#endif - - return action; - } - - protected virtual void WriteHash(NetDataWriter writer) - { - writer.Put(GetHash()); - } - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Produces error when static")] - public T CreateNestedClassInstance() where T : class, INetSerializable, new() - { - return new T(); - } - - /// - /// Register nested property type - /// - /// INetSerializable structure - public void RegisterNestedType() where T : struct, INetSerializable - { - _netSerializer.RegisterNestedType(); - } - - /// - /// Register nested property type - /// - /// - /// - public void RegisterNestedType(Action writeDelegate, Func readDelegate) - { - _netSerializer.RegisterNestedType(writeDelegate, readDelegate); - } - - /// - /// Register nested property type - /// - /// INetSerializable class - public void RegisterNestedType(Func constructor) where T : class, INetSerializable - { - _netSerializer.RegisterNestedType(constructor); - } - - /// - /// Reads all available data from NetDataReader and calls OnReceive delegates - /// - /// NetDataReader with packets data - public void ReadAllPackets(NetDataReader reader) - { - while (reader.AvailableBytes > 0) + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadAllPackets(NetDataReader reader, object userData) { - ReadPacket(reader); + while (reader.AvailableBytes > 0) + ReadPacket(reader, userData); } - } - /// - /// Reads all available data from NetDataReader and calls OnReceive delegates - /// - /// NetDataReader with packets data - /// Argument that passed to OnReceivedEvent - /// Malformed packet - public void ReadAllPackets(NetDataReader reader, object userData) - { - while (reader.AvailableBytes > 0) + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Malformed packet + public void ReadPacket(NetDataReader reader) { - ReadPacket(reader, userData); + ReadPacket(reader, null); } - } - /* public void Send(NetPeer peer, T packet, DeliveryMethod options) where T : class, new() - { - _netDataWriter.Reset(); - Write(_netDataWriter, packet); - peer.Send(_netDataWriter, options); - } - - public void SendNetSerializable(NetPeer peer, T packet, DeliveryMethod options) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteNetSerializable(_netDataWriter, packet); - peer.Send(_netDataWriter, options); - } - - public void Send(NetManager manager, T packet, DeliveryMethod options) where T : class, new() - { - _netDataWriter.Reset(); - Write(_netDataWriter, packet); - manager.SendToAll(_netDataWriter, options); - } - - public void SendNetSerializable(NetManager manager, T packet, DeliveryMethod options) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteNetSerializable(_netDataWriter, packet); - manager.SendToAll(_netDataWriter, options); - }*/ - - public void Write(NetDataWriter writer, T packet) where T : class, new() - { - WriteHash(writer); - _netSerializer.Serialize(writer, packet); - } - - public void WriteNetSerializable(NetDataWriter writer, T packet) where T : INetSerializable - { - WriteHash(writer); - packet.Serialize(writer); - } - - public byte[] Write(T packet) where T : class, new() - { -#if DEBUG - if (!typeof(T).IsDefined(typeof(HidePacketInDebugLogsAttribute), false)) + public void Write(NetDataWriter writer, T packet) where T : class, new() { - Log.Debug($"Packet Sent: {packet.GetType().Name}"); + WriteHash(writer); + _netSerializer.Serialize(writer, packet); } -#endif - _netDataWriter.Reset(); - WriteHash(_netDataWriter); - _netSerializer.Serialize(_netDataWriter, packet); - return _netDataWriter.CopyData(); - } - - public byte[] WriteNetSerializable(T packet) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteHash(_netDataWriter); - packet.Serialize(_netDataWriter); - return _netDataWriter.CopyData(); - } - /// - /// Reads one packet from NetDataReader and calls OnReceive delegate - /// - /// NetDataReader with packet - /// Argument that passed to OnReceivedEvent - /// Malformed packet - private void ReadPacket(NetDataReader reader, object userData = null) - { - GetCallbackFromData(reader)(reader, userData); - } - - /// - /// Register and subscribe to packet receive event - /// - /// event that will be called when packet deserialized with ReadPacket method - /// Method that constructs packet instead of slow Activator.CreateInstance - /// 's fields are not supported, or it has no fields - public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() - { - _netSerializer.Register(); - _callbacks[GetHash()] = (reader, userData) => + public void WriteNetSerializable(NetDataWriter writer, ref T packet) where T : INetSerializable { - var reference = packetConstructor(); - _netSerializer.Deserialize(reader, reference); - onReceive(reference); - }; - } + WriteHash(writer); + packet.Serialize(writer); + } - /// - /// Register and subscribe to packet receive event (with userData) - /// - /// event that will be called when packet deserialized with ReadPacket method - /// Method that constructs packet instead of slow Activator.CreateInstance - /// 's fields are not supported, or it has no fields - public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() - { - _netSerializer.Register(); - _callbacks[GetHash()] = (reader, userData) => + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadPacket(NetDataReader reader, object userData) { - var reference = packetConstructor(); - _netSerializer.Deserialize(reader, reference); - onReceive(reference, (TUserData)userData); - }; - } + GetCallbackFromData(reader)(reader, userData); + } - /// - /// Register and subscribe to packet receive event - /// This method will overwrite last received packet class on receive (less garbage) - /// - /// event that will be called when packet deserialized with ReadPacket method - /// 's fields are not supported, or it has no fields - public void SubscribeReusable(Action onReceive) where T : class, new() - { - _netSerializer.Register(); - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => + /// + /// Register and subscribe to packet receive event + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet instead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() { - _netSerializer.Deserialize(reader, reference); - onReceive(reference); - }; - } + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + } - /// - /// Register and subscribe to packet receive event - /// This method will overwrite last received packet class on receive (less garbage) - /// - /// event that will be called when packet deserialized with ReadPacket method - /// 's fields are not supported, or it has no fields - public void SubscribeReusable(Action onReceive) where T : class, new() - { - _netSerializer.Register(); - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => + /// + /// Register and subscribe to packet receive event (with userData) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet instead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() { - _netSerializer.Deserialize(reader, reference); - onReceive(reference, (TUserData)userData); - }; + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + } -#if DEBUG - _callbacksDebugInfo[GetHash()] = typeof(T); -#endif - } + /// + /// Register and subscribe to packet receive event + /// This method will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable(Action onReceive) where T : class, new() + { + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + } - public void SubscribeNetSerializable( - Action onReceive, - Func packetConstructor) where T : INetSerializable - { - _callbacks[GetHash()] = (reader, userData) => + /// + /// Register and subscribe to packet receive event + /// This method will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable(Action onReceive) where T : class, new() { - var pkt = packetConstructor(); - pkt.Deserialize(reader); - onReceive(pkt, (TUserData)userData); - }; - } + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + } - public void SubscribeNetSerializable( - Action onReceive, - Func packetConstructor) where T : INetSerializable - { - _callbacks[GetHash()] = (reader, userData) => + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable { - var pkt = packetConstructor(); - pkt.Deserialize(reader); - onReceive(pkt); - }; - } + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt, (TUserData)userData); + }; + } - public void SubscribeNetSerializable( - Action onReceive) where T : INetSerializable, new() - { - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable { - reference.Deserialize(reader); - onReceive(reference, (TUserData)userData); - }; - } + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt); + }; + } - public void SubscribeNetSerializable( - Action onReceive) where T : INetSerializable, new() - { - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() { - reference.Deserialize(reader); - onReceive(reference); - }; - } + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference, (TUserData)userData); + }; + } - /// - /// Remove any subscriptions by type - /// - /// Packet type - /// true if remove is success - public bool RemoveSubscription() - { - return _callbacks.Remove(GetHash()); - } + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() + { + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference); + }; + } - private static class HashCache - { - public static bool Initialized; - public static ulong Id; + /// + /// Remove any subscriptions by type + /// + /// Packet type + /// true if remove is success + public bool RemoveSubscription() + { + return _callbacks.Remove(GetHash()); + } } - - protected delegate void SubscribeDelegate(NetDataReader reader, object userData); } diff --git a/NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs b/NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs new file mode 100644 index 000000000..f95b66736 --- /dev/null +++ b/NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using NebulaAPI.Interfaces; + +namespace NebulaModel.Networking.Serialization; + +public partial class NetPacketProcessor +{ + // Packet simulation stuff + private readonly Dictionary _callbacksDebugInfo = []; + private readonly NetDataWriter _netDataWriter = new(); + private readonly List delayedPackets = []; + private readonly Queue pendingPackets = new(); + + private readonly Random simulationRandom = new(); + private readonly int SimulatedMaxLatency = 50; + private readonly int SimulatedMinLatency = 20; + + public bool SimulateLatency = false; + + /// + /// Whether or not packet processing is enabled + /// + public bool Enable { get; set; } = true; + + /// + /// + /// + private static readonly NetDataWriter writer = new(); + + /// + /// Adds back some functionality that nebula relied on before the update. + /// This method was removed from LiteNetLib as it was not thread-safe, and is still not thread safe in below implementation. + /// @TODO: Optimize & move into `NebulaConnection.cs` + /// + public byte[] Write(T packet) where T : class, new() + { + writer.Reset(); + Write(writer, packet); + return writer.CopyData(); + } + + #region DEBUG_PACKET_DELAY + + public void ProcessPacketQueue() + { + lock (pendingPackets) + { + ProcessDelayedPackets(); + + while (pendingPackets.Count > 0 && Enable) + { + var packet = pendingPackets.Dequeue(); + ReadPacket(new NetDataReader(packet.Data), packet.UserData); + } + } + } + + [Conditional("DEBUG")] + private void ProcessDelayedPackets() + { + lock (delayedPackets) + { + var now = DateTime.UtcNow; + var deleteCount = 0; + + for (var i = 0; i < delayedPackets.Count; ++i) + { + if (now >= delayedPackets[i].DueTime) + { + pendingPackets.Enqueue(delayedPackets[i].Packet); + deleteCount = i + 1; + } + else + { + // We need to break to avoid messing up the order of the packets. + break; + } + } + + if (deleteCount > 0) + { + delayedPackets.RemoveRange(0, deleteCount); + } + } + } + + public void EnqueuePacketForProcessing(byte[] rawData, object userData) + { +#if DEBUG + if (SimulateLatency) + { + lock (delayedPackets) + { + var packet = new PendingPacket(rawData, userData); + var dueTime = DateTime.UtcNow.AddMilliseconds(simulationRandom.Next(SimulatedMinLatency, SimulatedMaxLatency)); + delayedPackets.Add(new DelayedPacket(packet, dueTime)); + } + } + else + { + lock (pendingPackets) + { + pendingPackets.Enqueue(new PendingPacket(rawData, userData)); + } + } +#else + lock (pendingPackets) + { + pendingPackets.Enqueue(new PendingPacket(rawData, userData)); + } +#endif + } + + #endregion +} + +public static class NetPacketProcessorExtension +{ +} diff --git a/NebulaModel/Networking/Serialization/NetSerializer.cs b/NebulaModel/Networking/Serialization/NetSerializer.cs index 2771371de..24ae5c03a 100644 --- a/NebulaModel/Networking/Serialization/NetSerializer.cs +++ b/NebulaModel/Networking/Serialization/NetSerializer.cs @@ -1,932 +1,739 @@ -#region - -using System; +using System; using System.Collections.Generic; using System.Net; using System.Reflection; +using System.Runtime.Serialization; using NebulaAPI.Interfaces; -#endregion - -namespace NebulaModel.Networking.Serialization; - -public class InvalidTypeException : ArgumentException -{ - public InvalidTypeException(string message) : base(message) { } -} - -public class ParseException : Exception +namespace NebulaModel.Networking.Serialization { - public ParseException(string message) : base(message) { } -} - -public class NetSerializer -{ - private readonly int _maxStringLength; - private readonly Dictionary _registeredTypes = new(); - - private NetDataWriter _writer; - - public NetSerializer() : this(0) + public class InvalidTypeException : ArgumentException { + public InvalidTypeException(string message) : base(message) { } } - public NetSerializer(int maxStringLength) + public class ParseException : Exception { - _maxStringLength = maxStringLength; + public ParseException(string message) : base(message) { } } - /// - /// Register custom property type - /// - /// INetSerializable structure - public void RegisterNestedType() where T : struct, INetSerializable + public class NetSerializer { - _registeredTypes.Add(typeof(T), new CustomTypeStruct()); - } - - /// - /// Register custom property type - /// - /// INetSerializable class - public void RegisterNestedType(Func constructor) where T : class, INetSerializable - { - _registeredTypes.Add(typeof(T), new CustomTypeClass(constructor)); - } - - /// - /// Register custom property type - /// - /// Any packet - /// custom type writer - /// custom type reader - public void RegisterNestedType(Action writer, Func reader) - { - _registeredTypes.Add(typeof(T), new CustomTypeStatic(writer, reader)); - } + private enum CallType + { + Basic, + Array, + List + } - private ClassInfo RegisterInternal() - { - if (ClassInfo.Instance != null) + private abstract class FastCall { - return ClassInfo.Instance; + public CallType Type; + public virtual void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) { Type = type; } + public abstract void Read(T inf, NetDataReader r); + public abstract void Write(T inf, NetDataWriter w); + public abstract void ReadArray(T inf, NetDataReader r); + public abstract void WriteArray(T inf, NetDataWriter w); + public abstract void ReadList(T inf, NetDataReader r); + public abstract void WriteList(T inf, NetDataWriter w); } - var t = typeof(T); - var props = t.GetProperties( - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.GetProperty | - BindingFlags.SetProperty); - var serializers = new List>(); - foreach (var property in props) + private abstract class FastCallSpecific : FastCall { - var propertyType = property.PropertyType; + protected Func Getter; + protected Action Setter; + protected Func GetterArr; + protected Action SetterArr; + protected Func> GetterList; + protected Action> SetterList; - var elementType = propertyType.IsArray ? propertyType.GetElementType() : propertyType; - var callType = propertyType.IsArray ? CallType.Array : CallType.Basic; + public override void ReadArray(TClass inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); } + public override void WriteArray(TClass inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); } + public override void ReadList(TClass inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); } + public override void WriteList(TClass inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); } - if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)) + protected TProperty[] ReadArrayHelper(TClass inf, NetDataReader r) { - elementType = propertyType.GetGenericArguments()[0]; - callType = CallType.List; + ushort count = r.GetUShort(); + var arr = GetterArr(inf); + arr = arr == null || arr.Length != count ? new TProperty[count] : arr; + SetterArr(inf, arr); + return arr; } - // Note from Cod: Required to get it to build - // TODO: Fix this - /*if (Attribute.IsDefined(property, typeof(IgnoreDataMemberAttribute))) - continue;*/ - - var getMethod = property.GetGetMethod(); - var setMethod = property.GetSetMethod(); - if (getMethod == null || setMethod == null) + protected TProperty[] WriteArrayHelper(TClass inf, NetDataWriter w) { - continue; + var arr = GetterArr(inf); + w.Put((ushort)arr.Length); + return arr; } - FastCall serialzer = null; - if (propertyType.IsEnum) + protected List ReadListHelper(TClass inf, NetDataReader r, out int len) { - var underlyingType = Enum.GetUnderlyingType(propertyType); - if (underlyingType == typeof(byte)) + len = r.GetUShort(); + var list = GetterList(inf); + if (list == null) { - serialzer = new EnumByteSerializer(property, propertyType); + list = new List(len); + SetterList(inf, list); } - else if (underlyingType == typeof(int)) + return list; + } + + protected List WriteListHelper(TClass inf, NetDataWriter w, out int len) + { + var list = GetterList(inf); + if (list == null) { - serialzer = new EnumIntSerializer(property, propertyType); + len = 0; + w.Put(0); + return null; } - else + len = list.Count; + w.Put((ushort)len); + return list; + } + + public override void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) + { + base.Init(getMethod, setMethod, type); + switch (type) { - throw new InvalidTypeException("Not supported enum underlying type: " + underlyingType.Name); + case CallType.Array: + GetterArr = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); + SetterArr = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); + break; + case CallType.List: + GetterList = (Func>)Delegate.CreateDelegate(typeof(Func>), getMethod); + SetterList = (Action>)Delegate.CreateDelegate(typeof(Action>), setMethod); + break; + default: + Getter = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); + Setter = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); + break; } } - else if (elementType == typeof(string)) + } + + private abstract class FastCallSpecificAuto : FastCallSpecific + { + protected abstract void ElementRead(NetDataReader r, out TProperty prop); + protected abstract void ElementWrite(NetDataWriter w, ref TProperty prop); + + public override void Read(TClass inf, NetDataReader r) + { + ElementRead(r, out var elem); + Setter(inf, elem); + } + + public override void Write(TClass inf, NetDataWriter w) + { + var elem = Getter(inf); + ElementWrite(w, ref elem); + } + + public override void ReadArray(TClass inf, NetDataReader r) { - serialzer = new StringSerializer(_maxStringLength); + var arr = ReadArrayHelper(inf, r); + for (int i = 0; i < arr.Length; i++) + ElementRead(r, out arr[i]); } - else if (elementType == typeof(bool)) + + public override void WriteArray(TClass inf, NetDataWriter w) { - serialzer = new BoolSerializer(); + var arr = WriteArrayHelper(inf, w); + for (int i = 0; i < arr.Length; i++) + ElementWrite(w, ref arr[i]); } - else if (elementType == typeof(byte)) + } + + private sealed class FastCallStatic : FastCallSpecific + { + private readonly Action _writer; + private readonly Func _reader; + + public FastCallStatic(Action write, Func read) { - serialzer = new ByteSerializer(); + _writer = write; + _reader = read; } - else if (elementType == typeof(sbyte)) + + public override void Read(TClass inf, NetDataReader r) { Setter(inf, _reader(r)); } + public override void Write(TClass inf, NetDataWriter w) { _writer(w, Getter(inf)); } + + public override void ReadList(TClass inf, NetDataReader r) { - serialzer = new SByteSerializer(); + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + if (i < listCount) + list[i] = _reader(r); + else + list.Add(_reader(r)); + } + if (len < listCount) + list.RemoveRange(len, listCount - len); } - else if (elementType == typeof(short)) + + public override void WriteList(TClass inf, NetDataWriter w) { - serialzer = new ShortSerializer(); + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + _writer(w, list[i]); } - else if (elementType == typeof(ushort)) + + public override void ReadArray(TClass inf, NetDataReader r) { - serialzer = new UShortSerializer(); + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i] = _reader(r); } - else if (elementType == typeof(int)) + + public override void WriteArray(TClass inf, NetDataWriter w) { - serialzer = new IntSerializer(); + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + _writer(w, arr[i]); } - else if (elementType == typeof(uint)) + } + + private sealed class FastCallStruct : FastCallSpecific where TProperty : struct, INetSerializable + { + private TProperty _p; + + public override void Read(TClass inf, NetDataReader r) { - serialzer = new UIntSerializer(); + _p.Deserialize(r); + Setter(inf, _p); } - else if (elementType == typeof(long)) + + public override void Write(TClass inf, NetDataWriter w) { - serialzer = new LongSerializer(); + _p = Getter(inf); + _p.Serialize(w); + } + + public override void ReadList(TClass inf, NetDataReader r) + { + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + var itm = default(TProperty); + itm.Deserialize(r); + if(i < listCount) + list[i] = itm; + else + list.Add(itm); + } + if (len < listCount) + list.RemoveRange(len, listCount - len); } - else if (elementType == typeof(ulong)) + + public override void WriteList(TClass inf, NetDataWriter w) { - serialzer = new ULongSerializer(); + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + list[i].Serialize(w); } - else if (elementType == typeof(float)) + + public override void ReadArray(TClass inf, NetDataReader r) { - serialzer = new FloatSerializer(); + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Deserialize(r); } - else if (elementType == typeof(double)) + + public override void WriteArray(TClass inf, NetDataWriter w) { - serialzer = new DoubleSerializer(); + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Serialize(w); } - else if (elementType == typeof(char)) + } + + private sealed class FastCallClass : FastCallSpecific where TProperty : class, INetSerializable + { + private readonly Func _constructor; + public FastCallClass(Func constructor) { _constructor = constructor; } + + public override void Read(TClass inf, NetDataReader r) { - serialzer = new CharSerializer(); + var p = _constructor(); + p.Deserialize(r); + Setter(inf, p); } - else if (elementType == typeof(IPEndPoint)) + + public override void Write(TClass inf, NetDataWriter w) { - serialzer = new IPEndPointSerializer(); + var p = Getter(inf); + p?.Serialize(w); } - else + + public override void ReadList(TClass inf, NetDataReader r) { - if (elementType != null) + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) { - _registeredTypes.TryGetValue(elementType, out var customType); - if (customType != null) + if (i < listCount) { - serialzer = customType.Get(); + list[i].Deserialize(r); + } + else + { + var itm = _constructor(); + itm.Deserialize(r); + list.Add(itm); } } + if (len < listCount) + list.RemoveRange(len, listCount - len); } - if (serialzer != null) + public override void WriteList(TClass inf, NetDataWriter w) { - serialzer.Init(getMethod, setMethod, callType); - serializers.Add(serialzer); + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + list[i].Serialize(w); } - else + + public override void ReadArray(TClass inf, NetDataReader r) { - throw new InvalidTypeException("Unknown property type: " + propertyType.FullName); + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + { + arr[i] = _constructor(); + arr[i].Deserialize(r); + } } - } - ClassInfo.Instance = new ClassInfo(serializers); - return ClassInfo.Instance; - } - - /// 's fields are not supported, or it has no fields - public void Register() - { - RegisterInternal(); - } - /// - /// Reads packet with known type - /// - /// NetDataReader with packet - /// Returns packet if packet in reader is matched type - /// 's fields are not supported, or it has no fields - public T Deserialize(NetDataReader reader) where T : class, new() - { - var info = RegisterInternal(); - var result = new T(); - try - { - info.Read(result, reader); - } - catch - { - return null; + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Serialize(w); + } } - return result; - } - /// - /// Reads packet with known type (non alloc variant) - /// - /// NetDataReader with packet - /// Deserialization target - /// Returns true if packet in reader is matched type - /// 's fields are not supported, or it has no fields - public bool Deserialize(NetDataReader reader, T target) where T : class, new() - { - var info = RegisterInternal(); - try + private class IntSerializer : FastCallSpecific { - info.Read(target, reader); + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetInt()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetIntArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - catch + + private class UIntSerializer : FastCallSpecific { - return false; + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUInt()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUIntArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - return true; - } - - /// - /// Serialize object to NetDataWriter (fast) - /// - /// Serialization target NetDataWriter - /// Object to serialize - /// 's fields are not supported, or it has no fields - public void Serialize(NetDataWriter writer, T obj) where T : class, new() - { - RegisterInternal().Write(obj, writer); - } - - /// - /// Serialize object to byte array - /// - /// Object to serialize - /// byte array with serialized data - public byte[] Serialize(T obj) where T : class, new() - { - _writer ??= new NetDataWriter(); - - _writer.Reset(); - Serialize(_writer, obj); - return _writer.CopyData(); - } - - private enum CallType - { - Basic, - Array, - List - } - - private abstract class FastCall - { - public CallType Type; - public virtual void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) { Type = type; } - - public abstract void Read(T inf, NetDataReader r); - - public abstract void Write(T inf, NetDataWriter w); - - public abstract void ReadArray(T inf, NetDataReader r); - - public abstract void WriteArray(T inf, NetDataWriter w); - - public abstract void ReadList(T inf, NetDataReader r); - - public abstract void WriteList(T inf, NetDataWriter w); - } - - private abstract class FastCallSpecific : FastCall - { - protected Func Getter; - protected Func GetterArr; - private Func> GetterList; - protected Action Setter; - protected Action SetterArr; - private Action> SetterList; - - public override void ReadArray(TClass inf, NetDataReader r) + private class ShortSerializer : FastCallSpecific { - throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetShort()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetShortArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - public override void WriteArray(TClass inf, NetDataWriter w) + private class UShortSerializer : FastCallSpecific { - throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUShort()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUShortArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - public override void ReadList(TClass inf, NetDataReader r) + private class LongSerializer : FastCallSpecific { - throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetLong()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetLongArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - public override void WriteList(TClass inf, NetDataWriter w) + private class ULongSerializer : FastCallSpecific { - throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetULong()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetULongArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - protected TProperty[] ReadArrayHelper(TClass inf, NetDataReader r) + private class ByteSerializer : FastCallSpecific { - var count = r.GetUShort(); - var arr = GetterArr(inf); - arr = arr == null || arr.Length != count ? new TProperty[count] : arr; - SetterArr(inf, arr); - return arr; + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetByte()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBytesWithLength()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutBytesWithLength(GetterArr(inf)); } } - protected TProperty[] WriteArrayHelper(TClass inf, NetDataWriter w) + private class SByteSerializer : FastCallSpecific { - var arr = GetterArr(inf); - w.Put((ushort)arr.Length); - return arr; + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetSByte()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetSBytesWithLength()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutSBytesWithLength(GetterArr(inf)); } } - protected List ReadListHelper(TClass inf, NetDataReader r, out int len) + private class FloatSerializer : FastCallSpecific { - len = r.GetUShort(); - var list = GetterList(inf); - if (list != null) - { - return list; - } - list = new List(len); - SetterList(inf, list); - return list; + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetFloat()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetFloatArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - protected List WriteListHelper(TClass inf, NetDataWriter w, out int len) + private class DoubleSerializer : FastCallSpecific { - var list = GetterList(inf); - if (list == null) - { - len = 0; - w.Put(0); - return null; - } - len = list.Count; - w.Put((ushort)len); - return list; + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetDouble()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetDoubleArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - public override void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) + private class BoolSerializer : FastCallSpecific { - base.Init(getMethod, setMethod, type); - switch (type) - { - case CallType.Array: - GetterArr = (Func)Delegate.CreateDelegate(typeof(Func), - getMethod); - SetterArr = (Action)Delegate.CreateDelegate(typeof(Action), - setMethod); - break; - case CallType.List: - GetterList = (Func>)Delegate.CreateDelegate(typeof(Func>), - getMethod); - SetterList = (Action>)Delegate.CreateDelegate( - typeof(Action>), setMethod); - break; - case CallType.Basic: - default: - Getter = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); - Setter = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); - break; - } + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetBool()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBoolArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } } - } - - private abstract class FastCallSpecificAuto : FastCallSpecific - { - protected abstract void ElementRead(NetDataReader r, out TProperty prop); - protected abstract void ElementWrite(NetDataWriter w, ref TProperty prop); - - public override void Read(TClass inf, NetDataReader r) + private class CharSerializer : FastCallSpecificAuto { - ElementRead(r, out var elem); - Setter(inf, elem); + protected override void ElementWrite(NetDataWriter w, ref char prop) { w.Put(prop); } + protected override void ElementRead(NetDataReader r, out char prop) { prop = r.GetChar(); } } - public override void Write(TClass inf, NetDataWriter w) + private class IPEndPointSerializer : FastCallSpecificAuto { - var elem = Getter(inf); - ElementWrite(w, ref elem); + protected override void ElementWrite(NetDataWriter w, ref IPEndPoint prop) { w.Put(prop); } + protected override void ElementRead(NetDataReader r, out IPEndPoint prop) { prop = r.GetNetEndPoint(); } } - public override void ReadArray(TClass inf, NetDataReader r) + private class StringSerializer : FastCallSpecific { - var arr = ReadArrayHelper(inf, r); - for (var i = 0; i < arr.Length; i++) - { - ElementRead(r, out arr[i]); - } + private readonly int _maxLength; + public StringSerializer(int maxLength) { _maxLength = maxLength > 0 ? maxLength : short.MaxValue; } + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetString(_maxLength)); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf), _maxLength); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetStringArray(_maxLength)); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf), _maxLength); } } - public override void WriteArray(TClass inf, NetDataWriter w) + private class EnumByteSerializer : FastCall { - var arr = WriteArrayHelper(inf, w); - for (var i = 0; i < arr.Length; i++) + protected readonly PropertyInfo Property; + protected readonly Type PropertyType; + public EnumByteSerializer(PropertyInfo property, Type propertyType) { - ElementWrite(w, ref arr[i]); + Property = property; + PropertyType = propertyType; } + public override void Read(T inf, NetDataReader r) { Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetByte()), null); } + public override void Write(T inf, NetDataWriter w) { w.Put((byte)Property.GetValue(inf, null)); } + public override void ReadArray(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: Enum[]"); } + public override void WriteArray(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: Enum[]"); } + public override void ReadList(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: List"); } + public override void WriteList(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: List"); } } - } - - private sealed class FastCallStatic : FastCallSpecific - { - private readonly Func _reader; - private readonly Action _writer; - public FastCallStatic(Action write, Func read) + private class EnumIntSerializer : EnumByteSerializer { - _writer = write; - _reader = read; + public EnumIntSerializer(PropertyInfo property, Type propertyType) : base(property, propertyType) { } + public override void Read(T inf, NetDataReader r) { Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetInt()), null); } + public override void Write(T inf, NetDataWriter w) { w.Put((int)Property.GetValue(inf, null)); } } - public override void Read(TClass inf, NetDataReader r) { Setter(inf, _reader(r)); } - - public override void Write(TClass inf, NetDataWriter w) { _writer(w, Getter(inf)); } - - public override void ReadList(TClass inf, NetDataReader r) + private sealed class ClassInfo { - var list = ReadListHelper(inf, r, out var len); - var listCount = list.Count; - for (var i = 0; i < len; i++) - { - if (i < listCount) - { - list[i] = _reader(r); - } - else - { - list.Add(_reader(r)); - } - } - if (len < listCount) + public static ClassInfo Instance; + private readonly FastCall[] _serializers; + private readonly int _membersCount; + + public ClassInfo(List> serializers) { - list.RemoveRange(len, listCount - len); + _membersCount = serializers.Count; + _serializers = serializers.ToArray(); } - } - public override void WriteList(TClass inf, NetDataWriter w) - { - var list = WriteListHelper(inf, w, out var len); - for (var i = 0; i < len; i++) + public void Write(T obj, NetDataWriter writer) { - _writer(w, list[i]); + for (int i = 0; i < _membersCount; i++) + { + var s = _serializers[i]; + if (s.Type == CallType.Basic) + s.Write(obj, writer); + else if (s.Type == CallType.Array) + s.WriteArray(obj, writer); + else + s.WriteList(obj, writer); + } } - } - public override void ReadArray(TClass inf, NetDataReader r) - { - var arr = ReadArrayHelper(inf, r); - var len = arr.Length; - for (var i = 0; i < len; i++) + public void Read(T obj, NetDataReader reader) { - arr[i] = _reader(r); + for (int i = 0; i < _membersCount; i++) + { + var s = _serializers[i]; + if (s.Type == CallType.Basic) + s.Read(obj, reader); + else if(s.Type == CallType.Array) + s.ReadArray(obj, reader); + else + s.ReadList(obj, reader); + } } } - public override void WriteArray(TClass inf, NetDataWriter w) + private abstract class CustomType { - var arr = WriteArrayHelper(inf, w); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - _writer(w, arr[i]); - } + public abstract FastCall Get(); } - } - - private sealed class FastCallStruct : FastCallSpecific - where TProperty : struct, INetSerializable - { - private TProperty _p; - public override void Read(TClass inf, NetDataReader r) + private sealed class CustomTypeStruct : CustomType where TProperty : struct, INetSerializable { - _p.Deserialize(r); - Setter(inf, _p); + public override FastCall Get() { return new FastCallStruct(); } } - public override void Write(TClass inf, NetDataWriter w) + private sealed class CustomTypeClass : CustomType where TProperty : class, INetSerializable { - _p = Getter(inf); - _p.Serialize(w); + private readonly Func _constructor; + public CustomTypeClass(Func constructor) { _constructor = constructor; } + public override FastCall Get() { return new FastCallClass(_constructor); } } - public override void ReadList(TClass inf, NetDataReader r) + private sealed class CustomTypeStatic : CustomType { - var list = ReadListHelper(inf, r, out var len); - var listCount = list.Count; - for (var i = 0; i < len; i++) + private readonly Action _writer; + private readonly Func _reader; + public CustomTypeStatic(Action writer, Func reader) { - var itm = default(TProperty); - itm.Deserialize(r); - if (i < listCount) - { - list[i] = itm; - } - else - { - list.Add(itm); - } - } - if (len < listCount) - { - list.RemoveRange(len, listCount - len); + _writer = writer; + _reader = reader; } + public override FastCall Get() { return new FastCallStatic(_writer, _reader); } } - public override void WriteList(TClass inf, NetDataWriter w) + /// + /// Register custom property type + /// + /// INetSerializable structure + public void RegisterNestedType() where T : struct, INetSerializable { - var list = WriteListHelper(inf, w, out var len); - for (var i = 0; i < len; i++) - { - list[i].Serialize(w); - } + _registeredTypes.Add(typeof(T), new CustomTypeStruct()); } - public override void ReadArray(TClass inf, NetDataReader r) + /// + /// Register custom property type + /// + /// INetSerializable class + public void RegisterNestedType(Func constructor) where T : class, INetSerializable { - var arr = ReadArrayHelper(inf, r); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - arr[i].Deserialize(r); - } + _registeredTypes.Add(typeof(T), new CustomTypeClass(constructor)); } - public override void WriteArray(TClass inf, NetDataWriter w) + /// + /// Register custom property type + /// + /// Any packet + /// custom type writer + /// custom type reader + public void RegisterNestedType(Action writer, Func reader) { - var arr = WriteArrayHelper(inf, w); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - arr[i].Serialize(w); - } + _registeredTypes.Add(typeof(T), new CustomTypeStatic(writer, reader)); } - } - - private sealed class FastCallClass : FastCallSpecific - where TProperty : class, INetSerializable - { - private readonly Func _constructor; - public FastCallClass(Func constructor) { _constructor = constructor; } + private NetDataWriter _writer; + private readonly int _maxStringLength; + private readonly Dictionary _registeredTypes = new Dictionary(); - public override void Read(TClass inf, NetDataReader r) + public NetSerializer() : this(0) { - var p = _constructor(); - p.Deserialize(r); - Setter(inf, p); } - public override void Write(TClass inf, NetDataWriter w) + public NetSerializer(int maxStringLength) { - var p = Getter(inf); - p?.Serialize(w); + _maxStringLength = maxStringLength; } - public override void ReadList(TClass inf, NetDataReader r) + private ClassInfo RegisterInternal() { - var list = ReadListHelper(inf, r, out var len); - var listCount = list.Count; - for (var i = 0; i < len; i++) + if (ClassInfo.Instance != null) + return ClassInfo.Instance; + + Type t = typeof(T); + var props = t.GetProperties( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.GetProperty | + BindingFlags.SetProperty); + var serializers = new List>(); + for (int i = 0; i < props.Length; i++) { - if (i < listCount) + var property = props[i]; + var propertyType = property.PropertyType; + + var elementType = propertyType.IsArray ? propertyType.GetElementType() : propertyType; + var callType = propertyType.IsArray ? CallType.Array : CallType.Basic; + + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)) { - list[i].Deserialize(r); + elementType = propertyType.GetGenericArguments()[0]; + callType = CallType.List; } + + if (Attribute.IsDefined(property, typeof(IgnoreDataMemberAttribute))) + continue; + + var getMethod = property.GetGetMethod(); + var setMethod = property.GetSetMethod(); + if (getMethod == null || setMethod == null) + continue; + + FastCall serialzer = null; + if (propertyType.IsEnum) + { + var underlyingType = Enum.GetUnderlyingType(propertyType); + if (underlyingType == typeof(byte)) + serialzer = new EnumByteSerializer(property, propertyType); + else if (underlyingType == typeof(int)) + serialzer = new EnumIntSerializer(property, propertyType); + else + throw new InvalidTypeException("Not supported enum underlying type: " + underlyingType.Name); + } + else if (elementType == typeof(string)) + serialzer = new StringSerializer(_maxStringLength); + else if (elementType == typeof(bool)) + serialzer = new BoolSerializer(); + else if (elementType == typeof(byte)) + serialzer = new ByteSerializer(); + else if (elementType == typeof(sbyte)) + serialzer = new SByteSerializer(); + else if (elementType == typeof(short)) + serialzer = new ShortSerializer(); + else if (elementType == typeof(ushort)) + serialzer = new UShortSerializer(); + else if (elementType == typeof(int)) + serialzer = new IntSerializer(); + else if (elementType == typeof(uint)) + serialzer = new UIntSerializer(); + else if (elementType == typeof(long)) + serialzer = new LongSerializer(); + else if (elementType == typeof(ulong)) + serialzer = new ULongSerializer(); + else if (elementType == typeof(float)) + serialzer = new FloatSerializer(); + else if (elementType == typeof(double)) + serialzer = new DoubleSerializer(); + else if (elementType == typeof(char)) + serialzer = new CharSerializer(); + else if (elementType == typeof(IPEndPoint)) + serialzer = new IPEndPointSerializer(); else { - var itm = _constructor(); - itm.Deserialize(r); - list.Add(itm); + _registeredTypes.TryGetValue(elementType, out var customType); + if (customType != null) + serialzer = customType.Get(); + } + + if (serialzer != null) + { + serialzer.Init(getMethod, setMethod, callType); + serializers.Add(serialzer); + } + else + { + throw new InvalidTypeException("Unknown property type: " + propertyType.FullName); } } - if (len < listCount) - { - list.RemoveRange(len, listCount - len); - } + ClassInfo.Instance = new ClassInfo(serializers); + return ClassInfo.Instance; } - public override void WriteList(TClass inf, NetDataWriter w) + /// 's fields are not supported, or it has no fields + public void Register() { - var list = WriteListHelper(inf, w, out var len); - for (var i = 0; i < len; i++) - { - list[i].Serialize(w); - } + RegisterInternal(); } - public override void ReadArray(TClass inf, NetDataReader r) + /// + /// Reads packet with known type + /// + /// NetDataReader with packet + /// Returns packet if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public T Deserialize(NetDataReader reader) where T : class, new() { - var arr = ReadArrayHelper(inf, r); - var len = arr.Length; - for (var i = 0; i < len; i++) + var info = RegisterInternal(); + var result = new T(); + try { - arr[i] = _constructor(); - arr[i].Deserialize(r); + info.Read(result, reader); } - } - - public override void WriteArray(TClass inf, NetDataWriter w) - { - var arr = WriteArrayHelper(inf, w); - var len = arr.Length; - for (var i = 0; i < len; i++) + catch { - arr[i].Serialize(w); + return null; } - } - } - - private class IntSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetInt()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetIntArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class UIntSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUInt()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUIntArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class ShortSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetShort()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetShortArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class UShortSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUShort()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUShortArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class LongSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetLong()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetLongArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class ULongSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetULong()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetULongArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class ByteSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetByte()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBytesWithLength()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutBytesWithLength(GetterArr(inf)); } - } - - private class SByteSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetSByte()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetSBytesWithLength()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutSBytesWithLength(GetterArr(inf)); } - } - - private class FloatSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetFloat()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetFloatArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class DoubleSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetDouble()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetDoubleArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class BoolSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetBool()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBoolArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class CharSerializer : FastCallSpecificAuto - { - protected override void ElementWrite(NetDataWriter w, ref char prop) { w.Put(prop); } - - protected override void ElementRead(NetDataReader r, out char prop) { prop = r.GetChar(); } - } - - private class IPEndPointSerializer : FastCallSpecificAuto - { - protected override void ElementWrite(NetDataWriter w, ref IPEndPoint prop) { w.Put(prop); } - - protected override void ElementRead(NetDataReader r, out IPEndPoint prop) { prop = r.GetNetEndPoint(); } - } - - private class StringSerializer : FastCallSpecific - { - private readonly int _maxLength; - - public StringSerializer(int maxLength) { _maxLength = maxLength > 0 ? maxLength : short.MaxValue; } - - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetString(_maxLength)); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf), _maxLength); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetStringArray(_maxLength)); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf), _maxLength); } - } - - private class EnumByteSerializer : FastCall - { - protected readonly PropertyInfo Property; - protected readonly Type PropertyType; - - public EnumByteSerializer(PropertyInfo property, Type propertyType) - { - Property = property; - PropertyType = propertyType; - } - - public override void Read(T inf, NetDataReader r) - { - Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetByte()), null); - } - - public override void Write(T inf, NetDataWriter w) { w.Put((byte)Property.GetValue(inf, null)); } - - public override void ReadArray(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: Enum[]"); } - - public override void WriteArray(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: Enum[]"); } - - public override void ReadList(T inf, NetDataReader r) - { - throw new InvalidTypeException("Unsupported type: List"); - } - - public override void WriteList(T inf, NetDataWriter w) - { - throw new InvalidTypeException("Unsupported type: List"); - } - } - - private class EnumIntSerializer : EnumByteSerializer - { - public EnumIntSerializer(PropertyInfo property, Type propertyType) : base(property, propertyType) { } - - public override void Read(T inf, NetDataReader r) - { - Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetInt()), null); - } - - public override void Write(T inf, NetDataWriter w) { w.Put((int)Property.GetValue(inf, null)); } - } - - private sealed class ClassInfo - { - public static ClassInfo Instance; - private readonly int _membersCount; - private readonly FastCall[] _serializers; - - public ClassInfo(List> serializers) - { - _membersCount = serializers.Count; - _serializers = serializers.ToArray(); + return result; } - public void Write(T obj, NetDataWriter writer) + /// + /// Reads packet with known type (non alloc variant) + /// + /// NetDataReader with packet + /// Deserialization target + /// Returns true if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public bool Deserialize(NetDataReader reader, T target) where T : class, new() { - for (var i = 0; i < _membersCount; i++) + var info = RegisterInternal(); + try { - var s = _serializers[i]; - switch (s.Type) - { - case CallType.Basic: - s.Write(obj, writer); - break; - case CallType.Array: - s.WriteArray(obj, writer); - break; - case CallType.List: - default: - s.WriteList(obj, writer); - break; - } + info.Read(target, reader); } - } - - public void Read(T obj, NetDataReader reader) - { - for (var i = 0; i < _membersCount; i++) + catch { - var s = _serializers[i]; - switch (s.Type) - { - case CallType.Basic: - s.Read(obj, reader); - break; - case CallType.Array: - s.ReadArray(obj, reader); - break; - case CallType.List: - default: - s.ReadList(obj, reader); - break; - } + return false; } + return true; } - } - - private abstract class CustomType - { - public abstract FastCall Get(); - } - private sealed class CustomTypeStruct : CustomType where TProperty : struct, INetSerializable - { - public override FastCall Get() { return new FastCallStruct(); } - } - - private sealed class CustomTypeClass : CustomType where TProperty : class, INetSerializable - { - private readonly Func _constructor; - - public CustomTypeClass(Func constructor) { _constructor = constructor; } - - public override FastCall Get() { return new FastCallClass(_constructor); } - } - - private sealed class CustomTypeStatic : CustomType - { - private readonly Func _reader; - private readonly Action _writer; - - public CustomTypeStatic(Action writer, Func reader) + /// + /// Serialize object to NetDataWriter (fast) + /// + /// Serialization target NetDataWriter + /// Object to serialize + /// 's fields are not supported, or it has no fields + public void Serialize(NetDataWriter writer, T obj) where T : class, new() { - _writer = writer; - _reader = reader; + RegisterInternal().Write(obj, writer); } - public override FastCall Get() { return new FastCallStatic(_writer, _reader); } + /// + /// Serialize object to byte array + /// + /// Object to serialize + /// byte array with serialized data + public byte[] Serialize(T obj) where T : class, new() + { + if (_writer == null) + _writer = new NetDataWriter(); + _writer.Reset(); + Serialize(_writer, obj); + return _writer.CopyData(); + } } } From 6c5d18aa6bab9ee0a9bc235f740276605c36b416 Mon Sep 17 00:00:00 2001 From: Karim Rizk Date: Fri, 22 Dec 2023 21:44:36 +0200 Subject: [PATCH 2/3] Fix packets arriving corrupted after update LiteNetLib was forcing the maximum size of an array to that of a ushort. --- NebulaAPI/Interfaces/INetDataReader.cs | 2 +- NebulaAPI/Interfaces/INetDataWriter.cs | 4 +-- .../Networking/Serialization/NetDataReader.cs | 12 ++++----- .../Networking/Serialization/NetDataWriter.cs | 26 +++++++++++-------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/NebulaAPI/Interfaces/INetDataReader.cs b/NebulaAPI/Interfaces/INetDataReader.cs index a37979e59..263c1ca4b 100644 --- a/NebulaAPI/Interfaces/INetDataReader.cs +++ b/NebulaAPI/Interfaces/INetDataReader.cs @@ -68,7 +68,7 @@ int AvailableBytes IPEndPoint GetNetEndPoint(); byte GetByte(); sbyte GetSByte(); - T[] GetArray(ushort size); + T[] GetArray(int size); bool[] GetBoolArray(); ushort[] GetUShortArray(); short[] GetShortArray(); diff --git a/NebulaAPI/Interfaces/INetDataWriter.cs b/NebulaAPI/Interfaces/INetDataWriter.cs index c405cc401..f92789ed4 100644 --- a/NebulaAPI/Interfaces/INetDataWriter.cs +++ b/NebulaAPI/Interfaces/INetDataWriter.cs @@ -66,9 +66,9 @@ int Length void Put(string value, int maxLength); void Put(T obj) where T : INetSerializable; - void PutSBytesWithLength(sbyte[] data, int offset, ushort length); + void PutSBytesWithLength(sbyte[] data, int offset, int length); void PutSBytesWithLength(sbyte[] data); - void PutBytesWithLength(byte[] data, int offset, ushort length); + void PutBytesWithLength(byte[] data, int offset, int length); void PutBytesWithLength(byte[] data); void PutArray(Array arr, int sz); void PutArray(float[] value); diff --git a/NebulaModel/Networking/Serialization/NetDataReader.cs b/NebulaModel/Networking/Serialization/NetDataReader.cs index ee986813b..3427b072e 100644 --- a/NebulaModel/Networking/Serialization/NetDataReader.cs +++ b/NebulaModel/Networking/Serialization/NetDataReader.cs @@ -127,10 +127,10 @@ public sbyte GetSByte() return (sbyte)GetByte(); } - public T[] GetArray(ushort size) + public T[] GetArray(int size) { - ushort length = BitConverter.ToUInt16(_data, _position); - _position += 2; + int length = BitConverter.ToInt32(_data, _position); + _position += 4; T[] result = new T[length]; length *= size; Buffer.BlockCopy(_data, _position, result, 0, length); @@ -650,10 +650,10 @@ public bool TryGetStringArray(out string[] result) public bool TryGetBytesWithLength(out byte[] result) { - if (AvailableBytes >= 2) + if (AvailableBytes >= 4) { - ushort length = PeekUShort(); - if (length >= 0 && AvailableBytes >= 2 + length) + int length = PeekInt(); + if (length >= 0 && AvailableBytes >= 4 + length) { result = GetBytesWithLength(); return true; diff --git a/NebulaModel/Networking/Serialization/NetDataWriter.cs b/NebulaModel/Networking/Serialization/NetDataWriter.cs index c0acc0409..13fb1e67d 100644 --- a/NebulaModel/Networking/Serialization/NetDataWriter.cs +++ b/NebulaModel/Networking/Serialization/NetDataWriter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Runtime.CompilerServices; using System.Text; @@ -19,11 +19,13 @@ public int Capacity [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _data.Length; } + public byte[] Data { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _data; } + public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -233,13 +235,14 @@ public void Put(byte[] data) _position += data.Length; } - public void PutSBytesWithLength(sbyte[] data, int offset, ushort length) + public void PutSBytesWithLength(sbyte[] data, int offset, int length) { + const int intSize = 4; if (_autoResize) - ResizeIfNeed(_position + 2 + length); + ResizeIfNeed(_position + intSize + length); FastBitConverter.GetBytes(_data, _position, length); - Buffer.BlockCopy(data, offset, _data, _position + 2, length); - _position += 2 + length; + Buffer.BlockCopy(data, offset, _data, _position + intSize, length); + _position += intSize + length; } public void PutSBytesWithLength(sbyte[] data) @@ -247,7 +250,7 @@ public void PutSBytesWithLength(sbyte[] data) PutArray(data, 1); } - public void PutBytesWithLength(byte[] data, int offset, ushort length) + public void PutBytesWithLength(byte[] data, int offset, int length) { if (_autoResize) ResizeIfNeed(_position + 2 + length); @@ -268,14 +271,15 @@ public void Put(bool value) public void PutArray(Array arr, int sz) { - ushort length = arr == null ? (ushort) 0 : (ushort)arr.Length; + const int intSize = 4; + int length = arr == null ? 0 : arr.Length; sz *= length; if (_autoResize) - ResizeIfNeed(_position + sz + 2); + ResizeIfNeed(_position + sz + intSize); FastBitConverter.GetBytes(_data, _position, length); if (arr != null) - Buffer.BlockCopy(arr, 0, _data, _position + 2, sz); - _position += sz + 2; + Buffer.BlockCopy(arr, 0, _data, _position + intSize, sz); + _position += sz + intSize; } public void PutArray(float[] value) @@ -325,7 +329,7 @@ public void PutArray(bool[] value) public void PutArray(string[] value) { - ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; + int strArrayLength = value == null ? 0 : value.Length; Put(strArrayLength); for (int i = 0; i < strArrayLength; i++) Put(value[i]); From 934810c62fd41f119593682bd52f5a4977b7b23e Mon Sep 17 00:00:00 2001 From: Karim Rizk Date: Fri, 22 Dec 2023 22:06:08 +0200 Subject: [PATCH 3/3] Cleanup --- .../NetPacketProcessorExtension.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs b/NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs index f95b66736..3ba304505 100644 --- a/NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs +++ b/NebulaModel/Networking/Serialization/NetPacketProcessorExtension.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using NebulaAPI.Interfaces; +using NebulaAPI.Packets; +using NebulaModel.Logger; namespace NebulaModel.Networking.Serialization; @@ -9,7 +11,7 @@ public partial class NetPacketProcessor { // Packet simulation stuff private readonly Dictionary _callbacksDebugInfo = []; - private readonly NetDataWriter _netDataWriter = new(); + private readonly NetDataWriter writer = new(); private readonly List delayedPackets = []; private readonly Queue pendingPackets = new(); @@ -24,11 +26,6 @@ public partial class NetPacketProcessor /// public bool Enable { get; set; } = true; - /// - /// - /// - private static readonly NetDataWriter writer = new(); - /// /// Adds back some functionality that nebula relied on before the update. /// This method was removed from LiteNetLib as it was not thread-safe, and is still not thread safe in below implementation. @@ -38,6 +35,14 @@ public partial class NetPacketProcessor { writer.Reset(); Write(writer, packet); + +#if DEBUG + if (!typeof(T).IsDefined(typeof(HidePacketInDebugLogsAttribute), false)) + { + Log.Debug($"Packet Sent: {packet.GetType().Name}, Size: {writer.Data.Length}"); + } +#endif + return writer.CopyData(); } @@ -64,7 +69,7 @@ private void ProcessDelayedPackets() { var now = DateTime.UtcNow; var deleteCount = 0; - + for (var i = 0; i < delayedPackets.Count; ++i) { if (now >= delayedPackets[i].DueTime) @@ -78,7 +83,7 @@ private void ProcessDelayedPackets() break; } } - + if (deleteCount > 0) { delayedPackets.RemoveRange(0, deleteCount); @@ -109,13 +114,10 @@ public void EnqueuePacketForProcessing(byte[] rawData, object userData) lock (pendingPackets) { pendingPackets.Enqueue(new PendingPacket(rawData, userData)); + Log.Info($"Received packet of size: {rawData.Length}"); } #endif } #endregion -} - -public static class NetPacketProcessorExtension -{ -} +} \ No newline at end of file