You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 lines
6.5 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace IPA.Loader
  7. {
  8. /// <summary>
  9. /// A class to represent a transaction for changing the state of loaded mods.
  10. /// </summary>
  11. public sealed class StateTransitionTransaction : IDisposable
  12. {
  13. private readonly HashSet<PluginMetadata> currentlyEnabled;
  14. private readonly HashSet<PluginMetadata> currentlyDisabled;
  15. private readonly HashSet<PluginMetadata> toEnable = new HashSet<PluginMetadata>();
  16. private readonly HashSet<PluginMetadata> toDisable = new HashSet<PluginMetadata>();
  17. internal StateTransitionTransaction(IEnumerable<PluginMetadata> enabled, IEnumerable<PluginMetadata> disabled)
  18. {
  19. currentlyEnabled = new HashSet<PluginMetadata>(enabled.ToArray());
  20. currentlyDisabled = new HashSet<PluginMetadata>(disabled.ToArray());
  21. }
  22. /// <summary>
  23. /// Gets whether or not a game restart will be necessary to fully apply this transaction.
  24. /// </summary>
  25. /// <value><see langword="true"/> if any mod who's state is changed cannot be changed at runtime, <see langword="false"/> otherwise</value>
  26. public bool WillNeedRestart => toEnable.Concat(toDisable).Any(m => m.RuntimeOptions != RuntimeOptions.DynamicInit);
  27. internal IEnumerable<PluginMetadata> ToEnable => toEnable;
  28. internal IEnumerable<PluginMetadata> ToDisable => toDisable;
  29. /// <summary>
  30. /// Gets a list of plugins that are enabled according to this transaction's current state.
  31. /// </summary>
  32. public IEnumerable<PluginMetadata> EnabledPlugins
  33. => ThrowIfDisposed<IEnumerable<PluginMetadata>>()
  34. ?? currentlyEnabled.Except(toDisable).Concat(toEnable);
  35. /// <summary>
  36. /// Gets a list of plugins that are disabled according to this transaction's current state.
  37. /// </summary>
  38. public IEnumerable<PluginMetadata> DisabledPlugins
  39. => ThrowIfDisposed<IEnumerable<PluginMetadata>>()
  40. ?? currentlyDisabled.Except(toEnable).Concat(toDisable);
  41. /// <summary>
  42. /// Checks if a plugin is enabled according to this transaction's current state.
  43. /// </summary>
  44. /// <remarks>
  45. /// <para>This should be roughly equivalent to <c>EnabledPlugins.Contains(meta)</c>, but more performant.</para>
  46. /// <para>This should also always return the inverse of <see cref="IsDisabled(PluginMetadata)"/> for valid plugins.</para>
  47. /// </remarks>
  48. /// <param name="meta">the plugin to check</param>
  49. /// <returns><see langword="true"/> if the plugin is enabled, <see langword="false"/> otherwise</returns>
  50. /// <seealso cref="EnabledPlugins"/>
  51. /// <see cref="IsDisabled(PluginMetadata)"/>
  52. public bool IsEnabled(PluginMetadata meta)
  53. => ThrowIfDisposed<bool>()
  54. || (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta))
  55. || toEnable.Contains(meta);
  56. /// <summary>
  57. /// Checks if a plugin is disabled according to this transaction's current state.
  58. /// </summary>
  59. /// <remarks>
  60. /// <para>This should be roughly equivalent to <c>DisabledPlugins.Contains(meta)</c>, but more performant.</para>
  61. /// <para>This should also always return the inverse of <see cref="IsEnabled(PluginMetadata)"/> for valid plugins.</para>
  62. /// </remarks>
  63. /// <param name="meta">the plugin to check</param>
  64. /// <returns><see langword="true"/> if the plugin is disabled, <see langword="false"/> otherwise</returns>
  65. /// <seealso cref="DisabledPlugins"/>
  66. /// <see cref="IsEnabled(PluginMetadata)"/>
  67. public bool IsDisabled(PluginMetadata meta)
  68. => ThrowIfDisposed<bool>()
  69. || (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta))
  70. || toDisable.Contains(meta);
  71. /// <summary>
  72. /// Enables a plugin in this transaction.
  73. /// </summary>
  74. /// <param name="meta">the plugin to enable</param>
  75. /// <returns><see langword="true"/> if the transaction's state was changed, <see langword="false"/> otherwise</returns>
  76. public bool Enable(PluginMetadata meta)
  77. { // returns whether or not state was changed
  78. ThrowIfDisposed();
  79. if (!currentlyEnabled.Contains(meta) && !currentlyDisabled.Contains(meta))
  80. throw new ArgumentException(nameof(meta), "Plugin metadata does not represent a loadable plugin");
  81. if (toEnable.Contains(meta)) return false;
  82. if (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta)) return false;
  83. toDisable.Remove(meta);
  84. toEnable.Add(meta);
  85. return true;
  86. }
  87. /// <summary>
  88. /// Disables a plugin in this transaction.
  89. /// </summary>
  90. /// <param name="meta">the plugin to disable</param>
  91. /// <returns><see langword="true"/> if the transaction's state was changed, <see langword="false"/> otherwise</returns>
  92. public bool Disable(PluginMetadata meta)
  93. { // returns whether or not state was changed
  94. ThrowIfDisposed();
  95. if (!currentlyEnabled.Contains(meta) && !currentlyDisabled.Contains(meta))
  96. throw new ArgumentException(nameof(meta), "Plugin metadata does not represent a ");
  97. if (toEnable.Contains(meta)) return false;
  98. if (currentlyEnabled.Contains(meta) && !toDisable.Contains(meta)) return false;
  99. toDisable.Remove(meta);
  100. toEnable.Add(meta);
  101. return true;
  102. }
  103. /// <summary>
  104. /// Commits this transaction to actual state, enabling and disabling plugins as necessary.
  105. /// </summary>
  106. /// <returns>a <see cref="Task"/> which completes whenever all disables complete</returns>
  107. public Task Commit() => ThrowIfDisposed<Task>() ?? PluginManager.CommitTransaction(this);
  108. private void ThrowIfDisposed() => ThrowIfDisposed<byte>();
  109. private T ThrowIfDisposed<T>()
  110. {
  111. if (disposed)
  112. throw new ObjectDisposedException(nameof(StateTransitionTransaction));
  113. return default;
  114. }
  115. private bool disposed = false;
  116. /// <summary>
  117. /// Disposes and discards this transaction without committing it.
  118. /// </summary>
  119. public void Dispose()
  120. => disposed = true;
  121. }
  122. }