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
}