From 8ae0c08e8deac5fffbe647e54d574d5950993d48 Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Tue, 3 Sep 2024 19:29:19 +0200 Subject: [PATCH] Complete refactoring --- apps/amethyst/lib/amethyst.ex | 3 +- apps/amethyst/lib/api/game.ex | 57 ++++------------------ apps/amethyst/lib/apps/game_coordinator.ex | 14 +++--- apps/amethyst/lib/servers/play.ex | 9 +--- apps/example_game/lib/example/game.ex | 6 +++ 5 files changed, 23 insertions(+), 66 deletions(-) diff --git a/apps/amethyst/lib/amethyst.ex b/apps/amethyst/lib/amethyst.ex index e753709..9daeeea 100644 --- a/apps/amethyst/lib/amethyst.ex +++ b/apps/amethyst/lib/amethyst.ex @@ -26,8 +26,7 @@ defmodule Amethyst.Application do children = [ {Task.Supervisor, name: Amethyst.ConnectionSupervisor}, {Amethyst.Keys, 1024}, - {Amethyst.GameRegistry, []}, - {Amethyst.GameCoordinator, []}, + {Amethyst.GameCoordinator, %Amethyst.GameCoordinator.State{games: %{}, gid: 0}}, {PartitionSupervisor, child_spec: DynamicSupervisor.child_spec([]), name: Amethyst.GameMetaSupervisor diff --git a/apps/amethyst/lib/api/game.ex b/apps/amethyst/lib/api/game.ex index 55baefb..6f651f3 100644 --- a/apps/amethyst/lib/api/game.ex +++ b/apps/amethyst/lib/api/game.ex @@ -41,6 +41,9 @@ defmodule Amethyst.API.Game do - 'state_refs' are your references (see `instantiate/1`) """ @callback login(from :: pid(), player_cfg :: keyword(), state_refs :: map()) :: :accept | :reject + def login(%{:mod => mod, :refs => refs}, player_cfg) do + mod.login(self(), player_cfg, refs) + end @doc """ `player_position/3` is called when a player moves. This function is called with the absolute coordinates that the player client expects. TODO: Teleport Player API. @@ -50,57 +53,15 @@ defmodule Amethyst.API.Game do - `state_refs` are your references (see `instantiate/1`) """ @callback player_position(from :: pid(), {x :: float(), y :: float(), z :: float()}, state_refs :: map()) :: :ok + def player_position(%{:mod => mod, :refs => refs}, {x, y, z}) do + mod.player_position(self(), {x, y, z}, refs) + end @doc """ Whether or not this game instance can be joined by a new player. This should include basic logic such as - if joining makes sense, for instance + if joining makes sense, for instance if the game is full or if the game has already started. """ @callback joinable?(state_refs :: map()) :: boolean() - - def child_spec(mod, refs) do - %{ - id: __MODULE__, - start: {__MODULE__, :start_link, [mod, refs]} - } - end - - @spec start(Amethyst.API.Game, term()) :: {:ok, pid} - def start(mod, refs) do - pid = spawn(fn -> - true = refs |> Enum.all?(fn {_key, pid } -> Process.link(pid) end) - loop(mod, refs) - end) - {:ok, pid} - end - def start_link(mod, refs) do - {:ok, pid} = start(mod, refs) - Process.link(pid) - {:ok, pid} - end - defp loop(mod, refs) do - receive do - {:login, caller, cfg} -> - Task.Supervisor.start_child(refs[:task_supervisor], - fn -> mod.login(caller, cfg, refs) end) - {:player_position, caller, pos} -> - Task.Supervisor.start_child(refs[:task_supervisor], - fn -> mod.player_position(caller, pos, refs) end) - end - loop(mod, refs) - end - - def login(pid, cfg) do - send(pid, {:login, self(), cfg}) - receive do - :ok -> :ok - :refuse -> :refuse - end - end - - @spec player_position(atom() | pid() | port() | reference() | {atom(), atom()}, {float(), float(), float()}) :: {float(), float(), float()} - def player_position(pid, pos) do - send(pid, {:player_position, self(), pos}) - receive do - pos -> pos - end + def joinable?(%{:mod => mod, :refs => refs}) do + mod.joinable?(refs) end end diff --git a/apps/amethyst/lib/apps/game_coordinator.ex b/apps/amethyst/lib/apps/game_coordinator.ex index 23a876f..cfa3698 100644 --- a/apps/amethyst/lib/apps/game_coordinator.ex +++ b/apps/amethyst/lib/apps/game_coordinator.ex @@ -38,7 +38,7 @@ defmodule Amethyst.GameCoordinator do which defines callbacks for various events, a map containing pids to the game's state (called references or refs) and optional metadata. """ - defstruct mod: :none, refs: %{}, opts: [] + defstruct mod: :none, refs: %{}, opts: [], gid: 0 end @spec start_link(State) :: {:ok, pid()} @@ -87,20 +87,18 @@ defmodule Amethyst.GameCoordinator do # TODO: Instantiation can fail (including with an exception), and if it does the entire GameCoordinator goes down # We should gracefully handle situations where we cannot create a game. {:ok, refs} = type.instantiate(game_supervisor_pid) - refs = refs |> Map.put(:game_supervisor, game_supervisor_pid) |> Map.put(:task_supervisor, task_supervisor_pid) + refs = refs |> Map.put(:game_supervisor, game_supervisor_pid) |> Map.put(:task_supervisor, task_supervisor_pid) |> Map.put(:game_coordinator, self()) game = %Game{ - mod: type, refs: refs, opts: [] + mod: type, refs: refs, opts: [], gid: state.gid } games = state.games |> Map.put(state.gid, game) {game, %State{gid: state.gid + 1, games: games}} end defp _find(type, state) do - if length(existing) > 0 do - [{_mod, pid, _} | _] = existing - {pid, games} - else - {nil, games} + case state.games |> Enum.find(fn {_, game} -> game.mod == type end) do + nil -> {nil, state} + {_, game} -> {game, state} end end diff --git a/apps/amethyst/lib/servers/play.ex b/apps/amethyst/lib/servers/play.ex index eb1baf0..9f3b409 100644 --- a/apps/amethyst/lib/servers/play.ex +++ b/apps/amethyst/lib/servers/play.ex @@ -107,14 +107,7 @@ defmodule Amethyst.Server.Play do {:ok, state} else game = Keyword.fetch!(state, :game) - {nx, ny, nz} = Amethyst.API.Game.player_position(game, {x, y, z}) - # If pos is not very close to the position the client wants, synchronize them - # TODO: Make a general-purpose distance function - dist = :math.sqrt((nx - x)**2 + (ny - y)**2 + (nz - z)**2) - if dist > 0.01 do - # TODO: Implement synchronization - raise "The game wants to resynchronize the player, but I haven't implemented that yet" - end + {Amethyst.API.Game.player_position(game, {x, y, z}), state} end end def handle(tuple, _, state) do # TODO: These error cases should be somehow moved into some shared area? Maybe even moved out of the modules themselves diff --git a/apps/example_game/lib/example/game.ex b/apps/example_game/lib/example/game.ex index 2146abb..9e7af55 100644 --- a/apps/example_game/lib/example/game.ex +++ b/apps/example_game/lib/example/game.ex @@ -14,4 +14,10 @@ defmodule Example.Game do Logger.info("The refs for this game are #{inspect(refs)}") :ok end + + @impl true + def player_position(from, {x, y, z}, refs) do + Logger.info("Player at #{inspect(from)} moved to #{x}, #{y}, #{z}") + :ok + end end