Hey. I'm writing a server for a popular game (bonus points if you can tell which from the code snippet) and I've come to find the way I'm structuring my packets.. unweildy. Let's take a look at a server-bound packet as an example:
defmodule Erebus.Packet.ServerBound.PingRequest do
defstruct payload: nil
def parse(data) do
<<payload::size(64), rest::binary>> = data
packet = %__MODULE__{
payload: payload
}
{packet, rest}
end
end
```and a client-bound packet:```exs
defmodule Erebus.Packet.ClientBound.PingResponse do
defstruct payload: nil
def encode(packet) do
body = <<packet.payload::integer-size(64)>>
{1, body}
end
end
In order to parse a packet coming in from the server, we first:
- Decrypt the incoming packet stream (irrelevant to this question)
- Determine the size of the packet
- Determine the ID of the packet
- Send the remaining data to a big parse function, which takes the packet id, connection state, and data:
def parse_packet(data, state, bound_to \\ :server) do
{:ok, {packet_id, packet_data}, rest} = Erebus.Protocol.parse(data)
{pack, unparsed_data} = case {packet_id, state, bound_to} do
{0, :handshaking, :server} ->
Erebus.Packet.ServerBound.Handshake.parse(packet_data)
{0, :status, :server} ->
Erebus.Packet.ServerBound.StatusRequest.parse(packet_data)
# ...
end
if unparsed_data != "" do
Logger.warning("Unparsed trailing data on packet: #{unparsed_data}")
end
{pack, rest}
end