]> git.deb.at Git - rhonda/impostor.git/commitdiff
Implemented EventListener priorities (#62)
authorGerard Smit <gsmit1996@gmail.com>
Wed, 21 Oct 2020 12:55:03 +0000 (14:55 +0200)
committerGitHub <noreply@github.com>
Wed, 21 Oct 2020 12:55:03 +0000 (14:55 +0200)
* Improved EventManager

Now it actually will order the event listeners on their priority. Also I've cleaned up the registering of temporary event listeners.

* Ignore new Impostor.Api library while loading plugins

* Fixed Dockerfile

14 files changed:
Dockerfile
src/Impostor.Api/Events/Attributes/EventListenerAttribute.cs
src/Impostor.Api/Events/Managers/IEventManager.cs
src/Impostor.Server/Events/EventHandler.cs
src/Impostor.Server/Events/EventManager.cs
src/Impostor.Server/Events/Register/IRegisteredEventListener.cs [new file with mode: 0644]
src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs [new file with mode: 0644]
src/Impostor.Server/Events/Register/RegisteredEventListener.cs [new file with mode: 0644]
src/Impostor.Server/Events/Register/TemporaryEventRegister.cs [new file with mode: 0644]
src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs [new file with mode: 0644]
src/Impostor.Server/Events/RegisteredEventListener.cs [deleted file]
src/Impostor.Server/Events/TemporaryEventRegister.cs [deleted file]
src/Impostor.Server/Plugins/PluginLoader.cs
src/Impostor.Tests/Events/EventManagerTests.cs [new file with mode: 0644]

index 49fddc9c87b3ee9be82416f0ed7d931b4b2cd9b4..3066607985489c053e96a97b553a07577308aa18 100644 (file)
@@ -8,10 +8,8 @@ WORKDIR /source
 
 # Copy csproj and restore.
 COPY src/Impostor.Server/Impostor.Server.csproj ./src/Impostor.Server/Impostor.Server.csproj
-COPY src/Impostor.Server.Api/Impostor.Server.Api.csproj ./src/Impostor.Server.Api/Impostor.Server.Api.csproj
-COPY src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj ./src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj
-COPY src/Impostor.Shared/Impostor.Shared.csproj ./src/Impostor.Shared/Impostor.Shared.csproj
-COPY submodules/Hazel-Networking/Hazel/Hazel.csproj ./submodules/Hazel-Networking/Hazel/Hazel.csproj
+COPY src/Impostor.Api/Impostor.Api.csproj ./src/Impostor.Api/Impostor.Api.csproj
+COPY src/Impostor.Hazel/Impostor.Hazel.csproj ./src/Impostor.Hazel/Impostor.Hazel.csproj
 
 RUN case "$TARGETARCH" in \
     amd64)  NETCORE_PLATFORM='linux-x64';; \
@@ -20,13 +18,10 @@ RUN case "$TARGETARCH" in \
     *) echo "unsupported architecture"; exit 1 ;; \
   esac && \
   dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Server/Impostor.Server.csproj && \
-  dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Server.Api/Impostor.Server.Api.csproj && \
-  dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Server.Hazel/Impostor.Server.Hazel.csproj && \
-  dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Shared/Impostor.Shared.csproj && \
-  dotnet restore -r "$NETCORE_PLATFORM" ./submodules/Hazel-Networking/Hazel/Hazel.csproj
+  dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Api/Impostor.Api.csproj && \
+  dotnet restore -r "$NETCORE_PLATFORM" ./src/Impostor.Hazel/Impostor.Hazel.csproj
 
 # Copy everything else.
-COPY submodules/. ./submodules/
 COPY src/. ./src/
 RUN case "$TARGETARCH" in \
     amd64)  NETCORE_PLATFORM='linux-x64';; \
index a37394653be3f07b5dd149e8ad69ecadbf061185..b31d2d1a7c31df46386fb8dbe7d1298b877ca7ec 100644 (file)
@@ -30,10 +30,5 @@ namespace Impostor.Api.Events
         ///     If set to true, the listener will be called regardless of the <see cref="IEventCancelable.IsCancelled"/>.
         /// </summary>
         public bool IgnoreCancelled { get; set; }
-
-        /// <summary>
-        ///     The order of the priority.
-        /// </summary>
-        public int PriorityOrder { get; set; } = 100;
     }
 }
