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;
}
}