~ruther/NosSmooth

3e52548e23c46738ce2194dd02c79fdce85959c3 — František Boháček 2 years ago e31da37
feat(core): split raw client and managed client as well as packet handlers

Resolves #65
33 files changed, 681 insertions(+), 199 deletions(-)

M Core/NosSmooth.Core/Client/BaseNostaleClient.cs
M Core/NosSmooth.Core/Client/INostaleClient.cs
A Core/NosSmooth.Core/Client/ManagedNostaleClient.cs
M Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs
A Core/NosSmooth.Core/Packets/IPacketHandler.cs
M Core/NosSmooth.Core/Packets/IPostExecutionEvent.cs
M Core/NosSmooth.Core/Packets/IPreExecutionEvent.cs
A Core/NosSmooth.Core/Packets/IRawPacketResponder.cs
A Core/NosSmooth.Core/Packets/ManagedPacketHandler.cs
M Core/NosSmooth.Core/Packets/PacketEventArgs.cs
R Core/NosSmooth.Core/Packets/{PacketHandler => RawPacketHandler}.cs
M Core/NosSmooth.Core/Stateful/StatefulPreExecutionEvent.cs
M Core/NosSmooth.Game/Apis/Safe/NostaleChatApi.cs
M Core/NosSmooth.Game/Apis/Safe/NostaleSkillsApi.cs
M Core/NosSmooth.Game/Apis/Unsafe/UnsafeInventoryApi.cs
M Core/NosSmooth.Game/Apis/Unsafe/UnsafeMapApi.cs
M Core/NosSmooth.Game/Apis/Unsafe/UnsafeMateApi.cs
M Core/NosSmooth.Game/Apis/Unsafe/UnsafeMateSkillsApi.cs
M Core/NosSmooth.Game/Apis/Unsafe/UnsafeSkillsApi.cs
M Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs
M Extensions/NosSmooth.Extensions.Combat/CombatManager.cs
M Extensions/NosSmooth.Extensions.Combat/CombatState.cs
M Extensions/NosSmooth.Extensions.Combat/ICombatState.cs
M Samples/FileClient/Client.cs
M Samples/FileClient/Program.cs
M Tests/NosSmooth.Core.Tests/Fakes/FakeEmptyNostaleClient.cs
A Tests/NosSmooth.Core.Tests/Fakes/FakeLogger.cs
M Tests/NosSmooth.Core.Tests/Fakes/FakeNostaleClient.cs
M Tests/NosSmooth.Core.Tests/Fakes/Packets/Events/PacketEvent.cs
M Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacket.cs
M Tests/NosSmooth.Core.Tests/NosSmooth.Core.Tests.csproj
M Tests/NosSmooth.Core.Tests/Packets/PacketHandlerTests.Events.cs
M Tests/NosSmooth.Core.Tests/Stateful/StatefulInjectorTests.cs
M Core/NosSmooth.Core/Client/BaseNostaleClient.cs => Core/NosSmooth.Core/Client/BaseNostaleClient.cs +1 -25
@@ 22,53 22,29 @@ namespace NosSmooth.Core.Client;
public abstract class BaseNostaleClient : INostaleClient
{
    private readonly CommandProcessor _commandProcessor;
    private readonly IPacketSerializer _packetSerializer;

    /// <summary>
    /// Initializes a new instance of the <see cref="BaseNostaleClient"/> class.
    /// </summary>
    /// <param name="commandProcessor">The command processor.</param>
    /// <param name="packetSerializer">The packet serializer.</param>
    protected BaseNostaleClient
    (
        CommandProcessor commandProcessor,
        IPacketSerializer packetSerializer
        CommandProcessor commandProcessor
    )
    {
        _commandProcessor = commandProcessor;
        _packetSerializer = packetSerializer;
    }

    /// <inheritdoc />
    public abstract Task<Result> RunAsync(CancellationToken stopRequested = default);

    /// <inheritdoc />
    public virtual Task<Result> SendPacketAsync(IPacket packet, CancellationToken ct = default)
    {
        var serialized = _packetSerializer.Serialize(packet);

        return serialized.IsSuccess
            ? SendPacketAsync(serialized.Entity, ct)
            : Task.FromResult(Result.FromError(serialized));
    }

    /// <inheritdoc />
    public abstract Task<Result> SendPacketAsync(string packetString, CancellationToken ct = default);

    /// <inheritdoc />
    public abstract Task<Result> ReceivePacketAsync(string packetString, CancellationToken ct = default);

    /// <inheritdoc />
    public virtual Task<Result> ReceivePacketAsync(IPacket packet, CancellationToken ct = default)
    {
        var serialized = _packetSerializer.Serialize(packet);

        return serialized.IsSuccess
            ? ReceivePacketAsync(serialized.Entity, ct)
            : Task.FromResult(Result.FromError(serialized));
    }

    /// <inheritdoc />
    public Task<Result> SendCommandAsync(ICommand command, CancellationToken ct = default)
        => _commandProcessor.ProcessCommand(this, command, ct);
}

M Core/NosSmooth.Core/Client/INostaleClient.cs => Core/NosSmooth.Core/Client/INostaleClient.cs +0 -16
@@ 25,14 25,6 @@ public interface INostaleClient
    public Task<Result> RunAsync(CancellationToken stopRequested = default);

    /// <summary>
    /// Sends the given packet to the server.
    /// </summary>
    /// <param name="packet">The packet to send.</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> SendPacketAsync(IPacket packet, CancellationToken ct = default);

    /// <summary>
    /// Sends the given raw packet string.
    /// </summary>
    /// <param name="packetString">The packed string to send in plain text.</param>


@@ 49,14 41,6 @@ public interface INostaleClient
    public Task<Result> ReceivePacketAsync(string packetString, CancellationToken ct = default);

    /// <summary>
    /// Receives the given packet.
    /// </summary>
    /// <param name="packet">The packet to receive.</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> ReceivePacketAsync(IPacket packet, CancellationToken ct = default);

    /// <summary>
    /// Sends the given command to the client.
    /// </summary>
    /// <remarks>

