Browse Source

Added most of implicit value type serialization

pull/46/head
Anairkoen Schno 4 years ago
parent
commit
23ace928b1
6 changed files with 152 additions and 56 deletions
  1. +3
    -3
      IPA.Loader/Config/Providers/JsonConfigProvider.cs
  2. +84
    -17
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs
  3. +17
    -12
      IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs
  4. +12
    -13
      IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
  5. +30
    -4
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs
  6. +6
    -7
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs

+ 3
- 3
IPA.Loader/Config/Providers/JsonConfigProvider.cs View File

@ -24,10 +24,10 @@ namespace IPA.Config.Providers
public Value Load(FileInfo file)
{
if (!file.Exists) return Value.Null();
if (!file.Exists) return Value.Null();
try
{
{
JToken jtok;
using (var sreader = new StreamReader(file.OpenRead()))
{


+ 84
- 17
IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
using IPA.Logging;
using System;
using System.Collections;
using System.Collections.Generic;
@ -40,7 +41,8 @@ namespace IPA.Config.Stores
return typeof(Map); // default for various objects
}
private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, GetLocal GetLocal)
private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, GetLocal GetLocal,
Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
{
var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize));
@ -48,12 +50,12 @@ namespace IPA.Config.Stores
var noCreate = il.DefineLabel();
il.Emit(OpCodes.Stloc, valuel);
EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_0));
EmitLoad(il, member, thisarg);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
il.Emit(OpCodes.Brtrue_S, noCreate);
il.Emit(OpCodes.Pop);
EmitCreateChildGenerated(il, member.Type);
EmitCreateChildGenerated(il, member.Type, parentobj);
il.MarkLabel(noCreate);
il.Emit(OpCodes.Dup);
@ -61,15 +63,19 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize);
}
private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal)
private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal,
Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
{
EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal);
thisarg ??= il => il.Emit(OpCodes.Ldarg_0);
parentobj ??= thisarg;
EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal, thisarg, parentobj);
il.Emit(OpCodes.Newobj, member.Nullable_Construct);
}
// top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType
// after, top of stack will be thing to write to field
private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, GetLocal GetLocal)
private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, GetLocal GetLocal,
Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
{
if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing
@ -103,7 +109,66 @@ namespace IPA.Config.Stores
} // TODO: implement stuff for lists and maps of various types (probably call out somewhere else to figure out what to do)
else if (expected == typeof(Map))
{
EmitDeserializeGeneratedValue(il, member, expected, GetLocal);
if (!targetType.IsValueType)
{
EmitDeserializeGeneratedValue(il, member, expected, GetLocal, thisarg, parentobj);
}
else
{
var Map_TryGetValue = typeof(Map).GetMethod(nameof(Map.TryGetValue));
var mapLocal = GetLocal(typeof(Map));
var resultLocal = GetLocal(targetType, 1);
var valueLocal = GetLocal(typeof(Value));
var structure = ReadObjectMembers(targetType);
if (!structure.Any())
{
Logger.config.Warn($"Custom value type {targetType.FullName} (when compiling serialization of" +
$" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members");
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldloca, resultLocal);
il.Emit(OpCodes.Initobj, targetType);
}
else
{
il.Emit(OpCodes.Stloc, mapLocal);
EmitLoad(il, member, thisarg);
il.Emit(OpCodes.Stloc, resultLocal);
// TODO: pull this and the MakeCreator version out into another function
var nextLabel = il.DefineLabel();
// head of stack is Map instance
foreach (var mem in structure)
{
il.MarkLabel(nextLabel);
nextLabel = il.DefineLabel();
var endErrorLabel = il.DefineLabel();
il.Emit(OpCodes.Ldloc, mapLocal);
il.Emit(OpCodes.Ldstr, mem.Name);
il.Emit(OpCodes.Ldloca_S, valueLocal);
il.Emit(OpCodes.Call, Map_TryGetValue);
il.Emit(OpCodes.Brtrue_S, endErrorLabel);
EmitLogError(il, $"Missing key {mem.Name}", tailcall: false);
il.Emit(OpCodes.Br, nextLabel);
il.MarkLabel(endErrorLabel);
il.Emit(OpCodes.Ldloc_S, valueLocal);
EmitDeserializeMember(il, mem, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal, il => il.Emit(OpCodes.Ldloca, resultLocal), parentobj);
}
il.MarkLabel(nextLabel);
}
il.Emit(OpCodes.Ldloc, resultLocal);
/*il.Emit(OpCodes.Ldloca, resultLocal);
il.Emit(OpCodes.Ldobj, targetType);*/
}
}
else
{
@ -112,7 +177,8 @@ namespace IPA.Config.Stores
}
}
private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, GetLocal GetLocal)
private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, GetLocal GetLocal,
Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
{
var stlocal = GetLocal(typeof(Value));
var valLocal = GetLocal(member.Type);
@ -121,7 +187,7 @@ namespace IPA.Config.Stores
il.BeginExceptionBlock();
il.Emit(OpCodes.Ldsfld, member.ConverterField);
il.Emit(OpCodes.Ldloc, stlocal);
il.Emit(OpCodes.Ldarg_0);
parentobj(il);
if (member.IsGenericConverter)
{
@ -151,13 +217,14 @@ namespace IPA.Config.Stores
}
// emit takes the value being deserialized, logs on error, leaves nothing on stack
private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, GetLocal GetLocal)
private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, GetLocal GetLocal,
Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
{
var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
var implLabel = il.DefineLabel();
var passedTypeCheck = il.DefineLabel();
var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type);
var expectType = GetExpectedValueTypeForType(member.ConversionType);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Brtrue_S, implLabel); // null check
@ -175,13 +242,13 @@ namespace IPA.Config.Stores
var valTLocal = GetLocal(member.Type, 0);
il.Emit(OpCodes.Ldloca, valTLocal);
il.Emit(OpCodes.Initobj, member.Type);
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal));
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal), thisobj);
il.Emit(OpCodes.Br, nextLabel);
}
else
{
il.Emit(OpCodes.Pop);
EmitStore(il, member, il => il.Emit(OpCodes.Ldnull));
EmitStore(il, member, il => il.Emit(OpCodes.Ldnull), thisobj);
il.Emit(OpCodes.Br, nextLabel);
}
@ -244,11 +311,11 @@ namespace IPA.Config.Stores
il.MarkLabel(passedTypeCheck);
var local = GetLocal(member.Type, 0);
if (member.HasConverter) EmitDeserializeConverter(il, member, nextLabel, GetLocal);
else if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal);
else EmitDeserializeValue(il, member, member.Type, expectType, GetLocal);
if (member.HasConverter) EmitDeserializeConverter(il, member, nextLabel, GetLocal, thisobj, parentobj);
else if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal, thisobj, parentobj);
else EmitDeserializeValue(il, member, member.Type, expectType, GetLocal, thisobj, parentobj);
il.Emit(OpCodes.Stloc, local);
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local));
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local), thisobj);
}
}
}

