The IOs of Game Netcode #4 – Blocking and Non-Blocking Sockets

This is part of a series of posts revolving around game netcode development, the introduction and links to the other posts can be found here.

 

In this post, we’re finally back onto some netcode stuff. We’re going to address the differences between blocking and non-blocking sockets. Both types are usually provided by the socket implementation that comes with your OS, or are provided as part of the standard library for that particular language. In addition, they may also be wrapped into a single interface, simply operating in different modes (blocking or non-blocking mode) or separate interfaces, one dedicated for each.

 

Blocking Sockets

This is your typical TCP socket and it’s what you will find most of the time when looking for tutorials and examples of netcode. In other documentation and articles, if it does not specifically say they are non-blocking sockets, then they will no doubt be referring to the blocking variety. This is because they are easier to work with.

Simply put, blocking sockets will block the current thread until the read or write operation has finished. With a read operation, it will block until the specified number of bytes have been read. With a write operation, it will block until the specified bytes have been successfully written.

However, it is not as straight-forward as this. Socket implementations have their own internal socket buffers. So, when performing read and write operations, the thread blocking occurs when it cannot read from or write to the internal socket buffer (as it is either full or empty depending on the operation). Most of the time a write operation will never block because there will be space in the internal output buffer (except if you’re sending larges amounts of data, such as transmitting file data). The socket implementation will automatically send any data in the internal output buffer and accumulate new data in the internal input buffer when it receives it. So, if the network card has already received data into its internal input buffer that your game netcode is expecting, then the subsequent read operation will not block, it will read that data straight from the internal input buffer instantly without blocking.

This is why we’ve been dealing with threads up until this point. In order to make use of blocking sockets, you’ll need to run at least one thread per socket. This isn’t necessarily an issue on a client because you’ll only ever have one or two open sockets to the server at a time. However, on a server that is required to handle many concurrent clients, the thread overhead can begin to build up.

Of course, as with everything in the world of software development, there are always exceptions. For instance, you can check the number of available bytes to ensure everything required is in the input buffer before invoking a read operation and therefore never blocking. You can do similar checks for the write operations. This works well for simple netcode applications and will allow you to run the netcode for all your clients in a single thread, or even in the main thread (although this is viewed as extremely bad practice), without any major issues. However, with more complex netcode, especially variable sized data packets, it quickly becomes increasingly difficult. If you find yourself doing this to handle multiple clients in a single thread, you should really stop because you are actually reinventing the wheel. What you really should be using here are non-blocking sockets.

 

Non-Blocking Sockets

This type of socket is very similar to blocking sockets, with one difference. All operations performed on a non-blocking socket are expected to return immediately and thus will not block the current thread. This changes absolutely everything in regards to working with them. You can’t just read from a non-blocking socket and then deserialise the data as no data may be returned (and it will not block until there is some). Instead you need create your own buffer and read data into it, then when you have everything you need, deserialise the buffer.

And yes, I know you should be reading blocking sockets into your own buffers as well if you want to be able to deserialise any kind of complex data packets, but there are other complexities that need to be handled. When writing, if the socket’s internal write buffer is full (due to lots of data or poor bandwidth), the write operation will block. Which allows you to throw everything you have at it and it’ll block until it eventually goes through. This is not the case with a non-blocking socket. You have to monitor the number of bytes written and remove those bytes from your buffer yourself before the next write.

Why would you opt for a socket type which is clearly more difficult to handle? Well, as with most hard ways there are benefits. Knowing that no matter what you do the socket won’t block the current thread has its advantages. Checks can be removed, assumptions can be made. Once you design your netcode to handle instant returns of zero data, you’ll realise that you can run all sockets on a single thread. This is what I was referring to at the end of the section on blocking sockets and it is excellent when it comes to writing game servers. However, it gets better. You can make use of selectors.

 

Selectors

These handy utility classes allow you to query all objects registered with it in a single operation. I’m not entirely sure if there are different types, but the selectors I’ve worked with in Java and C++ are specifically designed for use with sockets and IO streams.

Once registered, you can query the selector for all streams which are ready for IO. If none are, then the select operation will block until at least one stream is ready, otherwise it will instantly return. After the select is called, you can then retrieve the list of ready streams. In C++ you can select the read ready and write ready streams individually, but in Java you will need to check whether the ready state refers reading, writing, or both. Either way, you can get a list of streams and the state they are in, which allows you to process all the reads and writes in one go without blocking. Once complete, you just loop back round and start at the select again.

