M Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs +90 -6
@@ 8,10 8,12 @@ using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.Core.Packets;
+using NosSmooth.Core.Stateful;
using NosSmooth.Packets.Extensions;
namespace NosSmooth.Core.Extensions;
@@ 88,13 90,16 @@ public static class ServiceCollectionExtensions
return serviceCollection.AddScoped(typeof(IEveryPacketResponder), responderType);
}
- if (!responderType.GetInterfaces().Any(
+ if (!responderType.GetInterfaces().Any
+ (
i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPacketResponder<>)
))
{
- throw new ArgumentException(
+ throw new ArgumentException
+ (
$"{nameof(responderType)} should implement IPacketResponder.",
- nameof(responderType));
+ nameof(responderType)
+ );
}
var responderTypeInterfaces = responderType.GetInterfaces();
@@ 140,13 145,16 @@ public static class ServiceCollectionExtensions
Type commandHandlerType
)
{
- if (!commandHandlerType.GetInterfaces().Any(
+ if (!commandHandlerType.GetInterfaces().Any
+ (
i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommandHandler<>)
))
{
- throw new ArgumentException(
+ throw new ArgumentException
+ (
$"{nameof(commandHandlerType)} should implement ICommandHandler.",
- nameof(commandHandlerType));
+ nameof(commandHandlerType)
+ );
}
var handlerTypeInterfaces = commandHandlerType.GetInterfaces();
@@ 186,4 194,80 @@ public static class ServiceCollectionExtensions
{
return serviceCollection.AddScoped<IPostExecutionEvent, TEvent>();
}
+
+ /// <summary>
+ /// Add the injector for stateful entities that may be replaced for different NosTale clients.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <returns>The collection.</returns>
+ public static IServiceCollection AddStatefulInjector(this IServiceCollection serviceCollection)
+ {
+ serviceCollection.RemoveAll<INostaleClient>();
+ return serviceCollection
+ .AddScoped<StatefulInjector>()
+ .AddSingleton<StatefulRepository>()
+ .AddPreExecutionEvent<StatefulPreExecutionEvent>()
+ .AddScoped<INostaleClient>
+ (
+ p =>
+ {
+ var nostaleClient = p.GetRequiredService<StatefulInjector>().Client;
+ if (nostaleClient == null)
+ {
+ throw new NullReferenceException("The client cannot be null.");
+ }
+
+ return nostaleClient;
+ }
+ )
+ .ReplaceStatefulEntities();
+ }
+
+ /// <summary>
+ /// Replace all the stateful entities that are added in the collection.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <returns>The collection.</returns>
+ public static IServiceCollection ReplaceStatefulEntities(this IServiceCollection serviceCollection)
+ {
+ foreach (var serviceDescriptor in serviceCollection)
+ {
+ var type = serviceDescriptor.ServiceType;
+ if (typeof(IStatefulEntity).IsAssignableFrom(type))
+ {
+ serviceCollection.AddStatefulEntity(type);
+ }
+ }
+
+ return serviceCollection;
+ }
+
+ /// <summary>
+ /// Add a stateful entity of the given type to be injectable into the scope of a client.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <typeparam name="TEntity">The type of the stateful entity.</typeparam>
+ /// <returns>The collection.</returns>
+ public static IServiceCollection AddStatefulEntity<TEntity>(this IServiceCollection serviceCollection)
+ => serviceCollection.AddStatefulEntity(typeof(TEntity));
+
+ /// <summary>
+ /// Add a stateful entity of the given type to be injectable into the scope of a client.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <param name="statefulEntityType">The type of the stateful entity.</param>
+ /// <returns>The collection.</returns>
+ public static IServiceCollection AddStatefulEntity
+ (this IServiceCollection serviceCollection, Type statefulEntityType)
+ {
+ serviceCollection.AddStatefulInjector();
+ serviceCollection.RemoveAll(statefulEntityType);
+ serviceCollection.Add
+ (
+ ServiceDescriptor.Scoped
+ (statefulEntityType, (p) => p.GetRequiredService<StatefulInjector>().GetEntity(p, statefulEntityType))
+ );
+
+ return serviceCollection;
+ }
}=
\ No newline at end of file
M Core/NosSmooth.Core/Packets/IPacketHandler.cs => Core/NosSmooth.Core/Packets/IPacketHandler.cs +5 -2
@@ 6,6 6,7 @@
using System.Threading;
using System.Threading.Tasks;
+using NosSmooth.Core.Client;
using NosSmooth.Packets;
using Remora.Results;
@@ 19,18 20,20 @@ public interface IPacketHandler
/// <summary>
/// Calls a responder for the given packet.
/// </summary>
+ /// <param name="client">The current NosTale client.</param>
/// <param name="packet">The packet.</param>
/// <param name="packetString">The string of the packet.</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> HandleReceivedPacketAsync(IPacket packet, string packetString, CancellationToken ct = default);
+ public Task<Result> HandleReceivedPacketAsync(INostaleClient client, IPacket packet, string packetString, CancellationToken ct = default);
/// <summary>
/// Calls a responder for the given packet.
/// </summary>
+ /// <param name="client">The current NosTale client.</param>
/// <param name="packet">The packet.</param>
/// <param name="packetString">The string of the packet.</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> HandleSentPacketAsync(IPacket packet, string packetString, CancellationToken ct = default);
+ public Task<Result> HandleSentPacketAsync(INostaleClient client, IPacket packet, string packetString, CancellationToken ct = default);
}
A Core/NosSmooth.Core/Stateful/IStatefulEntity.cs => Core/NosSmooth.Core/Stateful/IStatefulEntity.cs +21 -0
@@ 0,0 1,21 @@
+//
+// IStatefulEntity.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 NosSmooth.Core.Client;
+
+namespace NosSmooth.Core.Stateful;
+
+/// <summary>
+/// An entity that has a state that depends on the NosTale state.
+/// </summary>
+/// <remarks>
+/// Stateful entities can be replaced with scoped entities that will be
+/// injected depending on the current <see cref="INostaleClient"/>.
+/// That can be useful in an application that has multiple nostale clients.
+/// </remarks>
+public interface IStatefulEntity
+{
+}<
\ No newline at end of file
A Core/NosSmooth.Core/Stateful/StatefulInjector.cs => Core/NosSmooth.Core/Stateful/StatefulInjector.cs +49 -0
@@ 0,0 1,49 @@
+//
+// StatefulInjector.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 System;
+using NosSmooth.Core.Client;
+
+namespace NosSmooth.Core.Stateful;
+
+/// <summary>
+/// The scoped injector of stateful entities.
+/// </summary>
+public class StatefulInjector
+{
+ private readonly StatefulRepository _repository;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StatefulInjector"/> class.
+ /// </summary>
+ /// <param name="repository">The repository of stateful entity types.</param>
+ public StatefulInjector(StatefulRepository repository)
+ {
+ _repository = repository;
+ }
+
+ /// <summary>
+ /// Gets or sets the nostale client.
+ /// </summary>
+ public INostaleClient? Client { get; set; }
+
+ /// <summary>
+ /// Gets an entity of the specified type.
+ /// </summary>
+ /// <param name="services">The service provider.</param>
+ /// <param name="statefulEntityType">The type of the entity.</param>
+ /// <exception cref="NullReferenceException">Thrown if the client is not set.</exception>
+ /// <returns>The entity to inject.</returns>
+ public object GetEntity(IServiceProvider services, Type statefulEntityType)
+ {
+ if (Client is null)
+ {
+ throw new NullReferenceException("The client cannot be null in order to get a stateful entity.");
+ }
+
+ return _repository.GetEntity(services, Client, statefulEntityType);
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Core/Stateful/StatefulPreExecutionEvent.cs => Core/NosSmooth.Core/Stateful/StatefulPreExecutionEvent.cs +42 -0
@@ 0,0 1,42 @@
+//
+// StatefulPreExecutionEvent.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 System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using NosSmooth.Core.Client;
+using NosSmooth.Core.Packets;
+using NosSmooth.Packets;
+using Remora.Results;
+
+namespace NosSmooth.Core.Stateful;
+
+/// <summary>
+/// Event that injects stateful entities into the scope.
+/// </summary>
+public class StatefulPreExecutionEvent : IPreExecutionEvent
+{
+ private readonly StatefulInjector _injector;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StatefulPreExecutionEvent"/> class.
+ /// </summary>
+ /// <param name="injector">The injector.</param>
+ public StatefulPreExecutionEvent(StatefulInjector injector)
+ {
+ _injector = injector;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> ExecuteBeforeExecutionAsync<TPacket>
+ (INostaleClient client, PacketEventArgs<TPacket> packetArgs, CancellationToken ct = default)
+ where TPacket : IPacket
+ {
+ _injector.Client = client;
+ return Task.FromResult(Result.FromSuccess());
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.Core/Stateful/StatefulRepository.cs => Core/NosSmooth.Core/Stateful/StatefulRepository.cs +52 -0
@@ 0,0 1,52 @@
+//
+// StatefulRepository.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 System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
+using NosSmooth.Core.Client;
+
+namespace NosSmooth.Core.Stateful;
+
+/// <summary>
+/// Repository holding all the stateful entities for various NosTale clients.
+/// </summary>
+public class StatefulRepository
+{
+ private readonly Dictionary<INostaleClient, Dictionary<Type, object>> _statefulEntities;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StatefulRepository"/> class.
+ /// </summary>
+ public StatefulRepository()
+ {
+ _statefulEntities = new Dictionary<INostaleClient, Dictionary<Type, object>>();
+ }
+
+ /// <summary>
+ /// Get an entity for the given client and type.
+ /// </summary>
+ /// <param name="services">The service provider.</param>
+ /// <param name="client">The NosTale client.</param>
+ /// <param name="statefulEntityType">The type of the stateful entity to obtain.</param>
+ /// <returns>The obtained entity.</returns>
+ public object GetEntity(IServiceProvider services, INostaleClient client, Type statefulEntityType)
+ {
+ if (!_statefulEntities.ContainsKey(client))
+ {
+ _statefulEntities.Add(client, new Dictionary<Type, object>());
+ }
+
+ var value = _statefulEntities[client];
+ if (!value.ContainsKey(statefulEntityType))
+ {
+ value.Add(statefulEntityType, ActivatorUtilities.CreateInstance(services, statefulEntityType));
+ }
+
+ return value[statefulEntityType];
+ }
+}<
\ No newline at end of file