A Core/NosSmooth.Core/Client/ManagedNostaleClient.cs => Core/NosSmooth.Core/Client/ManagedNostaleClient.cs +84 -0
@@ 0,0 1,84 @@
//
//  ManagedNostaleClient.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.Threading;
using System.Threading.Tasks;
using NosSmooth.Core.Commands;
using NosSmooth.Packets;
using NosSmooth.PacketSerializer;
using Remora.Results;

namespace NosSmooth.Core.Client;

/// <summary>
/// A NosTale client that supports sending and receiving packets using .
/// </summary>
public class ManagedNostaleClient : INostaleClient
{
    private readonly INostaleClient _rawClient;
    private readonly IPacketSerializer _packetSerializer;

    /// <summary>
    /// Initializes a new instance of the <see cref="ManagedNostaleClient"/> class.
    /// </summary>
    /// <param name="rawClient">The raw nostale client.</param>
    /// <param name="packetSerializer">The packet serializer.</param>
    protected ManagedNostaleClient
    (
        INostaleClient rawClient,
        IPacketSerializer packetSerializer
    )
    {
        _rawClient = rawClient;
        _packetSerializer = packetSerializer;
    }

    /// <summary>
    /// Receives the given packet.
    /// </summary>
    /// <param name="packet">The packet to receive.</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> ReceivePacketAsync(IPacket packet, CancellationToken ct = default)
    {
        var serialized = _packetSerializer.Serialize(packet);

        return serialized.IsSuccess
            ? ReceivePacketAsync(serialized.Entity, ct)
            : Task.FromResult(Result.FromError(serialized));
    }

    /// <summary>
    /// Sends the given packet to the server.
    /// </summary>
    /// <param name="packet">The packet to send.</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> SendPacketAsync(IPacket packet, CancellationToken ct = default)
    {
        var serialized = _packetSerializer.Serialize(packet);

        return serialized.IsSuccess
            ? SendPacketAsync(serialized.Entity, ct)
            : Task.FromResult(Result.FromError(serialized));
    }

    /// <inheritdoc />
    public Task<Result> RunAsync(CancellationToken stopRequested = default)
        => _rawClient.RunAsync(stopRequested);

    /// <inheritdoc />
    public Task<Result> SendPacketAsync(string packetString, CancellationToken ct = default)
        => _rawClient.SendPacketAsync(packetString, ct);

    /// <inheritdoc />
    public Task<Result> ReceivePacketAsync(string packetString, CancellationToken ct = default)
        => _rawClient.ReceivePacketAsync(packetString, ct);

    /// <inheritdoc />
    public Task<Result> SendCommandAsync(ICommand command, CancellationToken ct = default)
        => _rawClient.SendCommandAsync(command, ct);
}
\ No newline at end of file

M Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs +23 -3
@@ 26,7 26,7 @@ namespace NosSmooth.Core.Extensions;
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds base packet and command handling for nostale client.
    /// Adds base packet (raw packets) and command handling for nostale client.
    /// </summary>
    /// <param name="serviceCollection">The service collection to register the responder to.</param>
    /// <returns>The collection.</returns>


@@ 36,10 36,30 @@ public static class ServiceCollectionExtensions
    )
    {
        serviceCollection
            .TryAddSingleton<PacketHandler>();
            .TryAddSingleton<IPacketHandler, RawPacketHandler>();
        serviceCollection.AddSingleton<CommandProcessor>();

        return serviceCollection;
    }

    /// <summary>
    /// Add managed packet handling for nostale client.
    /// </summary>
    /// <remarks>
    /// Adds a managed packet handler that calls managed packet responders.
    /// Adds contractor and contract packet responder.
    /// </remarks>
    /// <param name="serviceCollection">The service collection to register the responder to.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddManagedNostaleCore
    (
        this IServiceCollection serviceCollection
    )
    {
        serviceCollection.AddNostaleCore();
        serviceCollection.Replace(ServiceDescriptor.Singleton<IPacketHandler, ManagedPacketHandler>());
        serviceCollection.AddPacketSerialization();
        serviceCollection.AddSingleton<CommandProcessor>();
        serviceCollection.AddTransient<ManagedNostaleClient>();

        serviceCollection
            .AddSingleton<Contractor>()

A Core/NosSmooth.Core/Packets/IPacketHandler.cs => Core/NosSmooth.Core/Packets/IPacketHandler.cs +36 -0
@@ 0,0 1,36 @@
//
//  IPacketHandler.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.Threading;
using System.Threading.Tasks;
using NosSmooth.Core.Client;
using NosSmooth.Packets;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using Remora.Results;

namespace NosSmooth.Core.Packets;

/// <summary>
/// Calls registered responders for the packet that should be handled.
/// </summary>
public interface IPacketHandler
{
    /// <summary>
    /// Calls a responder for the given packet.
    /// </summary>
    /// <param name="client">The current NosTale client.</param>
    /// <param name="packetType">The source of 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> HandlePacketAsync
    (
        INostaleClient client,
        PacketSource packetType,
        string packetString,
        CancellationToken ct = default
    );
}
\ No newline at end of file

M Core/NosSmooth.Core/Packets/IPostExecutionEvent.cs => Core/NosSmooth.Core/Packets/IPostExecutionEvent.cs +16 -0
@@ 35,4 35,20 @@ public interface IPostExecutionEvent
        CancellationToken ct = default
    )
        where TPacket : IPacket;

    /// <summary>
    /// Execute the post execution event.
    /// </summary>
    /// <param name="client">The NosTale client.</param>
    /// <param name="packetArgs">The packet arguments.</param>
    /// <param name="executionResults">The results from the packet responders.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not succeed.</returns>
    public Task<Result> ExecuteAfterExecutionAsync
    (
        INostaleClient client,
        PacketEventArgs packetArgs,
        IReadOnlyList<Result> executionResults,
        CancellationToken ct = default
    );
}
\ No newline at end of file

