Don't load executor behaviors as a dep, but initialize after all services are loaded. Experimenting on services

This commit is contained in:
Kwoth
2021-07-01 23:53:06 +02:00
parent 3c82c1f919
commit 9f34f8f00f
5 changed files with 35 additions and 94 deletions

View File

@@ -21,9 +21,6 @@ using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Common.Configs; using NadekoBot.Common.Configs;
using NadekoBot.Db; using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Modules.CustomReactions.Services;
using NadekoBot.Modules.Utility.Services;
using Serilog; using Serilog;
namespace NadekoBot namespace NadekoBot
@@ -132,8 +129,6 @@ namespace NadekoBot
AllowAutoRedirect = false AllowAutoRedirect = false
}); });
svcs.LoadFrom(Assembly.GetAssembly(typeof(CommandHandler)));
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1") if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
{ {
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>(); svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
@@ -144,6 +139,7 @@ namespace NadekoBot
.AddSingleton<IReadyExecutor>(x => (IReadyExecutor)x.GetRequiredService<ICoordinator>()); .AddSingleton<IReadyExecutor>(x => (IReadyExecutor)x.GetRequiredService<ICoordinator>());
} }
// todo no public bot attribute
svcs.Scan(scan => scan svcs.Scan(scan => scan
.FromAssemblyOf<IReadyExecutor>() .FromAssemblyOf<IReadyExecutor>()
.AddClasses(classes => classes.AssignableTo<IReadyExecutor>()) .AddClasses(classes => classes.AssignableTo<IReadyExecutor>())
@@ -160,6 +156,12 @@ namespace NadekoBot
.AsSelf() .AsSelf()
.AsImplementedInterfaces() .AsImplementedInterfaces()
.WithSingletonLifetime() .WithSingletonLifetime()
// services
.AddClasses(classes => classes.AssignableTo<INService>())
.AsImplementedInterfaces()
.AsSelf()
.WithSingletonLifetime()
); );
// svcs.AddSingleton<IReadyExecutor>(x => x.GetService<SelfService>()); // svcs.AddSingleton<IReadyExecutor>(x => x.GetService<SelfService>());
@@ -168,7 +170,8 @@ namespace NadekoBot
//initialize Services //initialize Services
Services = svcs.BuildServiceProvider(); Services = svcs.BuildServiceProvider();
var commandHandler = Services.GetService<CommandHandler>(); var exec = Services.GetRequiredService<IBehaviourExecutor>();
exec.Initialize();
if (Client.ShardId == 0) if (Client.ShardId == 0)
{ {

View File

@@ -3,7 +3,6 @@ using Discord.Commands;
using Discord.Net; using Discord.Net;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Common.Collections; using NadekoBot.Common.Collections;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -12,7 +11,6 @@ using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common.Configs; using NadekoBot.Common.Configs;
using NadekoBot.Db; using NadekoBot.Db;
using Serilog; using Serilog;
@@ -28,9 +26,9 @@ namespace NadekoBot.Services
private readonly BotConfigService _bss; private readonly BotConfigService _bss;
private readonly Bot _bot; private readonly Bot _bot;
private readonly IBehaviourExecutor _behaviourExecutor; private readonly IBehaviourExecutor _behaviourExecutor;
private IServiceProvider _services; private readonly IServiceProvider _services;
private ConcurrentDictionary<ulong, string> _prefixes { get; } = new ConcurrentDictionary<ulong, string>(); private readonly ConcurrentDictionary<ulong, string> _prefixes;
public event Func<IUserMessage, CommandInfo, Task> CommandExecuted = delegate { return Task.CompletedTask; }; public event Func<IUserMessage, CommandInfo, Task> CommandExecuted = delegate { return Task.CompletedTask; };
public event Func<CommandInfo, ITextChannel, string, Task> CommandErrored = delegate { return Task.CompletedTask; }; public event Func<CommandInfo, ITextChannel, string, Task> CommandErrored = delegate { return Task.CompletedTask; };
@@ -106,7 +104,8 @@ namespace NadekoBot.Services
gc.Prefix = prefix; gc.Prefix = prefix;
uow.SaveChanges(); uow.SaveChanges();
} }
_prefixes.AddOrUpdate(guild.Id, prefix, (key, old) => prefix);
_prefixes[guild.Id] = prefix;
return prefix; return prefix;
} }
@@ -179,8 +178,8 @@ namespace NadekoBot.Services
"Message: {3}\n\t" + "Message: {3}\n\t" +
"Error: {4}", "Error: {4}",
usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0}
(channel is null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} channel is null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]", // {1}
(channel is null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} channel is null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]", // {2}
usrMsg.Content,// {3} usrMsg.Content,// {3}
errorMessage errorMessage
//exec.Result.ErrorReason // {4} //exec.Result.ErrorReason // {4}

