Allow plugins to set dependencies, softdependencies and loadbefores to determine the load order of plugins (#385)

Co-authored-by: js6pak <kubastaron@hotmail.com>
This commit is contained in:
Harm Hoogeveenandjs6pak authored and GitHub committed 2021-04-13 20:28:24 +00:00
1 parent d47343df08
commit 283f59b51f
8 files changed
+167 -21

No files matched your search

@@ -0,0 +1,9 @@
namespace Impostor.Api.Plugins
{
public enum DependencyType
{
HardDependency,
SoftDependency,
LoadBefore,
}
}
@@ -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; }
}
}
@@ -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; }
+2 -6
View 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
{
}
@@ -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;
@@ -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;
}
}
@@ -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}";
}
}
}
+108 -4
View 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]);
}
}
}