Browse Source

Added support for property selectors and numeric enums

voice-allocs
RogueException 7 years ago
parent
commit
016b7fa550
48 changed files with 652 additions and 290 deletions
  1. +2
    -2
      src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs
  2. +4
    -1
      src/Discord.Net.Core/Entities/Permissions/Overwrite.cs
  3. +4
    -4
      src/Discord.Net.Core/Entities/Users/UserStatus.cs
  4. +1
    -0
      src/Discord.Net.Rest/API/Common/Presence.cs
  5. +3
    -4
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  6. +4
    -4
      src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs
  7. +2
    -2
      src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs
  8. +2
    -2
      src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs
  9. +4
    -4
      src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs
  10. +2
    -2
      src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs
  11. +11
    -0
      src/Discord.Net.Rest/Serialization/ModelSelectorGroups.cs
  12. +2
    -0
      src/Discord.Net.Rpc/API/RpcFrame.cs
  13. +5
    -4
      src/Discord.Net.Rpc/DiscordRpcApiClient.cs
  14. +2
    -1
      src/Discord.Net.Rpc/DiscordRpcClient.cs
  15. +2
    -2
      src/Discord.Net.Serialization/Attributes/ModelEnumValueAttribute.cs
  16. +3
    -1
      src/Discord.Net.Serialization/Attributes/ModelPropertyAttribute.cs
  17. +17
    -0
      src/Discord.Net.Serialization/Attributes/ModelSelectorAttribute.cs
  18. +9
    -0
      src/Discord.Net.Serialization/Attributes/ModelStringEnumAttribute.cs
  19. +35
    -10
      src/Discord.Net.Serialization/ConverterCollection.cs
  20. +76
    -19
      src/Discord.Net.Serialization/EnumMap.cs
  21. +1
    -1
      src/Discord.Net.Serialization/Extensions/JsonReaderExtensions.cs
  22. +5
    -5
      src/Discord.Net.Serialization/Json/Converters/Collections.cs
  23. +35
    -0
      src/Discord.Net.Serialization/Json/Converters/Dynamic.cs
  24. +51
    -5
      src/Discord.Net.Serialization/Json/Converters/Enum.cs
  25. +5
    -5
      src/Discord.Net.Serialization/Json/Converters/Nullable.cs
  26. +7
    -7
      src/Discord.Net.Serialization/Json/Converters/Object.cs
  27. +6
    -6
      src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs
  28. +8
    -8
      src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs
  29. +6
    -6
      src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs
  30. +12
    -12
      src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs
  31. +9
    -8
      src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs
  32. +12
    -12
      src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs
  33. +8
    -3
      src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs
  34. +0
    -14
      src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs
  35. +19
    -10
      src/Discord.Net.Serialization/Json/JsonPropertyMap.cs
  36. +28
    -7
      src/Discord.Net.Serialization/Json/JsonSerializer.cs
  37. +8
    -5
      src/Discord.Net.Serialization/ModelMap.cs
  38. +74
    -4
      src/Discord.Net.Serialization/PropertyMap.cs
  39. +40
    -0
      src/Discord.Net.Serialization/SelectorGroup.cs
  40. +32
    -25
      src/Discord.Net.Serialization/Serializer.cs
  41. +20
    -0
      src/Discord.Net.WebSocket/API/Gateway/GatewaySocketFrame.cs
  42. +7
    -5
      src/Discord.Net.WebSocket/API/Voice/VoiceSocketFrame.cs
  43. +6
    -5
      src/Discord.Net.WebSocket/Audio/AudioClient.cs
  44. +2
    -1
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  45. +11
    -14
      src/Discord.Net.WebSocket/DiscordSocketApiClient.cs
  46. +37
    -45
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  47. +11
    -14
      src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs
  48. +2
    -1
      src/Discord.Net.Webhook/DiscordWebhookClient.cs

+ 2
- 2
src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs View File

@@ -4,9 +4,9 @@ namespace Discord
{
public enum PermissionTarget
{
[ModelEnum("role")]
[ModelEnumValue("role")]
Role,
[ModelEnum("user")]
[ModelEnumValue("user")]
User
}
}