View File

@@ -2,6 +2,7 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
@@ -11,5 +12,7 @@ namespace NadekoBot.Services
public Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg); public Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg);
Task<bool> RunLateBlockersAsync(ICommandContext context, CommandInfo cmd); Task<bool> RunLateBlockersAsync(ICommandContext context, CommandInfo cmd);
Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg); Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg);
public void Initialize();
} }
} }

View File

@@ -7,29 +7,29 @@ using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using Serilog; using Serilog;
using Microsoft.Extensions.DependencyInjection;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
public sealed class BehaviorExecutor : IBehaviourExecutor public sealed class BehaviorExecutor : IBehaviourExecutor
{ {
private readonly DiscordSocketClient _client; private readonly IServiceProvider _services;
private readonly IEnumerable<ILateExecutor> _lateExecutors; private IEnumerable<ILateExecutor> _lateExecutors;
private readonly IEnumerable<ILateBlocker> _lateBlockers; private IEnumerable<ILateBlocker> _lateBlockers;
private readonly IEnumerable<IEarlyBehavior> _earlyBehaviors; private IEnumerable<IEarlyBehavior> _earlyBehaviors;
private readonly IEnumerable<IInputTransformer> _transformers; private IEnumerable<IInputTransformer> _transformers;
public BehaviorExecutor( public BehaviorExecutor(IServiceProvider services)
DiscordSocketClient client,
IEnumerable<ILateExecutor> lateExecutors,
IEnumerable<ILateBlocker> lateBlockers,
IEnumerable<IEarlyBehavior> earlyBehaviors,
IEnumerable<IInputTransformer> transformers)
{ {
_client = client; _services = services;
_lateExecutors = lateExecutors; }
_lateBlockers = lateBlockers;
_earlyBehaviors = earlyBehaviors; public void Initialize()
_transformers = transformers; {
_lateExecutors = _services.GetServices<ILateExecutor>();
_lateBlockers = _services.GetServices<ILateBlocker>();
_earlyBehaviors = _services.GetServices<IEarlyBehavior>();
_transformers = _services.GetServices<IInputTransformer>();
} }
// todo early behaviors should print for themselves // todo early behaviors should print for themselves

View File

@@ -355,69 +355,5 @@ namespace NadekoBot.Extensions
return msg.Content.Headers.ContentLength / 1.MB(); return msg.Content.Headers.ContentLength / 1.MB();
} }
public static IEnumerable<Type> LoadFrom(this IServiceCollection collection, Assembly assembly)
{
// list of all the types which are added with this method
List<Type> addedTypes = new List<Type>();
Type[] allTypes;
try
{
// first, get all types in te assembly
allTypes = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
Log.Error(ex, "Error loading assembly types");
return Enumerable.Empty<Type>();
}
// all types which have INService implementation are services
// which are supposed to be loaded with this method
// ignore all interfaces and abstract classes
var services = new Queue<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract
#if GLOBAL_NADEKO
&& x.GetTypeInfo().GetCustomAttribute<NoPublicBotAttribute>() is null
#endif
)
.ToArray());
// we will just return those types when we're done instantiating them
addedTypes.AddRange(services);
// get all interfaces which inherit from INService
// as we need to also add a service for each one of interfaces
// so that DI works for them too
var interfaces = new HashSet<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& x.GetTypeInfo().IsInterface));
// keep instantiating until we've instantiated them all
while (services.Count > 0)
{
var serviceType = services.Dequeue(); //get a type i need to add
if (collection.FirstOrDefault(x => x.ServiceType == serviceType) != null) // if that type is already added, skip
continue;
//also add the same type
var interfaceType = interfaces.FirstOrDefault(x => serviceType.GetInterfaces().Contains(x));
if (interfaceType != null)
{
addedTypes.Add(interfaceType);
collection.AddSingleton(interfaceType, serviceType);
}
else
{
collection.AddSingleton(serviceType, serviceType);
}
}
return addedTypes;
}
} }
} }