# 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';; \
*) 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';; \
/// 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
{
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>
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;
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;
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 />
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);
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
{
/// </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>())
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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
+++ /dev/null
-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
+++ /dev/null
-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
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);
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();
--- /dev/null
+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