~ruther/NosSmooth

8270c3aeb9db6f8650203b34111be28accc5413e — František Boháček 3 years ago 5c40f7e
feat(core): add pre and post responder events
6 files changed, 214 insertions(+), 17 deletions(-)

M Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs
D Core/NosSmooth.Core/Packets/Converters/ISpecificPacketSerializer.cs
D Core/NosSmooth.Core/Packets/Converters/InPacketSerializer.cs
A Core/NosSmooth.Core/Packets/IPostExecutionEvent.cs
A Core/NosSmooth.Core/Packets/IPreExecutionEvent.cs
M Core/NosSmooth.Core/Packets/PacketHandler.cs
M Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs => Core/NosSmooth.Core/Extensions/ServiceCollectionExtensions.cs +25 -1
@@ 162,4 162,28 @@ public static class ServiceCollectionExtensions

        return serviceCollection;
    }
}

    /// <summary>
    /// Add the given pre execution event that will be executed before the packet responders.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <typeparam name="TEvent">The pre execution event type.</typeparam>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddPreExecutionEvent<TEvent>(this IServiceCollection serviceCollection)
        where TEvent : class, IPreExecutionEvent
    {
        return serviceCollection.AddScoped<IPreExecutionEvent, TEvent>();
    }

    /// <summary>
    /// Add the given post execution event that will be executed after the packet responders.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <typeparam name="TEvent">The pre execution event type.</typeparam>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddPostExecutionEvent<TEvent>(this IServiceCollection serviceCollection)
        where TEvent : class, IPostExecutionEvent
    {
        return serviceCollection.AddScoped<IPostExecutionEvent, TEvent>();
    }
}
\ No newline at end of file

D Core/NosSmooth.Core/Packets/Converters/ISpecificPacketSerializer.cs => Core/NosSmooth.Core/Packets/Converters/ISpecificPacketSerializer.cs +0 -1
@@ 1,1 0,0 @@

\ No newline at end of file

D Core/NosSmooth.Core/Packets/Converters/InPacketSerializer.cs => Core/NosSmooth.Core/Packets/Converters/InPacketSerializer.cs +0 -1
@@ 1,1 0,0 @@

\ No newline at end of file

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

namespace NosSmooth.Core.Packets;

/// <summary>
/// Event executed after the packet responders.
/// </summary>
public interface IPostExecutionEvent
{
    /// <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>
    /// <typeparam name="TPacket">The type of the packet.</typeparam>
    /// <returns>A result that may or may not succeed.</returns>
    public Task<Result> ExecuteAfterExecutionAsync<TPacket>
    (
        INostaleClient client,
        PacketEventArgs<TPacket> packetArgs,
        IReadOnlyList<Result> executionResults,
        CancellationToken ct = default
    )
        where TPacket : IPacket;
}
\ No newline at end of file

A Core/NosSmooth.Core/Packets/IPreExecutionEvent.cs => Core/NosSmooth.Core/Packets/IPreExecutionEvent.cs +38 -0
@@ 0,0 1,38 @@
//
//  IPreExecutionEvent.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 Remora.Results;

namespace NosSmooth.Core.Packets;

/// <summary>
/// Event executed prior to packet responders.
/// </summary>
public interface IPreExecutionEvent
{
    /// <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>
    /// <typeparam name="TPacket">The type of the packet.</typeparam>
    /// <returns>A result that may or may not succeed.</returns>
    public Task<Result> ExecuteBeforeExecutionAsync<TPacket>
    (
        INostaleClient client,
        PacketEventArgs<TPacket> packetArgs,
        CancellationToken ct = default
    )
        where TPacket : IPacket;
}
\ No newline at end of file

M Core/NosSmooth.Core/Packets/PacketHandler.cs => Core/NosSmooth.Core/Packets/PacketHandler.cs +113 -14
@@ 11,6 11,7 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Core.Client;
using NosSmooth.Packets;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using Remora.Results;


@@ 32,15 33,28 @@ public class PacketHandler : IPacketHandler
    }

    /// <inheritdoc />
    public Task<Result> HandleReceivedPacketAsync(IPacket packet, string packetString, CancellationToken ct)
        => HandlePacketAsync(PacketSource.Server, packet, packetString, ct);
    public Task<Result> HandleReceivedPacketAsync
    (
        INostaleClient client,
        IPacket packet,
        string packetString,
        CancellationToken ct
    )
        => HandlePacketAsync(client, PacketSource.Server, packet, packetString, ct);

    /// <inheritdoc />
    public Task<Result> HandleSentPacketAsync(IPacket packet, string packetString, CancellationToken ct)
        => HandlePacketAsync(PacketSource.Client, packet, packetString, ct);
    public Task<Result> HandleSentPacketAsync
    (
        INostaleClient client,
        IPacket packet,
        string packetString,
        CancellationToken ct
    )
        => HandlePacketAsync(client, PacketSource.Client, packet, packetString, ct);

    private Task<Result> HandlePacketAsync
    (
        INostaleClient client,
        PacketSource packetType,
        IPacket packet,
        string packetString,


@@ 59,16 73,23 @@ public class PacketHandler : IPacketHandler
        }

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

    private async Task<Result> DispatchResponder<TPacket>(
    private async Task<Result> DispatchResponder<TPacket>
    (
        INostaleClient client,
        PacketSource packetType,
        TPacket packet,
        string packetString,


@@ 77,10 98,17 @@ public class PacketHandler : IPacketHandler
        where TPacket : class, IPacket
    {
        using var scope = _provider.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>();

        var packetEventArgs = new PacketEventArgs<TPacket>(packetType, packet, packetString);
        var tasks = packetResponders.Select(responder => responder.Respond(packetEventArgs, ct)).ToList();
        tasks.AddRange(genericPacketResponders.Select(responder => responder.Respond(packetEventArgs, ct)));



@@ 95,6 123,12 @@ public class PacketHandler : IPacketHandler
            }
        }

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

        return errors.Count switch
        {
            0 => Result.FromSuccess(),


@@ 102,4 136,69 @@ public class PacketHandler : IPacketHandler
            _ => new AggregateError(errors.Cast<IResult>().ToArray())
        };
    }
}

    private async Task<Result> ExecuteBeforeExecutionAsync<TPacket>
    (
        IServiceProvider services,
        INostaleClient client,
        PacketEventArgs<TPacket> eventArgs,
        CancellationToken ct
    )
        where TPacket : IPacket
    {
        var results = await Task.WhenAll
        (
            services.GetServices<IPreExecutionEvent>()
                .Select(x => 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())
        };
    }

    private async Task<Result> ExecuteAfterExecutionAsync<TPacket>
    (
        IServiceProvider services,
        INostaleClient client,
        PacketEventArgs<TPacket> eventArgs,
        IReadOnlyList<Result> executionResults,
        CancellationToken ct
    )
        where TPacket : IPacket
    {
        var results = await Task.WhenAll
        (
            services.GetServices<IPostExecutionEvent>()
                .Select(x => 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())
        };
    }
}
\ No newline at end of file

Do not follow this link