- #nullable enable
- using System;
- using System.Collections.Generic;
- using System.Text.Json.Nodes;
- #if NET3
- using Net3_Proxy;
- #endif
-
- namespace IPA.Loader.Features
- {
- /// <summary>
- /// The root interface for a mod Feature.
- /// </summary>
- /// <remarks>
- /// Avoid storing any data in any subclasses. If you do, it may result in a failure to load the feature.
- /// </remarks>
- public abstract class Feature
- {
- /// <summary>
- /// Initializes the feature with the data provided in the definition.
- /// </summary>
- /// <remarks>
- /// <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>
- /// <para>Returning <see langword="false" /> does <i>not</i> prevent the plugin from being loaded. It simply prevents the feature from being used.</para>
- /// </remarks>
- /// <param name="meta">the metadata of the plugin that is being prepared</param>
- /// <param name="featureData">the data provided with the feature</param>
- /// <returns><see langword="true"/> if the feature is valid for the plugin, <see langword="false"/> otherwise</returns>
- protected abstract bool Initialize(PluginMetadata meta, JsonObject featureData);
-
- /// <summary>
- /// The message to be logged when the feature is not valid for a plugin.
- /// This should also be set whenever either <see cref="BeforeInit"/> returns false.
- /// </summary>
- /// <value>the message to show when the feature is marked invalid</value>
- public virtual string? InvalidMessage { get; protected set; }
-
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="plugin">the plugin to be initialized</param>
- /// <returns>whether or not to call the Init method</returns>
- public virtual void BeforeInit(PluginMetadata plugin) { }
-
- /// <summary>
- /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
- /// </summary>
- /// <param name="plugin">the plugin that was just initialized</param>
- /// <param name="pluginInstance">the instance of the plugin being initialized</param>
- public virtual void AfterInit(PluginMetadata plugin, object pluginInstance) => AfterInit(plugin);
-
- /// <summary>
- /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
- /// </summary>
- /// <param name="plugin">the plugin that was just initialized</param>
- public virtual void AfterInit(PluginMetadata plugin) { }
-
- /// <summary>
- /// Called after a plugin with this feature appplied is disabled.
- /// </summary>
- /// <param name="plugin">the plugin that was disabled</param>
- public virtual void AfterDisable(PluginMetadata plugin) { }
-
- // TODO: rework features to take arguments as JSON objects
-
- static Feature()
- {
- Reset();
- }
-
- internal static void Reset()
- {
- featureTypes = new()
- {
- { "IPA.DefineFeature", typeof(DefineFeature) }
- };
- featureDelcarers = new()
- {
- { "IPA.DefineFeature", null }
- };
- }
-
- private static Dictionary<string, Type> featureTypes = null!;
- private static Dictionary<string, PluginMetadata?> featureDelcarers = null!;
-
- internal static bool HasFeature(string name) => featureTypes.ContainsKey(name);
-
- internal static bool PreregisterFeature(PluginMetadata defining, string name)
- {
- if (featureDelcarers.ContainsKey(name)) return false;
- featureDelcarers.Add(name, defining);
- return true;
- }
-
- internal static bool RegisterFeature(PluginMetadata definingPlugin, string name, Type type)
- {
- if (!typeof(Feature).IsAssignableFrom(type))
- throw new ArgumentException($"Feature type not subclass of {nameof(Feature)}", nameof(type));
-
- if (featureTypes.ContainsKey(name)) return false;
-
- if (featureDelcarers.TryGetValue(name, out var declarer))
- {
- if (definingPlugin != declarer)
- return false;
- }
- else
- {
- featureDelcarers.Add(name, definingPlugin);
- }
-
- featureTypes.Add(name, type);
- return true;
- }
-
- private class EmptyFeature : Feature
- {
- protected override bool Initialize(PluginMetadata meta, JsonObject featureData)
- {
- throw new NotImplementedException();
- }
- }
-
- internal string FeatureName = null!;
-
- internal class Instance
- {
- public readonly PluginMetadata AppliedTo;
- public readonly string Name;
- public readonly JsonObject Data;
-
- public Instance(PluginMetadata appliedTo, string name, JsonObject data)
- {
- AppliedTo = appliedTo;
- Name = name;
- Data = data;
- type = null;
- }
-
- private Type? type;
- public bool TryGetDefiningPlugin(out PluginMetadata? plugin)
- {
- return featureDelcarers.TryGetValue(Name, out plugin);
- }
-
- // returns whether or not Initialize returned true, feature is always set when the thing exists
- public bool TryCreate(out Feature feature)
- {
- if (type == null)
- {
- if (!featureTypes.TryGetValue(Name, out type))
- {
- feature = new EmptyFeature() { InvalidMessage = "No such feature type found", FeatureName = Name };
- return false;
- }
- }
-
- bool result;
- try
- {
- feature = (Feature)Activator.CreateInstance(type);
- feature.FeatureName = Name;
-
- result = feature.Initialize(AppliedTo, Data);
- }
- catch (Exception e)
- {
- result = false;
- feature = new EmptyFeature() { InvalidMessage = e.ToString(), FeatureName = Name };
- }
- return result;
- }
- }
- }
- }
|