summaryrefslogtreecommitdiff
path: root/Impostor-dev/src/Impostor.Server/Events/Register
diff options
context:
space:
mode:
Diffstat (limited to 'Impostor-dev/src/Impostor.Server/Events/Register')
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs15
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs27
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs30
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs166
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs59
-rw-r--r--Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs27
6 files changed, 324 insertions, 0 deletions
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs
new file mode 100644
index 0000000..479a3f6
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/IRegisteredEventListener.cs
@@ -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/Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs
new file mode 100644
index 0000000..a21c3b1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/InvokedRegisteredEventListener.cs
@@ -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/Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs
new file mode 100644
index 0000000..e81e8f8
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/ManualRegisteredEventListener.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Threading.Tasks;
+using Impostor.Api.Events;
+
+namespace Impostor.Server.Events.Register
+{
+ internal class ManualRegisteredEventListener : IRegisteredEventListener
+ {
+ public Type EventType { get; } = typeof(object);
+
+ private readonly IManualEventListener _manualEventListener;
+
+ public ManualRegisteredEventListener(IManualEventListener manualEventListener)
+ {
+ _manualEventListener = manualEventListener;
+ }
+
+ public EventPriority Priority => _manualEventListener.Priority;
+
+ public ValueTask InvokeAsync(object eventHandler, object @event, IServiceProvider provider)
+ {
+ if (@event is IEvent typedEvent)
+ {
+ return _manualEventListener.Execute(typedEvent);
+ }
+
+ return ValueTask.CompletedTask;
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs
new file mode 100644
index 0000000..120a45e
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/RegisteredEventListener.cs
@@ -0,0 +1,166 @@
+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 PropertyInfo IsCancelledProperty = typeof(IEventCancelable).GetProperty(nameof(IEventCancelable.IsCancelled))!;
+
+ 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, IsCancelledProperty),
+ 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, IsCancelledProperty),
+ 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);
+ }
+ }
+ }
+}
diff --git a/Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs b/Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs
new file mode 100644
index 0000000..1446ad1
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/TemporaryEventRegister.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+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/Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs b/Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs
new file mode 100644
index 0000000..dd668c5
--- /dev/null
+++ b/Impostor-dev/src/Impostor.Server/Events/Register/WrappedRegisteredEventListener.cs
@@ -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