]> git.deb.at Git - rhonda/impostor.git/commitdiff
Allow plugins to set dependencies, softdependencies and loadbefores to determine...
authorHarm Hoogeveen <33639379+HarmHoog@users.noreply.github.com>
Tue, 13 Apr 2021 20:28:24 +0000 (22:28 +0200)
committerGitHub <noreply@github.com>
Tue, 13 Apr 2021 20:28:24 +0000 (20:28 +0000)
Co-authored-by: js6pak <kubastaron@hotmail.com>
src/Impostor.Api/Plugins/DependencyType.cs [new file with mode: 0644]
src/Impostor.Api/Plugins/ImpostorDependencyAttribute.cs [new file with mode: 0644]
src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs
src/Impostor.Plugins.Debugger/DebugPlugin.cs
src/Impostor.Plugins.Example/ExamplePlugin.cs
src/Impostor.Server/Plugins/DependencyInformation.cs [new file with mode: 0644]
src/Impostor.Server/Plugins/PluginInformation.cs
src/Impostor.Server/Plugins/PluginLoader.cs

diff --git a/src/Impostor.Api/Plugins/DependencyType.cs b/src/Impostor.Api/Plugins/DependencyType.cs
new file mode 100644 (file)
index 0000000..255e51e
--- /dev/null
@@ -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 (file)
index 0000000..ae55299
--- /dev/null
@@ -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; }
+    }
+}
index e2d6b024953af8f84aa30d452d9fbf105719999b..c921f9f1717db58b159599b0f24081b11ac25d3a 100644 (file)
@@ -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; }
 
index a978619bb904e4d460458872592eb4be0acc0e46..c22442c8f3a6191df284bf557b7f0cf27b639b01 100644 (file)
@@ -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
     {
     }
index a3c50e47cd5738589709f69a4775a2751aad97b4..7d88079b9c93684d98cd619548ff160545162427 100644 (file)
@@ -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<ExamplePlugin> _logger;
diff --git a/src/Impostor.Server/Plugins/DependencyInformation.cs b/src/Impostor.Server/Plugins/DependencyInformation.cs
new file mode 100644 (file)
index 0000000..23bf935
--- /dev/null
@@ -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;
+    }
+}
index f9f61a1febdb74b3a7fb2610b78505d67c3f37d2..7da76716803400e2b01bd7b4134c07ea6c5f13bf 100644 (file)
@@ -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<ImpostorPluginAttribute>()!;
 
+            Dependencies = pluginType.GetCustomAttributes<ImpostorDependencyAttribute>().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<DependencyInformation> 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}";
         }
     }
 }
index 90b9caf8309b1eb94176f70c5cd36d963caaf833..529cc169708e62e13943a569efb9ffd24919f6d7 100644 (file)
@@ -101,19 +101,21 @@ namespace Impostor.Server.Plugins
                         .Select(Activator.CreateInstance)
                         .Cast<IPluginStartup>()
                         .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<PluginLoaderService>(provider, plugins));
+                services.AddHostedService(provider => ActivatorUtilities.CreateInstance<PluginLoaderService>(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<PluginInformation> LoadOrderPlugins(IEnumerable<PluginInformation> plugins)
+        {
+            var pluginDictionary = new Dictionary<string, PluginInformation>();
+            var hardDependencies = new Dictionary<string, List<string>>();
+
+            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<string>());
+
+            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<string>();
+            var ordered = new List<PluginInformation>();
+            foreach (var plugin in checkedPlugins)
+            {
+                if (!processed.Contains(plugin))
+                {
+                    RecursiveOrder(plugin, dependencyGraph, processed, ordered, pluginDictionary);
+                }
+            }
+
+            return ordered;
+        }
+
+        private static List<string> CheckHardDependencies(
+            List<string> plugins,
+            IReadOnlyDictionary<string, List<string>> 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<string, List<string>> dependencyGraph,
+            ICollection<string> processed,
+            ICollection<PluginInformation> ordered,
+            IReadOnlyDictionary<string, PluginInformation> 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]);
+        }
     }
 }