So I’ve played quite a few XNAGS games, and quite a few that have been released to Xbox LIVE Marketplace as part of the Xbox LIVE Community Games beta.  Based on the types of issues we’ve seen and feedback in general about good development practices, we set out to create the “best practices” for creating Community Games document.  This will be a living document that is continuously updated and expanded upon as we find out what works, what doesn’t work, what is difficult, and what are some things most games should attempt to do.  There is one issue, however, that I’ve seen crop up quite often and I figure it’s worth discussing in more depth what it is, what causes it, and how to prevent it.

 

Let’s call it the “Storage API Hang of Fun”.  You’ve probably run across it.  You build your game, add save game or high score support and being a good XNAGS citizen, use the Storage APIs to get a hold of a proper StorageContainer so you can read and write the files to the correct place.  Everything seems to be working well; it’s time to beta test!  So you package up your game and hand it out to a few people to try.

 

“Your game freezes my Xbox 360”

 

What? How can this be? It was working perfectly on my machine! I thought the purpose of a console was that it is a fixed hardware platform that is easy to develop against since everyone will have the same hardware!

 

Except there is one area that each console can be slightly different: Storage devices, or more specifically, the number of devices available.  Ask the person who is having trouble if they have a HDD and a Memory Unit plugged in.  Most likely they do.  Have them remove the Memory Unit and I bet the game starts to function properly.  Problem solved!  But why did it happen? Let’s take a look into a couple of areas that have led you down this path.

 

The Guide

In XNA Game Studio 2.0 we provided access to Gamer Profiles and the Guide functionality of the Xbox 360.  The Guide is a system service that is running in the background of your game.  This is the UI that is displayed when the user hits the silver Xbox 360 button on their controllers, want to create a Friend request, view their messages, etc.  The system “hooks” our call to GraphicsDevice.Present (which gets called for you inside of Game.EndDraw).  Inside of this, it performs any input processing and drawing, which is how it can draw on top of your game without you needing to do anything.

 

The only thing you need to do is to make sure your game is running, i.e. having Update and Draw called into (and you allowing them to return) at least 15 times a second (the recommended minimum).  As long as your game isn’t blocked on the main thread, the Guide will be able to come and go as it pleases and everyone is happy.

 

Unless you block the main thread.  This is why we designed many of the Guide APIs to use the .NET asynchronous call pattern.  If the particular Guide API you are calling requires user input and expects data to return, then you have to call the Guide API such that it occurs on a background thread…this allows your main thread to continue to run, which will eventually call GraphicsDevice.Present.

 

Storage APIs

Guide.BeginShowStorageDeviceSelector is one such API.  The purpose of this API is to allow the user to choose what storage device they would like the game to read and write from.  Since the Xbox 360 can have any combination of a HDD and up to two Memory Units, a “well behaved” game will allow the user to choose whichever device that is present.

 

When we released XNA Game Studio Express, we provided a non-asynchronous Guide.ShowStorageDeviceSelector API, knowing that on the Xbox 360, calling this on the main thread would instantly lock your console.  We provided for those that would like to call this blocking version from a background thread.  In XNAGS 2.0 we introduced many more Guide APIs and we wanted to clean things up a bit and make them more consistent.  We realized that the correct way to call this API was to call it asynchronously, so we removed the blocking version and helped those unfamiliar with the pattern to move their code.  The native APIs that we call by default won’t display the selection UI if there is only one storage device available.  Instead it will return instantly with the “default” device.  In our first release, we passed in a flag that basically says “always show the UI”.  In another effort to help make this easier, we stopped passing this flag in XNAGS 2.0 so that if only one device is available, the call will return immediately.  Unfortunately the combination of these two changes (requiring you to call it asynchronously and not display the UI if there is only one device), led many people to write the following code:

 

 

IAsyncResult result = Guide.BeginShowStorageDeviceSelector(...);

 

while(result.IsCompleted == false)

{

}

 

StorageDevice device = Guide.EndShowStorageDeviceSelector(result);

 

 

 

If you call this piece of code on the main thread…what will happen?  The Begin call will succeed, we’ll then sit there and spin until the user has selected the device they want to use and the operation is “complete”.  We’ll then call the End method and get the device.  But if we’re spinning on IsCompleted, then that means GraphicsDevice.Present is not getting called, which means the user won’t be able to select a device, so that while condition will never complete.

 

The game is now deadlocked.  The user will have to reboot their Xbox 360.

 

Unless…they only have one storage device available.  In which case, IsCompleted will immediately be true, you’ll get down to the End call and everything will return normally!

 

So, a long description of a relatively simple problem and one that is easy to get yourself into.  How do we fix it?  By handling the async pattern correctly.  You have two choices and they are both valid.  One is to store the IAsyncResult as a member variable in your game and check IsCompleted inside of your Update call.  The other is to pass in a callback method to BeginShowStorageDeviceSelector.  Once the operation is completed, your method will get called, the IAsyncResult will get passed to you, and you can use it to complete the operation.  I generally prefer the callback method, but again, either pattern is equally correct.

 

Here’s a simple example of how you can call this API so that it behaves correctly regardless of the number of storage devices the user has.

 

 

public class MyGameState

{

    public StorageDevice Device;

}

 

public class MyGame : Game

{

    protected override void LoadContent()

    {

        // Some object you want to have passed into the callback

        MyGameState myGameState = new MyGameState();

 

        Guide.BeginShowStorageDeviceSelector(StorageCompletedCallback, myGameState);

    }

 

    void StorageCompletedCallback(IAsyncResult result)

    {

        // Retrieve the state you passed in

        MyGameState myGameState = (MyGameState)result.AsyncState;

 

        // Complete the call and retrieve the selected StorageDevice

        myGameState.Device = Guide.EndShowStorageDeviceSelector(result);

    }

}

 

 

Hopefully if you’ve run into this problem with your games, you now have a better understanding of what is occurring, and how to fix it.