+ 4
- 1
src/Discord.Net.Core/Entities/Permissions/Overwrite.cs View File

@@ -1,10 +1,13 @@
namespace Discord
using Discord.Serialization;

namespace Discord
{
public struct Overwrite
{
/// <summary> Gets the unique identifier for the object this overwrite is targeting. </summary>
public ulong TargetId { get; }
/// <summary> Gets the type of object this overwrite is targeting. </summary>
[ModelStringEnum]
public PermissionTarget TargetType { get; }
/// <summary> Gets the permissions associated with this overwrite entry. </summary>
public OverwritePermissions Permissions { get; }


+ 4
- 4
src/Discord.Net.Core/Entities/Users/UserStatus.cs View File

@@ -5,13 +5,13 @@ namespace Discord
public enum UserStatus
{
Offline,
[ModelEnum("online")]
[ModelEnumValue("online")]
Online,
[ModelEnum("idle")]
[ModelEnumValue("idle")]
Idle,
[ModelEnum("idle", EnumValueType.WriteOnly)]
[ModelEnumValue("idle", EnumValueType.WriteOnly)]
AFK,
[ModelEnum("dnd")]
[ModelEnumValue("dnd")]
DoNotDisturb,
Invisible,
}


+ 1
- 0
src/Discord.Net.Rest/API/Common/Presence.cs View File

@@ -10,6 +10,7 @@ namespace Discord.API
[ModelProperty("guild_id")]
public Optional<ulong> GuildId { get; set; }
[ModelProperty("status")]
[ModelStringEnum]
public UserStatus Status { get; set; }
[ModelProperty("game")]
public Game Game { get; set; }


+ 3
- 4
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -191,7 +191,7 @@ namespace Discord.API
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;

if (_formatters.TryDequeue(out var data))
if (!_formatters.TryDequeue(out var data))
data = new ArrayFormatter(128, SymbolTable.InvariantUtf8);
try
{
@@ -240,7 +240,7 @@ namespace Discord.API
options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId;
options.IsClientBucket = AuthTokenType == TokenType.User;

if (_formatters.TryDequeue(out var data))
if (!_formatters.TryDequeue(out var data))
data = new ArrayFormatter(128, SymbolTable.InvariantUtf8);
try
{
@@ -1168,13 +1168,12 @@ namespace Discord.API
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 ReadOnlyBuffer<byte> SerializeJson(ArrayFormatter data, object value)
protected ReadOnlyBuffer<byte> SerializeJson<T>(ArrayFormatter data, T value)
{
_serializer.Write(data, value);
return new ReadOnlyBuffer<byte>(data.Formatted.Array, 0, data.Formatted.Count);
}
protected T DeserializeJson<T>(ReadOnlyBuffer<byte> data)
where T : class, new()
{
return _serializer.Read<T>(data);
}


+ 4
- 4
src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs View File

@@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters
_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)
reader.Read();
if (reader.ValueType == JsonValueType.Number)
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)
_innerConverter.Write(map, ref writer, value.Object, isTopLevel);
_innerConverter.Write(map, model, ref writer, value.Object, isTopLevel);
else
{
if (isTopLevel)


+ 2
- 2
src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs View File

@@ -5,7 +5,7 @@ namespace Discord.Serialization.Json.Converters
{
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)
reader.Read();
@@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected String");
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;
if (value.Stream != null)


+ 2
- 2
src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs View File

@@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters
{
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)
reader.Read();
@@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number");
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)
writer.WriteAttribute(map.Key, value);


+ 4
- 4
src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs View File

@@ -11,13 +11,13 @@ namespace Discord.Serialization.Json.Converters
_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)
_innerConverter.Write(map, ref writer, value.Value, isTopLevel);
_innerConverter.Write(map, model, ref writer, value.Value, isTopLevel);
}
}
}

+ 2
- 2
src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs View File

@@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters
{
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)
reader.Read();
@@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number");
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)
writer.WriteAttribute(map.Key, value);


