A Core/NosSmooth.Game/Events/Handlers/EventDispatcher.cs => Core/NosSmooth.Game/Events/Handlers/EventDispatcher.cs +51 -0
@@ 0,0 1,51 @@
+//
+// EventDispatcher.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Extensions.DependencyInjection;
+using Remora.Results;
+
+namespace NosSmooth.Game.Events.Handlers;
+
+/// <summary>
+/// Dispatches <see cref="IGameResponder"/> with <see cref="IGameEvent"/>.
+/// </summary>
+public class EventDispatcher
+{
+ private readonly IServiceProvider _provider;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EventDispatcher"/> class.
+ /// </summary>
+ /// <param name="provider">The services provider.</param>
+ public EventDispatcher(IServiceProvider provider)
+ {
+ _provider = provider;
+ }
+
+ /// <summary>
+ /// Dispatches game responders that are registered in the service collection.
+ /// </summary>
+ /// <param name="event">The event to dispatch.</param>
+ /// <param name="ct">The cancellation token for cancelling the operation.</param>
+ /// <typeparam name="TEvent">The type of the event.</typeparam>
+ /// <returns>A result that may or may not have succeeded.</returns>
+ public async Task<Result> DispatchEvent<TEvent>(TEvent @event, CancellationToken ct = default)
+ where TEvent : IGameEvent
+ {
+ var results = await Task.WhenAll(
+ _provider
+ .GetServices<IGameResponder<TEvent>>()
+ .Select(responder => responder.Respond(@event, ct))
+ );
+
+ return results.Length switch
+ {
+ 0 => Result.FromSuccess(),
+ 1 => results[0],
+ _ => new AggregateError(results.Cast<IResult>().ToArray()),
+ };
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Game/Events/Handlers/IGameResponder.cs => Core/NosSmooth.Game/Events/Handlers/IGameResponder.cs +34 -0
@@ 0,0 1,34 @@
+//
+// IGameResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using OneOf.Types;
+using Remora.Results;
+
+namespace NosSmooth.Game.Events.Handlers;
+
+/// <summary>
+/// Represents interface for classes that respond to <see cref="IGameEvent"/>.
+/// </summary>
+public interface IGameResponder
+{
+}
+
+/// <summary>
+/// Represents interface for classes that respond to game events.
+/// Responds to <typeparamref name="TPacket"/>.
+/// </summary>
+/// <typeparam name="TEvent">The event type this responder responds to.</typeparam>
+public interface IGameResponder<TEvent> : IGameResponder
+ where TEvent : IGameEvent
+{
+ /// <summary>
+ /// Respond to the given packet.
+ /// </summary>
+ /// <param name="packet">The packet to respond to.</param>
+ /// <param name="ct">The cancellation token for cancelling the operation.</param>
+ /// <returns>A result that may or may not have succeeded.</returns>
+ public Task<Result> Respond(TEvent packet, CancellationToken ct = default);
+}<
\ No newline at end of file
A Core/NosSmooth.Game/Events/IGameEvent.cs => Core/NosSmooth.Game/Events/IGameEvent.cs +14 -0
@@ 0,0 1,14 @@
+//
+// IGameEvent.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace NosSmooth.Game.Events;
+
+/// <summary>
+/// Represents base interface for game events.
+/// </summary>
+public interface IGameEvent
+{
+}<
\ No newline at end of file
A Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs +32 -0
@@ 0,0 1,32 @@
+
+ /// <summary>
+ /// Adds the given game event responder.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <param name="gameResponder">The type of the event responder.</param>
+ /// <returns>The collection.</returns>
+ public static IServiceCollection AddGameResponder(this IServiceCollection serviceCollection, Type gameResponder)
+ {
+ if (!gameResponder.GetInterfaces().Any(
+ i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IGameResponder<>)
+ ))
+ {
+ throw new ArgumentException(
+ $"{nameof(gameResponder)} should implement IGameResponder.",
+ nameof(gameResponder));
+ }
+
+ var handlerTypeInterfaces = gameResponder.GetInterfaces();
+ var handlerInterfaces = handlerTypeInterfaces.Where
+ (
+ r => r.IsGenericType && r.GetGenericTypeDefinition() == typeof(IGameResponder<>)
+ );
+
+ foreach (var handlerInterface in handlerInterfaces)
+ {
+ serviceCollection.AddScoped(handlerInterface, gameResponder);
+ }
+
+ return serviceCollection;
+ }
+}<
\ No newline at end of file