Published July 24, 2017

Developing Lag Compensated Multiplayer Game, pt. 2: The Server

In the previous part of the series, we’ve implemented an offline game and got ourselves ready to tackle the multiplayer. This time we’ll get our hands dirty fleshing out the server part.

Lag compensated games

This is the second part of the series. In case you missed the first one or want to go further, check them out below:

The server

Out of client-server architecture we’re going to develop the server first, since the client has to connect to something. In a nutshell, the server will run in cycles: gather Controls input from Players, run through game logic computations and finally send back updated game state to the clients.

Several key points can be drawn from this overview. Clients will only send Controls input, not their game state. This eliminates the need for client state validation on the server side (client can only say “my forwards key is pressed” which is always a valid possibility, rather than “I’m totally at [20x,30y]” even though it was [20x,5y] in the last frame). This in turn naturally implies that the server has to run game logic to become a single source of truth.

In order to make it all possible clients and a server have to communicate using a common format and maintain a fast two-way channel of exchanging messages. They also need to have some way of uniquely identifying game objects across the network barrier, so when the server says “this Bullet collided with this Player” the client knows which this Bullet and this Player it is. Knowing this, we’re ready to implement our server-related logic.

Infrastructural Chores

Remember that the infrastructure is already taken care of in the supplementing repository for this article series at part 2: the server, so if you’re coding along you can just delete everything in module source code and have an infrastructure up and ready.

Still here? A stubborn little fella, aren’t ya? Ok, first we will create a brand new server module, alongside of other top-level module (core, desktop etc.). It will be somewhat similar to a desktop module in a sense that it’s runnable, so we can just take build.gradle from desktop and adjust names accordingly for server. Then we’ll create necessary src/com/asteroids/game/server/ namespace inside. Then we’ll need to go back to the Asteroids project root where we’ll include server module in settings.gradle, add proper directories in .gitignore, and for a final mundane step, add tasks for new project declaration:

There are two interesting dependencies here.
We will include a headless LibGDX backend instead of a normal desktop one. Why? We won’t really need a GPU in order to run a server code, so that shaves off a bit of overhead and you’ll save yourself some hassle if your hosting service doesn’t provide boxes equipped with GPU at all. natives-desktop is just there to make headless backend work.
Another thing is netty-socketio. This will be our underlying workhorse socket server. It’s an implementation of Socket.IO that suits our needs for fast bidirectional message exchange.

There’s one more place in which we’ll need to add dependencies, and that’s the core project:

As you may have guessed, we’re going to use plain old JSON as message format and Jackson is a fine tool to convert objects back and forth. “Back and forth” part also explains why this is a core dependency rather than server one. Side note: Keep in mind that there will be a lot of messages flying between the machines and, if your future game sends large state messages, you may want to get out of debug mode. For our little Asteroids JSON will suffice.

netty-socketio uses SLF4J API for logging and will complain if no implementation is found on the classpath, so we’ll provide the simple one.

Whew. That’s it, onto the proper development now.

Identifying things

First of all, we need some way to uniquely identify those models that will need to have their state updated – those will be a Player (with its Ship), and a Bullet. An ability to identify a thing by id seems like a trait, so we’ll implement it in Identifiable interface that will be placed in model package:

Next we’ll make a Player and a Bullet implement this interface:

Ids are now mandatory for Player and Bullet, so make sure you’ll add them where those objects are created.
First place is inside of Ship:

And another in AsteroidsGame:

As we’ll be able to identify models, we’ll also want to pick and remove them out of Containers by id.
Let’s declare it in the Container interface. Note that we’ll also need to modify our Containers to only accept Things that are Identifiable.

We’ll also have to update ContainerRenderer and its generic bounds accordingly:

Additionally we’ll need the ability to delete Bullets from BulletsContainer by shooter id.
This will come in handy when a Player will disconnect and we’ll want to remove all of his Bullets.

Opening up models to obtain and set their data

We’ll need to pass a bunch of data to and from the server about the current state of our models. It means that they can no longer be those closed boxes of state anymore. We need to open them up. We have to figure out where these things are to send their position. We’ve already abstracted behavior of Visible things into an interface, and having a position (and rotation) is clearly a trait of a Visiblething, so we’ll add methods to set and retrieve these properties on said interface:

