using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace IPA.Loader { /// /// A class to represent a transaction for changing the state of loaded mods. /// 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()); } /// /// 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); internal IEnumerable ToEnable => toEnable; internal IEnumerable ToDisable => toDisable; /// /// Gets a list of plugins that are enabled according to this transaction's current state. /// public IEnumerable EnabledPlugins => ThrowIfDisposed>() ?? 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); /// /// Checks if a plugin is enabled according to this transaction's current state. /// /// /// This should be roughly equivalent to EnabledPlugins.Contains(meta), but more performant. /// This should also always return the inverse of for valid plugins. /// /// the plugin to check /// if the plugin is enabled, otherwise /// /// public bool IsEnabled(PluginMetadata meta) => ThrowIfDisposed() || (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta)) || toEnable.Contains(meta); /// /// Checks if a plugin is disabled according to this transaction's current state. /// /// /// This should be roughly equivalent to DisabledPlugins.Contains(meta), but more performant. /// This should also always return the inverse of for valid plugins. /// /// the plugin to check /// if the plugin is disabled, otherwise /// /// public bool IsDisabled(PluginMetadata meta) => ThrowIfDisposed() || (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta)) || toDisable.Contains(meta); /// /// Enables a plugin in this transaction. /// /// the plugin to enable /// if the transaction's state was changed, otherwise 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; } /// /// Disables a plugin in this transaction. /// /// the plugin to disable /// if the transaction's state was changed, otherwise 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; } /// /// Commits this transaction to actual state, enabling and disabling plugins as necessary. /// /// a which completes whenever all disables complete public Task Commit() => ThrowIfDisposed() ?? PluginManager.CommitTransaction(this); private void ThrowIfDisposed() => ThrowIfDisposed(); private T ThrowIfDisposed() { if (disposed) throw new ObjectDisposedException(nameof(StateTransitionTransaction)); return default; } private bool disposed = false; /// /// Disposes and discards this transaction without committing it. /// public void Dispose() => disposed = true; } }