\ No newline at end of file
index a9d433bfe24165e1ef3b84c47c6912e9313d136b..07a7f7cf134a98ee39ea65e936e612eda8b9497c 100644 (file)
@@ -5,15 +5,6 @@ namespace Impostor.Api.Events.Managers
 {
     public interface IEventManager
     {
-        /// <summary>
-        ///     Register a temporary event listener.
-        /// </summary>
-        /// <param name="callback">Event callback.</param>
-        /// <returns>Disposable that unregisters the callback from the event manager.</returns>
-        /// <typeparam name="TEvent">Type of the event.</typeparam>
-        IDisposable Register<TEvent>(Func<IServiceProvider, TEvent, ValueTask> callback)
-            where TEvent : IEvent;
-
         /// <summary>
         ///     Register a temporary event listener.
         /// </summary>
index e4582ca7a8e4e48199d71ed78d3dd4447ee9162d..190f7f398918143bdf5a91e4eb0b95371d54a6b5 100644 (file)
@@ -1,10 +1,11 @@
 using Impostor.Api.Events;
+using Impostor.Server.Events.Register;
 
 namespace Impostor.Server.Events
 {
     internal readonly struct EventHandler
     {
-        public EventHandler(IEventListener o, RegisteredEventListener listener)
+        public EventHandler(IEventListener o, IRegisteredEventListener listener)
         {
             Object = o;
             Listener = listener;
@@ -12,9 +13,9 @@ namespace Impostor.Server.Events
 
         public IEventListener Object { get; }
 
-        public RegisteredEventListener Listener { get; }
+        public IRegisteredEventListener Listener { get; }
 
-        public void Deconstruct(out IEventListener o, out RegisteredEventListener listener)
+        public void Deconstruct(out IEventListener o, out IRegisteredEventListener listener)
         {
             o = Object;
             listener = Listener;
index 05a3e70d90a574aa1155a726fe2e559817a30633..504fcd0ce13a9f1872116e492de8d8411db7d890 100644 (file)
@@ -6,30 +6,20 @@ using System.Reflection;
 using System.Threading.Tasks;
 using Impostor.Api.Events;
 using Impostor.Api.Events.Managers;
+using Impostor.Server.Events.Register;
 using Microsoft.Extensions.DependencyInjection;
 
 namespace Impostor.Server.Events
 {
     internal class EventManager : IEventManager
     {
-        private readonly ConcurrentDictionary<Type, object> _temporaryEventListeners;
+        private readonly ConcurrentDictionary<Type, TemporaryEventRegister> _temporaryEventListeners;
         private readonly IServiceProvider _serviceProvider;
 
         public EventManager(IServiceProvider serviceProvider)
         {
             _serviceProvider = serviceProvider;
-            _temporaryEventListeners = new ConcurrentDictionary<Type, object>();
-        }
-
-        /// <inheritdoc />
-        public IDisposable Register<TEvent>(Func<IServiceProvider, TEvent, ValueTask> callback)
-            where TEvent : IEvent
-        {
-            var register = (TemporaryEventRegister<TEvent>) _temporaryEventListeners.GetOrAdd(
-                typeof(TEvent),
-                _ => new TemporaryEventRegister<TEvent>());
-
-            return register.Add(callback);
+            _temporaryEventListeners = new ConcurrentDictionary<Type, TemporaryEventRegister>();
         }
 
         /// <inheritdoc />
@@ -41,17 +31,23 @@ namespace Impostor.Server.Events
                 throw new ArgumentNullException(nameof(listener));
             }
 
-            var registerMethod = typeof(EventManager).GetMethod(nameof(RegisterListenerImpl), BindingFlags.Instance | BindingFlags.NonPublic);
-            var methods = RegisteredEventListener.FromType(listener.GetType());
-            var disposes = new IDisposable[methods.Count];
+            var eventListeners = RegisteredEventListener.FromType(listener.GetType());
+            var disposes = new IDisposable[eventListeners.Count];
 
-            for (var i = 0; i < methods.Count; i++)
+            foreach (var eventListener in eventListeners)
             {
-                var method = methods[i];
+                IRegisteredEventListener wrappedEventListener = new WrappedRegisteredEventListener(eventListener, listener);
+
+                if (invoker != null)
+                {
+                    wrappedEventListener = new InvokedRegisteredEventListener(wrappedEventListener, invoker);
+                }
+
+                var register = _temporaryEventListeners.GetOrAdd(
+                    wrappedEventListener.EventType,
+                    _ => new TemporaryEventRegister());
 
-                disposes[i] = (IDisposable) registerMethod!
-                    .MakeGenericMethod(method.EventType)
-                    .Invoke(this, new object[] { listener, method, invoker });
+                register.Add(wrappedEventListener);
             }
 
             return new MultiDisposable(disposes);
@@ -74,15 +70,11 @@ namespace Impostor.Server.Events
 
             try
             {
-                foreach (var (handler, eventListener) in GetHandlers<T>(scope.ServiceProvider))
+                foreach (var (handler, eventListener) in GetHandlers<T>(scope.ServiceProvider)
+                    .OrderByDescending(e => e.Listener.Priority))
                 {
                     await eventListener.InvokeAsync(handler, @event, scope.ServiceProvider);
                 }
-
-                if (_temporaryEventListeners.TryGetValue(typeof(T), out var cb))
-                {
-                    await ((TemporaryEventRegister<T>) cb).CallAsync(scope.ServiceProvider, @event);
-                }
             }
             finally
             {
@@ -95,7 +87,7 @@ namespace Impostor.Server.Events
         /// </summary>
         /// <param name="services">Current service provider.</param>
         /// <returns>The event listeners.</returns>
-        private static IEnumerable<EventHandler> GetHandlers<TEvent>(IServiceProvider services)
+        private IEnumerable<EventHandler> GetHandlers<TEvent>(IServiceProvider services)
             where TEvent : IEvent
         {
             foreach (var handler in services.GetServices<IEventListener>())
@@ -112,14 +104,14 @@ namespace Impostor.Server.Events
                     yield return new EventHandler(handler, eventHandler);
                 }
             }
-        }
 
-        private IDisposable RegisterListenerImpl<TEvent>(object obj, RegisteredEventListener listener, Func<Func<Task>, Task> invoker = null)
-            where TEvent : IEvent
-        {
-            return invoker == null
-                ? Register<TEvent>((provider, @event) => listener.InvokeAsync(obj, @event, provider))
-                : Register<TEvent>((provider, @event) => new ValueTask(invoker(() => listener.InvokeAsync(obj, @event, provider).AsTask())));
+            if (_temporaryEventListeners.TryGetValue(typeof(TEvent), out var cb))
+            {
+                foreach (var eventListener in cb.GetEventListeners())
+                {
+                    yield return new EventHandler(null, eventListener);
+                }
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs b/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs
new file mode 100644 (file)
index 0000000..479a3f6
--- /dev/null
@@ -0,0 +1,15 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+    internal interface IRegisteredEventListener
+    {
+        Type EventType { get; }
+
+        EventPriority Priority { get; }
+
+        ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider);
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs b/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs
new file mode 100644 (file)
index 0000000..a21c3b1
--- /dev/null
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+    internal class InvokedRegisteredEventListener : IRegisteredEventListener
+    {
+        private readonly IRegisteredEventListener _innerObject;
+        private readonly Func<Func<Task>, Task> _invoker;
+
+        public InvokedRegisteredEventListener(IRegisteredEventListener innerObject, Func<Func<Task>, Task> invoker)
+        {
+            _innerObject = innerObject;
+            _invoker = invoker;
+        }
+
+        public Type EventType => _innerObject.EventType;
+
+        public EventPriority Priority => _innerObject.Priority;
+
+        public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+        {
+            return new ValueTask(_invoker(() => _innerObject.InvokeAsync(eventHandler, @event, provider).AsTask()));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/Register/RegisteredEventListener.cs b/src/Impostor.Server/Events/Register/RegisteredEventListener.cs
new file mode 100644 (file)
index 0000000..0c49a07
--- /dev/null
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Impostor.Server.Events.Register
+{
+    internal class RegisteredEventListener : IRegisteredEventListener
+    {
+        private static readonly ConcurrentDictionary<Type, RegisteredEventListener[]> Instances = new ConcurrentDictionary<Type, RegisteredEventListener[]>();
+        private readonly Func<object, object, IServiceProvider, ValueTask> _invoker;
+        private readonly Type _eventListenerType;
+
+        public RegisteredEventListener(Type eventType, MethodInfo method, EventListenerAttribute attribute, Type eventListenerType)
+        {
+            EventType = eventType;
+            _eventListenerType = eventListenerType;
+            Priority = attribute.Priority;
+            IgnoreCancelled = attribute.IgnoreCancelled;
+            Method = method.GetFriendlyName(showParameters: false);
+            _invoker = CreateInvoker(method, attribute.IgnoreCancelled);
+        }
+
+        public Type EventType { get; }
+
+        public EventPriority Priority { get; }
+
+        public int PriorityOrder { get; set; }
+
+        public bool IgnoreCancelled { get; }
+
+        public string Method { get; }
+
+        public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+        {
+            return _invoker(eventHandler, @event, provider);
+        }
+
+        private Func<object, object, IServiceProvider, ValueTask> CreateInvoker(MethodInfo method, bool ignoreCancelled)
+        {
+            var instance = Expression.Parameter(typeof(object), "instance");
+            var eventParameter = Expression.Parameter(typeof(object), "event");
+            var provider = Expression.Parameter(typeof(IServiceProvider), "provider");
+            var @event = Expression.Convert(eventParameter, EventType);
+
+            var getRequiredService = typeof(ServiceProviderServiceExtensions)
+                .GetMethod("GetRequiredService", new[] { typeof(IServiceProvider) });
+
+            if (getRequiredService == null)
+            {
+                throw new InvalidOperationException("The method GetRequiredService could not be found.");
+            }
+
+            var methodArguments = method.GetParameters();
+            var arguments = new Expression[methodArguments.Length];
+
+            for (var i = 0; i < methodArguments.Length; i++)
+            {
+                var methodArgument = methodArguments[i];
+
+                if (typeof(IEvent).IsAssignableFrom(methodArgument.ParameterType)
+                    && methodArgument.ParameterType.IsAssignableFrom(EventType))
+                {
+                    arguments[i] = @event;
+                }
+                else
+                {
+                    arguments[i] = Expression.Call(
+                        getRequiredService.MakeGenericMethod(methodArgument.ParameterType),
+                        provider);
+                }
+            }
+
+            var returnTarget = Expression.Label(typeof(ValueTask));
+            Expression invoke = Expression.Call(Expression.Convert(instance, _eventListenerType), method, arguments);
+
+            if (method.ReturnType == typeof(void))
+            {
+                if (!ignoreCancelled && typeof(IEventCancelable).IsAssignableFrom(EventType))
+                {
+                    invoke = Expression.Block(
+                        Expression.IfThenElse(
+                            Expression.Property(@event, nameof(IEventCancelable.IsCancelled)),
+                            Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
+                            Expression.Block(
+                                invoke,
+                                Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))))),
+                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
+                }
+                else
+                {
+                    invoke = Expression.Block(
+                        invoke,
+                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
+                }
+            }
+            else if (method.ReturnType == typeof(ValueTask))
+            {
+                if (!ignoreCancelled && typeof(IEventCancelable).IsAssignableFrom(EventType))
+                {
+                    invoke = Expression.Block(
+                        Expression.IfThenElse(
+                            Expression.Property(@event, nameof(IEventCancelable.IsCancelled)),
+                            Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
+                            Expression.Return(returnTarget, invoke)),
+                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
+                }
+            }
+            else
+            {
+                throw new InvalidOperationException($"The method {method.GetFriendlyName()} must return void or ValueTask.");
+            }
+
+            return Expression.Lambda<Func<object, object, IServiceProvider, ValueTask>>(invoke,  instance, eventParameter, provider)
+                .Compile();
+        }
+
+        public static IReadOnlyList<RegisteredEventListener> FromType(Type type)
+        {
+            return Instances.GetOrAdd(type, t =>
+            {
+                return t.GetMethods()
+                    .Where(m => !m.IsStatic && m.GetCustomAttributes(typeof(EventListenerAttribute), false).Any())
+                    .SelectMany(m => FromMethod(t, m))
+                    .ToArray();
+            });
+        }
+
+        public static IEnumerable<RegisteredEventListener> FromMethod(Type listenerType, MethodInfo methodType)
+        {
+            // Get the return type.
+            var returnType = methodType.ReturnType;
+
+            if (returnType != typeof(void) && returnType != typeof(ValueTask))
+            {
+                throw new InvalidOperationException($"The method {methodType.GetFriendlyName()} does not return void or ValueTask.");
+            }
+
+            // Register the event.
+            foreach (var attribute in methodType.GetCustomAttributes<EventListenerAttribute>(false))
+            {
+                var eventType = attribute.Event;
+
+                if (eventType == null)
+                {
+                    if (methodType.GetParameters().Length == 0 || !typeof(IEvent).IsAssignableFrom(methodType.GetParameters()[0].ParameterType))
+                    {
+                        throw new InvalidOperationException($"The first parameter of the method {methodType.GetFriendlyName()} should be the type {nameof(IEvent)}.");
+                    }
+
+                    eventType = methodType.GetParameters()[0].ParameterType;
+                }
+
+                yield return new RegisteredEventListener(eventType, methodType, attribute, listenerType);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs b/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs
new file mode 100644 (file)
index 0000000..52cf629
--- /dev/null
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+    internal class TemporaryEventRegister
+    {
+        private readonly ConcurrentDictionary<int, IRegisteredEventListener> _callbacks;
+        private int _idLast;
+
+        public TemporaryEventRegister()
+        {
+            _callbacks = new ConcurrentDictionary<int, IRegisteredEventListener>();
+        }
+
+        public IEnumerable<IRegisteredEventListener> GetEventListeners()
+        {
+            return _callbacks.Select(i => i.Value);
+        }
+
+        public IDisposable Add(IRegisteredEventListener callback)
+        {
+            var id = Interlocked.Increment(ref _idLast);
+
+            if (!_callbacks.TryAdd(id, callback))
+            {
+                Debug.Fail("Failed to register the event listener");
+            }
+
+            return new UnregisterEvent(this, id);
+        }
+
+        private void Remove(int id)
+        {
+            _callbacks.TryRemove(id, out _);
+        }
+
+        private class UnregisterEvent : IDisposable
+        {
+            private readonly TemporaryEventRegister _register;
+            private readonly int _id;
+
+            public UnregisterEvent(TemporaryEventRegister register, int id)
+            {
+                _register = register;
+                _id = id;
+            }
+
+            public void Dispose()
+            {
+                _register.Remove(_id);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs b/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs
new file mode 100644 (file)
index 0000000..dd668c5
--- /dev/null
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+    internal class WrappedRegisteredEventListener : IRegisteredEventListener
+    {
+        private readonly IRegisteredEventListener _innerObject;
+        private readonly object _object;
+
+        public WrappedRegisteredEventListener(IRegisteredEventListener innerObject, object o)
+        {
+            _innerObject = innerObject;
+            _object = o;
+        }
+
+        public Type EventType => _innerObject.EventType;
+
+        public EventPriority Priority => _innerObject.Priority;
+
+        public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+        {
+            return _innerObject.InvokeAsync(_object, @event, provider);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/RegisteredEventListener.cs b/src/Impostor.Server/Events/RegisteredEventListener.cs
deleted file mode 100644 (file)
index 80df9d8..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Reflection;
-using System.Threading.Tasks;
-using Impostor.Api.Events;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Impostor.Server.Events
-{
-    internal class RegisteredEventListener
-    {
-        private static readonly ConcurrentDictionary<Type, RegisteredEventListener[]> Instances = new ConcurrentDictionary<Type, RegisteredEventListener[]>();
-        private readonly Func<object, object, IServiceProvider, ValueTask> _invoker;
-        private readonly Type _eventListenerType;
-
-        public RegisteredEventListener(Type eventType, MethodInfo method, EventListenerAttribute attribute, Type eventListenerType)
-        {
-            EventType = eventType;
-            _eventListenerType = eventListenerType;
-            Priority = attribute.Priority;
-            PriorityOrder = attribute.PriorityOrder;
-            IgnoreCancelled = attribute.IgnoreCancelled;
-            Method = method.GetFriendlyName(showParameters: false);
-            _invoker = CreateInvoker(method, attribute.IgnoreCancelled);
-        }
-
-        public Type EventType { get; }
-
-        public EventPriority Priority { get; }
-
-        public int PriorityOrder { get; set; }
-
-        public bool IgnoreCancelled { get; }
-
-        public string Method { get; }
-
-        public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
-        {
-            return _invoker(eventHandler, @event, provider);
-        }
-
-        private Func<object, object, IServiceProvider, ValueTask> CreateInvoker(MethodInfo method, bool ignoreCancelled)
-        {
-            var instance = Expression.Parameter(typeof(object), "instance");
-            var eventParameter = Expression.Parameter(typeof(object), "event");
-            var provider = Expression.Parameter(typeof(IServiceProvider), "provider");
-            var @event = Expression.Convert(eventParameter, EventType);
-
-            var getRequiredService = typeof(ServiceProviderServiceExtensions)
-                .GetMethod("GetRequiredService", new[] { typeof(IServiceProvider) });
-
-            if (getRequiredService == null)
-            {
-                throw new InvalidOperationException("The method GetRequiredService could not be found.");
-            }
-
-            var methodArguments = method.GetParameters();
-            var arguments = new Expression[methodArguments.Length];
-
-            for (var i = 0; i < methodArguments.Length; i++)
-            {
-                var methodArgument = methodArguments[i];
-
-                if (typeof(IEvent).IsAssignableFrom(methodArgument.ParameterType)
-                    && methodArgument.ParameterType.IsAssignableFrom(EventType))
-                {
-                    arguments[i] = @event;
-                }
-                else
-                {
-                    arguments[i] = Expression.Call(
-                        getRequiredService.MakeGenericMethod(methodArgument.ParameterType),
-                        provider);
-                }
-            }
-
-            var returnTarget = Expression.Label(typeof(ValueTask));
-            Expression invoke = Expression.Call(Expression.Convert(instance, _eventListenerType), method, arguments);
-
-            if (method.ReturnType == typeof(void))
-            {
-                if (!ignoreCancelled && typeof(IEventCancelable).IsAssignableFrom(EventType))
-                {
-                    invoke = Expression.Block(
-                        Expression.IfThenElse(
-                            Expression.Property(@event, nameof(IEventCancelable.IsCancelled)),
-                            Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
-                            Expression.Block(
-                                invoke,
-                                Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))))),
-                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
-                }
-                else
-                {
-                    invoke = Expression.Block(
-                        invoke,
-                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
-                }
-            }
-            else if (method.ReturnType == typeof(ValueTask))
-            {
-                if (!ignoreCancelled && typeof(IEventCancelable).IsAssignableFrom(EventType))
-                {
-                    invoke = Expression.Block(
-                        Expression.IfThenElse(
-                            Expression.Property(@event, nameof(IEventCancelable.IsCancelled)),
-                            Expression.Return(returnTarget, Expression.Default(typeof(ValueTask))),
-                            Expression.Return(returnTarget, invoke)),
-                        Expression.Label(returnTarget, Expression.Default(typeof(ValueTask))));
-                }
-            }
-            else
-            {
-                throw new InvalidOperationException($"The method {method.GetFriendlyName()} must return void or ValueTask.");
-            }
-
-            return Expression.Lambda<Func<object, object, IServiceProvider, ValueTask>>(invoke,  instance, eventParameter, provider)
-                .Compile();
-        }
-
-        public static IReadOnlyList<RegisteredEventListener> FromType(Type type)
-        {
-            return Instances.GetOrAdd(type, t =>
-            {
-                return t.GetMethods()
-                    .Where(m => !m.IsStatic && m.GetCustomAttributes(typeof(EventListenerAttribute), false).Any())
-                    .SelectMany(m => FromMethod(t, m))
-                    .ToArray();
-            });
-        }
-
-        public static IEnumerable<RegisteredEventListener> FromMethod(Type listenerType, MethodInfo methodType)
-        {
-            // Get the return type.
-            var returnType = methodType.ReturnType;
-
-            if (returnType != typeof(void) && returnType != typeof(ValueTask))
-            {
-                throw new InvalidOperationException($"The method {methodType.GetFriendlyName()} does not return void or ValueTask.");
-            }
-
-            // Register the event.
-            foreach (var attribute in methodType.GetCustomAttributes<EventListenerAttribute>(false))
-            {
-                var eventType = attribute.Event;
-
-                if (eventType == null)
-                {
-                    if (methodType.GetParameters().Length == 0 || !typeof(IEvent).IsAssignableFrom(methodType.GetParameters()[0].ParameterType))
-                    {
-                        throw new InvalidOperationException($"The first parameter of the method {methodType.GetFriendlyName()} should be the type {nameof(IEvent)}.");
-                    }
-
-                    eventType = methodType.GetParameters()[0].ParameterType;
-                }
-
-                yield return new RegisteredEventListener(eventType, methodType, attribute, listenerType);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Impostor.Server/Events/TemporaryEventRegister.cs b/src/Impostor.Server/Events/TemporaryEventRegister.cs
deleted file mode 100644 (file)
index 8bcb643..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Impostor.Api.Events;
-
-namespace Impostor.Server.Events
-{
-    internal class TemporaryEventRegister<T>
-        where T : IEvent
-    {
-        private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
-        private readonly List<Func<IServiceProvider, T, ValueTask>> _callbacks = new List<Func<IServiceProvider, T, ValueTask>>();
-
-        public async ValueTask CallAsync(IServiceProvider provider, T @event)
-        {
-            await semaphoreSlim.WaitAsync();
-
-            try
-            {
-                foreach (var callback in _callbacks)
-                {
-                    await callback.Invoke(provider, @event);
-                }
-            }
-            finally
-            {
-                semaphoreSlim.Release();
-            }
-        }
-
-        public IDisposable Add(Func<IServiceProvider, T, ValueTask> callback)
-        {
-            semaphoreSlim.Wait();
-
-            try
-            {
-                _callbacks.Add(callback);
-            }
-            finally
-            {
-                semaphoreSlim.Release();
-            }
-
-            return new UnregisterEvent(this, callback);
-        }
-
-        private void Remove(Func<IServiceProvider, T, ValueTask> callback)
-        {
-            semaphoreSlim.Wait();
-
-            try
-            {
-                _callbacks.Remove(callback);
-            }
-            finally
-            {
-                semaphoreSlim.Release();
-            }
-        }
-
-        private class UnregisterEvent : IDisposable
-        {
-            private readonly TemporaryEventRegister<T> _register;
-            private readonly Func<IServiceProvider, T, ValueTask> _callback;
-
-            public UnregisterEvent(TemporaryEventRegister<T> register, Func<IServiceProvider, T, ValueTask> callback)
-            {
-                _register = register;
-                _callback = callback;
-            }
-
-            public void Dispose()
-            {
-                _register.Remove(_callback);
-            }
-        }
-    }
-}
\ No newline at end of file
index 8a1bcfcccbb99e58e47d1bb992413572d9fb0f66..2b3985dd86e02ae081eaf35bfaff3d3d10091100 100644 (file)
@@ -30,8 +30,7 @@ namespace Impostor.Server.Plugins
 
             var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
             matcher.AddInclude("*.dll");
-            matcher.AddExclude("Impostor.Server.Api.dll");
-            matcher.AddExclude("Impostor.Shared.dll");
+            matcher.AddExclude("Impostor.Api.dll");
 
             RegisterAssemblies(pluginPaths, matcher, assemblyInfos, true);
             RegisterAssemblies(libraryPaths, matcher, assemblyInfos, false);
@@ -53,7 +52,7 @@ namespace Impostor.Server.Plugins
 
             var plugins = assemblies
                 .SelectMany(a => a.GetTypes())
-                .Where(typeof(IPlugin).IsAssignableFrom)
+                .Where(t => typeof(IPlugin).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract)
                 .Select(Activator.CreateInstance)
                 .Cast<IPlugin>()
                 .ToList();
diff --git a/src/Impostor.Tests/Events/EventManagerTests.cs b/src/Impostor.Tests/Events/EventManagerTests.cs
new file mode 100644 (file)
index 0000000..08b33f8
--- /dev/null
@@ -0,0 +1,168 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+using Impostor.Api.Events.Managers;
+using Impostor.Server.Events;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Impostor.Tests.Events
+{
+    public class EventManagerTests
+    {
+        public static readonly IEnumerable<object[]> TestModes = new []
+        {
+            new object[] { TestMode.Service },
+            new object[] { TestMode.Temporary }
+        };
+
+        [Theory]
+        [MemberData(nameof(TestModes))]
+        public async ValueTask CallEvent(TestMode mode)
+        {
+            var listener = new EventListener();
+            var eventManager = CreatEventManager(mode, listener);
+
+            await eventManager.CallAsync(new SetValueEvent(1));
+
+            Assert.Equal(1, listener.Value);
+        }
+
+        [Theory]
+        [MemberData(nameof(TestModes))]
+        public async Task CallPriority(TestMode mode)
+        {
+            var listener = new PriorityEventListener();
+            var eventManager = CreatEventManager(mode, listener);
+
+            await eventManager.CallAsync(new SetValueEvent(1));
+
+            Assert.Equal(new []
+            {
+                EventPriority.Monitor,
+                EventPriority.Highest,
+                EventPriority.High,
+                EventPriority.Normal,
+                EventPriority.Low,
+                EventPriority.Lowest
+            }, listener.Priorities);
+        }
+
+        [Theory]
+        [MemberData(nameof(TestModes))]
+        public async ValueTask CancelEvent(TestMode mode)
+        {
+            var listener = new EventListener();
+            var eventManager = CreatEventManager(
+                mode,
+                new CancelAtHighEventListener(),
+                listener
+            );
+
+            await eventManager.CallAsync(new SetValueEvent(1));
+
+            Assert.Equal(0, listener.Value);
+        }
+
+        [Theory]
+        [MemberData(nameof(TestModes))]
+        public async Task CancelPriority(TestMode mode)
+        {
+            var listener = new PriorityEventListener();
+            var eventManager = CreatEventManager(
+                mode,
+                new CancelAtHighEventListener(),
+                listener
+            );
+
+            await eventManager.CallAsync(new SetValueEvent(1));
+
+            Assert.Equal(new []
+            {
+                EventPriority.Monitor,
+                EventPriority.Highest
+            }, listener.Priorities);
+        }
+
+        private static IEventManager CreatEventManager(TestMode mode, params IEventListener[] listeners)
+        {
+            var services = new ServiceCollection();
+            services.AddSingleton<IEventManager, EventManager>();
+
+            if (mode == TestMode.Service)
+            {
+                foreach (var listener in listeners)
+                {
+                    services.AddSingleton(listener);
+                }
+            }
+
+            var eventManager = services.BuildServiceProvider().GetRequiredService<IEventManager>();
+
+            if (mode == TestMode.Temporary)
+            {
+                foreach (var listener in listeners)
+                {
+                    eventManager.RegisterListener(listener);
+                }
+            }
+
+            return eventManager;
+        }
+
+        public enum TestMode
+        {
+            Service,
+            Temporary
+        }
+
+        public class SetValueEvent : IEventCancelable
+        {
+            public SetValueEvent(int value)
+            {
+                Value = value;
+            }
+
+            public int Value { get; }
+
+            public bool IsCancelled { get; set; }
+        }
+
+        private class CancelAtHighEventListener : IEventListener
+        {
+            [EventListener(Priority = EventPriority.High)]
+            public void OnSetCalled(SetValueEvent e) => e.IsCancelled = true;
+        }
+
+        private class EventListener : IEventListener
+        {
+            public int Value { get; private set; }
+
+            [EventListener]
+            public void OnSetCalled(SetValueEvent e) => Value = e.Value;
+        }
+
+        private class PriorityEventListener : IEventListener
+        {
+            public List<EventPriority> Priorities { get; } = new List<EventPriority>();
+
+            [EventListener(EventPriority.Lowest)]
+            public void OnLowest(SetValueEvent e) => Priorities.Add(EventPriority.Lowest);
+
+            [EventListener(EventPriority.Low)]
+            public void OnLow(SetValueEvent e) => Priorities.Add(EventPriority.Low);
+
+            [EventListener]
+            public void OnNormal(SetValueEvent e) => Priorities.Add(EventPriority.Normal);
+
+            [EventListener(EventPriority.High)]
+            public void OnHigh(SetValueEvent e) => Priorities.Add(EventPriority.High);
+
+            [EventListener(EventPriority.Highest)]
+            public void OnHighest(SetValueEvent e) => Priorities.Add(EventPriority.Highest);
+
+            [EventListener(EventPriority.Monitor)]
+            public void OnMonitor(SetValueEvent e) => Priorities.Add(EventPriority.Monitor);
+        }
+    }
+}
\ No newline at end of file