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.

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