#nullable enable
|
|
using IPA.Config.Data;
|
|
using IPA.Config.Stores.Attributes;
|
|
using IPA.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
#if NET3
|
|
using Net3_Proxy;
|
|
using Array = Net3_Proxy.Array;
|
|
#endif
|
|
|
|
namespace IPA.Config.Stores
|
|
{
|
|
internal static partial class GeneratedStoreImpl
|
|
{
|
|
private static bool NeedsCorrection(SerializedMemberInfo member)
|
|
{
|
|
if (member.HasConverter) return false;
|
|
var memberType = member.ConversionType;
|
|
var expectType = GetExpectedValueTypeForType(memberType);
|
|
|
|
if (expectType == typeof(Map)) // TODO: make this slightly saner
|
|
{
|
|
if (expectType.IsValueType)
|
|
{ // custom value type
|
|
return ReadObjectMembers(memberType).Any(NeedsCorrection);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// expects start value on stack, exits with final value on stack
|
|
private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, LocalAllocator GetLocal,
|
|
Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
|
|
{
|
|
if (!NeedsCorrection(member)) return;
|
|
|
|
var endLabel = il.DefineLabel();
|
|
|
|
// TODO: when we have a nullable, we need to save to a local to call methods
|
|
if (member.IsNullable)
|
|
{
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod());
|
|
il.Emit(OpCodes.Brfalse, endLabel);
|
|
il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod());
|
|
}
|
|
|
|
var convType = member.ConversionType;
|
|
if (!convType.IsValueType)
|
|
{
|
|
// currently the only thing for this is where expect == Map, so do generate shit
|
|
var copyFrom = typeof(IGeneratedStore<>).MakeGenericType(convType).GetMethod(nameof(IGeneratedStore<Config>.CopyFrom));
|
|
var noCreate = il.DefineLabel();
|
|
using var valLocal = GetLocal.Allocate(convType);
|
|
|
|
if (member.AllowNull)
|
|
{
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Brfalse_S, endLabel); // thing is null, just bypass it all
|
|
}
|
|
if (!alwaysNew)
|
|
{
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
|
|
il.Emit(OpCodes.Brtrue_S, endLabel); // our input is already something we like
|
|
}
|
|
il.Emit(OpCodes.Stloc, valLocal);
|
|
if (!alwaysNew)
|
|
{
|
|
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, convType, parentobj);
|
|
il.MarkLabel(noCreate);
|
|
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Ldloc, valLocal);
|
|
il.Emit(shouldLock ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Callvirt, copyFrom);
|
|
}
|
|
else
|
|
{
|
|
// for special value types, we'll go ahead and correct each of their members
|
|
var structure = ReadObjectMembers(convType);
|
|
|
|
using var valueLocal = GetLocal.Allocate(convType);
|
|
il.Emit(OpCodes.Stloc, valueLocal);
|
|
|
|
void LdlocaValueLocal(ILGenerator il)
|
|
=> il.Emit(OpCodes.Ldloca, valueLocal);
|
|
|
|
foreach (var mem in structure)
|
|
{
|
|
if (NeedsCorrection(mem))
|
|
EmitLoadCorrectStore(il, mem, shouldLock, alwaysNew, GetLocal,
|
|
LdlocaValueLocal, LdlocaValueLocal, parentobj);
|
|
}
|
|
|
|
il.Emit(OpCodes.Ldloc, valueLocal);
|
|
}
|
|
|
|
if (member.IsNullable)
|
|
il.Emit(OpCodes.Newobj, member.Nullable_Construct);
|
|
|
|
il.MarkLabel(endLabel);
|
|
}
|
|
|
|
private static void EmitLoadCorrectStore(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, LocalAllocator GetLocal,
|
|
Action<ILGenerator> loadFrom, Action<ILGenerator> storeTo, Action<ILGenerator> parentobj)
|
|
{
|
|
EmitStore(il, member, il =>
|
|
{
|
|
EmitLoad(il, member, loadFrom); // load the member
|
|
EmitCorrectMember(il, member, shouldLock, alwaysNew, GetLocal, storeTo, parentobj); // correct it
|
|
}, storeTo);
|
|
}
|
|
}
|
|
}
|