diff --git a/IPA.Loader/Loader/StateTransitionTransaction.cs b/IPA.Loader/Loader/StateTransitionTransaction.cs
index 3fec30cf..918203ec 100644
--- a/IPA.Loader/Loader/StateTransitionTransaction.cs
+++ b/IPA.Loader/Loader/StateTransitionTransaction.cs
@@ -26,7 +26,10 @@ namespace IPA.Loader
/// Gets whether or not a game restart will be necessary to fully apply this transaction.
///
/// if any mod who's state is changed cannot be changed at runtime, otherwise
- public bool WillNeedRestart => toEnable.Concat(toDisable).Any(m => m.RuntimeOptions != RuntimeOptions.DynamicInit);
+ /// if this object has been disposed
+ public bool WillNeedRestart
+ => ThrowIfDisposed()
+ || toEnable.Concat(toDisable).Any(m => m.RuntimeOptions != RuntimeOptions.DynamicInit);
internal IEnumerable ToEnable => toEnable;
internal IEnumerable ToDisable => toDisable;
@@ -34,15 +37,17 @@ namespace IPA.Loader
///
/// Gets a list of plugins that are enabled according to this transaction's current state.
///
- public IEnumerable EnabledPlugins
- => ThrowIfDisposed>()
- ?? currentlyEnabled.Except(toDisable).Concat(toEnable);
+ /// if this object has been disposed
+ public IEnumerable EnabledPlugins
+ => ThrowIfDisposed>() ?? DisabledPluginsInternal;
+ private IEnumerable EnabledPluginsInternal => currentlyEnabled.Except(toDisable).Concat(toEnable);
///
/// Gets a list of plugins that are disabled according to this transaction's current state.
///
- public IEnumerable DisabledPlugins
- => ThrowIfDisposed>()
- ?? currentlyDisabled.Except(toEnable).Concat(toDisable);
+ /// if this object has been disposed
+ public IEnumerable DisabledPlugins
+ => ThrowIfDisposed>() ?? DisabledPluginsInternal;
+ private IEnumerable DisabledPluginsInternal => currentlyDisabled.Except(toEnable).Concat(toDisable);
///
/// Checks if a plugin is enabled according to this transaction's current state.
@@ -53,11 +58,13 @@ namespace IPA.Loader
///
/// the plugin to check
/// if the plugin is enabled, otherwise
+ /// if this object has been disposed
///
- ///
+ ///
public bool IsEnabled(PluginMetadata meta)
- => ThrowIfDisposed()
- || (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta))
+ => ThrowIfDisposed() || IsEnabledInternal(meta);
+ private bool IsEnabledInternal(PluginMetadata meta)
+ => (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta))
|| toEnable.Contains(meta);
///
/// Checks if a plugin is disabled according to this transaction's current state.
@@ -68,26 +75,66 @@ namespace IPA.Loader
///
/// the plugin to check
/// if the plugin is disabled, otherwise
+ /// if this object has been disposed
///
- ///
+ ///
public bool IsDisabled(PluginMetadata meta)
- => ThrowIfDisposed()
- || (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta))
+ => ThrowIfDisposed() || IsDisabledInternal(meta);
+ private bool IsDisabledInternal(PluginMetadata meta)
+ => (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta))
|| toDisable.Contains(meta);
///
/// Enables a plugin in this transaction.
///
/// the plugin to enable
+ /// whether or not to automatically enable all dependencies of the plugin
/// if the transaction's state was changed, otherwise
- public bool Enable(PluginMetadata meta)
+ /// if this object has been disposed
+ /// if is not loadable
+ ///
+ public bool Enable(PluginMetadata meta, bool autoDeps = true)
+ => Enable(meta, out var _, autoDeps);
+
+ ///
+ /// Enables a plugin in this transaction.
+ ///
+ ///
+ /// will only be set when is .
+ ///
+ /// the plugin to enable
+ /// if successful, otherwise a set of plugins that need to be enabled first
+ /// whether or not to automatically enable all dependencies
+ /// if the transaction's state was changed, otherwise
+ /// if this object has been disposed
+ /// if is not loadable
+ public bool Enable(PluginMetadata meta, out IEnumerable disabledDeps, bool autoDeps = false)
{ // 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;
+ disabledDeps = null;
+ if (IsEnabledInternal(meta)) return false;
+
+ var needsEnabled = meta.Dependencies.Where(m => DisabledPluginsInternal.Contains(m));
+ if (autoDeps)
+ {
+ foreach (var dep in needsEnabled)
+ {
+ var res = Disable(dep, out var failedDisabled, true);
+ if (failedDisabled == null) continue;
+ disabledDeps = failedDisabled;
+ return res;
+ }
+ }
+ else if (needsEnabled.Any())
+ {
+ // there are currently enabled plugins that depend on this
+ disabledDeps = needsEnabled;
+ return false;
+ }
+
toDisable.Remove(meta);
toEnable.Add(meta);
return true;
@@ -97,17 +144,55 @@ namespace IPA.Loader
/// Disables a plugin in this transaction.
///
/// the plugin to disable
+ /// whether or not to automatically disable all dependents of the plugin
/// if the transaction's state was changed, otherwise
- public bool Disable(PluginMetadata meta)
+ /// if this object has been disposed
+ /// if is not loadable
+ ///
+ public bool Disable(PluginMetadata meta, bool autoDependents = true)
+ => Disable(meta, out var _, autoDependents);
+
+ ///
+ /// Disables a plugin in this transaction.
+ ///
+ ///
+ /// will only be set when is .
+ ///
+ /// the plugin to disable
+ /// if successful, otherwise a set of plugins that need to be disabled first
+ /// whether or not to automatically disable all dependents of the plugin
+ /// if the transaction's state was changed, otherwise
+ /// if this object has been disposed
+ /// if is not loadable
+ public bool Disable(PluginMetadata meta, out IEnumerable enabledDependents, bool autoDependents = false)
{ // 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 ");
+ 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);
+ enabledDependents = null;
+ if (IsDisabledInternal(meta)) return false;
+
+ var needsDisabled = EnabledPluginsInternal.Where(m => m.Dependencies.Contains(meta));
+ if (autoDependents)
+ {
+ foreach (var dep in needsDisabled)
+ {
+ var res = Disable(dep, out var failedEnabled, true);
+ if (failedEnabled == null) continue;
+ enabledDependents = failedEnabled;
+ return res;
+ }
+ }
+ else if (needsDisabled.Any())
+ {
+ // there are currently enabled plugins that depend on this
+ enabledDependents = needsDisabled;
+ return false;
+ }
+
+ toDisable.Add(meta);
+ toEnable.Remove(meta);
return true;
}
@@ -115,6 +200,7 @@ namespace IPA.Loader
/// Commits this transaction to actual state, enabling and disabling plugins as necessary.
///
/// a which completes whenever all disables complete
+ /// if this object has been disposed
public Task Commit() => ThrowIfDisposed() ?? PluginManager.CommitTransaction(this);
private void ThrowIfDisposed() => ThrowIfDisposed();