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.

159 lines
6.8 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using IPA.Config;
  6. using IPA.Logging;
  7. using IPA.Utilities;
  8. using System.Linq.Expressions;
  9. #if NET3
  10. using Net3_Proxy;
  11. #endif
  12. namespace IPA.Loader
  13. {
  14. /// <summary>
  15. /// The type that handles value injecting into a plugin's initialization methods.
  16. /// </summary>
  17. public static class PluginInitInjector
  18. {
  19. /// <summary>
  20. /// A typed injector for a plugin's Init method. When registered, called for all associated types. If it returns null, the default for the type will be used.
  21. /// </summary>
  22. /// <param name="previous">the previous return value of the function, or <see langword="null"/> if never called for plugin.</param>
  23. /// <param name="param">the <see cref="ParameterInfo"/> of the parameter being injected.</param>
  24. /// <param name="meta">the <see cref="PluginMetadata"/> for the plugin being loaded.</param>
  25. /// <returns>the value to inject into that parameter.</returns>
  26. public delegate object InjectParameter(object previous, ParameterInfo param, PluginMetadata meta);
  27. /// <summary>
  28. /// Adds an injector to be used when calling future plugins' Init methods.
  29. /// </summary>
  30. /// <param name="type">the type of the parameter.</param>
  31. /// <param name="injector">the function to call for injection.</param>
  32. public static void AddInjector(Type type, InjectParameter injector)
  33. {
  34. injectors.Add(new TypedInjector(type, injector));
  35. }
  36. private struct TypedInjector : IEquatable<TypedInjector>
  37. {
  38. public Type Type;
  39. public InjectParameter Injector;
  40. public TypedInjector(Type t, InjectParameter i)
  41. { Type = t; Injector = i; }
  42. public object Inject(object prev, ParameterInfo info, PluginMetadata meta)
  43. => Injector(prev, info, meta);
  44. public bool Equals(TypedInjector other)
  45. => Type == other.Type && Injector == other.Injector;
  46. public override bool Equals(object obj)
  47. => obj is TypedInjector i && Equals(i);
  48. public override int GetHashCode()
  49. => Type.GetHashCode() ^ Injector.GetHashCode();
  50. public static bool operator ==(TypedInjector a, TypedInjector b) => a.Equals(b);
  51. public static bool operator !=(TypedInjector a, TypedInjector b) => !a.Equals(b);
  52. }
  53. private static readonly List<TypedInjector> injectors = new List<TypedInjector>
  54. {
  55. new TypedInjector(typeof(Logger), (prev, param, meta) => prev ?? new StandardLogger(meta.Name)),
  56. new TypedInjector(typeof(PluginMetadata), (prev, param, meta) => prev ?? meta),
  57. new TypedInjector(typeof(Config.Config), (prev, param, meta) =>
  58. {
  59. if (prev != null) return prev;
  60. return Config.Config.GetConfigFor(meta.Name, param);
  61. })
  62. };
  63. private static int? MatchPriority(Type target, Type source)
  64. {
  65. if (target == source) return int.MaxValue;
  66. if (!target.IsAssignableFrom(source)) return null;
  67. if (!target.IsInterface && !source.IsSubclassOf(target)) return int.MinValue;
  68. int value = 0;
  69. while (true)
  70. {
  71. if (source == null) return value;
  72. if (target.IsInterface && source.GetInterfaces().Contains(target))
  73. return value;
  74. else if (target == source)
  75. return value;
  76. else
  77. {
  78. value--; // lower priority
  79. source = source.BaseType;
  80. }
  81. }
  82. }
  83. private static readonly MethodInfo InjectMethod = typeof(PluginInitInjector).GetMethod(nameof(Inject), BindingFlags.NonPublic | BindingFlags.Static);
  84. internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, ParameterExpression persistVar, Func<IEnumerable<Expression>, Expression> exprGen)
  85. {
  86. var arr = Expression.Variable(typeof(object[]), "initArr");
  87. return Expression.Block(new[] { arr },
  88. Expression.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta, persistVar)),
  89. exprGen(initParams
  90. .Select(p => p.ParameterType)
  91. .Select((t, i) => Expression.Convert(
  92. Expression.ArrayIndex(arr, Expression.Constant(i)), t))));
  93. }
  94. internal static object[] Inject(ParameterInfo[] initParams, PluginMetadata meta, ref object persist)
  95. {
  96. var initArgs = new List<object>();
  97. var previousValues = persist as Dictionary<TypedInjector, object>;
  98. if (previousValues == null)
  99. {
  100. previousValues = new Dictionary<TypedInjector, object>(injectors.Count);
  101. persist = previousValues;
  102. }
  103. foreach (var param in initParams)
  104. {
  105. var paramType = param.ParameterType;
  106. var value = paramType.GetDefault();
  107. var toUse = injectors.Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
  108. .Where(t => t.priority != null) // filter null priorities
  109. .Select(t => (t.inject, priority: t.priority.Value)) // remove nullable
  110. .OrderByDescending(t => t.priority) // sort by value
  111. .Select(t => t.inject); // remove priority value
  112. // this tries injectors in order of closest match by type provided
  113. foreach (var pair in toUse)
  114. {
  115. object prev = null;
  116. if (previousValues.ContainsKey(pair))
  117. prev = previousValues[pair];
  118. var val = pair.Inject(prev, param, meta);
  119. if (previousValues.ContainsKey(pair))
  120. previousValues[pair] = val;
  121. else
  122. previousValues.Add(pair, val);
  123. if (val == null) continue;
  124. value = val;
  125. break;
  126. }
  127. initArgs.Add(value);
  128. }
  129. //init.Invoke(instance, initArgs.ToArray());
  130. return initArgs.ToArray();
  131. }
  132. }
  133. }