How we handled 350+ online connections on a video game

Médéric Burlet - Sep 20 '21 - - Dev Community

We were working with a client on building a video game that had multiplayer integrated.

The game is more of a tabletop turn based game (think monopoly, life). We had to make sure turns and actions were synchronized across the board.

Furthermore we had the restraint that only one of the players is essentially the one enables to perform the actions. This means we had to account for scenarios where the client would be disconnected and we had to pass the power of decision to another user.

I got the power

Choosing the stack

Having had some experience with game jams and other multiplayer games we decided to go with a typescript server leveraging Socket.IO

This would let use easily create a client / server using the same typescript types for exchanging data information.

sending

Session Context

Introduction

First we wanted to setup our context. This means we could then interact with any game session or client data throughout any Socket.IO event.

export class SessionContext {
    activeSessions: Record<string, GameSession>
    clientSockets: string[]
    constructor () {
        this.activeSessions = {};
        this.clientSockets = [];
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we can see our context is consisted of activeSessions and clientSockets

clientSockets is just an array of strings of the ids of each clients connection. This is used later on on handling disconnection.

activeSessions are were we have a list of game session. We use here the type Record as this lets us access information on a faster scale with just the following:

const mySession = sessionContext.activeSessions[sessionId]
Enter fullscreen mode Exit fullscreen mode

Our class then had a few extra functions that don't need explanation such as (addActiveSession, getSessionByClientSocket, etc.)

Handling Disconnections

It was important for us to handle disconnections as if a client in power gets disconnected we want to give that power to someone else.

This is where the clientSockets comes in as it lets us do a difference with the list of clients handled by Socket.IO. This is needed as the disconnect event does not tell us exactly who disconnected.

We can then have something like this:

reconcileDisconnect(ioClients: string[], io: SocketIO.Server) {
    // Compare the list of the IO Server clients vs the one we stored
    const disconnectedClient = this.clientSockets.filter(x => !ioClients.includes(x));
    // If we have more than one disconnected user then we have discrepancy
    if (disconnectedClient.length > 0) {
        // Loop through all the disconnected clients
        for (let i = 0; i < disconnectedClient.length; i++) {
            // Init local variables for easy access
            const tmpClient = disconnectedClient[i]
            const sessionOfDisconnected = this.getSessionByClientSocket(tmpClient)
            // If disconnected client was in a session
            if (sessionOfDisconnected) {
                const tmpSession = this.activeSessions[sessionOfDisconnected]
                console.log("GM : " + tmpSession.master);
                // Remove him from the GameSession Client list
                delete tmpSession.clients[tmpClient]
                // Remove him from the global pool of connections
                this.clientSockets.splice(this.clientSockets.indexOf(tmpClient), 1)
                // Check if there are no more clients in the GameSession
                if (Object.keys(tmpSession.clients).length <= 0) {
                    // Delete GameSession if there are no more users
                    delete this.activeSessions[sessionOfDisconnected]
                } else {
                    // Check if he was has power
                    if (tmpSession.hasPower === tmpClient) {
                        // Log a message if we switched master
                        if (tmpSession.switchMaster()) {
                            log(`Power of the Session (${tmpSession.hash}) has been changed to Client (${tmpSession.hasPower})`);
                            let newPowerClient = tmpSession.getClientFromSocketId(tmpSession.hasPower);
                            tmpSession.broadcast({ type: "newGameMaster", query: newPowerClient.hash }, io, true);
                        }
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And we now have a new person in power or our session deleted if the person was the last user in the session.

power

Conclusion

We have a few other functions for getting and setting information but the main point of this article was to show how to handle identity of disconnections and how to access data efficiently using context.


Pixium Digital - Shaping your project with technology and innovation
https://pixiumdigital.com
https://github.com/pixiumdigital

. . . . . . . . . . . . . . . . . . .
Terabox Video Player