A recent post on Shawn Hargreaves’ blog reminded me that I never got around to sharing my technique for interacting with the XNA Guide API and doing other asynchronous operations during my game’s execution. It’s a simple application of some of the cooperative-threading techniques I’ve blogged about previously, and makes what would otherwise be a somewhat painful exercise relatively simple in practice. So, for those who are working on XNA games or just curious, I’ll go into detail on the technique here and share the source code with you!

The basic goal here is to be able to write game code in an imperative, sequential manner, without having to deal with locks, callbacks, polling, or race conditions. You rarely achieve perfection when dealing with concurrency, but even getting within sight of perfection can be worth the effort if it means you don’t have to spend time pulling your hair out trying to reproduce a rare threading bug.

In my game, any operation that can be performed off the main thread is done without blocking the main thread. Loading content, loading files, saving games, loading games, interacting with the guide, etc. This means that I can seamlessly load things in the background while the game is running without any noticeable stuttering. Normally, you’d need to do lots of juggling to handle this correctly – locking, threads, explicit synchronization – but thanks to cooperative threading, I don’t have to deal with any of that. The techniques I describe in this post are the same basic techniques I’m using for my game. Hopefully, that means they should work for yours too (though unfortunately that means if there are bugs in them, you’ve probably just found a bug in my game… whoops.)

Unless you’re working on an XNA game that uses networking (which sadly, I am not, so I can’t speak to the difficulties there), the biggest concurrency related issues you’re likely to deal with are twofold:

  • Interacting with the XNA Guide APIs
  • Performing I/O

By the end of this article I’ll have shown you how you can tackle these problems in your XNA games without having to deal directly with threads or synchronization, in a simple, imperative manner that works equally well on the PC and XBox 360.

ss_02

The Guide APIs are almost all designed in a manner that requires use of either threads, callbacks/continuations, or polling, because they come as Begin/End pairs that require management of ordering and state beyond a single frame. This is for the most part a necessity, as detailed in the blog post I mentioned above. So instead of trying to find a workaround, the best course of action is to simplify the process of working with those APIs – if possible, without introducing new issues or restricting functionality.

I/O is a more interesting problem; even when writing desktop applications, you often perform large I/O operations that have the potential to block, and in those cases, a well-written application uses concurrency techniques – threading, callbacks, etc – to avoid ‘hanging’ and frustrating the user. However, on almost any modern desktop machine, you can typically get away with tiny I/O operations if done correctly – a good example is Firefox 3, which still regularly performs short disk operations on its UI thread, which can cause it to hang if your machine is under extreme I/O load, but works fine in almost every other situation.

The XBox 360 makes I/O particularly challenging because you can’t rely on latency and throughput characteristics that you might be used to on a desktop machine: Not only do you have to deal with the downsides inherent in hard disks, but you also have to deal with the possibility of a player using a memory card, and even worse, the possiblity of the available storage devices changing while your game is running (due to memory card insertion/removal, etc). This means that doing I/O in the main thread is pretty much a non-starter. You’re stuck: It’s concurrency time.

Luckily, cooperative threading provides a great solution for tackling both of these issues: It lets you reason about your problems in a manner you’re familiar with, and solve them by writing imperative code that still behaves correctly in tough situations, at the expense of some slight overhead and minor changes to your game code. You don’t have to build a tremendously complex state machine (since the C# compiler can be convinced to do the heavy lifting for us), and you don’t have to worry about locking and synchronization since all your code is guaranteed to run sequentially in the same thread, at the appropriate time.

Read the rest of this entry »

Tags: , , , ,