Skip to content

Instantly share code, notes, and snippets.

@anandgeorge
Forked from andrewhao/game.ex
Created July 16, 2021 07:59
Show Gist options
  • Save anandgeorge/fb1d858e411446e11f0d7a1a78e581f4 to your computer and use it in GitHub Desktop.
Save anandgeorge/fb1d858e411446e11f0d7a1a78e581f4 to your computer and use it in GitHub Desktop.
Dynamic Supervisors in Elixir
defmodule Game do
use GenServer
def init(game_id) do
{:ok, %{game_id: game_id}}
end
def start_link(game_id) do
GenServer.start_link(__MODULE__, game_id, name: {:global, "game:#{game_id}"})
end
def add_player(pid, player_name) do
GenServer.call(pid, {:add_player, player_name})
end
def handle_call({:add_player, player_name}, _from, %{game_id: game_id} = state) do
# Uh oh, we started this process but it's not under supervision!
start_status = Player.start_link({player_name, game_id})
{:reply, start_status, state}
end
end
defmodule SupervisedGame do
# ...
def handle_call({:add_player, player_name}, _from, %{game_id: game_id} = state) do
# Now we replace this with supervised management
start_status = PlayerSupervisor.add_player(player_name, game_id)
{:reply, start_status, state}
end
end
defmodule Player do
use GenServer
def init({player_name, game_id}) do
{:ok, %{name: player_name, game_id: game_id}}
end
def start_link({player_name, game_id}) do
GenServer.start_link(
__MODULE__,
{player_name, game_id},
name: {:global, "player:#{player_name}"}
)
end
def get(pid) do
GenServer.call(pid, :get)
end
def handle_call(:get, _from, state) do
{:reply, {:ok, state}, state}
end
end
defmodule PlayerDynamicSupervisor do
use DynamicSupervisor
def start_link(_arg) do
DynamicSupervisor.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
DynamicSupervisor.init(strategy: :one_for_one)
end
# Start a Player process and add it to supervision
def add_player(player_name, game_id) do
# Note that start_child now directly takes in a child_spec.
child_spec = {Player, {player_name, game_id}}
# Equivalent to:
# child_spec = Player.child_spec({player_name, game_id})
DynamicSupervisor.start_child(__MODULE__, child_spec)
end
# Terminate a Player process and remove it from supervision
def remove_player(player_pid) do
DynamicSupervisor.terminate_child(__MODULE__, player_pid)
end
# Nice utility method to check which processes are under supervision
def children do
DynamicSupervisor.which_children(__MODULE__)
end
# Nice utility method to check which processes are under supervision
def count_children do
DynamicSupervisor.count_children(__MODULE__)
end
end
defmodule Player do
# ...
# Insert this start_link/2 method, which intercepts the extra `[]`
# argument from the Supervisor and molds it back to correct form.
def start_link([], {player_name, game_id}) do
start_link({player_name, game_id})
end
def start_link({player_name, game_id}) do
# Original implementation...
end
# ...
end
defmodule PlayerSupervisor do
use Supervisor
def start_link([]) do
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
Supervisor.init([Player], strategy: :simple_one_for_one)
end
# Start a Player process and add it to supervision
def add_player(player_name, game_id) do
# Note that the second arg to start_child/2 must be an Enumerable
Supervisor.start_child(__MODULE__, [{player_name, game_id}])
end
# Terminate a Player process and remove it from supervision
def remove_player(player_pid) do
Supervisor.terminate_child(__MODULE__, player_pid)
end
# Nice utility method to check which processes are under supervision
def children do
Supervisor.which_children(__MODULE__)
end
# Nice utility method to check which processes are under supervision
def count_children do
Supervisor.count_children(__MODULE__)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment