Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

June 2024: Performance and API updates #36

Merged
merged 7 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions IGDB/IGDBApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Threading.Tasks;
using IGDB.Models;
using IGDB.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestEase;
Expand Down
5 changes: 5 additions & 0 deletions IGDB/Identity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,10 @@ public IdentitiesOrValues(object[] values)
var list = values.Select(value => (T)value).ToArray();
Values = list;
}

public IdentitiesOrValues(T[] values)
{
Values = values;
}
}
}
2 changes: 1 addition & 1 deletion IGDB/Models/ExternalGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ExternalGame : ITimestamps, IIdentifier, IHasChecksum
public IdentityOrValue<Game> Game { get; set; }
public long? Id { get; set; }

public ExternalGameMedia Media { get; set; }
public ExternalGameMedia? Media { get; set; }

public string Name { get; set; }

Expand Down
107 changes: 84 additions & 23 deletions IGDB/Serialization/IdentityConverter.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static IGDB.Serialization.LambdaActivator;

namespace IGDB
namespace IGDB.Serialization
{
public class IdentityConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return IsAssignableToGenericType(objectType, typeof(IdentityOrValue<>)) ||
IsAssignableToGenericType(objectType, typeof(IdentitiesOrValues<>));
return IsIdentityOrValue(objectType) || IsIdentitiesOrValues(objectType);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
Expand All @@ -24,7 +24,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
var expandedType = objectType.GetGenericArguments()[0];
var value = reader.Value;

if (IsAssignableToGenericType(objectType, typeof(IdentitiesOrValues<>)))
if (IsIdentitiesOrValues(objectType))
{
if (reader.TokenType != JsonToken.StartArray)
{
Expand All @@ -48,28 +48,33 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
}
}

var valuesActivator = GetValuesActivator(objectType);
var identitiesActivator = GetIdentitiesActivator(objectType);

// If any are objects, it means the IDs should be ignored
if (values.All(v => v.GetType().IsAssignableFrom(typeof(long))))
{
return Activator.CreateInstance(objectType, values.Cast<long>().ToArray());
return identitiesActivator(values.Cast<long>().ToArray());
}

var objects = values.Where(v => !v.GetType().IsAssignableFrom(typeof(long)));
var convertedValues = objects.ToArray();
var ctor = objectType.GetConstructor(new[] { typeof(object[]) });
return ctor.Invoke(new[] { convertedValues });
return valuesActivator(new[] { convertedValues });
}
else if (IsAssignableToGenericType(objectType, typeof(IdentityOrValue<>)))
else if (IsIdentityOrValue(objectType))
{
var identityActivator = GetIdentityActivator(objectType);
var valueActivator = GetValueActivator(objectType);

if (reader.TokenType == JsonToken.StartObject)
{
// objects
return Activator.CreateInstance(objectType, serializer.Deserialize(reader, expandedType));
return valueActivator(serializer.Deserialize(reader, expandedType));
}
else if (reader.TokenType == JsonToken.Integer)
{
// int ids
return Activator.CreateInstance(objectType, (long)reader.Value);
return identityActivator((long)reader.Value);
}
}

Expand All @@ -82,11 +87,11 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
{
dynamic identity = value;

if (IsAssignableToGenericType(value.GetType(), typeof(IdentitiesOrValues<>)))
if (IsIdentitiesOrValues(value.GetType()))
{
serializer.Serialize(writer, identity.Ids ?? identity.Values ?? null);
}
else if (IsAssignableToGenericType(value.GetType(), typeof(IdentityOrValue<>)))
else if (IsIdentityOrValue(value.GetType()))
{
serializer.Serialize(writer, identity.Id ?? identity.Value ?? null);
}
Expand All @@ -97,25 +102,81 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
}
}

public static bool IsAssignableToGenericType(Type givenType, Type genericType)
private static readonly string IdentitiesOrValuesName = typeof(IdentitiesOrValues<>).Name;
private static readonly string IdentityOrValueName = typeof(IdentityOrValue<>).Name;