M Core/NosSmooth.Core/Packets/IPreExecutionEvent.cs => Core/NosSmooth.Core/Packets/IPreExecutionEvent.cs +17 -0
@@ 35,4 35,21 @@ public interface IPreExecutionEvent
        CancellationToken ct = default
    )
        where TPacket : IPacket;

    /// <summary>
    /// Execute the pre execution event.
    /// </summary>
    /// <remarks>
    /// If an error is retuned, the packet responders won't be called.
    /// </remarks>
    /// <param name="client">The NosTale client.</param>
    /// <param name="packetArgs">The packet arguments.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not succeed.</returns>
    public Task<Result> ExecuteBeforeExecutionAsync
    (
        INostaleClient client,
        PacketEventArgs packetArgs,
        CancellationToken ct = default
    );
}
\ No newline at end of file

A Core/NosSmooth.Core/Packets/IRawPacketResponder.cs => Core/NosSmooth.Core/Packets/IRawPacketResponder.cs +26 -0
@@ 0,0 1,26 @@
//
//  IRawPacketResponder.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.Threading;
using System.Threading.Tasks;
using Remora.Results;

namespace NosSmooth.Core.Packets;

/// <summary>
/// Represents interface for classes that respond to packets.
/// Responds to a raw packet string.
/// </summary>
public interface IRawPacketResponder
{
    /// <summary>
    /// Respond to the given packet.
    /// </summary>
    /// <param name="packetArgs">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(PacketEventArgs packetArgs, CancellationToken ct = default);
}
\ No newline at end of file

A Core/NosSmooth.Core/Packets/ManagedPacketHandler.cs => Core/NosSmooth.Core/Packets/ManagedPacketHandler.cs +309 -0
@@ 0,0 1,309 @@
//
//  ManagedPacketHandler.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 System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.Packets;
using NosSmooth.PacketSerializer;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using NosSmooth.PacketSerializer.Errors;
using Remora.Results;

namespace NosSmooth.Core.Packets;

/// <summary>
/// Calls IRawPacketResponder and IPacketResponder{T}.
/// </summary>
public class ManagedPacketHandler : IPacketHandler
{
    private readonly IServiceProvider _services;
    private readonly IPacketSerializer _packetSerializer;
    private readonly ILogger<ManagedPacketHandler> _logger;
    private readonly IPacketHandler _rawPacketHandler;

    /// <summary>
    /// Initializes a new instance of the <see cref="ManagedPacketHandler"/> class.
    /// </summary>
    /// <param name="services">The service provider.</param>
    /// <param name="packetSerializer">The packet serializer.</param>
    /// <param name="logger">The logger.</param>
    public ManagedPacketHandler
        (IServiceProvider services, IPacketSerializer packetSerializer, ILogger<ManagedPacketHandler> logger)
    {
        _rawPacketHandler = new RawPacketHandler(services);
        _services = services;
        _packetSerializer = packetSerializer;
        _logger = logger;
    }

    /// <inheritdoc />
    public async Task<Result> HandlePacketAsync
    (
        INostaleClient client,
        PacketSource packetType,
        string packetString,
        CancellationToken ct = default
    )
    {
        var rawResult = await _rawPacketHandler.HandlePacketAsync(client, packetType, packetString, ct);

        IPacket packet;
        var deserializedResult = _packetSerializer.Deserialize(packetString, packetType);
        if (!deserializedResult.IsDefined(out var _))
        {
            if (deserializedResult.Error is not PacketConverterNotFoundError)
            {
                _logger.LogWarning("Could not parse {Packet}. Reason:", packetString);
                _logger.LogResultError(deserializedResult);
                packet = new ParsingFailedPacket(deserializedResult, packetString);
            }
            else
            {
                packet = new UnresolvedPacket(packetString.Split(' ')[0], packetString);
            }
        }
        else
        {
            packet = deserializedResult.Entity;
        }

        var managedResult = await HandlePacketAsync
        (
            client,
            packetType,
            packet,
            packetString,
            ct
        );

        if (!rawResult.IsSuccess && !managedResult.IsSuccess)
        {
            return new AggregateError(rawResult, managedResult);
        }

        if (!rawResult.IsSuccess)
        {
            return rawResult;
        }

        if (!managedResult.IsSuccess)
        {
            return managedResult;
        }

        return Result.FromSuccess();
    }

    /// <summary>
    /// Calls a responder for the given packet.
    /// </summary>
    /// <param name="client">The current NosTale client.</param>
    /// <param name="packetType">The source of the packet.</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>
    private Task<Result> HandlePacketAsync
    (
        INostaleClient client,
        PacketSource packetType,
        IPacket packet,
        string packetString,
        CancellationToken ct = default
    )
    {
        var processMethod = GetType().GetMethod
        (
            nameof(DispatchResponder),
            BindingFlags.NonPublic | BindingFlags.Instance
        );

        if (processMethod is null)
        {
            throw new InvalidOperationException("Could not find process command generic method in command processor.");
        }

        var boundProcessMethod = processMethod.MakeGenericMethod(packet.GetType());
        return (Task<Result>)boundProcessMethod.Invoke
        (
            this,
            new object[]
            {
                client,
                packetType,
                packet,
                packetString,
                ct
            }
        )!;
    }

    private async Task<Result> DispatchResponder<TPacket>
    (
        INostaleClient client,
        PacketSource packetType,
        TPacket packet,
        string packetString,
        CancellationToken ct
    )
        where TPacket : class, IPacket
    {
        using var scope = _services.CreateScope();
        var packetEventArgs = new PacketEventArgs<TPacket>(packetType, packet, packetString);

        var preExecutionResult = await ExecuteBeforeExecutionAsync(scope.ServiceProvider, client, packetEventArgs, ct);
        if (!preExecutionResult.IsSuccess)
        {
            return preExecutionResult;
        }

        var packetResponders = scope.ServiceProvider.GetServices<IPacketResponder<TPacket>>();
        var genericPacketResponders = scope.ServiceProvider.GetServices<IEveryPacketResponder>();

        Result[] results;
        try
        {
            var tasks = packetResponders.Select
                (responder => SafeCall(() => responder.Respond(packetEventArgs, ct))).ToList();
            tasks.AddRange
                (genericPacketResponders.Select(responder => SafeCall(() => responder.Respond(packetEventArgs, ct))));

            results = await Task.WhenAll(tasks);
        }
        catch (Exception e)
        {
            results = new Result[] { e };
        }

        var errors = new List<Result>();
        foreach (var result in results)
        {
            if (!result.IsSuccess)
            {
                errors.Add(result);
            }
        }

        var postExecutionResult = await ExecuteAfterExecutionAsync
        (
            scope.ServiceProvider,
            client,
            packetEventArgs,
            results,
            ct
        );
        if (!postExecutionResult.IsSuccess)
        {
            errors.Add(postExecutionResult);
        }

        return errors.Count switch
        {
            0 => Result.FromSuccess(),
            1 => errors[0],
            _ => new AggregateError(errors.Cast<IResult>().ToArray())
        };
    }