+ 11
- 0
src/Discord.Net.Rest/Serialization/ModelSelectorGroups.cs View File

@@ -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);
}
}

+ 2
- 0
src/Discord.Net.Rpc/API/RpcFrame.cs View File

@@ -14,7 +14,9 @@ namespace Discord.API.Rpc
public Optional<string> Event { get; set; }
[ModelProperty("data")]
public Optional<ReadOnlyBuffer<byte>> Data { get; set; }

[ModelProperty("args")]
[ModelSelector(ModelSelectorGroups.RpcFrame, nameof(Event))]
public object Args { get; set; }
}
}

+ 5
- 4
src/Discord.Net.Rpc/DiscordRpcApiClient.cs View File

@@ -5,6 +5,7 @@ using Discord.Net.Rest;
using Discord.Net.WebSockets;
using Discord.Rpc;
using Discord.Serialization;
using Discord.Serialization.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -41,11 +42,11 @@ namespace Discord.API
}
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)
{
var error = Serializer.Json.Read<ErrorEvent>(data);
var error = DiscordJsonSerializer.Global.Read<ErrorEvent>(data);
return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message));
}
}
@@ -231,12 +232,12 @@ namespace Discord.API
try
{
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);
_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);
return await requestTracker.Promise.Task.ConfigureAwait(false);
}


+ 2
- 1
src/Discord.Net.Rpc/DiscordRpcClient.cs View File

@@ -8,6 +8,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Discord.Serialization;
using Discord.Serialization.Json;

namespace Discord.Rpc
{
@@ -37,7 +38,7 @@ namespace Discord.Rpc
_authorizeLock = new SemaphoreSlim(1, 1);
_rpcLogger = LogManager.CreateLogger("RPC");

_serializer = new Serializer(SerializationFormat.Json);
_serializer = DiscordJsonSerializer.Global.CreateScope();
_serializer.Error += ex =>
{
_rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();


src/Discord.Net.Serialization/EnumValueAttribute.cs → src/Discord.Net.Serialization/Attributes/ModelEnumValueAttribute.cs View File

@@ -10,12 +10,12 @@ namespace Discord.Serialization
}

[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class ModelEnumAttribute : Attribute
public class ModelEnumValueAttribute : Attribute
{
public string Key { get; }
public EnumValueType Type { get; }

public ModelEnumAttribute(string key, EnumValueType type = EnumValueType.ReadWrite)
public ModelEnumValueAttribute(string key, EnumValueType type = EnumValueType.ReadWrite)
{
Key = key;
Type = type;

src/Discord.Net.Serialization/ModelPropertyAttribute.cs → src/Discord.Net.Serialization/Attributes/ModelPropertyAttribute.cs View File

@@ -8,7 +8,9 @@ namespace Discord.Serialization
public string Key { get; }
public bool ExcludeNull { get; set; }

public ModelPropertyAttribute(string key = null)
public ModelPropertyAttribute()
: this(null) { }
public ModelPropertyAttribute(string key)
{
Key = key;
}

+ 17
- 0
src/Discord.Net.Serialization/Attributes/ModelSelectorAttribute.cs View File

@@ -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;
}
}
}

+ 9
- 0
src/Discord.Net.Serialization/Attributes/ModelStringEnumAttribute.cs View File

@@ -0,0 +1,9 @@
using System;

namespace Discord.Serialization
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ModelStringEnumAttribute : Attribute
{
}
}

+ 35
- 10
src/Discord.Net.Serialization/ConverterCollection.cs View File

@@ -17,14 +17,20 @@ namespace Discord.Serialization
private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo();

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)
{
_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)
@@ -66,15 +72,22 @@ namespace Discord.Serialization
_mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection());
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)
{
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)
{
@@ -108,7 +121,7 @@ namespace Discord.Serialization
converterType = converterType.MakeGenericType(type);
var converterTypeInfo = converterType.GetTypeInfo();

var constructors = converterTypeInfo.DeclaredConstructors.ToArray();
var constructors = converterTypeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray();
if (constructors.Length == 0)
throw new SerializationException($"{converterType.Name} is missing a constructor");
if (constructors.Length != 1)
@@ -148,5 +161,17 @@ namespace Discord.Serialization
return converters.DefaultConverterType;
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>());
}
}

