Least intrusive/best way of having a process inspect itself in a time interval

The use case is simple, in a timed game (independent timers for each player), where a process handles the game state, what’s the best way of having it recurrently introspect to check for certain conditions?

There are checks whenever an action is executed, but if say, a player sits idle and its timer reaches the end - without him having executed anything, what is the least intrusive way of checking the state every 5secs (arbitrary)?

caveats:

  • it always needs to introspect the state, because the remaining timer for a player is stored in the players state, so a game runs out of time when a player reaches his 25min limit, but it might happen that the total is reached more than 25min from the start if both players play a while and then one of them goes idle (say player1 spent up to 10min, and then player2 went idle, the game would reach its end at around 35min). The players timers are updated correctly and already in place, stored as an integer representing seconds in the JSON game state.

  • from a performance point of view, would it be impactful to have the timer running and broadcasting the new times to a socket at a given interval? (specially imagining there could be hundreds of simultaneous games - not really the case for now or anytime soon, but thinking about it). I think that perhaps this is not needed as in the front-end I do build the timers in JS whenever a new state is broadcasted, so it would only need to be sent in case a game actually ran out of time.

  • I would also like to only start this introspection once the game has been on for more than 25min (since it’s useless to check before) but how would I deal with a game that crashes? Just recalculate the remaining minimum time from the state and apply that as an offset to start introspecting?

On another note, what would be your strategy for persisting game states? (should I make this a separate thread? there’s a lot of questions about this, but specifically for a turn-based game, where a game state is composed of a json object with around 1500 keys)

Right now each game is run by its own GenServer process with a global ref, under a Supervisor. On first initiation it creates the game and saves it to the db, it then uses that as it’s state, if the process crashes it loads the game from the JSON column in the saved DB record whenever it’s requested to serve it and doesn’t have a state, and otherwise always uses its state to serve the game state.

I’m thinking of simply “casting” the save action (since it doesn’t require a reply) and since it will only cast in case it’s a valid game state anyway, there shouldn’t be a problem in getting Ecto to update the record in the background from the cast. Do you have any better ideas? Or things I should be aware of?

Thanks, your opinions are welcomed!

Micael, check out Process#send_after/4

It also returns a reference to a timer, so you can easily cancel it if you need to on a player’s action

2 Likes

Or if you know you will not get messages then just returning a timeout message from the genserver results is fine (but that is cleared on every new message, so Process.send_after/4 is usual). Process.send_after/4 uses a single process to send messages out that can become a bottleneck if you are doing, say, thousands or millions a second (there are libraries that fix it) but if not then use it fine. :slight_smile:

1 Like

Thanks Tony, I think this is it - so simple :smiley:

Well, it would be a good sign if it was handling thousands of requests per second (unless I accidentally looped the requests) :slight_smile:

Regarding the timeout, you mean on the genserver call/ ? How would I leverage that ? The genserver calls are basically housekeeping, they just call initialization, return and save (cast) the game state? The communication to the clients is done through broadcasting (outside the genserver as part of the channel handle_call)?

Just on all genserver responses you’d add a timeout message, then you get that timeout message if it is hit without receiving a message by then. It is basically the after clause in a receive with all the usual things about it, but it is very efficient.

So on handle_call, I would do {:reply, state, state, last_calculated_min_time_for_game_ending} ?
And this would be sweeped whenever a new call is handled? And if no call is made to this gen-server, when reaching that time it would call handle_info(:timeout, …) ? Sounds like a good way of doing it as well, thanks, I might actually use this instead then - you say it’s more performative than having the .send_after’s? (I’m probably worrying about a problem that will never actually popup but yeah)

Correct. :slight_smile:

Different use cases though. Doing a send_after will ‘always’ send it to you, no matter what messages you get between (unless you cancel it, but it can still arrive depending on timing). The timeout is only for when no messages are received. :slight_smile:

1 Like

Yes, indeed they look to do different things, what I’m thinking though is that in essence the timeout matches what I want to happen better - and doesn’t require me to “cancel” the send_after once a new move is made (and subsequently request a new send_after to be set). When crashing both ways cancel themselves so in that both would work just fine. Thanks!

1 Like