From 2053d7c7c43831de2235c7fffb9e32120ed1b675 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 28 Jan 2016 07:43:07 -0400 Subject: [PATCH] Reworked several parts of the ETF serializer --- src/Discord.Net/ETF/ETFWriter.cs | 278 +++++++++++++++---------------- 1 file changed, 130 insertions(+), 148 deletions(-) diff --git a/src/Discord.Net/ETF/ETFWriter.cs b/src/Discord.Net/ETF/ETFWriter.cs index 6222d6427..f16706997 100644 --- a/src/Discord.Net/ETF/ETFWriter.cs +++ b/src/Discord.Net/ETF/ETFWriter.cs @@ -13,23 +13,13 @@ namespace Discord.ETF 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[] _nilExtBytes = new byte[] { (byte)ETFType.NIL_EXT}; 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 readonly static MethodInfo _writeTMethod = typeof(ETFWriter).GetTypeInfo() - .GetDeclaredMethods(nameof(Write)) - .Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0]) - .Single(); - private readonly static MethodInfo _writeNullableTMethod = typeof(ETFWriter).GetTypeInfo() - .GetDeclaredMethods(nameof(Write)) - .Where(x => - { - if (!x.IsGenericMethodDefinition) return false; - var p = x.GetParameters()[0].ParameterType.GetTypeInfo(); - return p.IsGenericType && p.GetGenericTypeDefinition() == typeof(Nullable<>); - }) - .Single(); + 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 readonly Stream _stream; @@ -58,29 +48,6 @@ namespace Discord.ETF _serializers = new ConcurrentDictionary(); _indirectSerializers = new ConcurrentDictionary(); } - - enum EnumTest1 { A, B, C } - public static byte[] Test() - { - using (var stream = new MemoryStream()) - { - using (var writer = new ETFWriter(stream)) - { - var request = new API.Client.Rest.SendMessageRequest(109384029348) - { - Content = "TestMsg", - Nonce = null, - IsTTS = false - }; - writer.Write(request); - /*writer.Write((EnumTest1?)EnumTest1.C); - writer.Write((object)(EnumTest1?)EnumTest1.C); - writer.Write((EnumTest1?)null); - writer.Write((object)(EnumTest1?)null);*/ - } - return stream.ToArray(); - } - } public void Write(bool value) { @@ -177,18 +144,19 @@ namespace Discord.ETF _stream.Write(_buffer, 0, 9); } - public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerMillisecond)); + public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); - public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } + public void Write(bool? value) { if (value.HasValue) Write((bool)value.Value); else WriteNil(); } public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } - public void Write(ushort? 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(uint? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } + public void Write(ushort? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } public void Write(int? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } - public void Write(ulong? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } + public void Write(uint? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } public void Write(long? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } - public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); } + public void Write(ulong? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } public void Write(double? value) { if (value.HasValue) Write(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(byte[] value) @@ -251,10 +219,13 @@ namespace Discord.ETF _buffer[2] = (byte)((length >> 16) & 0xFF); _buffer[3] = (byte)((length >> 8) & 0xFF); _buffer[4] = (byte)(length & 0xFF); + _stream.Write(_buffer, 0, 5); + for (int i = 0; i < array.Length; i++) Write(array[i]); - WriteNilExt(); - _stream.Write(_buffer, 0, 5); + + _buffer[0] = (byte)ETFType.NIL_EXT; + _stream.Write(_buffer, 0, 1); } else WriteNil(); @@ -269,12 +240,13 @@ namespace Discord.ETF _buffer[2] = (byte)((length >> 16) & 0xFF); _buffer[3] = (byte)((length >> 8) & 0xFF); _buffer[4] = (byte)(length & 0xFF); + _stream.Write(_buffer, 0, 5); + foreach (var pair in obj) { Write(pair.Key); Write(pair.Value); } - _stream.Write(_buffer, 0, 5); } else WriteNil(); @@ -292,147 +264,137 @@ namespace Discord.ETF WriteNil(); } + private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length); + public virtual void Flush() => _stream.Flush(); public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin); - - private Action CreateSerializer(Type type, TypeInfo typeInfo, bool isObject) + + #region Emit + private Action CreateSerializer(Type type, TypeInfo typeInfo, bool isDirect) { - var method = new DynamicMethod(!isObject ? "SerializeETF" : "SerializeIndirectETF", - null, new[] { typeof(ETFWriter), isObject? typeof(object) : type }, true); + var method = new DynamicMethod(isDirect ? "SerializeETF" : "SerializeIndirectETF", + null, new[] { typeof(ETFWriter), isDirect ? type : typeof(object) }, true); var generator = method.GetILGenerator(); - - if (typeInfo.IsPrimitive || typeInfo.IsEnum) + + generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) + generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), value + if (!isDirect) { - generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) - generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), value - if (isObject && typeInfo.IsValueType) - generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), value - EmitETFWriteValue(generator, type, typeInfo); + if (typeInfo.IsValueType) //Unbox value types + generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), real_value + else //Cast reference types + generator.Emit(OpCodes.Castclass, type); //ETFWriter(this), real_value + generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(type), null); //Call generic version } else - { - //Scan for certain interfaces - Type dictionaryI = null, enumerableI = null; - foreach (var i in typeInfo.ImplementedInterfaces - .Where(x => x.IsConstructedGenericType)) - { - if (i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) - { - //TODO: Emit null check - dictionaryI = i; - break; - } - else if (i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - //TODO: Emit null check - enumerableI = i; - break; - } - } - - if (dictionaryI != null) - { - throw new NotImplementedException(); - } - else if (enumerableI != null) - { - throw new NotImplementedException(); - } - else - { - //TODO: Add field/property names - typeInfo.ForEachField(f => - { - string name; - if (!f.IsPublic || !IsETFProperty(f, out name)) return; - - generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) - generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name - generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); - generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) - generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj - generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue - if (isObject && typeInfo.IsValueType) - generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), obj.fieldValue - EmitETFWriteValue(generator, f.FieldType); - }); - - typeInfo.ForEachProperty(p => - { - string name; - if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; - - generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) - generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name - generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); - generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) - generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj - generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(this), obj.propValue - if (isObject && typeInfo.IsValueType) - generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), obj.propValue - EmitETFWriteValue(generator, p.PropertyType); - }); - } - } + EmitWriteValue(generator, type, typeInfo, true); generator.Emit(OpCodes.Ret); return method.CreateDelegate(typeof(Action)) as Action; } - - private void EmitETFWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo = null) - { - if (typeInfo == null) - typeInfo = type.GetTypeInfo(); - + private void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) + { //Convert enum types to their base type if (typeInfo.IsEnum) { type = Enum.GetUnderlyingType(type); typeInfo = type.GetTypeInfo(); - } + } - //Check if this type already has a direct call or simple conversion + //Primitives/Enums Type targetType = null; if (!typeInfo.IsEnum && IsType(type, typeof(long), typeof(ulong), typeof(double), typeof(bool), typeof(string), typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), typeof(bool?), typeof(float?), typeof(double?), typeof(object), typeof(DateTime))) - targetType = type; //Direct + { + //No conversion needed + targetType = type; + } else if (IsType(type, typeof(sbyte), typeof(short), typeof(int))) { + //Convert to long generator.Emit(OpCodes.Conv_I8); targetType = typeof(long); } else if (IsType(type, typeof(byte), typeof(ushort), typeof(uint))) { + //Convert to ulong generator.Emit(OpCodes.Conv_U8); targetType = typeof(ulong); } else if (IsType(type, typeof(float))) { + //Convert to double generator.Emit(OpCodes.Conv_R8); targetType = typeof(double); } - - //Primitives/Enums if (targetType != null) generator.EmitCall(OpCodes.Call, GetWriteMethod(targetType), null); - //Nullable Non-Primitives - else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) + //Dictionaries + else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces + .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) + generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + + //Enumerable + else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces + .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + + //Nullable Structs + else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && + typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); //Structs/Classes else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) - generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(type), null); + { + if (isTop) + { + typeInfo.ForEachField(f => + { + string name; + if (!f.IsPublic || !IsETFProperty(f, out name)) return; + + generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) + generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); + generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) + generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj + generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue + EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false); + }); + + typeInfo.ForEachProperty(p => + { + string name; + if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; + + generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) + generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); + generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) + generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj + generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(this), obj.propValue + EmitWriteValue(generator, p.PropertyType, p.PropertyType.GetTypeInfo(), false); + }); + } + else + { + //While we could drill deeper and make a large serializer that also serializes all subclasses, + //it's more efficient to serialize on a per-type basis via another Write call. + generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); + } + } //Unsupported (decimal, char) else throw new InvalidOperationException($"Serializing {type.Name} is not supported."); } - private bool IsType(Type type, params Type[] types) + private static bool IsType(Type type, params Type[] types) { for (int i = 0; i < types.Length; i++) { @@ -441,17 +403,7 @@ namespace Discord.ETF } return false; } - private MethodInfo GetWriteMethod(Type paramType) - { - return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) - .Where(x => x.GetParameters()[0].ParameterType == paramType) - .FirstOrDefault(); - } - - private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length); - private void WriteNilExt() => _stream.Write(_nilExtBytes, 0, _nilExtBytes.Length); - - private bool IsETFProperty(FieldInfo f, out string name) + private static bool IsETFProperty(FieldInfo f, out string name) { var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); if (attrib != null) @@ -462,7 +414,7 @@ namespace Discord.ETF name = null; return false; } - private bool IsETFProperty(PropertyInfo p, out string name) + private static bool IsETFProperty(PropertyInfo p, out string name) { var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); if (attrib != null) @@ -474,6 +426,36 @@ namespace Discord.ETF return false; } + private static MethodInfo GetWriteMethod(Type paramType) + { + return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) + .Where(x => x.GetParameters()[0].ParameterType == paramType) + .FirstOrDefault(); + } + private static MethodInfo GetGenericWriteMethod(Type genericType) + { + if (genericType == null) + { + return typeof(ETFWriter).GetTypeInfo() + .GetDeclaredMethods(nameof(Write)) + .Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0]) + .Single(); + } + else + { + return typeof(ETFWriter).GetTypeInfo() + .GetDeclaredMethods(nameof(Write)) + .Where(x => + { + if (!x.IsGenericMethodDefinition) return false; + var p = x.GetParameters()[0].ParameterType.GetTypeInfo(); + return p.IsGenericType && p.GetGenericTypeDefinition() == genericType; + }) + .Single(); + } + } + #endregion + #region IDisposable private bool _isDisposed = false;