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.

1231 lines
54 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 class Impl : IConfigStore
  103. {
  104. private IGeneratedStore generated;
  105. internal static ConstructorInfo Ctor = typeof(Impl).GetConstructor(new[] { typeof(IGeneratedStore) });
  106. public Impl(IGeneratedStore store) => generated = store;
  107. private readonly AutoResetEvent resetEvent = new AutoResetEvent(false);
  108. public WaitHandle SyncObject => resetEvent;
  109. internal static MethodInfo SyncObjectGetMethod = typeof(Impl).GetProperty(nameof(SyncObject)).GetGetMethod();
  110. public ReaderWriterLockSlim WriteSyncObject { get; } = new ReaderWriterLockSlim();
  111. internal static MethodInfo WriteSyncObjectGetMethod = typeof(Impl).GetProperty(nameof(WriteSyncObject)).GetGetMethod();
  112. internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged));
  113. public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged();
  114. public void SignalChanged() => resetEvent.Set();
  115. internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged));
  116. public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s).InvokeChanged();
  117. public void InvokeChanged() => generated.Changed();
  118. internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead));
  119. public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s).TakeRead();
  120. public void TakeRead() => WriteSyncObject.EnterReadLock();
  121. internal static MethodInfo ImplReleaseReadMethod = typeof(Impl).GetMethod(nameof(ImplReleaseRead));
  122. public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s).ReleaseRead();
  123. public void ReleaseRead() => WriteSyncObject.ExitReadLock();
  124. internal static MethodInfo ImplTakeWriteMethod = typeof(Impl).GetMethod(nameof(ImplTakeWrite));
  125. public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s).TakeWrite();
  126. public void TakeWrite() => WriteSyncObject.EnterWriteLock();
  127. internal static MethodInfo ImplReleaseWriteMethod = typeof(Impl).GetMethod(nameof(ImplReleaseWrite));
  128. public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s).ReleaseWrite();
  129. public void ReleaseWrite() => WriteSyncObject.ExitWriteLock();
  130. internal static MethodInfo FindImplMethod = typeof(Impl).GetMethod(nameof(FindImpl));
  131. public static Impl FindImpl(IGeneratedStore store)
  132. {
  133. while (store?.Parent != null) store = store.Parent; // walk to the top of the tree
  134. return store?.Impl;
  135. }
  136. internal static MethodInfo ReadFromMethod = typeof(Impl).GetMethod(nameof(ReadFrom));
  137. public void ReadFrom(IConfigProvider provider)
  138. {
  139. var values = provider.Load();
  140. Logger.config.Debug("Generated impl ReadFrom");
  141. Logger.config.Debug($"Read {values}");
  142. generated.Deserialize(values);
  143. ReleaseWrite();
  144. generated.OnReload();
  145. TakeWrite(); // must take again for runtime to be happy (which is unfortunate)
  146. }
  147. internal static MethodInfo WriteToMethod = typeof(Impl).GetMethod(nameof(WriteTo));
  148. public void WriteTo(IConfigProvider provider)
  149. {
  150. var values = generated.Serialize();
  151. Logger.config.Debug("Generated impl WriteTo");
  152. Logger.config.Debug($"Serialized {values}");
  153. provider.Store(values);
  154. }
  155. }
  156. private static Dictionary<Type, Func<IGeneratedStore, IConfigStore>> generatedCreators = new Dictionary<Type, Func<IGeneratedStore, IConfigStore>>();
  157. private static Dictionary<Type, Dictionary<string, Type>> memberMaps = new Dictionary<Type, Dictionary<string, Type>>();
  158. public static T Create<T>() where T : class => (T)Create(typeof(T));
  159. public static IConfigStore Create(Type type) => Create(type, null);
  160. private static readonly MethodInfo CreateGParent =
  161. typeof(GeneratedStore).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Static, null,
  162. CallingConventions.Any, new[] { typeof(IGeneratedStore) }, Array.Empty<ParameterModifier>());
  163. internal static T Create<T>(IGeneratedStore parent) where T : class => (T)Create(typeof(T), parent);
  164. private static IConfigStore Create(Type type, IGeneratedStore parent)
  165. {
  166. if (generatedCreators.TryGetValue(type, out var creator))
  167. return creator(parent);
  168. else
  169. {
  170. creator = MakeCreator(type);
  171. generatedCreators.Add(type, creator);
  172. return creator(parent);
  173. }
  174. }
  175. internal const string GeneratedAssemblyName = "IPA.Config.Generated";
  176. private static AssemblyBuilder assembly = null;
  177. private static AssemblyBuilder Assembly
  178. {
  179. get
  180. {
  181. if (assembly == null)
  182. {
  183. var name = new AssemblyName(GeneratedAssemblyName);
  184. assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
  185. }
  186. return assembly;
  187. }
  188. }
  189. internal static void DebugSaveAssembly(string file)
  190. {
  191. Assembly.Save(file);
  192. }
  193. private static ModuleBuilder module = null;
  194. private static ModuleBuilder Module
  195. {
  196. get
  197. {
  198. if (module == null)
  199. module = Assembly.DefineDynamicModule(Assembly.GetName().Name, Assembly.GetName().Name + ".dll");
  200. return module;
  201. }
  202. }
  203. private struct SerializedMemberInfo
  204. {
  205. public string Name;
  206. public MemberInfo Member;
  207. public Type Type;
  208. public bool AllowNull;
  209. public bool IsVirtual;
  210. public bool IsField;
  211. public bool IsNullable; // signifies whether this is a Nullable<T>
  212. public bool IsGenericConverter; // used so we can call directly to the generic version if it is
  213. public Type Converter;
  214. public Type ConverterTarget;
  215. // invalid for objects with IsNullabe false
  216. public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
  217. // invalid for objects with IsNullabe false
  218. public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
  219. // invalid for objects with IsNullabe false
  220. public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable<int>.Value));
  221. // invalid for objects with IsNullabe false
  222. public ConstructorInfo Nullable_Construct => Type.GetConstructor(new[] { NullableWrappedType });
  223. }
  224. private static Func<IGeneratedStore, IConfigStore> MakeCreator(Type type)
  225. {
  226. var baseCtor = type.GetConstructor(Type.EmptyTypes); // get a default constructor
  227. if (baseCtor == null)
  228. throw new ArgumentException("Config type does not have a public parameterless constructor");
  229. var typeBuilder = Module.DefineType($"{type.FullName}<Generated>",
  230. TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, type);
  231. var typeField = typeBuilder.DefineField("<>_type", typeof(Type), FieldAttributes.Private | FieldAttributes.InitOnly);
  232. var implField = typeBuilder.DefineField("<>_impl", typeof(Impl), FieldAttributes.Private | FieldAttributes.InitOnly);
  233. var parentField = typeBuilder.DefineField("<>_parent", typeof(IGeneratedStore), FieldAttributes.Private | FieldAttributes.InitOnly);
  234. // none of this can be Expressions because CompileToMethod requires a static target method for some dumbass reason
  235. #region Parse base object structure
  236. var baseChanged = type.GetMethod("Changed", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
  237. if (baseChanged != null && !baseChanged.IsVirtual) baseChanged = null; // limit this to just the one thing
  238. var baseOnReload = type.GetMethod("OnReload", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
  239. if (baseOnReload != null && !baseOnReload.IsVirtual) baseOnReload = null; // limit this to just the one thing
  240. var structure = new List<SerializedMemberInfo>();
  241. // TODO: support converters
  242. static bool ProcessAttributesFor(ref SerializedMemberInfo member)
  243. {
  244. var attrs = member.Member.GetCustomAttributes(true);
  245. var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull();
  246. if (ignores.Any()) // we ignore
  247. {
  248. return false;
  249. }
  250. var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull();
  251. member.Name = member.Member.Name;
  252. member.IsNullable = member.Type.IsGenericType
  253. && member.Type.GetGenericTypeDefinition() == typeof(Nullable<>);
  254. member.AllowNull = !nonNullables.Any() && (!member.Type.IsValueType || member.IsNullable);
  255. var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault();
  256. if (nameAttr != null)
  257. member.Name = nameAttr.Name;
  258. var converterAttr = attrs.Select(o => o as UseConverterAttribute).NonNull().FirstOrDefault();
  259. if (converterAttr != null)
  260. {
  261. member.Converter = converterAttr.ConverterType;
  262. member.ConverterTarget = converterAttr.ConverterTargetType;
  263. member.IsGenericConverter = member.ConverterTarget != null;
  264. }
  265. return true;
  266. }
  267. // only looks at public/protected properties
  268. foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  269. {
  270. if (prop.GetSetMethod(true)?.IsPrivate ?? true)
  271. { // we enter this block if the setter is inacessible or doesn't exist
  272. continue; // ignore props without setter
  273. }
  274. if (prop.GetGetMethod(true)?.IsPrivate ?? true)
  275. { // we enter this block if the getter is inacessible or doesn't exist
  276. continue; // ignore props without getter
  277. }
  278. var smi = new SerializedMemberInfo
  279. {
  280. Member = prop,
  281. IsVirtual = (prop.GetGetMethod(true)?.IsVirtual ?? false) ||
  282. (prop.GetSetMethod(true)?.IsVirtual ?? false),
  283. IsField = false,
  284. Type = prop.PropertyType
  285. };
  286. if (!ProcessAttributesFor(ref smi)) continue;
  287. structure.Add(smi);
  288. }
  289. // only look at public/protected fields
  290. foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  291. {
  292. if (field.IsPrivate) continue;
  293. var smi = new SerializedMemberInfo
  294. {
  295. Member = field,
  296. IsVirtual = false,
  297. IsField = true,
  298. Type = field.FieldType
  299. };
  300. if (!ProcessAttributesFor(ref smi)) continue;
  301. structure.Add(smi);
  302. }
  303. #endregion
  304. #region Constructor
  305. var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(IGeneratedStore) });
  306. { // because this is a constructor, it has to be raw IL
  307. var il = ctor.GetILGenerator();
  308. il.Emit(OpCodes.Ldarg_0); // keep this at bottom of stack
  309. il.Emit(OpCodes.Dup);
  310. il.Emit(OpCodes.Call, baseCtor);
  311. il.Emit(OpCodes.Dup);
  312. il.Emit(OpCodes.Ldarg_1); // load parent
  313. il.Emit(OpCodes.Stfld, parentField);
  314. il.Emit(OpCodes.Dup);
  315. EmitTypeof(il, type);
  316. il.Emit(OpCodes.Stfld, typeField);
  317. il.Emit(OpCodes.Dup);
  318. il.Emit(OpCodes.Dup);
  319. il.Emit(OpCodes.Newobj, Impl.Ctor);
  320. il.Emit(OpCodes.Stfld, implField);
  321. var GetLocal = MakeGetLocal(il);
  322. foreach (var member in structure)
  323. {
  324. EmitMemberFix(il, member, GetLocal);
  325. }
  326. il.Emit(OpCodes.Pop);
  327. il.Emit(OpCodes.Ret);
  328. }
  329. #endregion
  330. const MethodAttributes propertyMethodAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
  331. const MethodAttributes virtualPropertyMethodAttr = propertyMethodAttr | MethodAttributes.Virtual | MethodAttributes.Final;
  332. const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final;
  333. #region IGeneratedStore
  334. typeBuilder.AddInterfaceImplementation(typeof(IGeneratedStore));
  335. var IGeneratedStore_t = typeof(IGeneratedStore);
  336. var IGeneratedStore_GetImpl = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Impl)).GetGetMethod();
  337. var IGeneratedStore_GetType = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Type)).GetGetMethod();
  338. var IGeneratedStore_GetParent = IGeneratedStore_t.GetProperty(nameof(IGeneratedStore.Parent)).GetGetMethod();
  339. var IGeneratedStore_Serialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Serialize));
  340. var IGeneratedStore_Deserialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Deserialize));
  341. var IGeneratedStore_OnReload = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.OnReload));
  342. var IGeneratedStore_Changed = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Changed));
  343. #region IGeneratedStore.OnReload
  344. var onReload = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.OnReload)}", virtualMemberMethod, null, Type.EmptyTypes);
  345. typeBuilder.DefineMethodOverride(onReload, IGeneratedStore_OnReload);
  346. if (baseOnReload != null) typeBuilder.DefineMethodOverride(onReload, baseOnReload);
  347. {
  348. var il = onReload.GetILGenerator();
  349. if (baseOnReload != null)
  350. {
  351. il.Emit(OpCodes.Ldarg_0); // load this
  352. il.Emit(OpCodes.Tailcall);
  353. il.Emit(OpCodes.Call, baseOnReload); // load impl field
  354. }
  355. il.Emit(OpCodes.Ret);
  356. }
  357. #endregion
  358. #region IGeneratedStore.Impl
  359. var implProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Impl), PropertyAttributes.None, typeof(Impl), null);
  360. var implPropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Impl)}", virtualPropertyMethodAttr, implProp.PropertyType, Type.EmptyTypes);
  361. implProp.SetGetMethod(implPropGet);
  362. typeBuilder.DefineMethodOverride(implPropGet, IGeneratedStore_GetImpl);
  363. {
  364. var il = implPropGet.GetILGenerator();
  365. il.Emit(OpCodes.Ldarg_0); // load this
  366. il.Emit(OpCodes.Ldfld, implField); // load impl field
  367. il.Emit(OpCodes.Ret);
  368. }
  369. #endregion
  370. #region IGeneratedStore.Type
  371. var typeProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Type), PropertyAttributes.None, typeof(Type), null);
  372. var typePropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Type)}", virtualPropertyMethodAttr, typeProp.PropertyType, Type.EmptyTypes);
  373. typeProp.SetGetMethod(typePropGet);
  374. typeBuilder.DefineMethodOverride(typePropGet, IGeneratedStore_GetType);
  375. {
  376. var il = typePropGet.GetILGenerator();
  377. il.Emit(OpCodes.Ldarg_0); // load this
  378. il.Emit(OpCodes.Ldfld, typeField); // load impl field
  379. il.Emit(OpCodes.Ret);
  380. }
  381. #endregion
  382. #region IGeneratedStore.Parent
  383. var parentProp = typeBuilder.DefineProperty(nameof(IGeneratedStore.Parent), PropertyAttributes.None, typeof(IGeneratedStore), null);
  384. var parentPropGet = typeBuilder.DefineMethod($"<g>{nameof(IGeneratedStore.Parent)}", virtualPropertyMethodAttr, parentProp.PropertyType, Type.EmptyTypes);
  385. parentProp.SetGetMethod(parentPropGet);
  386. typeBuilder.DefineMethodOverride(parentPropGet, IGeneratedStore_GetParent);
  387. {
  388. var il = parentPropGet.GetILGenerator();
  389. il.Emit(OpCodes.Ldarg_0); // load this
  390. il.Emit(OpCodes.Ldfld, parentField); // load impl field
  391. il.Emit(OpCodes.Ret);
  392. }
  393. #endregion
  394. #region IGeneratedStore.Serialize
  395. var serializeGen = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.Serialize)}", virtualPropertyMethodAttr, IGeneratedStore_Serialize.ReturnType, Type.EmptyTypes);
  396. typeBuilder.DefineMethodOverride(serializeGen, IGeneratedStore_Serialize);
  397. { // this is non-locking because the only code that will call this will already own the correct lock
  398. var il = serializeGen.GetILGenerator();
  399. var Map_Add = typeof(Map).GetMethod(nameof(Map.Add));
  400. il.Emit(OpCodes.Call, typeof(Value).GetMethod(nameof(Value.Map)));
  401. // the map is now at the top of the stack
  402. var GetLocal = MakeGetLocal(il);
  403. foreach (var member in structure)
  404. {
  405. il.Emit(OpCodes.Dup);
  406. il.Emit(OpCodes.Ldstr, member.Name); // TODO: make this behave with annotations
  407. EmitSerializeMember(il, member, GetLocal);
  408. il.Emit(OpCodes.Call, Map_Add);
  409. }
  410. // the map is still at the top of the stack, return it
  411. il.Emit(OpCodes.Ret);
  412. }
  413. #endregion
  414. #region IGeneratedStore.Deserialize
  415. var deserializeGen = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.Deserialize)}", virtualPropertyMethodAttr, null,
  416. new[] { IGeneratedStore_Deserialize.GetParameters()[0].ParameterType });
  417. typeBuilder.DefineMethodOverride(deserializeGen, IGeneratedStore_Deserialize);
  418. { // this is non-locking because the only code that will call this will already own the correct lock
  419. var il = deserializeGen.GetILGenerator();
  420. var Map_t = typeof(Map);
  421. var Map_TryGetValue = Map_t.GetMethod(nameof(Map.TryGetValue));
  422. var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
  423. var valueLocal = il.DeclareLocal(typeof(Value));
  424. var nonNull = il.DefineLabel();
  425. il.Emit(OpCodes.Ldarg_1);
  426. il.Emit(OpCodes.Brtrue, nonNull);
  427. EmitLogError(il, "Attempting to deserialize null", tailcall: true);
  428. il.Emit(OpCodes.Ret);
  429. il.MarkLabel(nonNull);
  430. il.Emit(OpCodes.Ldarg_1);
  431. il.Emit(OpCodes.Isinst, Map_t);
  432. il.Emit(OpCodes.Dup); // duplicate cloned value
  433. var notMapError = il.DefineLabel();
  434. il.Emit(OpCodes.Brtrue, notMapError);
  435. // handle error
  436. il.Emit(OpCodes.Pop); // removes the duplicate value
  437. EmitLogError(il, $"Invalid root for deserializing {type.FullName}", tailcall: true,
  438. expected: il => EmitTypeof(il, Map_t), found: il =>
  439. {
  440. il.Emit(OpCodes.Ldarg_1);
  441. il.Emit(OpCodes.Callvirt, Object_GetType);
  442. });
  443. il.Emit(OpCodes.Ret);
  444. var nextLabel = notMapError;
  445. var GetLocal = MakeGetLocal(il);
  446. // head of stack is Map instance
  447. foreach (var member in structure)
  448. {
  449. il.MarkLabel(nextLabel);
  450. nextLabel = il.DefineLabel();
  451. var endErrorLabel = il.DefineLabel();
  452. il.Emit(OpCodes.Dup);
  453. il.Emit(OpCodes.Ldstr, member.Name);
  454. il.Emit(OpCodes.Ldloca_S, valueLocal);
  455. il.Emit(OpCodes.Call, Map_TryGetValue);
  456. il.Emit(OpCodes.Brtrue_S, endErrorLabel);
  457. EmitLogError(il, $"Missing key {member.Name}", tailcall: false);
  458. il.Emit(OpCodes.Br, nextLabel);
  459. il.MarkLabel(endErrorLabel);
  460. il.Emit(OpCodes.Ldloc_S, valueLocal);
  461. EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal);
  462. }
  463. il.MarkLabel(nextLabel);
  464. il.Emit(OpCodes.Pop); // removes the duplicate value
  465. il.Emit(OpCodes.Ret);
  466. }
  467. #endregion
  468. #endregion
  469. #region IConfigStore
  470. typeBuilder.AddInterfaceImplementation(typeof(IConfigStore));
  471. var IConfigStore_t = typeof(IConfigStore);
  472. var IConfigStore_GetSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.SyncObject)).GetGetMethod();
  473. var IConfigStore_GetWriteSyncObject = IConfigStore_t.GetProperty(nameof(IConfigStore.WriteSyncObject)).GetGetMethod();
  474. var IConfigStore_WriteTo = IConfigStore_t.GetMethod(nameof(IConfigStore.WriteTo));
  475. var IConfigStore_ReadFrom = IConfigStore_t.GetMethod(nameof(IConfigStore.ReadFrom));
  476. #region IConfigStore.SyncObject
  477. var syncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.SyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
  478. var syncObjPropGet = typeBuilder.DefineMethod($"<g>{nameof(IConfigStore.SyncObject)}", virtualPropertyMethodAttr, syncObjProp.PropertyType, Type.EmptyTypes);
  479. syncObjProp.SetGetMethod(syncObjPropGet);
  480. typeBuilder.DefineMethodOverride(syncObjPropGet, IConfigStore_GetSyncObject);
  481. {
  482. var il = syncObjPropGet.GetILGenerator();
  483. il.Emit(OpCodes.Ldarg_0);
  484. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  485. il.Emit(OpCodes.Tailcall);
  486. il.Emit(OpCodes.Call, Impl.SyncObjectGetMethod);
  487. il.Emit(OpCodes.Ret);
  488. }
  489. #endregion
  490. #region IConfigStore.WriteSyncObject
  491. var writeSyncObjProp = typeBuilder.DefineProperty(nameof(IConfigStore.WriteSyncObject), PropertyAttributes.None, typeof(WaitHandle), null);
  492. var writeSyncObjPropGet = typeBuilder.DefineMethod($"<g>{nameof(IConfigStore.WriteSyncObject)}", virtualPropertyMethodAttr, writeSyncObjProp.PropertyType, Type.EmptyTypes);
  493. writeSyncObjProp.SetGetMethod(writeSyncObjPropGet);
  494. typeBuilder.DefineMethodOverride(writeSyncObjPropGet, IConfigStore_GetWriteSyncObject);
  495. {
  496. var il = writeSyncObjPropGet.GetILGenerator();
  497. il.Emit(OpCodes.Ldarg_0);
  498. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  499. il.Emit(OpCodes.Tailcall);
  500. il.Emit(OpCodes.Call, Impl.WriteSyncObjectGetMethod);
  501. il.Emit(OpCodes.Ret);
  502. }
  503. #endregion
  504. #region IConfigStore.WriteTo
  505. var writeTo = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.WriteTo)}", virtualMemberMethod, null, new[] { typeof(IConfigProvider) });
  506. typeBuilder.DefineMethodOverride(writeTo, IConfigStore_WriteTo);
  507. {
  508. var il = writeTo.GetILGenerator();
  509. il.Emit(OpCodes.Ldarg_0);
  510. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  511. il.Emit(OpCodes.Ldarg_1);
  512. il.Emit(OpCodes.Tailcall);
  513. il.Emit(OpCodes.Call, Impl.WriteToMethod);
  514. il.Emit(OpCodes.Ret);
  515. }
  516. #endregion
  517. #region IConfigStore.ReadFrom
  518. var readFrom = typeBuilder.DefineMethod($"<>{nameof(IConfigStore.ReadFrom)}", virtualMemberMethod, null, new[] { typeof(IConfigProvider) });
  519. typeBuilder.DefineMethodOverride(readFrom, IConfigStore_ReadFrom);
  520. {
  521. var il = readFrom.GetILGenerator();
  522. il.Emit(OpCodes.Ldarg_0);
  523. il.Emit(OpCodes.Call, Impl.FindImplMethod);
  524. il.Emit(OpCodes.Ldarg_1);
  525. il.Emit(OpCodes.Tailcall);
  526. il.Emit(OpCodes.Call, Impl.ReadFromMethod);
  527. il.Emit(OpCodes.Ret);
  528. }
  529. #endregion
  530. #endregion
  531. #region Changed
  532. var coreChanged = typeBuilder.DefineMethod(
  533. "<>Changed",
  534. virtualMemberMethod,
  535. null, Type.EmptyTypes);
  536. {
  537. var il = coreChanged.GetILGenerator();
  538. il.Emit(OpCodes.Ldarg_0);
  539. il.Emit(OpCodes.Call, Impl.ImplSignalChangedMethod);
  540. il.Emit(OpCodes.Ret); // simply call our impl's SignalChanged method and return
  541. }
  542. if (baseChanged != null) {
  543. var changedMethod = typeBuilder.DefineMethod( // copy to override baseChanged
  544. baseChanged.Name,
  545. virtualMemberMethod,
  546. null, Type.EmptyTypes);
  547. typeBuilder.DefineMethodOverride(changedMethod, baseChanged);
  548. {
  549. var il = changedMethod.GetILGenerator();
  550. il.Emit(OpCodes.Ldarg_0);
  551. il.Emit(OpCodes.Call, baseChanged); // call base
  552. il.Emit(OpCodes.Ldarg_0);
  553. il.Emit(OpCodes.Tailcall);
  554. il.Emit(OpCodes.Call, coreChanged); // call back to the core change method
  555. il.Emit(OpCodes.Ret);
  556. }
  557. coreChanged = changedMethod; // switch to calling this version instead of just the default
  558. }
  559. typeBuilder.DefineMethodOverride(coreChanged, IGeneratedStore_Changed);
  560. #endregion
  561. // TODO: generate overrides for all the virtual properties
  562. foreach (var member in structure.Where(m => m.IsVirtual))
  563. { // IsVirtual implies !IsField
  564. var prop = member.Member as PropertyInfo;
  565. var get = prop.GetGetMethod(true);
  566. var set = prop.GetSetMethod(true);
  567. var propBuilder = typeBuilder.DefineProperty($"{member.Name}#", PropertyAttributes.None, member.Type, null);
  568. var propGet = typeBuilder.DefineMethod($"<g>{propBuilder.Name}", virtualPropertyMethodAttr, member.Type, Type.EmptyTypes);
  569. propBuilder.SetGetMethod(propGet);
  570. typeBuilder.DefineMethodOverride(propGet, get);
  571. {
  572. var il = propGet.GetILGenerator();
  573. var local = il.DeclareLocal(member.Type);
  574. il.Emit(OpCodes.Ldarg_0);
  575. il.Emit(OpCodes.Call, Impl.ImplTakeReadMethod); // take the read lock
  576. il.BeginExceptionBlock();
  577. il.Emit(OpCodes.Ldarg_0);
  578. il.Emit(OpCodes.Call, get); // call base getter
  579. il.Emit(OpCodes.Stloc, local);
  580. il.BeginFinallyBlock();
  581. il.Emit(OpCodes.Ldarg_0);
  582. il.Emit(OpCodes.Call, Impl.ImplReleaseReadMethod); // release the read lock
  583. il.EndExceptionBlock();
  584. il.Emit(OpCodes.Ldloc, local);
  585. il.Emit(OpCodes.Ret);
  586. }
  587. var propSet = typeBuilder.DefineMethod($"<s>{propBuilder.Name}", virtualPropertyMethodAttr, null, new[] { member.Type });
  588. propBuilder.SetSetMethod(propSet);
  589. typeBuilder.DefineMethodOverride(propSet, set);
  590. { // TODO: decide if i want to correct the value before or after i take the write lock
  591. var il = propSet.GetILGenerator();
  592. il.Emit(OpCodes.Ldarg_0);
  593. il.Emit(OpCodes.Call, Impl.ImplTakeWriteMethod); // take the write lock
  594. il.BeginExceptionBlock();
  595. il.Emit(OpCodes.Ldarg_0);
  596. il.Emit(OpCodes.Ldarg_1);
  597. EmitCorrectMember(il, member);
  598. il.Emit(OpCodes.Call, set);
  599. il.BeginFinallyBlock();
  600. il.Emit(OpCodes.Ldarg_0);
  601. il.Emit(OpCodes.Call, Impl.ImplReleaseWriteMethod); // release the write lock
  602. il.EndExceptionBlock();
  603. il.Emit(OpCodes.Ldarg_0);
  604. il.Emit(OpCodes.Call, Impl.ImplInvokeChangedMethod);
  605. il.Emit(OpCodes.Ret);
  606. }
  607. }
  608. var genType = typeBuilder.CreateType();
  609. var parentParam = Expression.Parameter(typeof(IGeneratedStore), "parent");
  610. var creatorDel = Expression.Lambda<Func<IGeneratedStore, IConfigStore>>(
  611. Expression.New(ctor, parentParam), parentParam
  612. ).Compile();
  613. { // register a member map
  614. var dict = new Dictionary<string, Type>();
  615. foreach (var member in structure)
  616. dict.Add(member.Name, member.Type);
  617. memberMaps.Add(type, dict);
  618. }
  619. return creatorDel;
  620. }
  621. private delegate LocalBuilder GetLocal(Type type, int idx = 0);
  622. private static GetLocal MakeGetLocal(ILGenerator il)
  623. { // TODO: improve this shit a bit so that i can release a hold of a variable and do more auto managing
  624. var locals = new List<LocalBuilder>();
  625. LocalBuilder GetLocal(Type ty, int i = 0)
  626. { // TODO: pull this garbage out somewhere sane somehow
  627. var builder = locals.Where(b => b.LocalType == ty).Skip(i).FirstOrDefault();
  628. if (builder == null)
  629. {
  630. builder = il.DeclareLocal(ty);
  631. locals.Add(builder);
  632. }
  633. return builder;
  634. }
  635. return GetLocal;
  636. }
  637. private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static);
  638. internal static void LogError(Type expected, Type found, string message)
  639. {
  640. Logger.config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}");
  641. }
  642. private static bool NeedsCorrection(SerializedMemberInfo member)
  643. {
  644. return false;
  645. }
  646. // expects start value on stack, exits with final value on stack
  647. private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member)
  648. {
  649. if (!NeedsCorrection(member)) return;
  650. // TODO: impl
  651. }
  652. // expects the this param to be on the stack
  653. private static void EmitMemberFix(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal)
  654. {
  655. if (!NeedsCorrection(member)) return;
  656. var local = GetLocal(member.Type);
  657. EmitLoad(il, member); // load the member
  658. EmitCorrectMember(il, member); // correct it
  659. il.Emit(OpCodes.Stloc, local);
  660. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local));
  661. }
  662. #region Utility
  663. private static void EmitLoad(ILGenerator il, SerializedMemberInfo member)
  664. {
  665. il.Emit(OpCodes.Ldarg_0); // load this
  666. if (member.IsField)
  667. il.Emit(OpCodes.Ldfld, member.Member as FieldInfo);
  668. else
  669. { // member is a property
  670. var prop = member.Member as PropertyInfo;
  671. var getter = prop.GetGetMethod();
  672. if (getter == null) throw new InvalidOperationException($"Property {member.Name} does not have a getter and is not ignored");
  673. il.Emit(OpCodes.Call, getter);
  674. }
  675. }
  676. private static void EmitStore(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> value)
  677. {
  678. il.Emit(OpCodes.Ldarg_0); // load this
  679. value(il);
  680. if (member.IsField)
  681. il.Emit(OpCodes.Stfld, member.Member as FieldInfo);
  682. else
  683. { // member is a property
  684. var prop = member.Member as PropertyInfo;
  685. var setter = prop.GetSetMethod();
  686. if (setter == null) throw new InvalidOperationException($"Property {member.Name} does not have a setter and is not ignored");
  687. il.Emit(OpCodes.Call, setter);
  688. }
  689. }
  690. private static void EmitLogError(ILGenerator il, string message, bool tailcall = false, Action<ILGenerator> expected = null, Action<ILGenerator> found = null)
  691. {
  692. if (expected == null) expected = il => il.Emit(OpCodes.Ldnull);
  693. if (found == null) found = il => il.Emit(OpCodes.Ldnull);
  694. expected(il);
  695. found(il);
  696. il.Emit(OpCodes.Ldstr, message);
  697. if (tailcall) il.Emit(OpCodes.Tailcall);
  698. il.Emit(OpCodes.Call, LogErrorMethod);
  699. }
  700. private static readonly MethodInfo Type_GetTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle));
  701. private static void EmitTypeof(ILGenerator il, Type type)
  702. {
  703. il.Emit(OpCodes.Ldtoken, type);
  704. il.Emit(OpCodes.Call, Type_GetTypeFromHandle);
  705. }
  706. private static Type Decimal_t = typeof(decimal);
  707. private static ConstructorInfo Decimal_FromFloat = Decimal_t.GetConstructor(new[] { typeof(float) });
  708. private static ConstructorInfo Decimal_FromDouble = Decimal_t.GetConstructor(new[] { typeof(double) });
  709. private static ConstructorInfo Decimal_FromInt = Decimal_t.GetConstructor(new[] { typeof(int) });
  710. private static ConstructorInfo Decimal_FromUInt = Decimal_t.GetConstructor(new[] { typeof(uint) });
  711. private static ConstructorInfo Decimal_FromLong = Decimal_t.GetConstructor(new[] { typeof(long) });
  712. private static ConstructorInfo Decimal_FromULong = Decimal_t.GetConstructor(new[] { typeof(ulong) });
  713. private static void EmitNumberConvertTo(ILGenerator il, Type to, Type from)
  714. { // WARNING: THIS USES THE NO-OVERFLOW OPCODES
  715. if (to == from) return;
  716. if (to == Decimal_t)
  717. {
  718. if (from == typeof(float)) il.Emit(OpCodes.Newobj, Decimal_FromFloat);
  719. else if (from == typeof(double)) il.Emit(OpCodes.Newobj, Decimal_FromDouble);
  720. else if (from == typeof(long)) il.Emit(OpCodes.Newobj, Decimal_FromLong);
  721. else if (from == typeof(ulong)) il.Emit(OpCodes.Newobj, Decimal_FromULong);
  722. else if (from == typeof(int)) il.Emit(OpCodes.Newobj, Decimal_FromInt);
  723. else if (from == typeof(uint)) il.Emit(OpCodes.Newobj, Decimal_FromUInt);
  724. else if (from == typeof(IntPtr))
  725. {
  726. EmitNumberConvertTo(il, typeof(long), from);
  727. EmitNumberConvertTo(il, to, typeof(long));
  728. }
  729. else if (from == typeof(UIntPtr))
  730. {
  731. EmitNumberConvertTo(il, typeof(ulong), from);
  732. EmitNumberConvertTo(il, to, typeof(ulong));
  733. }
  734. else
  735. { // if the source is anything else, we first convert to int because that can contain all other values
  736. EmitNumberConvertTo(il, typeof(int), from);
  737. EmitNumberConvertTo(il, to, typeof(int));
  738. };
  739. }
  740. else if (from == Decimal_t)
  741. {
  742. if (to == typeof(IntPtr))
  743. {
  744. EmitNumberConvertTo(il, typeof(long), from);
  745. EmitNumberConvertTo(il, to, typeof(long));
  746. }
  747. else if (to == typeof(UIntPtr))
  748. {
  749. EmitNumberConvertTo(il, typeof(ulong), from);
  750. EmitNumberConvertTo(il, to, typeof(ulong));
  751. }
  752. else
  753. {
  754. var method = Decimal_t.GetMethod($"To{to.Name}"); // conveniently, this is the pattern of the to* names
  755. il.Emit(OpCodes.Call, method);
  756. }
  757. }
  758. else if (to == typeof(IntPtr)) il.Emit(OpCodes.Conv_I);
  759. else if (to == typeof(UIntPtr)) il.Emit(OpCodes.Conv_U);
  760. else if (to == typeof(sbyte)) il.Emit(OpCodes.Conv_I1);
  761. else if (to == typeof(byte)) il.Emit(OpCodes.Conv_U1);
  762. else if (to == typeof(short)) il.Emit(OpCodes.Conv_I2);
  763. else if (to == typeof(ushort)) il.Emit(OpCodes.Conv_U2);
  764. else if (to == typeof(int)) il.Emit(OpCodes.Conv_I4);
  765. else if (to == typeof(uint)) il.Emit(OpCodes.Conv_U4);
  766. else if (to == typeof(long)) il.Emit(OpCodes.Conv_I8);
  767. else if (to == typeof(ulong)) il.Emit(OpCodes.Conv_U8);
  768. else if (to == typeof(float))
  769. {
  770. if (from == typeof(byte)
  771. || from == typeof(ushort)
  772. || from == typeof(uint)
  773. || from == typeof(ulong)
  774. || from == typeof(UIntPtr)) il.Emit(OpCodes.Conv_R_Un);
  775. il.Emit(OpCodes.Conv_R4);
  776. }
  777. else if (to == typeof(double))
  778. {
  779. if (from == typeof(byte)
  780. || from == typeof(ushort)
  781. || from == typeof(uint)
  782. || from == typeof(ulong)
  783. || from == typeof(UIntPtr)) il.Emit(OpCodes.Conv_R_Un);
  784. il.Emit(OpCodes.Conv_R8);
  785. }
  786. }
  787. private static void EmitCreateChildGenerated(ILGenerator il, Type childType)
  788. {
  789. var method = CreateGParent.MakeGenericMethod(childType);
  790. il.Emit(OpCodes.Ldarg_0);
  791. il.Emit(OpCodes.Call, method);
  792. }
  793. #endregion
  794. #region Serialize
  795. // emit takes no args, leaves Value at top of stack
  796. private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal)
  797. {
  798. EmitLoad(il, member);
  799. var endSerialize = il.DefineLabel();
  800. if (member.AllowNull)
  801. {
  802. var passedNull = il.DefineLabel();
  803. il.Emit(OpCodes.Dup);
  804. if (member.IsNullable)
  805. il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod());
  806. il.Emit(OpCodes.Brtrue, passedNull);
  807. il.Emit(OpCodes.Pop);
  808. il.Emit(OpCodes.Ldnull);
  809. il.Emit(OpCodes.Br, endSerialize);
  810. il.MarkLabel(passedNull);
  811. }
  812. if (member.IsNullable)
  813. il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod());
  814. var targetType = GetExpectedValueTypeForType(member.Type);
  815. if (targetType == typeof(Text))
  816. { // only happens when arg is a string or char
  817. var TextCreate = typeof(Value).GetMethod(nameof(Value.Text));
  818. if (member.Type == typeof(char))
  819. {
  820. var strFromChar = typeof(char).GetMethod(nameof(char.ToString), new[] { typeof(char) });
  821. il.Emit(OpCodes.Call, strFromChar);
  822. }
  823. il.Emit(OpCodes.Call, TextCreate);
  824. }
  825. else if (targetType == typeof(Boolean))
  826. {
  827. var BoolCreate = typeof(Value).GetMethod(nameof(Value.Bool));
  828. il.Emit(OpCodes.Call, BoolCreate);
  829. }
  830. else if (targetType == typeof(Integer))
  831. {
  832. var IntCreate = typeof(Value).GetMethod(nameof(Value.Integer));
  833. EmitNumberConvertTo(il, IntCreate.GetParameters()[0].ParameterType, member.Type);
  834. il.Emit(OpCodes.Call, IntCreate);
  835. }
  836. else if (targetType == typeof(FloatingPoint))
  837. {
  838. var FloatCreate = typeof(Value).GetMethod(nameof(Value.Float));
  839. EmitNumberConvertTo(il, FloatCreate.GetParameters()[0].ParameterType, member.Type);
  840. il.Emit(OpCodes.Call, FloatCreate);
  841. }
  842. else if (targetType == typeof(List))
  843. {
  844. // TODO: impl this (enumerables)
  845. il.Emit(OpCodes.Pop);
  846. il.Emit(OpCodes.Ldnull);
  847. }
  848. else if (targetType == typeof(Map))
  849. {
  850. // TODO: support other aggregate types
  851. // for now, we assume that its a generated type implementing IGeneratedStore
  852. var IGeneratedStore_Serialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Serialize));
  853. il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize);
  854. }
  855. il.MarkLabel(endSerialize);
  856. // TODO: implement converters
  857. }
  858. #endregion
  859. #region Deserialize
  860. private static Type GetExpectedValueTypeForType(Type valT)
  861. {
  862. if (typeof(Value).IsAssignableFrom(valT)) // this is a Value subtype
  863. return valT;
  864. if (valT == typeof(string)
  865. || valT == typeof(char)) return typeof(Text);
  866. if (valT == typeof(bool)) return typeof(Boolean);
  867. if (valT == typeof(byte)
  868. || valT == typeof(sbyte)
  869. || valT == typeof(short)
  870. || valT == typeof(ushort)
  871. || valT == typeof(int)
  872. || valT == typeof(uint)
  873. || valT == typeof(long)
  874. || valT == typeof(ulong)) return typeof(Integer);
  875. if (valT == typeof(float)
  876. || valT == typeof(double)
  877. || valT == typeof(decimal)) return typeof(FloatingPoint);
  878. if (typeof(IEnumerable).IsAssignableFrom(valT)) return typeof(List);
  879. // TODO: fill this out the rest of the way
  880. // TODO: support converters
  881. return typeof(Map); // default for various objects
  882. }
  883. private static void EmitDeserializeGeneratedValue(ILGenerator il, Type targetType, Type srcType, GetLocal GetLocal)
  884. {
  885. var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize));
  886. var valuel = GetLocal(srcType, 0);
  887. il.Emit(OpCodes.Stloc, valuel);
  888. EmitCreateChildGenerated(il, targetType);
  889. il.Emit(OpCodes.Dup);
  890. il.Emit(OpCodes.Ldloc, valuel);
  891. il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize);
  892. }
  893. private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal)
  894. {
  895. EmitDeserializeValue(il, member.NullableWrappedType, expected, GetLocal);
  896. il.Emit(OpCodes.Newobj, member.Nullable_Construct);
  897. }
  898. // top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType
  899. // after, top of stack will be thing to write to field
  900. private static void EmitDeserializeValue(ILGenerator il, Type targetType, Type expected, GetLocal GetLocal)
  901. {
  902. if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing
  903. if (expected == typeof(Text))
  904. {
  905. var getter = expected.GetProperty(nameof(Text.Value)).GetGetMethod();
  906. il.Emit(OpCodes.Call, getter);
  907. if (targetType == typeof(char))
  908. {
  909. var strIndex = typeof(string).GetProperty("Chars").GetGetMethod(); // string's indexer is specially named Chars
  910. il.Emit(OpCodes.Ldc_I4_0);
  911. il.Emit(OpCodes.Call, strIndex);
  912. }
  913. }
  914. else if (expected == typeof(Boolean))
  915. {
  916. var getter = expected.GetProperty(nameof(Boolean.Value)).GetGetMethod();
  917. il.Emit(OpCodes.Call, getter);
  918. }
  919. else if (expected == typeof(Integer))
  920. {
  921. var getter = expected.GetProperty(nameof(Integer.Value)).GetGetMethod();
  922. il.Emit(OpCodes.Call, getter);
  923. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  924. }
  925. else if (expected == typeof(FloatingPoint))
  926. {
  927. var getter = expected.GetProperty(nameof(FloatingPoint.Value)).GetGetMethod();
  928. il.Emit(OpCodes.Call, getter);
  929. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  930. } // TODO: implement stuff for lists and maps of various types (probably call out somewhere else to figure out what to do)
  931. else if (expected == typeof(Map))
  932. {
  933. EmitDeserializeGeneratedValue(il, targetType, expected, GetLocal);
  934. }
  935. else // TODO: support converters
  936. {
  937. il.Emit(OpCodes.Pop);
  938. il.Emit(OpCodes.Ldnull);
  939. }
  940. }
  941. // emit takes the value being deserialized, logs on error, leaves nothing on stack
  942. private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, GetLocal GetLocal)
  943. {
  944. var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
  945. var implLabel = il.DefineLabel();
  946. var passedTypeCheck = il.DefineLabel();
  947. var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type);
  948. il.Emit(OpCodes.Dup);
  949. il.Emit(OpCodes.Brtrue_S, implLabel); // null check
  950. if (!member.AllowNull)
  951. {
  952. il.Emit(OpCodes.Pop);
  953. EmitLogError(il, $"Member {member.Name} ({member.Type}) not nullable", tailcall: false,
  954. expected: il => EmitTypeof(il, expectType));
  955. il.Emit(OpCodes.Br, nextLabel);
  956. }
  957. else if (member.IsNullable)
  958. {
  959. il.Emit(OpCodes.Pop);
  960. var valTLocal = GetLocal(member.Type, 0);
  961. il.Emit(OpCodes.Ldloca, valTLocal);
  962. il.Emit(OpCodes.Initobj, member.Type);
  963. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal));
  964. il.Emit(OpCodes.Br, nextLabel);
  965. }
  966. else
  967. {
  968. il.Emit(OpCodes.Pop);
  969. EmitStore(il, member, il => il.Emit(OpCodes.Ldnull));
  970. il.Emit(OpCodes.Br, nextLabel);
  971. }
  972. il.MarkLabel(implLabel);
  973. il.Emit(OpCodes.Isinst, expectType); //replaces on stack
  974. il.Emit(OpCodes.Dup); // duplicate cloned value
  975. il.Emit(OpCodes.Brtrue, passedTypeCheck); // null check
  976. var errorHandle = il.DefineLabel();
  977. // special cases to handle coersion between Float and Int
  978. if (expectType == typeof(FloatingPoint))
  979. {
  980. var specialTypeCheck = il.DefineLabel();
  981. il.Emit(OpCodes.Pop);
  982. getValue(il);
  983. il.Emit(OpCodes.Isinst, typeof(Integer)); //replaces on stack
  984. il.Emit(OpCodes.Dup); // duplicate cloned value
  985. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  986. var Integer_CoerceToFloat = typeof(Integer).GetMethod(nameof(Integer.AsFloat));
  987. il.Emit(OpCodes.Call, Integer_CoerceToFloat);
  988. il.Emit(OpCodes.Br, passedTypeCheck);
  989. }
  990. else if (expectType == typeof(Integer))
  991. {
  992. var specialTypeCheck = il.DefineLabel();
  993. il.Emit(OpCodes.Pop);
  994. getValue(il);
  995. il.Emit(OpCodes.Isinst, typeof(FloatingPoint)); //replaces on stack
  996. il.Emit(OpCodes.Dup); // duplicate cloned value
  997. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  998. var Float_CoerceToInt = typeof(FloatingPoint).GetMethod(nameof(FloatingPoint.AsInteger));
  999. il.Emit(OpCodes.Call, Float_CoerceToInt);
  1000. il.Emit(OpCodes.Br, passedTypeCheck);
  1001. }
  1002. il.MarkLabel(errorHandle);
  1003. il.Emit(OpCodes.Pop);
  1004. EmitLogError(il, $"Unexpected type deserializing {member.Name}", tailcall: false,
  1005. expected: il => EmitTypeof(il, expectType), found: il =>
  1006. {
  1007. getValue(il);
  1008. il.Emit(OpCodes.Callvirt, Object_GetType);
  1009. });
  1010. il.Emit(OpCodes.Br, nextLabel);
  1011. il.MarkLabel(passedTypeCheck);
  1012. var local = GetLocal(member.Type, 0);
  1013. if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal);
  1014. else EmitDeserializeValue(il, member.Type, expectType, GetLocal);
  1015. il.Emit(OpCodes.Stloc, local);
  1016. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local));
  1017. }
  1018. #endregion
  1019. }
  1020. }