You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1564 lines
69 KiB

  1. using IPA.Config.Data;
  2. using IPA.Config.Stores.Attributes;
  3. using IPA.Logging;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Reflection.Emit;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using System.Linq.Expressions;
  13. using System.Runtime.CompilerServices;
  14. using System.IO;
  15. using Boolean = IPA.Config.Data.Boolean;
  16. using System.Collections;
  17. using IPA.Utilities;
  18. #if NET3
  19. using Net3_Proxy;
  20. using Array = Net3_Proxy.Array;
  21. #endif
  22. [assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedExtension.AssemblyVisibilityTarget)]
  23. namespace IPA.Config.Stores
  24. {
  25. /// <summary>
  26. /// A class providing an extension for <see cref="Config"/> to make it easy to use generated
  27. /// config stores.
  28. /// </summary>
  29. public static class GeneratedExtension
  30. {
  31. /// <summary>
  32. /// The name of the assembly that internals must be visible to to allow internal protection.
  33. /// </summary>
  34. public const string AssemblyVisibilityTarget = GeneratedStore.GeneratedAssemblyName;
  35. /// <summary>
  36. /// Creates a generated <see cref="IConfigStore"/> of type <typeparamref name="T"/>, registers it to
  37. /// the <see cref="Config"/> object, and returns it. This also forces a synchronous config load via
  38. /// <see cref="Config.LoadSync"/> if <paramref name="loadSync"/> is <see langword="true"/>.
  39. /// </summary>
  40. /// <remarks>
  41. /// <para>
  42. /// <typeparamref name="T"/> must be a public non-<see langword="sealed"/> class.
  43. /// It can also be internal, but in that case, then your assembly must have the following attribute
  44. /// to allow the generated code to reference it.
  45. /// <code>
  46. /// [assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedExtension.AssemblyVisibilityTarget)]
  47. /// </code>
  48. /// </para>
  49. /// <para>
  50. /// Only fields and properties that are public or protected will be considered, and only properties
  51. /// where both the getter and setter are public or protected are considered. Any fields or properties
  52. /// with an <see cref="IgnoreAttribute"/> applied to them are also ignored. Having properties be <see langword="virtual"/> is not strictly
  53. /// necessary, however it allows the generated type to keep track of changes and lock around them so that the config will auto-save.
  54. /// </para>
  55. /// <para>
  56. /// All of the attributes in the <see cref="Attributes"/> namespace are handled as described by them.
  57. /// </para>
  58. /// <para>
  59. /// If the <typeparamref name="T"/> declares a public or protected, <see langword="virtual"/>
  60. /// method <c>Changed()</c>, then that method may be called to artificially signal to the runtime that the content of the object
  61. /// has changed. That method will also be called after the write locks are released when a property is set anywhere in the owning
  62. /// tree. This will only be called on the outermost generated object of the config structure, even if the change being signaled
  63. /// is somewhere deep into the tree. TODO: is this a good idea?
  64. /// </para>
  65. /// <para>
  66. /// Similarly, <typeparamref name="T"/> can declare a public or protected, <see langword="virtual"/>
  67. /// method <c>OnReload()</c>, which will be called on the filesystem reader thread after the object has been repopulated with new data
  68. /// values. It will be called <i>after</i> the write lock for this object is released. This will only be called on the outermost generated
  69. /// object of the config structure.
  70. /// </para>
  71. /// <para>
  72. /// Similarly, <typeparamref name="T"/> can declare a public or protected, <see langword="virtual"/>
  73. /// method <c>CopyFrom(ConfigType)</c> (the first parameter is the type it is defined on), which may be called to copy the properties from
  74. /// another object of its type easily, and more importantly, as only one change. Its body will be executed after the values have been copied.
  75. /// </para>
  76. /// <para>
  77. /// TODO: describe details of generated stores
  78. /// </para>
  79. /// </remarks>
  80. /// <typeparam name="T">the type to wrap</typeparam>
  81. /// <param name="cfg">the <see cref="Config"/> to register to</param>
  82. /// <param name="loadSync">whether to synchronously load the content, or trigger an async load</param>
  83. /// <returns>a generated instance of <typeparamref name="T"/> as a special <see cref="IConfigStore"/></returns>
  84. public static T Generated<T>(this Config cfg, bool loadSync = true) where T : class
  85. {
  86. var ret = GeneratedStore.Create<T>();
  87. cfg.SetStore(ret as IConfigStore);
  88. if (loadSync)
  89. cfg.LoadSync();
  90. else
  91. cfg.LoadAsync();
  92. return ret;
  93. }
  94. }
  95. internal static class GeneratedStore
  96. {
  97. internal interface IGeneratedStore
  98. {
  99. Type Type { get; }
  100. IGeneratedStore Parent { get; }
  101. Impl Impl { get; }
  102. void OnReload();
  103. void Changed();
  104. Value Serialize();
  105. void Deserialize(Value val);
  106. }
  107. internal interface IGeneratedStore<T> : IGeneratedStore where T : class
  108. {
  109. void CopyFrom(T source, bool useLock);
  110. }
  111. internal class Impl : IConfigStore
  112. {
  113. private readonly IGeneratedStore generated;
  114. internal static ConstructorInfo Ctor = typeof(Impl).GetConstructor(new[] { typeof(IGeneratedStore) });
  115. public Impl(IGeneratedStore store) => generated = store;
  116. private readonly AutoResetEvent resetEvent = new AutoResetEvent(false);
  117. public WaitHandle SyncObject => resetEvent;
  118. public static WaitHandle ImplGetSyncObject(IGeneratedStore s) => FindImpl(s).SyncObject;
  119. internal static MethodInfo ImplGetSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetSyncObject));
  120. public ReaderWriterLockSlim WriteSyncObject { get; } = new ReaderWriterLockSlim();
  121. public static ReaderWriterLockSlim ImplGetWriteSyncObject(IGeneratedStore s) => FindImpl(s)?.WriteSyncObject;
  122. internal static MethodInfo ImplGetWriteSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetWriteSyncObject));
  123. internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged));
  124. public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged();
  125. public void SignalChanged() => resetEvent.Set();
  126. internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged));
  127. public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s).InvokeChanged();
  128. public void InvokeChanged() => generated.Changed();
  129. internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead));
  130. public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s).TakeRead();
  131. public void TakeRead() => WriteSyncObject.EnterReadLock();
  132. internal static MethodInfo ImplReleaseReadMethod = typeof(Impl).GetMethod(nameof(ImplReleaseRead));
  133. public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s).ReleaseRead();
  134. public void ReleaseRead() => WriteSyncObject.ExitReadLock();
  135. internal static MethodInfo ImplTakeWriteMethod = typeof(Impl).GetMethod(nameof(ImplTakeWrite));
  136. public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s).TakeWrite();
  137. public void TakeWrite() => WriteSyncObject.EnterWriteLock();
  138. internal static MethodInfo ImplReleaseWriteMethod = typeof(Impl).GetMethod(nameof(ImplReleaseWrite));
  139. public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s).ReleaseWrite();
  140. public void ReleaseWrite() => WriteSyncObject.ExitWriteLock();
  141. public static Impl FindImpl(IGeneratedStore store)
  142. {
  143. while (store?.Parent != null) store = store.Parent; // walk to the top of the tree
  144. return store?.Impl;
  145. }
  146. internal static MethodInfo ImplReadFromMethod = typeof(Impl).GetMethod(nameof(ImplReadFrom));
  147. public static void ImplReadFrom(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).ReadFrom(provider);
  148. public void ReadFrom(ConfigProvider provider)
  149. {
  150. var values = provider.Load();
  151. Logger.config.Debug("Generated impl ReadFrom");
  152. Logger.config.Debug($"Read {values}");
  153. generated.Deserialize(values);
  154. ReleaseWrite();
  155. generated.OnReload();
  156. TakeWrite(); // must take again for runtime to be happy (which is unfortunate)
  157. }
  158. internal static MethodInfo ImplWriteToMethod = typeof(Impl).GetMethod(nameof(ImplWriteTo));
  159. public static void ImplWriteTo(IGeneratedStore s, ConfigProvider provider) => FindImpl(s).WriteTo(provider);
  160. public void WriteTo(ConfigProvider provider)
  161. {
  162. var values = generated.Serialize();
  163. Logger.config.Debug("Generated impl WriteTo");
  164. Logger.config.Debug($"Serialized {values}");
  165. provider.Store(values);
  166. }
  167. }
  168. private static readonly Dictionary<Type, (GeneratedStoreCreator ctor, Type type)> generatedCreators = new Dictionary<Type, (GeneratedStoreCreator ctor, Type type)>();
  169. public static T Create<T>() where T : class => (T)Create(typeof(T));
  170. public static IConfigStore Create(Type type) => Create(type, null);
  171. private static readonly MethodInfo CreateGParent =
  172. typeof(GeneratedStore).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Static, null,
  173. CallingConventions.Any, new[] { typeof(IGeneratedStore) }, Array.Empty<ParameterModifier>());
  174. internal static T Create<T>(IGeneratedStore parent) where T : class => (T)Create(typeof(T), parent);
  175. private static IConfigStore Create(Type type, IGeneratedStore parent)
  176. => GetCreator(type)(parent);
  177. internal static GeneratedStoreCreator GetCreator(Type t)
  178. {
  179. if (generatedCreators.TryGetValue(t, out var gen))
  180. return gen.ctor;
  181. else
  182. {
  183. gen = MakeCreator(t);
  184. generatedCreators.Add(t, gen);
  185. return gen.ctor;
  186. }
  187. }
  188. internal static Type GetGeneratedType(Type t)
  189. {
  190. if (generatedCreators.TryGetValue(t, out var gen))
  191. return gen.type;
  192. else
  193. {
  194. gen = MakeCreator(t);
  195. generatedCreators.Add(t, gen);
  196. return gen.type;
  197. }
  198. }
  199. internal const string GeneratedAssemblyName = "IPA.Config.Generated";
  200. private static AssemblyBuilder assembly = null;
  201. private static AssemblyBuilder Assembly
  202. {
  203. get
  204. {
  205. if (assembly == null)
  206. {
  207. var name = new AssemblyName(GeneratedAssemblyName);
  208. assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
  209. }
  210. return assembly;
  211. }
  212. }
  213. internal static void DebugSaveAssembly(string file)
  214. {
  215. Assembly.Save(file);
  216. }
  217. private static ModuleBuilder module = null;
  218. private static ModuleBuilder Module
  219. {
  220. get
  221. {
  222. if (module == null)
  223. module = Assembly.DefineDynamicModule(Assembly.GetName().Name, Assembly.GetName().Name + ".dll");
  224. return module;
  225. }
  226. }
  227. private class SerializedMemberInfo
  228. {
  229. public string Name;
  230. public MemberInfo Member;
  231. public Type Type;
  232. public bool AllowNull;
  233. public bool IsVirtual;
  234. public bool IsField;
  235. public bool IsNullable; // signifies whether this is a Nullable<T>
  236. public bool HasConverter;
  237. public bool IsGenericConverter; // used so we can call directly to the generic version if it is
  238. public Type Converter;
  239. public Type ConverterBase;
  240. public Type ConverterTarget;
  241. public FieldInfo ConverterField;
  242. // invalid for objects with IsNullabe false
  243. public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
  244. // invalid for objects with IsNullabe false
  245. public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
  246. // invalid for objects with IsNullabe false
  247. public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable<int>.Value));
  248. // invalid for objects with IsNullabe false
  249. public ConstructorInfo Nullable_Construct => Type.GetConstructor(new[] { NullableWrappedType });
  250. }
  251. internal delegate IConfigStore GeneratedStoreCreator(IGeneratedStore parent);
  252. private static (GeneratedStoreCreator ctor, Type type) MakeCreator(Type type)
  253. { // note that this does not and should not use converters by default for everything
  254. if (!type.IsClass) throw new ArgumentException("Config type is not a class");
  255. var baseCtor = type.GetConstructor(Type.EmptyTypes); // get a default constructor
  256. if (baseCtor == null)
  257. throw new ArgumentException("Config type does not have a public parameterless constructor");
  258. #region Parse base object structure
  259. var baseChanged = type.GetMethod("Changed", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
  260. if (baseChanged != null && !baseChanged.IsVirtual) baseChanged = null;
  261. var baseOnReload = type.GetMethod("OnReload", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
  262. if (baseOnReload != null && !baseOnReload.IsVirtual) baseOnReload = null;
  263. var baseCopyFrom = type.GetMethod("CopyFrom", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { type }, Array.Empty<ParameterModifier>());
  264. if (baseCopyFrom != null && !baseCopyFrom.IsVirtual) baseCopyFrom = null;
  265. var structure = new List<SerializedMemberInfo>();
  266. bool ProcessAttributesFor(ref SerializedMemberInfo member)
  267. {
  268. var attrs = member.Member.GetCustomAttributes(true);
  269. var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull();
  270. if (ignores.Any()) // we ignore
  271. {
  272. return false;
  273. }
  274. var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull();
  275. member.Name = member.Member.Name;
  276. member.IsNullable = member.Type.IsGenericType
  277. && member.Type.GetGenericTypeDefinition() == typeof(Nullable<>);
  278. member.AllowNull = !nonNullables.Any() && (!member.Type.IsValueType || member.IsNullable);
  279. var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault();
  280. if (nameAttr != null)
  281. member.Name = nameAttr.Name;
  282. member.HasConverter = false;
  283. var converterAttr = attrs.Select(o => o as UseConverterAttribute).NonNull().FirstOrDefault();
  284. if (converterAttr != null)
  285. {
  286. member.Converter = converterAttr.ConverterType;
  287. member.IsGenericConverter = converterAttr.IsGenericConverter;
  288. if (member.Converter.GetConstructor(Type.EmptyTypes) == null)
  289. {
  290. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not default-constructible");
  291. goto endConverterAttr; // is there a better control flow structure to do this?
  292. }
  293. if (member.Converter.ContainsGenericParameters)
  294. {
  295. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that has unfilled type parameters");
  296. goto endConverterAttr;
  297. }
  298. if (member.Converter.IsInterface || member.Converter.IsAbstract)
  299. {
  300. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not constructible");
  301. goto endConverterAttr;
  302. }
  303. var targetType = converterAttr.ConverterTargetType;
  304. if (!member.IsGenericConverter)
  305. {
  306. try
  307. {
  308. var conv = Activator.CreateInstance(converterAttr.ConverterType) as IValueConverter;
  309. targetType = conv.Type;
  310. }
  311. catch
  312. {
  313. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter who's target type could not be determined");
  314. goto endConverterAttr;
  315. }
  316. }
  317. if (targetType != member.Type)
  318. {
  319. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not of the member's type");
  320. goto endConverterAttr;
  321. }
  322. member.ConverterTarget = targetType;
  323. if (member.IsGenericConverter)
  324. member.ConverterBase = typeof(ValueConverter<>).MakeGenericType(targetType);
  325. else
  326. member.ConverterBase = typeof(IValueConverter);
  327. member.HasConverter = true;
  328. }
  329. endConverterAttr:
  330. return true;
  331. }
  332. // only looks at public/protected properties
  333. foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  334. {
  335. if (prop.GetSetMethod(true)?.IsPrivate ?? true)
  336. { // we enter this block if the setter is inacessible or doesn't exist
  337. continue; // ignore props without setter
  338. }
  339. if (prop.GetGetMethod(true)?.IsPrivate ?? true)
  340. { // we enter this block if the getter is inacessible or doesn't exist
  341. continue; // ignore props without getter
  342. }
  343. var smi = new SerializedMemberInfo
  344. {
  345. Member = prop,
  346. IsVirtual = (prop.GetGetMethod(true)?.IsVirtual ?? false) ||
  347. (prop.GetSetMethod(true)?.IsVirtual ?? false),
  348. IsField = false,
  349. Type = prop.PropertyType
  350. };
  351. if (!ProcessAttributesFor(ref smi)) continue;
  352. structure.Add(smi);
  353. }
  354. // only look at public/protected fields
  355. foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  356. {
  357. if (field.IsPrivate) continue;
  358. var smi = new SerializedMemberInfo
  359. {
  360. Member = field,
  361. IsVirtual = false,
  362. IsField = true,
  363. Type = field.FieldType
  364. };
  365. if (!ProcessAttributesFor(ref smi)) continue;
  366. structure.Add(smi);
  367. }
  368. #endregion
  369. var typeBuilder = Module.DefineType($"{type.FullName}<Generated>",
  370. TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, type);
  371. var typeField = typeBuilder.DefineField("<>_type", typeof(Type), FieldAttributes.Private | FieldAttributes.InitOnly);
  372. var implField = typeBuilder.DefineField("<>_impl", typeof(Impl), FieldAttributes.Private | FieldAttributes.InitOnly);
  373. var parentField = typeBuilder.DefineField("<>_parent", typeof(IGeneratedStore), FieldAttributes.Private | FieldAttributes.InitOnly);
  374. #region Converter fields
  375. var uniqueConverterTypes = structure.Where(m => m.HasConverter).Select(m => m.Converter).Distinct().ToArray();
  376. var converterFields = new Dictionary<Type, FieldInfo>(uniqueConverterTypes.Length);
  377. foreach (var convType in uniqueConverterTypes)
  378. {
  379. var field = typeBuilder.DefineField($"<converter>_{convType}", convType,
  380. FieldAttributes.Private | FieldAttributes.InitOnly | FieldAttributes.Static);
  381. converterFields.Add(convType, field);
  382. foreach (var member in structure.Where(m => m.HasConverter && m.Converter == convType))
  383. member.ConverterField = field;
  384. }
  385. #endregion
  386. #region Static constructor
  387. var cctor = typeBuilder.DefineConstructor(MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
  388. {
  389. var il = cctor.GetILGenerator();
  390. foreach (var kvp in converterFields)
  391. {
  392. var typeCtor = kvp.Key.GetConstructor(Type.EmptyTypes);
  393. il.Emit(OpCodes.Newobj, typeCtor);
  394. il.Emit(OpCodes.Stsfld, kvp.Value);
  395. }
  396. il.Emit(OpCodes.Ret);
  397. }
  398. #endregion
  399. #region Constructor
  400. var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IGeneratedStore) });
  401. {
  402. var il = ctor.GetILGenerator();
  403. il.Emit(OpCodes.Ldarg_0); // keep this at bottom of stack
  404. il.Emit(OpCodes.Dup);
  405. il.Emit(OpCodes.Call, baseCtor);
  406. il.Emit(OpCodes.Dup);
  407. il.Emit(OpCodes.Ldarg_1); // load parent
  408. il.Emit(OpCodes.Stfld, parentField);
  409. il.Emit(OpCodes.Dup);
  410. EmitTypeof(il, type);
  411. il.Emit(OpCodes.Stfld, typeField);
  412. var noImplLabel = il.DefineLabel();
  413. il.Emit(OpCodes.Ldarg_1);
  414. il.Emit(OpCodes.Brtrue, noImplLabel);
  415. il.Emit(OpCodes.Dup);
  416. il.Emit(OpCodes.Dup);
  417. il.Emit(OpCodes.Newobj, Impl.Ctor);
  418. il.Emit(OpCodes.Stfld, implField);
  419. il.MarkLabel(noImplLabel);
  420. var GetLocal = MakeGetLocal(il);
  421. foreach (var member in structure)
  422. {
  423. EmitStore(il, member, il =>
  424. {
  425. EmitLoad(il, member); // load the member
  426. EmitCorrectMember(il, member, false, true, GetLocal); // correct it
  427. });
  428. }
  429. il.Emit(OpCodes.Pop);
  430. il.Emit(OpCodes.Ret);
  431. }
  432. #endregion
  433. const MethodAttributes propertyMethodAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
  434. const MethodAttributes virtualPropertyMethodAttr = propertyMethodAttr | MethodAttributes.Virtual | MethodAttributes.Final;
  435. const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final;
  436. #region IGeneratedStore
  437. typeBuilder.AddInterfaceImplementation(typeof(IGeneratedStore));
  438. var IGeneratedStore_t = typeof(IGeneratedStore);
  439. var IGeneratedStore_GetImpl = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Impl)).GetGetMethod();
  440. var IGeneratedStore_GetType = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Type)).GetGetMethod();
  441. var IGeneratedStore_GetParent = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Parent)).GetGetMethod();
  442. var IGeneratedStore_Serialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Serialize));
  443. var IGeneratedStore_Deserialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Deserialize));
  444. var IGeneratedStore_OnReload = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.OnReload));
  445. var IGeneratedStore_Changed = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Changed));
  446. #region IGeneratedStore.OnReload
  447. var onReload = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.OnReload)}", virtualMemberMethod, null, Type.EmptyTypes);
  448. typeBuilder.DefineMethodOverride(onReload, IGeneratedStore_OnReload);
  449. if (baseOnReload != null) typeBuilder.DefineMethodOverride(onReload, baseOnReload);
  450. {
  451. var il = onReload.GetILGenerator();
  452. if (baseOnReload != null)
  453. {
  454. il.Emit(OpCodes.Ldarg_0); // load this
  455. il.Emit(OpCodes.Tailcall);
  456. il.Emit(OpCodes.Call, baseOnReload); // load impl field
  457. }
  458. il.Emit(OpCodes.Ret);
  459. }
  460. #endregion
  461. #region IGeneratedStore.Impl
  462. var implProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Impl), PropertyAttributes.None, typeof(Impl), null);
  463. var implPropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Impl)}", virtualPropertyMethodAttr, implProp.PropertyType, Type.EmptyTypes);
  464. implProp.SetGetMethod(implPropGet);
  465. typeBuilder.DefineMethodOverride(implPropGet, IGeneratedStore_GetImpl);
  466. {
  467. var il = implPropGet.GetILGenerator();
  468. il.Emit(OpCodes.Ldarg_0); // load this
  469. il.Emit(OpCodes.Ldfld, implField); // load impl field
  470. il.Emit(OpCodes.Ret);
  471. }
  472. #endregion
  473. #region IGeneratedStore.Type
  474. var typeProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Type), PropertyAttributes.None, typeof(Type), null);
  475. var typePropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Type)}", virtualPropertyMethodAttr, typeProp.PropertyType, Type.EmptyTypes);
  476. typeProp.SetGetMethod(typePropGet);
  477. typeBuilder.DefineMethodOverride(typePropGet, IGeneratedStore_GetType);
  478. {
  479. var il = typePropGet.GetILGenerator();
  480. il.Emit(OpCodes.Ldarg_0); // load this
  481. il.Emit(OpCodes.Ldfld, typeField); // load impl field
  482. il.Emit(OpCodes.Ret);
  483. }
  484. #endregion
  485. #region IGeneratedStore.Parent
  486. var parentProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Parent), PropertyAttributes.None, typeof(IGeneratedStore), null);
  487. var parentPropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Parent)}", virtualPropertyMethodAttr, parentProp.PropertyType, Type.EmptyTypes);
  488. parentProp.SetGetMethod(parentPropGet);
  489. typeBuilder.DefineMethodOverride(parentPropGet, IGeneratedStore_GetParent);
  490. {
  491. var il = parentPropGet.GetILGenerator();
  492. il.Emit(OpCodes.Ldarg_0); // load this
  493. il.Emit(OpCodes.Ldfld, parentField); // load impl field
  494. il.Emit(OpCodes.Ret);
  495. }
  496. #endregion
  497. #region IGeneratedStore.Serialize
  498. var serializeGen = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.Serialize)}", virtualPropertyMethodAttr, IGeneratedStore_Serialize.ReturnType, Type.EmptyTypes);
  499. typeBuilder.DefineMethodOverride(serializeGen, IGeneratedStore_Serialize);
  500. { // this is non-locking because the only code that will call this will already own the correct lock
  501. var il = serializeGen.GetILGenerator();
  502. var Map_Add = typeof(Map).GetMethod(nameof(Map.Add));
  503. var mapLocal = il.DeclareLocal(typeof(Map));
  504. var GetLocal = MakeGetLocal(il);
  505. var valLocal = GetLocal(typeof(Value));
  506. il.Emit(OpCodes.Call, typeof(Value).GetMethod(nameof(Value.Map)));
  507. il.Emit(OpCodes.Stloc, mapLocal);
  508. foreach (var member in structure)
  509. {
  510. EmitSerializeMember(il, member, GetLocal);
  511. il.Emit(OpCodes.Stloc, valLocal);
  512. il.Emit(OpCodes.Ldloc, mapLocal);
  513. il.Emit(OpCodes.Ldstr, member.Name);
  514. il.Emit(OpCodes.Ldloc, valLocal);
  515. il.Emit(OpCodes.Call, Map_Add);
  516. }
  517. il.Emit(OpCodes.Ldloc, mapLocal);
  518. il.Emit(OpCodes.Ret);
  519. }
  520. #endregion
  521. #region IGeneratedStore.Deserialize
  522. var deserializeGen = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.Deserialize)}", virtualPropertyMethodAttr, null,
  523. new[] { IGeneratedStore_Deserialize.GetParameters()[0].ParameterType });
  524. typeBuilder.DefineMethodOverride(deserializeGen, IGeneratedStore_Deserialize);
  525. { // this is non-locking because the only code that will call this will already own the correct lock
  526. var il = deserializeGen.GetILGenerator();
  527. var Map_t = typeof(Map);
  528. var Map_TryGetValue = Map_t.GetMethod(nameof(Map.TryGetValue));
  529. var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
  530. var valueLocal = il.DeclareLocal(typeof(Value));
  531. var mapLocal = il.DeclareLocal(typeof(Map));
  532. var nonNull = il.DefineLabel();
  533. il.Emit(OpCodes.Ldarg_1);
  534. il.Emit(OpCodes.Brtrue, nonNull);
  535. EmitLogError(il, "Attempting to deserialize null", tailcall: true);
  536. il.Emit(OpCodes.Ret);
  537. il.MarkLabel(nonNull);
  538. il.Emit(OpCodes.Ldarg_1);
  539. il.Emit(OpCodes.Isinst, Map_t);
  540. il.Emit(OpCodes.Dup); // duplicate cloned value
  541. il.Emit(OpCodes.Stloc, mapLocal);
  542. var notMapError = il.DefineLabel();
  543. il.Emit(OpCodes.Brtrue, notMapError);
  544. // handle error
  545. EmitLogError(il, $"Invalid root for deserializing {type.FullName}", tailcall: true,
  546. expected: il => EmitTypeof(il, Map_t), found: il =>
  547. {
  548. il.Emit(OpCodes.Ldarg_1);
  549. il.Emit(OpCodes.Callvirt, Object_GetType);
  550. });
  551. il.Emit(OpCodes.Ret);
  552. var nextLabel = notMapError;
  553. var GetLocal = MakeGetLocal(il);
  554. // head of stack is Map instance
  555. foreach (var member in structure)
  556. {
  557. il.MarkLabel(nextLabel);
  558. nextLabel = il.DefineLabel();
  559. var endErrorLabel = il.DefineLabel();
  560. il.Emit(OpCodes.Ldloc, mapLocal);
  561. il.Emit(OpCodes.Ldstr, member.Name);
  562. il.Emit(OpCodes.Ldloca_S, valueLocal);
  563. il.Emit(OpCodes.Call, Map_TryGetValue);
  564. il.Emit(OpCodes.Brtrue_S, endErrorLabel);
  565. EmitLogError(il, $"Missing key {member.Name}", tailcall: false);
  566. il.Emit(OpCodes.Br, nextLabel);
  567. il.MarkLabel(endErrorLabel);
  568. il.Emit(OpCodes.Ldloc_S, valueLocal);
  569. EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal);
  570. }
  571. il.MarkLabel(nextLabel);
  572. il.Emit(OpCodes.Ret);
  573. }
  574. #endregion
  575. #endregion
  576. #region IGeneratedStore<T>
  577. var IGeneratedStore_T_t = typeof(IGeneratedStore<>).MakeGenericType(type);
  578. typeBuilder.AddInterfaceImplementation(IGeneratedStore_T_t);
  579. var IGeneratedStore_T_CopyFrom = IGeneratedStore_T_t.GetMethod(nameof(IGeneratedStore<Config>.CopyFrom));
  580. #region IGeneratedStore<T>.CopyFrom
  581. var copyFrom = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore<Config>.CopyFrom)}", virtualMemberMethod, null, new[] { type, typeof(bool) });
  582. typeBuilder.DefineMethodOverride(copyFrom, IGeneratedStore_T_CopyFrom);
  583. {
  584. var il = copyFrom.GetILGenerator();
  585. var startLock = il.DefineLabel();
  586. il.Emit(OpCodes.Ldarg_2);
  587. il.Emit(OpCodes.Brfalse, startLock);
  588. il.Emit(OpCodes.Ldarg_0);
  589. il.Emit(OpCodes.Call, Impl.ImplTakeWriteMethod); // take the write lock
  590. il.MarkLabel(startLock);
  591. var GetLocal = MakeGetLocal(il);
  592. foreach (var member in structure)
  593. {
  594. il.BeginExceptionBlock();
  595. EmitStore(il, member, il =>
  596. {
  597. EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_1));
  598. EmitCorrectMember(il, member, false, false, GetLocal);
  599. });
  600. il.BeginCatchBlock(typeof(Exception));
  601. EmitWarnException(il, $"Error while copying from member {member.Name}");
  602. il.EndExceptionBlock();
  603. }
  604. var endLock = il.DefineLabel();
  605. il.Emit(OpCodes.Ldarg_2);
  606. il.Emit(OpCodes.Brfalse, endLock);
  607. il.Emit(OpCodes.Ldarg_0);
  608. il.Emit(OpCodes.Call, Impl.ImplReleaseWriteMethod); // release write lock
  609. il.MarkLabel(endLock);
  610. il.Emit(OpCodes.Ret);
  611. }
  612. #endregion
  613. #endregion
  614. #region IConfigStore
  615. typeBuilder.AddInterfaceImplementation(typeof(IConfigStore));
  616. var IConfigStore_t = typeof(IConfigStore);
  617. var IConfigStore_GetSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.SyncObject)).GetGetMethod();
  618. var IConfigStore_GetWriteSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.WriteSyncObject)).GetGetMethod();
  619. var IConfigStore_WriteTo = IConfigStore_t.GetMethod(nameof(IConfigStore.WriteTo));
  620. var IConfigStore_ReadFrom = IConfigStore_t.GetMethod(nameof(IConfigStore.ReadFrom));
  621. #region IConfigStore.SyncObject
  622. var syncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.SyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
  623. var syncObjPropGet = typeBuilder.DefineMethod($"<g>{nameof(IConfigStore.SyncObject)}", virtualPropertyMethodAttr, syncObjProp.PropertyType, Type.EmptyTypes);
  624. syncObjProp.SetGetMethod(syncObjPropGet);
  625. typeBuilder.DefineMethodOverride(syncObjPropGet, IConfigStore_GetSyncObject);
  626. {
  627. var il = syncObjPropGet.GetILGenerator();
  628. il.Emit(OpCodes.Ldarg_0);
  629. il.Emit(OpCodes.Tailcall);
  630. il.Emit(OpCodes.Call, Impl.ImplGetSyncObjectMethod);
  631. il.Emit(OpCodes.Ret);
  632. }
  633. #endregion
  634. #region IConfigStore.WriteSyncObject
  635. var writeSyncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.WriteSyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
  636. var writeSyncObjPropGet = typeBuilder.DefineMethod($"<g>{nameof(IConfigStore.WriteSyncObject)}", virtualPropertyMethodAttr, writeSyncObjProp.PropertyType, Type.EmptyTypes);
  637. writeSyncObjProp.SetGetMethod(writeSyncObjPropGet);
  638. typeBuilder.DefineMethodOverride(writeSyncObjPropGet, IConfigStore_GetWriteSyncObject);
  639. {
  640. var il = writeSyncObjPropGet.GetILGenerator();
  641. il.Emit(OpCodes.Ldarg_0);
  642. il.Emit(OpCodes.Tailcall);
  643. il.Emit(OpCodes.Call, Impl.ImplGetWriteSyncObjectMethod);
  644. il.Emit(OpCodes.Ret);
  645. }
  646. #endregion
  647. #region IConfigStore.WriteTo
  648. var writeTo = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.WriteTo)}", virtualMemberMethod, null, new[] { typeof(ConfigProvider) });
  649. typeBuilder.DefineMethodOverride(writeTo, IConfigStore_WriteTo);
  650. {
  651. var il = writeTo.GetILGenerator();
  652. il.Emit(OpCodes.Ldarg_0);
  653. il.Emit(OpCodes.Ldarg_1);
  654. il.Emit(OpCodes.Tailcall);
  655. il.Emit(OpCodes.Call, Impl.ImplWriteToMethod);
  656. il.Emit(OpCodes.Ret);
  657. }
  658. #endregion
  659. #region IConfigStore.ReadFrom
  660. var readFrom = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.ReadFrom)}", virtualMemberMethod, null, new[] { typeof(ConfigProvider) });
  661. typeBuilder.DefineMethodOverride(readFrom, IConfigStore_ReadFrom);
  662. {
  663. var il = readFrom.GetILGenerator();
  664. il.Emit(OpCodes.Ldarg_0);
  665. il.Emit(OpCodes.Ldarg_1);
  666. il.Emit(OpCodes.Tailcall);
  667. il.Emit(OpCodes.Call, Impl.ImplReadFromMethod);
  668. il.Emit(OpCodes.Ret);
  669. }
  670. #endregion
  671. #endregion
  672. #region Changed
  673. var coreChanged = typeBuilder.DefineMethod(
  674. "<>Changed",
  675. virtualMemberMethod,
  676. null, Type.EmptyTypes);
  677. {
  678. var il = coreChanged.GetILGenerator();
  679. il.Emit(OpCodes.Ldarg_0);
  680. il.Emit(OpCodes.Call, Impl.ImplSignalChangedMethod);
  681. il.Emit(OpCodes.Ret); // simply call our impl's SignalChanged method and return
  682. }
  683. if (baseChanged != null) {
  684. var changedMethod = typeBuilder.DefineMethod( // copy to override baseChanged
  685. baseChanged.Name,
  686. virtualMemberMethod,
  687. null, Type.EmptyTypes);
  688. typeBuilder.DefineMethodOverride(changedMethod, baseChanged);
  689. {
  690. var il = changedMethod.GetILGenerator();
  691. il.Emit(OpCodes.Ldarg_0);
  692. il.Emit(OpCodes.Call, baseChanged); // call base
  693. il.Emit(OpCodes.Ldarg_0);
  694. il.Emit(OpCodes.Tailcall);
  695. il.Emit(OpCodes.Call, coreChanged); // call back to the core change method
  696. il.Emit(OpCodes.Ret);
  697. }
  698. coreChanged = changedMethod; // switch to calling this version instead of just the default
  699. }
  700. typeBuilder.DefineMethodOverride(coreChanged, IGeneratedStore_Changed);
  701. #endregion
  702. #region base.CopyFrom
  703. if (baseCopyFrom != null)
  704. {
  705. var pubCopyFrom = typeBuilder.DefineMethod(
  706. baseCopyFrom.Name,
  707. virtualMemberMethod,
  708. null, new[] { type });
  709. typeBuilder.DefineMethodOverride(pubCopyFrom, baseCopyFrom);
  710. {
  711. var il = pubCopyFrom.GetILGenerator();
  712. // TODO: use transactional changes
  713. il.Emit(OpCodes.Ldarg_0);
  714. il.Emit(OpCodes.Ldarg_1);
  715. il.Emit(OpCodes.Ldc_I4_1);
  716. il.Emit(OpCodes.Call, copyFrom); // call internal
  717. il.Emit(OpCodes.Ldarg_0);
  718. il.Emit(OpCodes.Ldarg_1);
  719. il.Emit(OpCodes.Call, baseCopyFrom); // call base
  720. il.Emit(OpCodes.Ldarg_0);
  721. il.Emit(OpCodes.Tailcall);
  722. il.Emit(OpCodes.Call, coreChanged); // call changed
  723. il.Emit(OpCodes.Ret);
  724. }
  725. }
  726. #endregion
  727. #region Members
  728. foreach (var member in structure.Where(m => m.IsVirtual))
  729. { // IsVirtual implies !IsField
  730. var prop = member.Member as PropertyInfo;
  731. var get = prop.GetGetMethod(true);
  732. var set = prop.GetSetMethod(true);
  733. var propBuilder = typeBuilder.DefineProperty($"{member.Name}#", PropertyAttributes.None, member.Type, null);
  734. var propGet = typeBuilder.DefineMethod($"<g>{propBuilder.Name}", virtualPropertyMethodAttr, member.Type, Type.EmptyTypes);
  735. propBuilder.SetGetMethod(propGet);
  736. typeBuilder.DefineMethodOverride(propGet, get);
  737. {
  738. var il = propGet.GetILGenerator();
  739. var local = il.DeclareLocal(member.Type);
  740. il.Emit(OpCodes.Ldarg_0);
  741. il.Emit(OpCodes.Call, Impl.ImplTakeReadMethod); // take the read lock
  742. il.BeginExceptionBlock();
  743. il.Emit(OpCodes.Ldarg_0);
  744. il.Emit(OpCodes.Call, get); // call base getter
  745. il.Emit(OpCodes.Stloc, local);
  746. il.BeginFinallyBlock();
  747. il.Emit(OpCodes.Ldarg_0);
  748. il.Emit(OpCodes.Call, Impl.ImplReleaseReadMethod); // release the read lock
  749. il.EndExceptionBlock();
  750. il.Emit(OpCodes.Ldloc, local);
  751. il.Emit(OpCodes.Ret);
  752. }
  753. var propSet = typeBuilder.DefineMethod($"<s>{propBuilder.Name}", virtualPropertyMethodAttr, null, new[] { member.Type });
  754. propBuilder.SetSetMethod(propSet);
  755. typeBuilder.DefineMethodOverride(propSet, set);
  756. { // TODO: decide if i want to correct the value before or after i take the write lock
  757. var il = propSet.GetILGenerator();
  758. var GetLocal = MakeGetLocal(il);
  759. il.Emit(OpCodes.Ldarg_0);
  760. il.Emit(OpCodes.Call, Impl.ImplTakeWriteMethod); // take the write lock
  761. il.BeginExceptionBlock();
  762. il.Emit(OpCodes.Ldarg_0);
  763. il.Emit(OpCodes.Ldarg_1);
  764. EmitCorrectMember(il, member, false, false, GetLocal);
  765. il.Emit(OpCodes.Call, set);
  766. il.BeginFinallyBlock();
  767. il.Emit(OpCodes.Ldarg_0);
  768. il.Emit(OpCodes.Call, Impl.ImplReleaseWriteMethod); // release the write lock
  769. il.EndExceptionBlock();
  770. il.Emit(OpCodes.Ldarg_0);
  771. il.Emit(OpCodes.Call, Impl.ImplInvokeChangedMethod);
  772. il.Emit(OpCodes.Ret);
  773. }
  774. }
  775. #endregion
  776. var genType = typeBuilder.CreateType();
  777. var parentParam = Expression.Parameter(typeof(IGeneratedStore), "parent");
  778. var creatorDel = Expression.Lambda<GeneratedStoreCreator>(
  779. Expression.New(ctor, parentParam), parentParam
  780. ).Compile();
  781. return (creatorDel, genType);
  782. }
  783. private delegate LocalBuilder GetLocal(Type type, int idx = 0);
  784. private static GetLocal MakeGetLocal(ILGenerator il)
  785. { // TODO: improve this shit a bit so that i can release a hold of a variable and do more auto managing
  786. var locals = new List<LocalBuilder>();
  787. LocalBuilder GetLocal(Type ty, int i = 0)
  788. {
  789. var builder = locals.Where(b => b.LocalType == ty).Skip(i).FirstOrDefault();
  790. if (builder == null)
  791. {
  792. builder = il.DeclareLocal(ty);
  793. locals.Add(builder);
  794. }
  795. return builder;
  796. }
  797. return GetLocal;
  798. }
  799. private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static);
  800. internal static void LogError(Type expected, Type found, string message)
  801. {
  802. Logger.config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}");
  803. }
  804. private static readonly MethodInfo LogWarningMethod = typeof(GeneratedStore).GetMethod(nameof(LogWarning), BindingFlags.NonPublic | BindingFlags.Static);
  805. internal static void LogWarning(string message)
  806. {
  807. Logger.config.Warn(message);
  808. }
  809. private static readonly MethodInfo LogWarningExceptionMethod = typeof(GeneratedStore).GetMethod(nameof(LogWarningException), BindingFlags.NonPublic | BindingFlags.Static);
  810. internal static void LogWarningException(Exception exception)
  811. {
  812. Logger.config.Warn(exception);
  813. }
  814. private static bool NeedsCorrection(SerializedMemberInfo member)
  815. {
  816. var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type);
  817. if (expectType == typeof(Map)) // TODO: make this slightly saner
  818. return true;
  819. return false;
  820. }
  821. // expects start value on stack, exits with final value on stack
  822. private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, GetLocal GetLocal)
  823. {
  824. if (!NeedsCorrection(member)) return;
  825. var endLabel = il.DefineLabel();
  826. if (member.IsNullable)
  827. {
  828. il.Emit(OpCodes.Dup);
  829. il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod());
  830. il.Emit(OpCodes.Brfalse, endLabel);
  831. il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod());
  832. }
  833. // TODO: impl the rest of this
  834. // currently the only thing for this is where expect == Map, so do generate shit
  835. var copyFrom = typeof(IGeneratedStore<>).MakeGenericType(member.Type).GetMethod(nameof(IGeneratedStore<Config>.CopyFrom));
  836. var noCreate = il.DefineLabel();
  837. var valLocal = GetLocal(member.Type);
  838. if (!alwaysNew)
  839. {
  840. il.Emit(OpCodes.Dup);
  841. il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
  842. il.Emit(OpCodes.Brtrue_S, endLabel); // our input is already something we like
  843. }
  844. il.Emit(OpCodes.Stloc, valLocal);
  845. if (!alwaysNew)
  846. {
  847. EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_0));
  848. il.Emit(OpCodes.Dup);
  849. il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
  850. il.Emit(OpCodes.Brtrue_S, noCreate);
  851. il.Emit(OpCodes.Pop);
  852. }
  853. EmitCreateChildGenerated(il, member.Type);
  854. il.MarkLabel(noCreate);
  855. il.Emit(OpCodes.Dup);
  856. il.Emit(OpCodes.Ldloc, valLocal);
  857. il.Emit(shouldLock ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
  858. il.Emit(OpCodes.Callvirt, copyFrom);
  859. // TODO: impl the rest of this
  860. if (member.IsNullable)
  861. il.Emit(OpCodes.Newobj, member.Nullable_Construct);
  862. il.MarkLabel(endLabel);
  863. }
  864. #region Utility
  865. private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> thisarg = null)
  866. {
  867. if (thisarg == null)
  868. thisarg = il => il.Emit(OpCodes.Ldarg_0);
  869. thisarg(il); // load this
  870. if (member.IsField)
  871. il.Emit(OpCodes.Ldfld, member.Member as FieldInfo);
  872. else
  873. { // member is a property
  874. var prop = member.Member as PropertyInfo;
  875. var getter = prop.GetGetMethod();
  876. if (getter == null) throw new InvalidOperationException($"Property {member.Name} does not have a getter and is not ignored");
  877. il.Emit(OpCodes.Call, getter);
  878. }
  879. }
  880. private static void EmitStore(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> value)
  881. {
  882. il.Emit(OpCodes.Ldarg_0); // load this
  883. value(il);
  884. if (member.IsField)
  885. il.Emit(OpCodes.Stfld, member.Member as FieldInfo);
  886. else
  887. { // member is a property
  888. var prop = member.Member as PropertyInfo;
  889. var setter = prop.GetSetMethod();
  890. if (setter == null) throw new InvalidOperationException($"Property {member.Name} does not have a setter and is not ignored");
  891. il.Emit(OpCodes.Call, setter);
  892. }
  893. }
  894. private static void EmitWarnException(ILGenerator il, string v)
  895. {
  896. il.Emit(OpCodes.Ldstr, v);
  897. il.Emit(OpCodes.Call, LogWarningMethod);
  898. il.Emit(OpCodes.Call, LogWarningExceptionMethod);
  899. }
  900. private static void EmitLogError(ILGenerator il, string message, bool tailcall = false, Action<ILGenerator> expected = null, Action<ILGenerator> found = null)
  901. {
  902. if (expected == null) expected = il => il.Emit(OpCodes.Ldnull);
  903. if (found == null) found = il => il.Emit(OpCodes.Ldnull);
  904. expected(il);
  905. found(il);
  906. il.Emit(OpCodes.Ldstr, message);
  907. if (tailcall) il.Emit(OpCodes.Tailcall);
  908. il.Emit(OpCodes.Call, LogErrorMethod);
  909. }
  910. private static readonly MethodInfo Type_GetTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle));
  911. private static void EmitTypeof(ILGenerator il, Type type)
  912. {
  913. il.Emit(OpCodes.Ldtoken, type);
  914. il.Emit(OpCodes.Call, Type_GetTypeFromHandle);
  915. }
  916. private static Type Decimal_t = typeof(decimal);
  917. private static ConstructorInfo Decimal_FromFloat = Decimal_t.GetConstructor(new[] { typeof(float) });
  918. private static ConstructorInfo Decimal_FromDouble = Decimal_t.GetConstructor(new[] { typeof(double) });
  919. private static ConstructorInfo Decimal_FromInt = Decimal_t.GetConstructor(new[] { typeof(int) });
  920. private static ConstructorInfo Decimal_FromUInt = Decimal_t.GetConstructor(new[] { typeof(uint) });
  921. private static ConstructorInfo Decimal_FromLong = Decimal_t.GetConstructor(new[] { typeof(long) });
  922. private static ConstructorInfo Decimal_FromULong = Decimal_t.GetConstructor(new[] { typeof(ulong) });
  923. private static void EmitNumberConvertTo(ILGenerator il, Type to, Type from)
  924. { // WARNING: THIS USES THE NO-OVERFLOW OPCODES
  925. if (to == from) return;
  926. if (to == Decimal_t)
  927. {
  928. if (from == typeof(float)) il.Emit(OpCodes.Newobj, Decimal_FromFloat);
  929. else if (from == typeof(double)) il.Emit(OpCodes.Newobj, Decimal_FromDouble);
  930. else if (from == typeof(long)) il.Emit(OpCodes.Newobj, Decimal_FromLong);
  931. else if (from == typeof(ulong)) il.Emit(OpCodes.Newobj, Decimal_FromULong);
  932. else if (from == typeof(int)) il.Emit(OpCodes.Newobj, Decimal_FromInt);
  933. else if (from == typeof(uint)) il.Emit(OpCodes.Newobj, Decimal_FromUInt);
  934. else if (from == typeof(IntPtr))
  935. {
  936. EmitNumberConvertTo(il, typeof(long), from);
  937. EmitNumberConvertTo(il, to, typeof(long));
  938. }
  939. else if (from == typeof(UIntPtr))
  940. {
  941. EmitNumberConvertTo(il, typeof(ulong), from);
  942. EmitNumberConvertTo(il, to, typeof(ulong));
  943. }
  944. else
  945. { // if the source is anything else, we first convert to int because that can contain all other values
  946. EmitNumberConvertTo(il, typeof(int), from);
  947. EmitNumberConvertTo(il, to, typeof(int));
  948. };
  949. }
  950. else if (from == Decimal_t)
  951. {
  952. if (to == typeof(IntPtr))
  953. {
  954. EmitNumberConvertTo(il, typeof(long), from);
  955. EmitNumberConvertTo(il, to, typeof(long));
  956. }
  957. else if (to == typeof(UIntPtr))
  958. {
  959. EmitNumberConvertTo(il, typeof(ulong), from);
  960. EmitNumberConvertTo(il, to, typeof(ulong));
  961. }
  962. else
  963. {
  964. var method = Decimal_t.GetMethod($"To{to.Name}"); // conveniently, this is the pattern of the to* names
  965. il.Emit(OpCodes.Call, method);
  966. }
  967. }
  968. else if (to == typeof(IntPtr)) il.Emit(OpCodes.Conv_I);
  969. else if (to == typeof(UIntPtr)) il.Emit(OpCodes.Conv_U);
  970. else if (to == typeof(sbyte)) il.Emit(OpCodes.Conv_I1);
  971. else if (to == typeof(byte)) il.Emit(OpCodes.Conv_U1);
  972. else if (to == typeof(short)) il.Emit(OpCodes.Conv_I2);
  973. else if (to == typeof(ushort)) il.Emit(OpCodes.Conv_U2);
  974. else if (to == typeof(int)) il.Emit(OpCodes.Conv_I4);
  975. else if (to == typeof(uint)) il.Emit(OpCodes.Conv_U4);
  976. else if (to == typeof(long)) il.Emit(OpCodes.Conv_I8);
  977. else if (to == typeof(ulong)) il.Emit(OpCodes.Conv_U8);
  978. else if (to == typeof(float))
  979. {
  980. if (from == typeof(byte)
  981. || from == typeof(ushort)
  982. || from == typeof(uint)
  983. || from == typeof(ulong)
  984. || from == typeof(UIntPtr)) il.Emit(OpCodes.Conv_R_Un);
  985. il.Emit(OpCodes.Conv_R4);
  986. }
  987. else if (to == typeof(double))
  988. {
  989. if (from == typeof(byte)
  990. || from == typeof(ushort)
  991. || from == typeof(uint)
  992. || from == typeof(ulong)
  993. || from == typeof(UIntPtr)) il.Emit(OpCodes.Conv_R_Un);
  994. il.Emit(OpCodes.Conv_R8);
  995. }
  996. }
  997. private static void EmitCreateChildGenerated(ILGenerator il, Type childType)
  998. {
  999. var method = CreateGParent.MakeGenericMethod(childType);
  1000. il.Emit(OpCodes.Ldarg_0);
  1001. il.Emit(OpCodes.Call, method);
  1002. }
  1003. #endregion
  1004. #region Serialize
  1005. // emit takes no args, leaves Value at top of stack
  1006. private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal)
  1007. {
  1008. EmitLoad(il, member);
  1009. var endSerialize = il.DefineLabel();
  1010. if (member.AllowNull)
  1011. {
  1012. var passedNull = il.DefineLabel();
  1013. il.Emit(OpCodes.Dup);
  1014. if (member.IsNullable)
  1015. il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod());
  1016. il.Emit(OpCodes.Brtrue, passedNull);
  1017. il.Emit(OpCodes.Pop);
  1018. il.Emit(OpCodes.Ldnull);
  1019. il.Emit(OpCodes.Br, endSerialize);
  1020. il.MarkLabel(passedNull);
  1021. }
  1022. if (member.IsNullable)
  1023. il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod());
  1024. var targetType = GetExpectedValueTypeForType(member.Type);
  1025. if (member.HasConverter)
  1026. {
  1027. var stlocal = GetLocal(member.Type);
  1028. var valLocal = GetLocal(typeof(Value));
  1029. il.Emit(OpCodes.Stloc, stlocal);
  1030. il.BeginExceptionBlock();
  1031. il.Emit(OpCodes.Ldsfld, member.ConverterField);
  1032. il.Emit(OpCodes.Ldloc, stlocal);
  1033. if (member.IsGenericConverter)
  1034. {
  1035. var toValue = member.ConverterBase.GetMethod(nameof(ValueConverter<int>.ToValue),
  1036. new[] { member.ConverterTarget, typeof(object) });
  1037. il.Emit(OpCodes.Ldarg_0);
  1038. il.Emit(OpCodes.Callvirt, toValue); // TODO: figure out how to devirtualize this
  1039. }
  1040. else
  1041. {
  1042. var toValue = typeof(IValueConverter).GetMethod(nameof(IValueConverter.ToValue),
  1043. new[] { typeof(object), typeof(object) });
  1044. il.Emit(OpCodes.Box);
  1045. il.Emit(OpCodes.Ldarg_0);
  1046. il.Emit(OpCodes.Callvirt, toValue);
  1047. }
  1048. il.Emit(OpCodes.Stloc, valLocal);
  1049. il.BeginCatchBlock(typeof(Exception));
  1050. EmitWarnException(il, "Error serializing member using converter");
  1051. il.Emit(OpCodes.Ldnull);
  1052. il.Emit(OpCodes.Stloc, valLocal);
  1053. il.EndExceptionBlock();
  1054. il.Emit(OpCodes.Ldloc, valLocal);
  1055. }
  1056. else if (targetType == typeof(Text))
  1057. { // only happens when arg is a string or char
  1058. var TextCreate = typeof(Value).GetMethod(nameof(Value.Text));
  1059. if (member.Type == typeof(char))
  1060. {
  1061. var strFromChar = typeof(char).GetMethod(nameof(char.ToString), new[] { typeof(char) });
  1062. il.Emit(OpCodes.Call, strFromChar);
  1063. }
  1064. il.Emit(OpCodes.Call, TextCreate);
  1065. }
  1066. else if (targetType == typeof(Boolean))
  1067. {
  1068. var BoolCreate = typeof(Value).GetMethod(nameof(Value.Bool));
  1069. il.Emit(OpCodes.Call, BoolCreate);
  1070. }
  1071. else if (targetType == typeof(Integer))
  1072. {
  1073. var IntCreate = typeof(Value).GetMethod(nameof(Value.Integer));
  1074. EmitNumberConvertTo(il, IntCreate.GetParameters()[0].ParameterType, member.Type);
  1075. il.Emit(OpCodes.Call, IntCreate);
  1076. }
  1077. else if (targetType == typeof(FloatingPoint))
  1078. {
  1079. var FloatCreate = typeof(Value).GetMethod(nameof(Value.Float));
  1080. EmitNumberConvertTo(il, FloatCreate.GetParameters()[0].ParameterType, member.Type);
  1081. il.Emit(OpCodes.Call, FloatCreate);
  1082. }
  1083. else if (targetType == typeof(List))
  1084. {
  1085. // TODO: impl this (enumerables)
  1086. il.Emit(OpCodes.Pop);
  1087. il.Emit(OpCodes.Ldnull);
  1088. }
  1089. else if (targetType == typeof(Map))
  1090. {
  1091. // TODO: support other aggregate types
  1092. // for now, we assume that its a generated type implementing IGeneratedStore
  1093. var IGeneratedStore_Serialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Serialize));
  1094. il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize);
  1095. }
  1096. il.MarkLabel(endSerialize);
  1097. }
  1098. #endregion
  1099. #region Deserialize
  1100. private static Type GetExpectedValueTypeForType(Type valT)
  1101. {
  1102. if (typeof(Value).IsAssignableFrom(valT)) // this is a Value subtype
  1103. return valT;
  1104. if (valT == typeof(string)
  1105. || valT == typeof(char)) return typeof(Text);
  1106. if (valT == typeof(bool)) return typeof(Boolean);
  1107. if (valT == typeof(byte)
  1108. || valT == typeof(sbyte)
  1109. || valT == typeof(short)
  1110. || valT == typeof(ushort)
  1111. || valT == typeof(int)
  1112. || valT == typeof(uint)
  1113. || valT == typeof(long)
  1114. || valT == typeof(IntPtr)) return typeof(Integer);
  1115. if (valT == typeof(float)
  1116. || valT == typeof(double)
  1117. || valT == typeof(decimal)
  1118. || valT == typeof(ulong) // ulong gets put into this, because decimal can hold it
  1119. || valT == typeof(UIntPtr)) return typeof(FloatingPoint);
  1120. if (typeof(IEnumerable).IsAssignableFrom(valT)) return typeof(List);
  1121. // TODO: fill this out the rest of the way
  1122. return typeof(Map); // default for various objects
  1123. }
  1124. private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, GetLocal GetLocal)
  1125. {
  1126. var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize));
  1127. var valuel = GetLocal(srcType, 0);
  1128. var noCreate = il.DefineLabel();
  1129. il.Emit(OpCodes.Stloc, valuel);
  1130. EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_0));
  1131. il.Emit(OpCodes.Dup);
  1132. il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
  1133. il.Emit(OpCodes.Brtrue_S, noCreate);
  1134. il.Emit(OpCodes.Pop);
  1135. EmitCreateChildGenerated(il, member.Type);
  1136. il.MarkLabel(noCreate);
  1137. il.Emit(OpCodes.Dup);
  1138. il.Emit(OpCodes.Ldloc, valuel);
  1139. il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize);
  1140. }
  1141. private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal)
  1142. {
  1143. EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal);
  1144. il.Emit(OpCodes.Newobj, member.Nullable_Construct);
  1145. }
  1146. // top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType
  1147. // after, top of stack will be thing to write to field
  1148. private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, GetLocal GetLocal)
  1149. {
  1150. if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing
  1151. if (expected == typeof(Text))
  1152. {
  1153. var getter = expected.GetProperty(nameof(Text.Value)).GetGetMethod();
  1154. il.Emit(OpCodes.Call, getter);
  1155. if (targetType == typeof(char))
  1156. {
  1157. var strIndex = typeof(string).GetProperty("Chars").GetGetMethod(); // string's indexer is specially named Chars
  1158. il.Emit(OpCodes.Ldc_I4_0);
  1159. il.Emit(OpCodes.Call, strIndex);
  1160. }
  1161. }
  1162. else if (expected == typeof(Boolean))
  1163. {
  1164. var getter = expected.GetProperty(nameof(Boolean.Value)).GetGetMethod();
  1165. il.Emit(OpCodes.Call, getter);
  1166. }
  1167. else if (expected == typeof(Integer))
  1168. {
  1169. var getter = expected.GetProperty(nameof(Integer.Value)).GetGetMethod();
  1170. il.Emit(OpCodes.Call, getter);
  1171. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  1172. }
  1173. else if (expected == typeof(FloatingPoint))
  1174. {
  1175. var getter = expected.GetProperty(nameof(FloatingPoint.Value)).GetGetMethod();
  1176. il.Emit(OpCodes.Call, getter);
  1177. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  1178. } // TODO: implement stuff for lists and maps of various types (probably call out somewhere else to figure out what to do)
  1179. else if (expected == typeof(Map))
  1180. {
  1181. EmitDeserializeGeneratedValue(il, member, expected, GetLocal);
  1182. }
  1183. else
  1184. {
  1185. il.Emit(OpCodes.Pop);
  1186. il.Emit(OpCodes.Ldnull);
  1187. }
  1188. }
  1189. private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, GetLocal GetLocal)
  1190. {
  1191. var stlocal = GetLocal(typeof(Value));
  1192. var valLocal = GetLocal(member.Type);
  1193. il.Emit(OpCodes.Stloc, stlocal);
  1194. il.BeginExceptionBlock();
  1195. il.Emit(OpCodes.Ldsfld, member.ConverterField);
  1196. il.Emit(OpCodes.Ldloc, stlocal);
  1197. il.Emit(OpCodes.Ldarg_0);
  1198. if (member.IsGenericConverter)
  1199. {
  1200. var fromValue = member.ConverterBase.GetMethod(nameof(ValueConverter<int>.FromValue),
  1201. new[] { typeof(Value), typeof(object) });
  1202. il.Emit(OpCodes.Callvirt, fromValue);
  1203. }
  1204. else
  1205. {
  1206. var fromValue = typeof(IValueConverter).GetMethod(nameof(IValueConverter.FromValue),
  1207. new[] { typeof(Value), typeof(object) });
  1208. il.Emit(OpCodes.Callvirt, fromValue);
  1209. if (member.Type.IsValueType)
  1210. il.Emit(OpCodes.Unbox);
  1211. }
  1212. il.Emit(OpCodes.Stloc, valLocal);
  1213. il.BeginCatchBlock(typeof(Exception));
  1214. EmitWarnException(il, "Error occurred while deserializing");
  1215. il.Emit(OpCodes.Leave, nextLabel);
  1216. il.EndExceptionBlock();
  1217. il.Emit(OpCodes.Ldloc, valLocal);
  1218. }
  1219. // emit takes the value being deserialized, logs on error, leaves nothing on stack
  1220. private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, GetLocal GetLocal)
  1221. {
  1222. var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
  1223. var implLabel = il.DefineLabel();
  1224. var passedTypeCheck = il.DefineLabel();
  1225. var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type);
  1226. il.Emit(OpCodes.Dup);
  1227. il.Emit(OpCodes.Brtrue_S, implLabel); // null check
  1228. if (!member.AllowNull)
  1229. {
  1230. il.Emit(OpCodes.Pop);
  1231. EmitLogError(il, $"Member {member.Name} ({member.Type}) not nullable", tailcall: false,
  1232. expected: il => EmitTypeof(il, expectType));
  1233. il.Emit(OpCodes.Br, nextLabel);
  1234. }
  1235. else if (member.IsNullable)
  1236. {
  1237. il.Emit(OpCodes.Pop);
  1238. var valTLocal = GetLocal(member.Type, 0);
  1239. il.Emit(OpCodes.Ldloca, valTLocal);
  1240. il.Emit(OpCodes.Initobj, member.Type);
  1241. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal));
  1242. il.Emit(OpCodes.Br, nextLabel);
  1243. }
  1244. else
  1245. {
  1246. il.Emit(OpCodes.Pop);
  1247. EmitStore(il, member, il => il.Emit(OpCodes.Ldnull));
  1248. il.Emit(OpCodes.Br, nextLabel);
  1249. }
  1250. if (!member.HasConverter)
  1251. {
  1252. il.MarkLabel(implLabel);
  1253. il.Emit(OpCodes.Isinst, expectType); //replaces on stack
  1254. il.Emit(OpCodes.Dup); // duplicate cloned value
  1255. il.Emit(OpCodes.Brtrue, passedTypeCheck); // null check
  1256. }
  1257. var errorHandle = il.DefineLabel();
  1258. // special cases to handle coersion between Float and Int
  1259. if (member.HasConverter)
  1260. il.MarkLabel(implLabel);
  1261. else if (expectType == typeof(FloatingPoint))
  1262. {
  1263. var specialTypeCheck = il.DefineLabel();
  1264. il.Emit(OpCodes.Pop);
  1265. getValue(il);
  1266. il.Emit(OpCodes.Isinst, typeof(Integer)); //replaces on stack
  1267. il.Emit(OpCodes.Dup); // duplicate cloned value
  1268. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  1269. var Integer_CoerceToFloat = typeof(Integer).GetMethod(nameof(Integer.AsFloat));
  1270. il.Emit(OpCodes.Call, Integer_CoerceToFloat);
  1271. il.Emit(OpCodes.Br, passedTypeCheck);
  1272. }
  1273. else if (expectType == typeof(Integer))
  1274. {
  1275. var specialTypeCheck = il.DefineLabel();
  1276. il.Emit(OpCodes.Pop);
  1277. getValue(il);
  1278. il.Emit(OpCodes.Isinst, typeof(FloatingPoint)); //replaces on stack
  1279. il.Emit(OpCodes.Dup); // duplicate cloned value
  1280. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  1281. var Float_CoerceToInt = typeof(FloatingPoint).GetMethod(nameof(FloatingPoint.AsInteger));
  1282. il.Emit(OpCodes.Call, Float_CoerceToInt);
  1283. il.Emit(OpCodes.Br, passedTypeCheck);
  1284. }
  1285. if (!member.HasConverter)
  1286. {
  1287. il.MarkLabel(errorHandle);
  1288. il.Emit(OpCodes.Pop);
  1289. EmitLogError(il, $"Unexpected type deserializing {member.Name}", tailcall: false,
  1290. expected: il => EmitTypeof(il, expectType), found: il =>
  1291. {
  1292. getValue(il);
  1293. il.Emit(OpCodes.Callvirt, Object_GetType);
  1294. });
  1295. il.Emit(OpCodes.Br, nextLabel);
  1296. }
  1297. il.MarkLabel(passedTypeCheck);
  1298. var local = GetLocal(member.Type, 0);
  1299. if (member.HasConverter) EmitDeserializeConverter(il, member, nextLabel, GetLocal);
  1300. else if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal);
  1301. else EmitDeserializeValue(il, member, member.Type, expectType, GetLocal);
  1302. il.Emit(OpCodes.Stloc, local);
  1303. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local));
  1304. }
  1305. #endregion
  1306. }
  1307. }