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.

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