| @@ -4,9 +4,9 @@ namespace Discord | |||||
| { | { | ||||
| public enum PermissionTarget | public enum PermissionTarget | ||||
| { | { | ||||
| [ModelEnum("role")] | |||||
| [ModelEnumValue("role")] | |||||
| Role, | Role, | ||||
| [ModelEnum("user")] | |||||
| [ModelEnumValue("user")] | |||||
| User | User | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,10 +1,13 @@ | |||||
| namespace Discord | |||||
| using Discord.Serialization; | |||||
| namespace Discord | |||||
| { | { | ||||
| public struct Overwrite | public struct Overwrite | ||||
| { | { | ||||
| /// <summary> Gets the unique identifier for the object this overwrite is targeting. </summary> | /// <summary> Gets the unique identifier for the object this overwrite is targeting. </summary> | ||||
| public ulong TargetId { get; } | public ulong TargetId { get; } | ||||
| /// <summary> Gets the type of object this overwrite is targeting. </summary> | /// <summary> Gets the type of object this overwrite is targeting. </summary> | ||||
| [ModelStringEnum] | |||||
| public PermissionTarget TargetType { get; } | public PermissionTarget TargetType { get; } | ||||
| /// <summary> Gets the permissions associated with this overwrite entry. </summary> | /// <summary> Gets the permissions associated with this overwrite entry. </summary> | ||||
| public OverwritePermissions Permissions { get; } | public OverwritePermissions Permissions { get; } | ||||
| @@ -5,13 +5,13 @@ namespace Discord | |||||
| public enum UserStatus | public enum UserStatus | ||||
| { | { | ||||
| Offline, | Offline, | ||||
| [ModelEnum("online")] | |||||
| [ModelEnumValue("online")] | |||||
| Online, | Online, | ||||
| [ModelEnum("idle")] | |||||
| [ModelEnumValue("idle")] | |||||
| Idle, | Idle, | ||||
| [ModelEnum("idle", EnumValueType.WriteOnly)] | |||||
| [ModelEnumValue("idle", EnumValueType.WriteOnly)] | |||||
| AFK, | AFK, | ||||
| [ModelEnum("dnd")] | |||||
| [ModelEnumValue("dnd")] | |||||
| DoNotDisturb, | DoNotDisturb, | ||||
| Invisible, | Invisible, | ||||
| } | } | ||||
| @@ -10,6 +10,7 @@ namespace Discord.API | |||||
| [ModelProperty("guild_id")] | [ModelProperty("guild_id")] | ||||
| public Optional<ulong> GuildId { get; set; } | public Optional<ulong> GuildId { get; set; } | ||||
| [ModelProperty("status")] | [ModelProperty("status")] | ||||
| [ModelStringEnum] | |||||
| public UserStatus Status { get; set; } | public UserStatus Status { get; set; } | ||||
| [ModelProperty("game")] | [ModelProperty("game")] | ||||
| public Game Game { get; set; } | public Game Game { get; set; } | ||||
| @@ -191,7 +191,7 @@ namespace Discord.API | |||||
| options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | ||||
| options.IsClientBucket = AuthTokenType == TokenType.User; | options.IsClientBucket = AuthTokenType == TokenType.User; | ||||
| if (_formatters.TryDequeue(out var data)) | |||||
| if (!_formatters.TryDequeue(out var data)) | |||||
| data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | ||||
| try | try | ||||
| { | { | ||||
| @@ -240,7 +240,7 @@ namespace Discord.API | |||||
| options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; | ||||
| options.IsClientBucket = AuthTokenType == TokenType.User; | options.IsClientBucket = AuthTokenType == TokenType.User; | ||||
| if (_formatters.TryDequeue(out var data)) | |||||
| if (!_formatters.TryDequeue(out var data)) | |||||
| data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | ||||
| try | try | ||||
| { | { | ||||
| @@ -1168,13 +1168,12 @@ namespace Discord.API | |||||
| throw new InvalidOperationException("Client is not logged in."); | throw new InvalidOperationException("Client is not logged in."); | ||||
| } | } | ||||
| protected static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | protected static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); | ||||
| protected ReadOnlyBuffer<byte> SerializeJson(ArrayFormatter data, object value) | |||||
| protected ReadOnlyBuffer<byte> SerializeJson<T>(ArrayFormatter data, T value) | |||||
| { | { | ||||
| _serializer.Write(data, value); | _serializer.Write(data, value); | ||||
| return new ReadOnlyBuffer<byte>(data.Formatted.Array, 0, data.Formatted.Count); | return new ReadOnlyBuffer<byte>(data.Formatted.Array, 0, data.Formatted.Count); | ||||
| } | } | ||||
| protected T DeserializeJson<T>(ReadOnlyBuffer<byte> data) | protected T DeserializeJson<T>(ReadOnlyBuffer<byte> data) | ||||
| where T : class, new() | |||||
| { | { | ||||
| return _serializer.Read<T>(data); | return _serializer.Read<T>(data); | ||||
| } | } | ||||
| @@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters | |||||
| _innerConverter = innerConverter; | _innerConverter = innerConverter; | ||||
| } | } | ||||
| public EntityOrId<T> Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public EntityOrId<T> Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| if (reader.ValueType == JsonValueType.Number) | if (reader.ValueType == JsonValueType.Number) | ||||
| return new EntityOrId<T>(reader.ParseUInt64()); | return new EntityOrId<T>(reader.ParseUInt64()); | ||||
| return new EntityOrId<T>(_innerConverter.Read(map, ref reader, false)); | |||||
| return new EntityOrId<T>(_innerConverter.Read(map, model, ref reader, false)); | |||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, EntityOrId<T> value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, EntityOrId<T> value, bool isTopLevel) | |||||
| { | { | ||||
| if (value.Object != null) | if (value.Object != null) | ||||
| _innerConverter.Write(map, ref writer, value.Object, isTopLevel); | |||||
| _innerConverter.Write(map, model, ref writer, value.Object, isTopLevel); | |||||
| else | else | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| @@ -5,7 +5,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| { | { | ||||
| internal class ImagePropertyConverter : IJsonPropertyConverter<API.Image> | internal class ImagePropertyConverter : IJsonPropertyConverter<API.Image> | ||||
| { | { | ||||
| public API.Image Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public API.Image Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return new API.Image(reader.ParseString()); | return new API.Image(reader.ParseString()); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, API.Image value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, API.Image value, bool isTopLevel) | |||||
| { | { | ||||
| string str; | string str; | ||||
| if (value.Stream != null) | if (value.Stream != null) | ||||
| @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| { | { | ||||
| internal class Int53PropertyConverter : IJsonPropertyConverter<long> | internal class Int53PropertyConverter : IJsonPropertyConverter<long> | ||||
| { | { | ||||
| public long Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number"); | throw new SerializationException("Bad input, expected Number"); | ||||
| return reader.ParseInt64(); | return reader.ParseInt64(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, long value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -11,13 +11,13 @@ namespace Discord.Serialization.Json.Converters | |||||
| _innerConverter = innerConverter; | _innerConverter = innerConverter; | ||||
| } | } | ||||
| public Optional<T> Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| => new Optional<T>(_innerConverter.Read(map, ref reader, isTopLevel)); | |||||
| public Optional<T> Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| => new Optional<T>(_innerConverter.Read(map, model, ref reader, isTopLevel)); | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, Optional<T> value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, Optional<T> value, bool isTopLevel) | |||||
| { | { | ||||
| if (value.IsSpecified) | if (value.IsSpecified) | ||||
| _innerConverter.Write(map, ref writer, value.Value, isTopLevel); | |||||
| _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| { | { | ||||
| internal class UInt53PropertyConverter : IJsonPropertyConverter<ulong> | internal class UInt53PropertyConverter : IJsonPropertyConverter<ulong> | ||||
| { | { | ||||
| public ulong Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number"); | throw new SerializationException("Bad input, expected Number"); | ||||
| return reader.ParseUInt64(); | return reader.ParseUInt64(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -0,0 +1,11 @@ | |||||
| namespace Discord.Serialization | |||||
| { | |||||
| public static class ModelSelectorGroups | |||||
| { | |||||
| public const string GatewayFrame = nameof(GatewayFrame); | |||||
| public const string GatewayDispatchFrame = nameof(GatewayDispatchFrame); | |||||
| public const string VoiceFrame = nameof(VoiceFrame); | |||||
| public const string VoiceDispatchFrame = nameof(VoiceDispatchFrame); | |||||
| public const string RpcFrame = nameof(RpcFrame); | |||||
| } | |||||
| } | |||||
| @@ -14,7 +14,9 @@ namespace Discord.API.Rpc | |||||
| public Optional<string> Event { get; set; } | public Optional<string> Event { get; set; } | ||||
| [ModelProperty("data")] | [ModelProperty("data")] | ||||
| public Optional<ReadOnlyBuffer<byte>> Data { get; set; } | public Optional<ReadOnlyBuffer<byte>> Data { get; set; } | ||||
| [ModelProperty("args")] | [ModelProperty("args")] | ||||
| [ModelSelector(ModelSelectorGroups.RpcFrame, nameof(Event))] | |||||
| public object Args { get; set; } | public object Args { get; set; } | ||||
| } | } | ||||
| } | } | ||||
| @@ -5,6 +5,7 @@ using Discord.Net.Rest; | |||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rpc; | using Discord.Rpc; | ||||
| using Discord.Serialization; | using Discord.Serialization; | ||||
| using Discord.Serialization.Json; | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -41,11 +42,11 @@ namespace Discord.API | |||||
| } | } | ||||
| public Task SetResultAsync(ReadOnlyBuffer<byte> data) | public Task SetResultAsync(ReadOnlyBuffer<byte> data) | ||||
| { | { | ||||
| return Promise.TrySetResultAsync(Serializer.Json.Read<T>(data)); | |||||
| return Promise.TrySetResultAsync(DiscordJsonSerializer.Global.Read<T>(data)); | |||||
| } | } | ||||
| public Task SetExceptionAsync(ReadOnlyBuffer<byte> data) | public Task SetExceptionAsync(ReadOnlyBuffer<byte> data) | ||||
| { | { | ||||
| var error = Serializer.Json.Read<ErrorEvent>(data); | |||||
| var error = DiscordJsonSerializer.Global.Read<ErrorEvent>(data); | |||||
| return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -231,12 +232,12 @@ namespace Discord.API | |||||
| try | try | ||||
| { | { | ||||
| var guid = Guid.NewGuid(); | var guid = Guid.NewGuid(); | ||||
| payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = SerializeJson(data1, payload), Nonce = guid }; | |||||
| var frame = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = SerializeJson(data1, payload), Nonce = guid }; | |||||
| var requestTracker = new RpcRequest<TResponse>(options); | var requestTracker = new RpcRequest<TResponse>(options); | ||||
| _requests[guid] = requestTracker; | _requests[guid] = requestTracker; | ||||
| await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, SerializeJson(data2, payload), true, options)).ConfigureAwait(false); | |||||
| await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, SerializeJson(data2, frame), true, options)).ConfigureAwait(false); | |||||
| await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); | await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); | ||||
| return await requestTracker.Promise.Task.ConfigureAwait(false); | return await requestTracker.Promise.Task.ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -8,6 +8,7 @@ using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Threading; | using System.Threading; | ||||
| using Discord.Serialization; | using Discord.Serialization; | ||||
| using Discord.Serialization.Json; | |||||
| namespace Discord.Rpc | namespace Discord.Rpc | ||||
| { | { | ||||
| @@ -37,7 +38,7 @@ namespace Discord.Rpc | |||||
| _authorizeLock = new SemaphoreSlim(1, 1); | _authorizeLock = new SemaphoreSlim(1, 1); | ||||
| _rpcLogger = LogManager.CreateLogger("RPC"); | _rpcLogger = LogManager.CreateLogger("RPC"); | ||||
| _serializer = new Serializer(SerializationFormat.Json); | |||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -10,12 +10,12 @@ namespace Discord.Serialization | |||||
| } | } | ||||
| [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] | [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] | ||||
| public class ModelEnumAttribute : Attribute | |||||
| public class ModelEnumValueAttribute : Attribute | |||||
| { | { | ||||
| public string Key { get; } | public string Key { get; } | ||||
| public EnumValueType Type { get; } | public EnumValueType Type { get; } | ||||
| public ModelEnumAttribute(string key, EnumValueType type = EnumValueType.ReadWrite) | |||||
| public ModelEnumValueAttribute(string key, EnumValueType type = EnumValueType.ReadWrite) | |||||
| { | { | ||||
| Key = key; | Key = key; | ||||
| Type = type; | Type = type; | ||||
| @@ -8,7 +8,9 @@ namespace Discord.Serialization | |||||
| public string Key { get; } | public string Key { get; } | ||||
| public bool ExcludeNull { get; set; } | public bool ExcludeNull { get; set; } | ||||
| public ModelPropertyAttribute(string key = null) | |||||
| public ModelPropertyAttribute() | |||||
| : this(null) { } | |||||
| public ModelPropertyAttribute(string key) | |||||
| { | { | ||||
| Key = key; | Key = key; | ||||
| } | } | ||||
| @@ -0,0 +1,17 @@ | |||||
| using System; | |||||
| namespace Discord.Serialization | |||||
| { | |||||
| [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] | |||||
| public class ModelSelectorAttribute : Attribute | |||||
| { | |||||
| public string SelectorProperty { get; } | |||||
| public string SelectorGroup { get; } | |||||
| public ModelSelectorAttribute(string selectorProperty, string selectorGroup) | |||||
| { | |||||
| SelectorProperty = selectorProperty; | |||||
| SelectorGroup = selectorGroup; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| using System; | |||||
| namespace Discord.Serialization | |||||
| { | |||||
| [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] | |||||
| public class ModelStringEnumAttribute : Attribute | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -17,14 +17,20 @@ namespace Discord.Serialization | |||||
| private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); | private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); | ||||
| private readonly Serializer _serializer; | private readonly Serializer _serializer; | ||||
| private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>(); | |||||
| private readonly Dictionary<Type, ConverterTypeCollection> _types = new Dictionary<Type, ConverterTypeCollection>(); | |||||
| private readonly Dictionary<Type, ConverterTypeCollection> _mappedGenericTypes = new Dictionary<Type, ConverterTypeCollection>(); | |||||
| private readonly ConverterTypeCollection _genericTypes = new ConverterTypeCollection(); | |||||
| private readonly ConcurrentDictionary<Type, object> _cache; | |||||
| private readonly Dictionary<Type, ConverterTypeCollection> _types; | |||||
| private readonly Dictionary<Type, ConverterTypeCollection> _mappedGenericTypes; | |||||
| private readonly ConverterTypeCollection _genericTypes; | |||||
| private readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, ISelectorGroup>> _selectorGroups; | |||||
| internal ConverterCollection(Serializer serializer) | internal ConverterCollection(Serializer serializer) | ||||
| { | { | ||||
| _serializer = serializer; | _serializer = serializer; | ||||
| _cache = new ConcurrentDictionary<Type, object>(); | |||||
| _types = new Dictionary<Type, ConverterTypeCollection>(); | |||||
| _mappedGenericTypes = new Dictionary<Type, ConverterTypeCollection>(); | |||||
| _genericTypes = new ConverterTypeCollection(); | |||||
| _selectorGroups = new ConcurrentDictionary<Type, ConcurrentDictionary<string, ISelectorGroup>>(); | |||||
| } | } | ||||
| public void Add(Type type, Type converterType) | public void Add(Type type, Type converterType) | ||||
| @@ -66,15 +72,22 @@ namespace Discord.Serialization | |||||
| _mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection()); | _mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection()); | ||||
| converters.Conditionals.Add((condition, openConverterType)); | converters.Conditionals.Add((condition, openConverterType)); | ||||
| } | } | ||||
| public void AddSelector(string groupKey, Type keyType, object keyValue, Type converterType) | |||||
| { | |||||
| var group = GetSelectorGroup(keyType, groupKey); | |||||
| group.AddDynamicConverter(keyValue, converterType); | |||||
| } | |||||
| public object Get(Type type, PropertyInfo propInfo = null) | public object Get(Type type, PropertyInfo propInfo = null) | ||||
| { | { | ||||
| if (!_cache.TryGetValue(type, out var result)) | |||||
| if (propInfo == null) //Can only cache top-level due to attribute influences | |||||
| { | { | ||||
| object converter = Create(type, propInfo); | |||||
| result = _cache.GetOrAdd(type, converter); | |||||
| if (_cache.TryGetValue(type, out var result)) | |||||
| return result; | |||||
| return _cache.GetOrAdd(type, Create(type, propInfo)); | |||||
| } | } | ||||
| return result; | |||||
| return Create(type, propInfo); | |||||
| } | } | ||||
| private object Create(Type type, PropertyInfo propInfo) | private object Create(Type type, PropertyInfo propInfo) | ||||
| { | { | ||||
| @@ -108,7 +121,7 @@ namespace Discord.Serialization | |||||
| converterType = converterType.MakeGenericType(type); | converterType = converterType.MakeGenericType(type); | ||||
| var converterTypeInfo = converterType.GetTypeInfo(); | var converterTypeInfo = converterType.GetTypeInfo(); | ||||
| var constructors = converterTypeInfo.DeclaredConstructors.ToArray(); | |||||
| var constructors = converterTypeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); | |||||
| if (constructors.Length == 0) | if (constructors.Length == 0) | ||||
| throw new SerializationException($"{converterType.Name} is missing a constructor"); | throw new SerializationException($"{converterType.Name} is missing a constructor"); | ||||
| if (constructors.Length != 1) | if (constructors.Length != 1) | ||||
| @@ -148,5 +161,17 @@ namespace Discord.Serialization | |||||
| return converters.DefaultConverterType; | return converters.DefaultConverterType; | ||||
| return null; | return null; | ||||
| } | } | ||||
| public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) | |||||
| { | |||||
| var keyGroup = GetSelectorKeyGroup(keyType); | |||||
| if (keyGroup.TryGetValue(groupKey, out var selectorGroup)) | |||||
| return selectorGroup; | |||||
| return CreateSelectorGroup(keyType, keyGroup, groupKey); | |||||
| } | |||||
| private ISelectorGroup CreateSelectorGroup(Type keyType, ConcurrentDictionary<string, ISelectorGroup> keyGroup, string groupKey) | |||||
| => keyGroup.GetOrAdd(groupKey, Activator.CreateInstance(typeof(SelectorGroup<>).MakeGenericType(keyType)) as ISelectorGroup); | |||||
| private ConcurrentDictionary<string, ISelectorGroup> GetSelectorKeyGroup(Type keyType) | |||||
| => _selectorGroups.GetOrAdd(keyType, _ => new ConcurrentDictionary<string, ISelectorGroup>()); | |||||
| } | } | ||||
| } | } | ||||
| @@ -16,9 +16,12 @@ namespace Discord.Serialization | |||||
| { | { | ||||
| public static readonly EnumMap<T> Instance = new EnumMap<T>(); | public static readonly EnumMap<T> Instance = new EnumMap<T>(); | ||||
| private readonly BufferDictionary<T> _keyToValue; | |||||
| private readonly Dictionary<string, T> _keyToValue; | |||||
| private readonly BufferDictionary<T> _utf8KeyToValue; | |||||
| private readonly Dictionary<long, T> _intToValue; | |||||
| private readonly Dictionary<T, string> _valueToKey; | private readonly Dictionary<T, string> _valueToKey; | ||||
| private readonly Dictionary<T, ReadOnlyBuffer<byte>> _valueToUtf8Key; | |||||
| private readonly Dictionary<T, long> _valueToInt; | |||||
| public EnumMap() | public EnumMap() | ||||
| { | { | ||||
| @@ -26,50 +29,104 @@ namespace Discord.Serialization | |||||
| if (!typeInfo.IsEnum) | if (!typeInfo.IsEnum) | ||||
| throw new InvalidOperationException($"{typeInfo.Name} is not an Enum"); | throw new InvalidOperationException($"{typeInfo.Name} is not an Enum"); | ||||
| _keyToValue = new BufferDictionary<T>(); | |||||
| _keyToValue = new Dictionary<string, T>(); | |||||
| _utf8KeyToValue = new BufferDictionary<T>(); | |||||
| _intToValue = new Dictionary<long, T>(); | |||||
| _valueToKey = new Dictionary<T, string>(); | _valueToKey = new Dictionary<T, string>(); | ||||
| _valueToUtf8Key = new Dictionary<T, ReadOnlyBuffer<byte>>(); | |||||
| _valueToInt = new Dictionary<T, long>(); | |||||
| foreach (T val in Enum.GetValues(typeof(T)).OfType<T>()) | foreach (T val in Enum.GetValues(typeof(T)).OfType<T>()) | ||||
| { | { | ||||
| var fieldInfo = typeInfo.GetDeclaredField(Enum.GetName(typeof(T), val)); | var fieldInfo = typeInfo.GetDeclaredField(Enum.GetName(typeof(T), val)); | ||||
| var attr = fieldInfo.GetCustomAttribute<ModelEnumAttribute>(); | |||||
| var attr = fieldInfo.GetCustomAttribute<ModelEnumValueAttribute>(); | |||||
| if (attr != null) | if (attr != null) | ||||
| { | { | ||||
| var key = new ReadOnlyBuffer<byte>(new Utf8String(attr.Key).Bytes.ToArray()); | |||||
| string key = attr.Key; | |||||
| var keyBuffer = new ReadOnlyBuffer<byte>(new Utf8String(attr.Key).Bytes.ToArray()); | |||||
| if (attr.Type != EnumValueType.WriteOnly) | if (attr.Type != EnumValueType.WriteOnly) | ||||
| _keyToValue.Add(key, val); | |||||
| if (attr.Type != EnumValueType.ReadOnly) | |||||
| { | { | ||||
| _valueToUtf8Key.Add(val, key); | |||||
| _valueToKey.Add(val, attr.Key); | |||||
| _keyToValue.Add(key, val); | |||||
| _utf8KeyToValue.Add(keyBuffer, val); | |||||
| } | } | ||||
| if (attr.Type != EnumValueType.ReadOnly) | |||||
| _valueToKey.Add(val, key); | |||||
| } | } | ||||
| var underlyingType = Enum.GetUnderlyingType(typeof(T)); | |||||
| long baseVal; | |||||
| if (underlyingType == typeof(sbyte)) | |||||
| baseVal = (sbyte)(ValueType)val; | |||||
| else if (underlyingType == typeof(short)) | |||||
| baseVal = (short)(ValueType)val; | |||||
| else if (underlyingType == typeof(int)) | |||||
| baseVal = (int)(ValueType)val; | |||||
| else if (underlyingType == typeof(long)) | |||||
| baseVal = (long)(ValueType)val; | |||||
| else if (underlyingType == typeof(byte)) | |||||
| baseVal = (byte)(ValueType)val; | |||||
| else if (underlyingType == typeof(ushort)) | |||||
| baseVal = (ushort)(ValueType)val; | |||||
| else if (underlyingType == typeof(uint)) | |||||
| baseVal = (uint)(ValueType)val; | |||||
| else if (underlyingType == typeof(ulong)) | |||||
| baseVal = (long)(ulong)(ValueType)val; | |||||
| else | |||||
| throw new SerializationException($"Unsupported underlying enum type: {underlyingType.Name}"); | |||||
| _intToValue.Add(baseVal, val); | |||||
| _valueToInt.Add(val, baseVal); | |||||
| } | } | ||||
| } | } | ||||
| public T GetValue(ReadOnlyBuffer<byte> key) | |||||
| public T FromKey(ReadOnlyBuffer<byte> key) | |||||
| { | { | ||||
| if (_keyToValue.TryGetValue(key, out var value)) | |||||
| if (_utf8KeyToValue.TryGetValue(key, out var value)) | |||||
| return value; | return value; | ||||
| throw new SerializationException($"Unknown enum key: {new Utf8String(key.Span).ToString()}"); | throw new SerializationException($"Unknown enum key: {new Utf8String(key.Span).ToString()}"); | ||||
| } | } | ||||
| public T GetValue(ReadOnlySpan<byte> key) | |||||
| public T FromKey(ReadOnlySpan<byte> key) | |||||
| { | { | ||||
| if (_keyToValue.TryGetValue(key, out var value)) | |||||
| if (_utf8KeyToValue.TryGetValue(key, out var value)) | |||||
| return value; | return value; | ||||
| throw new SerializationException($"Unknown enum key: {new Utf8String(key).ToString()}"); | throw new SerializationException($"Unknown enum key: {new Utf8String(key).ToString()}"); | ||||
| } | } | ||||
| public string GetKey(T value) | |||||
| public string ToUtf16Key(T value) | |||||
| { | { | ||||
| if (_valueToKey.TryGetValue(value, out var key)) | if (_valueToKey.TryGetValue(value, out var key)) | ||||
| return key; | return key; | ||||
| throw new SerializationException($"Unknown enum value: {value}"); | throw new SerializationException($"Unknown enum value: {value}"); | ||||
| } | } | ||||
| public ReadOnlyBuffer<byte> GetUtf8Key(T value) | |||||
| public T FromInt64(ReadOnlyBuffer<byte> intBuffer) | |||||
| => FromInt64(intBuffer.Span); | |||||
| public T FromInt64(ReadOnlySpan<byte> intBuffer) | |||||
| { | { | ||||
| if (_valueToUtf8Key.TryGetValue(value, out var key)) | |||||
| return key; | |||||
| long intValue = intBuffer.ParseInt64(); | |||||
| if (_intToValue.TryGetValue(intValue, out var value)) | |||||
| return value; | |||||
| throw new SerializationException($"Unknown enum value: {intValue}"); | |||||
| } | |||||
| public long ToInt64(T value) | |||||
| { | |||||
| if (_valueToInt.TryGetValue(value, out var intValue)) | |||||
| return intValue; | |||||
| throw new SerializationException($"Unknown enum value: {value}"); | |||||
| } | |||||
| public T FromUInt64(ReadOnlyBuffer<byte> intBuffer) | |||||
| => FromUInt64(intBuffer.Span); | |||||
| public T FromUInt64(ReadOnlySpan<byte> intBuffer) | |||||
| { | |||||
| ulong intValue = intBuffer.ParseUInt64(); | |||||
| if (_intToValue.TryGetValue((long)intValue, out var value)) | |||||
| return value; | |||||
| throw new SerializationException($"Unknown enum value: {intValue}"); | |||||
| } | |||||
| public ulong ToUInt64(T value) | |||||
| { | |||||
| if (_valueToInt.TryGetValue(value, out var intValue)) | |||||
| return (ulong)intValue; | |||||
| throw new SerializationException($"Unknown enum value: {value}"); | throw new SerializationException($"Unknown enum value: {value}"); | ||||
| } | } | ||||
| } | } | ||||
| @@ -29,7 +29,7 @@ namespace Discord.Serialization | |||||
| public static Guid ParseGuid(this JsonReader reader) => reader.Value.ParseGuid(); | public static Guid ParseGuid(this JsonReader reader) => reader.Value.ParseGuid(); | ||||
| } | } | ||||
| public static class JsonUtils | |||||
| public static class JsonReaderUtils | |||||
| { | { | ||||
| public static void Skip(ref JsonReader reader) | public static void Skip(ref JsonReader reader) | ||||
| { | { | ||||
| @@ -3,7 +3,7 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class ListPropertyConverter<T> : IJsonPropertyConverter<List<T>> | |||||
| public class ListPropertyConverter<T> : IJsonPropertyConverter<List<T>> | |||||
| { | { | ||||
| private readonly IJsonPropertyConverter<T> _innerConverter; | private readonly IJsonPropertyConverter<T> _innerConverter; | ||||
| @@ -12,25 +12,25 @@ namespace Discord.Serialization.Json.Converters | |||||
| _innerConverter = innerConverter; | _innerConverter = innerConverter; | ||||
| } | } | ||||
| public List<T> Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public List<T> Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) | if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) | ||||
| throw new SerializationException("Bad input, expected StartArray"); | throw new SerializationException("Bad input, expected StartArray"); | ||||
| var list = new List<T>(); | var list = new List<T>(); | ||||
| while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) | while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) | ||||
| list.Add(_innerConverter.Read(map, ref reader, false)); | |||||
| list.Add(_innerConverter.Read(map, model, ref reader, false)); | |||||
| return list; | return list; | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, List<T> value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, List<T> value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteArrayStart(map.Key); | writer.WriteArrayStart(map.Key); | ||||
| else | else | ||||
| writer.WriteArrayStart(); | writer.WriteArrayStart(); | ||||
| for (int i = 0; i < value.Count; i++) | for (int i = 0; i < value.Count; i++) | ||||
| _innerConverter.Write(map, ref writer, value[i], false); | |||||
| _innerConverter.Write(map, model, ref writer, value[i], false); | |||||
| writer.WriteArrayEnd(); | writer.WriteArrayEnd(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,35 @@ | |||||
| using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | |||||
| { | |||||
| //TODO: Only supports cases where the key arrives first | |||||
| public class DynamicPropertyConverter : IJsonPropertyConverter<object> | |||||
| { | |||||
| public object Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | |||||
| if (map.GetDynamicConverter(model, false) is IJsonPropertyReader<object> converter) | |||||
| return converter.Read(map, model, ref reader, isTopLevel); | |||||
| else | |||||
| { | |||||
| JsonReaderUtils.Skip(ref reader); | |||||
| return null; | |||||
| } | |||||
| } | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, object value, bool isTopLevel) | |||||
| { | |||||
| if (value == null) | |||||
| { | |||||
| if (isTopLevel) | |||||
| writer.WriteAttributeNull(map.Key); | |||||
| else | |||||
| writer.WriteNull(); | |||||
| } | |||||
| else | |||||
| { | |||||
| var converter = map.GetDynamicConverter(model, true) as IJsonPropertyWriter<object>; | |||||
| converter.Write(map, model, ref writer, value, isTopLevel); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -2,22 +2,68 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class EnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| public class Int64EnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| where T : struct | where T : struct | ||||
| { | { | ||||
| private static readonly EnumMap<T> _map = EnumMap.For<T>(); | private static readonly EnumMap<T> _map = EnumMap.For<T>(); | ||||
| public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | |||||
| if (isTopLevel) | |||||
| reader.Read(); | |||||
| if (reader.ValueType != JsonValueType.Number && reader.ValueType != JsonValueType.String) | |||||
| throw new SerializationException("Bad input, expected Number or String"); | |||||
| return _map.FromInt64(reader.Value); | |||||
| } | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) | |||||
| { | |||||
| long key = _map.ToInt64(value); | |||||
| if (isTopLevel) | |||||
| writer.WriteAttribute(map.Key, key); | |||||
| else | |||||
| writer.WriteValue(key); | |||||
| } | |||||
| } | |||||
| public class UInt64EnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| where T : struct | |||||
| { | |||||
| private static readonly EnumMap<T> _map = EnumMap.For<T>(); | |||||
| public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | |||||
| if (isTopLevel) | |||||
| reader.Read(); | |||||
| if (reader.ValueType != JsonValueType.Number && reader.ValueType != JsonValueType.String) | |||||
| throw new SerializationException("Bad input, expected Number or String"); | |||||
| return _map.FromUInt64(reader.Value); | |||||
| } | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) | |||||
| { | |||||
| ulong key = _map.ToUInt64(value); | |||||
| if (isTopLevel) | |||||
| writer.WriteAttribute(map.Key, key); | |||||
| else | |||||
| writer.WriteValue(key); | |||||
| } | |||||
| } | |||||
| public class StringEnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| where T : struct | |||||
| { | |||||
| private static readonly EnumMap<T> _map = EnumMap.For<T>(); | |||||
| public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| if (reader.ValueType != JsonValueType.String) | if (reader.ValueType != JsonValueType.String) | ||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return _map.GetValue(reader.Value); | |||||
| return _map.FromKey(reader.Value); | |||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) | |||||
| { | { | ||||
| string key = _map.GetKey(value); | |||||
| string key = _map.ToUtf16Key(value); | |||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, key); | writer.WriteAttribute(map.Key, key); | ||||
| else | else | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class NullablePropertyConverter<T> : IJsonPropertyConverter<T?> | |||||
| public class NullablePropertyConverter<T> : IJsonPropertyConverter<T?> | |||||
| where T : struct | where T : struct | ||||
| { | { | ||||
| private readonly IJsonPropertyConverter<T> _innerConverter; | private readonly IJsonPropertyConverter<T> _innerConverter; | ||||
| @@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters | |||||
| _innerConverter = innerConverter; | _innerConverter = innerConverter; | ||||
| } | } | ||||
| public T? Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public T? Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| if (reader.ValueType == JsonValueType.Null) | if (reader.ValueType == JsonValueType.Null) | ||||
| return null; | return null; | ||||
| return _innerConverter.Read(map, ref reader, false); | |||||
| return _innerConverter.Read(map, model, ref reader, false); | |||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, T? value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, T? value, bool isTopLevel) | |||||
| { | { | ||||
| if (value.HasValue) | if (value.HasValue) | ||||
| _innerConverter.Write(map, ref writer, value.Value, isTopLevel); | |||||
| _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); | |||||
| else | else | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class ObjectPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| public class ObjectPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| where T : class, new() | where T : class, new() | ||||
| { | { | ||||
| private readonly ModelMap<T> _map; | private readonly ModelMap<T> _map; | ||||
| @@ -12,27 +12,27 @@ namespace Discord.Serialization.Json.Converters | |||||
| _map = serializer.MapModel<T>(); | _map = serializer.MapModel<T>(); | ||||
| } | } | ||||
| public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| var model = new T(); | |||||
| var subModel = new T(); | |||||
| if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartObject) | if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartObject) | ||||
| throw new SerializationException("Bad input, expected StartObject"); | throw new SerializationException("Bad input, expected StartObject"); | ||||
| while (reader.Read()) | while (reader.Read()) | ||||
| { | { | ||||
| if (reader.TokenType == JsonTokenType.EndObject) | if (reader.TokenType == JsonTokenType.EndObject) | ||||
| return model; | |||||
| return subModel; | |||||
| if (reader.TokenType != JsonTokenType.PropertyName) | if (reader.TokenType != JsonTokenType.PropertyName) | ||||
| throw new SerializationException("Bad input, expected PropertyName"); | throw new SerializationException("Bad input, expected PropertyName"); | ||||
| if (_map.TryGetProperty(reader.Value, out var property)) | if (_map.TryGetProperty(reader.Value, out var property)) | ||||
| (property as IJsonPropertyMap<T>).Read(model, ref reader); | |||||
| (property as IJsonPropertyMap<T>).Read(subModel, ref reader); | |||||
| else | else | ||||
| JsonUtils.Skip(ref reader); //Unknown property, skip | |||||
| JsonReaderUtils.Skip(ref reader); //Unknown property, skip | |||||
| } | } | ||||
| throw new SerializationException("Bad input, expected EndObject"); | throw new SerializationException("Bad input, expected EndObject"); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteObjectStart(map.Key); | writer.WriteObjectStart(map.Key); | ||||
| @@ -3,9 +3,9 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class DateTimePropertyConverter : IJsonPropertyConverter<DateTime> | |||||
| public class DateTimePropertyConverter : IJsonPropertyConverter<DateTime> | |||||
| { | { | ||||
| public DateTime Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public DateTime Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseDateTime(); | return reader.ParseDateTime(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, DateTime value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -22,9 +22,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class DateTimeOffsetPropertyConverter : IJsonPropertyConverter<DateTimeOffset> | |||||
| public class DateTimeOffsetPropertyConverter : IJsonPropertyConverter<DateTimeOffset> | |||||
| { | { | ||||
| public DateTimeOffset Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public DateTimeOffset Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -32,7 +32,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseDateTimeOffset(); | return reader.ParseDateTimeOffset(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class SinglePropertyConverter : IJsonPropertyConverter<float> | |||||
| public class SinglePropertyConverter : IJsonPropertyConverter<float> | |||||
| { | { | ||||
| public float Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public float Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseSingle(); | return reader.ParseSingle(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, float value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value.ToString()); | writer.WriteAttribute(map.Key, value.ToString()); | ||||
| @@ -21,9 +21,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class DoublePropertyConverter : IJsonPropertyConverter<double> | |||||
| public class DoublePropertyConverter : IJsonPropertyConverter<double> | |||||
| { | { | ||||
| public double Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public double Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseDouble(); | return reader.ParseDouble(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, double value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, double value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value.ToString()); | writer.WriteAttribute(map.Key, value.ToString()); | ||||
| @@ -42,7 +42,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| internal class DecimalPropertyConverter : IJsonPropertyConverter<decimal> | internal class DecimalPropertyConverter : IJsonPropertyConverter<decimal> | ||||
| { | { | ||||
| public decimal Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public decimal Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseDecimal(); | return reader.ParseDecimal(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, decimal value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, decimal value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value.ToString()); | writer.WriteAttribute(map.Key, value.ToString()); | ||||
| @@ -3,9 +3,9 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class BooleanPropertyConverter : IJsonPropertyConverter<bool> | |||||
| public class BooleanPropertyConverter : IJsonPropertyConverter<bool> | |||||
| { | { | ||||
| public bool Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public bool Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| default: throw new SerializationException("Bad input, expected False or True"); | default: throw new SerializationException("Bad input, expected False or True"); | ||||
| } | } | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, bool value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -25,9 +25,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class GuidPropertyConverter : IJsonPropertyConverter<Guid> | |||||
| public class GuidPropertyConverter : IJsonPropertyConverter<Guid> | |||||
| { | { | ||||
| public Guid Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public Guid Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseGuid(); | return reader.ParseGuid(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, Guid value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value.ToString()); | writer.WriteAttribute(map.Key, value.ToString()); | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class Int8PropertyConverter : IJsonPropertyConverter<sbyte> | |||||
| public class Int8PropertyConverter : IJsonPropertyConverter<sbyte> | |||||
| { | { | ||||
| public sbyte Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public sbyte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt8(); | return reader.ParseInt8(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, sbyte value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -21,9 +21,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class Int16PropertyConverter : IJsonPropertyConverter<short> | |||||
| public class Int16PropertyConverter : IJsonPropertyConverter<short> | |||||
| { | { | ||||
| public short Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public short Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt16(); | return reader.ParseInt16(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, short value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -40,9 +40,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class Int32PropertyConverter : IJsonPropertyConverter<int> | |||||
| public class Int32PropertyConverter : IJsonPropertyConverter<int> | |||||
| { | { | ||||
| public int Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public int Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt32(); | return reader.ParseInt32(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, int value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -59,9 +59,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class Int64PropertyConverter : IJsonPropertyConverter<long> | |||||
| public class Int64PropertyConverter : IJsonPropertyConverter<long> | |||||
| { | { | ||||
| public long Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt64(); | return reader.ParseInt64(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, long value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value.ToString()); | writer.WriteAttribute(map.Key, value.ToString()); | ||||
| @@ -1,11 +1,10 @@ | |||||
| using System.Text.Json; | using System.Text.Json; | ||||
| using System.Text.Utf8; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| /*internal class CharPropertyConverter : IJsonPropertyConverter<char> | |||||
| /*public class CharPropertyConverter : IJsonPropertyConverter<char> | |||||
| { | { | ||||
| public char Read(PropertyMap map, JsonReader reader, bool isTopLevel) | |||||
| public char Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -13,7 +12,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseChar(); | return reader.ParseChar(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, JsonWriter writer, char value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, char value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -22,17 +21,19 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| }*/ | }*/ | ||||
| internal class StringPropertyConverter : IJsonPropertyConverter<string> | |||||
| public class StringPropertyConverter : IJsonPropertyConverter<string> | |||||
| { | { | ||||
| public string Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public string Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| if (reader.ValueType != JsonValueType.String) | |||||
| if (reader.ValueType == JsonValueType.Null) | |||||
| return null; | |||||
| else if (reader.ValueType != JsonValueType.String) | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseString(); | return reader.ParseString(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, string value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class UInt8PropertyConverter : IJsonPropertyConverter<byte> | |||||
| public class UInt8PropertyConverter : IJsonPropertyConverter<byte> | |||||
| { | { | ||||
| public byte Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public byte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt8(); | return reader.ParseUInt8(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, byte value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -21,9 +21,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class UInt16PropertyConverter : IJsonPropertyConverter<ushort> | |||||
| public class UInt16PropertyConverter : IJsonPropertyConverter<ushort> | |||||
| { | { | ||||
| public ushort Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public ushort Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt16(); | return reader.ParseUInt16(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, ushort value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -40,9 +40,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class UInt32PropertyConverter : IJsonPropertyConverter<uint> | |||||
| public class UInt32PropertyConverter : IJsonPropertyConverter<uint> | |||||
| { | { | ||||
| public uint Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public uint Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt32(); | return reader.ParseUInt32(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, uint value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value); | writer.WriteAttribute(map.Key, value); | ||||
| @@ -59,9 +59,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| } | } | ||||
| internal class UInt64PropertyConverter : IJsonPropertyConverter<ulong> | |||||
| public class UInt64PropertyConverter : IJsonPropertyConverter<ulong> | |||||
| { | { | ||||
| public ulong Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| public ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt64(); | return reader.ParseUInt64(); | ||||
| } | } | ||||
| public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | |||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Key, value.ToString()); | writer.WriteAttribute(map.Key, value.ToString()); | ||||
| @@ -2,9 +2,14 @@ | |||||
| namespace Discord.Serialization.Json | namespace Discord.Serialization.Json | ||||
| { | { | ||||
| public interface IJsonPropertyConverter<T> | |||||
| public interface IJsonPropertyConverter<T> : IJsonPropertyReader<T>, IJsonPropertyWriter<T> { } | |||||
| public interface IJsonPropertyReader<out T> | |||||
| { | |||||
| T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel); | |||||
| } | |||||
| public interface IJsonPropertyWriter<in T> | |||||
| { | { | ||||
| T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel); | |||||
| void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel); | |||||
| void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,14 +0,0 @@ | |||||
| using System; | |||||
| using System.Text.Json; | |||||
| namespace Discord.Serialization | |||||
| { | |||||
| internal interface IJsonPropertyMap<TModel> | |||||
| { | |||||
| string Key { get; } | |||||
| ReadOnlyBuffer<byte> Utf8Key { get; } | |||||
| void Write(TModel model, ref JsonWriter writer); | |||||
| void Read(TModel model, ref JsonReader reader); | |||||
| } | |||||
| } | |||||
| @@ -4,19 +4,28 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json | namespace Discord.Serialization.Json | ||||
| { | { | ||||
| internal class JsonPropertyMap<TModel, TType> : PropertyMap, IJsonPropertyMap<TModel> | |||||
| internal interface IJsonPropertyMap<TModel> | |||||
| { | { | ||||
| private readonly IJsonPropertyConverter<TType> _converter; | |||||
| private readonly Func<TModel, TType> _getFunc; | |||||
| private readonly Action<TModel, TType> _setFunc; | |||||
| string Key { get; } | |||||
| ReadOnlyBuffer<byte> Utf8Key { get; } | |||||
| public JsonPropertyMap(PropertyInfo propInfo, IJsonPropertyConverter<TType> converter) | |||||
| : base(propInfo) | |||||
| void Write(TModel model, ref JsonWriter writer); | |||||
| void Read(TModel model, ref JsonReader reader); | |||||
| } | |||||
| internal class JsonPropertyMap<TModel, TValue> : PropertyMap<TModel, TValue>, IJsonPropertyMap<TModel> | |||||
| { | |||||
| private readonly IJsonPropertyConverter<TValue> _converter; | |||||
| private readonly Func<TModel, TValue> _getFunc; | |||||
| private readonly Action<TModel, TValue> _setFunc; | |||||
| public JsonPropertyMap(Serializer serializer, PropertyInfo propInfo, IJsonPropertyConverter<TValue> converter) | |||||
| : base(serializer, propInfo) | |||||
| { | { | ||||
| _converter = converter; | _converter = converter; | ||||
| _getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func<TModel, TType>)) as Func<TModel, TType>; | |||||
| _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action<TModel, TType>)) as Action<TModel, TType>; | |||||
| _getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func<TModel, TValue>)) as Func<TModel, TValue>; | |||||
| _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action<TModel, TValue>)) as Action<TModel, TValue>; | |||||
| } | } | ||||
| public void Write(TModel model, ref JsonWriter writer) | public void Write(TModel model, ref JsonWriter writer) | ||||
| @@ -24,11 +33,11 @@ namespace Discord.Serialization.Json | |||||
| var value = _getFunc(model); | var value = _getFunc(model); | ||||
| if (value == null && ExcludeNull) | if (value == null && ExcludeNull) | ||||
| return; | return; | ||||
| _converter.Write(this, ref writer, value, true); | |||||
| _converter.Write(this, model, ref writer, value, true); | |||||
| } | } | ||||
| public void Read(TModel model, ref JsonReader reader) | public void Read(TModel model, ref JsonReader reader) | ||||
| { | { | ||||
| var value = _converter.Read(this, ref reader, true); | |||||
| var value = _converter.Read(this, model, ref reader, true); | |||||
| _setFunc(model, value); | _setFunc(model, value); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | |||||
| using System.Reflection; | using System.Reflection; | ||||
| using System.Text; | using System.Text; | ||||
| using System.Text.Formatting; | using System.Text.Formatting; | ||||
| @@ -28,7 +29,7 @@ namespace Discord.Serialization.Json | |||||
| AddConverter<double, Converters.DoublePropertyConverter>(); | AddConverter<double, Converters.DoublePropertyConverter>(); | ||||
| AddConverter<decimal, Converters.DecimalPropertyConverter>(); | AddConverter<decimal, Converters.DecimalPropertyConverter>(); | ||||
| //AddConverter<char, Converters.CharPropertyConverter>(); //char.Parse does not support Json.Net's serialization | |||||
| //AddConverter<char, Converters.CharPropertyConverter>(); //TODO: char.Parse does not support Json.Net's serialization | |||||
| AddConverter<string, Converters.StringPropertyConverter>(); | AddConverter<string, Converters.StringPropertyConverter>(); | ||||
| AddConverter<DateTime, Converters.DateTimePropertyConverter>(); | AddConverter<DateTime, Converters.DateTimePropertyConverter>(); | ||||
| @@ -37,11 +38,20 @@ namespace Discord.Serialization.Json | |||||
| AddConverter<bool, Converters.BooleanPropertyConverter>(); | AddConverter<bool, Converters.BooleanPropertyConverter>(); | ||||
| AddConverter<Guid, Converters.GuidPropertyConverter>(); | AddConverter<Guid, Converters.GuidPropertyConverter>(); | ||||
| AddConverter<object, Converters.DynamicPropertyConverter>( | |||||
| (type, prop) => prop.GetCustomAttributes<ModelSelectorAttribute>().Any()); | |||||
| AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>)); | AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>)); | ||||
| AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>)); | AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>)); | ||||
| AddGenericConverter(typeof(Converters.EnumPropertyConverter<>), (type, prop) => type.IsEnum); | |||||
| AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), (type, prop) => type.IsClass); | |||||
| AddGenericConverter(typeof(Converters.StringEnumPropertyConverter<>), | |||||
| (type, prop) => type.IsEnum && prop.GetCustomAttribute<ModelStringEnumAttribute>() != null); | |||||
| AddGenericConverter(typeof(Converters.Int64EnumPropertyConverter<>), | |||||
| (type, prop) => type.IsEnum && IsSignedEnum(Enum.GetUnderlyingType(type.AsType()))); | |||||
| AddGenericConverter(typeof(Converters.UInt64EnumPropertyConverter<>), | |||||
| (type, prop) => type.IsEnum && IsUnsignedEnum(Enum.GetUnderlyingType(type.AsType()))); | |||||
| AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), | |||||
| (type, prop) => type.IsClass); | |||||
| } | } | ||||
| protected JsonSerializer(JsonSerializer parent) : base(parent) { } | protected JsonSerializer(JsonSerializer parent) : base(parent) { } | ||||
| public JsonSerializer CreateScope() => new JsonSerializer(this); | public JsonSerializer CreateScope() => new JsonSerializer(this); | ||||
| @@ -56,22 +66,33 @@ namespace Discord.Serialization.Json | |||||
| protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | ||||
| { | { | ||||
| var converter = (IJsonPropertyConverter<TValue>)GetConverter(typeof(TValue), propInfo); | var converter = (IJsonPropertyConverter<TValue>)GetConverter(typeof(TValue), propInfo); | ||||
| return new JsonPropertyMap<TModel, TValue>(propInfo, converter); | |||||
| return new JsonPropertyMap<TModel, TValue>(this, propInfo, converter); | |||||
| } | } | ||||
| public override TModel Read<TModel>(ReadOnlyBuffer<byte> data) | public override TModel Read<TModel>(ReadOnlyBuffer<byte> data) | ||||
| { | { | ||||
| var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); | var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); | ||||
| if (!reader.Read()) | if (!reader.Read()) | ||||
| return null; | |||||
| return default; | |||||
| var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | ||||
| return converter.Read(null, ref reader, false); | |||||
| return converter.Read(null, null, ref reader, false); | |||||
| } | } | ||||
| public override void Write<TModel>(ArrayFormatter stream, TModel model) | public override void Write<TModel>(ArrayFormatter stream, TModel model) | ||||
| { | { | ||||
| var writer = new JsonWriter(stream); | var writer = new JsonWriter(stream); | ||||
| var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | ||||
| converter.Write(null, ref writer, model, false); | |||||
| converter.Write(null, null, ref writer, model, false); | |||||
| } | } | ||||
| private static bool IsSignedEnum(Type underlyingType) | |||||
| => underlyingType == typeof(sbyte) || | |||||
| underlyingType == typeof(short) || | |||||
| underlyingType == typeof(int) || | |||||
| underlyingType == typeof(long); | |||||
| private static bool IsUnsignedEnum(Type underlyingType) | |||||
| => underlyingType == typeof(byte) || | |||||
| underlyingType == typeof(ushort) || | |||||
| underlyingType == typeof(uint) || | |||||
| underlyingType == typeof(ulong); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,24 +1,27 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Reflection; | |||||
| namespace Discord.Serialization | namespace Discord.Serialization | ||||
| { | { | ||||
| public class ModelMap<TModel> | public class ModelMap<TModel> | ||||
| where TModel : class, new() | where TModel : class, new() | ||||
| { | { | ||||
| private BufferDictionary<PropertyMap> _dictionary; | |||||
| private BufferDictionary<PropertyMap> _propDict; | |||||
| public bool HasDynamics { get; } | |||||
| public PropertyMap[] Properties { get; } | public PropertyMap[] Properties { get; } | ||||
| public ModelMap(List<PropertyMap> properties) | |||||
| public ModelMap(Serializer serializer, TypeInfo type, List<PropertyMap> properties) | |||||
| { | { | ||||
| Properties = properties.ToArray(); | Properties = properties.ToArray(); | ||||
| _dictionary = new BufferDictionary<PropertyMap>(properties.ToDictionary(x => x.Utf8Key)); | |||||
| _propDict = new BufferDictionary<PropertyMap>(properties.ToDictionary(x => x.Utf8Key)); | |||||
| } | } | ||||
| public bool TryGetProperty(ReadOnlyBuffer<byte> key, out PropertyMap value) | public bool TryGetProperty(ReadOnlyBuffer<byte> key, out PropertyMap value) | ||||
| => _dictionary.TryGetValue(key, out value); | |||||
| => _propDict.TryGetValue(key, out value); | |||||
| public bool TryGetProperty(ReadOnlySpan<byte> key, out PropertyMap value) | public bool TryGetProperty(ReadOnlySpan<byte> key, out PropertyMap value) | ||||
| => _dictionary.TryGetValue(key, out value); | |||||
| => _propDict.TryGetValue(key, out value); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Reflection; | using System.Reflection; | ||||
| using System.Text.Utf8; | using System.Text.Utf8; | ||||
| @@ -8,15 +10,83 @@ namespace Discord.Serialization | |||||
| { | { | ||||
| public string Key { get; } | public string Key { get; } | ||||
| public ReadOnlyBuffer<byte> Utf8Key { get; } | public ReadOnlyBuffer<byte> Utf8Key { get; } | ||||
| public string Name { get; } | |||||
| public bool ExcludeNull { get; } | public bool ExcludeNull { get; } | ||||
| public PropertyMap(PropertyInfo propInfo) | |||||
| public PropertyMap(Serializer serializer, PropertyInfo propInfo) | |||||
| { | { | ||||
| var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | |||||
| Name = propInfo.Name; | |||||
| Key = jsonProperty?.Key ?? propInfo.Name; | |||||
| var attr = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | |||||
| Key = attr.Key ?? propInfo.Name; | |||||
| Utf8Key = new ReadOnlyBuffer<byte>(new Utf8String(Key).Bytes.ToArray()); | Utf8Key = new ReadOnlyBuffer<byte>(new Utf8String(Key).Bytes.ToArray()); | ||||
| ExcludeNull = jsonProperty?.ExcludeNull ?? false; | |||||
| ExcludeNull = attr.ExcludeNull; | |||||
| } | |||||
| public abstract object GetDynamicConverter(object model, bool throwOnMissing); | |||||
| } | |||||
| public abstract class PropertyMap<TModel, TValue> : PropertyMap | |||||
| { | |||||
| private class Selector | |||||
| { | |||||
| private static readonly MethodInfo _getSelectorKeyFunc | |||||
| = typeof(Selector).GetTypeInfo().GetDeclaredMethod(nameof(GetSelectorKey)); | |||||
| private readonly ISelectorGroup _group; | |||||
| private readonly Delegate _getSelectorFunc; | |||||
| private readonly Delegate _getWrappedSelectorFunc; | |||||
| public Selector(PropertyInfo prop, ISelectorGroup group, Delegate getSelectorFunc) | |||||
| { | |||||
| _group = group; | |||||
| _getSelectorFunc = getSelectorFunc; | |||||
| var funcType = typeof(Func<,>).MakeGenericType(typeof(object), prop.PropertyType); | |||||
| _getWrappedSelectorFunc = _getSelectorKeyFunc.MakeGenericMethod(prop.PropertyType).CreateDelegate(funcType, this); | |||||
| } | |||||
| private TKey GetSelectorKey<TKey>(object model) | |||||
| => (_getSelectorFunc as Func<TModel, TKey>)((TModel)model); | |||||
| public object GetDynamicConverter(object model) | |||||
| => _group.GetDynamicConverter(_getWrappedSelectorFunc, model); | |||||
| } | |||||
| private readonly Delegate _getSelectorFunc, _getWrappedSelectorFunc; | |||||
| private readonly IReadOnlyList<Selector> _selectors; | |||||
| public PropertyMap(Serializer serializer, PropertyInfo propInfo) | |||||
| : base(serializer, propInfo) | |||||
| { | |||||
| _selectors = propInfo.GetCustomAttributes<ModelSelectorAttribute>() | |||||
| .Select(x => | |||||
| { | |||||
| var prop = typeof(TModel).GetTypeInfo().DeclaredProperties.FirstOrDefault(y => y.Name == x.SelectorProperty); | |||||
| if (prop == null) | |||||
| throw new SerializationException($"Selector key \"{x.SelectorProperty}\" not found"); | |||||
| var selectorGroup = serializer.GetSelectorGroup(prop.PropertyType, x.SelectorGroup); | |||||
| var funcType = typeof(Func<,>).MakeGenericType(typeof(TModel), prop.PropertyType); | |||||
| var selectorFunc = prop.GetMethod.CreateDelegate(funcType); | |||||
| return new Selector(prop, selectorGroup, selectorFunc); | |||||
| }) | |||||
| .ToList(); | |||||
| } | |||||
| public override object GetDynamicConverter(object model, bool throwOnMissing) | |||||
| { | |||||
| for (int i = 0; i < _selectors.Count; i++) | |||||
| { | |||||
| object converter = _selectors[i].GetDynamicConverter(model); | |||||
| if (converter != null) | |||||
| return converter; | |||||
| } | |||||
| if (throwOnMissing) | |||||
| throw new SerializationException($"Unable to find a converter for {Name}."); | |||||
| return null; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,40 @@ | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| namespace Discord.Serialization | |||||
| { | |||||
| public interface ISelectorGroup | |||||
| { | |||||
| Type Type { get; } | |||||
| void AddDynamicConverter(object key, object converter); | |||||
| object GetDynamicConverter(Delegate getKeyFunc, object model); | |||||
| } | |||||
| internal class SelectorGroup<TKey> : ISelectorGroup | |||||
| { | |||||
| private ConcurrentDictionary<TKey, object> _mapping; | |||||
| public Type Type => typeof(TKey); | |||||
| public SelectorGroup() | |||||
| { | |||||
| _mapping = new ConcurrentDictionary<TKey, object>(); | |||||
| } | |||||
| public void AddDynamicConverter(object key, object converter) | |||||
| => _mapping[(TKey)key] = converter; | |||||
| public object GetDynamicConverter(Delegate getKeyFunc, object model) | |||||
| { | |||||
| var keyFunc = getKeyFunc as Func<object, TKey>; | |||||
| var key = keyFunc(model); | |||||
| if (key == null) | |||||
| return null; | |||||
| if (_mapping.TryGetValue(key, out var converter)) | |||||
| return converter; | |||||
| return null; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -32,41 +32,49 @@ namespace Discord.Serialization | |||||
| IsScoped = true; | IsScoped = true; | ||||
| } | } | ||||
| protected object GetConverter(Type type, PropertyInfo propInfo = null) | |||||
| => _converters.Get(type, propInfo); | |||||
| protected object GetConverter(Type valueType, PropertyInfo propInfo = null) | |||||
| => _converters.Get(valueType, propInfo); | |||||
| public void AddConverter(Type type, Type converter) | |||||
| public void AddConverter(Type valueType, Type converterType) | |||||
| { | { | ||||
| CheckScoped(); | CheckScoped(); | ||||
| _converters.Add(type, converter); | |||||
| _converters.Add(valueType, converterType); | |||||
| } | } | ||||
| public void AddConverter(Type type, Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||||
| public void AddConverter(Type valueType, Type converterType, Func<TypeInfo, PropertyInfo, bool> condition) | |||||
| { | { | ||||
| CheckScoped(); | CheckScoped(); | ||||
| _converters.Add(type, converter, condition); | |||||
| _converters.Add(valueType, converterType, condition); | |||||
| } | } | ||||
| public void AddGenericConverter(Type converter) | |||||
| public void AddGenericConverter(Type converterType) | |||||
| { | { | ||||
| CheckScoped(); | CheckScoped(); | ||||
| _converters.AddGeneric(converter); | |||||
| _converters.AddGeneric(converterType); | |||||
| } | } | ||||
| public void AddGenericConverter(Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||||
| public void AddGenericConverter(Type converterType, Func<TypeInfo, PropertyInfo, bool> condition) | |||||
| { | { | ||||
| CheckScoped(); | CheckScoped(); | ||||
| _converters.AddGeneric(converter, condition); | |||||
| _converters.AddGeneric(converterType, condition); | |||||
| } | } | ||||
| public void AddGenericConverter(Type value, Type converter) | |||||
| public void AddGenericConverter(Type valueType, Type converterType) | |||||
| { | { | ||||
| CheckScoped(); | CheckScoped(); | ||||
| _converters.AddGeneric(value, converter); | |||||
| _converters.AddGeneric(valueType, converterType); | |||||
| } | } | ||||
| public void AddGenericConverter(Type value, Type converter, Func<TypeInfo, PropertyInfo, bool> condition) | |||||
| public void AddGenericConverter(Type valueType, Type converterType, Func<TypeInfo, PropertyInfo, bool> condition) | |||||
| { | { | ||||
| CheckScoped(); | CheckScoped(); | ||||
| _converters.AddGeneric(value, converter, condition); | |||||
| _converters.AddGeneric(valueType, converterType, condition); | |||||
| } | } | ||||
| public void AddSelectorConverter(string groupKey, Type keyType, object keyValue, Type converterType) | |||||
| { | |||||
| CheckScoped(); | |||||
| _converters.AddSelector(groupKey, keyType, keyValue, converterType); | |||||
| } | |||||
| public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) | |||||
| => _converters.GetSelectorGroup(keyType, groupKey); | |||||
| protected internal ModelMap<TModel> MapModel<TModel>() | protected internal ModelMap<TModel> MapModel<TModel>() | ||||
| where TModel : class, new() | where TModel : class, new() | ||||
| { | { | ||||
| @@ -80,24 +88,23 @@ namespace Discord.Serialization | |||||
| var properties = new List<PropertyMap>(); | var properties = new List<PropertyMap>(); | ||||
| for (int i = 0; i < propInfos.Length; i++) | for (int i = 0; i < propInfos.Length; i++) | ||||
| { | { | ||||
| var propMap = MapProperty<TModel>(propInfos[i]); | |||||
| properties.Add(propMap); | |||||
| if (propInfos[i].GetCustomAttribute<ModelPropertyAttribute>() != null) | |||||
| { | |||||
| var propMap = MapProperty<TModel>(propInfos[i]); | |||||
| properties.Add(propMap); | |||||
| } | |||||
| } | } | ||||
| return new ModelMap<TModel>(properties); | |||||
| return new ModelMap<TModel>(this, type, properties); | |||||
| }) as ModelMap<TModel>; | }) as ModelMap<TModel>; | ||||
| } | } | ||||
| private PropertyMap MapProperty<TModel>(PropertyInfo propInfo) | private PropertyMap MapProperty<TModel>(PropertyInfo propInfo) | ||||
| where TModel : class, new() | |||||
| => _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; | => _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; | ||||
| protected abstract PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | |||||
| where TModel : class, new(); | |||||
| public abstract TModel Read<TModel>(ReadOnlyBuffer<byte> data) | |||||
| where TModel : class, new(); | |||||
| public abstract void Write<TModel>(ArrayFormatter stream, TModel model) | |||||
| where TModel : class, new(); | |||||
| protected abstract PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo); | |||||
| public abstract TModel Read<TModel>(ReadOnlyBuffer<byte> data); | |||||
| public abstract void Write<TModel>(ArrayFormatter stream, TModel model); | |||||
| private void CheckScoped() | private void CheckScoped() | ||||
| { | { | ||||
| if (IsScoped) | if (IsScoped) | ||||
| @@ -0,0 +1,20 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Discord.Serialization; | |||||
| namespace Discord.API.Gateway | |||||
| { | |||||
| internal class GatewaySocketFrame | |||||
| { | |||||
| [ModelProperty("op")] | |||||
| public GatewayOpCode Operation { get; set; } | |||||
| [ModelProperty("t", ExcludeNull = true)] | |||||
| public string Type { get; set; } | |||||
| [ModelProperty("s", ExcludeNull = true)] | |||||
| public int? Sequence { get; set; } | |||||
| [ModelProperty("d")] | |||||
| [ModelSelector(nameof(Operation), ModelSelectorGroups.GatewayFrame)] | |||||
| [ModelSelector(nameof(Type), ModelSelectorGroups.GatewayDispatchFrame)] | |||||
| public object Payload { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -1,18 +1,20 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Discord.Serialization; | using Discord.Serialization; | ||||
| using System; | |||||
| namespace Discord.API | |||||
| namespace Discord.API.Voice | |||||
| { | { | ||||
| internal class SocketFrame | |||||
| internal class VoiceSocketFrame | |||||
| { | { | ||||
| [ModelProperty("op")] | [ModelProperty("op")] | ||||
| public int Operation { get; set; } | |||||
| public VoiceOpCode Operation { get; set; } | |||||
| [ModelProperty("t", ExcludeNull = true)] | [ModelProperty("t", ExcludeNull = true)] | ||||
| public string Type { get; set; } | public string Type { get; set; } | ||||
| [ModelProperty("s", ExcludeNull = true)] | [ModelProperty("s", ExcludeNull = true)] | ||||
| public int? Sequence { get; set; } | public int? Sequence { get; set; } | ||||
| [ModelProperty("d")] | [ModelProperty("d")] | ||||
| public ReadOnlyBuffer<byte> Payload { get; set; } | |||||
| [ModelSelector(nameof(Operation), ModelSelectorGroups.VoiceFrame)] | |||||
| [ModelSelector(nameof(Type), ModelSelectorGroups.VoiceDispatchFrame)] | |||||
| public object Payload { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -11,6 +11,7 @@ using System.Threading.Tasks; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
| using Discord.Serialization; | using Discord.Serialization; | ||||
| using Discord.Serialization.Json; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| @@ -66,7 +67,7 @@ namespace Discord.Audio | |||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | ||||
| _serializer = new Serializer(SerializationFormat.Json); | |||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -225,7 +226,7 @@ namespace Discord.Audio | |||||
| _streams.Clear(); | _streams.Clear(); | ||||
| } | } | ||||
| private async Task ProcessMessageAsync(VoiceOpCode opCode, ReadOnlyBuffer<byte> payload) | |||||
| private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) | |||||
| { | { | ||||
| _lastMessageTime = Environment.TickCount; | _lastMessageTime = Environment.TickCount; | ||||
| @@ -236,7 +237,7 @@ namespace Discord.Audio | |||||
| case VoiceOpCode.Ready: | case VoiceOpCode.Ready: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); | await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); | ||||
| var data = _serializer.Read<ReadyEvent>(payload); | |||||
| var data = payload as ReadyEvent; | |||||
| _ssrc = data.SSRC; | _ssrc = data.SSRC; | ||||
| @@ -252,7 +253,7 @@ namespace Discord.Audio | |||||
| case VoiceOpCode.SessionDescription: | case VoiceOpCode.SessionDescription: | ||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); | await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); | ||||
| var data = _serializer.Read<SessionDescriptionEvent>(payload); | |||||
| var data = payload as SessionDescriptionEvent; | |||||
| if (data.Mode != DiscordVoiceApiClient.Mode) | if (data.Mode != DiscordVoiceApiClient.Mode) | ||||
| throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | ||||
| @@ -288,7 +289,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); | await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); | ||||
| var data = _serializer.Read<SpeakingEvent>(payload); | |||||
| var data = payload as SpeakingEvent; | |||||
| _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up | _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up | ||||
| await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); | await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); | ||||
| @@ -7,6 +7,7 @@ using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Threading; | using System.Threading; | ||||
| using Discord.Serialization; | using Discord.Serialization; | ||||
| using Discord.Serialization.Json; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| @@ -54,7 +55,7 @@ namespace Discord.WebSocket | |||||
| _baseConfig = config; | _baseConfig = config; | ||||
| _connectionGroupLock = new SemaphoreSlim(1, 1); | _connectionGroupLock = new SemaphoreSlim(1, 1); | ||||
| _serializer = new Serializer(SerializationFormat.Json); | |||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -21,8 +21,8 @@ namespace Discord.API | |||||
| { | { | ||||
| public event Func<GatewayOpCode, Task> SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } | public event Func<GatewayOpCode, Task> SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<GatewayOpCode, Task>> _sentGatewayMessageEvent = new AsyncEvent<Func<GatewayOpCode, Task>>(); | private readonly AsyncEvent<Func<GatewayOpCode, Task>> _sentGatewayMessageEvent = new AsyncEvent<Func<GatewayOpCode, Task>>(); | ||||
| public event Func<GatewayOpCode, int?, string, ReadOnlyBuffer<byte>, Task> ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<GatewayOpCode, int?, string, ReadOnlyBuffer<byte>, Task>> _receivedGatewayEvent = new AsyncEvent<Func<GatewayOpCode, int?, string, ReadOnlyBuffer<byte>, Task>>(); | |||||
| public event Func<GatewayOpCode, int?, string, object, Task> ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<GatewayOpCode, int?, string, object, Task>> _receivedGatewayEvent = new AsyncEvent<Func<GatewayOpCode, int?, string, object, Task>>(); | |||||
| public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | ||||
| @@ -60,16 +60,16 @@ namespace Discord.API | |||||
| _decompressionStream.SetLength(_decompressionStream.Position); | _decompressionStream.SetLength(_decompressionStream.Position); | ||||
| _decompressionStream.Position = 0; | _decompressionStream.Position = 0; | ||||
| var msg = _serializer.Read<SocketFrame>(_decompressionStream.ToReadOnlyBuffer()); | |||||
| var msg = _serializer.Read<GatewaySocketFrame>(_decompressionStream.ToReadOnlyBuffer()); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | |||||
| await _receivedGatewayEvent.InvokeAsync(msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| var msg = _serializer.Read<SocketFrame>(data); | |||||
| var msg = _serializer.Read<GatewaySocketFrame>(data); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | |||||
| await _receivedGatewayEvent.InvokeAsync(msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); | |||||
| } | } | ||||
| }; | }; | ||||
| WebSocketClient.Closed += async ex => | WebSocketClient.Closed += async ex => | ||||
| @@ -173,20 +173,17 @@ namespace Discord.API | |||||
| { | { | ||||
| CheckState(); | CheckState(); | ||||
| if (_formatters.TryDequeue(out var data1)) | |||||
| data1 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | |||||
| if (_formatters.TryDequeue(out var data2)) | |||||
| data2 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | |||||
| if (!_formatters.TryDequeue(out var data)) | |||||
| data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | |||||
| try | try | ||||
| { | { | ||||
| payload = new SocketFrame { Operation = (int)opCode, Payload = SerializeJson(data1, payload) }; | |||||
| await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, SerializeJson(data2, payload), true, options)).ConfigureAwait(false); | |||||
| var frame = new GatewaySocketFrame { Operation = opCode, Payload = payload }; | |||||
| await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, SerializeJson(data, frame), true, options)).ConfigureAwait(false); | |||||
| await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | ||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _formatters.Enqueue(data1); | |||||
| _formatters.Enqueue(data2); | |||||
| _formatters.Enqueue(data); | |||||
| } | } | ||||
| } | } | ||||
| @@ -5,6 +5,7 @@ using Discord.Net.Udp; | |||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using Discord.Serialization; | using Discord.Serialization; | ||||
| using Discord.Serialization.Json; | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -87,7 +88,7 @@ namespace Discord.WebSocket | |||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | ||||
| _serializer = new Serializer(SerializationFormat.Json); | |||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -382,7 +383,7 @@ namespace Discord.WebSocket | |||||
| gameModel).ConfigureAwait(false); | gameModel).ConfigureAwait(false); | ||||
| } | } | ||||
| private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, ReadOnlyBuffer<byte> payload) | |||||
| private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) | |||||
| { | { | ||||
| if (seq != null) | if (seq != null) | ||||
| _lastSeq = seq.Value; | _lastSeq = seq.Value; | ||||
| @@ -395,7 +396,7 @@ namespace Discord.WebSocket | |||||
| case GatewayOpCode.Hello: | case GatewayOpCode.Hello: | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); | ||||
| var data = _serializer.Read<HelloEvent>(payload); | |||||
| var data = payload as HelloEvent; | |||||
| _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); | _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); | ||||
| } | } | ||||
| @@ -428,7 +429,7 @@ namespace Discord.WebSocket | |||||
| _sessionId = null; | _sessionId = null; | ||||
| _lastSeq = 0; | _lastSeq = 0; | ||||
| bool retry = IsTrue(); | |||||
| bool retry = (bool)payload; | |||||
| if (retry) | if (retry) | ||||
| _connection.Reconnect(); //TODO: Untested | _connection.Reconnect(); //TODO: Untested | ||||
| else | else | ||||
| @@ -451,7 +452,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<ReadyEvent>(payload); | |||||
| var data = payload as ReadyEvent; | |||||
| var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | ||||
| var currentUser = SocketSelfUser.Create(this, state, data.User); | var currentUser = SocketSelfUser.Create(this, state, data.User); | ||||
| @@ -521,7 +522,7 @@ namespace Discord.WebSocket | |||||
| //Guilds | //Guilds | ||||
| case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
| { | { | ||||
| var data = _serializer.Read<ExtendedGuild>(payload); | |||||
| var data = payload as ExtendedGuild; | |||||
| if (data.Unavailable == false) | if (data.Unavailable == false) | ||||
| { | { | ||||
| @@ -573,7 +574,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Guild>(payload); | |||||
| var data = payload as API.Guild; | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -592,7 +593,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Gateway.GuildEmojiUpdateEvent>(payload); | |||||
| var data = payload as API.Gateway.GuildEmojiUpdateEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -610,7 +611,7 @@ namespace Discord.WebSocket | |||||
| case "GUILD_SYNC": | case "GUILD_SYNC": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildSyncEvent>(payload); | |||||
| var data = payload as GuildSyncEvent; | |||||
| var guild = State.GetGuild(data.Id); | var guild = State.GetGuild(data.Id); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -631,7 +632,7 @@ namespace Discord.WebSocket | |||||
| break; | break; | ||||
| case "GUILD_DELETE": | case "GUILD_DELETE": | ||||
| { | { | ||||
| var data = _serializer.Read<ExtendedGuild>(payload); | |||||
| var data = payload as ExtendedGuild; | |||||
| if (data.Unavailable == true) | if (data.Unavailable == true) | ||||
| { | { | ||||
| type = "GUILD_UNAVAILABLE"; | type = "GUILD_UNAVAILABLE"; | ||||
| @@ -673,7 +674,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Channel>(payload); | |||||
| var data = payload as API.Channel; | |||||
| SocketChannel channel = null; | SocketChannel channel = null; | ||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| @@ -705,7 +706,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Channel>(payload); | |||||
| var data = payload as API.Channel; | |||||
| var channel = State.GetChannel(data.Id); | var channel = State.GetChannel(data.Id); | ||||
| if (channel != null) | if (channel != null) | ||||
| { | { | ||||
| @@ -733,7 +734,7 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); | ||||
| SocketChannel channel = null; | SocketChannel channel = null; | ||||
| var data = _serializer.Read<API.Channel>(payload); | |||||
| var data = payload as API.Channel; | |||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| var guild = State.GetGuild(data.GuildId.Value); | var guild = State.GetGuild(data.GuildId.Value); | ||||
| @@ -771,7 +772,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildMemberAddEvent>(payload); | |||||
| var data = payload as GuildMemberAddEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -797,7 +798,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildMemberUpdateEvent>(payload); | |||||
| var data = payload as GuildMemberUpdateEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -835,7 +836,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildMemberRemoveEvent>(payload); | |||||
| var data = payload as GuildMemberRemoveEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -870,7 +871,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildMembersChunkEvent>(payload); | |||||
| var data = payload as GuildMembersChunkEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -894,7 +895,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<RecipientEvent>(payload); | |||||
| var data = payload as RecipientEvent; | |||||
| if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | ||||
| { | { | ||||
| var user = channel.GetOrAddUser(data.User); | var user = channel.GetOrAddUser(data.User); | ||||
| @@ -911,7 +912,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<RecipientEvent>(payload); | |||||
| var data = payload as RecipientEvent; | |||||
| if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) | ||||
| { | { | ||||
| var user = channel.RemoveUser(data.User.Id); | var user = channel.RemoveUser(data.User.Id); | ||||
| @@ -936,7 +937,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildRoleCreateEvent>(payload); | |||||
| var data = payload as GuildRoleCreateEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -960,7 +961,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildRoleUpdateEvent>(payload); | |||||
| var data = payload as GuildRoleUpdateEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -995,7 +996,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildRoleDeleteEvent>(payload); | |||||
| var data = payload as GuildRoleDeleteEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1029,7 +1030,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildBanEvent>(payload); | |||||
| var data = payload as GuildBanEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1055,7 +1056,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<GuildBanEvent>(payload); | |||||
| var data = payload as GuildBanEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1083,7 +1084,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Message>(payload); | |||||
| var data = payload as API.Message; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1130,7 +1131,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Message>(payload); | |||||
| var data = payload as API.Message; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1177,7 +1178,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Message>(payload); | |||||
| var data = payload as API.Message; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1204,7 +1205,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Gateway.Reaction>(payload); | |||||
| var data = payload as API.Gateway.Reaction; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | ||||
| @@ -1228,7 +1229,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Gateway.Reaction>(payload); | |||||
| var data = payload as API.Gateway.Reaction; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | ||||
| @@ -1252,7 +1253,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<RemoveAllReactionsEvent>(payload); | |||||
| var data = payload as RemoveAllReactionsEvent; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | ||||
| @@ -1274,7 +1275,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<MessageDeleteBulkEvent>(payload); | |||||
| var data = payload as MessageDeleteBulkEvent; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1305,7 +1306,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.Presence>(payload); | |||||
| var data = payload as API.Presence; | |||||
| if (data.GuildId.IsSpecified) | if (data.GuildId.IsSpecified) | ||||
| { | { | ||||
| @@ -1364,7 +1365,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<TypingStartEvent>(payload); | |||||
| var data = payload as TypingStartEvent; | |||||
| if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | ||||
| { | { | ||||
| var guild = (channel as SocketGuildChannel)?.Guild; | var guild = (channel as SocketGuildChannel)?.Guild; | ||||
| @@ -1386,7 +1387,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.User>(payload); | |||||
| var data = payload as API.User; | |||||
| if (data.Id == CurrentUser.Id) | if (data.Id == CurrentUser.Id) | ||||
| { | { | ||||
| var before = CurrentUser.Clone(); | var before = CurrentUser.Clone(); | ||||
| @@ -1406,7 +1407,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<API.VoiceState>(payload); | |||||
| var data = payload as API.VoiceState; | |||||
| SocketUser user; | SocketUser user; | ||||
| SocketVoiceState before, after; | SocketVoiceState before, after; | ||||
| if (data.GuildId != null) | if (data.GuildId != null) | ||||
| @@ -1478,7 +1479,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | ||||
| var data = _serializer.Read<VoiceServerUpdateEvent>(payload); | |||||
| var data = payload as VoiceServerUpdateEvent; | |||||
| var guild = State.GetGuild(data.GuildId); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1528,15 +1529,6 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); | await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); | ||||
| } | } | ||||
| bool IsTrue() | |||||
| { | |||||
| ref var ptr = ref payload.Span.DangerousGetPinnableReference(); | |||||
| return Unsafe.Add(ref ptr, 0) == (byte)'t' && | |||||
| Unsafe.Add(ref ptr, 1) == (byte)'r' && | |||||
| Unsafe.Add(ref ptr, 2) == (byte)'u' && | |||||
| Unsafe.Add(ref ptr, 3) == (byte)'e'; | |||||
| } | |||||
| } | } | ||||
| private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) | private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) | ||||
| @@ -31,8 +31,8 @@ namespace Discord.Audio | |||||
| public event Func<int, Task> SentData { add { _sentDataEvent.Add(value); } remove { _sentDataEvent.Remove(value); } } | public event Func<int, Task> SentData { add { _sentDataEvent.Add(value); } remove { _sentDataEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<int, Task>> _sentDataEvent = new AsyncEvent<Func<int, Task>>(); | private readonly AsyncEvent<Func<int, Task>> _sentDataEvent = new AsyncEvent<Func<int, Task>>(); | ||||
| public event Func<VoiceOpCode, ReadOnlyBuffer<byte>, Task> ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<VoiceOpCode, ReadOnlyBuffer<byte>, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, ReadOnlyBuffer<byte>, Task>>(); | |||||
| public event Func<VoiceOpCode, object, Task> ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } | |||||
| private readonly AsyncEvent<Func<VoiceOpCode, object, Task>> _receivedEvent = new AsyncEvent<Func<VoiceOpCode, object, Task>>(); | |||||
| public event Func<byte[], int, int, Task> ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } } | public event Func<byte[], int, int, Task> ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<byte[], int, int, Task>> _receivedPacketEvent = new AsyncEvent<Func<byte[], int, int, Task>>(); | private readonly AsyncEvent<Func<byte[], int, int, Task>> _receivedPacketEvent = new AsyncEvent<Func<byte[], int, int, Task>>(); | ||||
| public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | ||||
| @@ -79,16 +79,16 @@ namespace Discord.Audio | |||||
| _decompressionStream.SetLength(_decompressionStream.Position); | _decompressionStream.SetLength(_decompressionStream.Position); | ||||
| _decompressionStream.Position = 0; | _decompressionStream.Position = 0; | ||||
| var msg = _serializer.Read<SocketFrame>(_decompressionStream.ToReadOnlyBuffer()); | |||||
| var msg = _serializer.Read<VoiceSocketFrame>(_decompressionStream.ToReadOnlyBuffer()); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | |||||
| await _receivedEvent.InvokeAsync(msg.Operation, msg.Payload).ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| var msg = _serializer.Read<SocketFrame>(data); | |||||
| var msg = _serializer.Read<VoiceSocketFrame>(data); | |||||
| if (msg != null) | if (msg != null) | ||||
| await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); | |||||
| await _receivedEvent.InvokeAsync(msg.Operation, msg.Payload).ConfigureAwait(false); | |||||
| } | } | ||||
| }; | }; | ||||
| WebSocketClient.Closed += async ex => | WebSocketClient.Closed += async ex => | ||||
| @@ -114,20 +114,17 @@ namespace Discord.Audio | |||||
| public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) | public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) | ||||
| { | { | ||||
| if (_formatters.TryDequeue(out var data1)) | |||||
| data1 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | |||||
| if (_formatters.TryDequeue(out var data2)) | |||||
| data2 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | |||||
| if (!_formatters.TryDequeue(out var data)) | |||||
| data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); | |||||
| try | try | ||||
| { | { | ||||
| payload = new SocketFrame { Operation = (int)opCode, Payload = SerializeJson(data1, payload) }; | |||||
| await WebSocketClient.SendAsync(SerializeJson(data2, payload), true).ConfigureAwait(false); | |||||
| var frame = new VoiceSocketFrame { Operation = opCode, Payload = payload }; | |||||
| await WebSocketClient.SendAsync(SerializeJson(data, frame), true).ConfigureAwait(false); | |||||
| await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | ||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _formatters.Enqueue(data1); | |||||
| _formatters.Enqueue(data2); | |||||
| _formatters.Enqueue(data); | |||||
| } | } | ||||
| } | } | ||||
| public async Task SendAsync(byte[] data, int offset, int bytes) | public async Task SendAsync(byte[] data, int offset, int bytes) | ||||
| @@ -6,6 +6,7 @@ using System.Threading.Tasks; | |||||
| using System.Linq; | using System.Linq; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Serialization; | using Discord.Serialization; | ||||
| using Discord.Serialization.Json; | |||||
| namespace Discord.Webhook | namespace Discord.Webhook | ||||
| { | { | ||||
| @@ -29,7 +30,7 @@ namespace Discord.Webhook | |||||
| { | { | ||||
| _webhookId = webhookId; | _webhookId = webhookId; | ||||
| _serializer = new Serializer(SerializationFormat.Json); | |||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||