public static bool IsIdentityOrValue(Type givenType)
{
return givenType.Name.Contains(IdentityOrValueName);
}

public static bool IsIdentitiesOrValues(Type givenType)
{
return givenType.Name.Contains(IdentitiesOrValuesName);
}

private static readonly IDictionary<Type, ObjectActivator> identitiesActivators
= new Dictionary<Type, ObjectActivator>();
private static readonly IDictionary<Type, ObjectActivator> valuesActivators
= new Dictionary<Type, ObjectActivator>();
private static readonly IDictionary<Type, ObjectActivator> identityActivators
= new Dictionary<Type, ObjectActivator>();
private static readonly IDictionary<Type, ObjectActivator> valueActivators
= new Dictionary<Type, ObjectActivator>();

public static ObjectActivator GetIdentitiesActivator(Type objectType)
{
if (identitiesActivators.ContainsKey(objectType))
{
return identitiesActivators[objectType];
}

ConstructorInfo ctor = objectType.GetConstructors().Skip(1).First();
var activator = GetActivator(ctor);
identitiesActivators[objectType] = activator;
return activator;
}

public static ObjectActivator GetValuesActivator(Type objectType)
{
var interfaceTypes = givenType.GetInterfaces();
if (valuesActivators.ContainsKey(objectType))
{
return valuesActivators[objectType];
}

ConstructorInfo ctor = objectType.GetConstructors().Skip(2).First();
var activator = GetActivator(ctor);
valuesActivators[objectType] = activator;
return activator;
}

foreach (var it in interfaceTypes)
public static ObjectActivator GetIdentityActivator(Type objectType)
{
if (identityActivators.ContainsKey(objectType))
{
if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
return true;
return identityActivators[objectType];
}

if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
return true;
ConstructorInfo ctor = objectType.GetConstructors().Skip(1).First();
var activator = GetActivator(ctor);
identityActivators[objectType] = activator;
return activator;
}

Type baseType = givenType.BaseType;
if (baseType == null) return false;
public static ObjectActivator GetValueActivator(Type objectType)
{
if (valueActivators.ContainsKey(objectType))
{
return valueActivators[objectType];
}

return IsAssignableToGenericType(baseType, genericType);
ConstructorInfo ctor = objectType.GetConstructors().Skip(2).First();
var activator = GetActivator(ctor);
valueActivators[objectType] = activator;
return activator;
}
}



}
47 changes: 47 additions & 0 deletions IGDB/Serialization/LamdaActivator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Linq.Expressions;
using System.Reflection;

namespace IGDB.Serialization
{
public delegate object ObjectActivator(params object[] args);

/// <summary>
/// See: https://rogerjohansson.blog/2008/02/28/linq-expressions-creating-objects/
/// </summary>
internal static class LambdaActivator
{
public static ObjectActivator GetActivator(ConstructorInfo ctor)
{
ParameterInfo[] paramsInfo = ctor.GetParameters();

ParameterExpression param =
Expression.Parameter(typeof(object[]), "args");

Expression[] argsExp =
new Expression[paramsInfo.Length];

for (int i = 0; i < paramsInfo.Length; i++)
{
Expression index = Expression.Constant(i);
Type paramType = paramsInfo[i].ParameterType;

Expression paramAccessorExp =
Expression.ArrayIndex(param, index);

Expression paramCastExp =
Expression.Convert(paramAccessorExp, paramType);

argsExp[i] = paramCastExp;
}

NewExpression newExp = Expression.New(ctor, argsExp);

LambdaExpression lambda =
Expression.Lambda(typeof(ObjectActivator), newExp, param);

ObjectActivator compiled = (ObjectActivator)lambda.Compile();
return compiled;
}
}
}
2 changes: 1 addition & 1 deletion IGDB/Serialization/UnixTimestampConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace IGDB
namespace IGDB.Serialization
{
public class UnixTimestampConverter : JsonConverter
{
Expand Down
Loading