defmodule Odinsea.Channel.Server do @moduledoc """ A single game channel server. """ use GenServer require Logger defstruct [:channel_id, :socket, :port, :clients] def start_link(channel_id) do GenServer.start_link(__MODULE__, channel_id, name: via_tuple(channel_id)) end def via_tuple(channel_id) do {:via, Registry, {Odinsea.Channel.Registry, channel_id}} end @impl true def init(channel_id) do ports = Application.get_env(:odinsea, :game, [])[:channel_ports] || %{} port = Map.get(ports, channel_id, 8584 + channel_id) case :gen_tcp.listen(port, tcp_options()) do {:ok, socket} -> Logger.info("Channel #{channel_id} listening on port #{port}") send(self(), :accept) {:ok, %__MODULE__{ channel_id: channel_id, socket: socket, port: port, clients: %{} }} {:error, reason} -> Logger.error("Failed to start channel #{channel_id} on port #{port}: #{inspect(reason)}") {:stop, reason} end end @impl true def handle_info(:accept, %{socket: socket, channel_id: channel_id} = state) do case :gen_tcp.accept(socket) do {:ok, client_socket} -> {:ok, _pid} = DynamicSupervisor.start_child( Odinsea.ClientSupervisor, {Odinsea.Channel.Client, {client_socket, channel_id}} ) send(self(), :accept) {:noreply, state} {:error, reason} -> Logger.warning("Channel #{channel_id} accept error: #{inspect(reason)}") send(self(), :accept) {:noreply, state} end end defp tcp_options do [ :binary, packet: :raw, active: false, reuseaddr: true, backlog: 100 ] end end