How Packets Work

Read time 2 minutes
On this page

A packet is a plain C# class with attributes that tell Nalix how to put it on the wire and read it back off. You write the class once, in a project shared by server and client, and both sides use the same definition.

The attributes

// samples/HelloWorld/HelloWorld.Contracts/HelloRequestPacket.cs
[GenerateFormatter]
[SerializePackable(SerializeLayout.Explicit)]
public sealed partial class HelloRequestPacket
    : PacketBase<HelloRequestPacket>,
      IFixedSizeSerializable,
      IPacketStaticOpcode
{
    public static ushort StaticOpCode => 0x7001;

    [SerializeOrder(0)]
    public byte Greeting { get; set; }

    public HelloRequestPacket() => this.Greeting = 1;
}
  • [GenerateFormatter] generates the binary reader/writer for this class at compile time and registers the packet in PacketRegistry via a [ModuleInitializer] — you never hand-write serialization code. (Note: [Packet] is obsolete (NAL001) and no longer required.)
  • StaticOpCode (IPacketStaticOpcode) is the 16-bit number that identifies this packet type on the wire. Pick any value that doesn’t collide with another packet in your project.
  • [SerializeOrder(n)] fixes each field’s position in the byte stream, starting at 0. Once a packet ships, don’t reorder or remove existing SerializeOrder values — add new fields with higher numbers instead.

Full source: samples/HelloWorld/HelloWorld.Contracts/HelloRequestPacket.cs

Variable-length fields

Fixed-size fields like byte and int are enough for simple packets, but chat messages, usernames, and other free-form data need variable-length strings:

// samples/ChatRoom/ChatRoom.Contracts/ChatMessagePacket.cs
[GenerateFormatter]
[SerializePackable(SerializeLayout.Explicit)]
public sealed partial class ChatMessagePacket : PacketBase<ChatMessagePacket>, IPacketStaticOpcode
{
    public static ushort StaticOpCode => 0x7101;

    [SerializeOrder(0)]
    public string Username { get; set; } = string.Empty;

    [SerializeOrder(1)]
    public string Message { get; set; } = string.Empty;

    public ChatMessagePacket() { }
}

Full source: samples/ChatRoom/ChatRoom.Contracts/ChatMessagePacket.cs

Registration happens automatically

You don’t call anything to register a packet type. Loading the assembly that contains it (via a module initializer generated alongside [GenerateFormatter]) registers it for you. If a handler can’t be found at runtime, the most common cause is that the packet’s assembly was never loaded or referenced — see Common problems.

Next steps