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();