Packages can be loaded/unloaded. IUnloadableService interface added whose method Unload, if service implements it, will be called when the module is unloaded.

This commit is contained in:
Master Kwoth
2017-10-05 00:51:12 +02:00
parent 599245b1ca
commit 33ac43e1b5
74 changed files with 866 additions and 520 deletions

View File

@ -1,6 +1,5 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
@ -17,57 +16,83 @@ namespace NadekoBot.Services
public interface INServiceProvider : IServiceProvider, IEnumerable<object>
{
T GetService<T>();
IEnumerable<Type> LoadFrom(Assembly assembly);
INServiceProvider AddManual<T>(T obj);
object Unload(Type t);
}
public class NServiceProvider : INServiceProvider
{
public class ServiceProviderBuilder
private readonly object _locker = new object();
private readonly Logger _log;
public readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
public IReadOnlyDictionary<Type, object> Services => _services;
public NServiceProvider()
{
private ConcurrentDictionary<Type, object> _dict = new ConcurrentDictionary<Type, object>();
private readonly Logger _log;
_log = LogManager.GetCurrentClassLogger();
}
public ServiceProviderBuilder()
public T GetService<T>()
{
return (T)((IServiceProvider)(this)).GetService(typeof(T));
}
object IServiceProvider.GetService(Type serviceType)
{
_services.TryGetValue(serviceType, out var toReturn);
return toReturn;
}
public INServiceProvider AddManual<T>(T obj)
{
lock (_locker)
{
_log = LogManager.GetCurrentClassLogger();
_services.TryAdd(typeof(T), obj);
}
return this;
}
public ServiceProviderBuilder AddManual<T>(T obj)
public IEnumerable<Type> LoadFrom(Assembly assembly)
{
List<Type> addedTypes = new List<Type>();
Type[] allTypes;
try
{
_dict.TryAdd(typeof(T), obj);
return this;
allTypes = assembly.GetTypes();
}
public NServiceProvider Build()
catch (ReflectionTypeLoadException ex)
{
return new NServiceProvider(_dict);
Console.WriteLine(ex.LoaderExceptions[0]);
return Enumerable.Empty<Type>();
}
public ServiceProviderBuilder LoadFrom(Assembly assembly)
{
var allTypes = assembly.GetTypes();
var services = new Queue<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract
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<NoPublicBot>() == null
&& x.GetTypeInfo().GetCustomAttribute<NoPublicBot>() == null
#endif
)
.ToArray());
.ToArray());
var interfaces = new HashSet<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& x.GetTypeInfo().IsInterface));
addedTypes.AddRange(services);
var alreadyFailed = new Dictionary<Type, int>();
var interfaces = new HashSet<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& x.GetTypeInfo().IsInterface));
var alreadyFailed = new Dictionary<Type, int>();
lock (_locker)
{
var sw = Stopwatch.StartNew();
var swInstance = new Stopwatch();
while (services.Count > 0)
{
var type = services.Dequeue(); //get a type i need to make an instance of
if (_dict.TryGetValue(type, out _)) // if that type is already instantiated, skip
if (_services.TryGetValue(type, out _)) // if that type is already instantiated, skip
continue;
var ctor = type.GetConstructors()[0];
@ -79,7 +104,7 @@ namespace NadekoBot.Services
var args = new List<object>(argTypes.Length);
foreach (var arg in argTypes) //get constructor arguments from the dictionary of already instantiated types
{
if (_dict.TryGetValue(arg, out var argObj)) //if i got current one, add it to the list of instances and move on
if (_services.TryGetValue(arg, out var argObj)) //if i got current one, add it to the list of instances and move on
args.Add(argObj);
else //if i failed getting it, add it to the end, and break
{
@ -97,7 +122,7 @@ namespace NadekoBot.Services
}
if (args.Count != argTypes.Length)
continue;
// _log.Info("Loading " + type.Name);
swInstance.Restart();
var instance = ctor.Invoke(args.ToArray());
swInstance.Stop();
@ -105,38 +130,34 @@ namespace NadekoBot.Services
_log.Info($"{type.Name} took {swInstance.Elapsed.TotalSeconds:F2}s to load.");
var interfaceType = interfaces.FirstOrDefault(x => instance.GetType().GetInterfaces().Contains(x));
if (interfaceType != null)
_dict.TryAdd(interfaceType, instance);
{
addedTypes.Add(interfaceType);
_services.TryAdd(interfaceType, instance);
}
_dict.TryAdd(type, instance);
_services.TryAdd(type, instance);
}
sw.Stop();
_log.Info($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
return this;
}
return addedTypes;
}
private readonly ImmutableDictionary<Type, object> _services;
private NServiceProvider() { }
public NServiceProvider(IDictionary<Type, object> services)
public object Unload(Type t)
{
this._services = services.ToImmutableDictionary();
}
public T GetService<T>()
{
return (T)((IServiceProvider)(this)).GetService(typeof(T));
}
object IServiceProvider.GetService(Type serviceType)
{
_services.TryGetValue(serviceType, out var toReturn);
return toReturn;
lock (_locker)
{
if (_services.TryGetValue(t, out var obj))
{
_services.Remove(t);
return obj;
}
}
return null;
}
IEnumerator IEnumerable.GetEnumerator() => _services.Values.GetEnumerator();
public IEnumerator<object> GetEnumerator() => _services.Values.GetEnumerator();
}
}
}