diff --git a/apps/amethyst/lib/amethyst.ex b/apps/amethyst/lib/amethyst.ex index cb4c1b1..e753709 100644 --- a/apps/amethyst/lib/amethyst.ex +++ b/apps/amethyst/lib/amethyst.ex @@ -27,7 +27,11 @@ defmodule Amethyst.Application do {Task.Supervisor, name: Amethyst.ConnectionSupervisor}, {Amethyst.Keys, 1024}, {Amethyst.GameRegistry, []}, - {Amethyst.GameCoordinator, []} + {Amethyst.GameCoordinator, []}, + {PartitionSupervisor, + child_spec: DynamicSupervisor.child_spec([]), + name: Amethyst.GameMetaSupervisor + } ] children = case Application.fetch_env!(:amethyst, :port) do diff --git a/apps/amethyst/lib/api/game.ex b/apps/amethyst/lib/api/game.ex index 6b86c5e..f059f13 100644 --- a/apps/amethyst/lib/api/game.ex +++ b/apps/amethyst/lib/api/game.ex @@ -19,9 +19,9 @@ defmodule Amethyst.API.Game do This module includes the interface for defining and registering a game with Amethyst. """ - @callback instantiate() :: {:ok, new_state :: term} | {:error, reason :: term} - @callback login(from :: pid(), player_cfg :: keyword(), state :: term) :: {:ok, new_state :: term} | {:refuse, new_state :: term} - @callback player_position(from :: pid(), {x :: float(), y :: float(), z :: float()}, state :: term) :: {{x :: float(), y :: float(), z :: float()}, state :: term} + @callback instantiate(supervisor :: pid()) :: {:ok, state_refs :: map()} | {:error, reason :: term} + @callback login(from :: pid(), player_cfg :: keyword(), state_refs :: map()) :: :ok + @callback player_position(from :: pid(), {x :: float(), y :: float(), z :: float()}, state_refs :: map()) :: :ok defmacro __using__(opts) do meta = Keyword.get(opts, :meta, []) @@ -41,27 +41,20 @@ defmodule Amethyst.API.Game do end @spec start(Amethyst.API.Game, term()) :: no_return() - def start(mod, state) do - loop(mod, state) + def start(mod, refs) do + true = refs |> Enum.all?(fn {_key, pid } -> Process.link(pid) end) + loop(mod, refs) end - defp loop(mod, state) do + defp loop(mod, refs) do receive do {:login, caller, cfg} -> - case mod.login(caller, cfg, state) do - {:ok, state} -> - send(caller, :ok) - loop(mod, state) - {:refuse, state} -> - send(caller, :refuse) - loop(mod, state) - end + Task.Supervisor.start_child(refs[:task_supervisor], + fn -> mod.login(caller, cfg, refs) end) {:player_position, caller, pos} -> - case mod.player_position(caller, pos, state) do - {pos, state} -> - send(caller, pos) - loop(mod, state) - end + 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 diff --git a/apps/amethyst/lib/apps/game_coordinator.ex b/apps/amethyst/lib/apps/game_coordinator.ex index bdc6158..f912274 100644 --- a/apps/amethyst/lib/apps/game_coordinator.ex +++ b/apps/amethyst/lib/apps/game_coordinator.ex @@ -53,8 +53,20 @@ defmodule Amethyst.GameCoordinator do end defp _create(type, games) do - state = type.instantiate() - pid = spawn(Amethyst.API.Game, :start, [type, state]) + # Create a DynamicSupervisor for this game + {:ok, game_supervisor_pid} = DynamicSupervisor.start_child( + {:via, PartitionSupervisor, {Amethyst.GameMetaSupervisor, type}}, + DynamicSupervisor + ) + {:ok, task_supervisor_pid} = DynamicSupervisor.start_child( + game_supervisor_pid, + Task.Supervisor + ) + # 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) + pid = spawn(Amethyst.API.Game, :start, [type, refs]) games = [{type, pid, []} | games] {pid, games} end diff --git a/apps/example_game/lib/example/game.ex b/apps/example_game/lib/example/game.ex index 9093165..6eae968 100644 --- a/apps/example_game/lib/example/game.ex +++ b/apps/example_game/lib/example/game.ex @@ -3,13 +3,14 @@ defmodule Example.Game do use Amethyst.API.Game, meta: [default: true] @impl true - def instantiate() do - {:ok, [:hello]} + def instantiate(supervisor) do + Logger.info("The supervisor for this game is at #{inspect(supervisor)}") + {:ok, %{}} end @impl true - def login(from, cfg, state) do + def login(from, cfg, _state) do Logger.info("Player logged in from #{inspect(from)}: #{inspect(cfg)}") - {:ok, state} + :ok end end