Now, I’ve done some light benchmarking to see which performs better, one socket per thread or all sockets on a single thread using a selector. All I can tell you is that using a selector is blazingly fast and, with the other benefits of lower thread overhead and being much easier to manage on a single thread, I very much prefer this approach these days. It makes up the core of the netcode in our Solitude game server and I have measured it before a couple times looking for bottlenecks (which existed elsewhere) and it doesn’t even register a microsecond with multiple clients connected.

 

Conclusion

Non-blocking sockets are definitely better than their blocking brethren, but they are a little trickier to set up and use. However, once you have your net framework done, you don’t really ever have to worry about it again. Of course, the benefits you get only really apply to server applications. We still use a blocking socket on its own thread in the client because there is absolutely no point in changing it now. There will be zero benefit.

 

Anyway, that pretty much wraps up this post. As always, if you want to chat about any of the topics or issues I’ve raised you can either comment below, catch me on IRC (on the navigation bar click Community→Chat) or send a tweet to @Jargon64.

Thanks for reading! 🙂

The IOs of Game Netcode #3 – Synchronisation and Locks

This is part of a series of posts revolving around game netcode development, the introduction and links to the other posts can be found here.

 

We’ve already established you need to run your netcode framework on a different thread to your main game’s update/render loop, something which all netcode developers will agree is a requirement for writing good netcode. This means you’re already in danger of the threading and concurrency issues mentioned in the last post. So how do you ensure that you don’t run into these issues? Well, you have a few weapons in your arsenal to tackle the problem. The first one I’m going to discuss is locks.

 

Locks

Think of locks as your most basic weaponry against threading issues. The pistol with infinite ammo you switch back to after your power-up wears off. Locking is a synchronisation mechanism that ensures that only one thread can execute a piece of code at any given time. It does by forcing other threads to wait until the currently active thread is finished with the locked code.

Locks are such a common mechanism that I’m pretty sure all modern languages that support multi-threading also support locking. It’d be pretty disastrous if they didn’t. The main differences are the types of locks, the terminology and how they’re used. You usually have a couple of types available to you, such as object-level locks and method-level locks. Object-level locks are the bread and butter here and more complex locking mechanisms can be built upon them. In C# an object-level lock looks like this:

lock (this) {
	// Your synchronised code
}

In Java you achieve object-level locks using synchronized blocks, which are essentially the same thing:

synchronized (this) {
	// Your synchronised code
}

Unfortunately, in C++ you do not have a simple statement for object-level locking (although I’m sure you can create a template to do it). Instead, you need to instantiate a mutex (mutually exclusive) object (part of the C++ standard library) and perform the lock operation on that.

std::mutex mtx; // Declared in the class constructor

mtx.lock();
// Your synchronised code
mtx.unlock();

These code snippets will all do the same thing when multiple threads hit it at the same time. The first thread to request the lock will get it and be allowed to proceed to execute the code within the lock. All other threads will request the lock, but will be forced to wait (block) until the thread with the lock leaves the synchronised code, after which another thread will receive rights to the lock and proceed.

There are also method-level locks, which work similarly to the object-level locks, except work at the method level, synchronising everything inside a particular method. In C# this is achieved with the following:

[MethodImpl(MethodImplOptions.Synchronized)]
public void SynchronisedMethod() {
	// Your synchronised code
}

In Java, you use the same synchronized keyword as in object-level locking:

public synchronized void synchronisedMethod() {
	// Your synchronised code
}

C++ does not have a mechanism for method-level locking, but this can be easily achieved with a simple object-level lock. The method-level locking is just a convenience mechanism for object-level locking everything in a method.

Now, method-level locking and the examples I’ve shown for object-level locking have a pretty serious issue to take into consideration. A thread can achieve a lock on a method or an object while another thread has the same lock on a different object of the same class. This is because these types of locks are handled at the object level (locking against it’s own instance or a member object). For the most part this is exactly what is required, but sometimes there are situations in which you need to synchronise all instances of a particular class. This is called class-level locking and can be achieved in a couple of ways. You can perform an object-lock on a static object shared by all instances of a specific class, or you can do method-level locking on static methods.

Of course, with most coding concepts, there is a down-side to very up. With locking mechanisms, that is deadlocks

 

Deadlocks

A deadlock is when a thread is indefinitely blocked by a lock and they usually occur when multiple threads are executing code that results in nested locks on. For example, let’s take the classic bank transfer scenario.

public class Account {

	private double balance;

	public Account(double balance) {
		this.balance = balance;
	}

	public void withdraw(double amount) {
		balance -= amount;
	}

	public void deposit(double amount) {
		balance += amount;
	}

}

public class Bank {

	public void transfer(Account from, Account to, double amount) {
		synchronized (from) {
			synchronized (to) {
				from.withdraw(amount);
				to.deposit(to);
			}
		}
	}

}

Both accounts are synchronized so that exclusive access is obtained and the program is assured that it can perform all operations required of the transfer without blocking. The deadlock arises when a Bank executes two opposing transfers between the same two accounts at the same time (separate threads).

final Account account1 = new Account(1000);
final Account account2 = new Account(1000);
final Bank bank = new Bank();

new Thread(new Runnable() {
	public void run() {
		bank.transfer(account1, account2, 500);
	}
}).start();

new Thread(new Runnable() {
	public void run() {
		bank.transfer(account2, account1, 500);
	}
}).start();

The result can vary depending on execution order, but if both threads manage to obtain their first lock, then a deadlock will occur. This is because both threads cannot obtain their second lock until the other thread releases it, resulting in both thread blocking indefinitely.

The primary reason behind deadlocks is bad software design. Deadlocks can easily be avoided if the developer takes a step back and designs the inter-thread communication first. If the program/system makes use of multiple threads, then a deadlock scenario should be at the forefront of the developer’s mind. I’ve seen a number of poorly written programs where the deadlock scenario isn’t even considered and, for the most part, the program runs fine, but occasionally a deadlock will occur. This is because the developer will just throw in a lock here or there to ensure they don’t run into concurrent modification exceptions and suddenly have methods with locks calling other methods with locks, resulting a nested deadlock. Of course, the way they solve this is to ensure the proper execution order by adding more locks, which as you can guess, just makes things worse.

In my experience, the best implementation of multi-threaded operation consists of very few locks, but in the right places. DESIGN YOUR MULTITHREADING FIRST! 🙂

 

Thread-safe and Concurrent Data Structures

So now you should understand how to synchronise your multithreaded code, while avoiding potential deadlocks. It will help you avoid the concurrency issues when writing good threaded netcode. Now, I’m going to briefly touch on a set of special data structures that are designed to further help you avoid concurrency issues and deadlocks.

Thread-safe and concurrent data structures are a set of data structures that follow a certain set of rules to ensure that race conditions do not happen. This is done through a variety of different methods, such as locking and atomic operations.

They take care of all of the concurrency issues without any of the potential deadlocks, making your life a lot easier. Java, C# and C++ all have a decent set of thread-safe data structures in their standard libraries, too many to go through here, but it’s definitely worth searching their respective documentations and read up on the specifics.

One thing you need to keep in mind though, even though the add and remove operations are thread-safe, any iterators or enumerations generated from these data structures may not be. Meaning, if you loop over all the elements in a thread-safe data structure, the resulting collection may not be thread-safe. So if another thread adds or removes an element to the data structure while the loop is being processed, you’ll end up with concurrent modification and a race condition causing unpredictable behaviour. Fortunately, these thread-safe data structures usually offer up some kind of fail-fast iterator or enumerator which will throw a concurrent modification exception when it detects that the underlying structure has been modified after it has been created, which can then be handled properly by the developer.

You can usually avoid the concurrent modification exception by using the correct concurrent data structure for the job and in the right place. I like to use a concurrent queue for inter-thread communication as it allows me to iterate through the queue in a thread-safe way using peek and pop operations (no need for iterators or enumerators), which I will show an example of in a later post.

 

This pretty much wraps up this post. It’s a bit longer than the previous ones but I wanted to finish with the multithreading so that I can actually get into some nitty-gritty netcode stuff next post 🙂 Hopefully you can take something away from this that will aid you in your game development, and as always, if you want to chat or pick my brains about anything you can either comment below, catch me on IRC (click Chat above) or fire me a tweet to @Jargon64.

Thanks for reading! 🙂

The IOs of Game Netcode #2 – Threading and Concurrency

This is part of a series of posts revolving around game netcode development, the introduction and links to the other posts can be found here.

 