+ 17
- 12
IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs View File

@ -1,5 +1,6 @@
using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -17,6 +18,7 @@ namespace IPA.Config.Stores
{
internal delegate IConfigStore GeneratedStoreCreator(IGeneratedStore parent);
private static void GetMethodThis(ILGenerator il) => il.Emit(OpCodes.Ldarg_0);
private static (GeneratedStoreCreator ctor, Type type) MakeCreator(Type type)
{ // note that this does not and should not use converters by default for everything
@ -44,7 +46,9 @@ namespace IPA.Config.Stores
var isINotifyPropertyChanged = type.FindInterfaces((i, t) => i == (Type)t, typeof(INotifyPropertyChanged)).Length != 0;
var hasNotifyAttribute = type.GetCustomAttribute<NotifyPropertyChangesAttribute>() != null;
var structure = ReadObjectMembers(type, throwOnPrivateProperty: false, throwOnPrivateField: false);
var structure = ReadObjectMembers(type);
if (!structure.Any())
Logger.config.Warn($"Custom type {type.FullName} has no accessible members");
#endregion
var typeBuilder = Module.DefineType($"{type.FullName}<Generated>",
@ -118,9 +122,9 @@ namespace IPA.Config.Stores
{
EmitStore(il, member, il =>
{
EmitLoad(il, member); // load the member
EmitCorrectMember(il, member, false, true, GetLocal); // correct it
});
EmitLoad(il, member, GetMethodThis); // load the member
EmitCorrectMember(il, member, false, true, GetLocal, GetMethodThis, GetMethodThis); // correct it
}, GetMethodThis);
}
il.Emit(OpCodes.Pop);
@ -437,7 +441,7 @@ namespace IPA.Config.Stores
il.MarkLabel(endErrorLabel);
il.Emit(OpCodes.Ldloc_S, valueLocal);
EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal);
EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal, GetMethodThis, GetMethodThis);
}
il.MarkLabel(nextLabel);
@ -610,8 +614,8 @@ namespace IPA.Config.Stores
EmitStore(il, member, il =>
{
EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_1));
EmitCorrectMember(il, member, false, false, GetLocal);
});
EmitCorrectMember(il, member, false, false, GetLocal, GetMethodThis, GetMethodThis);
}, GetMethodThis);
il.BeginCatchBlock(typeof(Exception));
@ -713,7 +717,7 @@ namespace IPA.Config.Stores
propBuilder.SetSetMethod(propSet);
typeBuilder.DefineMethodOverride(propSet, set);
{ // TODO: decide if i want to correct the value before or after i take the write lock
{
var il = propSet.GetILGenerator();
var transactionLocal = il.DeclareLocal(IDisposable_t);
@ -727,7 +731,7 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
EmitCorrectMember(il, member, false, false, GetLocal);
EmitCorrectMember(il, member, false, false, GetLocal, GetMethodThis, GetMethodThis);
il.Emit(OpCodes.Call, set);
il.BeginFinallyBlock();
@ -773,7 +777,8 @@ namespace IPA.Config.Stores
}
// expects start value on stack, exits with final value on stack
private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, GetLocal GetLocal)
private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, GetLocal GetLocal,
Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
{
if (!NeedsCorrection(member)) return;
// this will never be called for a custom value type
@ -804,13 +809,13 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Stloc, valLocal);
if (!alwaysNew)
{
EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_0));
EmitLoad(il, member, thisobj);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
il.Emit(OpCodes.Brtrue_S, noCreate);
il.Emit(OpCodes.Pop);
}
EmitCreateChildGenerated(il, member.Type);
EmitCreateChildGenerated(il, member.Type, parentobj);
il.MarkLabel(noCreate);
il.Emit(OpCodes.Dup);