    private async Task<Result> ExecuteBeforeExecutionAsync<TPacket>
    (
        IServiceProvider services,
        INostaleClient client,
        PacketEventArgs<TPacket> eventArgs,
        CancellationToken ct
    )
        where TPacket : IPacket
    {
        try
        {
            var results = await Task.WhenAll
            (
                services.GetServices<IPreExecutionEvent>()
                    .Select(x => SafeCall(() => x.ExecuteBeforeExecutionAsync(client, eventArgs, ct)))
            );

            var errorResults = new List<Result>();
            foreach (var result in results)
            {
                if (!result.IsSuccess)
                {
                    errorResults.Add(result);
                }
            }

            return errorResults.Count switch
            {
                1 => errorResults[0],
                0 => Result.FromSuccess(),
                _ => new AggregateError(errorResults.Cast<IResult>().ToArray())
            };
        }
        catch (Exception e)
        {
            return e;
        }
    }

    private async Task<Result> ExecuteAfterExecutionAsync<TPacket>
    (
        IServiceProvider services,
        INostaleClient client,
        PacketEventArgs<TPacket> eventArgs,
        IReadOnlyList<Result> executionResults,
        CancellationToken ct
    )
        where TPacket : IPacket
    {
        try
        {
            var results = await Task.WhenAll
            (
                services.GetServices<IPostExecutionEvent>()
                    .Select(x => SafeCall(() => x.ExecuteAfterExecutionAsync(client, eventArgs, executionResults, ct)))
            );

            var errorResults = new List<Result>();
            foreach (var result in results)
            {
                if (!result.IsSuccess)
                {
                    errorResults.Add(result);
                }
            }

            return errorResults.Count switch
            {
                1 => errorResults[0],
                0 => Result.FromSuccess(),
                _ => new AggregateError(errorResults.Cast<IResult>().ToArray())
            };
        }
        catch (Exception e)
        {
            return e;
        }
    }

    private async Task<Result> SafeCall(Func<Task<Result>> task)
    {
        try
        {
            return await task();
        }
        catch (Exception e)
        {
            return e;
        }
    }
}
\ No newline at end of file

M Core/NosSmooth.Core/Packets/PacketEventArgs.cs => Core/NosSmooth.Core/Packets/PacketEventArgs.cs +7 -0
@@ 9,6 9,13 @@ using NosSmooth.PacketSerializer.Abstractions.Attributes;
namespace NosSmooth.Core.Packets;

/// <summary>
/// Arguments for <see cref="IPacketResponder{TPacket}"/>, <see cref="IRawPacketResponder"/>.
/// </summary>
/// <param name="Source">The source of the packet.</param>
/// <param name="PacketString">The packet string.</param>
public record PacketEventArgs(PacketSource Source, string PacketString);

/// <summary>
/// Arguments for <see cref="IPacketResponder{TPacket}"/>
/// </summary>
/// <param name="Source">The source of the packet.</param>

R Core/NosSmooth.Core/Packets/PacketHandler.cs => Core/NosSmooth.Core/Packets/RawPacketHandler.cs +35 -71
@@ 1,5 1,5 @@
//
//  PacketHandler.cs
//
//  RawPacketHandler.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.


@@ 7,7 7,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;


@@ 19,77 18,33 @@ using Remora.Results;
namespace NosSmooth.Core.Packets;

/// <summary>
/// Calls registered responders for the packet that should be handled.
/// Calls IRawPacketResponder.
/// </summary>
public class PacketHandler
public class RawPacketHandler : IPacketHandler
{
    private readonly IServiceProvider _provider;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketHandler"/> class.
    /// </summary>
    /// <param name="provider">The dependency injection provider.</param>
    public PacketHandler(IServiceProvider provider)
    {
        _provider = provider;
    }
    private readonly IServiceProvider _services;

    /// <summary>
    /// Calls a responder for the given packet.
    /// Initializes a new instance of the <see cref="RawPacketHandler"/> class.
    /// </summary>
    /// <param name="client">The current NosTale client.</param>
    /// <param name="packetType">The source of the packet.</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> HandlePacketAsync
    (
        INostaleClient client,
        PacketSource packetType,
        IPacket packet,
        string packetString,
        CancellationToken ct = default
    )
    /// <param name="services">The serivce provider.</param>
    public RawPacketHandler(IServiceProvider services)
    {
        var processMethod = GetType().GetMethod
        (
            nameof(DispatchResponder),
            BindingFlags.NonPublic | BindingFlags.Instance
        );

        if (processMethod is null)
        {
            throw new InvalidOperationException("Could not find process command generic method in command processor.");
        }
        _services = services;

        var boundProcessMethod = processMethod.MakeGenericMethod(packet.GetType());
        return (Task<Result>)boundProcessMethod.Invoke
        (
            this,
            new object[]
            {
                client,
                packetType,
                packet,
                packetString,
                ct
            }
        )!;
    }

    private async Task<Result> DispatchResponder<TPacket>
    /// <inheritdoc />
    public async Task<Result> HandlePacketAsync
    (
        INostaleClient client,
        PacketSource packetType,
        TPacket packet,
        string packetString,
        CancellationToken ct
        CancellationToken ct = default
    )
        where TPacket : class, IPacket
    {
        using var scope = _provider.CreateScope();
        var packetEventArgs = new PacketEventArgs<TPacket>(packetType, packet, packetString);
        using var scope = _services.CreateScope();
        var packetEventArgs = new PacketEventArgs(packetType, packetString);

        var preExecutionResult = await ExecuteBeforeExecutionAsync(scope.ServiceProvider, client, packetEventArgs, ct);
        if (!preExecutionResult.IsSuccess)


@@ 97,14 52,13 @@ public class PacketHandler
            return preExecutionResult;
        }

