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! 🙂

Interfacing with UI #2 – Scalability & Aspect Ratios

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.

The first article in this series discussed the different libraries that exist and the pros and cons of both. In this article I’ll explain the choice of UI library we selected for our current game in development and how we tackled the scalability and aspect ratios problem of UI design and implementation.

 

Our Library Choice

Daikon Forge GUI

Based off the information discussed in the last article, we decided to go with Daikon Forge UI framework (DF-GUI) for our game. For all the good and bad, DF-GUI is a raster based library for Unity. Along with all the previously discussed advantages we felt the source code access was crucial so we could maintain ownership over our codebase. While we would have been able to obtain the source code to some of the vector based libraries the cost was prohibitively expensive. This limits us from some of the nicer vector libraries but as long as this is planned for, it isn’t a major problem.

It’s important to note that DF-GUI is being redesigned from the ground up for version 2.x. If you intend to buy DF-GUI you will want to either wait until 2.x is released, which that is risky unless you’re not on a schedule, or use another library.

The rest of this article will discuss two approaches to solving scaling and aspect ratio issues and will go into our reasons for selecting the one we did. I’ll try to keep things as generic as possible.

 

Why not use NGUI?

Since this question may pop into a few heads I’ll tackle it straight away. NGUI is a widely used UI library for Unity. It was so widely used that Unity even hired the lead / sole developer to help them create uGUI and NGUI was used as the starting code base (even though apparently it’s changed a lot since then).

Not to go too deeply into this point I feel we should touch on it at least a little. We used NGUI 2.x in a previous project spanning seven months. While it’s a powerful UI library we found we were fighting with it every step of the way. Over the past few months NGUI has been undergoing major redesigns and features for the 3.x branch. We tried an early version of the 3.x branch out and, while the changes were improvements, we felt we were going down the same road as before. Most of the examples are completely out of date and, whilst the NGUI forums are very active, the developer support is usually limited to a single line reply. Needless to say we felt it wasn’t for us and so we decided to look for alternatives and found DF-GUI. In the end, some people are very happy with NGUI so I’d recommend you do your research into it either way.

 

Why not wait for uGUI?

With Unity’s very own uGUI arriving this summer in Unity 4.6 why not wait for it? One rule of thumb is to never wait for technologies to arrive to develop on. The technology usually will not arrive when it’s meant to and when it does arrive it’ll be, or do, less than you anticipated.

 

The Problem – Scale and Aspect Ratio

Since we are using a raster based library we accept the problems previously discussed, primarily ensuring scaling and aspect ratios don’t destroy a carefully crafted UI. So the main problem breaks down into two problems.

 

Pixel Perfect Scaling & Blurring

Game UIs need to scale. Without scaling you’ll end up playing games that seem to have tiny user interfaces since they were designed for smaller resolutions than you’re currently playing at. The problem when scaling a raster based UI system is that you tend to get blurry images. This is a very similar effect to when you run a game at non-native resolution and the game text and UI is slightly blurry. The term I’m using, pixel perfect scaling, is a bit of a misnomer. An image of size 200×200 pixels will only be pixel perfect if it stays at 200×200 pixel in screen space. A nine-sliced sprite, however, can be a little more flexible when it comes to being pixel perfect. This can scale and remain sharp.

Pixel Perfect Example

 

Aspect Ratios & Stretching

Games UIs need to accommodate the main aspect ratios that exist at the time of creation and the near future. For us at the moment we’re seeing 16:9, 16:10, 4:3 and 5:4 still being used. The difference in horizontal screen space between 16:9 and 4:3 is fairly sizable and this difference can cause some big issues with UI layouts, especially if the UI should maintain a specific user experience.

 

Aspect Ratio Flexible Layouts

So to fix these two problems I did a lot of research and came to the conclusion that it’s actually hard to find information on this. There seems to be two approaches, which are to ensure the entire UI can scale and stretch or adopt a safe zone aspect ratio to allow for non-stretching (but this could be extended to support stretching too).

 

Stretchable UI

The focus of this approach is to develop the UI so that it stretches to accommodate the different aspect ratios sensibly. The layout is entirely anchor based and would be linked to screen resolution and specific UI elements. The top level UI elements, usually panels, would be anchored to other UI panels and screen edges so when the aspect ratio changes the UI would grow and shrink accordingly. If set up well, changing the size would fill up the blank space that would otherwise appear when changing from a smaller (4:3) to larger aspect ratio (16:9).

