0
\$\begingroup\$

I'm working on a real time multiplayer browser-based game.

The game is top down on variable size tile based maps. There is no central map where all players come together, the entire game plays out in multiple maps. (Player chooses one on login, but has the ability to switch whenever they want). Let's take the following loop that runs on the server side of things:

let tick_time = Duration::from_millis(500); // e.g. Tick 2 times per second.
let mut next_tick = Instant::now();

loop {
    let now = Instant::now();

    if now >= next_tick {
        // Parse user input
        // Update state
        // Update to state to users in map

        next_tick += tick_time;
    } else {
        let wait_time = next_tick - now;
        sleep(wait_time - Duration::from_millis(1)).await;
    }
}

In my 'map based' gameplay scenario, would it make sense to spawn a thread for every map where one of these loops runs? Or would it be recommended to have only a singular thread with a singular game loop that updates every map? Or is there perhaps a better, already existing, pattern for this?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

To ensure you don't schedule too much work in a single thread, I'd recommend using a server-side thread pool for this. Otherwise you may overrun your time budget per "tick".

Threads in the thread pool are assigned to calculate one "tick" of each client's map. Let's say you have 4 threads and 10 clients. The thread pool starts off this server "cycle" (let's call it a game turn, for all players/clients) with 4 available threads. These 4 are immediately each assigned a map to work on (say maps [0]-[3]).

As each client's map [0]-[3] completes, its associated thread is returned to the thread pool, i.e. it is now considered freed up for use. A trigger / event / callback fires that notifies that the thread pool has just gained an free thread. Your code subscribes to that event and uses it to assign that free thread to the next client map that needs processing, say for example map [4]. This will happen out of order, as and when each of threads [0]-[3] complete. This goes on until all client maps have been processed for this game turn, and by this point all threads are returned to the pool, ready for the next game turn to begin.

Timing

This depends very much on your setup, which only you will know all the details of.

Note that the above is not a loop, but a single cycle of a loop. You will repeat this sequence either based on a a loop in your own code that only does processing every x fractions of a second (sleeps or delays in between), or it may be run by an event loop running on the server (such as via setInterval in Node.js).

In a lockstep game server OTOH, you would actually do nothing at all until every single client has signalled (replied) to the server, saying that they have received the map data and that it checks out OK. Maybe they will also need to report their transformations to the map, to the server, meaning they will need to do their own processing phase first, and return their changes to the server. Only at this stage, can the server run the next tick.

\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .