diff --git a/Continuity.AuthServer/AuthSession.cs b/Continuity.AuthServer/AuthSession.cs index d52da17..164b319 100644 --- a/Continuity.AuthServer/AuthSession.cs +++ b/Continuity.AuthServer/AuthSession.cs @@ -6,6 +6,7 @@ using Continuity.AuthServer.Packets; using MassTransit.Mediator; using Microsoft.Extensions.Logging; using NetCoreServer; +using Rai.PacketMediator; using Wonderking.Packets; namespace Continuity.AuthServer; @@ -30,11 +31,11 @@ public class AuthSession : TcpSession return base.Send(buffer); } - public Task SendAsync(IPacket packet) + public Task SendAsync(IOutgoingPacket packet) { var type = packet.GetType(); _logger.LogInformation("Packet of type {Type} is being serialized", type.Name); - var packetIdAttribute = type.GetCustomAttribute(); + var packetIdAttribute = type.GetCustomAttribute(); if (packetIdAttribute == null) { return Task.CompletedTask; diff --git a/Continuity.AuthServer/Consumers/PacketConsumer.cs b/Continuity.AuthServer/Consumers/PacketConsumer.cs index 4e42d9f..034cf57 100644 --- a/Continuity.AuthServer/Consumers/PacketConsumer.cs +++ b/Continuity.AuthServer/Consumers/PacketConsumer.cs @@ -1,23 +1,26 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using Continuity.AuthServer.Packets; -using Continuity.AuthServer.Services; using MassTransit; +using NetCoreServer; +using Rai.PacketMediator; +using Wonderking.Packets; namespace Continuity.AuthServer.Consumers; public class PacketConsumer : IConsumer { - private readonly PacketDistributorService _distributorService; + private readonly PacketDistributorService _distributorService; - public PacketConsumer(PacketDistributorService distributorService) + public PacketConsumer(PacketDistributorService distributorService) { _distributorService = distributorService; } public Task Consume(ConsumeContext context) { - _distributorService.AddPacket(context.Message); + _distributorService.AddPacket(context.Message.MessageBody, context.Message.OperationCode, + context.Message.Session); return Task.CompletedTask; } } diff --git a/Continuity.AuthServer/PacketHandlers/ChannelSelectionHandler.cs b/Continuity.AuthServer/PacketHandlers/ChannelSelectionHandler.cs index c177f72..e5e121d 100644 --- a/Continuity.AuthServer/PacketHandlers/ChannelSelectionHandler.cs +++ b/Continuity.AuthServer/PacketHandlers/ChannelSelectionHandler.cs @@ -5,13 +5,14 @@ using Continuity.AuthServer.DB.Documents; using DotNext.Collections.Generic; using Microsoft.EntityFrameworkCore; using NetCoreServer; +using Rai.PacketMediator; using Wonderking.Packets.Incoming; using Wonderking.Packets.Outgoing; using Wonderking.Packets.Outgoing.Data; namespace Continuity.AuthServer.PacketHandlers; -public partial class ChannelSelectionHandler : IPacketHandler +public partial class ChannelSelectionHandler : IPacketHandler { private readonly WonderkingContext _wonderkingContext; diff --git a/Continuity.AuthServer/PacketHandlers/CharacterCreationHandler.cs b/Continuity.AuthServer/PacketHandlers/CharacterCreationHandler.cs index 2522a67..169249f 100644 --- a/Continuity.AuthServer/PacketHandlers/CharacterCreationHandler.cs +++ b/Continuity.AuthServer/PacketHandlers/CharacterCreationHandler.cs @@ -5,6 +5,7 @@ using Continuity.AuthServer.DB.Documents; using Continuity.AuthServer.Services; using Microsoft.EntityFrameworkCore; using NetCoreServer; +using Rai.PacketMediator; using Wonderking.Game.Data.Character; using Wonderking.Game.Mapping; using Wonderking.Packets.Incoming; @@ -13,7 +14,7 @@ using Wonderking.Packets.Outgoing.Data; namespace Continuity.AuthServer.PacketHandlers; -public class CharacterCreationHandler : IPacketHandler +public class CharacterCreationHandler : IPacketHandler { private readonly CharacterStatsMappingConfiguration _characterStatsMapping; private readonly ItemObjectPoolService _itemObjectPoolService; diff --git a/Continuity.AuthServer/PacketHandlers/CharacterDeletionHandler.cs b/Continuity.AuthServer/PacketHandlers/CharacterDeletionHandler.cs index cff7c4b..2a5001c 100644 --- a/Continuity.AuthServer/PacketHandlers/CharacterDeletionHandler.cs +++ b/Continuity.AuthServer/PacketHandlers/CharacterDeletionHandler.cs @@ -3,12 +3,13 @@ using Continuity.AuthServer.DB; using Microsoft.EntityFrameworkCore; using NetCoreServer; +using Rai.PacketMediator; using Wonderking.Packets.Incoming; using Wonderking.Packets.Outgoing; namespace Continuity.AuthServer.PacketHandlers; -public class CharacterDeletionHandler : IPacketHandler +public class CharacterDeletionHandler : IPacketHandler { private readonly WonderkingContext _wonderkingContext; diff --git a/Continuity.AuthServer/PacketHandlers/CharacterNameCheckHandler.cs b/Continuity.AuthServer/PacketHandlers/CharacterNameCheckHandler.cs index ddd6c37..bbe0d62 100644 --- a/Continuity.AuthServer/PacketHandlers/CharacterNameCheckHandler.cs +++ b/Continuity.AuthServer/PacketHandlers/CharacterNameCheckHandler.cs @@ -3,12 +3,13 @@ using Continuity.AuthServer.DB; using Microsoft.EntityFrameworkCore; using NetCoreServer; +using Rai.PacketMediator; using Wonderking.Packets.Incoming; using Wonderking.Packets.Outgoing; namespace Continuity.AuthServer.PacketHandlers; -public class CharacterNameCheckHandler : IPacketHandler +public class CharacterNameCheckHandler : IPacketHandler { private readonly WonderkingContext _wonderkingContext; diff --git a/Continuity.AuthServer/PacketHandlers/IPacketHandler.cs b/Continuity.AuthServer/PacketHandlers/IPacketHandler.cs deleted file mode 100644 index 4737145..0000000 --- a/Continuity.AuthServer/PacketHandlers/IPacketHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. - -using System.Diagnostics; -using JetBrains.Annotations; -using NetCoreServer; -using Wonderking.Packets; - -namespace Continuity.AuthServer.PacketHandlers; - -[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] -public interface IPacketHandler : IPacketHandler where T : IPacket -{ - [UsedImplicitly] - public Task HandleAsync(T packet, TcpSession session); - - async Task IPacketHandler.TryHandleAsync(IPacket packet, TcpSession session) - { - if (packet is not T tPacket) - { - return false; - } - - using (var activity = new ActivitySource(nameof(Server)).StartActivity("HandleAsync")) - { - activity?.SetTag("Handler", this.ToString()); - activity?.SetTag("PacketId", packet.ToString()); - await HandleAsync(tPacket, session); - } - - return true; - } -} - -public interface IPacketHandler -{ - Task TryHandleAsync(IPacket packet, TcpSession session); -} diff --git a/Continuity.AuthServer/PacketHandlers/LoginHandler.cs b/Continuity.AuthServer/PacketHandlers/LoginHandler.cs index c63fd30..647a07e 100644 --- a/Continuity.AuthServer/PacketHandlers/LoginHandler.cs +++ b/Continuity.AuthServer/PacketHandlers/LoginHandler.cs @@ -11,13 +11,14 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using NetCoreServer; +using Rai.PacketMediator; using Wonderking.Packets.Incoming; using Wonderking.Packets.Outgoing; using Wonderking.Packets.Outgoing.Data; namespace Continuity.AuthServer.PacketHandlers; -public class LoginHandler : IPacketHandler +public class LoginHandler : IPacketHandler { private readonly IConfiguration _configuration; private readonly ILogger _logger; diff --git a/Continuity.AuthServer/Program.cs b/Continuity.AuthServer/Program.cs index 3d335c3..0ab1f80 100644 --- a/Continuity.AuthServer/Program.cs +++ b/Continuity.AuthServer/Program.cs @@ -12,12 +12,15 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using NetCoreServer; using Npgsql; using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; +using Rai.PacketMediator; using Wonderking.Game.Mapping; +using Wonderking.Packets; var builder = Host.CreateApplicationBuilder(); #if DEBUG @@ -83,7 +86,8 @@ if (configuration.GetValue("Tracing:Enabled")) { tracing.AddZipkinExporter(options => options.Endpoint = new Uri(configuration["Zipkin:Endpoint"] ?? string.Empty)); - tracing.AddOtlpExporter(options => options.Endpoint = new Uri(configuration["OTLP:Tracing:Endpoint"] ?? string.Empty)); + tracing.AddOtlpExporter(options => + options.Endpoint = new Uri(configuration["OTLP:Tracing:Endpoint"] ?? string.Empty)); }); } @@ -102,12 +106,13 @@ builder.Services.AddSingleton( File.ReadAllText("config/character-stats.mapping.json")) ?? throw new InvalidOperationException()); builder.Services.AddSingleton(loggerFactory); -builder.Services.AddSingleton(); +builder.Services.AddSingleton>(); builder.Services.AddSingleton(); builder.Services.AddHostedService(provider => provider.GetService() ?? throw new InvalidOperationException()); builder.Services.AddHostedService(provider => - provider.GetService() ?? throw new InvalidOperationException()); + provider.GetService>() ?? + throw new InvalidOperationException()); builder.Services.AddMassTransit(x => { x.UsingInMemory((context, configurator) => configurator.ConfigureEndpoints(context)); diff --git a/Continuity.AuthServer/Services/PacketDistributorService.cs b/Continuity.AuthServer/Services/PacketDistributorService.cs deleted file mode 100644 index 78ce171..0000000 --- a/Continuity.AuthServer/Services/PacketDistributorService.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. - -using System.Collections.Concurrent; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Reflection; -using Continuity.AuthServer.LoggerMessages; -using Continuity.AuthServer.PacketHandlers; -using Continuity.AuthServer.Packets; -using DotNext.Collections.Generic; -using DotNext.Linq.Expressions; -using DotNext.Metaprogramming; -using MassTransit.Internals; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.VisualBasic.CompilerServices; -using Newtonsoft.Json; -using Wonderking.Packets; - -namespace Continuity.AuthServer.Services; - -using static CodeGenerator; -using static ExpressionBuilder; - -public class PacketDistributorService : IHostedService, IDisposable -{ - private readonly ConcurrentQueue _concurrentQueue; - - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - - private ImmutableDictionary> _deserializationMap; - - private ConcurrentDictionary _packetHandlersInstantiation; - private readonly ActivitySource _activitySource; - - public PacketDistributorService(ILogger logger, IServiceProvider serviceProvider) - { - _concurrentQueue = new ConcurrentQueue(); - _logger = logger; - _serviceProvider = serviceProvider; - _activitySource = new ActivitySource(nameof(Server)); - } - - public Task StartAsync(CancellationToken cancellationToken) - { - var tempDeserializationMap = - new Dictionary>(); - - var wonderkingAssembly = Assembly.GetAssembly(typeof(IPacket)); - var packetsTypes = GetPacketsWithId(wonderkingAssembly); - var packetHandlers = GetAllPacketHandlersWithId(Assembly.GetExecutingAssembly()); - _packetHandlersInstantiation = new ConcurrentDictionary(); - packetHandlers.ForEach(x => - { - var packetHandler = - ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, - x.Value); - _packetHandlersInstantiation.TryAdd(x.Key, packetHandler as IPacketHandler); - }); - foreach (var packetsType in packetsTypes) - { - var lambda = Lambda>(fun => - { - var argPacketData = fun[0]; - var newPacket = packetsType.Value.New(); - - var packetVariable = DeclareVariable(packetsType.Value, "packet"); - Assign(packetVariable, newPacket); - Call(packetVariable, nameof(IPacket.Deserialize), argPacketData); - - Return(packetVariable); - }).Compile(); - _logger.PacketCreationFunctionCreated(packetsType.Key); - tempDeserializationMap.Add(packetsType.Key, lambda); - } - - _deserializationMap = tempDeserializationMap.ToImmutableDictionary(); - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - private Dictionary GetPacketsWithId(Assembly executingAssembly) - { - // ! : We are filtering if types that don't have an instance of the required Attribute - var packetsWithId = executingAssembly.GetTypes().AsParallel() - .Where(type => type.HasInterface(typeof(IPacket)) && type is { IsInterface: false, IsAbstract: false }) - .Where(type => type.Namespace?.Contains("Incoming") ?? false) - .Select(type => new { Type = type, Attribute = type.GetCustomAttribute() }) - .Where(item => item.Attribute is not null) - .ToDictionary(item => item.Attribute!.Code, item => item.Type); - if (packetsWithId is not { Count: 0 }) - { - packetsWithId.AsParallel() - .ForAll(packet => _logger.PacketWithIdAdded(packet.Key, packet.Value.FullName)); - return packetsWithId; - } - - _logger.NoPacketsFound(); - throw new IncompleteInitialization(); - } - - private Dictionary GetAllPacketHandlersWithId(Assembly assembly) - { - // ! : We are filtering if types that don't have an instance of the required Attribute - var packetHandlersWithId = assembly.GetTypes().AsParallel() - .Where(t => - t is { IsClass: true, IsAbstract: false } && Array.Exists(t - .GetInterfaces(), i => - i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPacketHandler<>))) - .Select(type => new - { - Type = type, - PacketId = type - .GetInterfaces().First(t1 => - t1 is { IsGenericType: true } && t1.GetGenericTypeDefinition() == typeof(IPacketHandler<>)) - .GetGenericArguments()[0].GetCustomAttribute()?.Code - }) - .Where(x => x.PacketId is not null) - .ToDictionary( - x => x.PacketId!.Value, x => x.Type - ); - - if (packetHandlersWithId is not { Count: 0 }) - { - packetHandlersWithId.AsParallel().ForAll(packetHandler => - _logger.PacketHandlerWithIdAdded(packetHandler.Key, packetHandler.Value.FullName)); - return packetHandlersWithId; - } - - _logger.NoPacketHandlersFound(); - throw new IncompleteInitialization(); - } - - public void AddPacket(RawPacket rawPacket) - { - _concurrentQueue.Enqueue(rawPacket); - DequeueRawPacket(); - _logger.PacketReceived(rawPacket.OperationCode); - } - - private void DequeueRawPacket() - { - if (_concurrentQueue.TryDequeue(out var item)) - { - ThreadPool.QueueUserWorkItem(InvokePacketHandler, item, preferLocal: false); - } - } - - private void InvokePacketHandler(RawPacket item) - { - IPacket packet; - _logger.PacketDequeued(item.Session.Id, item.OperationCode); - if (!_deserializationMap.TryGetValue(item.OperationCode, out var value)) - { - _logger.PacketTypeNotFound(item.OperationCode); - return; - } - - using (var packetParsingActivity = _activitySource.StartActivity("PacketParsing")) - { - packetParsingActivity?.SetTag("PacketId", item.OperationCode); - packet = value(item.MessageBody); - } - - using (var packetHandlerActivity = _activitySource.StartActivity("PacketHandler")) - { - packetHandlerActivity?.SetTag("PacketId", item.OperationCode); - _ = _packetHandlersInstantiation[item.OperationCode].TryHandleAsync(packet, item.Session); - } - - _logger.PacketData(JsonConvert.SerializeObject(packet)); - _logger.PacketFinished(item.Session.Id, item.OperationCode); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - _activitySource.Dispose(); - } -} diff --git a/Continuity.sln b/Continuity.sln index ebb8be6..941ed9f 100644 --- a/Continuity.sln +++ b/Continuity.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Be EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wonderking", "Wonderking\Wonderking.csproj", "{6B53A10B-C397-4347-BB00-A12272D0528E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rai.PacketMediator", "Rai.PacketMediator\Rai.PacketMediator.csproj", "{D6FA787F-6B95-4679-BC6F-EED10B591E5C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {6B53A10B-C397-4347-BB00-A12272D0528E}.Debug|Any CPU.Build.0 = Debug|Any CPU {6B53A10B-C397-4347-BB00-A12272D0528E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6B53A10B-C397-4347-BB00-A12272D0528E}.Release|Any CPU.Build.0 = Release|Any CPU + {D6FA787F-6B95-4679-BC6F-EED10B591E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6FA787F-6B95-4679-BC6F-EED10B591E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6FA787F-6B95-4679-BC6F-EED10B591E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6FA787F-6B95-4679-BC6F-EED10B591E5C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Wonderking/Packets/IPacket.cs b/Rai.PacketMediator/IBidirectionalPacket.cs similarity index 56% rename from Wonderking/Packets/IPacket.cs rename to Rai.PacketMediator/IBidirectionalPacket.cs index b7c5399..42e4c4b 100644 --- a/Wonderking/Packets/IPacket.cs +++ b/Rai.PacketMediator/IBidirectionalPacket.cs @@ -2,11 +2,7 @@ using JetBrains.Annotations; -namespace Wonderking.Packets; +namespace Rai.PacketMediator; [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] -public interface IPacket -{ - public void Deserialize(byte[] data); - public byte[] Serialize(); -} +public interface IBidirectionalPacket : IOutgoingPacket, IIncomingPacket; diff --git a/Rai.PacketMediator/IIncomingPacket.cs b/Rai.PacketMediator/IIncomingPacket.cs new file mode 100644 index 0000000..40e3c19 --- /dev/null +++ b/Rai.PacketMediator/IIncomingPacket.cs @@ -0,0 +1,8 @@ +// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. + +namespace Rai.PacketMediator; + +public interface IIncomingPacket : IPacket +{ + public void Deserialize(byte[] data); +} diff --git a/Rai.PacketMediator/IOutgoingPacket.cs b/Rai.PacketMediator/IOutgoingPacket.cs new file mode 100644 index 0000000..eb1feb7 --- /dev/null +++ b/Rai.PacketMediator/IOutgoingPacket.cs @@ -0,0 +1,8 @@ +// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. + +namespace Rai.PacketMediator; + +public interface IOutgoingPacket : IPacket +{ + public byte[] Serialize(); +} diff --git a/Rai.PacketMediator/IPacket.cs b/Rai.PacketMediator/IPacket.cs new file mode 100644 index 0000000..e78399c --- /dev/null +++ b/Rai.PacketMediator/IPacket.cs @@ -0,0 +1,5 @@ +// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. + +namespace Rai.PacketMediator; + +public interface IPacket; diff --git a/Rai.PacketMediator/IPacketHandler.cs b/Rai.PacketMediator/IPacketHandler.cs new file mode 100644 index 0000000..12e8c5f --- /dev/null +++ b/Rai.PacketMediator/IPacketHandler.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. + +using System.Diagnostics; +using JetBrains.Annotations; + +namespace Rai.PacketMediator; + +[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] +public interface IPacketHandler : IPacketHandler where T : IIncomingPacket +{ + [UsedImplicitly] + public Task HandleAsync(T packet, S session); + + async Task IPacketHandler.TryHandleAsync(IIncomingPacket packet, S session) + { + if (packet is not T tPacket) + { + return false; + } + + using var activity = new ActivitySource(nameof(PacketMediator)).StartActivity(nameof(HandleAsync)); + activity?.AddTag("Handler", this.ToString()); + activity?.AddTag("Packet", packet.ToString()); + await HandleAsync(tPacket, session); + + return true; + } +} + +public interface IPacketHandler +{ + Task TryHandleAsync(IIncomingPacket packet, S session); +} diff --git a/Rai.PacketMediator/PacketDistributorService.cs b/Rai.PacketMediator/PacketDistributorService.cs new file mode 100644 index 0000000..5ff6005 --- /dev/null +++ b/Rai.PacketMediator/PacketDistributorService.cs @@ -0,0 +1,171 @@ +// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. + +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Reflection; +using DotNext.Collections.Generic; +using DotNext.Linq.Expressions; +using DotNext.Metaprogramming; +using MassTransit.Internals; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualBasic.CompilerServices; + +namespace Rai.PacketMediator; + +using static CodeGenerator; + +public class PacketDistributorService : Microsoft.Extensions.Hosting.IHostedService, IDisposable where T : Enum +{ + private readonly ConcurrentQueue> _concurrentQueue; + + private readonly IServiceProvider _serviceProvider; + private readonly Assembly[] _sourcesContainingPackets; + + private ImmutableDictionary> _deserializationMap; + + private ConcurrentDictionary?> _packetHandlersInstantiation; + private readonly ActivitySource _activitySource; + + public PacketDistributorService(IServiceProvider serviceProvider, + Assembly[] sourcesContainingPackets) + { + _concurrentQueue = new ConcurrentQueue>(); + _serviceProvider = serviceProvider; + _sourcesContainingPackets = sourcesContainingPackets; + _activitySource = new ActivitySource(nameof(PacketMediator)); + } + + private Dictionary RetrievePacketsDictionary() + { + using var activity = this._activitySource.StartActivity(); + var packetsWithId = this._sourcesContainingPackets.SelectMany(a => a.GetTypes() + .Where(type => + type.HasInterface(typeof(IIncomingPacket)) && type is { IsInterface: false, IsAbstract: false }) + .Select(type => new { Type = type, Attribute = type.GetCustomAttribute>() }) + .Where(item => item.Attribute is not null) + .ToDictionary(item => item.Attribute!.Code, item => item.Type)).ToDictionary(); + + activity?.AddTag("AmountOfPackets", packetsWithId.Count); + + return packetsWithId; + } + + private Dictionary GetAllPacketHandlersWithId() + { + using var activity = this._activitySource.StartActivity(); + var packetHandlersWithId = this._sourcesContainingPackets.SelectMany(assembly => assembly.GetTypes() + .Where(t => + t is { IsClass: true, IsAbstract: false } && Array.Exists(t + .GetInterfaces(), i => + i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPacketHandler))) + .Select(type => new + { + Type = type, + PacketId = type + .GetInterfaces().First(t1 => + t1 is { IsGenericType: true } && t1.GetGenericTypeDefinition() == typeof(IPacketHandler)) + .GetGenericArguments().First(t => t.HasAttribute>()) + .GetAttribute>().First().Code + }) + .ToDictionary( + x => x.PacketId, x => x.Type + )).ToDictionary(); + activity?.AddTag("AmountOfPacketHandlers", packetHandlersWithId.Count); + return packetHandlersWithId; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _activitySource.Dispose(); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + var packetDictionary = RetrievePacketsDictionary(); + + if (packetDictionary is { Count: 0 }) + { + throw new IncompleteInitialization(); + } + + var packetHandlers = GetAllPacketHandlersWithId(); + + if (packetHandlers is { Count: 0 }) + { + throw new IncompleteInitialization(); + } + + var tempDeserializationMap = + new Dictionary>(); + _packetHandlersInstantiation = new ConcurrentDictionary?>(); + packetHandlers.ForEach(x => + { + var packetHandler = + ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, + x.Value); + _packetHandlersInstantiation.TryAdd(x.Key, packetHandler as IPacketHandler); + }); + foreach (var packetsType in packetDictionary) + { + var lambda = Lambda>(fun => + { + var argPacketData = fun[0]; + var newPacket = packetsType.Value.New(); + + var packetVariable = DeclareVariable(packetsType.Value, "packet"); + Assign(packetVariable, newPacket); + Call(packetVariable, nameof(IIncomingPacket.Deserialize), argPacketData); + + Return(packetVariable); + }).Compile(); + tempDeserializationMap.Add(packetsType.Key, lambda); + } + + _deserializationMap = tempDeserializationMap.ToImmutableDictionary(); + return Task.CompletedTask; + } + + public void AddPacket(byte[] packetData, T operationCode, S session) + { + _concurrentQueue.Enqueue((packetData, operationCode, session)); + DequeueRawPacket(); + } + + private void DequeueRawPacket() + { + if (_concurrentQueue.TryDequeue(out var item)) + { + ThreadPool.QueueUserWorkItem(InvokePacketHandler, item, preferLocal: false); + } + } + + private void InvokePacketHandler((byte[], T, S) valueTuple) + { + IIncomingPacket packet; + var (packetData, operationCode, session) = valueTuple; + if (!_deserializationMap.TryGetValue(operationCode, out var func)) + { + return; + } + + using (var packetParsingActivity = _activitySource.StartActivity("PacketParsing")) + { + packetParsingActivity?.SetTag("PacketId", operationCode); + packet = func(packetData); + } + + using (var packetHandlerActivity = _activitySource.StartActivity("PacketHandler")) + { + packetHandlerActivity?.SetTag("PacketId", operationCode); + _ = _packetHandlersInstantiation[operationCode]?.TryHandleAsync(packet, session); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/Rai.PacketMediator/PacketIdAttribute.cs b/Rai.PacketMediator/PacketIdAttribute.cs new file mode 100644 index 0000000..ba5029e --- /dev/null +++ b/Rai.PacketMediator/PacketIdAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. + +namespace Rai.PacketMediator; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public abstract class PacketIdAttribute : Attribute where T : Enum +{ + protected PacketIdAttribute(T code) + { + Code = code; + } + + public T Code { get; } +} diff --git a/Rai.PacketMediator/Rai.PacketMediator.csproj b/Rai.PacketMediator/Rai.PacketMediator.csproj new file mode 100644 index 0000000..b6b02dd --- /dev/null +++ b/Rai.PacketMediator/Rai.PacketMediator.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Wonderking/Packets/Incoming/ChannelSelectionPacket.cs b/Wonderking/Packets/Incoming/ChannelSelectionPacket.cs index 8d23aea..c7ec7af 100644 --- a/Wonderking/Packets/Incoming/ChannelSelectionPacket.cs +++ b/Wonderking/Packets/Incoming/ChannelSelectionPacket.cs @@ -1,9 +1,11 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. +using Rai.PacketMediator; + namespace Wonderking.Packets.Incoming; -[PacketId(OperationCode.ChannelSelection)] -public class ChannelSelectionPacket : IPacket +[WonderkingPacketId(OperationCode.ChannelSelection)] +public class ChannelSelectionPacket : IIncomingPacket { public required ushort ServerId { get; set; } public required ushort ChannelId { get; set; } diff --git a/Wonderking/Packets/Incoming/CharacterCreationPacket.cs b/Wonderking/Packets/Incoming/CharacterCreationPacket.cs index 3560539..b0f6259 100644 --- a/Wonderking/Packets/Incoming/CharacterCreationPacket.cs +++ b/Wonderking/Packets/Incoming/CharacterCreationPacket.cs @@ -1,12 +1,13 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using System.Text; +using Rai.PacketMediator; using Wonderking.Game.Data.Character; namespace Wonderking.Packets.Incoming; -[PacketId(OperationCode.CharacterCreation)] -public class CharacterCreationPacket : IPacket +[WonderkingPacketId(OperationCode.CharacterCreation)] +public class CharacterCreationPacket : IIncomingPacket { public required byte Slot { get; set; } public required byte Unknown { get; set; } diff --git a/Wonderking/Packets/Incoming/CharacterDeletePacket.cs b/Wonderking/Packets/Incoming/CharacterDeletePacket.cs index aa7f8b5..da7780b 100644 --- a/Wonderking/Packets/Incoming/CharacterDeletePacket.cs +++ b/Wonderking/Packets/Incoming/CharacterDeletePacket.cs @@ -1,11 +1,12 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using System.Text; +using Rai.PacketMediator; namespace Wonderking.Packets.Incoming; -[PacketId(OperationCode.CharacterDeletion)] -public class CharacterDeletePacket : IPacket +[WonderkingPacketId(OperationCode.CharacterDeletion)] +public class CharacterDeletePacket : IIncomingPacket { public byte Slot { get; set; } public string Name { get; set; } diff --git a/Wonderking/Packets/Incoming/CharacterNameCheckPacket.cs b/Wonderking/Packets/Incoming/CharacterNameCheckPacket.cs index 85c5a6a..4b4521a 100644 --- a/Wonderking/Packets/Incoming/CharacterNameCheckPacket.cs +++ b/Wonderking/Packets/Incoming/CharacterNameCheckPacket.cs @@ -1,11 +1,12 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using System.Text; +using Rai.PacketMediator; namespace Wonderking.Packets.Incoming; -[PacketId(OperationCode.CharacterNameCheck)] -public class CharacterNameCheckPacket : IPacket +[WonderkingPacketId(OperationCode.CharacterNameCheck)] +public class CharacterNameCheckPacket : IIncomingPacket { public required string Name { get; set; } diff --git a/Wonderking/Packets/Incoming/LoginInfoPacket.cs b/Wonderking/Packets/Incoming/LoginInfoPacket.cs index 6f707da..faf6cd3 100644 --- a/Wonderking/Packets/Incoming/LoginInfoPacket.cs +++ b/Wonderking/Packets/Incoming/LoginInfoPacket.cs @@ -1,11 +1,12 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using System.Text; +using Rai.PacketMediator; namespace Wonderking.Packets.Incoming; -[PacketId(OperationCode.LoginInfo)] -public class LoginInfoPacket : IPacket +[WonderkingPacketId(OperationCode.LoginInfo)] +public class LoginInfoPacket : IIncomingPacket { public required string Username { get; set; } diff --git a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs index f5b3340..44f809f 100644 --- a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs @@ -2,12 +2,13 @@ using System.Buffers.Binary; using System.Text; +using Rai.PacketMediator; using Wonderking.Packets.Outgoing.Data; namespace Wonderking.Packets.Outgoing; -[PacketId(OperationCode.ChannelSelectionResponse)] -public class ChannelSelectionResponsePacket : IPacket +[WonderkingPacketId(OperationCode.ChannelSelectionResponse)] +public class ChannelSelectionResponsePacket : IOutgoingPacket { public required byte ChannelIsFullFlag { get; set; } public required string Endpoint { get; set; } diff --git a/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs b/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs index 055824f..e6a1eb5 100644 --- a/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs @@ -2,12 +2,13 @@ using System.Buffers.Binary; using System.Text; +using Rai.PacketMediator; using Wonderking.Packets.Outgoing.Data; namespace Wonderking.Packets.Outgoing; -[PacketId(OperationCode.CharacterCreationResponse)] -public class CharacterCreationResponsePacket : IPacket +[WonderkingPacketId(OperationCode.CharacterCreationResponse)] +public class CharacterCreationResponsePacket : IOutgoingPacket { public required CharacterData Character { get; set; } public required int Slot { get; set; } diff --git a/Wonderking/Packets/Outgoing/CharacterDeleteResponsePacket.cs b/Wonderking/Packets/Outgoing/CharacterDeleteResponsePacket.cs index cb74f71..01b0911 100644 --- a/Wonderking/Packets/Outgoing/CharacterDeleteResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/CharacterDeleteResponsePacket.cs @@ -1,11 +1,12 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. +using Rai.PacketMediator; using NotSupportedException = System.NotSupportedException; namespace Wonderking.Packets.Outgoing; -[PacketId(OperationCode.CharacterDeletionResponse)] -public class CharacterDeleteResponsePacket : IPacket +[WonderkingPacketId(OperationCode.CharacterDeletionResponse)] +public class CharacterDeleteResponsePacket : IOutgoingPacket { public required byte HasToBeZero { get; set; } diff --git a/Wonderking/Packets/Outgoing/CharacterNameCheckPacketResponse.cs b/Wonderking/Packets/Outgoing/CharacterNameCheckPacketResponse.cs index 650d86c..97ff542 100644 --- a/Wonderking/Packets/Outgoing/CharacterNameCheckPacketResponse.cs +++ b/Wonderking/Packets/Outgoing/CharacterNameCheckPacketResponse.cs @@ -1,9 +1,11 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. +using Rai.PacketMediator; + namespace Wonderking.Packets.Outgoing; -[PacketId(OperationCode.CharacterNameCheckResponse)] -public class CharacterNameCheckPacketResponse : IPacket +[WonderkingPacketId(OperationCode.CharacterNameCheckResponse)] +public class CharacterNameCheckPacketResponse : IOutgoingPacket { public required bool IsTaken { get; set; } diff --git a/Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs b/Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs index 2f8397c..be90526 100644 --- a/Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs +++ b/Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs @@ -1,11 +1,12 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using System.Text; +using Rai.PacketMediator; namespace Wonderking.Packets.Outgoing; -[PacketId(OperationCode.CharacterSelectionSetGuildName)] -public class CharacterSelectionSetGuildNamePacket : IPacket +[WonderkingPacketId(OperationCode.CharacterSelectionSetGuildName)] +public class CharacterSelectionSetGuildNamePacket : IOutgoingPacket { public required string[] GuildNames { get; set; } diff --git a/Wonderking/Packets/Outgoing/LoginResponsePacket.cs b/Wonderking/Packets/Outgoing/LoginResponsePacket.cs index d3e81f4..1e9e668 100644 --- a/Wonderking/Packets/Outgoing/LoginResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/LoginResponsePacket.cs @@ -1,12 +1,13 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using System.Buffers.Binary; +using Rai.PacketMediator; using Wonderking.Packets.Outgoing.Data; namespace Wonderking.Packets.Outgoing; -[PacketId(OperationCode.LoginResponse)] -public class LoginResponsePacket : IPacket +[WonderkingPacketId(OperationCode.LoginResponse)] +public class LoginResponsePacket : IOutgoingPacket { public required LoginResponseReason ResponseReason { get; set; } public required byte UnknownFlag { get; set; } = 1; diff --git a/Wonderking/Packets/PacketIdAttribute.cs b/Wonderking/Packets/WonderkingPacketIdAttribute.cs similarity index 52% rename from Wonderking/Packets/PacketIdAttribute.cs rename to Wonderking/Packets/WonderkingPacketIdAttribute.cs index 7c38845..dfb4709 100644 --- a/Wonderking/Packets/PacketIdAttribute.cs +++ b/Wonderking/Packets/WonderkingPacketIdAttribute.cs @@ -1,14 +1,13 @@ // Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. +using Rai.PacketMediator; + namespace Wonderking.Packets; [AttributeUsage(AttributeTargets.Class, Inherited = false)] -public class PacketIdAttribute : Attribute +public class WonderkingPacketIdAttribute : PacketIdAttribute { - public PacketIdAttribute(OperationCode code) + public WonderkingPacketIdAttribute(OperationCode code) : base(code) { - Code = code; } - - public OperationCode Code { get; } } diff --git a/Wonderking/Wonderking.csproj b/Wonderking/Wonderking.csproj index 3f8ddbb..3f9e136 100644 --- a/Wonderking/Wonderking.csproj +++ b/Wonderking/Wonderking.csproj @@ -41,4 +41,7 @@ + + +