diff --git a/IPA.Loader/Config/Stores/CollectionConverter.cs b/IPA.Loader/Config/Stores/CollectionConverter.cs index b43b426a..55cfe034 100644 --- a/IPA.Loader/Config/Stores/CollectionConverter.cs +++ b/IPA.Loader/Config/Stores/CollectionConverter.cs @@ -4,7 +4,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using IPA.Config.Data; - +using IPA.Logging; + namespace IPA.Config.Stores.Converters { /// @@ -53,6 +54,7 @@ namespace IPA.Config.Stores.Converters /// protected void PopulateFromValue(TCollection col, List list, object parent) { + //Logger.log.Debug($"CollectionConverter<{typeof(T)}, {typeof(TCollection)}>({BaseConverter.GetType()}).PopulateFromValue([object], {list}, {parent.GetType()})"); foreach (var it in list) col.Add(BaseConverter.FromValue(it, parent)); } @@ -79,7 +81,7 @@ namespace IPA.Config.Stores.Converters /// the object owning /// the that was serialized into /// - public override Value ToValue(TCollection obj, object parent) + public override Value ToValue(TCollection obj, object parent) => Value.From(obj.Select(t => BaseConverter.ToValue(t, parent))); } /// diff --git a/IPA.Loader/Config/Stores/Converters.cs b/IPA.Loader/Config/Stores/Converters.cs index b60fc936..1a7367a4 100644 --- a/IPA.Loader/Config/Stores/Converters.cs +++ b/IPA.Loader/Config/Stores/Converters.cs @@ -1,5 +1,6 @@ using IPA.Config.Data; using IPA.Config.Stores.Attributes; +using IPA.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -44,7 +45,7 @@ namespace IPA.Config.Stores.Converters private static readonly IValConv Impl = ValConvImpls.Impl as IValConv ?? new ValConv(); public ValueConverter Get() => Impl.Get(); ValueConverter IValConv.Get() - => null; // default to null + => new CustomValueTypeConverter(); } private class ValConvImpls : IValConv, IValConv, IValConv, @@ -89,24 +90,29 @@ namespace IPA.Config.Stores.Converters private static ValueConverter MakeDefault() { - var t = typeof(T); + var t = typeof(T); + //Logger.log.Debug($"Converter<{t}>.MakeDefault()"); if (t.IsValueType) { // we have to do this garbo to make it accept the thing that we know is a value type at instantiation time if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) - { // this is a Nullable + { // this is a Nullable + //Logger.log.Debug($"gives NullableConverter<{Nullable.GetUnderlyingType(t)}>"); return Activator.CreateInstance(typeof(NullableConverter<>).MakeGenericType(Nullable.GetUnderlyingType(t))) as ValueConverter; - } - + } + + //Logger.log.Debug($"gives converter for value type {t}"); var valConv = Activator.CreateInstance(typeof(Converter.ValConv<>).MakeGenericType(t)) as Converter.IValConv; return valConv.Get(); } else if (t == typeof(string)) - { + { + //Logger.log.Debug($"gives StringConverter"); return new StringConverter() as ValueConverter; } else - { + { + //Logger.log.Debug($"gives CustomObjectConverter<{t}>"); return Activator.CreateInstance(typeof(CustomObjectConverter<>).MakeGenericType(t)) as ValueConverter; } } diff --git a/IPA.Loader/Config/Stores/CustomObjectConverter.cs b/IPA.Loader/Config/Stores/CustomObjectConverter.cs index f8e1a947..20465742 100644 --- a/IPA.Loader/Config/Stores/CustomObjectConverter.cs +++ b/IPA.Loader/Config/Stores/CustomObjectConverter.cs @@ -85,4 +85,18 @@ namespace IPA.Config.Stores.Converters => impl.ToValue(obj, parent); } + public class CustomValueTypeConverter : ValueConverter where T : struct + { + private static readonly GeneratedStoreImpl.SerializeObject serialize + = GeneratedStoreImpl.GetSerializerDelegate(); + private static readonly GeneratedStoreImpl.DeserializeObject deserialize + = GeneratedStoreImpl.GetDeserializerDelegate(); + + public override T FromValue(Value value, object parent) + => deserialize(value, parent); + + public override Value ToValue(T obj, object parent) + => serialize(obj); + } + } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs new file mode 100644 index 00000000..cd46498f --- /dev/null +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ConversionDelegates.cs @@ -0,0 +1,183 @@ +using IPA.Config.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.Config.Stores +{ + internal static partial class GeneratedStoreImpl + { + + internal delegate Value SerializeObject(T obj); + internal delegate T DeserializeObject(Value val, object parent); + + private static class DelegateStore + { + public static SerializeObject Serialize; + public static DeserializeObject Deserialize; + } + + internal static SerializeObject GetSerializerDelegate() + => DelegateStore.Serialize ??= GetSerializerDelegateInternal(); + + private static SerializeObject GetSerializerDelegateInternal() + { + var type = typeof(T); +#if DEBUG + var defType = Module.DefineType($"{type.FullName}", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract); + var dynMethod = defType.DefineMethod("SerializeType", MethodAttributes.Public | MethodAttributes.Static, typeof(Value), new[] { type }); +#else + var dynMethod = new DynamicMethod($"SerializeType>>{type.FullName}", typeof(Value), new[] { type }, Module, true); +#endif + + var structure = ReadObjectMembers(type); + + //CreateAndInitializeConvertersFor(type, structure); + + var loadObject = type.IsValueType + ? (Action)(il => il.Emit(OpCodes.Ldarga_S, 0)) + : il => il.Emit(OpCodes.Ldarg_0); + { + var il = dynMethod.GetILGenerator(); + + var GetLocal = MakeLocalAllocator(il); + + EmitLogError(il, $"Entered SerializeType delegate for type {type}"); + + if (!type.IsValueType) + { + var notIGeneratedStore = il.DefineLabel(); + var IGeneratedStore_t = typeof(IGeneratedStore); + var IGeneratedStore_Serialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Serialize)); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Isinst, IGeneratedStore_t); + il.Emit(OpCodes.Brfalse, notIGeneratedStore); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, IGeneratedStore_t); + il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize); + il.Emit(OpCodes.Ret); + + il.MarkLabel(notIGeneratedStore); + } + + EmitLogError(il, $"Serializing structure of {type}"); + EmitSerializeStructure(il, structure, GetLocal, loadObject); + + il.Emit(OpCodes.Ret); + } + +#if DEBUG + defType.CreateType(); + return (SerializeObject)Delegate.CreateDelegate(typeof(SerializeObject), dynMethod); +#else + return (SerializeObject)dynMethod.CreateDelegate(typeof(SerializeObject)); +#endif + } + + internal static DeserializeObject GetDeserializerDelegate() + => DelegateStore.Deserialize ??= GetDeserializerDelegateInternal(); + + private static DeserializeObject GetDeserializerDelegateInternal() + { + var type = typeof(T); + //var dynMethod = new DynamicMethod($"DeserializeType>>{type.FullName}", type, new[] { typeof(Value), typeof(object) }, Module, true); + +#if DEBUG + var defType = Module.DefineType($"{type.FullName}", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract); + var dynMethod = defType.DefineMethod("DeserializeType", MethodAttributes.Public | MethodAttributes.Static, type, new[] { typeof(Value), typeof(object) }); +#else + var dynMethod = new DynamicMethod($"DeserializeType>>{type.FullName}", type, new[] { typeof(Value), typeof(object) }, Module, true); +#endif + + var structure = ReadObjectMembers(type); + + //CreateAndInitializeConvertersFor(type, structure); + + { + var il = dynMethod.GetILGenerator(); + + var GetLocal = MakeLocalAllocator(il); + + var IGeneratedStore_t = typeof(IGeneratedStore); + var IGeneratedStore_Deserialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Deserialize)); + + void ParentObj(ILGenerator il) + { + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Isinst, IGeneratedStore_t); + } + + EmitLogError(il, $"Entered DeserializeType delegate for type {type}"); + + if (!type.IsValueType) + { + EmitLogError(il, $"Forwarding to created type serialization"); + EmitCreateChildGenerated(il, type, ParentObj); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Castclass, IGeneratedStore_t); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize); + il.Emit(OpCodes.Ret); + } + else + { + var Map_t = typeof(Map); + var Map_TryGetValue = Map_t.GetMethod(nameof(Map.TryGetValue)); + var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType)); + + var valueLocal = il.DeclareLocal(typeof(Value)); + var mapLocal = il.DeclareLocal(typeof(Map)); + var resultLocal = il.DeclareLocal(type); + + var nonNull = il.DefineLabel(); + + EmitLogError(il, $"Deserializing structure of {type}"); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Brtrue, nonNull); + + EmitLogError(il, "Attempting to deserialize null", tailcall: false); + il.Emit(OpCodes.Ldloc, resultLocal); + il.Emit(OpCodes.Ret); + + il.MarkLabel(nonNull); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Isinst, Map_t); + il.Emit(OpCodes.Dup); // duplicate cloned value + il.Emit(OpCodes.Stloc, mapLocal); + var notMapError = il.DefineLabel(); + il.Emit(OpCodes.Brtrue, notMapError); + // handle error + EmitLogError(il, $"Invalid root for deserializing {type.FullName}", tailcall: false, + expected: il => EmitTypeof(il, Map_t), found: il => + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Callvirt, Object_GetType); + }); + il.Emit(OpCodes.Ldloc, resultLocal); + il.Emit(OpCodes.Ret); + + il.MarkLabel(notMapError); + + EmitDeserializeStructure(il, structure, mapLocal, valueLocal, GetLocal, il => il.Emit(OpCodes.Ldloca, resultLocal), ParentObj); + + il.Emit(OpCodes.Ldloc, resultLocal); + il.Emit(OpCodes.Ret); + } + } + +#if DEBUG + defType.CreateType(); + return (DeserializeObject)Delegate.CreateDelegate(typeof(DeserializeObject), dynMethod); +#else + return (DeserializeObject)dynMethod.CreateDelegate(typeof(DeserializeObject)); +#endif + } + } +} diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs index 02ad0e0f..15d54c97 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs @@ -38,7 +38,7 @@ 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, LocalAllocator GetLocal, Action thisobj, Action parentobj) { if (!NeedsCorrection(member)) return; @@ -59,7 +59,7 @@ namespace IPA.Config.Stores // currently the only thing for this is where expect == Map, so do generate shit var copyFrom = typeof(IGeneratedStore<>).MakeGenericType(convType).GetMethod(nameof(IGeneratedStore.CopyFrom)); var noCreate = il.DefineLabel(); - var valLocal = GetLocal(convType); + using var valLocal = GetLocal.Allocate(convType); if (member.AllowNull) { @@ -94,7 +94,7 @@ namespace IPA.Config.Stores // for special value types, we'll go ahead and correct each of their members var structure = ReadObjectMembers(convType); - var valueLocal = GetLocal(convType); + using var valueLocal = GetLocal.Allocate(convType); il.Emit(OpCodes.Stloc, valueLocal); void LdlocaValueLocal(ILGenerator il) @@ -116,7 +116,7 @@ namespace IPA.Config.Stores il.MarkLabel(endLabel); } - private static void EmitLoadCorrectStore(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, GetLocal GetLocal, + private static void EmitLoadCorrectStore(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, LocalAllocator GetLocal, Action loadFrom, Action storeTo, Action parentobj) { EmitStore(il, member, il => diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs index 23856c70..7f0bdffe 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs @@ -18,12 +18,12 @@ namespace IPA.Config.Stores { internal static partial class GeneratedStoreImpl { - private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, GetLocal GetLocal, + private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, LocalAllocator GetLocal, Action thisarg, Action parentobj) { var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize)); - var valuel = GetLocal(srcType, 0); + using var valuel = GetLocal.Allocate(srcType); var noCreate = il.DefineLabel(); il.Emit(OpCodes.Stloc, valuel); @@ -40,7 +40,7 @@ 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, LocalAllocator GetLocal, Action thisarg, Action parentobj) { thisarg ??= il => il.Emit(OpCodes.Ldarg_0); @@ -51,7 +51,7 @@ namespace IPA.Config.Stores // 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, LocalAllocator GetLocal, Action thisarg, Action parentobj) { if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing @@ -93,9 +93,9 @@ namespace IPA.Config.Stores else { - var mapLocal = GetLocal(typeof(Map)); - var resultLocal = GetLocal(targetType, 1); - var valueLocal = GetLocal(typeof(Value)); + using var mapLocal = GetLocal.Allocate(typeof(Map)); + using var resultLocal = GetLocal.Allocate(targetType); + using var valueLocal = GetLocal.Allocate(typeof(Value)); var structure = ReadObjectMembers(targetType); if (!structure.Any()) @@ -129,7 +129,7 @@ namespace IPA.Config.Stores private static void EmitDeserializeStructure(ILGenerator il, IEnumerable structure, LocalBuilder mapLocal, LocalBuilder valueLocal, - GetLocal GetLocal, Action thisobj, Action parentobj) + LocalAllocator GetLocal, Action thisobj, Action parentobj) { var Map_TryGetValue = typeof(Map).GetMethod(nameof(Map.TryGetValue)); @@ -158,11 +158,11 @@ 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, LocalAllocator GetLocal, Action thisobj, Action parentobj) { - var stlocal = GetLocal(typeof(Value)); - var valLocal = GetLocal(member.Type); + using var stlocal = GetLocal.Allocate(typeof(Value)); + using var valLocal = GetLocal.Allocate(member.Type); il.Emit(OpCodes.Stloc, stlocal); il.BeginExceptionBlock(); @@ -198,7 +198,7 @@ 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 getValue, GetLocal GetLocal, + private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action getValue, LocalAllocator GetLocal, Action thisobj, Action parentobj) { var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType)); @@ -220,7 +220,7 @@ namespace IPA.Config.Stores else if (member.IsNullable) { il.Emit(OpCodes.Pop); - var valTLocal = GetLocal(member.Type, 0); + using var valTLocal = GetLocal.Allocate(member.Type); il.Emit(OpCodes.Ldloca, valTLocal); il.Emit(OpCodes.Initobj, member.Type); EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal), thisobj); @@ -291,7 +291,7 @@ namespace IPA.Config.Stores il.MarkLabel(passedTypeCheck); - var local = GetLocal(member.Type, 0); + using var local = GetLocal.Allocate(member.Type); 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); diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs index 165a5270..11add0b6 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs @@ -97,6 +97,47 @@ namespace IPA.Config.Stores return module; } + } + + private static readonly Dictionary> TypeRequiredConverters = new Dictionary>(); + private static void CreateAndInitializeConvertersFor(Type type, IEnumerable structure) + { + if (!TypeRequiredConverters.TryGetValue(type, out var converters)) + { + var converterFieldType = Module.DefineType($"{type.FullName}", + TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.AnsiClass); // a static class + + var uniqueConverterTypes = structure.Where(m => m.HasConverter).Select(m => m.Converter).Distinct().ToArray(); + converters = new Dictionary(uniqueConverterTypes.Length); + + foreach (var convType in uniqueConverterTypes) + { + var field = converterFieldType.DefineField($"_{convType}", convType, + FieldAttributes.FamORAssem | FieldAttributes.InitOnly | FieldAttributes.Static); + converters.Add(convType, field); + } + + var cctor = converterFieldType.DefineConstructor(MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes); + { + var il = cctor.GetILGenerator(); + + foreach (var kvp in converters) + { + var typeCtor = kvp.Key.GetConstructor(Type.EmptyTypes); + il.Emit(OpCodes.Newobj, typeCtor); + il.Emit(OpCodes.Stsfld, kvp.Value); + } + + il.Emit(OpCodes.Ret); + } + + TypeRequiredConverters.Add(type, converters); + + converterFieldType.CreateType(); + } + + foreach (var member in structure.Where(m => m.HasConverter)) + member.ConverterField = converters[member.Converter]; } } } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs index 483d239b..85713b1f 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs @@ -59,17 +59,17 @@ namespace IPA.Config.Stores internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged)); public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged(); - public void SignalChanged() + public void SignalChanged() { - try - { - resetEvent.Set(); - } - catch (ObjectDisposedException e) - { - Logger.config.Error($"ObjectDisposedException while signalling a change for generated store {generated?.GetType()}"); - Logger.config.Error(e); - } + try + { + resetEvent.Set(); + } + catch (ObjectDisposedException e) + { + Logger.config.Error($"ObjectDisposedException while signalling a change for generated store {generated?.GetType()}"); + Logger.config.Error(e); + } } internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged)); @@ -142,22 +142,22 @@ namespace IPA.Config.Stores public void Dispose() => Dispose(true); private void Dispose(bool addToStore) - { - if (data.owns) - { - data.impl.inChangeTransaction = false; - data.impl.InvokeChanged(); - } + { + if (data.owns) + { + data.impl.inChangeTransaction = false; + data.impl.InvokeChanged(); + } data.nested?.Dispose(); - try - { - if (data.ownsWrite) + try + { + if (data.ownsWrite) data.impl.ReleaseWrite(); } - catch - { - } - if (addToStore) + catch + { + } + if (addToStore) freeTransactionObjs.Push(this); } @@ -175,8 +175,8 @@ namespace IPA.Config.Stores public static void ImplReadFrom(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).ReadFrom(provider); public void ReadFrom(ConfigProvider provider) { + Logger.config.Debug($"Generated impl ReadFrom {generated.GetType()}"); var values = provider.Load(); - Logger.config.Debug("Generated impl ReadFrom"); Logger.config.Debug($"Read {values}"); generated.Deserialize(values); @@ -188,8 +188,8 @@ namespace IPA.Config.Stores public static void ImplWriteTo(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).WriteTo(provider); public void WriteTo(ConfigProvider provider) { + Logger.config.Debug($"Generated impl WriteTo {generated.GetType()}"); var values = generated.Serialize(); - Logger.config.Debug("Generated impl WriteTo"); Logger.config.Debug($"Serialized {values}"); provider.Store(values); } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs index 9ded5ca5..18ad87b3 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs @@ -62,7 +62,7 @@ namespace IPA.Config.Stores var implField = typeBuilder.DefineField("<>_impl", typeof(Impl), FieldAttributes.Private | FieldAttributes.InitOnly); var parentField = typeBuilder.DefineField("<>_parent", typeof(IGeneratedStore), FieldAttributes.Private | FieldAttributes.InitOnly); - #region Converter fields + /*#region Converter fields var uniqueConverterTypes = structure.Where(m => m.HasConverter).Select(m => m.Converter).Distinct().ToArray(); var converterFields = new Dictionary(uniqueConverterTypes.Length); @@ -91,7 +91,9 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Ret); } - #endregion + #endregion*/ + + //CreateAndInitializeConvertersFor(type, structure); #region Constructor var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IGeneratedStore) }); @@ -120,7 +122,7 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Stfld, implField); il.MarkLabel(noImplLabel); - var GetLocal = MakeGetLocal(il); + var GetLocal = MakeLocalAllocator(il); foreach (var member in structure) { @@ -357,26 +359,10 @@ namespace IPA.Config.Stores { // this is non-locking because the only code that will call this will already own the correct lock var il = serializeGen.GetILGenerator(); - var Map_Add = typeof(Map).GetMethod(nameof(Map.Add)); - var mapLocal = il.DeclareLocal(typeof(Map)); + var GetLocal = MakeLocalAllocator(il); - var GetLocal = MakeGetLocal(il); - var valLocal = GetLocal(typeof(Value)); - - il.Emit(OpCodes.Call, typeof(Value).GetMethod(nameof(Value.Map))); - il.Emit(OpCodes.Stloc, mapLocal); - - foreach (var member in structure) - { - EmitSerializeMember(il, member, GetLocal, GetMethodThis); - il.Emit(OpCodes.Stloc, valLocal); - il.Emit(OpCodes.Ldloc, mapLocal); - il.Emit(OpCodes.Ldstr, member.Name); - il.Emit(OpCodes.Ldloc, valLocal); - il.Emit(OpCodes.Call, Map_Add); - } + EmitSerializeStructure(il, structure, GetLocal, GetMethodThis); - il.Emit(OpCodes.Ldloc, mapLocal); il.Emit(OpCodes.Ret); } #endregion @@ -421,7 +407,7 @@ namespace IPA.Config.Stores il.MarkLabel(notMapError); - var GetLocal = MakeGetLocal(il); + var GetLocal = MakeLocalAllocator(il); // head of stack is Map instance EmitDeserializeStructure(il, structure, mapLocal, valueLocal, GetLocal, GetMethodThis, GetMethodThis); @@ -585,7 +571,7 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Stloc, transactionLocal); il.MarkLabel(startLock); - var GetLocal = MakeGetLocal(il); + var GetLocal = MakeLocalAllocator(il); foreach (var member in structure) { @@ -697,7 +683,7 @@ namespace IPA.Config.Stores var il = propSet.GetILGenerator(); var transactionLocal = il.DeclareLocal(IDisposable_t); - var GetLocal = MakeGetLocal(il); + var GetLocal = MakeLocalAllocator(il); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, coreChangeTransaction); // take the write lock diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs index 2db850e6..9467ee7c 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs @@ -181,6 +181,7 @@ namespace IPA.Config.Stores structure.Add(smi); } + CreateAndInitializeConvertersFor(type, structure); return structure; } } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs index e8ac69dd..262d39bf 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs @@ -18,7 +18,7 @@ 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, Action thisarg) + private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, LocalAllocator GetLocal, Action thisarg) { EmitLoad(il, member, thisarg); @@ -47,8 +47,8 @@ namespace IPA.Config.Stores var targetType = GetExpectedValueTypeForType(memberConversionType); if (member.HasConverter) { - var stlocal = GetLocal(member.Type); - var valLocal = GetLocal(typeof(Value)); + using var stlocal = GetLocal.Allocate(member.Type); + using var valLocal = GetLocal.Allocate(typeof(Value)); il.Emit(OpCodes.Stloc, stlocal); il.BeginExceptionBlock(); @@ -130,7 +130,7 @@ namespace IPA.Config.Stores if (!member.IsVirtual) { var noCreate = il.DefineLabel(); - var stlocal = GetLocal(member.Type); + 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); @@ -151,10 +151,7 @@ namespace IPA.Config.Stores } 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); + using var valueLocal = GetLocal.Allocate(memberConversionType); var structure = ReadObjectMembers(memberConversionType); if (!structure.Any()) @@ -168,19 +165,35 @@ namespace IPA.Config.Stores 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); - } + EmitSerializeStructure(il, structure, GetLocal, il => il.Emit(OpCodes.Ldloca, valueLocal)); } } il.MarkLabel(endSerialize); } + + private static void EmitSerializeStructure(ILGenerator il, IEnumerable structure, LocalAllocator GetLocal, Action thisarg) + { + 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); + 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); + } } } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs index c4a2bea7..11a64e4f 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs @@ -3,9 +3,11 @@ using IPA.Logging; using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Boolean = IPA.Config.Data.Boolean; @@ -36,24 +38,56 @@ namespace IPA.Config.Stores } #endregion - private delegate LocalBuilder GetLocal(Type type, int idx = 0); + //private delegate LocalBuilder LocalAllocator(Type type, int idx = 0); - private static GetLocal MakeGetLocal(ILGenerator il) - { // TODO: improve this shit a bit so that i can release a hold of a variable and do more auto managing - var locals = new List(); + private static LocalAllocator MakeLocalAllocator(ILGenerator il) + => new LocalAllocator(il); - LocalBuilder GetLocal(Type ty, int i = 0) + private struct AllocatedLocal : IDisposable + { + internal readonly LocalAllocator allocator; + public LocalBuilder Local { get; } + + public AllocatedLocal(LocalAllocator alloc, LocalBuilder builder) { - var builder = locals.Where(b => b.LocalType == ty).Skip(i).FirstOrDefault(); - if (builder == null) - { - builder = il.DeclareLocal(ty); - locals.Add(builder); - } - return builder; + allocator = alloc; + Local = builder; } - return GetLocal; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator LocalBuilder(AllocatedLocal loc) => loc.Local; + + public void Dealloc() => allocator.Deallocate(this); + public void Dispose() => Dealloc(); + } + + private sealed class LocalAllocator + { + private readonly ILGenerator ilSource; + private readonly Dictionary> unallocatedLocals = new Dictionary>(); + public LocalAllocator(ILGenerator il) + => ilSource = il; + + private Stack GetLocalListForType(Type type) + { + if (!unallocatedLocals.TryGetValue(type, out var list)) + unallocatedLocals.Add(type, list = new Stack()); + return list; + } + + public AllocatedLocal Allocate(Type type) + { + var list = GetLocalListForType(type); + if (list.Count < 1) list.Push(ilSource.DeclareLocal(type)); + return new AllocatedLocal(this, list.Pop()); + } + + public void Deallocate(AllocatedLocal loc) + { + Debug.Assert(loc.allocator == this); + var list = GetLocalListForType(loc.Local.LocalType); + list.Push(loc.Local); + } } private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action thisarg) diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index 67842208..6354d825 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -103,6 +103,7 @@ + diff --git a/Refs/Main.dll b/Refs/Main.dll index 509f50f8..3fbf4d43 100644 Binary files a/Refs/Main.dll and b/Refs/Main.dll differ diff --git a/Refs/Unity.TextMeshPro.dll b/Refs/Unity.TextMeshPro.dll index 397fc3af..987d92ff 100644 Binary files a/Refs/Unity.TextMeshPro.dll and b/Refs/Unity.TextMeshPro.dll differ diff --git a/Refs/UnityEngine.CoreModule.Net4.dll b/Refs/UnityEngine.CoreModule.Net4.dll index 45c11737..66992ff7 100644 Binary files a/Refs/UnityEngine.CoreModule.Net4.dll and b/Refs/UnityEngine.CoreModule.Net4.dll differ diff --git a/Refs/UnityEngine.UI.dll b/Refs/UnityEngine.UI.dll index bb1d0707..76fffee1 100644 Binary files a/Refs/UnityEngine.UI.dll and b/Refs/UnityEngine.UI.dll differ