In the last post of The IOs of Game Netcode (found here), I talked about a few general rules of thumb I usually follow when starting a new netcode framework. Over the next few posts I’m going to go a little deeper into the technical options available to us as netcode developers and what routes I take based on different scenarios. The first topic I’m going to start with is threading, and the resulting issue – concurrency. This post is quite long so I’ve only addressed the issues the developer should keep in mind while working with threads. The solutions to these issues will be covered in the next post 🙂

 

Threading

As any netcode developer will know, the first hurdle you will hit when writing netcode or working with sockets is the threading issue. Normally, you can only execute a single piece of program code at a time. Threading allows you to execute multiple pieces program code simultaneously by running it on different threads. By default, reading and writing via sockets will block current execution of program code until it is complete. This isn’t necessarily an issue with writing to a socket unless you are writing faster than the hardware can handle (network card or modem), but if you’re reading, the operation will block until there is data to be read. One of the ways around this is to check the number of available bytes to be read before reading and if there are bytes available, only read that much. However, even with using this method, the actual reading and writing of bytes will block, even if it’s just a moment, and you don’t want this in your main game or render loop. Another way around this is to use asynchronous read and write operations. These run in their own threads automatically (provided by the socket library you are using) and pass the data back via a callback when complete. They have their uses, such as web services, but for real-time game netcode they can begin to cause problems as you cannot be sure of the order of transmission and you start to encroach of race condition territory.

So, in order to effectively read and write across a network with sockets, without causing the main program to hang while it is doing so, you’ll need at least one additional thread to perform the socket operations on. The reason I say at least one additional thread is because when developing your game client, you only really need a single socket to connect to your server. However, for the server, you’ll need a thread for every connecting client to handle each of the socket operations. For those of you who are now thinking “Why not use non-blocking sockets?”, I’m aware of this and it will be covered in a future post, but for the time being I’m focusing on the standard variety of sockets as there’s a lot more to consider with using non-blocking sockets 🙂

Anyway, as soon as you start working with multiple threads, it opens up a whole new bag of worms in the form of concurrent modifications.

 

Concurrency

Concurrent modifications are when you are reading a value of a variable in one thread, while it is being modified in another. Another example is looping over a collection while different thread is adding or removing an element from the collection. Most languages languages allow this type of access with unpredictable results. This is due to two main reasons.

 

Race Conditions

The first is the race condition, you just don’t know what thread is going to access the variable first. There are three scenarios for a race condition:

  • Read & Read – Both threads want to read the value of a variable. It is unknown which thread reads the variable first but it doesn’t matter as it does not change. Both threads read the same value.

  • Read & Write – One thread reads the variable, while the other writes to it. The final value of the variable will always be what is written, but the value read by the reading thread may be that of the variable before the write, or after. This can lead to the aforementioned unpredictable behaviour and potential crashes.

  • Write & Write – Both threads want to write. No read operations are carried out, but that does not eliminate an unpredictable value being read later. This is because the final value of the variable is unknown. It is the value of whichever thread wrote to the variable last.

The above scenarios are very specific and only show two threads accessing a single variable. However, in reality, these threads would be doing more than just reading and writing to a variable. For example, we have a shared (global or static) float variable called currentSpeed accessed by both threads:

Thread 1 – Anti speed-hacking protection

currentSpeed = player.getVelocity().getMagnitude();
if (currentSpeed > MAX_PLAYER_SPEED) {
    player.disconnect();
}

Thread 2 – Find the fastest moving entity

Entity fastestEntity = null;
currentSpeed = 0;
for (Entity entity : entities) {
    if (entity.getVelocity().getMagnitude() > currentSpeed) {
        currentSpeed = entity.getVelocity().getMagnitude();
        fastestEntity = entity;
    }
}
return fastestEntity;

For the record, you should never share a variable between two different tasks like this, but if you did this is how it might play out. For this example we are going to assume that the player is moving at a speed of 4 and there is one other entity in the world moving at a speed of 10:

// Start with Thread 2
Entity fastestEntity = null;
currentSpeed = 0;
for (Entity entity : entities) {
    if (entity.getVelocity().getMagnitude() > currentSpeed) {
// Switch to Thread 1
currentSpeed = player.getVelocity().getMagnitude();
// Switch to Thread 2
        currentSpeed = entity.getVelocity().getMagnitude();
        fastestEntity = entity;
    }
}
// Switch to Thread 1
if (currentSpeed > MAX_PLAYER_SPEED) {
    player.disconnect();
}