Here we hit an important consideration to take into account. Depending on the UI design, the children of those top level panels may not be intended to be stretched. Plain sprites, as opposed to nine-sliced sprites, look bad when stretched width wise only. Think of an image for an icon for instance. On the other hand, any child panels would usually be fine to stretch as these tend to be nine-sliced sprites, but again the children of these panels may not stretch well. A mixture of fixed aspect ratio and stretch support is needed.

For us, DF-GUI’s 1.x anchor system isn’t flexible enough to support this approach very well in my opinion. Trying to construct a stretchable UI lead to a lot of frustrating days trying to ensure the aspect ratios would stretch but also maintain the overall user experience. The new NGUI 3.x anchor system would help a lot in this situation as it’s more flexible but at the time of testing NGUI 3.x there were still some issues that contributed to our choice to go with DF-GUI.

In the end, from our experience, this approach requires more development effort and testing to get right than the safe zone approach below. Even then, a lot of work needs to go into ensuring the UI is well developed for each aspect ratio so there are no large empty spaces within UI panels. This would happen when stretching a UI but not having enough content to actually fill the UI with. In this case minimum and maximum sizes would help but these would have to be computed at runtime as min / max sizes are limiting when factoring in scaling and resolution sizes.

 

Safe Zone UI

The focus of this approach is to develop the UI for the smallest aspect ratio but taking into account the highest resolution. Anchors are used to edge fit UI.

To help explain the logic, below is an example of the UI running at 1280×720 resolution. The implementation consists of two UI containers. The first is always set to maintain the core 4:3 aspect ratio and is coloured green. The second is set to expand to the full resolution of the game and is coloured blue.

Aspect Ratios

To allow for the maximum use of a game resolution that is not 4:3, the core UI (green) is scaled to the maximum possible resolution whilst still maintaining the 4:3 aspect ratio (this is why in the example the UI core is 960×720). The blue UI container is always at the full resolution of the game.

To use this layout the rules are:

  • All elements must fit in the core UI container when running in 4:3 aspect ratio.
  • All elements must be created within the core UI container. This maintains a consistent scale and prevents unwanted UI stretching.
  • Any elements that need to be on the edge or corners of the screen must use anchors. Anchors will position the element correctly regardless of aspect ratio by locating the corners / edges of the blue container. Even though anchored elements may be outside the core UI container, they will still be a child of it. This maintains scale and prevents stretching.

 

So what about if you want to stretch some UI elements in this layout? You would still be able to with a UI library that supported anchors or you developed your own. In our case, we will probably develop our own unless DF-GUI 2.x introduces a more extended anchor system.

 

Scaling – Dynamic Fonts

To maintain sharp fonts in the game use of dynamic fonts are a must. Traditional fonts are bitmap based and scale badly causing blurring. You either have lots of font bitmaps of different sizes, which is needless and takes up more space, or you use dynamic fonts. With dynamic fonts, Unity uses the FreeType font rendering engine to create the font texture at runtime. This helps a lot but a dynamic font set to size 12 will still be small when shown on resolutions larger than the design time resolution. The last step to correctly scale the font is having the dynamic font size actually set as:

[the design time font size] X [scale index]

This scale index would be calculated by the design time resolution compared to the current runtime resolution.

 

Scaling – Sprite Atlases

Since we are using a raster based UI system there will be times when a image will not scale well due to it being too small or too large. In this case we will need to use different sprite atlases and swap them in depending on the current resolution. This isn’t a great solution as it involves a whole new set of images at different resolutions but if the original sprite atlases is of high enough resolution this may not be an issue for some games. Scaling down a high resolution image is always prefered than trying to scale up a low resolution one.

 

Closing

The two approaches I’ve outlined along with the surrounding techniques are almost certainly not the only approaches to scaling and aspect ratios but I found very little information on this area. These are the approaches I discovered and developed upon. Hopefully this helps some of you out there. If you have similar experiences, or have different approaches I’d love to hear your thoughts. Comment, email or grab me on Twitter at @CWolf.

Thanks for reading.

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)

@SolitudeGame: Status update: Fuel reserves low. Asteroid mining facility detected on sensors. No response to our communications. On approach station appears to be abandoned and running on emergency power only. Away mission approved. Mission objective: Search and salvage - fuel is a priority.

23/03/2022 @ 11:00am UTC

@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