|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace IPA.Loader
|
|
{
|
|
/// <summary>
|
|
/// A class to represent a transaction for changing the state of loaded mods.
|
|
/// </summary>
|
|
public sealed class StateTransitionTransaction : IDisposable
|
|
{
|
|
private readonly HashSet<PluginMetadata> currentlyEnabled;
|
|
private readonly HashSet<PluginMetadata> currentlyDisabled;
|
|
private readonly HashSet<PluginMetadata> toEnable = new HashSet<PluginMetadata>();
|
|
private readonly HashSet<PluginMetadata> toDisable = new HashSet<PluginMetadata>();
|
|
|
|
internal StateTransitionTransaction(IEnumerable<PluginMetadata> enabled, IEnumerable<PluginMetadata> disabled)
|
|
{
|
|
currentlyEnabled = new HashSet<PluginMetadata>(enabled.ToArray());
|
|
currentlyDisabled = new HashSet<PluginMetadata>(disabled.ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether or not a game restart will be necessary to fully apply this transaction.
|
|
/// </summary>
|
|
/// <value><see langword="true"/> if any mod who's state is changed cannot be changed at runtime, <see langword="false"/> otherwise</value>
|
|
public bool WillNeedRestart => toEnable.Concat(toDisable).Any(m => m.RuntimeOptions != RuntimeOptions.DynamicInit);
|
|
|
|
internal IEnumerable<PluginMetadata> ToEnable => toEnable;
|
|
internal IEnumerable<PluginMetadata> ToDisable => toDisable;
|
|
|
|
/// <summary>
|
|
/// Gets a list of plugins that are enabled according to this transaction's current state.
|
|
/// </summary>
|
|
public IEnumerable<PluginMetadata> EnabledPlugins
|
|
=> ThrowIfDisposed<IEnumerable<PluginMetadata>>()
|
|
?? currentlyEnabled.Except(toDisable).Concat(toEnable);
|
|
/// <summary>
|
|
/// Gets a list of plugins that are disabled according to this transaction's current state.
|
|
/// </summary>
|
|
public IEnumerable<PluginMetadata> DisabledPlugins
|
|
=> ThrowIfDisposed<IEnumerable<PluginMetadata>>()
|
|
?? currentlyDisabled.Except(toEnable).Concat(toDisable);
|
|
|
|
/// <summary>
|
|
/// Checks if a plugin is enabled according to this transaction's current state.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>This should be roughly equivalent to <c>EnabledPlugins.Contains(meta)</c>, but more performant.</para>
|
|
/// <para>This should also always return the inverse of <see cref="IsDisabled(PluginMetadata)"/> for valid plugins.</para>
|
|
/// </remarks>
|
|
/// <param name="meta">the plugin to check</param>
|
|
/// <returns><see langword="true"/> if the plugin is enabled, <see langword="false"/> otherwise</returns>
|
|
/// <seealso cref="EnabledPlugins"/>
|
|
/// <see cref="IsDisabled(PluginMetadata)"/>
|
|
public bool IsEnabled(PluginMetadata meta)
|
|
=> ThrowIfDisposed<bool>()
|
|
|| (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta))
|
|
|| toEnable.Contains(meta);
|
|
/// <summary>
|
|
/// Checks if a plugin is disabled according to this transaction's current state.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>This should be roughly equivalent to <c>DisabledPlugins.Contains(meta)</c>, but more performant.</para>
|
|
/// <para>This should also always return the inverse of <see cref="IsEnabled(PluginMetadata)"/> for valid plugins.</para>
|
|
/// </remarks>
|
|
/// <param name="meta">the plugin to check</param>
|
|
/// <returns><see langword="true"/> if the plugin is disabled, <see langword="false"/> otherwise</returns>
|
|
/// <seealso cref="DisabledPlugins"/>
|
|
/// <see cref="IsEnabled(PluginMetadata)"/>
|
|
public bool IsDisabled(PluginMetadata meta)
|
|
=> ThrowIfDisposed<bool>()
|
|
|| (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta))
|
|
|| toDisable.Contains(meta);
|
|
|
|
/// <summary>
|
|
/// Enables a plugin in this transaction.
|
|
/// </summary>
|
|
/// <param name="meta">the plugin to enable</param>
|
|
/// <returns><see langword="true"/> if the transaction's state was changed, <see langword="false"/> otherwise</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disables a plugin in this transaction.
|
|
/// </summary>
|
|
/// <param name="meta">the plugin to disable</param>
|
|
/// <returns><see langword="true"/> if the transaction's state was changed, <see langword="false"/> otherwise</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Commits this transaction to actual state, enabling and disabling plugins as necessary.
|
|
/// </summary>
|
|
/// <returns>a <see cref="Task"/> which completes whenever all disables complete</returns>
|
|
public Task Commit() => ThrowIfDisposed<Task>() ?? PluginManager.CommitTransaction(this);
|
|
|
|
private void ThrowIfDisposed() => ThrowIfDisposed<byte>();
|
|
private T ThrowIfDisposed<T>()
|
|
{
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(StateTransitionTransaction));
|
|
return default;
|
|
}
|
|
|
|
private bool disposed = false;
|
|
/// <summary>
|
|
/// Disposes and discards this transaction without committing it.
|
|
/// </summary>
|
|
public void Dispose()
|
|
=> disposed = true;
|
|
}
|
|
}
|