In this scenario, the first time currentSpeed is assigned is after the first switch, where it gets set to 4. Before it can test the value of currentSpeed, the process switches to thread 2, where currentSpeed is set to the value of the fastest moving entity’s speed, which 10. Then the process switches back to thread 1 to perform the test. Oh look, the player is moving at a speed of 10, they must be speed-hacking, better disconnect them!

This occurs because the threads can switch at any point in during normal processing and is always something you need to keep in mind while working with multiple threads. There are mechanisms to get around these issues, but first…

 

Non-Atomic Operations

The second reason concurrent access can cause unpredictable results is due to non-atomic load and store operations. This is a bit more low level and might be harder to grasp for those who aren’t familiar with CPU architecture. There are a couple of definitions when it comes to the atomicity of an operation. It can refer to a single instruction or an operation of multiple instructions. Essentially, an operation is considered atomic if it completes in a single step relative to other threads. Therefore, a non-atomic operation can also result in a race condition as described above, but for the purposes of this section, we’ll be focusing on single instructions.

When you want to run your game (or program), you need to compile it into machine code first. Every developer knows this. During the compilation process, the compiler reads our source code and optimises it internally before outputting machine code, therefore the machine code doesn’t directly reflect the logic that we’ve defined. Most developers know this. One of the optimisations compilers do is to maximise CPU register usage. General purpose CPU registers typically have a size equal to the bit-architecture of the system. Modern day systems are 64 bit architecture and have 64 bit general purpose CPU registers. If you have two 32 bit integers that have some operation performed on them, the compiler will attempt to optimise the machine code to load them both into the same 64 bit register to perform the operation more efficiently. Some developers know this.

Now, the problem lies in the scenario where you attempt to perform an operation on a data type that has a larger bit requirement than the CPU register can handle, or the register already has some active data in it. The data ends up being split into multiple machine code instructions – and this is what causes the problem. Can you remember when I mentioned that threads can switch at any point in normal processing? Well, this happens at the machine code instruction level. So, a simple variable assignment such as:

long timestamp = 1L;

Can be split into two machine code instructions, with a thread switch right in the middle.

Not many developers know this.

This is the very essence of non-atomic operations. If processing switches to another thread during a multi-instruction load or store, the race condition is the least of your worries. Depending on your operation, you’ll either end up with a torn-read or a torn-write. One thread attempts to write a 64 bit integer to a variable but only gets as far as the first 32 bit store instruction, another thread reads the full 64 bit contents of the variable, then the first thread writes the second 32 bit store instruction. What the second thread ends up reading is one-half correct data, one-half bad data and one-whole big problem.

 

Some of you may now be thinking “Holy crap, threads are dangerous, how the hell do programs even function without exploding into a flaming ball of random corruption!?” Well, the answer is yes, they are dangerous, but there are also certain principles you can abide by and mechanisms you can use that prevent this pseudo-random behaviour. However, these topics will be addressed in the next post 😉

Like before, if you have any questions about this post or just want to chat netcode, please comment below or fire me a tweet at @Jargon64.

Thanks for reading! 🙂

The IOs of Game Netcode #1 – A Few Rules of Thumb

This is part of a series of posts revolving around game netcode development, the introduction and links to the other posts can be found here.

 

First of all, welcome to the series! 🙂

This first instalment of The IOs of Game Netcode will cover a few rules that I’ve come to follow whenever I approach a new netcode project. Now, as I’ve been writing this I’ve come to realise that because netcode is mainly backend code, there isn’t going to be a whole lot to look at except large walls of text. I’m afraid I can’t do much about that. Hopefully, you find the posts interesting enough to persevere 🙂 So, without further ado, here are my rules of thumb…

 

Text is BAD

What I’m referring to here is text-based netcode serialisation is bad and you shouldn’t do it. If you do it, then you are a bad person and should feel bad. Of course, there are exceptions to this rule. The first being web services, which in all fairness is a pretty big exception. Turn-based games can get away with it as well. Also, you may not have a choice if you’re developing a mobile game due to unreliable Internet connections and are forced down the RESTful web-service route here too. I’ve played a few real-time multiplayer mobile games and they’re alright, if you’re on wifi, otherwise it’s a terrible experience.

So, if you don’t fall into one of the above exceptions, you’re going to want to go with object serialisation at least otherwise you’re going to get a lot of overhead on parsing your netcode into usable formats. Now, for a lot of you this is pretty obvious and you may be asking yourself “Why would any sane person try and implement a real-time game’s netcode with text-based serialisation?” Well, for some of the more inexperienced developers out there, it may seem like a good idea to use a flexible text format like XML or JSON for your netcode because “everything can read it” and “other people can write clients to consume it like a web service”. Just save yourself the headache and stop yourself now.

