diff options
Diffstat (limited to 'Impostor-dev/src/Impostor.Server/Events/Register')
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 |
