mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-20 01:46:11 +00:00
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:
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; }
|
||||
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user