Complete refactoring
Some checks failed
Build & Test / nix-build (push) Failing after 28s

This commit is contained in:
Kodi Craft 2024-09-03 19:29:19 +02:00
parent 4a5ccc719d
commit 8ae0c08e8d
Signed by: kodi
GPG Key ID: 69D9EED60B242822
5 changed files with 23 additions and 66 deletions

View File

@ -26,8 +26,7 @@ defmodule Amethyst.Application do
children = [ children = [
{Task.Supervisor, name: Amethyst.ConnectionSupervisor}, {Task.Supervisor, name: Amethyst.ConnectionSupervisor},
{Amethyst.Keys, 1024}, {Amethyst.Keys, 1024},
{Amethyst.GameRegistry, []}, {Amethyst.GameCoordinator, %Amethyst.GameCoordinator.State{games: %{}, gid: 0}},
{Amethyst.GameCoordinator, []},
{PartitionSupervisor, {PartitionSupervisor,
child_spec: DynamicSupervisor.child_spec([]), child_spec: DynamicSupervisor.child_spec([]),
name: Amethyst.GameMetaSupervisor name: Amethyst.GameMetaSupervisor

View File

@ -41,6 +41,9 @@ defmodule Amethyst.API.Game do
- 'state_refs' are your references (see `instantiate/1`) - 'state_refs' are your references (see `instantiate/1`)
""" """
@callback login(from :: pid(), player_cfg :: keyword(), state_refs :: map()) :: :accept | :reject @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 """ @doc """
`player_position/3` is called when a player moves. This function is called with the absolute coordinates `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. 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`) - `state_refs` are your references (see `instantiate/1`)
""" """
@callback player_position(from :: pid(), {x :: float(), y :: float(), z :: float()}, state_refs :: map()) :: :ok @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 """ @doc """
Whether or not this game instance can be joined by a new player. This should include basic logic such as 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() @callback joinable?(state_refs :: map()) :: boolean()
def joinable?(%{:mod => mod, :refs => refs}) do
def child_spec(mod, refs) do mod.joinable?(refs)
%{
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
end end
end end

View File

@ -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 which defines callbacks for various events, a map containing pids to the game's state (called references
or refs) and optional metadata. or refs) and optional metadata.
""" """
defstruct mod: :none, refs: %{}, opts: [] defstruct mod: :none, refs: %{}, opts: [], gid: 0
end end
@spec start_link(State) :: {:ok, pid()} @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 # 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. # We should gracefully handle situations where we cannot create a game.
{:ok, refs} = type.instantiate(game_supervisor_pid) {: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{ game = %Game{
mod: type, refs: refs, opts: [] mod: type, refs: refs, opts: [], gid: state.gid
} }
games = state.games |> Map.put(state.gid, game) games = state.games |> Map.put(state.gid, game)
{game, %State{gid: state.gid + 1, games: games}} {game, %State{gid: state.gid + 1, games: games}}
end end
defp _find(type, state) do defp _find(type, state) do
if length(existing) > 0 do case state.games |> Enum.find(fn {_, game} -> game.mod == type end) do
[{_mod, pid, _} | _] = existing nil -> {nil, state}
{pid, games} {_, game} -> {game, state}
else
{nil, games}
end end
end end

View File

@ -107,14 +107,7 @@ defmodule Amethyst.Server.Play do
{:ok, state} {:ok, state}
else else
game = Keyword.fetch!(state, :game) game = Keyword.fetch!(state, :game)
{nx, ny, nz} = Amethyst.API.Game.player_position(game, {x, y, z}) {Amethyst.API.Game.player_position(game, {x, y, z}), state}
# 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
end end
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 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

View File

@ -14,4 +14,10 @@ defmodule Example.Game do
Logger.info("The refs for this game are #{inspect(refs)}") Logger.info("The refs for this game are #{inspect(refs)}")
:ok :ok
end 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 end