+ 76
- 19
src/Discord.Net.Serialization/EnumMap.cs View File

@@ -16,9 +16,12 @@ namespace Discord.Serialization
{
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, ReadOnlyBuffer<byte>> _valueToUtf8Key;
private readonly Dictionary<T, long> _valueToInt;

public EnumMap()
{
@@ -26,50 +29,104 @@ namespace Discord.Serialization
if (!typeInfo.IsEnum)
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>();
_valueToUtf8Key = new Dictionary<T, ReadOnlyBuffer<byte>>();
_valueToInt = new Dictionary<T, long>();

foreach (T val in Enum.GetValues(typeof(T)).OfType<T>())
{
var fieldInfo = typeInfo.GetDeclaredField(Enum.GetName(typeof(T), val));
var attr = fieldInfo.GetCustomAttribute<ModelEnumAttribute>();
var attr = fieldInfo.GetCustomAttribute<ModelEnumValueAttribute>();
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)
_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;
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;
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))
return key;
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}");
}
}


+ 1
- 1
src/Discord.Net.Serialization/Extensions/JsonReaderExtensions.cs View File

@@ -29,7 +29,7 @@ namespace Discord.Serialization
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)
{


+ 5
- 5
src/Discord.Net.Serialization/Json/Converters/Collections.cs View File

@@ -3,7 +3,7 @@ using System.Text.Json;

namespace Discord.Serialization.Json.Converters
{
internal class ListPropertyConverter<T> : IJsonPropertyConverter<List<T>>
public class ListPropertyConverter<T> : IJsonPropertyConverter<List<T>>
{
private readonly IJsonPropertyConverter<T> _innerConverter;

@@ -12,25 +12,25 @@ namespace Discord.Serialization.Json.Converters
_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)
throw new SerializationException("Bad input, expected StartArray");

var list = new List<T>();
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;
}

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)
writer.WriteArrayStart(map.Key);
else
writer.WriteArrayStart();
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();
}
}


+ 35
- 0
src/Discord.Net.Serialization/Json/Converters/Dynamic.cs View File

@@ -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);
}
}
}
}

+ 51
- 5
src/Discord.Net.Serialization/Json/Converters/Enum.cs View File

@@ -2,22 +2,68 @@

namespace Discord.Serialization.Json.Converters
{
internal class EnumPropertyConverter<T> : IJsonPropertyConverter<T>
public class Int64EnumPropertyConverter<T> : IJsonPropertyConverter<T>
where T : struct
{
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)
reader.Read();
if (reader.ValueType != JsonValueType.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)
writer.WriteAttribute(map.Key, key);
else


+ 5
- 5
src/Discord.Net.Serialization/Json/Converters/Nullable.cs View File

@@ -2,7 +2,7 @@

namespace Discord.Serialization.Json.Converters
{
internal class NullablePropertyConverter<T> : IJsonPropertyConverter<T?>
public class NullablePropertyConverter<T> : IJsonPropertyConverter<T?>
where T : struct
{
private readonly IJsonPropertyConverter<T> _innerConverter;
@@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters
_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)
reader.Read();
if (reader.ValueType == JsonValueType.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)
_innerConverter.Write(map, ref writer, value.Value, isTopLevel);
_innerConverter.Write(map, model, ref writer, value.Value, isTopLevel);
else
{
if (isTopLevel)


+ 7
- 7
src/Discord.Net.Serialization/Json/Converters/Object.cs View File

@@ -2,7 +2,7 @@

namespace Discord.Serialization.Json.Converters
{
internal class ObjectPropertyConverter<T> : IJsonPropertyConverter<T>
public class ObjectPropertyConverter<T> : IJsonPropertyConverter<T>
where T : class, new()
{
private readonly ModelMap<T> _map;
@@ -12,27 +12,27 @@ namespace Discord.Serialization.Json.Converters
_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)
throw new SerializationException("Bad input, expected StartObject");
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return model;
return subModel;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new SerializationException("Bad input, expected PropertyName");
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
JsonUtils.Skip(ref reader); //Unknown property, skip
JsonReaderUtils.Skip(ref reader); //Unknown property, skip
}
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)
writer.WriteObjectStart(map.Key);


+ 6
- 6
src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs View File

@@ -3,9 +3,9 @@ using System.Text.Json;

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)
reader.Read();
@@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected String");
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)
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)
reader.Read();
@@ -32,7 +32,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected String");
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)
writer.WriteAttribute(map.Key, value);