In order to connect Bullets with Players we’ll also need a shooter id, so let’s expose that from Bullet:

Finding a spot for new Player

Whenever a Player connects, we need to place his Ship somewhere. This will be a job for the new method on previously introduced Respawner called respawnFor. Note the minor refactor in respawn that will now use this newly introduced method too:

Remotely controlled Players

Players that will be kept on the server won’t be controlled by the server – instead, they’ll be controlled by the clients and those controls will be sent to the server to pass on. So, they’ll be remotely controlled. Let’s implement RemoteControls class then, it will create a mutable value objects that can be updated with data coming from the client.

When we’ll have RemoteControls we’ll want to get it out of Player on the server to set its control state. This could be as simple as passing RemoteControls as controls to Player and adding a getter:

…yeah, ok. But we’re referring to Controls interface inside of Player and we specifically want to get RemoteControls back.
Maybe we could get away with a little casting:

Yuck. Nope, this is just wrong. Remote controls make no sense in context of plain Player and force-casting RemoteControls onto whatever Controls there are would obfuscate the code intent. We’re missing a type here, and that would be a new model called RemotePlayer

Inheriting from one model into another may initially raise your eyebrows, but the most important thing here is what meaning this code will convey. RemotePlayer is a Player. It’s a specialized type of Player, one that will always be remotely controlled and we’ll have just one additional method to retrieve those RemoteControls in order to update their state. Other than that, it’s perfectly fine to use RemotePlayer in whatever way you’d use a plain Player.

Sadly, due to how generics are implemented in Java, Container<RemotePlayer> is not a subtype of Container<Player>, so we’ll have to refactor the code that operates on it. Let’s start with PlayersContainer:

Same thing with Collider:

And also Respawner:

Of course, you’ll also have to change all occurrences of Player to PlayerType inside of these classes.

Data Transfer Objects and their mappers

AKA boring stuff again. Sorry, but it has to be done. We could get away with mapping JSON data directly into models, but I find that ugly and confusing, so I’ll verbosely express data exchange formats instead. Some of the source code in this section will be omitted because otherwise, it would go on and on, so in order to get the full source please refer to the corresponding repo.

First, let’s create a new package in core called dto. All our DTOs will be immutable value objects with very similar structure and we can encapsulate all the JSON conversion details within an interface, so let’s do that:

We’ll also need to annotate constructor fields so that Jackson knows what’s what. Take an exemplary ShipDto:

There’s really not much to it. Just an immutable value object consisting of primitive types that will get encoded as JSON out of the box. The point here is that we’ll be keeping all this JSON business out of our models, which is beneficial for code readability and cleanness. Code for other DTOs will be omitted because it follows the same pattern, but I’ll briefly describe them all and their properties here:

ControlsDto – will represent Controls state as boolean values.

BulletDto – will contain all the data necessary to create/update a Bullet, including shooting Player id.

ShipDto – will be similar to BulletDto, but won’t include Player id because ShipDto will always be nested inside of PlayerDto.

PlayerDto – aside from Player properties, it will also contain ShipDto for a given Player.

UuidDto will be a little wrapper around UUID when we’ll need to send just that.

GameStateDto – will contain complete game state necessary to render a screen. Specifically, it will be contents of PlayersContainer and BulletsContainer encoded as Lists of PlayerDto and BulletDto.

IntroductoryStateDto – this one will be a bit special. It will be sent once to a newly connected Player with PlayerDto containing its introductory data (id assigned by the server, ship position) as well as GameStateDto of the game right until this new Player joined.

Onto the mappers now, which we’ll place in a mapper package inside of previously introduced dto.
A mapper will be a bridge between Dtos and models. Every model that needs to be synced over the network (Player, Ship etc.) will have its own mapper that will be responsible for things like creating Dtos from models, setting models state according to Dto and so on. They will be entirely stateless, and therefore consist of static methods only. Again, as in case of Dtos, mappers will be somewhat similar to each other and it would be redundant to show the code of them all here, so let’s take a look at example ShipMapper:

