defmodule ElvenGard.Network.Socket do @moduledoc ~S""" Manage a socket. This module provides functionality for managing a socket in the network protocol. A socket is a connection between the server and a client. It maintains various socket fields, such as the socket ID, socket assigns, transport information, and the packet network encoder used for sending data. ## Socket fields - `:id`: The unique string ID of the socket. - `:assigns`: A map of socket assigns, which can be used to store custom data associated with the socket. The default value is `%{}`. - `:transport`: The [Ranch transport](https://ninenines.eu/docs/en/ranch/2.0/guide/transports/) used for the socket. - `:transport_pid`: The PID (Process ID) of the socket's transport process. - `:remaining`: The remaining bytes after receiving and packet deserialization. - `:encoder`: The `ElvenGard.Network.NetworkCodec` module used to encode packets in the `send/2` function. """ alias __MODULE__ alias ElvenGard.Network.UUID defstruct id: nil, transport: nil, transport_pid: nil, remaining: <<>>, assigns: %{}, encoder: :unset @type t :: %Socket{ id: String.t(), transport: atom, transport_pid: pid, remaining: bitstring, assigns: map, encoder: module | :unset } @doc """ Create a new socket structure. This function initializes a new socket with the given `transport_pid`, `transport`, and `encoder` module. """ @spec new(pid, atom, module) :: Socket.t() def new(transport_pid, transport, encoder) do %Socket{ id: UUID.uuid4(), transport_pid: transport_pid, transport: transport, encoder: encoder } end @doc """ Send a packet to the client. This function sends a packet to the client through the socket's transport. If the socket's `encoder` is set to `:unset`, the data is sent as is. Otherwise, the `encoder` module is used to serialize the data before sending it. ## Examples iex> ElvenGard.Network.Socket.send(socket, %LoginResponse{status: 200, message: "Welcome!"}) :ok """ @spec send(Socket.t(), struct() | iodata()) :: :ok | {:error, atom} def send(%Socket{encoder: :unset} = socket, data) do %Socket{transport: transport, transport_pid: transport_pid} = socket transport.send(transport_pid, data) end def send(%Socket{} = socket, message) do %Socket{transport: transport, transport_pid: transport_pid, encoder: encoder} = socket data = encoder.encode(message, socket) transport.send(transport_pid, data) end @doc """ Adds key value pairs to socket assigns. A single key value pair may be passed, a keyword list or map of assigns may be provided to be merged into existing socket assigns. ## Examples iex> assign(socket, :name, "ElvenGard") iex> socket.assigns.name == "ElvenGard" true iex> assign(socket, name: "ElvenGard", logo: "🌸") iex> socket.assigns.name == "ElvenGard" true iex> socket.assigns.logo == "🌸" true """ @spec assign(Socket.t(), atom, any) :: Socket.t() def assign(%Socket{} = socket, key, value) do assign(socket, [{key, value}]) end @spec assign(Socket.t(), map | keyword) :: Socket.t() def assign(%Socket{} = socket, attrs) when is_map(attrs) or is_list(attrs) do %{socket | assigns: Map.merge(socket.assigns, Map.new(attrs))} end end