+ 8
- 8
src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs View File

@@ -2,9 +2,9 @@

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)
reader.Read();
@@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
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)
reader.Read();
@@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
writer.WriteAttribute(map.Key, value.ToString());
@@ -42,7 +42,7 @@ namespace Discord.Serialization.Json.Converters

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)
reader.Read();
@@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
writer.WriteAttribute(map.Key, value.ToString());


+ 6
- 6
src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs View File

@@ -3,9 +3,9 @@ using System.Text.Json;

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)
reader.Read();
@@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters
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)
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)
reader.Read();
@@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected String");
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)
writer.WriteAttribute(map.Key, value.ToString());


+ 12
- 12
src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs View File

@@ -2,9 +2,9 @@

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)
reader.Read();
@@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
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)
reader.Read();
@@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
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)
reader.Read();
@@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
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)
reader.Read();
@@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
writer.WriteAttribute(map.Key, value.ToString());


+ 9
- 8
src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs View File

@@ -1,11 +1,10 @@
using System.Text.Json;
using System.Text.Utf8;

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)
reader.Read();
@@ -13,7 +12,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected String");
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)
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)
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");
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)
writer.WriteAttribute(map.Key, value);


+ 12
- 12
src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs View File

@@ -2,9 +2,9 @@

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)
reader.Read();
@@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
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)
reader.Read();
@@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
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)
reader.Read();
@@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
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)
reader.Read();
@@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters
throw new SerializationException("Bad input, expected Number or String");
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)
writer.WriteAttribute(map.Key, value.ToString());


+ 8
- 3
src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs View File

@@ -2,9 +2,14 @@

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);
}
}

+ 0
- 14
src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs View File

@@ -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);
}
}

+ 19
- 10
src/Discord.Net.Serialization/Json/JsonPropertyMap.cs View File

@@ -4,19 +4,28 @@ using System.Text.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;

_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)
@@ -24,11 +33,11 @@ namespace Discord.Serialization.Json
var value = _getFunc(model);
if (value == null && ExcludeNull)
return;
_converter.Write(this, ref writer, value, true);
_converter.Write(this, model, ref writer, value, true);
}
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);
}
}


+ 28
- 7
src/Discord.Net.Serialization/Json/JsonSerializer.cs View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Formatting;
@@ -28,7 +29,7 @@ namespace Discord.Serialization.Json
AddConverter<double, Converters.DoublePropertyConverter>();
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<DateTime, Converters.DateTimePropertyConverter>();
@@ -37,11 +38,20 @@ namespace Discord.Serialization.Json
AddConverter<bool, Converters.BooleanPropertyConverter>();
AddConverter<Guid, Converters.GuidPropertyConverter>();

AddConverter<object, Converters.DynamicPropertyConverter>(
(type, prop) => prop.GetCustomAttributes<ModelSelectorAttribute>().Any());

AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>));
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) { }
public JsonSerializer CreateScope() => new JsonSerializer(this);
@@ -56,22 +66,33 @@ namespace Discord.Serialization.Json
protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo 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)
{
var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8);
if (!reader.Read())
return null;
return default;
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)
{
var writer = new JsonWriter(stream);
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);
}
}

+ 8
- 5
src/Discord.Net.Serialization/ModelMap.cs View File