+ 12
- 13
IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs View File

@ -119,7 +119,16 @@ namespace IPA.Config.Stores
return true;
}
private static IEnumerable<SerializedMemberInfo> ReadObjectMembers(Type type, bool throwOnPrivateField = false, bool throwOnPrivateProperty = false)
private static readonly Dictionary<Type, SerializedMemberInfo[]> objectStructureCache = new Dictionary<Type, SerializedMemberInfo[]>();
private static IEnumerable<SerializedMemberInfo> ReadObjectMembers(Type type)
{
if (!objectStructureCache.TryGetValue(type, out var structure))
objectStructureCache.Add(type, structure = ReadObjectMembersInternal(type).ToArray());
return structure;
}
private static IEnumerable<SerializedMemberInfo> ReadObjectMembersInternal(Type type)
{
var structure = new List<SerializedMemberInfo>();
@ -132,12 +141,7 @@ namespace IPA.Config.Stores
}
if (prop.GetGetMethod(true)?.IsPrivate ?? true)
{ // we enter this block if the getter is inacessible or doesn't exist
//continue; // ignore props without getter
// TODO: is this what I want to do for private properties?
if (throwOnPrivateProperty)
throw new InvalidOperationException($"Cannot compile serializer for type {type} with private property {prop.Name}");
else
continue;
continue; // ignore props without getter
}
var smi = new SerializedMemberInfo
@ -158,12 +162,7 @@ namespace IPA.Config.Stores
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (field.IsPrivate)
{
if (throwOnPrivateField && !field.Name.Contains(">k__BackingField")) // don't throw for private fields that are just backing fields
throw new InvalidOperationException($"Cannot compile serializer for type {type} with private field {field.Name}");
else
continue;
}
continue;
var smi = new SerializedMemberInfo
{


+ 30
- 4
IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
using IPA.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
@ -13,9 +14,9 @@ namespace IPA.Config.Stores
internal static partial class GeneratedStoreImpl
{
// emit takes no args, leaves Value at top of stack
private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal)
private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal, Action<ILGenerator> thisarg = null)
{
EmitLoad(il, member);
EmitLoad(il, member, thisarg);
var endSerialize = il.DefineLabel();
@ -131,21 +132,46 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
il.Emit(OpCodes.Brtrue_S, noCreate);
il.Emit(OpCodes.Stloc, stlocal);
EmitCreateChildGenerated(il, member.Type);
EmitCreateChildGenerated(il, member.Type, GetMethodThis);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldloc, stlocal);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Callvirt, IGeneratedStoreT_CopyFrom);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, stlocal);
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, stlocal));
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, stlocal), thisarg);
il.MarkLabel(noCreate);
}
il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize);
}
else
{ // generate serialization for value types
var MapCreate = typeof(Value).GetMethod(nameof(Value.Map));
var MapAdd = typeof(Map).GetMethod(nameof(Map.Add));
var valueLocal = GetLocal(memberConversionType);
var structure = ReadObjectMembers(memberConversionType);
if (!structure.Any())
{
Logger.config.Warn($"Custom value type {memberConversionType.FullName} (when compiling serialization of" +
$" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members");
il.Emit(OpCodes.Pop);
}
else
{
il.Emit(OpCodes.Stloc, valueLocal);
}
il.Emit(OpCodes.Call, MapCreate);
foreach (var mem in structure)
{
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldstr, mem.Name);
EmitSerializeMember(il, mem, GetLocal, il => il.Emit(OpCodes.Ldloca, valueLocal));
il.Emit(OpCodes.Call, MapAdd);
}
}
}


+ 6
- 7
IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs View File

@ -49,10 +49,9 @@ namespace IPA.Config.Stores
return GetLocal;
}
private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> thisarg = null)
private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> thisarg)
{
if (thisarg == null)
thisarg = il => il.Emit(OpCodes.Ldarg_0);
thisarg ??= il => il.Emit(OpCodes.Ldarg_0);
thisarg(il); // load this
@ -68,9 +67,9 @@ namespace IPA.Config.Stores
}
}
private static void EmitStore(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> value)
private static void EmitStore(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> value, Action<ILGenerator> thisobj)
{
il.Emit(OpCodes.Ldarg_0); // load this
thisobj(il);
value(il);
if (member.IsField)
@ -196,10 +195,10 @@ namespace IPA.Config.Stores
}
}
private static void EmitCreateChildGenerated(ILGenerator il, Type childType)
private static void EmitCreateChildGenerated(ILGenerator il, Type childType, Action<ILGenerator> parentobj)
{
var method = CreateGParent.MakeGenericMethod(childType);
il.Emit(OpCodes.Ldarg_0);
parentobj(il);
il.Emit(OpCodes.Call, method);
}
}


Loading…
Cancel
Save