Skip to content

Commit

Permalink
June 2024: Performance and API updates (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
kamranayub authored Jun 13, 2024
1 parent ce8f8e3 commit a3d42b8
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 25 deletions.
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

0 comments on commit a3d42b8

Please sign in to comment.