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 != 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) { // TODO: implement Logger.config.Debug("Generated impl ReadFrom"); Logger.config.Debug($"Read {provider.Load()}"); } internal static MethodInfo WriteToMethod = typeof(Impl).GetMethod(nameof(WriteTo)); public void WriteTo(IConfigProvider provider) { var values = generated.Values; // TODO: implement Logger.config.Debug("Generated impl WriteTo"); } } 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 + ".dll"); return module; } } 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 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); var GetTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)); // TODO: possibly move all of this manual IL over to Linq.Expressions #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) }); { 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); il.Emit(OpCodes.Ldtoken, type); il.Emit(OpCodes.Call, GetTypeFromHandle); // effectively typeof(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 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; 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(); // TODO: implement get_Values il.Emit(OpCodes.Ldnull); 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(); // TODO: implement set_Values 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 kvp in structure) dict.Add(kvp.Key, kvp.Value.Type); memberMaps.Add(type, dict); } return creatorDel; } // expects the this param to be on the stack private static void EmitMemberFix(ILGenerator il, SerializedMemberInfo member) { } } }