If you haven’t guessed, yes, this is something I chose to do while developing Root Access. This was a long time ago now and what roped me in was the flexibility, you don’t need to deserialise everything you receive and different clients can select only what they need, but this was an inexperienced train of thought. Of course you want to deserialise everything, if you don’t you clearly don’t have efficient netcode. Even though it did work, and pretty well at first, it got progressively slower the more data we shoved through it. Eventually, I went back and recoded all of the netcode to use Java’s object serialisation framework, which took a long time and was a massive headache. So save yourself the trouble and don’t do it! 🙂

 

Everything is Multiplayer

It is my firm belief, that if you are making a game with multiplayer, you should bite the bullet and make your singleplayer multiplayer. What I mean by this is your singleplayer is actually a private multiplayer session consisting of one player using the system’s local loopback interface. Of course, this basically means you need to START with your netcode framework before you can make any kind of progress. Yes, this does slow down the initial project start quite considerably and also makes developing the singleplayer aspect of the game a bit slower and require more effort, but I believe the benefits are definitely worth it.

For starters, you’ll finish your game faster. If your singleplayer and multiplayer modes use exactly the same code, then you’re killing two birds with one stone. All you have to do is flip a switch and you go from singleplayer to multiplayer and vice versa. Writing two separate systems to manage singleplayer and multiplayer will just be a maintenance nightmare down the road. There shouldn’t be any latency issues running singleplayer through the loopback interface. If you are experiencing any kind of noticeable delay while playing a local singleplayer, this just points out that there may be an issue with your netcode design / implementation, forcing you to fix it and resulting in a better multiplayer experience as well! 🙂

Lastly, and this is the big one, you’ll avoid the gargantuan headache that is retrofitting multiplayer into your game. If you focus on singleplayer only to get your game out as soon as possible, with the idea of multiplayer coming much later on, you’re in for a world of hurt. I’ve seen so many game developers do this as well, and it basically appears that they’ve hit a brick wall in terms of progress because they are so busy behind the scenes refactoring, debugging and generally rewriting a huge portion of their game. I was actually impressed when the guys at Mojang managed to retrofit Minecraft’s singleplayer into a multiplayer system to support LAN play, but it required huge changes including the way their singleplayer stored its map and player data. So, if you’re planning multiplayer at ANY point during a game’s development, DO IT FIRST! You’ll thank me later 😉

 

Stand Alone, Together

Now this one may not be as obvious and a lot of people may actually disagree, but I think it’s something that can help a game’s design, development and maintainability. When writing your multiplayer framework for your game, separate the game client and server into separate projects, and also build them as separate binaries. It may be tempting to implement your multiplayer server into your game client as it’s easier to work in a single project, especially if the game doesn’t really require a dedicated server. Do it anyway. The reason I say this is because you’re going to end up with a lot of duplication, particularly in regards to the game data (which is understandable due to the client and server’s own knowledge of things). If you’re not careful you’ll start cross-referencing data without going through the netcode properly and before you know it, your multiplayer isn’t truly multiplayer and it’ll be a development and maintenance nightmare.

If you do separate your client and server into separate projects, but plan to compile and distribute together as one application, then I don’t really see a problem with this. I just prefer to go all the way and build as two separate applications, then you have a dedicated server from the beginning. Then for locally hosted games, you just get the game client to launch the server in the background, telling the server that the player is the host / has admin privileges by providing a player’s token or account ID to the server process. This also works for singleplayer games as mentioned in the Everything is Multiplayer section raised above, just you keep the game private, skip the game lobby screen and launch straight into the game.

What I have suggested so far makes the assumption that your client and server are written in the same language. If they’re not, then you’ll probably never encounter these issues, but there is also an advantage of working in a single language and that is shared projects. Back in the day when we were very much Java, Java, Java we usually started a new game with the same triad of projects. Client, server and shared. The client and server were dependent on the shared project, which housed all common functionality between the client and the server. It included the netcode framework, netcode commands and data structure classes. This removed a whole tonne of duplication in the projects and is something that worked very well for us while we worked in a single language. It’s a lot harder to do when you work in Unity and C++ :). Anyway, since those days, I’ve refined my thinking a bit and come to the conclusion that sharing the netcode framework and potentially the data structure classes is potentially a bad idea, and that only the netcode commands should be shared.

