From 283f59b51f96e236e5adcdff16c12630feacefcd Mon Sep 17 00:00:00 2001 From: Harm Hoogeveen <33639379+HarmHoog@users.noreply.github.com> Date: Tue, 13 Apr 2021 22:28:24 +0200 Subject: [PATCH] Allow plugins to set dependencies, softdependencies and loadbefores to determine the load order of plugins (#385) Co-authored-by: js6pak --- src/Impostor.Api/Plugins/DependencyType.cs | 9 ++ .../Plugins/ImpostorDependencyAttribute.cs | 18 +++ .../Plugins/ImpostorPluginAttribute.cs | 6 +- src/Impostor.Plugins.Debugger/DebugPlugin.cs | 8 +- src/Impostor.Plugins.Example/ExamplePlugin.cs | 8 +- .../Plugins/DependencyInformation.cs | 18 +++ .../Plugins/PluginInformation.cs | 9 +- src/Impostor.Server/Plugins/PluginLoader.cs | 112 +++++++++++++++++- 8 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 src/Impostor.Api/Plugins/DependencyType.cs create mode 100644 src/Impostor.Api/Plugins/ImpostorDependencyAttribute.cs create mode 100644 src/Impostor.Server/Plugins/DependencyInformation.cs diff --git a/src/Impostor.Api/Plugins/DependencyType.cs b/src/Impostor.Api/Plugins/DependencyType.cs new file mode 100644 index 0000000..255e51e --- /dev/null +++ b/src/Impostor.Api/Plugins/DependencyType.cs @@ -0,0 +1,9 @@ +namespace Impostor.Api.Plugins +{ + public enum DependencyType + { + HardDependency, + SoftDependency, + LoadBefore, + } +} diff --git a/src/Impostor.Api/Plugins/ImpostorDependencyAttribute.cs b/src/Impostor.Api/Plugins/ImpostorDependencyAttribute.cs new file mode 100644 index 0000000..ae55299 --- /dev/null +++ b/src/Impostor.Api/Plugins/ImpostorDependencyAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Impostor.Api.Plugins +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ImpostorDependencyAttribute : Attribute + { + public ImpostorDependencyAttribute(string id, DependencyType type) + { + Id = id; + DependencyType = type; + } + + public string Id { get; } + + public DependencyType DependencyType { get; } + } +} diff --git a/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs b/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs index e2d6b02..c921f9f 100644 --- a/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs +++ b/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs @@ -5,15 +5,15 @@ namespace Impostor.Api.Plugins [AttributeUsage(AttributeTargets.Class)] public class ImpostorPluginAttribute : Attribute { - public ImpostorPluginAttribute(string package, string name, string author, string version) + public ImpostorPluginAttribute(string id, string name, string author, string version) { - Package = package; + Id = id; Name = name; Author = author; Version = version; } - public string Package { get; } + public string Id { get; } public string Name { get; } diff --git a/src/Impostor.Plugins.Debugger/DebugPlugin.cs b/src/Impostor.Plugins.Debugger/DebugPlugin.cs index a978619..c22442c 100644 --- a/src/Impostor.Plugins.Debugger/DebugPlugin.cs +++ b/src/Impostor.Plugins.Debugger/DebugPlugin.cs @@ -1,12 +1,8 @@ -using Impostor.Api.Plugins; +using Impostor.Api.Plugins; namespace Impostor.Plugins.Debugger { - [ImpostorPlugin( - package: "gg.impostor.debugger", - name: "Debugger", - author: "Gerard", - version: "1.0.0")] + [ImpostorPlugin("gg.impostor.debugger", "Debugger", "Gerard", "1.0.0")] public class DebugPlugin : PluginBase { } diff --git a/src/Impostor.Plugins.Example/ExamplePlugin.cs b/src/Impostor.Plugins.Example/ExamplePlugin.cs index a3c50e4..7d88079 100644 --- a/src/Impostor.Plugins.Example/ExamplePlugin.cs +++ b/src/Impostor.Plugins.Example/ExamplePlugin.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Impostor.Api.Games.Managers; using Impostor.Api.Innersloth; using Impostor.Api.Plugins; @@ -6,11 +6,7 @@ using Microsoft.Extensions.Logging; namespace Impostor.Plugins.Example { - [ImpostorPlugin( - package: "gg.impostor.example", - name: "Example", - author: "AeonLucid", - version: "1.0.0")] + [ImpostorPlugin("gg.impostor.example", "Example", "AeonLucid", "1.0.0")] public class ExamplePlugin : PluginBase { private readonly ILogger _logger; diff --git a/src/Impostor.Server/Plugins/DependencyInformation.cs b/src/Impostor.Server/Plugins/DependencyInformation.cs new file mode 100644 index 0000000..23bf935 --- /dev/null +++ b/src/Impostor.Server/Plugins/DependencyInformation.cs @@ -0,0 +1,18 @@ +using Impostor.Api.Plugins; + +namespace Impostor.Server.Plugins +{ + public class DependencyInformation + { + private readonly ImpostorDependencyAttribute _attribute; + + public DependencyInformation(ImpostorDependencyAttribute attribute) + { + _attribute = attribute; + } + + public string Id => _attribute.Id; + + public DependencyType DependencyType => _attribute.DependencyType; + } +} diff --git a/src/Impostor.Server/Plugins/PluginInformation.cs b/src/Impostor.Server/Plugins/PluginInformation.cs index f9f61a1..7da7671 100644 --- a/src/Impostor.Server/Plugins/PluginInformation.cs +++ b/src/Impostor.Server/Plugins/PluginInformation.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using Impostor.Api.Plugins; @@ -12,11 +14,12 @@ namespace Impostor.Server.Plugins { _attribute = pluginType.GetCustomAttribute()!; + Dependencies = pluginType.GetCustomAttributes().Select(t => new DependencyInformation(t)).ToList(); Startup = startup; PluginType = pluginType; } - public string Package => _attribute.Package; + public string Id => _attribute.Id; public string Name => _attribute.Name; @@ -24,6 +27,8 @@ namespace Impostor.Server.Plugins public string Version => _attribute.Version; + public List Dependencies { get; } + public IPluginStartup? Startup { get; } public Type PluginType { get; } @@ -32,7 +37,7 @@ namespace Impostor.Server.Plugins public override string ToString() { - return $"{Package} {Name} ({Version}) by {Author}"; + return $"{Id} {Name} ({Version}) by {Author}"; } } } diff --git a/src/Impostor.Server/Plugins/PluginLoader.cs b/src/Impostor.Server/Plugins/PluginLoader.cs index 90b9caf..529cc16 100644 --- a/src/Impostor.Server/Plugins/PluginLoader.cs +++ b/src/Impostor.Server/Plugins/PluginLoader.cs @@ -101,19 +101,21 @@ namespace Impostor.Server.Plugins .Select(Activator.CreateInstance) .Cast() .FirstOrDefault(), - plugin.First())); + plugin.Single())); } - foreach (var plugin in plugins) + var orderedPlugins = LoadOrderPlugins(plugins); + + foreach (var plugin in orderedPlugins) { plugin.Startup?.ConfigureHost(builder); } builder.ConfigureServices(services => { - services.AddHostedService(provider => ActivatorUtilities.CreateInstance(provider, plugins)); + services.AddHostedService(provider => ActivatorUtilities.CreateInstance(provider, orderedPlugins)); - foreach (var plugin in plugins) + foreach (var plugin in orderedPlugins) { plugin.Startup?.ConfigureServices(services); } @@ -155,5 +157,107 @@ namespace Impostor.Server.Plugins assemblyInfos.Add(new AssemblyInformation(assemblyName, path, isPlugin)); } } + + private static List LoadOrderPlugins(IEnumerable plugins) + { + var pluginDictionary = new Dictionary(); + var hardDependencies = new Dictionary>(); + + foreach (var plugin in plugins) + { + pluginDictionary[plugin.Id] = plugin; + hardDependencies[plugin.Id] = plugin + .Dependencies + .Where(p => p.DependencyType == DependencyType.HardDependency) + .Select(p => p.Id) + .ToList(); + } + + var presentPlugins = pluginDictionary.Keys.ToList(); + + // Check whether the Hard Dependencies are present and remove those without. + var checkedPlugins = CheckHardDependencies(presentPlugins, hardDependencies); + + var dependencyGraph = checkedPlugins.ToDictionary(p => p, _ => new List()); + + foreach (var plugin in checkedPlugins) + { + foreach (var dependency in pluginDictionary[plugin].Dependencies.Where(d => checkedPlugins.Contains(d.Id))) + { + if (dependency.DependencyType == DependencyType.LoadBefore) + { + dependencyGraph[dependency.Id].Add(plugin); + } + else + { + dependencyGraph[plugin].Add(dependency.Id); + } + } + } + + var processed = new List(); + var ordered = new List(); + foreach (var plugin in checkedPlugins) + { + if (!processed.Contains(plugin)) + { + RecursiveOrder(plugin, dependencyGraph, processed, ordered, pluginDictionary); + } + } + + return ordered; + } + + private static List CheckHardDependencies( + List plugins, + IReadOnlyDictionary> hardDependencies) + { + foreach (var plugin in plugins) + { + if (!hardDependencies.ContainsKey(plugin)) + { + continue; + } + + foreach (var dependency in hardDependencies[plugin].Where(dependency => !plugins.Contains(dependency))) + { + Logger.Error( + "The plugin {plugin} has defined the plugin {dependency} as a hard dependency but its not present! {plugin} will not loaded.", + plugin, + dependency, + plugin + ); + + // Remove the plugin from the plugins to load. + plugins.Remove(plugin); + + // Since other plugins might have defined the removed plugin as a hard dependency a recheck is necessary. + return CheckHardDependencies(plugins, hardDependencies); + } + } + + return plugins; + } + + private static void RecursiveOrder( + string plugin, + IReadOnlyDictionary> dependencyGraph, + ICollection processed, + ICollection ordered, + IReadOnlyDictionary pluginDictionary) + { + processed.Add(plugin); + + foreach (var dependency in dependencyGraph[plugin]) + { + // First add the dependencies using a recursive call before adding itself. + if (!processed.Contains(dependency)) + { + RecursiveOrder(dependency, dependencyGraph, processed, ordered, pluginDictionary); + } + } + + ordered.Add(pluginDictionary[plugin]); + } } } -- 2.39.5