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.

149 lines
6.0 KiB

  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. #if NET3
  9. using Net3_Proxy;
  10. #endif
  11. namespace IPA.Loader
  12. {
  13. /// <summary>
  14. /// The type that handles value injecting into a plugin's Init.
  15. /// </summary>
  16. public static class PluginInitInjector
  17. {
  18. /// <summary>
  19. /// 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.
  20. /// </summary>
  21. /// <param name="previous">the previous return value of the function, or <see langword="null"/> if never called for plugin.</param>
  22. /// <param name="param">the <see cref="ParameterInfo"/> of the parameter being injected.</param>
  23. /// <param name="meta">the <see cref="PluginLoader.PluginMetadata"/> for the plugin being loaded.</param>
  24. /// <returns>the value to inject into that parameter.</returns>
  25. public delegate object InjectParameter(object previous, ParameterInfo param, PluginLoader.PluginMetadata meta);
  26. /// <summary>
  27. /// Adds an injector to be used when calling future plugins' Init methods.
  28. /// </summary>
  29. /// <param name="type">the type of the parameter.</param>
  30. /// <param name="injector">the function to call for injection.</param>
  31. public static void AddInjector(Type type, InjectParameter injector)
  32. {
  33. injectors.Add(new TypedInjector(type, injector));
  34. }
  35. private struct TypedInjector : IEquatable<TypedInjector>
  36. {
  37. public Type Type;
  38. public InjectParameter Injector;
  39. public TypedInjector(Type t, InjectParameter i)
  40. { Type = t; Injector = i; }
  41. public object Inject(object prev, ParameterInfo info, PluginLoader.PluginMetadata meta)
  42. => Injector(prev, info, meta);
  43. public bool Equals(TypedInjector other)
  44. => Type == other.Type && Injector == other.Injector;
  45. public override bool Equals(object obj)
  46. => obj is TypedInjector i && Equals(i);
  47. public override int GetHashCode()
  48. => Type.GetHashCode() ^ Injector.GetHashCode();
  49. public static bool operator ==(TypedInjector a, TypedInjector b) => a.Equals(b);
  50. public static bool operator !=(TypedInjector a, TypedInjector b) => !a.Equals(b);
  51. }
  52. private static readonly List<TypedInjector> injectors = new List<TypedInjector>
  53. {
  54. new TypedInjector(typeof(Logger), (prev, param, meta) => prev ?? new StandardLogger(meta.Name)),
  55. #pragma warning disable CS0618 // Type or member is obsolete
  56. new TypedInjector(typeof(IModPrefs), (prev, param, meta) => prev ?? new ModPrefs(meta)),
  57. #pragma warning restore CS0618 // Type or member is obsolete
  58. new TypedInjector(typeof(PluginLoader.PluginMetadata), (prev, param, meta) => prev ?? meta),
  59. new TypedInjector(typeof(IConfigProvider), (prev, param, meta) =>
  60. {
  61. if (prev != null) return prev;
  62. var cfgProvider = Config.Config.GetProviderFor(meta.Name, param);
  63. cfgProvider.Load();
  64. return cfgProvider;
  65. })
  66. };
  67. private static int? MatchPriority(Type target, Type source)
  68. {
  69. if (target == source) return int.MaxValue;
  70. if (!target.IsAssignableFrom(source)) return null;
  71. if (!target.IsInterface && !source.IsSubclassOf(target)) return int.MinValue;
  72. int value = 0;
  73. while (true)
  74. {
  75. if (source == null) return value;
  76. if (target.IsInterface && source.GetInterfaces().Contains(target))
  77. return value;
  78. else if (target == source)
  79. return value;
  80. else
  81. {
  82. value--; // lower priority
  83. source = source.BaseType;
  84. }
  85. }
  86. }
  87. internal static void Inject(MethodInfo init, PluginLoader.PluginInfo info)
  88. {
  89. var instance = info.Plugin;
  90. var meta = info.Metadata;
  91. var initArgs = new List<object>();
  92. var initParams = init.GetParameters();
  93. var previousValues = new Dictionary<TypedInjector, object>(injectors.Count);
  94. foreach (var param in initParams)
  95. {
  96. var paramType = param.ParameterType;
  97. var value = paramType.GetDefault();
  98. var toUse = injectors.Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
  99. .Where(t => t.priority != null) // filter null priorities
  100. .Select(t => (t.inject, priority: t.priority.Value)) // remove nullable
  101. .OrderByDescending(t => t.priority) // sort by value
  102. .Select(t => t.inject); // remove priority value
  103. // this tries injectors in order of closest match by type provided
  104. foreach (var pair in toUse)
  105. {
  106. object prev = null;
  107. if (previousValues.ContainsKey(pair))
  108. prev = previousValues[pair];
  109. var val = pair.Inject(prev, param, meta);
  110. if (previousValues.ContainsKey(pair))
  111. previousValues[pair] = val;
  112. else
  113. previousValues.Add(pair, val);
  114. if (val == null) continue;
  115. value = val;
  116. break;
  117. }
  118. initArgs.Add(value);
  119. }
  120. init.Invoke(instance, initArgs.ToArray());
  121. }
  122. }
  123. }