@@ -1,24 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Discord.Serialization
{
public class ModelMap<TModel>
where TModel : class, new()
{
private BufferDictionary<PropertyMap> _dictionary;
private BufferDictionary<PropertyMap> _propDict;

public bool HasDynamics { get; }
public PropertyMap[] Properties { get; }

public ModelMap(List<PropertyMap> properties)
public ModelMap(Serializer serializer, TypeInfo type, List<PropertyMap> properties)
{
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)
=> _dictionary.TryGetValue(key, out value);
=> _propDict.TryGetValue(key, out value);
public bool TryGetProperty(ReadOnlySpan<byte> key, out PropertyMap value)
=> _dictionary.TryGetValue(key, out value);
=> _propDict.TryGetValue(key, out value);
}
}

+ 74
- 4
src/Discord.Net.Serialization/PropertyMap.cs View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Utf8;

@@ -8,15 +10,83 @@ namespace Discord.Serialization
{
public string Key { get; }
public ReadOnlyBuffer<byte> Utf8Key { get; }
public string Name { 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());
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;
}
}
}

+ 40
- 0
src/Discord.Net.Serialization/SelectorGroup.cs View File

@@ -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
- 25
src/Discord.Net.Serialization/Serializer.cs View File

@@ -32,41 +32,49 @@ namespace Discord.Serialization
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();
_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();
_converters.Add(type, converter, condition);
_converters.Add(valueType, converterType, condition);
}

public void AddGenericConverter(Type converter)
public void AddGenericConverter(Type converterType)
{
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();
_converters.AddGeneric(converter, condition);
_converters.AddGeneric(converterType, condition);
}
public void AddGenericConverter(Type value, Type converter)
public void AddGenericConverter(Type valueType, Type converterType)
{
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();
_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>()
where TModel : class, new()
{
@@ -80,24 +88,23 @@ namespace Discord.Serialization
var properties = new List<PropertyMap>();
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>;
}

private PropertyMap MapProperty<TModel>(PropertyInfo propInfo)
where TModel : class, new()
=> _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()
{
if (IsScoped)


+ 20
- 0
src/Discord.Net.WebSocket/API/Gateway/GatewaySocketFrame.cs View File

@@ -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; }
}
}

src/Discord.Net.WebSocket/API/SocketFrame.cs → src/Discord.Net.WebSocket/API/Voice/VoiceSocketFrame.cs View File

@@ -1,18 +1,20 @@
#pragma warning disable CS1591
using Discord.Serialization;
using System;

namespace Discord.API
namespace Discord.API.Voice
{
internal class SocketFrame
internal class VoiceSocketFrame
{
[ModelProperty("op")]
public int Operation { get; set; }
public VoiceOpCode Operation { get; set; }
[ModelProperty("t", ExcludeNull = true)]
public string Type { get; set; }
[ModelProperty("s", ExcludeNull = true)]
public int? Sequence { get; set; }

[ModelProperty("d")]
public ReadOnlyBuffer<byte> Payload { get; set; }
[ModelSelector(nameof(Operation), ModelSelectorGroups.VoiceFrame)]
[ModelSelector(nameof(Type), ModelSelectorGroups.VoiceDispatchFrame)]
public object Payload { get; set; }
}
}

+ 6
- 5
src/Discord.Net.WebSocket/Audio/AudioClient.cs View File

@@ -11,6 +11,7 @@ using System.Threading.Tasks;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Discord.Serialization;
using Discord.Serialization.Json;

namespace Discord.Audio
{
@@ -66,7 +67,7 @@ namespace Discord.Audio
ChannelId = channelId;
_audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}");

_serializer = new Serializer(SerializationFormat.Json);
_serializer = DiscordJsonSerializer.Global.CreateScope();
_serializer.Error += ex =>
{
_audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();
@@ -225,7 +226,7 @@ namespace Discord.Audio
_streams.Clear();
}

