Browse Source

Added enum serialization

voice-allocs
RogueException 7 years ago
parent
commit
894efcca0a
10 changed files with 365 additions and 212 deletions
  1. +5
    -1
      src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs
  2. +7
    -1
      src/Discord.Net.Core/Entities/Users/UserStatus.cs
  3. +220
    -0
      src/Discord.Net.Serialization/BufferDictionary.cs
  4. +76
    -0
      src/Discord.Net.Serialization/EnumMap.cs
  5. +24
    -0
      src/Discord.Net.Serialization/EnumValueAttribute.cs
  6. +27
    -0
      src/Discord.Net.Serialization/Json/Converters/Enum.cs
  7. +0
    -12
      src/Discord.Net.Serialization/Json/Converters/Primitives.Enum.cs
  8. +1
    -1
      src/Discord.Net.Serialization/Json/JsonFormat.cs
  9. +4
    -197
      src/Discord.Net.Serialization/ModelMap.cs
  10. +1
    -0
      src/Discord.Net.Serialization/ModelPropertyAttribute.cs

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

@@ -1,8 +1,12 @@
namespace Discord
using Discord.Serialization;

namespace Discord
{
public enum PermissionTarget
{
[ModelEnum("role")]
Role,
[ModelEnum("user")]
User
}
}

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

@@ -1,11 +1,17 @@
namespace Discord
using Discord.Serialization;

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


+ 220
- 0
src/Discord.Net.Serialization/BufferDictionary.cs View File

@@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Discord.Serialization
{
internal class BufferDictionary<TValue>
{
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 TValue value; // Value of entry
}

private int[] _buckets;
private PropertyEntry[] _entries;
private int _count;
private int _freeList;
private int _freeCount;

public BufferDictionary()
{
int size = HashHelpers.GetPrime(0);
_buckets = new int[size];
for (int i = 0; i < _buckets.Length; i++) _buckets[i] = -1;
_entries = new PropertyEntry[size];
_freeList = -1;
}
public BufferDictionary(IReadOnlyDictionary<ReadOnlyBuffer<byte>, TValue> values)
{
int size = HashHelpers.GetPrime(values.Count);
_buckets = new int[size];
for (int i = 0; i < _buckets.Length; i++) _buckets[i] = -1;
_entries = new PropertyEntry[size];
_freeList = -1;

foreach (var value in values)
Add(value.Key, value.Value);
}

public void Add(ReadOnlyBuffer<byte> key, TValue value)
{
int hashCode = GetKeyHashCode(key) & 0x7FFFFFFF;
int targetBucket = hashCode % _buckets.Length;

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 key", 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 TryGetValue(ReadOnlyBuffer<byte> key, out TValue value)
{
int i = FindEntry(key);
if (i >= 0)
{
value = _entries[i].value;
return true;
}
value = default;
return false;
}
public bool TryGetValue(ReadOnlySpan<byte> key, out TValue 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)
{
int newSize = 2 * oldSize;
if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
return MaxPrimeArrayLength;
return GetPrime(newSize);
}

public const int MaxPrimeArrayLength = 0x7FEFFFFD;
}
}

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

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Utf8;

namespace Discord.Serialization
{
internal static class EnumMap
{
public static EnumMap<T> For<T>() where T : struct => EnumMap<T>.Instance;
}

public class EnumMap<T>
where T : struct
{
public static readonly EnumMap<T> Instance = new EnumMap<T>();

private readonly BufferDictionary<T> _keyToValue;
private readonly Dictionary<T, string> _valueToKey;
private readonly Dictionary<T, ReadOnlyBuffer<byte>> _valueToUtf8Key;

public EnumMap()
{
var typeInfo = typeof(T).GetTypeInfo();
if (!typeInfo.IsEnum)
throw new InvalidOperationException($"{typeInfo.Name} is not an Enum");

_keyToValue = new BufferDictionary<T>();
_valueToKey = new Dictionary<T, string>();
_valueToUtf8Key = new Dictionary<T, ReadOnlyBuffer<byte>>();

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

public T GetValue(ReadOnlyBuffer<byte> key)
{
if (_keyToValue.TryGetValue(key, out var value))
return value;
throw new SerializationException($"Unknown enum key: {new Utf8String(key.Span).ToString()}");
}
public T GetValue(ReadOnlySpan<byte> key)
{
if (_keyToValue.TryGetValue(key, out var value))
return value;
throw new SerializationException($"Unknown enum key: {new Utf8String(key).ToString()}");
}
public string GetKey(T value)
{
if (_valueToKey.TryGetValue(value, out var key))
return key;
throw new SerializationException($"Unknown enum value: {value}");
}
public ReadOnlyBuffer<byte> GetUtf8Key(T value)
{
if (_valueToUtf8Key.TryGetValue(value, out var key))
return key;
throw new SerializationException($"Unknown enum value: {value}");
}
}
}

+ 24
- 0
src/Discord.Net.Serialization/EnumValueAttribute.cs View File

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

namespace Discord.Serialization
{
public enum EnumValueType
{
ReadWrite,
ReadOnly,
WriteOnly
}

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

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

+ 27
- 0
src/Discord.Net.Serialization/Json/Converters/Enum.cs View File

@@ -0,0 +1,27 @@
using System.Text.Json;

namespace Discord.Serialization.Json.Converters
{
internal class EnumPropertyConverter<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)
{
if (isTopLevel)
reader.Read();
if (reader.ValueType != JsonValueType.String)
throw new SerializationException("Bad input, expected String");
return _map.GetValue(reader.Value);
}
public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel)
{
string key = _map.GetKey(value);
if (isTopLevel)
writer.WriteAttribute(map.Key, key);
else
writer.WriteValue(key);
}
}
}

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

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

namespace Discord.Serialization.Json.Converters
{
internal class EnumPropertyConverter<T> : IJsonPropertyConverter<T>
{
public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel)
=> throw new System.NotImplementedException();
public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel)
=> throw new System.NotImplementedException();
}
}

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

@@ -38,7 +38,7 @@ namespace Discord.Serialization.Json
AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>));
AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>));

//AddGenericConverter(typeof(Converters.EnumPropertyConverter<>), (type, prop) => type.IsEnum); //TODO: Impl Enums
AddGenericConverter(typeof(Converters.EnumPropertyConverter<>), (type, prop) => type.IsEnum);
AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), (type, prop) => type.IsClass);
}



+ 4
- 197
src/Discord.Net.Serialization/ModelMap.cs View File

@@ -7,211 +7,18 @@ namespace Discord.Serialization
public class ModelMap<TModel>
where TModel : class, new()
{
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;

private BufferDictionary<PropertyMap> _dictionary;
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;

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;
_dictionary = new BufferDictionary<PropertyMap>(properties.ToDictionary(x => x.Utf8Key));
}

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;
}
=> _dictionary.TryGetValue(key, out value);
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)
{
int newSize = 2 * oldSize;
if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
return MaxPrimeArrayLength;
return GetPrime(newSize);
}
public const int MaxPrimeArrayLength = 0x7FEFFFFD;
=> _dictionary.TryGetValue(key, out value);
}
}

+ 1
- 0
src/Discord.Net.Serialization/ModelPropertyAttribute.cs View File

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

namespace Discord.Serialization
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ModelPropertyAttribute : Attribute
{
public string Key { get; }


Loading…
Cancel
Save