Browse Source

Replace invalid span usage.

voice-allocs
RogueException 7 years ago
parent
commit
d00aef8982
19 changed files with 244 additions and 107 deletions
  1. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs
  2. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs
  3. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs
  4. +4
    -1
      src/Discord.Net.Serialization/Extensions/JsonReaderExtensions.cs
  5. +1
    -1
      src/Discord.Net.Serialization/Json/Converters/Collections.cs
  6. +1
    -1
      src/Discord.Net.Serialization/Json/Converters/Nullable.cs
  7. +3
    -4
      src/Discord.Net.Serialization/Json/Converters/Object.cs
  8. +2
    -2
      src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs
  9. +3
    -3
      src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs
  10. +2
    -2
      src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs
  11. +4
    -4
      src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs
  12. +1
    -21
      src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs
  13. +4
    -4
      src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs
  14. +4
    -4
      src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs
  15. +0
    -2
      src/Discord.Net.Serialization/Json/JsonFormat.cs
  16. +204
    -6
      src/Discord.Net.Serialization/ModelMap.cs
  17. +6
    -5
      src/Discord.Net.Serialization/PropertyMap.cs
  18. +2
    -2
      src/Discord.Net.Serialization/SerializationFormat.cs
  19. +0
    -42
      src/Discord.Net.Serialization/Utf8SpanComparer.cs

+ 1
- 1
src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs View File

@@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters
else
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value.Id);
writer.WriteAttribute(map.Key, value.Id);
else
writer.WriteValue(value.Id);
}


+ 1
- 1
src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value.ToString());
}


+ 1
- 1
src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value.ToString());
}


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

@@ -27,8 +27,11 @@ namespace Discord.Serialization

public static bool ParseBool(this JsonReader reader) => reader.Value.ParseBool();
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;
while (reader.Read() && reader._depth > initialDepth) { }


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

@@ -26,7 +26,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, List<T> value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteArrayStart(map.Utf16Key);
writer.WriteArrayStart(map.Key);
else
writer.WriteArrayStart();
for (int i = 0; i < value.Count; i++)


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

@@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters
else
{
if (isTopLevel)
writer.WriteAttributeNull(map.Utf16Key);
writer.WriteAttributeNull(map.Key);
else
writer.WriteNull();
}


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

@@ -1,5 +1,4 @@
using System.Text.Json;
using System.Text.Utf8;

namespace Discord.Serialization.Json.Converters
{
@@ -21,17 +20,17 @@ namespace Discord.Serialization.Json.Converters
if (reader.TokenType != JsonTokenType.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);
else
reader.Skip(); //Unknown property, skip
JsonUtils.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)
{
if (isTopLevel)
writer.WriteObjectStart(map.Utf16Key);
writer.WriteObjectStart(map.Key);
else
writer.WriteObjectStart();
for (int i = 0; i < _map.Properties.Length; i++)


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

@@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}


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

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value.ToString());
writer.WriteAttribute(map.Key, value.ToString());
else
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)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value.ToString());
writer.WriteAttribute(map.Key, value.ToString());
else
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)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value.ToString());
writer.WriteAttribute(map.Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


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

@@ -19,7 +19,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -38,7 +38,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value.ToString());
writer.WriteAttribute(map.Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


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

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value.ToString());
writer.WriteAttribute(map.Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


+ 1
- 21
src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs View File

@@ -35,29 +35,9 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
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());
}
}
}

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

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value);
writer.WriteAttribute(map.Key, value);
else
writer.WriteValue(value);
}
@@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Utf16Key, value.ToString());
writer.WriteAttribute(map.Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


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

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

namespace Discord.Serialization
{
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 Read(TModel model, ref JsonReader reader);


+ 0
- 2
src/Discord.Net.Serialization/Json/JsonFormat.cs View File

@@ -5,7 +5,6 @@ using System.Reflection;
using System.Text;
using System.Text.Formatting;
using System.Text.Json;
using System.Text.Utf8;

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<string, Converters.StringPropertyConverter>();
AddConverter<Utf8String, Converters.Utf8StringPropertyConverter>();

AddConverter<DateTime, Converters.DateTimePropertyConverter>();
AddConverter<DateTimeOffset, Converters.DateTimeOffsetPropertyConverter>();


+ 204
- 6
src/Discord.Net.Serialization/ModelMap.cs View File

@@ -7,13 +7,211 @@ namespace Discord.Serialization
public class ModelMap<TModel>
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;
}
}
}

+ 6
- 5
src/Discord.Net.Serialization/PropertyMap.cs View File

@@ -1,20 +1,21 @@
using System.Reflection;
using System;
using System.Reflection;
using System.Text.Utf8;

namespace Discord.Serialization
{
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 PropertyMap(PropertyInfo propInfo)
{
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;
}
}


+ 2
- 2
src/Discord.Net.Serialization/SerializationFormat.cs View File

@@ -29,11 +29,11 @@ namespace Discord.Serialization
.Where(x => x.CanRead && x.CanWrite)
.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++)
{
var propMap = MapProperty<TModel>(propInfos[i]);
properties.Add(propMap.Utf8Key, propMap);
properties.Add(propMap);
}
return new ModelMap<TModel>(properties);
}) as ModelMap<TModel>;


+ 0
- 42
src/Discord.Net.Serialization/Utf8SpanComparer.cs View File

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

Loading…
Cancel
Save