private async Task ProcessMessageAsync(VoiceOpCode opCode, ReadOnlyBuffer<byte> payload)
private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
{
_lastMessageTime = Environment.TickCount;

@@ -236,7 +237,7 @@ namespace Discord.Audio
case VoiceOpCode.Ready:
{
await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false);
var data = _serializer.Read<ReadyEvent>(payload);
var data = payload as ReadyEvent;

_ssrc = data.SSRC;

@@ -252,7 +253,7 @@ namespace Discord.Audio
case VoiceOpCode.SessionDescription:
{
await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false);
var data = _serializer.Read<SessionDescriptionEvent>(payload);
var data = payload as SessionDescriptionEvent;

if (data.Mode != DiscordVoiceApiClient.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);

var data = _serializer.Read<SpeakingEvent>(payload);
var data = payload as SpeakingEvent;
_ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up

await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking);


+ 2
- 1
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Discord.Serialization;
using Discord.Serialization.Json;

namespace Discord.WebSocket
{
@@ -54,7 +55,7 @@ namespace Discord.WebSocket
_baseConfig = config;
_connectionGroupLock = new SemaphoreSlim(1, 1);

_serializer = new Serializer(SerializationFormat.Json);
_serializer = DiscordJsonSerializer.Global.CreateScope();
_serializer.Error += ex =>
{
_restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();


+ 11
- 14
src/Discord.Net.WebSocket/DiscordSocketApiClient.cs View File

@@ -21,8 +21,8 @@ namespace Discord.API
{
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>>();
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); } }
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
@@ -60,16 +60,16 @@ namespace Discord.API
_decompressionStream.SetLength(_decompressionStream.Position);

_decompressionStream.Position = 0;
var msg = _serializer.Read<SocketFrame>(_decompressionStream.ToReadOnlyBuffer());
var msg = _serializer.Read<GatewaySocketFrame>(_decompressionStream.ToReadOnlyBuffer());
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
{
var msg = _serializer.Read<SocketFrame>(data);
var msg = _serializer.Read<GatewaySocketFrame>(data);
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 =>
@@ -173,20 +173,17 @@ namespace Discord.API
{
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
{
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);
}
finally
{
_formatters.Enqueue(data1);
_formatters.Enqueue(data2);
_formatters.Enqueue(data);
}
}



+ 37
- 45
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -5,6 +5,7 @@ using Discord.Net.Udp;
using Discord.Net.WebSockets;
using Discord.Rest;
using Discord.Serialization;
using Discord.Serialization.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -87,7 +88,7 @@ namespace Discord.WebSocket
_stateLock = new SemaphoreSlim(1, 1);
_gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}");

_serializer = new Serializer(SerializationFormat.Json);
_serializer = DiscordJsonSerializer.Global.CreateScope();
_serializer.Error += ex =>
{
_restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();
@@ -382,7 +383,7 @@ namespace Discord.WebSocket
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)
_lastSeq = seq.Value;
@@ -395,7 +396,7 @@ namespace Discord.WebSocket
case GatewayOpCode.Hello:
{
await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false);
var data = _serializer.Read<HelloEvent>(payload);
var data = payload as HelloEvent;

_heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken);
}
@@ -428,7 +429,7 @@ namespace Discord.WebSocket

