Hi. I am making a small 2 players game with Phoenix channels and I am using Presence to track connected users. My use case requires to terminate the game GenServer when any player leaves the channel, and notify another user about it.
So far I’ve achieved this by intercepting the “presence_diff” event in handle_out:
intercept ["presence_diff"]
def handle_out("presence_diff", %{leaves: leaves} = msg, socket) do
# check if someone left
case Enum.count(leaves) > 0 do
true ->
# kill gen server
Engine.kill_game(socket.assigns.game_id)
# notify players via terminate event
push(socket, "terminate", %{})
{:noreply, socket}
_ ->
push(socket, "presence_diff", msg)
{:noreply, socket}
end
end
So I am wondering if it’s a good way to intercept the “presence_diff” event, do some job there and send an absolutely different event? Or is there any different approach to that?
You can always just register a channel PID to a process that monitors it, then it can do whatever it wants when the monitor tells it the channel PID dies.
I do have a game channel for each individual game. Then I use join and terminate to signal join/leave of the game channel. I also store players in the game struct to check if You You are allowed to join/leave the channel.
This is an example of my terminate fuction, in the game channel…
def terminate(reason, socket) do
log("#{@name} > leave #{inspect(reason)}")
"game:" <> id = socket.topic
user = socket_assigned_user(socket.assigns)
with pid when is_pid(pid) <- Games.get_game(id),
game_state when not is_nil(game_state) <- Games.get_worker_state(pid)
do
if is_player(user.id, game_state), do: Games.leave_game(pid, user)
if Process.alive?(pid) do
notify_game_info(socket, pid)
else
broadcast!(socket, "game_removed", %{uuid: id})
end
end
:ok
end
That helped me a lot. I was unaware of terminate callback. I needed to handle when someone leaves the channel and didn’t know I could catch that in terminate. Thank you.
In addition to channel GenServer callbacks, it’s possible to use a technique like Phoenix.Presence uses to know when a channel dies. I extracted a gist (haven’t run it as is, but should work) from a project I’m working on recently.
True, it depends on what you’re wanting to optimize. Presence (and my code example) prefer consistency in the case of a failure. If you lose your monitors because you died, you would have to have a way to re-fetch that information in order to become consistent again. If you link, you will kill the other processes and they will re-connect (in the case of sockets).
Ultimately, it’s a tradeoff of if you believe your process will die and what you want the consequence of that death to be.
Yep, if you are just wanting a registry of connected processes then a monitor, but if you want to propogate death then a link, however trapping exits almost certainly means you want a monitor instead, or a supervisor.