| @@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| else | else | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value.Id); | |||||
| writer.WriteAttribute(map.Key, value.Id); | |||||
| else | else | ||||
| writer.WriteValue(value.Id); | writer.WriteValue(value.Id); | ||||
| } | } | ||||
| @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -27,8 +27,11 @@ namespace Discord.Serialization | |||||
| public static bool ParseBool(this JsonReader reader) => reader.Value.ParseBool(); | public static bool ParseBool(this JsonReader reader) => reader.Value.ParseBool(); | ||||
| public static Guid ParseGuid(this JsonReader reader) => reader.Value.ParseGuid(); | public static Guid ParseGuid(this JsonReader reader) => reader.Value.ParseGuid(); | ||||
| } | |||||
| public static void Skip(this JsonReader reader) | |||||
| public static class JsonUtils | |||||
| { | |||||
| public static void Skip(ref JsonReader reader) | |||||
| { | { | ||||
| int initialDepth = reader._depth; | int initialDepth = reader._depth; | ||||
| while (reader.Read() && reader._depth > initialDepth) { } | while (reader.Read() && reader._depth > initialDepth) { } | ||||
| @@ -26,7 +26,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, List<T> value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, List<T> value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteArrayStart(map.Utf16Key); | |||||
| 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++) | ||||
| @@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| else | else | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttributeNull(map.Utf16Key); | |||||
| writer.WriteAttributeNull(map.Key); | |||||
| else | else | ||||
| writer.WriteNull(); | writer.WriteNull(); | ||||
| } | } | ||||
| @@ -1,5 +1,4 @@ | |||||
| using System.Text.Json; | using System.Text.Json; | ||||
| using System.Text.Utf8; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| @@ -21,17 +20,17 @@ namespace Discord.Serialization.Json.Converters | |||||
| 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.PropertiesByKey.TryGetValue(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(model, ref reader); | ||||
| else | else | ||||
| reader.Skip(); //Unknown property, skip | |||||
| JsonUtils.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, ref JsonWriter writer, T value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteObjectStart(map.Utf16Key); | |||||
| writer.WriteObjectStart(map.Key); | |||||
| else | else | ||||
| writer.WriteObjectStart(); | writer.WriteObjectStart(); | ||||
| for (int i = 0; i < _map.Properties.Length; i++) | for (int i = 0; i < _map.Properties.Length; i++) | ||||
| @@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
| writer.WriteAttribute(map.Key, value.ToString()); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, double value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, double value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
| writer.WriteAttribute(map.Key, value.ToString()); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, decimal value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, decimal value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
| writer.WriteAttribute(map.Key, value.ToString()); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -19,7 +19,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -38,7 +38,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
| writer.WriteAttribute(map.Key, value.ToString()); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
| writer.WriteAttribute(map.Key, value.ToString()); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -35,29 +35,9 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| internal class Utf8StringPropertyConverter : IJsonPropertyConverter<Utf8String> | |||||
| { | |||||
| public Utf8String Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) | |||||
| { | |||||
| if (isTopLevel) | |||||
| reader.Read(); | |||||
| if (reader.ValueType != JsonValueType.String) | |||||
| throw new SerializationException("Bad input, expected String"); | |||||
| return new Utf8String(reader.Value); | |||||
| } | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, Utf8String value, bool isTopLevel) | |||||
| { | |||||
| //TODO: Serialization causes allocs, fix | |||||
| if (isTopLevel) | |||||
| writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
| else | |||||
| writer.WriteValue(value.ToString()); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value); | |||||
| writer.WriteAttribute(map.Key, value); | |||||
| else | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | ||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
| writer.WriteAttribute(map.Key, value.ToString()); | |||||
| else | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -1,12 +1,12 @@ | |||||
| using System.Text.Json; | |||||
| using System.Text.Utf8; | |||||
| using System; | |||||
| using System.Text.Json; | |||||
| namespace Discord.Serialization | namespace Discord.Serialization | ||||
| { | { | ||||
| internal interface IJsonPropertyMap<TModel> | internal interface IJsonPropertyMap<TModel> | ||||
| { | { | ||||
| string Utf16Key { get; } | |||||
| Utf8String Utf8Key { get; } | |||||
| string Key { get; } | |||||
| ReadOnlyBuffer<byte> Utf8Key { get; } | |||||
| void Write(TModel model, ref JsonWriter writer); | void Write(TModel model, ref JsonWriter writer); | ||||
| void Read(TModel model, ref JsonReader reader); | void Read(TModel model, ref JsonReader reader); | ||||
| @@ -5,7 +5,6 @@ using System.Reflection; | |||||
| using System.Text; | using System.Text; | ||||
| using System.Text.Formatting; | using System.Text.Formatting; | ||||
| using System.Text.Json; | using System.Text.Json; | ||||
| using System.Text.Utf8; | |||||
| namespace Discord.Serialization.Json | namespace Discord.Serialization.Json | ||||
| { | { | ||||
| @@ -29,7 +28,6 @@ namespace Discord.Serialization.Json | |||||
| //AddConverter<char, Converters.CharPropertyConverter>(); //char.Parse does not support Json.Net's serialization | //AddConverter<char, Converters.CharPropertyConverter>(); //char.Parse does not support Json.Net's serialization | ||||
| AddConverter<string, Converters.StringPropertyConverter>(); | AddConverter<string, Converters.StringPropertyConverter>(); | ||||
| AddConverter<Utf8String, Converters.Utf8StringPropertyConverter>(); | |||||
| AddConverter<DateTime, Converters.DateTimePropertyConverter>(); | AddConverter<DateTime, Converters.DateTimePropertyConverter>(); | ||||
| AddConverter<DateTimeOffset, Converters.DateTimeOffsetPropertyConverter>(); | AddConverter<DateTimeOffset, Converters.DateTimeOffsetPropertyConverter>(); | ||||
| @@ -7,13 +7,211 @@ namespace Discord.Serialization | |||||
| public class ModelMap<TModel> | public class ModelMap<TModel> | ||||
| where TModel : class, new() | where TModel : class, new() | ||||
| { | { | ||||
| public readonly PropertyMap[] Properties; | |||||
| public readonly Dictionary<ReadOnlySpan<byte>, PropertyMap> PropertiesByKey; | |||||
| private struct PropertyEntry | |||||
| { | |||||
| public int hashCode; // Lower 31 bits of hash code, -1 if unused | |||||
| public int next; // Index of next entry, -1 if last | |||||
| public ReadOnlyBuffer<byte> key; // Key of entry | |||||
| public PropertyMap value; // Value of entry | |||||
| } | |||||
| private int[] _buckets; | |||||
| private PropertyEntry[] _entries; | |||||
| private int _count; | |||||
| private int _freeList; | |||||
| private int _freeCount; | |||||
| public PropertyMap[] Properties { get; } | |||||
| public ModelMap(List<PropertyMap> properties) | |||||
| { | |||||
| Properties = properties.ToArray(); | |||||
| int size = HashHelpers.GetPrime(Properties.Length); | |||||
| _buckets = new int[size]; | |||||
| for (int i = 0; i < _buckets.Length; i++) _buckets[i] = -1; | |||||
| _entries = new PropertyEntry[size]; | |||||
| _freeList = -1; | |||||
| for (int i = 0; i < properties.Count; i++) | |||||
| AddProperty(properties[i].Utf8Key, properties[i]); | |||||
| } | |||||
| public void AddProperty(ReadOnlyBuffer<byte> key, PropertyMap value) | |||||
| { | |||||
| int hashCode = GetKeyHashCode(key) & 0x7FFFFFFF; | |||||
| int targetBucket = hashCode % _buckets.Length; | |||||
| public ModelMap(Dictionary<ReadOnlySpan<byte>, PropertyMap> properties) | |||||
| for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) | |||||
| { | |||||
| if (_entries[i].hashCode == hashCode && KeyEquals(_entries[i].key, key)) | |||||
| throw new ArgumentException("Duplicate property", nameof(key)); | |||||
| } | |||||
| int index; | |||||
| if (_freeCount > 0) | |||||
| { | |||||
| index = _freeList; | |||||
| _freeList = _entries[index].next; | |||||
| _freeCount--; | |||||
| } | |||||
| else | |||||
| { | |||||
| if (_count == _entries.Length) | |||||
| { | |||||
| Resize(); | |||||
| targetBucket = hashCode % _buckets.Length; | |||||
| } | |||||
| index = _count; | |||||
| _count++; | |||||
| } | |||||
| _entries[index].hashCode = hashCode; | |||||
| _entries[index].next = _buckets[targetBucket]; | |||||
| _entries[index].key = key; | |||||
| _entries[index].value = value; | |||||
| _buckets[targetBucket] = index; | |||||
| } | |||||
| private void Resize() | |||||
| { | |||||
| int newSize = HashHelpers.ExpandPrime(_count); | |||||
| var newBuckets = new int[newSize]; | |||||
| for (int i = 0; i < newBuckets.Length; i++) | |||||
| newBuckets[i] = -1; | |||||
| var newEntries = new PropertyEntry[newSize]; | |||||
| Array.Copy(_entries, 0, newEntries, 0, _count); | |||||
| for (int i = 0; i < _count; i++) | |||||
| { | |||||
| if (newEntries[i].hashCode >= 0) | |||||
| { | |||||
| int bucket = newEntries[i].hashCode % newSize; | |||||
| newEntries[i].next = newBuckets[bucket]; | |||||
| newBuckets[bucket] = i; | |||||
| } | |||||
| } | |||||
| _buckets = newBuckets; | |||||
| _entries = newEntries; | |||||
| } | |||||
| private int FindEntry(ReadOnlyBuffer<byte> key) | |||||
| { | |||||
| if (_buckets != null) | |||||
| { | |||||
| int hashCode = GetKeyHashCode(key) & 0x7FFFFFFF; | |||||
| for (int i = _buckets[hashCode % _buckets.Length]; i >= 0; i = _entries[i].next) | |||||
| { | |||||
| if (_entries[i].hashCode == hashCode && KeyEquals(_entries[i].key, key)) | |||||
| return i; | |||||
| } | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| private int FindEntry(ReadOnlySpan<byte> key) | |||||
| { | |||||
| if (_buckets != null) | |||||
| { | |||||
| int hashCode = GetKeyHashCode(key) & 0x7FFFFFFF; | |||||
| for (int i = _buckets[hashCode % _buckets.Length]; i >= 0; i = _entries[i].next) | |||||
| { | |||||
| if (_entries[i].hashCode == hashCode && KeyEquals(_entries[i].key, key)) | |||||
| return i; | |||||
| } | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| public bool TryGetProperty(ReadOnlyBuffer<byte> key, out PropertyMap value) | |||||
| { | |||||
| int i = FindEntry(key); | |||||
| if (i >= 0) | |||||
| { | |||||
| value = _entries[i].value; | |||||
| return true; | |||||
| } | |||||
| value = default; | |||||
| return false; | |||||
| } | |||||
| public bool TryGetProperty(ReadOnlySpan<byte> key, out PropertyMap value) | |||||
| { | |||||
| int i = FindEntry(key); | |||||
| if (i >= 0) | |||||
| { | |||||
| value = _entries[i].value; | |||||
| return true; | |||||
| } | |||||
| value = default; | |||||
| return false; | |||||
| } | |||||
| private bool KeyEquals(ReadOnlyBuffer<byte> x, ReadOnlyBuffer<byte> y) => x.Span.SequenceEqual(y.Span); | |||||
| private bool KeyEquals(ReadOnlyBuffer<byte> x, ReadOnlySpan<byte> y) => x.Span.SequenceEqual(y); | |||||
| private int GetKeyHashCode(ReadOnlyBuffer<byte> obj) => GetKeyHashCode(obj.Span); | |||||
| private int GetKeyHashCode(ReadOnlySpan<byte> obj) | |||||
| { | |||||
| //From Utf8String | |||||
| //TODO: Replace when they do | |||||
| unchecked | |||||
| { | |||||
| if (obj.Length <= 4) | |||||
| { | |||||
| int hash = obj.Length; | |||||
| for (int i = 0; i < obj.Length; i++) | |||||
| { | |||||
| hash <<= 8; | |||||
| hash ^= obj[i]; | |||||
| } | |||||
| return hash; | |||||
| } | |||||
| else | |||||
| { | |||||
| int hash = obj.Length; | |||||
| hash ^= obj[0]; | |||||
| hash <<= 8; | |||||
| hash ^= obj[1]; | |||||
| hash <<= 8; | |||||
| hash ^= obj[obj.Length - 2]; | |||||
| hash <<= 8; | |||||
| hash ^= obj[obj.Length - 1]; | |||||
| return hash; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| internal static class HashHelpers | |||||
| { | |||||
| public static readonly int[] primes = { | |||||
| 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, | |||||
| 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, | |||||
| 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, | |||||
| 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, | |||||
| 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369, 8639249, 10367101, | |||||
| 12440537, 14928671, 17914409, 21497293, 25796759, 30956117, 37147349, 44576837, 53492207, 64190669, | |||||
| 77028803, 92434613, 110921543, 133105859, 159727031, 191672443, 230006941, 276008387, 331210079, | |||||
| 397452101, 476942527, 572331049, 686797261, 824156741, 988988137, 1186785773, 1424142949, 1708971541, | |||||
| 2050765853, MaxPrimeArrayLength }; | |||||
| public static int GetPrime(int min) | |||||
| { | |||||
| for (int i = 0; i < primes.Length; i++) | |||||
| { | |||||
| int prime = primes[i]; | |||||
| if (prime >= min) return prime; | |||||
| } | |||||
| return min; | |||||
| } | |||||
| public static int ExpandPrime(int oldSize) | |||||
| { | { | ||||
| PropertiesByKey = properties; | |||||
| Properties = PropertiesByKey.Values.ToArray(); | |||||
| int newSize = 2 * oldSize; | |||||
| if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) | |||||
| return MaxPrimeArrayLength; | |||||
| return GetPrime(newSize); | |||||
| } | } | ||||
| public const int MaxPrimeArrayLength = 0x7FEFFFFD; | |||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -1,20 +1,21 @@ | |||||
| using System.Reflection; | |||||
| using System; | |||||
| using System.Reflection; | |||||
| using System.Text.Utf8; | using System.Text.Utf8; | ||||
| namespace Discord.Serialization | namespace Discord.Serialization | ||||
| { | { | ||||
| public abstract class PropertyMap | public abstract class PropertyMap | ||||
| { | { | ||||
| public string Utf16Key { get; } | |||||
| public Utf8String Utf8Key { get; } | |||||
| public string Key { get; } | |||||
| public ReadOnlyBuffer<byte> Utf8Key { get; } | |||||
| public bool ExcludeNull { get; } | public bool ExcludeNull { get; } | ||||
| public PropertyMap(PropertyInfo propInfo) | public PropertyMap(PropertyInfo propInfo) | ||||
| { | { | ||||
| var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | ||||
| Utf16Key = jsonProperty?.Key ?? propInfo.Name; | |||||
| Utf8Key = new Utf8String(Utf16Key); | |||||
| Key = jsonProperty?.Key ?? propInfo.Name; | |||||
| Utf8Key = new ReadOnlyBuffer<byte>(new Utf8String(Key).Bytes.ToArray()); | |||||
| ExcludeNull = jsonProperty?.ExcludeNull ?? false; | ExcludeNull = jsonProperty?.ExcludeNull ?? false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -29,11 +29,11 @@ namespace Discord.Serialization | |||||
| .Where(x => x.CanRead && x.CanWrite) | .Where(x => x.CanRead && x.CanWrite) | ||||
| .ToArray(); | .ToArray(); | ||||
| var properties = new Dictionary<ReadOnlySpan<byte>, PropertyMap>(propInfos.Length, Utf8SpanComparer.Instance); | |||||
| 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]); | var propMap = MapProperty<TModel>(propInfos[i]); | ||||
| properties.Add(propMap.Utf8Key, propMap); | |||||
| properties.Add(propMap); | |||||
| } | } | ||||
| return new ModelMap<TModel>(properties); | return new ModelMap<TModel>(properties); | ||||
| }) as ModelMap<TModel>; | }) as ModelMap<TModel>; | ||||
| @@ -1,42 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace Discord.Serialization | |||||
| { | |||||
| internal class Utf8SpanComparer : IEqualityComparer<ReadOnlySpan<byte>> | |||||
| { | |||||
| public static readonly Utf8SpanComparer Instance = new Utf8SpanComparer(); | |||||
| public bool Equals(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y) => x.SequenceEqual(y); | |||||
| public int GetHashCode(ReadOnlySpan<byte> obj) | |||||
| { | |||||
| //From Utf8String | |||||
| //TODO: Replace when they do | |||||
| unchecked | |||||
| { | |||||
| if (obj.Length <= 4) | |||||
| { | |||||
| int hash = obj.Length; | |||||
| for (int i = 0; i < obj.Length; i++) | |||||
| { | |||||
| hash <<= 8; | |||||
| hash ^= obj[i]; | |||||
| } | |||||
| return hash; | |||||
| } | |||||
| else | |||||
| { | |||||
| int hash = obj.Length; | |||||
| hash ^= obj[0]; | |||||
| hash <<= 8; | |||||
| hash ^= obj[1]; | |||||
| hash <<= 8; | |||||
| hash ^= obj[obj.Length - 2]; | |||||
| hash <<= 8; | |||||
| hash ^= obj[obj.Length - 1]; | |||||
| return hash; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||