_sessionId = null;
_lastSeq = 0;
bool retry = IsTrue();
bool retry = (bool)payload;
if (retry)
_connection.Reconnect(); //TODO: Untested
else
@@ -451,7 +452,7 @@ namespace Discord.WebSocket
{
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 currentUser = SocketSelfUser.Create(this, state, data.User);
@@ -521,7 +522,7 @@ namespace Discord.WebSocket
//Guilds
case "GUILD_CREATE":
{
var data = _serializer.Read<ExtendedGuild>(payload);
var data = payload as ExtendedGuild;

if (data.Unavailable == false)
{
@@ -573,7 +574,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -592,7 +593,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -610,7 +611,7 @@ namespace Discord.WebSocket
case "GUILD_SYNC":
{
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);
if (guild != null)
{
@@ -631,7 +632,7 @@ namespace Discord.WebSocket
break;
case "GUILD_DELETE":
{
var data = _serializer.Read<ExtendedGuild>(payload);
var data = payload as ExtendedGuild;
if (data.Unavailable == true)
{
type = "GUILD_UNAVAILABLE";
@@ -673,7 +674,7 @@ namespace Discord.WebSocket
{
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;
if (data.GuildId.IsSpecified)
{
@@ -705,7 +706,7 @@ namespace Discord.WebSocket
{
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);
if (channel != null)
{
@@ -733,7 +734,7 @@ namespace Discord.WebSocket
await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false);

SocketChannel channel = null;
var data = _serializer.Read<API.Channel>(payload);
var data = payload as API.Channel;
if (data.GuildId.IsSpecified)
{
var guild = State.GetGuild(data.GuildId.Value);
@@ -771,7 +772,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -797,7 +798,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -835,7 +836,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -870,7 +871,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -894,7 +895,7 @@ namespace Discord.WebSocket
{
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)
{
var user = channel.GetOrAddUser(data.User);
@@ -911,7 +912,7 @@ namespace Discord.WebSocket
{
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)
{
var user = channel.RemoveUser(data.User.Id);
@@ -936,7 +937,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -960,7 +961,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -995,7 +996,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -1029,7 +1030,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -1055,7 +1056,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -1083,7 +1084,7 @@ namespace Discord.WebSocket
{
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)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1130,7 +1131,7 @@ namespace Discord.WebSocket
{
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)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1177,7 +1178,7 @@ namespace Discord.WebSocket
{
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)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1204,7 +1205,7 @@ namespace Discord.WebSocket
{
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)
{
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);

var data = _serializer.Read<API.Gateway.Reaction>(payload);
var data = payload as API.Gateway.Reaction;
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
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);

var data = _serializer.Read<RemoveAllReactionsEvent>(payload);
var data = payload as RemoveAllReactionsEvent;
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
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);

var data = _serializer.Read<MessageDeleteBulkEvent>(payload);
var data = payload as MessageDeleteBulkEvent;
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1305,7 +1306,7 @@ namespace Discord.WebSocket
{
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)
{
@@ -1364,7 +1365,7 @@ namespace Discord.WebSocket
{
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)
{
var guild = (channel as SocketGuildChannel)?.Guild;
@@ -1386,7 +1387,7 @@ namespace Discord.WebSocket
{
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)
{
var before = CurrentUser.Clone();
@@ -1406,7 +1407,7 @@ namespace Discord.WebSocket
{
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;
SocketVoiceState before, after;
if (data.GuildId != null)
@@ -1478,7 +1479,7 @@ namespace Discord.WebSocket
{
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);
if (guild != null)
{
@@ -1528,15 +1529,6 @@ namespace Discord.WebSocket
{
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)


+ 11
- 14
src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs View File

@@ -31,8 +31,8 @@ namespace Discord.Audio
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>>();

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); } }
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); } }
@@ -79,16 +79,16 @@ namespace Discord.Audio
_decompressionStream.SetLength(_decompressionStream.Position);

_decompressionStream.Position = 0;
var msg = _serializer.Read<SocketFrame>(_decompressionStream.ToReadOnlyBuffer());
var msg = _serializer.Read<VoiceSocketFrame>(_decompressionStream.ToReadOnlyBuffer());
if (msg != null)
await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false);
await _receivedEvent.InvokeAsync(msg.Operation, msg.Payload).ConfigureAwait(false);
}
}
else
{
var msg = _serializer.Read<SocketFrame>(data);
var msg = _serializer.Read<VoiceSocketFrame>(data);
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 =>
@@ -114,20 +114,17 @@ namespace Discord.Audio

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
{
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);
}
finally
{
_formatters.Enqueue(data1);
_formatters.Enqueue(data2);
_formatters.Enqueue(data);
}
}
public async Task SendAsync(byte[] data, int offset, int bytes)


+ 2
- 1
src/Discord.Net.Webhook/DiscordWebhookClient.cs View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using System.Linq;
using Discord.Logging;
using Discord.Serialization;
using Discord.Serialization.Json;

namespace Discord.Webhook
{
@@ -29,7 +30,7 @@ namespace Discord.Webhook
{
_webhookId = webhookId;

_serializer = new Serializer(SerializationFormat.Json);
_serializer = DiscordJsonSerializer.Global.CreateScope();
_serializer.Error += ex =>
{
_restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();


Loading…
Cancel
Save