Browse Source

Added actual enable/disable commit functionality

pull/46/head
Anairkoen Schno 4 years ago
parent
commit
18676185be
3 changed files with 134 additions and 20 deletions
  1. +2
    -0
      IPA.Loader/Loader/DisabledConfig.cs
  2. +114
    -20
      IPA.Loader/Loader/PluginManager.cs
  3. +18
    -0
      IPA.Loader/Loader/StateTransitionTransaction.cs

+ 2
- 0
IPA.Loader/Loader/DisabledConfig.cs View File

@ -29,6 +29,8 @@ namespace IPA.Loader
[UseConverter(typeof(CollectionConverter<string, HashSet<string>>))]
public virtual HashSet<string> DisabledModIds { get; set; } = new HashSet<string>();
protected internal virtual void Changed() { }
protected virtual void OnReload()
{
if (DisabledModIds == null || Reset)


+ 114
- 20
IPA.Loader/Loader/PluginManager.cs View File

@ -12,9 +12,11 @@ using IPA.Utilities;
using Mono.Cecil;
using UnityEngine;
using Logger = IPA.Logging.Logger;
using static IPA.Loader.PluginLoader;
using IPA.Loader.Features;
using System.Threading.Tasks;
#if NET4
using TaskEx = System.Threading.Tasks.Task;
using Task = System.Threading.Tasks.Task;
#endif
#if NET3
using Net3_Proxy;
using Path = Net3_Proxy.Path;
@ -73,9 +75,115 @@ namespace IPA.Loader
public static StateTransitionTransaction PluginStateTransaction()
=> new StateTransitionTransaction(AllPlugins, DisabledPlugins);
private static readonly object commitTransactionLockObject = new object();
internal static Task CommitTransaction(StateTransitionTransaction transaction)
{
throw new NotImplementedException();
lock (commitTransactionLockObject)
{
if (transaction.CurrentlyEnabled.Except(AllPlugins)
.Concat(AllPlugins.Except(transaction.CurrentlyEnabled)).Any()
|| transaction.CurrentlyDisabled.Except(DisabledPlugins)
.Concat(DisabledPlugins.Except(transaction.DisabledPlugins)).Any())
{ // ensure that the transaction's base state reflects the current state, otherwise throw
throw new InvalidOperationException("Transaction no longer resembles the current state of plugins");
}
var toEnable = transaction.ToEnable;
var toDisable = transaction.ToDisable;
transaction.Dispose();
{
// first enable the mods that need to be
void DeTree(List<PluginMetadata> into, IEnumerable<PluginMetadata> tree)
{
foreach (var st in tree)
if (toEnable.Contains(st) && !into.Contains(st))
{
DeTree(into, st.Dependencies);
into.Add(st);
}
}
var enableOrder = new List<PluginMetadata>();
DeTree(enableOrder, toEnable);
foreach (var meta in enableOrder)
{
var executor = runtimeDisabledPlugins.FirstOrDefault(e => e.Metadata == meta);
if (executor != null)
runtimeDisabledPlugins.Remove(executor);
else
executor = PluginLoader.InitPlugin(meta, AllPlugins);
if (executor == null) continue; // couldn't initialize, skip to next
PluginLoader.DisabledPlugins.Remove(meta);
DisabledConfig.Instance.DisabledModIds.Remove(meta.Id ?? meta.Name);
_bsPlugins.Add(executor);
try
{
executor.Enable();
}
catch (Exception e)
{
Logger.loader.Error($"Error while enabling {meta.Id}:");
Logger.loader.Error(e);
// this should still be considered enabled, hence its position
}
}
}
Task result;
{
// then disable the mods that need to be
static DisableExecutor MakeDisableExec(PluginExecutor e)
=> new DisableExecutor
{
Executor = e,
Dependents = BSMetas.Where(f => f.Metadata.Dependencies.Contains(e.Metadata)).Select(MakeDisableExec)
};
var disableExecs = toDisable.Select(m => BSMetas.FirstOrDefault(e => e.Metadata == m)).NonNull().ToArray(); // eagerly evaluate once
foreach (var exec in disableExecs)
{
runtimeDisabledPlugins.Add(exec);
PluginLoader.DisabledPlugins.Add(exec.Metadata);
DisabledConfig.Instance.DisabledModIds.Add(exec.Metadata.Id ?? exec.Metadata.Name);
_bsPlugins.Remove(exec);
}
var disableStructure = disableExecs.Select(MakeDisableExec);
static Task Disable(DisableExecutor exec, Dictionary<PluginExecutor, Task> alreadyDisabled)
{
if (alreadyDisabled.TryGetValue(exec.Executor, out var task))
return task;
else
{
var res = TaskEx.WhenAll(exec.Dependents.Select(d => Disable(d, alreadyDisabled)))
.ContinueWith(t => TaskEx.WhenAll(t, exec.Executor.Disable())).Unwrap();
// The WhenAll above allows us to wait for the executor to disable, but still propagate errors
alreadyDisabled.Add(exec.Executor, res);
return res;
}
}
var disabled = new Dictionary<PluginExecutor, Task>();
result = TaskEx.WhenAll(disableStructure.Select(d => Disable(d, disabled)));
}
DisabledConfig.Instance.Changed();
return result;
}
}
private struct DisableExecutor
{
public PluginExecutor Executor;
public IEnumerable<DisableExecutor> Dependents;
}
// TODO: rewrite below
@ -240,6 +348,7 @@ namespace IPA.Loader
/// </summary>
/// <value>a collection of all disabled plugins as <see cref="PluginMetadata"/></value>
public static IEnumerable<PluginMetadata> DisabledPlugins => PluginLoader.DisabledPlugins;
private static readonly HashSet<PluginExecutor> runtimeDisabledPlugins = new HashSet<PluginExecutor>();
/// <summary>
/// An invoker for the <see cref="PluginEnabled"/> event.
@ -269,21 +378,6 @@ namespace IPA.Loader
/// <value>a collection of all enabled plugins as <see cref="PluginMetadata"/>s</value>
public static IEnumerable<PluginMetadata> AllPlugins => BSMetas.Select(p => p.Metadata);
/*
/// <summary>
/// Converts a plugin's metadata to a <see cref="PluginInfo"/>.
/// </summary>
/// <param name="meta">the metadata</param>
/// <returns>the plugin info</returns>
public static PluginInfo InfoFromMetadata(PluginMetadata meta)
{
if (IsDisabled(meta))
return runtimeDisabled.FirstOrDefault(p => p.Metadata == meta);
else
return AllPlugins.FirstOrDefault(p => p == meta);
}
*/
/// <summary>
/// An <see cref="IEnumerable{T}"/> of old IPA plugins.
/// </summary>
@ -323,8 +417,8 @@ namespace IPA.Loader
// initialize BSIPA plugins first
_bsPlugins.AddRange(PluginLoader.LoadPlugins());
var metadataPaths = PluginsMetadata.Select(m => m.File.FullName).ToList();
var ignoredPaths = ignoredPlugins.Select(m => m.Key.File.FullName).ToList();
var metadataPaths = PluginLoader.PluginsMetadata.Select(m => m.File.FullName).ToList();
var ignoredPaths = PluginLoader.ignoredPlugins.Select(m => m.Key.File.FullName).ToList();
var disabledPaths = DisabledPlugins.Select(m => m.File.FullName).ToList();
//Copy plugins to .cache


+ 18
- 0
IPA.Loader/Loader/StateTransitionTransaction.cs View File

@ -31,6 +31,8 @@ namespace IPA.Loader
=> ThrowIfDisposed<bool>()
|| toEnable.Concat(toDisable).Any(m => m.RuntimeOptions != RuntimeOptions.DynamicInit);
internal IEnumerable<PluginMetadata> CurrentlyEnabled => currentlyEnabled;
internal IEnumerable<PluginMetadata> CurrentlyDisabled => currentlyDisabled;
internal IEnumerable<PluginMetadata> ToEnable => toEnable;
internal IEnumerable<PluginMetadata> ToDisable => toDisable;
@ -199,8 +201,24 @@ namespace IPA.Loader
/// <summary>
/// Commits this transaction to actual state, enabling and disabling plugins as necessary.
/// </summary>
/// <remarks>
/// <para>After this completes, this transaction will be disposed.</para>
/// <para>
/// The <see cref="Task"/> that is returned will error if <b>any</b> of the mods being <b>disabled</b>
/// error. It is up to the caller to handle these in a sane way, like logging them. If nothing else, do something like this:
/// <code>
/// // get your transaction...
/// var complete = transaction.Commit();
/// complete.ContinueWith(t => {
/// if (t.IsFaulted)
/// Logger.log.Error($"Error disabling plugins: {t.Exception}");
/// }).Wait(); // if not Wait(), then something else to wait for completion
/// </code>
/// </para>
/// </remarks>
/// <returns>a <see cref="Task"/> which completes whenever all disables complete</returns>
/// <exception cref="ObjectDisposedException">if this object has been disposed</exception>
/// <exception cref="InvalidOperationException">if the plugins' state no longer matches this transaction's original state</exception>
public Task Commit() => ThrowIfDisposed<Task>() ?? PluginManager.CommitTransaction(this);
private void ThrowIfDisposed() => ThrowIfDisposed<byte>();


Loading…
Cancel
Save