There are more of them here, and that will conclude this section.

After all this is done, we’ll be able to finally start working on the actual server implementation.

Connection

…is the name of the package that will contain our, well, connection-related code. We’ll make such package inside core first, and it will contain declaration of high-level Events that can happen in the client-server communication.

Let’s go briefly through them all:

  • PLAYER_CONNECTING will be sent by the client to the server when this client wants to join the game.
  • PLAYER_CONNECTED will be sent by the server to the client when connecting process is finished and the client is ready to participate in the game.
  • OTHER_PLAYER_CONNECTED will be sent by the server to the remaining clients other than the one that just joined to let them know that it happened.
  • OTHER_PLAYER_DISCONNECTED will be the same as previous, but it will let remaining clients know that some other client has just left.
  • CONTROLS_SENT will be sent by the client to the server and will contain current state of client’s controls.
  • GAME_STATE_SENT will be sent by the server to all the clients to let them know about the current state of the game.

Based on those events we’re going to build a Server that will react to them and send them to the clients. Even though we’ll use SocketIO for that, there’s no reason to not have some other Server implementation in the future, so we’ll start with a common interface. It’s going to be placed in another package named connection just like the recent one, but this time we’ll create it in the server module.

This will give us an overview of Server responsibilities. It’ll provide ability to react to 3 types of events: any Player connecting, disconnecting and sending controls, and also be able to send 3 types of data: game state broadcast for all, introductory data for newly connected Player and notification about that Player to the remaining Players.
As you may expect, there’ll have to be something above the Server, that will supply it with data to act upon. This is true, in that sense the Server will be a tool that’s used by game logic (pretty similar to AsteroidsScreen you’ve seen earlier), but for now let’s focus on implementing SocketIO variant of the Server.

In the constructor, we’ll initialize SocketIOServer with desired host and port. It’ll be ready to connect whenever we’ll call a start method. We’ll also set up exceptions behavior and set up events, more on that later.

Event handler methods will enable us to expose Server events to higher level layer:

These methods will enable higher level layer to communicate events to clients:

Next, we’ll need to handle low level connection logic before exposing it to event handlers. PLAYER_CONNECTING is most interesting here, so we’ll take a closer look at it. Whenever Player is connecting, it won’t yet have an id – we need to assign it. In this case, it will make the most sense to assign Player with the same id as its assigned socket client, because that will make it easier to perform some operations later (ie. handle disconnections). Then we’ll pass this new Player‘s Dto to higher layer.

Lastly, we’ll have the server exceptions logging and some small helper methods. This will be just a bit of boilerplate we need to ease our work with the server.

Using the connection

In previous section, the notion of higher level layer using Server was brought up a few times. Now we’ll implement it. Basically it’s going to be a variation on the AsteroidsScreen, but it will register events and gather input from them. It’ll also conduct sending events to Players.
Let’s create AsteroidsServerScreen inside server module:

After server will be injected we can start listening to events. onPlayerConnected is particularly interesting, so let’s examine it in detail. When Player will send its representation as PlayerDto we will create a RemotePlayer to keep on the server, spawn a place for its Ship using Respawner and when that’s done send IntroductoryStateDto to that Player with information about its newly spawned Ship and general game state. We’ll also notifyOtherPlayersAboutConnected.

render method will be almost the same as in AsteroidsScreen, except that it will lack rendering (since we won’t need any), but it will instead broadcast game state to all the connected Players at the end of each frame.

Almost there

At this point we will have two things remaining to conclude the server part (for now).
First one will be an equivalent of AsteroidsGame that will configure and inject dependencies into AsteroidsServerScreen. Only new stuff here is reading server’s host and port from environment variables:

Lastly, in order for the server module to be runnable in a similar manner as desktop, we’re going to need a launcher:

This is it

Let’s give our server a spin with ./gradlew server:run (on Linux/macOS) or gradlew server:run (on Windows) and see it in action!

game server launching

We got it! This is what we’ve been working for this entire part.

Yeah.

Your face right now:

disappointed

I know, it seems underwhelming. But it’ll make sense when we’ll implement the client side, and that’s what the next part will be all about. Stay tuned 🙂