Packet Handler Generator
On this page
PacketHandlerGenerator (analyzers/Nalix.Analyzers.Generators/PacketHandlerGenerator.cs) is an incremental Roslyn source generator that compiles classes annotated with [PacketHandler] into zero-reflection IPacketHandlerCompiler implementations registered via [ModuleInitializer].
Source Mapping
analyzers/Nalix.Analyzers.Generators/PacketHandlerGenerator.csanalyzers/Nalix.Analyzers.Generators/Internal/GeneratorDiagnosticDescriptors.cs
What It Does at Compile Time
- Controller Discovery: Finds all concrete classes decorated with
[PacketHandler]and inspects their[PacketOpcode]methods. - Constructor &
[Inject]Wiring:- Selects the accessible (
publicorinternal) constructor with the most parameters to instantiate the controller, resolvingConfigurationLoaderparameters viaConfigurationManager.Instance.Get<TParam>()and service parameters viaInstanceManager.Instance.GetExistingInstance<TParam>()(falling back to the parameter’s default value or throwingInvalidOperationExceptionif required). - If the controller declares fields or properties marked with
[Inject](which requires the controller class to be declaredpartial), emits<Controller>_Inject.g.cscontainingprivate void InitializeDependencies()and invokes it inside the generated factory.
- Selects the accessible (
- Metadata Compilation: Reads
[PacketOpcode],[PacketTimeout],[PacketPermission],[PacketEncryption],[PacketRateLimit], and[PacketConcurrencyLimit]on each handler method (with controller-level fallback) and constructs aPacketMetadatadescriptor viaPacketMetadataBuilder. - Delegate Compilation: Emits a strongly-typed invoker lambda that casts
IPacketContext<TPacket>toIPacketContext<TConcretePacket>, resolves any[FromScope]parameters viactx.Scope.GetRequiredService<TParam>()(orctx.Scope.GetService<TParam>()when the parameter type is nullableTParam?), and invokes the handler without reflection or boxing. - Module Initializer Registration: Emits
PacketHandlersGenerated.Initialize()([ModuleInitializer]) callingPacketHandlerRegistry.Register(typeof(TController), new TController_PacketHandlerCompiler()).
Supported Handler Signatures
Every [PacketOpcode] handler method must follow this parameter contract:
| Parameter Position | Required Type / Attribute | How It Is Supplied |
|---|---|---|
Parameter 0 (Required) | IPacketContext<TPacket> (where TPacket : IPacket) | Passed directly from the pooled runtime dispatch context (context.Packet, context.Connection, context.Sender, context.Scope, context.CancellationToken). |
Parameters 1..N (Optional) | Reference type TService (or TService?) annotated with [FromScope] | Non-nullable TService is resolved via context.Scope.GetRequiredService<TService>(); nullable TService? is resolved via context.Scope.GetService<TService>(). |
Supported Return Types
voidTaskValueTaskTResult(for example, returning a responseIPacket,string, orbyte[])Task<TResult>ValueTask<TResult>IAsyncEnumerable<TElement>(server-to-client streaming responses)
Example
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Nalix.Abstractions.Injection;
using Nalix.Abstractions.Networking.Packets;
using Nalix.Abstractions.Security;
[PacketHandler("AuthController")]
public sealed partial class AuthController
{
[Inject]
private ILogger? _logger;
[PacketOpcode(0x1001)]
[PacketPermission(PermissionLevel.NONE)]
[PacketEncryption(true)]
public ValueTask HandleLoginAsync(
IPacketContext<LoginRequest> context,
[FromScope] IUserSessionTracker tracker)
{
_logger?.LogInformation("Login from {Id}", context.Connection.ConnectionId);
tracker.Track(context.Connection.ConnectionId);
return context.Sender.ReplyAsync(new LoginResponse { Success = true });
}
}
Diagnostics Emitted
| ID | Title | Severity | Cause |
|---|---|---|---|
NALIX003 | Packet controller handler has unsupported signature | Warning | Parameter 0 is missing or is not IPacketContext<TPacket>. |
NALIX067 | Handler method dropped from dispatch table | Error | A parameter at index >= 1 is missing [FromScope] or is not a reference type. |
NALIX104 | [Inject] field cannot be readonly | Error | A field marked [Inject] is declared readonly. |