        var packetResponders = scope.ServiceProvider.GetServices<IPacketResponder<TPacket>>();
        var genericPacketResponders = scope.ServiceProvider.GetServices<IEveryPacketResponder>();
        var packetResponders = scope.ServiceProvider.GetServices<IRawPacketResponder>();

        Result[] results;
        try
        {
            var tasks = packetResponders.Select(responder => responder.Respond(packetEventArgs, ct)).ToList();
            tasks.AddRange(genericPacketResponders.Select(responder => responder.Respond(packetEventArgs, ct)));
            var tasks = packetResponders.Select
                (responder => SafeCall(() => responder.Respond(packetEventArgs, ct))).ToList();

            results = await Task.WhenAll(tasks);
        }


@@ 143,21 97,20 @@ public class PacketHandler
        };
    }

    private async Task<Result> ExecuteBeforeExecutionAsync<TPacket>
    private async Task<Result> ExecuteBeforeExecutionAsync
    (
        IServiceProvider services,
        INostaleClient client,
        PacketEventArgs<TPacket> eventArgs,
        PacketEventArgs eventArgs,
        CancellationToken ct
    )
        where TPacket : IPacket
    {
        try
        {
            var results = await Task.WhenAll
            (
                services.GetServices<IPreExecutionEvent>()
                    .Select(x => x.ExecuteBeforeExecutionAsync(client, eventArgs, ct))
                    .Select(x => SafeCall(() => x.ExecuteBeforeExecutionAsync(client, eventArgs, ct)))
            );

            var errorResults = new List<Result>();


@@ 182,22 135,21 @@ public class PacketHandler
        }
    }

    private async Task<Result> ExecuteAfterExecutionAsync<TPacket>
    private async Task<Result> ExecuteAfterExecutionAsync
    (
        IServiceProvider services,
        INostaleClient client,
        PacketEventArgs<TPacket> eventArgs,
        PacketEventArgs eventArgs,
        IReadOnlyList<Result> executionResults,
        CancellationToken ct
    )
        where TPacket : IPacket
    {
        try
        {
            var results = await Task.WhenAll
            (
                services.GetServices<IPostExecutionEvent>()
                    .Select(x => x.ExecuteAfterExecutionAsync(client, eventArgs, executionResults, ct))
                    .Select(x => SafeCall(() => x.ExecuteAfterExecutionAsync(client, eventArgs, executionResults, ct)))
            );

            var errorResults = new List<Result>();


@@ 221,4 173,16 @@ public class PacketHandler
            return e;
        }
    }

    private async Task<Result> SafeCall(Func<Task<Result>> task)
    {
        try
        {
            return await task();
        }
        catch (Exception e)
        {
            return e;
        }
    }
}
\ No newline at end of file

