Skip to content

Commit

Permalink
Caching the constructors for the types we discover on the fly
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronpowell committed Aug 21, 2024
1 parent 3c3edc6 commit ed2992c
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 24 deletions.
28 changes: 19 additions & 9 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,28 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private object? ConvertToDictionary(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture, bool useMappingProtocol = false)
{
using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject));
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type);
IDictionary dict = (IDictionary)Activator.CreateInstance(dictType)!;
using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject));

Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type);
Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type);

typeInfo = new(returnType.GetConstructor([dictType])!, dictType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IDictionary dict = (IDictionary)typeInfo.TransientTypeConstructor!.Invoke([]);
nint itemsLength = CPythonAPI.PyList_Size(items);

for (nint i = 0; i < itemsLength; i++)
Expand All @@ -28,10 +39,9 @@ internal partial class PyObjectTypeConverter
object? convertedItem2 = ConvertTo(context, culture, item2, item2Type);

dict.Add(convertedItem1!, convertedItem2);
}

Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type);
return Activator.CreateInstance(returnType, dict);
}

return typeInfo.ReturnTypeConstructor.Invoke([dict]);
}

private PyObject ConvertFromDictionary(ITypeDescriptorContext? context, CultureInfo? culture, IDictionary dictionary)
Expand Down
18 changes: 12 additions & 6 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ internal partial class PyObjectTypeConverter
{
private object? ConvertToGeneratorIterator(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type item3Type = destinationType.GetGenericArguments()[2];
Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type);
ConstructorInfo ctor = generatorType.GetConstructors().First();
return ctor.Invoke([pyObject.Clone()]);
if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type item3Type = destinationType.GetGenericArguments()[2];
Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type);
ConstructorInfo ctor = generatorType.GetConstructors().First();
typeInfo = new(ctor);
knownDynamicTypes[destinationType] = typeInfo;
}

return typeInfo.ReturnTypeConstructor.Invoke([pyObject.Clone()]);
}
}
25 changes: 19 additions & 6 deletions src/CSnakes.Runtime/PyObjectTypeConverter.List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ internal partial class PyObjectTypeConverter
{
private object? ConvertToList(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type genericArgument = destinationType.GetGenericArguments()[0];
Type listType = typeof(List<>).MakeGenericType(genericArgument);
Type genericArgument = destinationType.GetGenericArguments()[0];

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type listType = typeof(List<>).MakeGenericType(genericArgument);
typeInfo = new(listType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IList list = (IList)Activator.CreateInstance(listType)!;
IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]);
for (var i = 0; i < CPythonAPI.PyList_Size(pyObject); i++)
{
using PyObject item = new(CPythonAPI.PyList_GetItem(pyObject, i));
Expand All @@ -24,10 +30,17 @@ internal partial class PyObjectTypeConverter

private object? ConvertToListFromSequence(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type genericArgument = destinationType.GetGenericArguments()[0];
Type listType = typeof(List<>).MakeGenericType(genericArgument);
Type genericArgument = destinationType.GetGenericArguments()[0];

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type listType = typeof(List<>).MakeGenericType(genericArgument);
typeInfo = new(listType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]);

IList list = (IList)Activator.CreateInstance(listType)!;
for (var i = 0; i < CPythonAPI.PySequence_Size(pyObject); i++)
{
using PyObject item = new(CPythonAPI.PySequence_GetItem(pyObject, i));
Expand Down
10 changes: 8 additions & 2 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,15 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo?
foreach (var value in tupleValues)
{
value.Dispose();
}

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length);
typeInfo = new(ctor);
knownDynamicTypes[destinationType] = typeInfo;
}

ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length);
return (ITuple)ctor.Invoke(clrValues);
return (ITuple)typeInfo.ReturnTypeConstructor.Invoke(clrValues);
}
}
5 changes: 4 additions & 1 deletion src/CSnakes.Runtime/PyObjectTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
using System.Runtime.CompilerServices;
using System.Numerics;
using System.Collections.Concurrent;
using System.Reflection;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter : TypeConverter
{
private readonly ConcurrentDictionary<Type, Type> knownTypeCache = [];
private readonly ConcurrentDictionary<Type, DynamicTypeInfo> knownDynamicTypes = [];

/// <summary>
/// Convert a Python object to a CLR managed object.
Expand Down Expand Up @@ -183,4 +184,6 @@ private PyObject ToPython(object? o, ITypeDescriptorContext? context, CultureInf

return result is null ? throw new NotImplementedException() : (PyObject)result;
}

record DynamicTypeInfo(ConstructorInfo ReturnTypeConstructor, ConstructorInfo? TransientTypeConstructor = null);
}

0 comments on commit ed2992c

Please sign in to comment.