deerob4

deerob4

I’m creating a game system, which includes a lobby and a countdown timer. Once enough players have joined, the countdown timer begins and upon completion tells the lobby to start the game.

Both are implemented as separate GenServers, registered using a via tuple in Registry. I’m a bit unsure of the best way for CountdownTimer to notify Lobby that the countdown has completed. I can think of a few different options:

  • Use Registry.lookup/2 to get the pid of the Lobby process, then send from the countdown timer to be handled in handle_info.

  • Add a countdown_completed/1 function to the lobby that takes the lobby id and calls GenServer.cast/2 to do whatever it needs. CountdownTimer can then call this upon completion. I’m not a fan of this because it seems to tie the two together a bit much.

  • Implement a pubsub mechanism using either Registry or Phoenix.PubSub so that the lobby can subscribe to the countdown completion event. This also has the advantage that my Phoenix channel could subscribe as well. This seems to be the most flexible solution, but I worry that it might be overkill.

I’d appreciate some advice on which method (or an alternative one) to go for. Thank you!

Showing Posts 1 to 7

LostKobrakai

LostKobrakai

I’m wondering why you need two processes here in the first place. Couldn’t the lobby do it’s own countdown.

Fl4m3Ph03n1x

Fl4m3Ph03n1x

Friendly code

I can see you tried to do some homework and you have some options to go about. Before anything, I would recommend you have a look at the examples for “Elixir in Action” (2nd edition):

I would redirect your attention to the Database hierarchy. @sasajuric also uses GenServers and Registry with via tuples, so I believe that just checking 3 or 4 files from his code would already direct you in the right path (right path being what I would recommend xD )

Direct call from CountdownTimer to Lobby

I discourage this. This means that CountdownTimer needs to know about the implementation of Lobby. This is highly discouraged in Elixir, if yo want both processes to communicate, they should have public API’s.

Public API

I believe this is what you mean for your second approach. You have a public function countdown_completed that other processes can call. This is by far the preferred method in the community because you expose an API, which allows you to change the structure of the messages being passed without affecting clients. I would caution against using a cast (CountdownTimer needs to make sure that Lobby received the message after all, or the game will never start, right?) but overall this would be my choice.

PubSub

Having in mind you will only have 1 process subscribed to the countdown, I believe a PubSub would be overkill. PubSub is nice if several processes are interested in knowing that the CountdownTimer has ended. If you only need to notify 1 process, then you don’t need the added complexity of the PubSub pattern.

peerreynders

peerreynders

This is a valid question. The scope of the problem you have described could be solved with the Lobby process using Process.send_after/4 and simply process the “complete” msg value when it is returned via the handle_info/2 callback.

I’m not a fan of this because it seems to tie the two together a bit much.

Now lets say there is a good reason for the CountdownTimer process to exist - maybe because it somehow coordinates a UI display of the countdown timer.

You can take inspiration from send_after. So rather than defining Lobby.countdown_completed/0, you solve the problem in the CountdownTimer API when the countdown is initiated.

CountdownTimer.new_timer(completed_msg, time)

CountdownTimer will implement this as a call so it will get from for free.

  • it can either use send_after itself with a {from, completed_msg} message value
  • or store {from, completed_msg} in the process state for later

So when the time comes CountdownTimer simply uses GenServer.cast(from, completed_msg) to return the requested completed_msg value to the Lobby that requested the timer (which it then has to handle in its own handle_cast/2).

In this particular situation I think it’s fine as the CountdownTimer process really doesn’t care if the Lobby process has died. Furthermore the CountdownTimer could just slap a monitor onto each of its client processes so that it can cleanup their timers as soon as possible.

srowley

srowley

I have a simple auction application that implements a timer for bidding and I simply use Process.send_after/4 and handle_info/2, and store the timer with the rest of the auction state. My front end can make calls to the server as needed to get an updated timer value, and when the timer expires handle_info/2 receives the message, does some things and lets the front end know that the timer expired.

That works fine.

Because the auction server also needs to send a bunch of other messages to the front end I ended up implementing a PubSub type of thing later anyway, to which this approach fit in nicely since my application was already generating a message for timer expiration.

Honestly the most trying part of implementing that was thinking through the various values the timer could have (false, a number, nil) and handling them appropriately, because in my case the timer gets started/paused/reset frequently under different conditions.

deerob4

deerob4 OP

Thanks all for your replies; they’ve been very helpful.

I’m mainly making CountdownTimer it’s own process because it seems like a separate concern to the lobby, which is mainly dealing with handing out the different roles in the game. I can definitely see the argument for merging the two, but in my head it seems a bit cleaner to separate them out.

I like this idea, but would it still work if the Lobby process is restarted for some reason? I’d expect the pid to be different then, so the from pid wouldn’t be correct. I’m also not sure how it’s different to the public Lobby.countdown_completed/1 function suggested by @Fl4m3Ph03n1x, which could call GenServer.cast itself.

LostKobrakai

LostKobrakai

Imho processes should be separated based on their runtime behaviour (error separation and stuff like that) and not on “concerns” like you’d separate classes by. The lobby and the timer are entangled in their runtime behavior so in my opinion there’s no reason to not run them in the same process.

peerreynders

peerreynders

Have a read through the following topic and the related article: To spawn, or not to spawn?

So, to summarize, in that post “thought concerns” refer to “I want to somehow organize a larger chunk of code, so it’s easier to work with”, while “runtime concerns” refer to “I want to get some observable runtime benefits, such as fault-tolerance, scalability, or potential for parallelism”.

Roughly (i.e. nothing is black and white):

  • thought concerns → modules
  • runtime concerns → processes

Also possibly:

I like this idea, but would it still work if the Lobby process is restarted for some reason?

The server argument for cast can be a name - so require that as an argument for new_timer.

The bigger issue is, how would a restarted Lobby process re-aquire the previous state (which may have been responsible for the last process crashing in the first place)? If the state is lost then the timer is meaningless.

I’m also not sure how it’s different

Coupling is significantly reduced.

CountdownTimer.new_timer(completed_msg, time) lets the client choose the exact format of the message to be handled by its own handle_cast/2 callback. And CountdownTimer isn’t coupled to the client module - not even at runtime.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews