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; using System.Threading.Tasks; using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.IO; #if NET3 using Net3_Proxy; using Array = Net3_Proxy.Array; #endif [assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedStore.GeneratedAssemblyName)] namespace IPA.Config.Stores { /// /// A class providing an extension for to make it easy to use generated /// config stores. /// public static class GeneratedStoreExtensions { /// /// Creates a generated of type , registers it to /// the object, and returns it. This also forces a synchronous config load via /// if is . /// /// /// /// must be a non- . /// /// /// TODO: describe details of generated stores /// /// /// the type to wrap /// the to register to /// whether to synchronously load the content, or trigger an async load /// a generated instance of as a special public static T Generated(this Config cfg, bool loadSync = true) where T : class { var ret = GeneratedStore.Create(); cfg.SetStore(ret as IConfigStore); if (loadSync) cfg.LoadSync(); else cfg.LoadAsync(); return ret; } } internal static class GeneratedStore { internal interface IGeneratedStore { /// /// serializes/deserializes to Value /// Value Values { get; set; } Type Type { get; } IGeneratedStore Parent { get; } Impl Impl { get; } } internal class Impl : IConfigStore { private IGeneratedStore generated; internal static ConstructorInfo Ctor = typeof(Impl).GetConstructor(new[] { typeof(IGeneratedStore) }); public Impl(IGeneratedStore store) => generated = store; private readonly AutoResetEvent resetEvent = new AutoResetEvent(false); public WaitHandle SyncObject => resetEvent; internal static MethodInfo SyncObjectGetMethod = typeof(Impl).GetProperty(nameof(SyncObject)).GetGetMethod(); public ReaderWriterLockSlim WriteSyncObject { get; } = new ReaderWriterLockSlim(); internal static MethodInfo WriteSyncObjectGetMethod = typeof(Impl).GetProperty(nameof(WriteSyncObject)).GetGetMethod(); internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged)); public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged(); public void SignalChanged() => resetEvent.Set(); internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead)); public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s).TakeRead(); public void TakeRead() => WriteSyncObject.EnterReadLock(); internal static MethodInfo ImplReleaseReadMethod = typeof(Impl).GetMethod(nameof(ImplReleaseRead)); public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s).ReleaseRead(); public void ReleaseRead() => WriteSyncObject.ExitWriteLock(); internal static MethodInfo ImplTakeWriteMethod = typeof(Impl).GetMethod(nameof(ImplTakeWrite)); public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s).TakeWrite(); public void TakeWrite() => WriteSyncObject.EnterWriteLock(); internal static MethodInfo ImplReleaseWriteMethod = typeof(Impl).GetMethod(nameof(ImplReleaseWrite)); public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s).ReleaseWrite(); public void ReleaseWrite() => WriteSyncObject.ExitWriteLock(); internal static MethodInfo FindImplMethod = typeof(Impl).GetMethod(nameof(FindImpl)); public static Impl FindImpl(IGeneratedStore store) { while (store?.Parent != null) store = store.Parent; // walk to the top of the tree return store?.Impl; } internal static MethodInfo ReadFromMethod = typeof(Impl).GetMethod(nameof(ReadFrom)); public void ReadFrom(IConfigProvider provider) { var values = provider.Load(); Logger.config.Debug("Generated impl ReadFrom"); Logger.config.Debug($"Read {values}"); generated.Values = values; } internal static MethodInfo WriteToMethod = typeof(Impl).GetMethod(nameof(WriteTo)); public void WriteTo(IConfigProvider provider) { var values = generated.Values; Logger.config.Debug("Generated impl WriteTo"); Logger.config.Debug($"Serialized {values}"); provider.Store(values); } } private static Dictionary> generatedCreators = new Dictionary>(); private static Dictionary> memberMaps = new Dictionary>(); public static T Create() where T : class => (T)Create(typeof(T)); public static IConfigStore Create(Type type) => Create(type, null); private static IConfigStore Create(Type type, IGeneratedStore parent) { if (generatedCreators.TryGetValue(type, out var creator)) return creator(parent); else { creator = MakeCreator(type); generatedCreators.Add(type, creator); return creator(parent); } } internal const string GeneratedAssemblyName = "IPA.Config.Generated"; private static AssemblyBuilder assembly = null; private static AssemblyBuilder Assembly { get { if (assembly == null) { var name = new AssemblyName(GeneratedAssemblyName); assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave); } return assembly; } } internal static void DebugSaveAssembly(string file) { Assembly.Save(file); } private static ModuleBuilder module = null; private static ModuleBuilder Module { get { if (module == null) module = Assembly.DefineDynamicModule(Assembly.GetName().Name, Assembly.GetName().Name + ".dll"); return module; } } private struct SerializedMemberInfo { public string Name; public MemberInfo Member; public bool IsVirtual; public bool IsField; public Type Type; } private static Func MakeCreator(Type type) { var baseCtor = type.GetConstructor(Type.EmptyTypes); // get a default constructor if (baseCtor == null) throw new ArgumentException("Config type does not have a public parameterless constructor"); var typeBuilder = Module.DefineType($"{type.FullName}.Generated", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, type); var typeField = typeBuilder.DefineField("<>_type", typeof(Type), FieldAttributes.Private | FieldAttributes.InitOnly); var implField = typeBuilder.DefineField("<>_impl", typeof(Impl), FieldAttributes.Private | FieldAttributes.InitOnly); var parentField = typeBuilder.DefineField("<>_parent", typeof(IGeneratedStore), FieldAttributes.Private | FieldAttributes.InitOnly); // none of this can be Expressions because CompileToMethod requires a static target method for some dumbass reason #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 List(); // TODO: incorporate attributes/base types // 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), IsField = false, Type = prop.PropertyType }; structure.Add(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, IsField = true, Type = field.FieldType }; structure.Add(smi); } #endregion #region Constructor var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IGeneratedStore) }); { // because this is a constructor, it has to be raw IL var il = ctor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // keep this at bottom of stack il.Emit(OpCodes.Dup); il.Emit(OpCodes.Call, baseCtor); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldarg_1); // load parent il.Emit(OpCodes.Stfld, parentField); il.Emit(OpCodes.Dup); EmitTypeof(il, type); il.Emit(OpCodes.Stfld, typeField); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Newobj, Impl.Ctor); il.Emit(OpCodes.Stfld, implField); foreach (var member in structure) EmitMemberFix(il, member); 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; const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | 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($"{nameof(IGeneratedStore.Impl)}", virtualPropertyMethodAttr, implProp.PropertyType, Type.EmptyTypes); implProp.SetGetMethod(implPropGet); typeBuilder.DefineMethodOverride(implPropGet, IGeneratedStore_GetImpl); { var il = implPropGet.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // load this il.Emit(OpCodes.Ldfld, implField); // load impl field il.Emit(OpCodes.Ret); } #endregion #region IGeneratedStore.Type var typeProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Type), PropertyAttributes.None, typeof(Type), null); var typePropGet = typeBuilder.DefineMethod($"{nameof(IGeneratedStore.Type)}", virtualPropertyMethodAttr, typeProp.PropertyType, Type.EmptyTypes); typeProp.SetGetMethod(typePropGet); typeBuilder.DefineMethodOverride(typePropGet, IGeneratedStore_GetType); { var il = typePropGet.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // load this il.Emit(OpCodes.Ldfld, typeField); // load impl field il.Emit(OpCodes.Ret); } #endregion #region IGeneratedStore.Parent var parentProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Parent), PropertyAttributes.None, typeof(IGeneratedStore), null); var parentPropGet = typeBuilder.DefineMethod($"{nameof(IGeneratedStore.Parent)}", virtualPropertyMethodAttr, parentProp.PropertyType, Type.EmptyTypes); parentProp.SetGetMethod(parentPropGet); typeBuilder.DefineMethodOverride(parentPropGet, IGeneratedStore_GetParent); { var il = parentPropGet.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // load this il.Emit(OpCodes.Ldfld, parentField); // load impl field il.Emit(OpCodes.Ret); } #endregion #region IGeneratedStore.Values var valuesProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Values), PropertyAttributes.None, typeof(Value), null); 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(); var Map_Add = typeof(Map).GetMethod(nameof(Map.Add)); il.Emit(OpCodes.Call, typeof(Value).GetMethod(nameof(Value.Map))); // the map is now at the top of the stack foreach (var member in structure) { il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldstr, member.Name); // TODO: make this behave with annotations EmitSerializeMember(il, member); il.Emit(OpCodes.Call, Map_Add); } // the map is still at the top of the stack, return it il.Emit(OpCodes.Ret); } { // this is non-locking because the only code that will call this will already own the correct lock var il = valuesPropSet.GetILGenerator(); 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 nonNull = il.DefineLabel(); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Brtrue, nonNull); EmitLogError(il, "Attempting to deserialize null", tailcall: true); il.Emit(OpCodes.Ret); il.MarkLabel(nonNull); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Isinst, Map_t); il.Emit(OpCodes.Dup); // duplicate cloned value var notMapError = il.DefineLabel(); il.Emit(OpCodes.Brtrue, notMapError); // handle error il.Emit(OpCodes.Pop); // removes the duplicate value EmitLogError(il, $"Invalid root for deserializing {type.FullName}", tailcall: true, expected: il => EmitTypeof(il, Map_t), found: il => { il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Callvirt, Object_GetType); }); il.Emit(OpCodes.Ret); var nextLabel = notMapError; // head of stack is Map instance foreach (var member in structure) { il.MarkLabel(nextLabel); nextLabel = il.DefineLabel(); var endErrorLabel = il.DefineLabel(); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldstr, member.Name); il.Emit(OpCodes.Ldloca_S, valueLocal); il.Emit(OpCodes.Call, Map_TryGetValue); il.Emit(OpCodes.Brtrue_S, endErrorLabel); EmitLogError(il, $"Missing key {member.Name}", tailcall: false); il.Emit(OpCodes.Br, nextLabel); il.MarkLabel(endErrorLabel); il.Emit(OpCodes.Ldloc_S, valueLocal); EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal)); } il.MarkLabel(nextLabel); il.Emit(OpCodes.Pop); // removes the duplicate value il.Emit(OpCodes.Ret); } #endregion #endregion #region IConfigStore typeBuilder.AddInterfaceImplementation(typeof(IConfigStore)); var IConfigStore_t = typeof(IConfigStore); var IConfigStore_GetSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.SyncObject)).GetGetMethod(); var IConfigStore_GetWriteSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.WriteSyncObject)).GetGetMethod(); var IConfigStore_WriteTo = IConfigStore_t.GetMethod(nameof(IConfigStore.WriteTo)); var IConfigStore_ReadFrom = IConfigStore_t.GetMethod(nameof(IConfigStore.ReadFrom)); #region IConfigStore.SyncObject var syncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.SyncObject), PropertyAttributes.None, typeof(WaitHandle), null); var syncObjPropGet = typeBuilder.DefineMethod($"{nameof(IConfigStore.SyncObject)}", virtualPropertyMethodAttr, syncObjProp.PropertyType, Type.EmptyTypes); syncObjProp.SetGetMethod(syncObjPropGet); typeBuilder.DefineMethodOverride(syncObjPropGet, IConfigStore_GetSyncObject); { var il = syncObjPropGet.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, Impl.FindImplMethod); il.Emit(OpCodes.Tailcall); il.Emit(OpCodes.Call, Impl.SyncObjectGetMethod); il.Emit(OpCodes.Ret); } #endregion #region IConfigStore.WriteSyncObject var writeSyncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.WriteSyncObject), PropertyAttributes.None, typeof(WaitHandle), null); var writeSyncObjPropGet = typeBuilder.DefineMethod($"{nameof(IConfigStore.WriteSyncObject)}", virtualPropertyMethodAttr, writeSyncObjProp.PropertyType, Type.EmptyTypes); writeSyncObjProp.SetGetMethod(writeSyncObjPropGet); typeBuilder.DefineMethodOverride(writeSyncObjPropGet, IConfigStore_GetWriteSyncObject); { var il = writeSyncObjPropGet.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, Impl.FindImplMethod); il.Emit(OpCodes.Tailcall); il.Emit(OpCodes.Call, Impl.WriteSyncObjectGetMethod); il.Emit(OpCodes.Ret); } #endregion #region IConfigStore.WriteTo var writeTo = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.WriteTo)}", virtualMemberMethod, null, new[] { typeof(IConfigProvider) }); typeBuilder.DefineMethodOverride(writeTo, IConfigStore_WriteTo); { var il = writeTo.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, Impl.FindImplMethod); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Tailcall); il.Emit(OpCodes.Call, Impl.WriteToMethod); il.Emit(OpCodes.Ret); } #endregion #region IConfigStore.ReadFrom var readFrom = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.ReadFrom)}", virtualMemberMethod, null, new[] { typeof(IConfigProvider) }); typeBuilder.DefineMethodOverride(readFrom, IConfigStore_ReadFrom); { var il = readFrom.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, Impl.FindImplMethod); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Tailcall); il.Emit(OpCodes.Call, Impl.ReadFromMethod); il.Emit(OpCodes.Ret); } #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 var genType = typeBuilder.CreateType(); var parentParam = Expression.Parameter(typeof(IGeneratedStore), "parent"); var creatorDel = Expression.Lambda>( Expression.New(ctor, parentParam), parentParam ).Compile(); { // register a member map var dict = new Dictionary(); foreach (var member in structure) dict.Add(member.Name, member.Type); memberMaps.Add(type, dict); } return creatorDel; } private static void EmitLogError(ILGenerator il, string message, bool tailcall = false, Action expected = null, Action found = null) { if (expected == null) expected = il => il.Emit(OpCodes.Ldnull); if (found == null) found = il => il.Emit(OpCodes.Ldnull); expected(il); found(il); il.Emit(OpCodes.Ldstr, message); if (tailcall) il.Emit(OpCodes.Tailcall); il.Emit(OpCodes.Call, LogErrorMethod); } private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static); internal static void LogError(Type expected, Type found, string message) { Logger.config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}"); } // expects the this param to be on the stack private static void EmitMemberFix(ILGenerator il, SerializedMemberInfo member) { // TODO: impl } private static readonly MethodInfo Type_GetTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)); private static void EmitTypeof(ILGenerator il, Type type) { il.Emit(OpCodes.Ldtoken, type); il.Emit(OpCodes.Call, Type_GetTypeFromHandle); } // emit takes no args, leaves Value at top of stack private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member) { // TODO: impl il.Emit(OpCodes.Ldnull); } private static Type GetExpectedValueTypeForType(Type valT) { if (typeof(Value).IsAssignableFrom(valT)) // this is a Value subtype return valT; // TODO: fill this out the rest of the way return typeof(string); // something that will always fail } internal static class Deserializers { } // 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) { var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType)); var implLabel = il.DefineLabel(); var passedTypeCheck = il.DefineLabel(); var expectType = GetExpectedValueTypeForType(member.Type); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brtrue_S, implLabel); // null check if (member.Type.IsValueType) { il.Emit(OpCodes.Pop); EmitLogError(il, $"Member {member.Name} ({member.Type}) not nullable", tailcall: false, expected: il => EmitTypeof(il, expectType)); il.Emit(OpCodes.Br, nextLabel); } else { // TODO: deserialize null sanely il.Emit(OpCodes.Nop); il.Emit(OpCodes.Pop); il.Emit(OpCodes.Br, nextLabel); } il.MarkLabel(implLabel); il.Emit(OpCodes.Isinst, expectType); //replaces on stack il.Emit(OpCodes.Dup); // duplicate cloned value il.Emit(OpCodes.Brtrue, passedTypeCheck); // null check il.Emit(OpCodes.Pop); EmitLogError(il, $"Unexpected type deserializing {member.Name}; type not nullable", tailcall: false, expected: il => EmitTypeof(il, expectType), found: il => { getValue(il); il.Emit(OpCodes.Callvirt, Object_GetType); }); il.Emit(OpCodes.Br, nextLabel); il.MarkLabel(passedTypeCheck); { // TODO: actually write the value il.Emit(OpCodes.Nop); } il.Emit(OpCodes.Pop); // this is just so the stack is balanced (currently removing the result of Isinst) } } }