diff --git a/IPA.Loader/Loader/DependencyResolutionLoopException.cs b/IPA.Loader/Loader/DependencyResolutionLoopException.cs new file mode 100644 index 00000000..3d7fd89b --- /dev/null +++ b/IPA.Loader/Loader/DependencyResolutionLoopException.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace IPA.Loader +{ + [SuppressMessage("Design", "CA1064:Exceptions should be public", Justification = "This is only thrown and caught in local code")] + internal sealed class DependencyResolutionLoopException : Exception + { + public DependencyResolutionLoopException(string message) : base(message) + { + } + + public DependencyResolutionLoopException(string message, Exception innerException) : base(message, innerException) + { + } + + public DependencyResolutionLoopException() + { + } + } +} diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index 60bacee6..64739d8b 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -357,7 +357,7 @@ namespace IPA.Loader public enum Reason { /// - /// An error was thrown either loading plugin information fomr disk, or when initializing the plugin. + /// An error was thrown either loading plugin information from disk, or when initializing the plugin. /// /// /// When this is the set in an structure, the member @@ -850,10 +850,37 @@ namespace IPA.Loader meta = plugin.Meta; if (!disabled) { - Resolve(plugin.Meta, ref disabled, out ignored); + try + { + ignored = false; + Resolve(plugin.Meta, ref disabled, out ignored); + } + catch (Exception e) + { + if (e is not DependencyResolutionLoopException) + { + Logger.loader.Error($"While performing load order resolution for {id}:"); + Logger.loader.Error(e); + } + + if (!ignored) + { + ignoredPlugins.Add(plugin.Meta, new(Reason.Error) + { + Error = e + }); + } + + ignored = true; + } + } + + if (!loadedPlugins.ContainsKey(id)) + { + // this condition is specifically for when we fail resolution because of a graph loop + Logger.loader.Trace($"- '{id}' resolved as ignored:{ignored},disabled:{disabled}"); + loadedPlugins.Add(id, (plugin.Meta, disabled, ignored)); } - Logger.loader.Trace($"- '{id}' resolved as ignored:{ignored},disabled:{disabled}"); - loadedPlugins.Add(id, (plugin.Meta, disabled, ignored)); return true; } Logger.loader.Trace($"- Not found"); @@ -868,12 +895,10 @@ namespace IPA.Loader if (isProcessing.Contains(plugin)) { Logger.loader.Error($"Loop detected while processing '{plugin.Name}'; flagging as ignored"); - // we can't safely add it to ignoredPlugins, because then when the ignore propagates up the stack, - // we may end up ignoring outselves again - ignored = true; - return; + throw new DependencyResolutionLoopException(); } + isProcessing.Add(plugin); using var _removeProcessing = Utils.ScopeGuard(() => isProcessing.Remove(plugin)); // if this method is being called, this is the first and only time that it has been called for this plugin. @@ -994,8 +1019,14 @@ namespace IPA.Loader } } - // we can now load the current plugin - outputOrder!.Add(plugin); + // specifically check if some strange stuff happened (like graph loops) causing this to be ignored + // from some other invocation + if (!ignoredPlugins.ContainsKey(plugin)) + { + // we can now load the current plugin + Logger.loader.Trace($"->'{plugin.Name}' loads here"); + outputOrder!.Add(plugin); + } // loadbefores have already been preprocessed into loadafters