diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index a0437ca5..714bc209 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -122,6 +122,7 @@ + diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index 8a128d10..a8b267d5 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -177,7 +177,6 @@ namespace IPA.Loader var rtOptionsValInt = (int)rtOptionsArg.Value; // `int` is the underlying type of RuntimeOptions meta.RuntimeOptions = (RuntimeOptions)rtOptionsValInt; - meta.IsAttributePlugin = true; meta.PluginType = type; return; } @@ -604,12 +603,6 @@ namespace IPA.Loader if (meta.Manifest.GameVersion != UnityGame.GameVersion) Logger.loader.Warn($"Mod {meta.Name} developed for game version {meta.Manifest.GameVersion}, so it may not work properly."); - if (!meta.IsAttributePlugin) - { - ignoredPlugins.Add(meta, new IgnoreReason(Reason.Unsupported) { ReasonText = "Non-attribute plugins are currently not supported" }); - return null; - } - if (meta.IsSelf) return new PluginExecutor(meta, true); @@ -708,115 +701,6 @@ namespace IPA.Loader } return exec; - - #region Interface plugin support - /*if (meta.IsSelf) - return new PluginInfo() - { - Metadata = meta, - Plugin = null - }; - - var info = new PluginInfo(); - - try - { - foreach (var dep in meta.Dependencies) - { - if (alreadyLoaded.Contains(dep)) continue; - - // otherwise... - - if (ignoredPlugins.TryGetValue(dep, out var reason)) - { // was added to the ignore list - ignoredPlugins.Add(meta, new IgnoreReason(Reason.Dependency) - { - ReasonText = $"Dependency was ignored at load time: {reason.ReasonText}", - RelatedTo = dep - }); - } - else - { // was not added to ignore list - ignoredPlugins.Add(meta, new IgnoreReason(Reason.Dependency) - { - ReasonText = $"Dependency was not already loaded at load time, but was also not ignored", - RelatedTo = dep - }); - } - - return null; - } - - Load(meta); - - Feature denyingFeature = null; - if (!meta.Features.All(f => (denyingFeature = f).BeforeLoad(meta))) - { - Logger.loader.Warn( - $"Feature {denyingFeature?.GetType()} denied plugin {meta.Name} from loading! {denyingFeature?.InvalidMessage}"); - ignoredPlugins.Add(meta, new IgnoreReason(Reason.Feature) - { - ReasonText = $"Denied in {nameof(Feature.BeforeLoad)} of feature {denyingFeature?.GetType()}:\n\t{denyingFeature?.InvalidMessage}" - }); - return null; - } - - var type = meta.Assembly.GetType(meta.PluginType.FullName); - var instance = Activator.CreateInstance(type) as IPlugin; - - info.Metadata = meta; - info.Plugin = instance; - - var init = type.GetMethod("Init", BindingFlags.Instance | BindingFlags.Public); - if (init != null) - { - denyingFeature = null; - if (!meta.Features.All(f => (denyingFeature = f).BeforeInit(info))) - { - Logger.loader.Warn( - $"Feature {denyingFeature?.GetType()} denied plugin {meta.Name} from initializing! {denyingFeature?.InvalidMessage}"); - ignoredPlugins.Add(meta, new IgnoreReason(Reason.Feature) - { - ReasonText = $"Denied in {nameof(Feature.BeforeInit)} of feature {denyingFeature?.GetType()}:\n\t{denyingFeature?.InvalidMessage}" - }); - return null; - } - - var args = PluginInitInjector.Inject(init.GetParameters(), meta); - init.Invoke(info.Plugin, args); - } - - foreach (var feature in meta.Features) - try - { - feature.AfterInit(info, info.Plugin); - } - catch (Exception e) - { - Logger.loader.Critical($"Feature errored in {nameof(Feature.AfterInit)}: {e}"); - } - } - catch (AmbiguousMatchException) - { - Logger.loader.Critical($"Only one Init allowed per plugin (ambiguous match in {meta.Name})"); - Logger.loader.Critical("@Developer: you *really* should fix this"); - // not adding to ignoredPlugins here because this should only happen in a development context - // if someone fucks this up on release thats on them - return null; - } - catch (Exception e) - { - Logger.loader.Error($"Could not init plugin {meta.Name}: {e}"); - ignoredPlugins.Add(meta, new IgnoreReason(Reason.Error) - { - ReasonText = "Error ocurred while initializing", - Error = e - }); - return null; - } - - return info;*/ - #endregion } internal static List LoadPlugins() diff --git a/IPA.Loader/Loader/PluginManager.cs b/IPA.Loader/Loader/PluginManager.cs index 0ccdc406..f104d1ca 100644 --- a/IPA.Loader/Loader/PluginManager.cs +++ b/IPA.Loader/Loader/PluginManager.cs @@ -14,6 +14,7 @@ using UnityEngine; using Logger = IPA.Logging.Logger; using static IPA.Loader.PluginLoader; using IPA.Loader.Features; +using System.Threading.Tasks; #if NET3 using Net3_Proxy; using Path = Net3_Proxy.Path; @@ -65,6 +66,14 @@ namespace IPA.Loader public static PluginMetadata GetDisabledPluginFromId(string name) => DisabledPlugins.FirstOrDefault(p => p.Id == name); + public static StateTransitionTransaction PluginStateTransaction() + => new StateTransitionTransaction(AllPlugins, DisabledPlugins); + + internal static Task CommitTransaction(StateTransitionTransaction transaction) + { + throw new NotImplementedException(); + } + // TODO: rewrite below /* /// @@ -251,7 +260,7 @@ namespace IPA.Loader public static event PluginDisableDelegate PluginDisabled; /// - /// Gets a list of all BSIPA plugins. + /// Gets a list of all enabled BSIPA plugins. /// /// a collection of all enabled plugins as s public static IEnumerable AllPlugins => BSMetas.Select(p => p.Metadata); diff --git a/IPA.Loader/Loader/PluginMetadata.cs b/IPA.Loader/Loader/PluginMetadata.cs index 930a8333..af1f31d0 100644 --- a/IPA.Loader/Loader/PluginMetadata.cs +++ b/IPA.Loader/Loader/PluginMetadata.cs @@ -89,11 +89,6 @@ namespace IPA.Loader /// The that the plugin specified in its . /// public RuntimeOptions RuntimeOptions { get; internal set; } - /// - /// Whether the plugin referred to by this metadata object is a BSIPA 4 attribute plugin. - /// - /// if the plugin is a BSIPA 4 plugin, if it is a BSIPA 3 plugin. - public bool IsAttributePlugin { get; internal set; } = false; /// /// Gets all of the metadata as a readable string. diff --git a/IPA.Loader/Loader/StateTransitionTransaction.cs b/IPA.Loader/Loader/StateTransitionTransaction.cs new file mode 100644 index 00000000..4e2c6388 --- /dev/null +++ b/IPA.Loader/Loader/StateTransitionTransaction.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.Loader +{ + public sealed class StateTransitionTransaction : IDisposable + { + private readonly HashSet currentlyEnabled; + private readonly HashSet currentlyDisabled; + private readonly HashSet toEnable = new HashSet(); + private readonly HashSet toDisable = new HashSet(); + + internal StateTransitionTransaction(IEnumerable enabled, IEnumerable disabled) + { + currentlyEnabled = new HashSet(enabled.ToArray()); + currentlyDisabled = new HashSet(disabled.ToArray()); + } + + public bool WillNeedRestart => toEnable.Concat(toDisable).Any(m => m.RuntimeOptions != RuntimeOptions.DynamicInit); + + internal IEnumerable ToEnable => toEnable; + internal IEnumerable ToDisable => toDisable; + + public IEnumerable EnabledPlugins => currentlyEnabled.Except(toDisable).Concat(toEnable); + public IEnumerable DisabledPlugins => currentlyDisabled.Except(toEnable).Concat(toDisable); + + public bool IsEnabled(PluginMetadata meta) + => ThrowIfDisposed() + || (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta)) + || toEnable.Contains(meta); + public bool IsDisabled(PluginMetadata meta) + => ThrowIfDisposed() + || (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta)) + || toDisable.Contains(meta); + + public bool Enable(PluginMetadata meta) + { // returns whether or not state was changed + ThrowIfDisposed(); + if (!currentlyEnabled.Contains(meta) && !currentlyDisabled.Contains(meta)) + throw new ArgumentException(nameof(meta), "Plugin metadata does not represent a loadable plugin"); + + if (toEnable.Contains(meta)) return false; + if (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta)) return false; + toDisable.Remove(meta); + toEnable.Add(meta); + return true; + } + + public bool Disable(PluginMetadata meta) + { // returns whether or not state was changed + ThrowIfDisposed(); + if (!currentlyEnabled.Contains(meta) && !currentlyDisabled.Contains(meta)) + throw new ArgumentException(nameof(meta), "Plugin metadata does not represent a "); + + if (toEnable.Contains(meta)) return false; + if (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta)) return false; + toDisable.Remove(meta); + toEnable.Add(meta); + return true; + } + + public Task Commit() => PluginManager.CommitTransaction(this); + + private void ThrowIfDisposed() => ThrowIfDisposed(); + private T ThrowIfDisposed() + { + if (disposed) + throw new ObjectDisposedException(nameof(StateTransitionTransaction)); + return default; + } + + private bool disposed = false; + public void Dispose() + => disposed = true; + } +} diff --git a/IPA.Loader/PluginInterfaces/Attributes/PluginAttribute.cs b/IPA.Loader/PluginInterfaces/Attributes/PluginAttribute.cs index d44d1044..15ff14db 100644 --- a/IPA.Loader/PluginInterfaces/Attributes/PluginAttribute.cs +++ b/IPA.Loader/PluginInterfaces/Attributes/PluginAttribute.cs @@ -50,6 +50,7 @@ namespace IPA /// With this option set, whether or not the plugin is disabled during a given run is constant for that entire run. /// /// + // enabled exactly once and never disabled SingleStartInit, /// /// @@ -69,6 +70,7 @@ namespace IPA /// re-used for subsequent enables. The plugin is expected to handle this gracefully, and behave in a way that makes sense. /// /// + // both enabled and disabled at runtime DynamicInit }