If you share your netcode framework you have to start writing in special cases depending on whether the process using the framework is the server or the client, as well as identifying the source of a netcode command (whether it is the server or a client and how to handle it). I actually saw someone tweet a code snippet that made reference to this exact check recently and, to be honest, I don’t think it’s needed. Of course, there’s always an exception to the rule. If you’re developing a peer-to-peer model, then you may not have a choice, depending on how your client gets its peer list. Anyway, the reason I mention that you shouldn’t be sharing a data model either is because your client should never know everything your server knows. The client should only know portion of your server’s game data, only what is relevant to it. Also, you should only be sending client required data for each entity class. Your server versions of the entity classes will contain a lot more and some of it may be sensitive. Your server’s game data classes will also contain a tonne of functionality regarding how the entity behaves, that the client should never need. If it does, then your design is wrong as your server is not fully authoritative and then you have another whole lot of problems, such as players being able to headshot everyone in the server with a press of a button or players setting their own stats (Yes, I’m looking at you Battlefield).

Some of you may be thinking “I don’t see the problem, I’ll just create pure abstract data classes with only the common data between the client and server, with no behavioural functionality. Then subclass in each of the client and server projects”. By all means, you can do this. I just find this to be a pain because you end up maintaining three data structures.

 

Anyway, this post has gone on far longer than I thought it would so wrapping it up now. Sorry for that, I’ll try to keep future posts more concise and maybe provide code snippets to illustrate my points 🙂

If you have any questions or just want to chat netcode, just comment below or fire me a tweet at @Jargon64.

Thanks for reading! 😀

The IOs of Game Netcode #0 – Introduction

I’ve done a lot of coding over the years and learnt a substantial amount, either through reading tutorials, looking at examples, trial and error, reverse engineering, etc. So I’ve come to think I have a wealth of knowledge regarding the subject these days.

For a long time I’ve been thinking that I’d like to give back in some way. So after some thought on what I could share I’ve come to realise that nearly all of the games and other projects I have worked on have networking implemented in some form or other, and for the most part, this has all been developed from the ground up with very little library use (you can take this as good or bad, probably a bit of both). Also, it hasn’t just been the same type of networking. Infact, it ranges all the way from XML serialisation to binary serialisation, and everything in between.

Now, I don’t consider myself a guru on the subject, but I’d like to think that I have an advanced level of knowledge on it. So, I’ve decided to create a series of posts based on my experience on game netcode development and what I think is the best way of implementing game netcode as well as the issues that I ran into that led me to think this way. Hopefully, some of the game devs out there will find what I will be sharing useful and maybe beneficial to their current projects!

I’ve decided to call the series The Ins and Outs of Game Netcode or shortened The IOs of Game Netcode. Now remember, these are my opinions based on my experiences, so don’t take it as fact. I don’t think there is a single right way to do it, but I’ll be sharing what works for me and how I do it.

This introduction post will also serve as a contents page (with links) for the series. So stay tuned 🙂

Contents

Interfacing with UI #4 – Coherent UI

February 5th, 2016

This is part of a series of posts revolving around user interface design and development, the introduction and links to the other posts can be found here. Last I wrote about user interfaces I discussed the new Unity UI system and I wrote about our process of porting from Daikon Forge to it. That was a year and a half ago and a lot has changed since then. To keep things interesting we decided to move from Unity UI (yet another move?!) to Coherent UI and I’ll explain why we did it. Why Move… Again?!... (read more)

@RogueVec: And so it begins! #RebootDevelop

19/04/2018 @ 8:05am UTC

@RogueVec: We'll be at @RebootDevelop this year. We can't wait! If you want to hang out just give us a shout! #RebootDevelop2018 #GameDev

16/04/2018 @ 12:06pm UTC

@SolitudeGame: Fullscreen terminals allow you to hook into your ship's guns for fine control! Moddable gun modules, terminals and UI! https://t.co/B5N01jrA70 #GameDev

8/12/2017 @ 4:58pm UTC

@CWolf: Woo! And, now we have a cross-compiled (nix --> win64) @SolitudeGame server in our build and deploy pipeline #RV #GameDev

28/11/2017 @ 3:39pm UTC

@CWolf: CI love #GameDev #RV

21/11/2017 @ 4:06pm UTC