diff --git a/IPA.Loader/Config/Stores/GeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStore.cs index 2547cd5b..88643281 100644 --- a/IPA.Loader/Config/Stores/GeneratedStore.cs +++ b/IPA.Loader/Config/Stores/GeneratedStore.cs @@ -108,7 +108,7 @@ namespace IPA.Config.Stores if (assembly == null) { var name = new AssemblyName("IPA.Config.Generated"); - assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); + assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave); } return assembly; @@ -126,6 +126,14 @@ namespace IPA.Config.Stores } } + private struct SerializedMemberInfo + { + public string Name; + public MemberInfo Member; + public bool IsVirtual; + public Type Type; + } + private static Func MakeCreator(Type type) { var baseCtor = type.GetConstructor(Type.EmptyTypes); // get a default constructor @@ -141,6 +149,44 @@ namespace IPA.Config.Stores var GetTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)); + #region Parse base object structure + var baseChanged = type.GetMethod("Changed", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty()); + if (baseChanged != null && !baseChanged.IsVirtual) baseChanged = null; // limit this to just the one thing + + var structure = new Dictionary(); + + // TODO: incorporate attributes + + // only looks at public properties + foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + var smi = new SerializedMemberInfo + { + Name = prop.Name, + Member = prop, + IsVirtual = (prop.GetGetMethod(true)?.IsVirtual ?? false) || + (prop.GetSetMethod(true)?.IsVirtual ?? false), + Type = prop.PropertyType + }; + + structure.Add(smi.Name, smi); + } + + // only look at public fields + foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public)) + { + var smi = new SerializedMemberInfo + { + Name = field.Name, + Member = field, + IsVirtual = false, + Type = field.FieldType + }; + + structure.Add(smi.Name, smi); + } + #endregion + #region Constructor // takes its parent var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IGeneratedStore) }); @@ -161,26 +207,38 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Call, GetTypeFromHandle); // effectively typeof(type) il.Emit(OpCodes.Stfld, typeField); - //il.Emit(OpCodes.Dup); // do this if there are additional initializations that need to be done to this type later + il.Emit(OpCodes.Dup); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Newobj, Impl.Ctor); il.Emit(OpCodes.Stfld, implField); - // TODO: do additional initializations for List, etc + foreach (var kvp in structure) + EmitMemberFix(il, kvp.Value); + + il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ret); } #endregion const MethodAttributes propertyMethodAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; + const MethodAttributes virtualPropertyMethodAttr = propertyMethodAttr | MethodAttributes.Virtual | MethodAttributes.Final; #region IGeneratedStore typeBuilder.AddInterfaceImplementation(typeof(IGeneratedStore)); + var IGeneratedStore_t = typeof(IGeneratedStore); + var IGeneratedStore_GetImpl = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Impl)).GetGetMethod(); + var IGeneratedStore_GetType = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Type)).GetGetMethod(); + var IGeneratedStore_GetParent = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Parent)).GetGetMethod(); + var IGeneratedStore_GetValues = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Values)).GetGetMethod(); + var IGeneratedStore_SetValues = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Values)).GetSetMethod(); + #region IGeneratedStore.Impl var implProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Impl), PropertyAttributes.None, typeof(Impl), null); - var implPropGet = typeBuilder.DefineMethod($"get_{nameof(IGeneratedStore.Impl)}", propertyMethodAttr, implProp.PropertyType, Type.EmptyTypes); + var implPropGet = typeBuilder.DefineMethod($"{nameof(IGeneratedStore.Impl)}", virtualPropertyMethodAttr, implProp.PropertyType, Type.EmptyTypes); implProp.SetGetMethod(implPropGet); + typeBuilder.DefineMethodOverride(implPropGet, IGeneratedStore_GetImpl); { var il = implPropGet.GetILGenerator(); @@ -192,8 +250,9 @@ namespace IPA.Config.Stores #endregion #region IGeneratedStore.Type var typeProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Type), PropertyAttributes.None, typeof(Type), null); - var typePropGet = typeBuilder.DefineMethod($"get_{nameof(IGeneratedStore.Type)}", propertyMethodAttr, typeProp.PropertyType, Type.EmptyTypes); + var typePropGet = typeBuilder.DefineMethod($"{nameof(IGeneratedStore.Type)}", virtualPropertyMethodAttr, typeProp.PropertyType, Type.EmptyTypes); typeProp.SetGetMethod(typePropGet); + typeBuilder.DefineMethodOverride(typePropGet, IGeneratedStore_GetType); { var il = typePropGet.GetILGenerator(); @@ -205,8 +264,9 @@ namespace IPA.Config.Stores #endregion #region IGeneratedStore.Parent var parentProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Parent), PropertyAttributes.None, typeof(IGeneratedStore), null); - var parentPropGet = typeBuilder.DefineMethod($"get_{nameof(IGeneratedStore.Parent)}", propertyMethodAttr, parentProp.PropertyType, Type.EmptyTypes); + var parentPropGet = typeBuilder.DefineMethod($"{nameof(IGeneratedStore.Parent)}", virtualPropertyMethodAttr, parentProp.PropertyType, Type.EmptyTypes); parentProp.SetGetMethod(parentPropGet); + typeBuilder.DefineMethodOverride(parentPropGet, IGeneratedStore_GetParent); { var il = parentPropGet.GetILGenerator(); @@ -218,10 +278,12 @@ namespace IPA.Config.Stores #endregion #region IGeneratedStore.Values var valuesProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Values), PropertyAttributes.None, typeof(Value), null); - var valuesPropGet = typeBuilder.DefineMethod($"get_{nameof(IGeneratedStore.Values)}", propertyMethodAttr, valuesProp.PropertyType, Type.EmptyTypes); - var valuesPropSet = typeBuilder.DefineMethod($"set_{nameof(IGeneratedStore.Values)}", propertyMethodAttr, null, new[] { valuesProp.PropertyType }); + var valuesPropGet = typeBuilder.DefineMethod($"{nameof(IGeneratedStore.Values)}", virtualPropertyMethodAttr, valuesProp.PropertyType, Type.EmptyTypes); + var valuesPropSet = typeBuilder.DefineMethod($"{nameof(IGeneratedStore.Values)}", virtualPropertyMethodAttr, null, new[] { valuesProp.PropertyType }); valuesProp.SetGetMethod(valuesPropGet); + typeBuilder.DefineMethodOverride(valuesPropGet, IGeneratedStore_GetValues); valuesProp.SetSetMethod(valuesPropSet); + typeBuilder.DefineMethodOverride(valuesPropSet, IGeneratedStore_SetValues); { // this is non-locking because the only code that will call this will already own the correct lock var il = valuesPropGet.GetILGenerator(); @@ -242,8 +304,54 @@ namespace IPA.Config.Stores #endregion #endregion + #region Changed + var coreChanged = typeBuilder.DefineMethod( + "<>Changed", + MethodAttributes.Public | MethodAttributes.HideBySig, + null, Type.EmptyTypes); + + { + var il = coreChanged.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, Impl.ImplSignalChangedMethod); + il.Emit(OpCodes.Ret); // simply call our impl's SignalChanged method and return + } + + if (baseChanged != null) { + var changedMethod = typeBuilder.DefineMethod( // copy to override baseChanged + baseChanged.Name, + MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig, + null, Type.EmptyTypes); + typeBuilder.DefineMethodOverride(changedMethod, baseChanged); + + { + var il = changedMethod.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, baseChanged); // call base + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Tailcall); + il.Emit(OpCodes.Call, coreChanged); // call back to the core change method + + il.Emit(OpCodes.Ret); + } + + coreChanged = changedMethod; // switch to calling this version instead of just the default + } + #endregion + + // TODO: generate overrides for all the virtual properties + return null; } + // expects the this param to be on the stack + private static void EmitMemberFix(ILGenerator il, SerializedMemberInfo member) + { + + } + } }