#nullable enable
|
|
using IPA.Config.Data;
|
|
using IPA.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Boolean = IPA.Config.Data.Boolean;
|
|
#if NET3
|
|
using Net3_Proxy;
|
|
using Array = Net3_Proxy.Array;
|
|
#endif
|
|
|
|
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, LocalAllocator GetLocal, Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
|
|
{
|
|
EmitLoad(il, member, thisarg);
|
|
|
|
using var valueTypeLocal =
|
|
member.IsNullable
|
|
? GetLocal.Allocate(member.Type)
|
|
: default;
|
|
|
|
if (member.IsNullable)
|
|
{
|
|
il.Emit(OpCodes.Stloc, valueTypeLocal.Local);
|
|
il.Emit(OpCodes.Ldloca, valueTypeLocal.Local);
|
|
}
|
|
|
|
var endSerialize = il.DefineLabel();
|
|
|
|
if (member.AllowNull)
|
|
{
|
|
var passedNull = il.DefineLabel();
|
|
|
|
il.Emit(OpCodes.Dup);
|
|
if (member.IsNullable)
|
|
il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod());
|
|
il.Emit(OpCodes.Brtrue, passedNull);
|
|
|
|
il.Emit(OpCodes.Pop);
|
|
il.Emit(OpCodes.Ldnull);
|
|
il.Emit(OpCodes.Br, endSerialize);
|
|
|
|
il.MarkLabel(passedNull);
|
|
}
|
|
|
|
if (member is { IsNullable: true, HasConverter: false })
|
|
il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod());
|
|
|
|
var memberConversionType = member.ConversionType;
|
|
var targetType = GetExpectedValueTypeForType(memberConversionType);
|
|
if (member.HasConverter)
|
|
{
|
|
using var stlocal = GetLocal.Allocate(member.IsNullable ? member.Type : memberConversionType);
|
|
using var valLocal = GetLocal.Allocate(typeof(Value));
|
|
|
|
if (member.IsNullable)
|
|
il.Emit(OpCodes.Ldloc_S, valueTypeLocal.Local);
|
|
|
|
il.Emit(OpCodes.Stloc, stlocal);
|
|
il.BeginExceptionBlock();
|
|
il.Emit(OpCodes.Ldsfld, member.ConverterField);
|
|
il.Emit(OpCodes.Ldloc, stlocal);
|
|
|
|
if (member.IsGenericConverter)
|
|
{
|
|
var toValueBase = member.ConverterBase.GetMethod(nameof(ValueConverter<int>.ToValue),
|
|
new[] { member.ConverterTarget, typeof(object) });
|
|
var toValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
.FirstOrDefault(m => m.GetBaseDefinition() == toValueBase) ?? toValueBase;
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Call, toValue);
|
|
}
|
|
else
|
|
{
|
|
var toValueBase = typeof(IValueConverter).GetMethod(nameof(IValueConverter.ToValue),
|
|
new[] { typeof(object), typeof(object) });
|
|
var toValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
.FirstOrDefault(m => m.GetBaseDefinition() == toValueBase) ?? toValueBase;
|
|
il.Emit(OpCodes.Box);
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Call, toValue);
|
|
}
|
|
|
|
il.Emit(OpCodes.Stloc, valLocal);
|
|
il.BeginCatchBlock(typeof(Exception));
|
|
EmitWarnException(il, "Error serializing member using converter");
|
|
il.Emit(OpCodes.Ldnull);
|
|
il.Emit(OpCodes.Stloc, valLocal);
|
|
il.EndExceptionBlock();
|
|
il.Emit(OpCodes.Ldloc, valLocal);
|
|
}
|
|
else if (targetType == typeof(Text))
|
|
{ // only happens when arg is a string or char
|
|
var TextCreate = typeof(Value).GetMethod(nameof(Value.Text));
|
|
if (member.Type == typeof(char))
|
|
{
|
|
var strFromChar = typeof(char).GetMethod(nameof(char.ToString), new[] { typeof(char) });
|
|
il.Emit(OpCodes.Call, strFromChar);
|
|
}
|
|
il.Emit(OpCodes.Call, TextCreate);
|
|
}
|
|
else if (targetType == typeof(Boolean))
|
|
{
|
|
var BoolCreate = typeof(Value).GetMethod(nameof(Value.Bool));
|
|
il.Emit(OpCodes.Call, BoolCreate);
|
|
}
|
|
else if (targetType == typeof(Integer))
|
|
{
|
|
var IntCreate = typeof(Value).GetMethod(nameof(Value.Integer));
|
|
EmitNumberConvertTo(il, IntCreate.GetParameters()[0].ParameterType, member.Type);
|
|
il.Emit(OpCodes.Call, IntCreate);
|
|
}
|
|
else if (targetType == typeof(FloatingPoint))
|
|
{
|
|
var FloatCreate = typeof(Value).GetMethod(nameof(Value.Float));
|
|
EmitNumberConvertTo(il, FloatCreate.GetParameters()[0].ParameterType, member.Type);
|
|
il.Emit(OpCodes.Call, FloatCreate);
|
|
}
|
|
else if (targetType == typeof(List))
|
|
{
|
|
// TODO: impl this (enumerables)
|
|
Logger.Config.Warn($"Implicit conversions to {targetType} are not currently implemented");
|
|
il.Emit(OpCodes.Pop);
|
|
il.Emit(OpCodes.Ldnull);
|
|
}
|
|
else if (targetType == typeof(Map))
|
|
{
|
|
// TODO: support other aggregate types
|
|
if (!memberConversionType.IsValueType)
|
|
{
|
|
// if it is a reference type, we assume that its a generated type implementing IGeneratedStore
|
|
var IGeneratedStore_Serialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Serialize));
|
|
var IGeneratedStoreT_CopyFrom = typeof(IGeneratedStore<>).MakeGenericType(member.Type)
|
|
.GetMethod(nameof(IGeneratedStore<object>.CopyFrom));
|
|
|
|
if (!member.IsVirtual)
|
|
{
|
|
var noCreate = il.DefineLabel();
|
|
using var stlocal = GetLocal.Allocate(member.Type);
|
|
|
|
// first check to make sure that this is an IGeneratedStore, because we don't control assignments to it
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
|
|
il.Emit(OpCodes.Brtrue_S, noCreate);
|
|
il.Emit(OpCodes.Stloc, stlocal);
|
|
EmitCreateChildGenerated(il, member.Type, parentobj);
|
|
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), thisarg);
|
|
il.MarkLabel(noCreate);
|
|
}
|
|
il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize);
|
|
}
|
|
else
|
|
{ // generate serialization for value types
|
|
using var valueLocal = GetLocal.Allocate(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);
|
|
}
|
|
|
|
EmitSerializeStructure(il, structure, GetLocal, il => il.Emit(OpCodes.Ldloca, valueLocal), parentobj);
|
|
}
|
|
}
|
|
|
|
il.MarkLabel(endSerialize);
|
|
}
|
|
|
|
private static void EmitSerializeStructure(ILGenerator il, IEnumerable<SerializedMemberInfo> structure, LocalAllocator GetLocal, Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
|
|
{
|
|
var MapCreate = typeof(Value).GetMethod(nameof(Value.Map));
|
|
var MapAdd = typeof(Map).GetMethod(nameof(Map.Add));
|
|
|
|
using var mapLocal = GetLocal.Allocate(typeof(Map));
|
|
using var valueLocal = GetLocal.Allocate(typeof(Value));
|
|
|
|
il.Emit(OpCodes.Call, MapCreate);
|
|
il.Emit(OpCodes.Stloc, mapLocal);
|
|
|
|
foreach (var mem in structure)
|
|
{
|
|
EmitSerializeMember(il, mem, GetLocal, thisarg, parentobj);
|
|
il.Emit(OpCodes.Stloc, valueLocal);
|
|
il.Emit(OpCodes.Ldloc, mapLocal);
|
|
il.Emit(OpCodes.Ldstr, mem.Name);
|
|
il.Emit(OpCodes.Ldloc, valueLocal);
|
|
il.Emit(OpCodes.Call, MapAdd);
|
|
}
|
|
|
|
il.Emit(OpCodes.Ldloc, mapLocal);
|
|
}
|
|
}
|
|
}
|