| @@ -12,21 +12,23 @@ namespace Discord.ETF | |||||
| { | { | ||||
| public unsafe class ETFWriter : IDisposable | public unsafe class ETFWriter : IDisposable | ||||
| { | { | ||||
| private readonly static byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; | |||||
| private readonly static byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; | |||||
| private readonly static byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; | |||||
| private static readonly ConcurrentDictionary<Type, Delegate> _serializers = new ConcurrentDictionary<Type, Delegate>(); | |||||
| private static readonly ConcurrentDictionary<Type, Delegate> _indirectSerializers = new ConcurrentDictionary<Type, Delegate>(); | |||||
| private readonly static MethodInfo _writeTMethod = GetGenericWriteMethod(null); | |||||
| private readonly static MethodInfo _writeNullableTMethod = GetGenericWriteMethod(typeof(Nullable<>)); | |||||
| private readonly static MethodInfo _writeDictionaryTMethod = GetGenericWriteMethod(typeof(IDictionary<,>)); | |||||
| private readonly static MethodInfo _writeEnumerableTMethod = GetGenericWriteMethod(typeof(IEnumerable<>)); | |||||
| private readonly static DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |||||
| private static readonly byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; | |||||
| private static readonly byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; | |||||
| private static readonly byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; | |||||
| private static readonly MethodInfo _writeTMethod = GetGenericWriteMethod(null); | |||||
| private static readonly MethodInfo _writeNullableTMethod = GetGenericWriteMethod(typeof(Nullable<>)); | |||||
| private static readonly MethodInfo _writeDictionaryTMethod = GetGenericWriteMethod(typeof(IDictionary<,>)); | |||||
| private static readonly MethodInfo _writeEnumerableTMethod = GetGenericWriteMethod(typeof(IEnumerable<>)); | |||||
| private static readonly DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |||||
| private readonly Stream _stream; | private readonly Stream _stream; | ||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| private readonly bool _leaveOpen; | private readonly bool _leaveOpen; | ||||
| private readonly Encoding _encoding; | private readonly Encoding _encoding; | ||||
| private readonly ConcurrentDictionary<Type, Delegate> _serializers, _indirectSerializers; | |||||
| public virtual Stream BaseStream | public virtual Stream BaseStream | ||||
| { | { | ||||
| @@ -45,8 +47,6 @@ namespace Discord.ETF | |||||
| _leaveOpen = leaveOpen; | _leaveOpen = leaveOpen; | ||||
| _buffer = new byte[11]; | _buffer = new byte[11]; | ||||
| _encoding = Encoding.UTF8; | _encoding = Encoding.UTF8; | ||||
| _serializers = new ConcurrentDictionary<Type, Delegate>(); | |||||
| _indirectSerializers = new ConcurrentDictionary<Type, Delegate>(); | |||||
| } | } | ||||
| public void Write(bool value) | public void Write(bool value) | ||||
| @@ -72,6 +72,7 @@ namespace Discord.ETF | |||||
| } | } | ||||
| else if (value >= int.MinValue && value <= int.MaxValue) | else if (value >= int.MinValue && value <= int.MaxValue) | ||||
| { | { | ||||
| //TODO: Does this encode negatives correctly? | |||||
| _buffer[0] = (byte)ETFType.INTEGER_EXT; | _buffer[0] = (byte)ETFType.INTEGER_EXT; | ||||
| _buffer[1] = (byte)(value >> 24); | _buffer[1] = (byte)(value >> 24); | ||||
| _buffer[2] = (byte)(value >> 16); | _buffer[2] = (byte)(value >> 16); | ||||
| @@ -145,7 +146,7 @@ namespace Discord.ETF | |||||
| public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); | public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); | ||||
| public void Write(bool? value) { if (value.HasValue) Write((bool)value.Value); else WriteNil(); } | |||||
| public void Write(bool? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | |||||
| public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | ||||
| public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | ||||
| public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | ||||
| @@ -158,35 +159,35 @@ namespace Discord.ETF | |||||
| public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); } | public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); } | ||||
| public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | ||||
| public void Write(byte[] value) | |||||
| public void Write(string value) | |||||
| { | { | ||||
| if (value != null) | if (value != null) | ||||
| { | { | ||||
| int count = value.Length; | |||||
| var bytes = _encoding.GetBytes(value); | |||||
| int count = bytes.Length; | |||||
| _buffer[0] = (byte)ETFType.BINARY_EXT; | _buffer[0] = (byte)ETFType.BINARY_EXT; | ||||
| _buffer[1] = (byte)(count >> 24); | _buffer[1] = (byte)(count >> 24); | ||||
| _buffer[2] = (byte)(count >> 16); | _buffer[2] = (byte)(count >> 16); | ||||
| _buffer[3] = (byte)(count >> 8); | _buffer[3] = (byte)(count >> 8); | ||||
| _buffer[4] = (byte)count; | _buffer[4] = (byte)count; | ||||
| _stream.Write(_buffer, 0, 5); | _stream.Write(_buffer, 0, 5); | ||||
| _stream.Write(value, 0, value.Length); | |||||
| _stream.Write(bytes, 0, bytes.Length); | |||||
| } | } | ||||
| else | else | ||||
| WriteNil(); | WriteNil(); | ||||
| } | } | ||||
| public void Write(string value) | |||||
| public void Write(byte[] value) | |||||
| { | { | ||||
| if (value != null) | if (value != null) | ||||
| { | { | ||||
| var bytes = _encoding.GetBytes(value); | |||||
| int count = bytes.Length; | |||||
| int count = value.Length; | |||||
| _buffer[0] = (byte)ETFType.BINARY_EXT; | _buffer[0] = (byte)ETFType.BINARY_EXT; | ||||
| _buffer[1] = (byte)(count >> 24); | _buffer[1] = (byte)(count >> 24); | ||||
| _buffer[2] = (byte)(count >> 16); | _buffer[2] = (byte)(count >> 16); | ||||
| _buffer[3] = (byte)(count >> 8); | _buffer[3] = (byte)(count >> 8); | ||||
| _buffer[4] = (byte)count; | _buffer[4] = (byte)count; | ||||
| _stream.Write(_buffer, 0, 5); | _stream.Write(_buffer, 0, 5); | ||||
| _stream.Write(bytes, 0, bytes.Length); | |||||
| _stream.Write(value, 0, value.Length); | |||||
| } | } | ||||
| else | else | ||||
| WriteNil(); | WriteNil(); | ||||
| @@ -269,7 +270,7 @@ namespace Discord.ETF | |||||
| public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin); | public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin); | ||||
| #region Emit | #region Emit | ||||
| private Action<ETFWriter, T> CreateSerializer<T>(Type type, TypeInfo typeInfo, bool isDirect) | |||||
| private static Action<ETFWriter, T> CreateSerializer<T>(Type type, TypeInfo typeInfo, bool isDirect) | |||||
| { | { | ||||
| var method = new DynamicMethod(isDirect ? "SerializeETF" : "SerializeIndirectETF", | var method = new DynamicMethod(isDirect ? "SerializeETF" : "SerializeIndirectETF", | ||||
| null, new[] { typeof(ETFWriter), isDirect ? type : typeof(object) }, true); | null, new[] { typeof(ETFWriter), isDirect ? type : typeof(object) }, true); | ||||
| @@ -291,7 +292,7 @@ namespace Discord.ETF | |||||
| generator.Emit(OpCodes.Ret); | generator.Emit(OpCodes.Ret); | ||||
| return method.CreateDelegate(typeof(Action<ETFWriter, T>)) as Action<ETFWriter, T>; | return method.CreateDelegate(typeof(Action<ETFWriter, T>)) as Action<ETFWriter, T>; | ||||
| } | } | ||||
| private void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) | |||||
| private static void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) | |||||
| { | { | ||||
| //Convert enum types to their base type | //Convert enum types to their base type | ||||
| if (typeInfo.IsEnum) | if (typeInfo.IsEnum) | ||||
| @@ -335,18 +336,21 @@ namespace Discord.ETF | |||||
| //Dictionaries | //Dictionaries | ||||
| else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces | else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces | ||||
| .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) | .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) | ||||
| { | |||||
| generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | ||||
| } | |||||
| //Enumerable | //Enumerable | ||||
| else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces | else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces | ||||
| .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) | .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) | ||||
| { | |||||
| generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | ||||
| } | |||||
| //Nullable Structs | //Nullable Structs | ||||
| else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && | |||||
| else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && | |||||
| typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) | typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) | ||||
| { | |||||
| generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | ||||
| } | |||||
| //Structs/Classes | //Structs/Classes | ||||
| else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) | else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) | ||||
| { | { | ||||
| @@ -387,7 +391,6 @@ namespace Discord.ETF | |||||
| generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | ||||
| } | } | ||||
| } | } | ||||
| //Unsupported (decimal, char) | //Unsupported (decimal, char) | ||||
| else | else | ||||
| throw new InvalidOperationException($"Serializing {type.Name} is not supported."); | throw new InvalidOperationException($"Serializing {type.Name} is not supported."); | ||||
| @@ -429,7 +432,7 @@ namespace Discord.ETF | |||||
| { | { | ||||
| return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) | return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) | ||||
| .Where(x => x.GetParameters()[0].ParameterType == paramType) | .Where(x => x.GetParameters()[0].ParameterType == paramType) | ||||
| .FirstOrDefault(); | |||||
| .Single(); | |||||
| } | } | ||||
| private static MethodInfo GetGenericWriteMethod(Type genericType) | private static MethodInfo GetGenericWriteMethod(Type genericType) | ||||
| { | { | ||||