M Core/NosSmooth.Core/Stateful/StatefulPreExecutionEvent.cs => Core/NosSmooth.Core/Stateful/StatefulPreExecutionEvent.cs +10 -1
@@ 41,8 41,17 @@ internal class StatefulPreExecutionEvent : IPreExecutionEvent, IPreCommandExecut
        return Task.FromResult(Result.FromSuccess());
    }

    /// <inheritdoc/>
    public Task<Result> ExecuteBeforeExecutionAsync
        (INostaleClient client, PacketEventArgs packetArgs, CancellationToken ct = default)
    {
        _injector.Client = client;
        return Task.FromResult(Result.FromSuccess());
    }

    /// <inheritdoc />
    public Task<Result> ExecuteBeforeCommandAsync<TCommand>(INostaleClient client, TCommand command, CancellationToken ct = default)
    public Task<Result> ExecuteBeforeCommandAsync<TCommand>
        (INostaleClient client, TCommand command, CancellationToken ct = default)
        where TCommand : ICommand
    {
        _injector.Client = client;

M Core/NosSmooth.Game/Apis/Safe/NostaleChatApi.cs => Core/NosSmooth.Game/Apis/Safe/NostaleChatApi.cs +2 -2
@@ 18,13 18,13 @@ namespace NosSmooth.Game.Apis.Safe;
public class NostaleChatApi
{
    // TODO: check length of the messages
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="NostaleChatApi"/> class.
    /// </summary>
    /// <param name="client">The nostale client.</param>
    public NostaleChatApi(INostaleClient client)
    public NostaleChatApi(ManagedNostaleClient client)
    {
        _client = client;
    }

M Core/NosSmooth.Game/Apis/Safe/NostaleSkillsApi.cs => Core/NosSmooth.Game/Apis/Safe/NostaleSkillsApi.cs +2 -2
@@ 25,7 25,7 @@ namespace NosSmooth.Game.Apis.Safe;
public class NostaleSkillsApi
{
    private readonly Game _game;
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;
    private readonly Contractor _contractor;

    /// <summary>


@@ 34,7 34,7 @@ public class NostaleSkillsApi
    /// <param name="game">The game.</param>
    /// <param name="client">The NosTale client.</param>
    /// <param name="contractor">The contractor.</param>
    public NostaleSkillsApi(Game game, INostaleClient client, Contractor contractor)
    public NostaleSkillsApi(Game game, ManagedNostaleClient client, Contractor contractor)
    {
        _game = game;
        _client = client;

M Core/NosSmooth.Game/Apis/Unsafe/UnsafeInventoryApi.cs => Core/NosSmooth.Game/Apis/Unsafe/UnsafeInventoryApi.cs +2 -2
@@ 23,7 23,7 @@ namespace NosSmooth.Game.Apis.Unsafe;
/// </summary>
public class UnsafeInventoryApi
{
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;
    private readonly Contractor _contractor;

    /// <summary>


@@ 31,7 31,7 @@ public class UnsafeInventoryApi
    /// </summary>
    /// <param name="client">The nostale client.</param>
    /// <param name="contractor">The contractor.</param>
    public UnsafeInventoryApi(INostaleClient client, Contractor contractor)
    public UnsafeInventoryApi(ManagedNostaleClient client, Contractor contractor)
    {
        _client = client;
        _contractor = contractor;

M Core/NosSmooth.Game/Apis/Unsafe/UnsafeMapApi.cs => Core/NosSmooth.Game/Apis/Unsafe/UnsafeMapApi.cs +2 -2
@@ 19,14 19,14 @@ namespace NosSmooth.Game.Apis.Unsafe;
public class UnsafeMapApi
{
    private readonly Game _game;
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="UnsafeMapApi"/> class.
    /// </summary>
    /// <param name="game">The game.</param>
    /// <param name="client">The client.</param>
    public UnsafeMapApi(Game game, INostaleClient client)
    public UnsafeMapApi(Game game, ManagedNostaleClient client)
    {
        _game = game;
        _client = client;

M Core/NosSmooth.Game/Apis/Unsafe/UnsafeMateApi.cs => Core/NosSmooth.Game/Apis/Unsafe/UnsafeMateApi.cs +2 -2
@@ 17,13 17,13 @@ namespace NosSmooth.Game.Apis.Unsafe;
/// </summary>
public class UnsafeMateApi
{
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="UnsafeMateApi"/> class.
    /// </summary>
    /// <param name="client">The client.</param>
    public UnsafeMateApi(INostaleClient client)
    public UnsafeMateApi(ManagedNostaleClient client)
    {
        _client = client;
    }

M Core/NosSmooth.Game/Apis/Unsafe/UnsafeMateSkillsApi.cs => Core/NosSmooth.Game/Apis/Unsafe/UnsafeMateSkillsApi.cs +2 -2
@@ 16,13 16,13 @@ namespace NosSmooth.Game.Apis.Unsafe;
/// </summary>
public class UnsafeMateSkillsApi
{
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="UnsafeMateSkillsApi"/> class.
    /// </summary>
    /// <param name="client">The client.</param>
    public UnsafeMateSkillsApi(INostaleClient client)
    public UnsafeMateSkillsApi(ManagedNostaleClient client)
    {
        _client = client;
    }

M Core/NosSmooth.Game/Apis/Unsafe/UnsafeSkillsApi.cs => Core/NosSmooth.Game/Apis/Unsafe/UnsafeSkillsApi.cs +2 -2
@@ 23,7 23,7 @@ namespace NosSmooth.Game.Apis.Unsafe;
/// </summary>
public class UnsafeSkillsApi
{
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;
    private readonly Game _game;
    private readonly Contractor _contractor;



@@ 33,7 33,7 @@ public class UnsafeSkillsApi
    /// <param name="client">The nostale client.</param>
    /// <param name="game">The game.</param>
    /// <param name="contractor">The contractor.</param>
    public UnsafeSkillsApi(INostaleClient client, Game game, Contractor contractor)
    public UnsafeSkillsApi(ManagedNostaleClient client, Game game, Contractor contractor)
    {
        _client = client;
        _game = game;

M Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Game/Extensions/ServiceCollectionExtensions.cs +1 -1
@@ 41,7 41,7 @@ public static class ServiceCollectionExtensions
    public static IServiceCollection AddNostaleGame(this IServiceCollection serviceCollection)
    {
        serviceCollection
            .AddNostaleCore()
            .AddManagedNostaleCore()
            .AddMemoryCache()
            .TryAddScoped<EventDispatcher>();
        serviceCollection.TryAddSingleton<Game>();

M Extensions/NosSmooth.Extensions.Combat/CombatManager.cs => Extensions/NosSmooth.Extensions.Combat/CombatManager.cs +2 -2
@@ 20,7 20,7 @@ namespace NosSmooth.Extensions.Combat;
/// </summary>
public class CombatManager : IStatefulEntity
{
    private readonly INostaleClient _client;
    private readonly ManagedNostaleClient _client;
    private readonly Game.Game _game;

    /// <summary>


@@ 28,7 28,7 @@ public class CombatManager : IStatefulEntity
    /// </summary>
    /// <param name="client">The NosTale client.</param>
    /// <param name="game">The game.</param>
    public CombatManager(INostaleClient client, Game.Game game)
    public CombatManager(ManagedNostaleClient client, Game.Game game)
    {
        _client = client;
        _game = game;

M Extensions/NosSmooth.Extensions.Combat/CombatState.cs => Extensions/NosSmooth.Extensions.Combat/CombatState.cs +2 -2
@@ 22,7 22,7 @@ internal class CombatState : ICombatState
    /// <param name="client">The NosTale client.</param>
    /// <param name="game">The game.</param>
    /// <param name="combatManager">The combat manager.</param>
    public CombatState(INostaleClient client, Game.Game game, CombatManager combatManager)
    public CombatState(ManagedNostaleClient client, Game.Game game, CombatManager combatManager)
    {
        Client = client;
        Game = game;


@@ 43,7 43,7 @@ internal class CombatState : ICombatState
    public Game.Game Game { get; }

    /// <inheritdoc/>
    public INostaleClient Client { get; }
    public ManagedNostaleClient Client { get; }

    /// <summary>
    /// Gets whether the manager may currently quit.

M Extensions/NosSmooth.Extensions.Combat/ICombatState.cs => Extensions/NosSmooth.Extensions.Combat/ICombatState.cs +1 -1
@@ 29,7 29,7 @@ public interface ICombatState
    /// <summary>
    /// Gets the NosTale client.
    /// </summary>
    public INostaleClient Client { get; }
    public ManagedNostaleClient Client { get; }

    /// <summary>
    /// Gets whether there is an operation that cannot be used

M Samples/FileClient/Client.cs => Samples/FileClient/Client.cs +3 -27
@@ 24,8 24,7 @@ namespace FileClient;
public class Client : BaseNostaleClient
{
    private const string LineRegex = ".*\\[(Recv|Send)\\]\t(.*)";
    private readonly PacketHandler _packetHandler;
    private readonly IPacketSerializer _packetSerializer;
    private readonly IPacketHandler _packetHandler;
    private readonly ILogger<Client> _logger;
    private readonly Stream _stream;



@@ 35,21 34,18 @@ public class Client : BaseNostaleClient
    /// <param name="stream">The stream with packets.</param>
    /// <param name="packetHandler">The packet handler.</param>
    /// <param name="commandProcessor">The command processor.</param>
    /// <param name="packetSerializer">The packet serializer.</param>
    /// <param name="logger">The logger.</param>
    public Client
    (
        Stream stream,
        PacketHandler packetHandler,
        IPacketHandler packetHandler,
        CommandProcessor commandProcessor,
        IPacketSerializer packetSerializer,
        ILogger<Client> logger
    )
        : base(commandProcessor, packetSerializer)
        : base(commandProcessor)
    {
        _stream = stream;
        _packetHandler = packetHandler;
        _packetSerializer = packetSerializer;
        _logger = logger;
    }



@@ 78,12 74,10 @@ public class Client : BaseNostaleClient
            var packetStr = match.Groups[2].Value;

            var source = type == "Recv" ? PacketSource.Server : PacketSource.Client;
            var packet = CreatePacket(packetStr, source);
            Result result = await _packetHandler.HandlePacketAsync
            (
                this,
                source,
                packet,
                packetStr,
                stopRequested
            );


@@ 103,7 97,6 @@ public class Client : BaseNostaleClient
        (
            this,
            PacketSource.Client,
            CreatePacket(packetString, PacketSource.Client),
            packetString,
            ct
        );


@@ 116,25 109,8 @@ public class Client : BaseNostaleClient
        (
            this,
            PacketSource.Server,
            CreatePacket(packetString, PacketSource.Server),
            packetString,
            ct
        );
    }

    private IPacket CreatePacket(string packetStr, PacketSource source)
    {
        var packetResult = _packetSerializer.Deserialize(packetStr, source);
        if (!packetResult.IsSuccess)
        {
            if (packetResult.Error is PacketConverterNotFoundError err)
            {
                return new UnresolvedPacket(err.Header, packetStr);
            }

            return new ParsingFailedPacket(packetResult, packetStr);
        }

        return packetResult.Entity;
    }
}
\ No newline at end of file

M Samples/FileClient/Program.cs => Samples/FileClient/Program.cs +2 -3
@@ 46,7 46,7 @@ public static class Program
            {
                coll.AddHostedService<App>();

                coll.AddNostaleCore()
                coll.AddManagedNostaleCore()
                    .AddNostaleGame()
                    .AddNostaleDataFiles()
                    .AddPacketResponder<PacketNotFoundResponder>()


@@ 57,9 57,8 @@ public static class Program
                    });
                coll.AddSingleton<INostaleClient>(p => new Client(
                    fileStream,
                    p.GetRequiredService<PacketHandler>(),
                    p.GetRequiredService<IPacketHandler>(),
                    p.GetRequiredService<CommandProcessor>(),
                    p.GetRequiredService<IPacketSerializer>(),
                    p.GetRequiredService<ILogger<Client>>()
                ));
            })

M Tests/NosSmooth.Core.Tests/Fakes/FakeEmptyNostaleClient.cs => Tests/NosSmooth.Core.Tests/Fakes/FakeEmptyNostaleClient.cs +0 -12
@@ 24,12 24,6 @@ public class FakeEmptyNostaleClient : INostaleClient
    }

    /// <inheritdoc />
    public Task<Result> SendPacketAsync(IPacket packet, CancellationToken ct = default)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc />
    public Task<Result> SendPacketAsync(string packetString, CancellationToken ct = default)
    {
        throw new NotImplementedException();


@@ 42,12 36,6 @@ public class FakeEmptyNostaleClient : INostaleClient
    }

    /// <inheritdoc />
    public Task<Result> ReceivePacketAsync(IPacket packet, CancellationToken ct = default)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc />
    public Task<Result> SendCommandAsync(ICommand command, CancellationToken ct = default)
    {
        throw new NotImplementedException();

A Tests/NosSmooth.Core.Tests/Fakes/FakeLogger.cs => Tests/NosSmooth.Core.Tests/Fakes/FakeLogger.cs +39 -0
@@ 0,0 1,39 @@
//
//  FakeLogger.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 Microsoft.Extensions.Logging;

namespace NosSmooth.Core.Tests.Fakes;

/// <inheritdoc />
public class FakeLogger<T> : ILogger<T>
{
    /// <inheritdoc />
    public void Log<TState>
    (
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter
    )
    {
    }

    /// <inheritdoc />
    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

    /// <inheritdoc />
    public IDisposable? BeginScope<TState>(TState state)
        where TState : notnull
    {
        return null;
    }
}
\ No newline at end of file

M Tests/NosSmooth.Core.Tests/Fakes/FakeNostaleClient.cs => Tests/NosSmooth.Core.Tests/Fakes/FakeNostaleClient.cs +0 -12
@@ 37,12 37,6 @@ public class FakeNostaleClient : INostaleClient
    }

    /// <inheritdoc />
    public Task<Result> SendPacketAsync(IPacket packet, CancellationToken ct = default)
    {
        throw new System.NotImplementedException();
    }

    /// <inheritdoc />
    public Task<Result> SendPacketAsync(string packetString, CancellationToken ct = default)
    {
        throw new System.NotImplementedException();


@@ 55,12 49,6 @@ public class FakeNostaleClient : INostaleClient
    }

    /// <inheritdoc />
    public Task<Result> ReceivePacketAsync(IPacket packet, CancellationToken ct = default)
    {
        throw new System.NotImplementedException();
    }

    /// <inheritdoc />
    public Task<Result> SendCommandAsync(ICommand command, CancellationToken ct = default)
        => Task.FromResult(_handleCommand(command, ct));
}
\ No newline at end of file

M Tests/NosSmooth.Core.Tests/Fakes/Packets/Events/PacketEvent.cs => Tests/NosSmooth.Core.Tests/Fakes/Packets/Events/PacketEvent.cs +15 -0
@@ 47,6 47,11 @@ public class PacketEvent : IPreExecutionEvent, IPostExecutionEvent
        => Task.FromResult(_preHandler(client, packetArgs.Source, packetArgs.Packet, packetArgs.PacketString));

    /// <inheritdoc />
    public Task<Result> ExecuteBeforeExecutionAsync
        (INostaleClient client, PacketEventArgs packetArgs, CancellationToken ct = default)
        => Task.FromResult(Result.FromSuccess());

    /// <inheritdoc />
    public Task<Result> ExecuteAfterExecutionAsync<TPacket>
    (
        INostaleClient client,


@@ 66,4 71,14 @@ public class PacketEvent : IPreExecutionEvent, IPostExecutionEvent
                executionResults
            )
        );

    /// <inheritdoc />
    public Task<Result> ExecuteAfterExecutionAsync
    (
        INostaleClient client,
        PacketEventArgs packetArgs,
        IReadOnlyList<Result> executionResults,
        CancellationToken ct = default
    )
        => Task.FromResult(Result.FromSuccess());
}
\ No newline at end of file

M Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacket.cs => Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacket.cs +8 -1
@@ 5,6 5,7 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets;
using NosSmooth.PacketSerializer.Abstractions.Attributes;

namespace NosSmooth.Core.Tests.Fakes.Packets;



@@ 12,4 13,10 @@ namespace NosSmooth.Core.Tests.Fakes.Packets;
/// A fake packet.
/// </summary>
/// <param name="Input">The input.</param>
public record FakePacket(string Input) : IPacket;
\ No newline at end of file
[PacketHeader("fake", PacketSource.Server)]
[GenerateSerializer(true)]
public record FakePacket
(
    [PacketGreedyIndex(0)]
    string Input
) : IPacket;
\ No newline at end of file

M Tests/NosSmooth.Core.Tests/NosSmooth.Core.Tests.csproj => Tests/NosSmooth.Core.Tests/NosSmooth.Core.Tests.csproj +7 -1
@@ 1,8 1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <TargetFrameworks>net7.0;netstandard2.1</TargetFrameworks>
        <Nullable>enable</Nullable>
        <LangVersion>10</LangVersion>

        <IsPackable>false</IsPackable>
    </PropertyGroup>


@@ 27,4 29,8 @@
      <ProjectReference Include="..\..\Core\NosSmooth.Core\NosSmooth.Core.csproj" />
    </ItemGroup>

    <ItemGroup>
        <ProjectReference Include="..\..\Packets\NosSmooth.PacketSerializersGenerator\NosSmooth.PacketSerializersGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    </ItemGroup>

</Project>

M Tests/NosSmooth.Core.Tests/Packets/PacketHandlerTests.Events.cs => Tests/NosSmooth.Core.Tests/Packets/PacketHandlerTests.Events.cs +11 -3
@@ 5,8 5,10 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Packets;
using NosSmooth.Core.Tests.Fakes;


@@ 14,6 16,8 @@ using NosSmooth.Core.Tests.Fakes.Commands;
using NosSmooth.Core.Tests.Fakes.Packets;
using NosSmooth.Core.Tests.Fakes.Packets.Events;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using NosSmooth.PacketSerializer.Extensions;
using NosSmooth.PacketSerializer.Packets;
using Remora.Results;
using Xunit;



@@ 34,7 38,10 @@ public class PacketHandlerTestsEvents
        var called = false;
        var client = new FakeEmptyNostaleClient();
        var provider = new ServiceCollection()
            .AddSingleton<PacketHandler>()
            .AddPacketSerialization()
            .AddGeneratedSerializers(Assembly.GetExecutingAssembly())
            .AddSingleton(typeof(ILogger<>), typeof(FakeLogger<>))
            .AddSingleton<IPacketHandler, ManagedPacketHandler>()
            .AddScoped<IPreExecutionEvent>
            (
                _ => new PacketEvent


@@ 59,8 66,9 @@ public class PacketHandlerTestsEvents
            )
            .BuildServiceProvider();

        var result = await provider.GetRequiredService<PacketHandler>().HandlePacketAsync
            (client, PacketSource.Client, new FakePacket("a"), "fake a");
        provider.GetRequiredService<IPacketTypesRepository>().AddPacketType(typeof(FakePacket));
        var result = await provider.GetRequiredService<IPacketHandler>().HandlePacketAsync
            (client, PacketSource.Client, "fake a");
        Assert.True(result.IsSuccess);
        Assert.True(called);
    }

M Tests/NosSmooth.Core.Tests/Stateful/StatefulInjectorTests.cs => Tests/NosSmooth.Core.Tests/Stateful/StatefulInjectorTests.cs +12 -4
@@ 4,8 4,10 @@
//  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.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Extensions;


@@ 17,6 19,8 @@ using NosSmooth.Core.Tests.Fakes.Packets;
using NosSmooth.Core.Tests.Packets;
using NosSmooth.Packets.Server.Maps;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using NosSmooth.PacketSerializer.Extensions;
using NosSmooth.PacketSerializer.Packets;
using Remora.Results;
using Xunit;



@@ 135,7 139,10 @@ public class StatefulInjectorTests
                .AddStatefulInjector()
                .AddStatefulEntity<FakeEntity>()
                .AddSingleton<CommandProcessor>()
                .AddSingleton<PacketHandler>()
                .AddSingleton(typeof(ILogger<>), typeof(FakeLogger<>))
                .AddPacketSerialization()
                .AddGeneratedSerializers(Assembly.GetExecutingAssembly())
                .AddSingleton<IPacketHandler, ManagedPacketHandler>()
                .AddScoped<IPacketResponder<FakePacket>>
                (p =>
                    {


@@ 161,10 168,11 @@ public class StatefulInjectorTests
                )
                .BuildServiceProvider();

        var handler = services.GetRequiredService<PacketHandler>();
        var handler = services.GetRequiredService<IPacketHandler>();

        Assert.True((await handler.HandlePacketAsync(client1, PacketSource.Server, new FakePacket("1"), "fake 1")).IsSuccess);
        Assert.True((await handler.HandlePacketAsync(client2, PacketSource.Server, new FakePacket("2"), "fake 2")).IsSuccess);
        services.GetRequiredService<IPacketTypesRepository>().AddPacketType(typeof(FakePacket));
        Assert.True((await handler.HandlePacketAsync(client1, PacketSource.Server, "fake 1")).IsSuccess);
        Assert.True((await handler.HandlePacketAsync(client2, PacketSource.Server, "fake 2")).IsSuccess);
        Assert.NotNull(entity1);
        Assert.NotNull(entity2);
        Assert.NotEqual(entity1, entity2);