This commit is contained in:
parent
4a5ccc719d
commit
8ae0c08e8d
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user