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.

174 lines
6.9 KiB

  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text.Json.Nodes;
  5. #if NET3
  6. using Net3_Proxy;
  7. #endif
  8. namespace IPA.Loader.Features
  9. {
  10. /// <summary>
  11. /// The root interface for a mod Feature.
  12. /// </summary>
  13. /// <remarks>
  14. /// Avoid storing any data in any subclasses. If you do, it may result in a failure to load the feature.
  15. /// </remarks>
  16. public abstract class Feature
  17. {
  18. /// <summary>
  19. /// Initializes the feature with the data provided in the definition.
  20. /// </summary>
  21. /// <remarks>
  22. /// <para>This gets called AFTER your <c>Init</c> method, but BEFORE the target's <c>Init</c> method. If it is applied to the defining plugin, <c>BeforeInit</c> is not called.</para>
  23. /// <para>Returning <see langword="false" /> does <i>not</i> prevent the plugin from being loaded. It simply prevents the feature from being used.</para>
  24. /// </remarks>
  25. /// <param name="meta">the metadata of the plugin that is being prepared</param>
  26. /// <param name="featureData">the data provided with the feature</param>
  27. /// <returns><see langword="true"/> if the feature is valid for the plugin, <see langword="false"/> otherwise</returns>
  28. protected abstract bool Initialize(PluginMetadata meta, JsonObject featureData);
  29. /// <summary>
  30. /// The message to be logged when the feature is not valid for a plugin.
  31. /// This should also be set whenever either <see cref="BeforeInit"/> returns false.
  32. /// </summary>
  33. /// <value>the message to show when the feature is marked invalid</value>
  34. public virtual string? InvalidMessage { get; protected set; }
  35. /// <summary>
  36. /// Called before a plugin's `Init` method is called. This will not be called if there is no `Init` method. This should never throw an exception. An exception will abort the loading of the plugin with an error.
  37. /// </summary>
  38. /// <param name="plugin">the plugin to be initialized</param>
  39. /// <returns>whether or not to call the Init method</returns>
  40. public virtual void BeforeInit(PluginMetadata plugin) { }
  41. /// <summary>
  42. /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
  43. /// </summary>
  44. /// <param name="plugin">the plugin that was just initialized</param>
  45. /// <param name="pluginInstance">the instance of the plugin being initialized</param>
  46. public virtual void AfterInit(PluginMetadata plugin, object pluginInstance) => AfterInit(plugin);
  47. /// <summary>
  48. /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
  49. /// </summary>
  50. /// <param name="plugin">the plugin that was just initialized</param>
  51. public virtual void AfterInit(PluginMetadata plugin) { }
  52. /// <summary>
  53. /// Called after a plugin with this feature appplied is disabled.
  54. /// </summary>
  55. /// <param name="plugin">the plugin that was disabled</param>
  56. public virtual void AfterDisable(PluginMetadata plugin) { }
  57. // TODO: rework features to take arguments as JSON objects
  58. static Feature()
  59. {
  60. Reset();
  61. }
  62. internal static void Reset()
  63. {
  64. featureTypes = new()
  65. {
  66. { "IPA.DefineFeature", typeof(DefineFeature) }
  67. };
  68. featureDelcarers = new()
  69. {
  70. { "IPA.DefineFeature", null }
  71. };
  72. }
  73. private static Dictionary<string, Type> featureTypes = null!;
  74. private static Dictionary<string, PluginMetadata?> featureDelcarers = null!;
  75. internal static bool HasFeature(string name) => featureTypes.ContainsKey(name);
  76. internal static bool PreregisterFeature(PluginMetadata defining, string name)
  77. {
  78. if (featureDelcarers.ContainsKey(name)) return false;
  79. featureDelcarers.Add(name, defining);
  80. return true;
  81. }
  82. internal static bool RegisterFeature(PluginMetadata definingPlugin, string name, Type type)
  83. {
  84. if (!typeof(Feature).IsAssignableFrom(type))
  85. throw new ArgumentException($"Feature type not subclass of {nameof(Feature)}", nameof(type));
  86. if (featureTypes.ContainsKey(name)) return false;
  87. if (featureDelcarers.TryGetValue(name, out var declarer))
  88. {
  89. if (definingPlugin != declarer)
  90. return false;
  91. }
  92. else
  93. {
  94. featureDelcarers.Add(name, definingPlugin);
  95. }
  96. featureTypes.Add(name, type);
  97. return true;
  98. }
  99. private class EmptyFeature : Feature
  100. {
  101. protected override bool Initialize(PluginMetadata meta, JsonObject featureData)
  102. {
  103. throw new NotImplementedException();
  104. }
  105. }
  106. internal string FeatureName = null!;
  107. internal class Instance
  108. {
  109. public readonly PluginMetadata AppliedTo;
  110. public readonly string Name;
  111. public readonly JsonObject Data;
  112. public Instance(PluginMetadata appliedTo, string name, JsonObject data)
  113. {
  114. AppliedTo = appliedTo;
  115. Name = name;
  116. Data = data;
  117. type = null;
  118. }
  119. private Type? type;
  120. public bool TryGetDefiningPlugin(out PluginMetadata? plugin)
  121. {
  122. return featureDelcarers.TryGetValue(Name, out plugin);
  123. }
  124. // returns whether or not Initialize returned true, feature is always set when the thing exists
  125. public bool TryCreate(out Feature feature)
  126. {
  127. if (type == null)
  128. {
  129. if (!featureTypes.TryGetValue(Name, out type))
  130. {
  131. feature = new EmptyFeature() { InvalidMessage = "No such feature type found", FeatureName = Name };
  132. return false;
  133. }
  134. }
  135. bool result;
  136. try
  137. {
  138. feature = (Feature)Activator.CreateInstance(type);
  139. feature.FeatureName = Name;
  140. result = feature.Initialize(AppliedTo, Data);
  141. }
  142. catch (Exception e)
  143. {
  144. result = false;
  145. feature = new EmptyFeature() { InvalidMessage = e.ToString(), FeatureName = Name };
  146. }
  147. return result;
  148. }
  149. }
  150. }
  151. }