Interview and Q&A at QCon 2008 in SF

Posted 11/20/2008 @ 04:30:00 PM by Joseph Molnar
Filed under: Programming , Web/Tech

I meant to post this earlier ... if you happen to be in the vicinity of San Francisco I’m being interviewed, in front of a live studio audience :), tomorrow at QCon San Francisco. The interview is a question and answer session regarding my experience architecting and building a scalable mobile platform (aka scanR) on .NET. The interview is being videotaped so I’ll post up a link once the video is available.

For those interested in attending in person, the interview is tomorrow at 10:45. Details can be found here.

Tales Framework for XNA: Technical Preview 1

Posted 11/06/2008 @ 08:01:00 AM by Joseph Molnar
Filed under: Programming , Tales Framework , Xbox 360 , XNA

Yes, it is true, I’m finally ready to release the first technical preview of the Tales Framework.

Summary

The Tales Framework is an extension library for XNA Game Studio 3.0 that adds hierarchical controls, input handling and easier network session handling to XNA.

The build of the framework is for Windows only. If you would like an Xbox 360 version or have any questions, please contact me at josephmolnar (at) hotmail.com.

Links

Link to version 0.56.26.0 of the framework, help file and the sample source code:

Previous blog posts that explain the features and limitations of Tales Framework version 0.56:

Sample Details

The sample is my 'real-world' test case for the framework. The sample isn’t quite what I would call production quality code yet, but it is fairly solid. As I work towards the final release of the framework I’ll be updating the sample code.

The sample is not a full working game. It is a working example of creating a menuing system and creating, find and joining a network game. In particular the sample has:

  • Full working menus for creating a new multi-player match
  • Full working menus for finding a multi-player match
  • Full working lobby
  • An example pause menu
  • A game screen (no actual game and networking isn’t 100% correct yet)

While the sample demonstrates the framework well, here are some of the more interesting places to look:

  • The pause menu shows binding a menu so input is only processed for the player that paused the menu.
  • Hitting the Start button or Enter key on the main menu demonstrates that moving a parent control moves all children controls within in.
  • The networking menus use the GameSessionHelper; you can hit the back button at any time.

Note: For the networking menus to work you must have a Games for Windows Live profile (local or network). If you don’t, when the sample starts hit the Home key and follow the instructions. This will initiate the installation of Games for Windows Live and the creation of a profile.

Tales Framework for XNA: Tech Preview 1 Limitations

Posted 10/26/2008 @ 06:00:00 PM by Joseph Molnar
Filed under: Programming , Tales Framework , Xbox 360 , XNA

I'll be releasing the framework soon so I wanted to share the preview's limitations. The items outlined below are essentially items on my improvement/to-do list. The high/med/low indicator is a priority rating; things marked high will be worked on first.

  • Layout and Rendering
    • (high) Poor use/sharing of SpriteBatches in controls .
    • (med) Use of Vector2d for size and position information. Vector2d uses floats so coordinate calculations that result in non-integer values can cause fonts to look blurred.
    • (low) No support for clipping.
    • (low) Labels do not support wrapping or multiple font types (developers can use the framework to create this ability).
    • (low) None of the layout handlers support control wrapping (developers can use the framework to create this ability).
    • (low) No support for table layout (developers can use the framework to create this ability).
  • Events, Callbacks and Input:
    • (high) Consistency between event types, delegates and input handling needs improvement.
    • (high) Spinners do not report the direction of a value change. Without this, sophisticated data validation isn’t possible.
    • (med) Only InputBinding events report the player that caused the event. Indirect events, such as MenuItem.Selected, do not report who caused the event.
    • (low) Keyboard input is currently bound to player 1 because in XNA 2.0 the USB keyboard reports its key presses across all gamepad keyboards.
    • (low) If a child is in focus, the parent isn’t considered in focus.
  • SystemDefaults:
    • (high) VisualDefaults presumes there are fonts called “Fonts/title”, “Fonts/normal”, “Fonts/small”, “Fonts/menu”.
    • (high) No support for overriding the various defaults.
  • Screen support:
    • (high) No generic support for a Screen with focusable elements; current implementation forces the use of Menus and MenuItems.
    • (high) The ScreenManager uses a push/pop mechanism for active Screens. I want to have better support for closing and not favouring pre-created Screens.
    • (med) Need additional Screen types (e.g. full-screen screens, dialogs/pop-ups).
    • (med) ScreenManager only supports a single Screen across all gamepads, instead of allowing one active Screen per gamepad.
  • Networking
    • (high) GameSession is largely a wrapper around NetworkSession and is missing some of the later's abilities. Need to reconsider what to do with this class.

Tales Framework for XNA: Network Helper

Posted 10/24/2008 @ 07:01:01 AM by Joseph Molnar
Filed under: Programming , Tales Framework , Xbox 360 , XNA

The past two Tales Framework posts discussed UI elements. In this post we will move to networking. In particular I’m going to concentrate on the class I built to aid the finding, creating and joining of network sessions.

XNA GS’s built-in networking functions provide a great consistent way to handle both local networked and Internet networked games while allowing you to choose either synchronous or asynchronous mechanisms to find, create and join sessions. What I did was create a helper class to alleviate one of the remaining tricky parts in getting a networked session going ... providing a responsive and thread-safe UI.

The menuing system of a game should be as responsive as possible, so you definitely do not want to use XNA's synchronous network find, create and join methods in direct response to user input. Even if you put the find, create and join calls in a separate thread or you use the asynchronous calls (which use separate threads when calling your callbacks) you still need to synchronize access to any data you share with the UI thread and manage and ensure you never try to create a NetworkSession and AvailableNetworkSessionCollection at the same time.

The GameSessionHelper class handles these tricky parts for you. For example, users can start a find operation and then immediately start a create operation and not have to wait between these two steps.

If you are going to use the GameSessionHelper class, these are the important steps:

  1. Subscribe to the GameSession.Helper.XXXFinished and GameSession.Helper.XXXErrored events.
  2. Call the GameSession.Helper.XXXStart methods to start an operation.
  3. In the menu’s Update or Draw method, call the GameSession.Helper.Update method. If new network data is available the call to GameSession.Helper.Update will cause either the XXXFinished or XXXErrored events to fire.
  4. When you close the menu call the GameSession.Helper.Clear method.

Here are the complete details of what happens when following the above example  and steps:

  1. User Action: User goes to the 'find network game' menu and starts looking for a network game.
    • Game Code: When the menu is created you should register for the GameSession.Helper.FindFinished and GameSession.Helper.FindErrored events. When they start looking for a network game you call GameSession.Helper.StartFind. The menu's Update method should continually call the GameSession.Helper.Update method.
    • Tales Code: The helper class asks XNA to perform a find operation.
  2. User Action: Use goes back, closing the menu before the GameSession.Helper.FindFinished or GameSession.Helper.FindErrored events fire.
    • Game Code: When the menu closes you should call GameSession.Helper.Clear. You should continue to call the GameSession.Helper.Update method.
    • Tales Code: The helper is still waiting to hear back from XNA libraries so it queues the request to clear the data.
  3. User Action: User goes to the 'create game' menu and creates a network game.
    • Game Code: When the menu is created you should register for the GameSession.Helper.CreateFinished and GameSession.Helper.CreateErrored events. When the user creates the network game call the GameSession.Helper.StartCreate. In the menu's Update method you should continually call the GameSession.Helper.Update method.
    • Tales Code: The helper has not yet heard back from the XNA framework on the initial find call so it queues up the request to do a create.
  4. User Action: User waits for the create game to start.
    • Game Code: Continue to call the GameSession.Helper.Update method.
    • Tales Code: The helper will eventually hear back from the XNA framework for the initial find request. The helper clears out the data, in this case the AvailableNetworkSessionCollection. Then the helper starts the game session create operation. Once the session is created the CreateFinished event will fire.

The above shows the user only waited during moments when they initiated a network request. The UI was fully responsive; to the user it felt like they were canceling or starting a new operation when in fact the old operation was completing in the background.

 

This is a pretty detailed look at the GameSessionHelper class. Too much code is required to provide an example in this post but the sample I'll be providing with the framework should provide a good amount of reference code.

Tales Framework for XNA: Input and Focus

Posted 10/18/2008 @ 10:00:00 PM by Joseph Molnar
Filed under: Programming , Tales Framework , Xbox 360 , XNA

In my last post I talked about controls and the control structure within the Tales Framework. In this post I will go over the input handling. The two most important ideas to understand are a) input gestures, which represent things like button presses, and b) how focus impacts input handling.

Input Gestures

When building a GameControl that takes input you need to bind a method to one or more input gestures that implement the IInputGesture interface.

The IInputGesture interface looks like this:

public interface IInputGesture {
    /// <summary>
    /// This method is used to check if we have input in a particular state.
    /// </summary>
    /// <param name="thePlayerIndex">The index of player that we are 
    /// checking input for.</param>
    /// <param name="theGameTime">The GameTime for the input update.</param>
    /// <returns>True if the expected input occurred.</returns>
    bool MatchGesture( PlayerIndex thePlayerIndex, GameTime theGameTime );
}

A gesture’s MatchGesture method simply checks some state, like a pressed button, for the specified player and if the state is true the method returns true.

For example, this is a simple gesture that detects if a button was literally just pressed:

public class ButtonDownGesture : IInputGesture {
    private readonly Buttons _buttons;

    /// <summary>
    /// Constructor taking the buttons in question.
    /// </summary>
    /// <param name="theButtons">The buttons to check to see if they
    /// were pressed.</param>
    public ButtonDownGesture( Buttons theButtons ) {
        _buttons = theButtons;
    }

    /// <summary>
    /// The buttons to check.
    /// </summary>
    public Buttons Buttons {
        get {
            return _buttons;
        }
    }

    /// <summary>
    /// This method checks to see if the configured buttons were
    /// just pressed.
    /// </summary>
    /// <param name="thePlayerIndex">The player index being checked.</param>
    /// <param name="theGameTime">The GameTime for the checking.</param>
    /// <returns>Returns true if the buttons were just pressed.</returns>
    public bool MatchGesture( 
        PlayerIndex thePlayerIndex, 
        GameTime theGameTime ) {
        
        bool returnValue = false;
        int index = ( int )thePlayerIndex;

        if( InputManager.CurrentGamePadStates[ index ].IsButtonDown( _buttons ) &&
            InputManager.PreviousGamePadStates[ index ].IsButtonUp( _buttons ) ) {
            returnValue = true;
        }

        return returnValue;
    }
}

The MatchGesture method uses the InputManager. The InputManager stores the current and previous states for gamepads and keyboards.

Developers can create their own gestures but the framework includes the critical gestures. There are simple gestures for detecting that a gamepad button or key was just pressed or just released, and there is support for more complicated gestures that only return true periodically while a user holds a button or key down.

Binding Gestures

So how do you use gestures in your controls or menus? GameControl has a protected method called BindGestures that takes a delegate and set of IInputGestures. By default the framework will automatically call the MatchGesture methods on the bound gestures and if one of the gestures indicates a match the delegate is called. Advanced framework users can also manually check gestures by using the InputManager.

This snippet of code from a sample spinner control shows the binding of gestures.

public class SampleSpinner : MenuItem {
    // <snip> 

    /// <summary>
    /// Initializes the left/right analog stick/d-pad 
    /// as the mechanism to select next/prev option.
    /// </summary>
    protected override void InitializeInput( ) {
        //base.InitializeInput( );
        BindInputGestures(
            this.HandlePrevValue,
            new IInputGesture[] {
                new ButtonDownGesture( Buttons.DPadLeft ),
                new ButtonDownGesture( Buttons.LeftThumbstickLeft )
            } );

        BindInputGestures(
            this.HandleNextValue,
            new IInputGesture[] {
                new ButtonDownGesture( Buttons.DPadRight ),
                new ButtonDownGesture( Buttons.LeftThumbstickRight )
            } );
    }   

    /// <summary>
    /// Handler for requesting the next option.
    /// </summary>
    protected void HandleNextValue( 
        GameControl theControl, 
        PlayerIndex thePlayer, 
        IInputGesture theGesture ) {
        // <snip> 
        // make it go to the next value
        // <snip> 
    }

    /// <summary>
    /// Handler for requesting the previous option.
    /// </summary>
    protected void HandlePrevValue( 
        GameControl theControl, 
        PlayerIndex thePlayer, 
        IInputGesture theGesture ) {
        // <snip> 
        // make it go to the previous value
        // <snip> 
    }
 
    // <snip> 
}

Input and Focus

Another important aspect of input, and making sure that your registered delegates are called, is understanding focus. The framework doesn’t call every single registered gesture for every single control, instead it checks the gestures for the controls that are in focus, and if a gesture isn’t matched it will traverse up through parent controls checking the registered gestures.

I said ‘checks the gestures for the controls that are in focus’. This implies more than one control can be in focus. This is true, however only one control can be in focus for each player. More details on this are discussed below in Advanced Focus.

To put a control in focus, you simply need to set its Focused property to true. Some of the high-level controls will automatically set the focus. For example, the ScreenManager will set the focus on Menus as they are made active while Menus will set the focus of MenuItems as a user moves between the menu options.

Advanced Focus

One of the advanced features for focus and input is the notion of FocusScope. While the default FocusScope will accept input from all players, you can create FocusScopes that control which players’ input will be accepted and processed. This means not only must a control be in focus, but a FocusScope must exist that allows input from the player. This is best explained with the following example.

It is common during game play that if a player hits the Start button the game will load a pause menu that pauses the game and only that same player can hit the Back button to close the pause menu. Input from other players is ignored.

This advanced ability can also be used to bind certain controls of a HUD to particular players in local multi-player games. If you want to use this ability I recommend you have a look at the FocusManager.

The above hopefully has given a good overview of input and focus handling. Additional posts regarding the framework will be coming soon.

Tales Framework for XNA: Controls

Posted 10/06/2008 @ 10:34:40 PM by Joseph Molnar
Filed under: Programming , Tales Framework , Xbox 360 , XNA

I’m finally putting together a post on the Tales Framework, the XNA library I am developing. While I haven’t had much time to work on the framework, I did complete the framework code that was holding back the preview. I’ll post the tech preview once I clean-up the sample. Until then I’ll post some details.

I thought I would start on the UI side, in particular, the idea of controls. I’m sure many first time XNA developers have experience with .NET Windows Forms, Windows Presentation Foundation (WPF) or ASP.NET development. All three use the idea of controls as the base item that represents what you see and what you interact with.

XNA doesn’t have such a concept. While XNA supports sophisticated 3D rendering and low-level 2D sprite handling, the idea of a control framework is quite useful for everything from building menu screens, to HUDs (Heads-Up-Displays), to managing UI state.

So in my attempt to dig deep into XNA I created a control structure that feels similar to Windows Forms and WPF while attempting to recognize the useful and practical differences that XNA brings.

The base class for UI controls is GameControl, which inherits from DrawableGameComponent. I have kept intact the use of Draw and Update but added a fair number of abilities:

  • Controls are hierarchical meaning they have a parent and can have children. This eases things like animation; moving a control automatically moves the children.
  • There are properties, logic and classes related to layout. This includes specifying how a control docks within the parent’s client space, how a control fills the parent’s client space, padding and layout handlers for managing how children are placed within the control’s client space.
  • There are InputBindings which map input events such as button and key presses to callback methods.
  • There are events and properties for handling focus. Ultimately, focus is managed through a FocusManager, which ties into how input is handled.

In addition to this base class, a variety of subclasses exist as starting points to ease the creation of menus and HUDs. The subclasses include:

  • Panel – Commonly used base class for controls that have children. GameControl can have children too, but Panel has logic for handling things like analyzing children locations to get the preferred size of the panel.
  • Screen – Commonly used base class for display screens, such as menus or the main game screen.
  • MenuScreen – Commonly used base class for menu screens. This class primarily adds logic on how to cycle between menu options.
  • MenuItem – Common class used as a menu. This class adds logic and events for selection handling.
  • Label – Simple control that represents text.
  • ImageBox – Simple control that represents an image.
  • NumberSpinnerControl – A control that manages the logic for creating a control that can increment/decrement numbers.
  • OptionSpinnerControl – A control that manages the logic for spinning through a set of non-numeric options.

Generally speaking the controls do not specify how they look but instead contain logic and members that manage how they are used. To manage what they look like you need to subclass. For example, the following code shows a sample number spinner. In this case the control indicates how it wants to look both when the control is in focus and not in focus:

/// <summary>
/// Sample number spinner that has a label containing text 
/// that indicates what the number represents, and another 
/// label that represents the current number.
/// </summary>
public class SampleNumberSpinnerControl : NumberSpinnerControl {
    private Label _valueLabel;
    private string _nameText;
    private Texture2D _background;
    private Color _focusBackgroundColour = new Color( 0xFF, 0xFF, 0xFF, 0x33 );

    /// <summary>
    /// Constructor taking the parameters needed for the numeric spinner.
    /// </summary>
    /// <param name="theText">User text describing the control.</param>
    /// <param name="theMinimumValue">The minimum numeric value.</param>
    /// <param name="theMaximumValue">The maximum numeric value.</param>
    /// <param name="theParent">The parent control.</param>
    public SampleNumberSpinnerControl( 
        string theText, 
        int theMinimumValue, 
        int theMaximumValue, 
        int theCurrentValue, 
        DrawableGameComponent theParent )
        : base( 
            theMinimumValue, 
            theMaximumValue, 
            theCurrentValue, 
            theParent ) {
        _nameText = theText;
    }

    /// <summary>
    /// Called to load any needed content, this implementation 
    /// creates a background texture used when the control is in focus.
    /// </summary>
    protected override void LoadContent( ) {
        base.LoadContent( );
        _background = new Texture2D( 
            this.GraphicsDevice, 
            1, 1, 1, 
            TextureUsage.None, 
            SurfaceFormat.Color );
        _background.SetData<Color>( new Color[ ] { Color.White } );
    }

    /// <summary>
    /// Called to unload loaded content, this implementation
    /// frees the background texture.
    /// </summary>
    protected override void UnloadContent( ) {
        base.UnloadContent( );
        _background.Dispose( );
    }

    /// <summary>
    /// Called to setup the control. This implementation
    /// indicates how we want the control to look..
    /// </summary>
    public override void Initialize( ) {
        base.Initialize( );

        // use horizontal flow layout where children align in the middle vertically
        HorizontalFlowLayout layout = new HorizontalFlowLayout( 
VerticalAlignment.Middle );
        // 6 pixels of padding on left and right, 2 on top and bottom.
        this.Padding = new Padding( 6, 2 );
        // indicate the control fills the parent's horizontal space
        this.Fill = new Vector2( 1.0f, 0 ); 
        this.LayoutHandler = layout;
    }

    /// <summary>
    /// Called to create children controls.
    /// </summary>
    protected override void InitializeControls( ) {
        base.InitializeControls( );

        SpriteBatch batch = new SpriteBatch( this.Parent.Game.GraphicsDevice );
        Label label = new Label( this );

        // first setup the user description label
        label.Fill = new Vector2( 0.4f, 0f );
        label.Font = SystemDefaults.VisualDefaults.MenuFont;
        label.Text = _nameText;
        label.SpriteBatch = batch;
        this.Controls.Add( label );

        // create a panel that holds arrows/label showing the numeric value
        Panel valuePanel = new Panel( this );

        valuePanel.Fill = new Vector2( 0.6f, 0f );
        this.Controls.Add( valuePanel );

        // first, left aligned, label ... a simple arrow to 
        // indicate you can press left to lower the value
        label = new Label( valuePanel );
        label.Padding = new Padding( 5, 0, 5, 0 );
        label.HorizontalDock = HorizontalAlignment.Left;
        label.Font = SystemDefaults.VisualDefaults.MenuFont;
        label.Text = "<";
        label.SpriteBatch = batch;
        valuePanel.Controls.Add( label );

        // now setup the center aligned label that holds the numeric text
        _valueLabel = new Label( valuePanel );
        _valueLabel.HorizontalDock = HorizontalAlignment.Center;
        _valueLabel.Font = SystemDefaults.VisualDefaults.MenuFont;
        _valueLabel.SpriteBatch = batch;
        _valueLabel.Color = SystemDefaults.VisualDefaults.NormalTextColor;
        _valueLabel.Text = GetValueString( );
        valuePanel.Controls.Add( _valueLabel );

        // now setup the final, right aligned, label ... a simple arrow
        // to indicate you can press right to increase the value
        label = new Label( valuePanel );
        label.Padding = new Padding( 5, 0, 5, 0 );
        label.HorizontalDock = HorizontalAlignment.Right;
        label.Font = SystemDefaults.VisualDefaults.MenuFont;
        label.Text = ">";
        label.SpriteBatch = batch;
        valuePanel.Controls.Add( label );
    }

    /// <summary>
    /// This overridden Draw paints a background if the control
    /// is in focus.
    /// </summary>
    public override void Draw( GameTime gameTime ) {
        if( this.Focused ) {
            _valueLabel.SpriteBatch.Begin( SpriteBlendMode.AlphaBlend );
            _valueLabel.SpriteBatch.Draw( 
                _background, 
                new Rectangle( 
                    ( int )this.PhysicalPosition.X, 
                    ( int )this.PhysicalPosition.Y, 
                    ( int )this.Size.X, 
                    ( int )this.Size.Y ), 
                    this._focusBackgroundColour );
            _valueLabel.SpriteBatch.End( );
        }

        base.Draw( gameTime );
    }
    
    /// <summary>
    /// OnFocusChanged is called when the focus changes. This 
    /// implementation changes the label that shows the current
    /// numeric value to use a different colour when it is in 
    /// focus than when it is not in focus.
    /// </summary>
    protected override void OnFocusedChanged( ) {
        base.OnFocusedChanged( );
        if( this.Focused ) {
            _valueLabel.Color = SystemDefaults.VisualDefaults.FocusTextColor;
        } else {
            _valueLabel.Color = SystemDefaults.VisualDefaults.NormalTextColor;
        }
    }

    /// <summary>
    /// This method is called when the numeric value has
    /// changed. This implementation sets the value 
    /// label to the value the user has changed it to.
    /// </summary>
    protected override void OnValueChanged( ) {
        _valueLabel.Text = GetValueString( );
        base.OnValueChanged( );
    }

    /// <summary>
    /// Simple method used to get the string value
    /// of the current chosen number. 
    /// </summary>
    protected string GetValueString( ) {
        return this.Value.ToString( );
    }
}

In the screenshot below you can see the sample numeric spinner in action. In fact you can see three. Two of spinners are not in focus, but the middle one is. You can see the description text aligned to left, beside it is a panel that contains the arrows (aligned to the left and right of the panel) and the numeric value (centered in the panel). The spinner that has focus has a lighter grey background and the value text is dark blue.

NumericSpinnerExample

Hopefully this has given you a good first look at the control structure for the Tales Framework. In future posts I’ll look at how input and focus work, as well as how network session handling works.

Side Notes:

  1. Some of the items outlined here are going to change prior to my 1.0 release. I’ll be outlining the items I plan on changing when I release the preview.
  2. Until the release of XNA Game Studio 3.0, the Tales Framework is built for XNA GS 2.0.
  3. If you use Windows Live Writer to write your blog entries and want something to do syntax highlighting like you see above, please contact me. Last year I wrote a configurable Live Writer plug-in that can handle most languages.

Developer Diary: XNA Extension Framework

Posted 07/21/2008 @ 12:30:08 AM by Joseph Molnar
Filed under: Developer Diary , Programming , Tales Framework , XNA

Since the 4th of July weekend I've been coding in XNA in my spare time. As is typical with me I've been putting together an extension framework. The goal of the framework is to provide some basic needs when writing XNA games. I'll release, as Microsoft calls them, a Community Technical Preview (CTP) soon.

While not exhaustive, here is what I've been doing:

  • A UI control, screen state and input management system. It is largely meant to handle things like menuing systems and heads-up displays. General features:
    • Hierarchical control structure similar to WinForms or WPF.
    • Controller-bound screen state management system.
    • Layout handling including control docking, automatic sizing and layout managers.
    • Focus manager that, unlike most UI systems, supports more than one control in focus at a time; it supports one control in focus per controller.
    • Input-mechanism that supports binding 'gestures' similar to WPF. 
    • Includes a few helpful controls, though nothing implies how a control should look; a developer has full control.
    • Developers can extend anything (e.g. they can add input gestures, layout managers, controls, etc).
  • A GameSession that replaces NetworkSession. General features:
    • Abstracted the notion of sessions so it supports both pure local or network games where the participants can be local users, network users or even bots.
    • Includes controls and screens that do not make assumptions on how something looks, but manage the lobby process. This means developers generally do not need to write the game session logic, but concentrate on how the lobby should look.

I'll post more details, including samples, once I release the CTP.

Developer Diary: XNA Threading - Locks

Posted 04/17/2007 @ 10:30:01 PM by Joseph Molnar
Filed under: Developer Diary , Programming , XNA

In my previous Developer Diary I discussed the problems that can arise from threaded development particularly as outlined in the example. In this post I discuss locks, what they are and how they can fix the problems with the example.

What are Locks?

The lock is one of the primary synchronization primitives in .NET. Locks are largely used to control access to a particular part of code (commonly called a critical section).

When a thread owns a lock no other thread is able to use the lock and execute the code protected by the lock. This results in the other threads waiting for the first thread to release its ownership. Once the first thread releases ownership one of the waiting threads will then own the lock and continue executing.

The basic way to create and use a lock is as follows:

class SomeClass {
    private object syncLock = new object( ); // create the lock in the class

   public void SomeMethod( ) {
        lock( syncLock ) { // request ownership of the lock
            // protected code here
        }                  // release ownership of lock
    }
}

The variable syncLock can actually be of any reference type. This means primitive and value types (such as int, float, Vector3, etc) cannot be used.

Updating Our Example

So how do locks help given our example? First let's update the code from the example in the previous Developer Diary (Note: the fully updated source is at the end of this post).

First we need our class-level shared lock.

        private object enemyDataLock = new object( );

After that we need to update the code in EnemyAIThreadMethod to lock just the area that updates the data that is used in the Draw method. You want to lock as little as possible otherwise you may slow down other threads who are also trying to use the lock. For example, if the Thread.Sleep( 10 ); statement was in the lock block then we would make it more difficult for the Draw method to maintain a high frame rate.

        private void EnemyAIThreadMethod( ) {
            while( !isStopping ) {
                // lock to make sure data is updated
                // correctly and Draw doesn't get 
                // partial results
                lock( enemyDataLock ) {
                    // NOTE: this is just filler code.
                    // Real code would analyze user location and if multiple
                    // enemies perform flocking, or other behaviours.
                    enemyRotation -= 0.01f;
                    enemyVelocity += new Vector3( 1, 0, 1 );
                    enemyPosition += enemyVelocity;
                }
                // sleeping will probably be in order, but outside 
                // of the lock so we have Draw a chance
                Thread.Sleep( 10 );
            }
        }

Finally we update the Draw method. At first glance you may consider simply putting a lock around the entire foreach block to ensure that we use the same values for enemyRotation and enemyPosition for each mesh in the model. The Draw method would look like this:

        public override void Draw( GameTime gameTime ) {
            // ... draw other aspects of the scene....

            // Copy any parent transforms.
            Matrix[ ] transforms = new Matrix[ enemyModel.Bones.Count ];
            enemyModel.CopyAbsoluteBoneTransformsTo( transforms );

            // make sure we have consistent data
            lock( enemyDataLock ) {
                // draw the enemy model
                foreach( ModelMesh mesh in enemyModel.Meshes ) {
                    foreach( BasicEffect effect in mesh.Effects ) {
                        effect.EnableDefaultLighting( );
                        // ... but ensure we draw it at the right location 
                        effect.World = transforms[ mesh.ParentBone.Index ]
                            * Matrix.CreateRotationY( enemyRotation )
                            * Matrix.CreateTranslation( enemyPosition );
                        effect.View = Matrix.CreateLookAt(
                            cameraPosition,
                            Vector3.Zero,
                            Vector3.Up );
                        effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                            MathHelper.ToRadians( 45.0f ),
                            aspectRatio,
                            1.0f,
                            10000.0f );
                    }
                    mesh.Draw( );
                }
            }

            // ... draw other aspects of the scene....
            
            base.Draw( gameTime );
        }

That is a lot of code to lock. Remember you want to lock as little as possible. A better approach is to create local copies of the data and only lock the point where the data is copied. Our new Draw method looks as follows:

        public override void Draw( GameTime gameTime ) {
            // ... draw other aspects of the scene....

            // Copy any parent transforms.
            Matrix[ ] transforms = new Matrix[ enemyModel.Bones.Count ];
            enemyModel.CopyAbsoluteBoneTransformsTo( transforms );

            // local copies of the class data
            Vector3 enemyPositionCopy;
            float enemyRotationCopy;

            // make sure we have consistent data
            lock( enemyDataLock ) { 
                enemyPositionCopy = enemyPosition;
                enemyRotationCopy = enemyRotation;
            }
            
            // draw the enemy model
            foreach( ModelMesh mesh in enemyModel.Meshes ) {
                foreach( BasicEffect effect in mesh.Effects ) {
                    effect.EnableDefaultLighting( );
                    // ... but ensure we draw it at the right location 
                    effect.World = transforms[ mesh.ParentBone.Index ]
                        * Matrix.CreateRotationY( enemyRotationCopy )
                        * Matrix.CreateTranslation( enemyPositionCopy );
                    effect.View = Matrix.CreateLookAt(
                        cameraPosition,
                        Vector3.Zero,
                        Vector3.Up );
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                        MathHelper.ToRadians( 45.0f ),
                        aspectRatio,
                        1.0f,
                        10000.0f );
                }
                mesh.Draw( );
            }

            // ... draw other aspects of the scene....

            base.Draw( gameTime );
        }
    }

This seriously cuts down on the amount of code locked allowing both threads to run more quickly. As a note, the Vector3 type is a value type (struct), instead of a reference type (class). This means the line enemyPositionCopy = enemyPosition; isn't simply assigning a reference, but making a field by field copy of the Vector3 members. This is an important distinction. If we had reference types then other code would need to be considered. I'll cover that in my next article.

Why Does It Work?

I mentioned in the previous article that our example needed updating to cover two problems.

The first problem was uncontrolled access to the data. This is fixed by the lock statements. The use of a lock prevents the Draw thread from using the enemy data while the data is being updated in the EnemyAIMethod thread and conversely the EnemyAIMethod can't update the values while the Draw method is copying them.

The second problem was getting around .NET's memory and synchronization model. The reason this isn't an issue is due to how locks work. I'll attempt a simple explanation since it is much more complicated than I'm describing. Locks explicitly indicate to .NET that there is threaded code so that certain optimizations will not occur. Some optimizations, such as the caching of data in processor and system caches, aren't an issue because a lock ensures that modified data makes it in and out of main memory.

As you may have guessed using locks incurs a performance penalty. However, it can be a small price to pay for correctness. More advanced options are available, but they are harder to code for. I'll outline one such approach in my next post.

In Summary

Locks are an important part of threading in .NET but, as with all things, they have their advantages and disadvantages.

Advantages of locks:

Disadvantages of locks:

  • Is fairly expensive to execute
  • Easier to create deadlock situations
What is a Deadlock?

It is best to discuss deadlocks when you have an example. Fortunately the XNA example code we have been using isn't subject to deadlocks, but the following piece of code is:

private object syncLockA = new object( );
private object syncLockB = new object( );

// thread one is running here
private void ThreadMethodOne( ) {
    while( !isStopping ) {
        lock( syncLockA ) {
            // do some work
            lock( syncLockB ) {
                // do some more work
            }
        }
    }
}

// thread two is running here
private void ThreadMethodTwo( ) {
    while( !isStopping ) {
        lock( syncLockB ) {
            // do some work
            lock( syncLockA ) {
                // do some more work
            }
        }
    }
}

The potential for deadlocks occur when two or more threads have two or more locks they are sharing. In the above code a deadlock can occur if we have a thread executing in ThreadMethodOne that is currently trying to acquire syncLockB after having acquired syncLockA, and we have another thread executing in ThreadMethodTwo trying to acquire syncLockA after having acquired syncLockB. These threads are now stuck forever since they are waiting for the other thread to release the additional lock needed.

That's a deadlock. Once you have multiple threads and multiple locks you need to be extra careful with your code.

Follow-on Posts

I hope this post gave you a good overview of how to use locks in .NET. In my next post I'll talk about another mechanism that can be used to handle our example, as well as some pointers for additional .NET threading details and recommendations.

 

Complete Updated Example Source Code

using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace SampleThreading {
    public class ThreadedEnemyComponent : DrawableGameComponent {
        // general members
        private ContentManager content;
        Vector3 cameraPosition = new Vector3( 0.0f, 50.0f, -5000.0f );
        float aspectRatio = 640.0f / 480.0f;

        // thread members
        private Thread enemyAIThread = null;
        private volatile bool isStopping = false;
        private object enemyDataLock = new object( );

        // enemy specific data (model, velocity, position rotation, etc).
        private Model enemyModel;
        private Vector3 enemyVelocity = Vector3.Zero;
        private Vector3 enemyPosition = Vector3.Zero;
        private float enemyRotation = 0.0f;

        /// <summary>
        /// Basic constructor taking the game the component is to be a part of.
        /// </summary>
        /// <param name="theGame">The game the component will belong to.</param>
        public ThreadedEnemyComponent( Game theGame )
            : base( theGame ) {
            content = new ContentManager( this.Game.Services );
        }

        /// <summary>
        /// Override Initialize to start our separate AI thread.
        /// </summary>
        public override void Initialize( ) {
            base.Initialize( );

            enemyAIThread = new Thread( EnemyAIThreadMethod );
            enemyAIThread.Start( );
        }

        /// <summary>
        /// Override dispose to ensure our thread is shutdown.
        /// </summary>
        /// <param name="disposing">
        /// Set to true to release manage and unmanaged resources.
        /// Set to false to release unmanaged resources.
        /// </param>
        protected override void Dispose( bool disposing ) {
            try {
                isStopping = true;
                if( disposing ) {
                    // let's shutdown our thread if it hasn't
                    // shutdown already
                    if( enemyAIThread != null ) {
                        enemyAIThread.Join( ); // wait for the to shutdown
                        enemyAIThread = null;
                    }
                }
            } finally {
                base.Dispose( disposing );
            }
        }

        protected override void LoadGraphicsContent( bool loadAllContent ) {
            if( loadAllContent ) {
                // load our enemy model
                enemyModel = this.content.Load<Model>( @"Content\Models\
enemyModel" );
            }
        }

        protected override void UnloadGraphicsContent( bool unloadAllContent ) {
            if( unloadAllContent == true ) {
                content.Unload( );
            }
        }

        /// <summary>
        /// The thread that runs the enemy AI
        /// </summary>
        private void EnemyAIThreadMethod( ) {
            while( !isStopping ) {
                // lock to make sure data is updated
                // correctly and Draw doesn't get 
                // partial results
                lock( enemyDataLock ) {
                    // NOTE: this is just filler code.
                    // Real code would analyze user location and if multiple
                    // enemies perform flocking, or other behaviours.
                    enemyRotation -= 0.01f;
                    enemyVelocity += new Vector3( 1, 0, 1 );
                    enemyPosition += enemyVelocity;
                }
                // sleeping will probably be in order, but outside 
                // of the lock so we have Draw a chance
                Thread.Sleep( 10 );
            }
        }

        /// <summary>
        /// The thread that does the drawing from frames
        /// </summary>
        public override void Draw( GameTime gameTime ) {
            // ... draw other aspects of the scene....

            // Copy any parent transforms.
            Matrix[ ] transforms = new Matrix[ enemyModel.Bones.Count ];
            enemyModel.CopyAbsoluteBoneTransformsTo( transforms );

            // local copies of the class data
            Vector3 enemyPositionCopy;
            float enemyRotationCopy;

            // make sure we have consistent data
            lock( enemyDataLock ) {
                enemyPositionCopy = enemyPosition;
                enemyRotationCopy = enemyRotation;
            }
            
            // draw the enemy model
            foreach( ModelMesh mesh in enemyModel.Meshes ) {
                foreach( BasicEffect effect in mesh.Effects ) {
                    effect.EnableDefaultLighting( );
                    // ... but ensure we draw it at the right location 
                    effect.World = transforms[ mesh.ParentBone.Index ]
                        * Matrix.CreateRotationY( enemyRotationCopy )
                        * Matrix.CreateTranslation( enemyPositionCopy );
                    effect.View = Matrix.CreateLookAt(
                        cameraPosition,
                        Vector3.Zero,
                        Vector3.Up );
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                        MathHelper.ToRadians( 45.0f ),
                        aspectRatio,
                        1.0f,
                        10000.0f );
                }
                mesh.Draw( );
            }

            // ... draw other aspects of the scene....

            base.Draw( gameTime );
        }
    }
}

Developer Diary: XNA Threading - The Problems

Posted 04/15/2007 @ 10:30:36 AM by Joseph Molnar
Filed under: Developer Diary , Programming , XNA

As an extension to my previous Developer Diary I thought it made sense to give some more background information on threading in .NET and how it may apply to XNA development. In part this is intended to provide some insight into the difficulties of threaded development. This will be a three post backgrounder.

This first post will introduce the example and discuss the problems with it. This example will be the point of reference for all three posts.

The Example

The premise of the example is that you want to put your enemy AI code in a separate thread so that Update and Draw will have as much time as possible to execute. The below example code is derived from a Microsoft example on how to draw models. I have modified the example to do AI calculations in a separate thread (there is no real AI code, just filler).

using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace SampleThreading {
    public class ThreadedEnemyComponent : DrawableGameComponent {
        // general members
        private ContentManager content;
        Vector3 cameraPosition = new Vector3( 0.0f, 50.0f, -5000.0f );
        float aspectRatio = 640.0f / 480.0f;

        // thread members
        private Thread enemyAIThread = null;
        private bool isStopping = false;

        // enemy specific data (model, velocity, position rotation, etc).
        private Model enemyModel;
        private Vector3 enemyVelocity = Vector3.Zero;
        Vector3 enemyPosition = Vector3.Zero;
        float enemyRotation = 0.0f;

        /// <summary>
        /// Basic constructor taking the game the component is to be a part of.
        /// </summary>
        /// <param name="theGame">The game the component will belong to.</param>
        public ThreadedEnemyComponent( Game theGame )
            : base( theGame ) {
            content = new ContentManager( this.Game.Services );
        }

        /// <summary>
        /// Override Initialize to start our separate AI thread.
        /// </summary>
        public override void Initialize( ) {
            base.Initialize( );

            enemyAIThread = new Thread( EnemyAIThreadMethod );
            enemyAIThread.Start( );
        }

        /// <summary>
        /// Override dispose to ensure our thread is shutdown.
        /// </summary>
        /// <param name="disposing">
        /// Set to true to release manage and unmanaged resources.
        /// Set to false to release unmanaged resources.
        /// </param>
        protected override void Dispose( bool disposing ) {
            try {
                isStopping = true;
                if( disposing ) {
                    // let's shutdown our thread if it hasn't
                    // shutdown already
                    if( enemyAIThread != null ) {
                        enemyAIThread.Join( ); // wait for the to shutdown
                        enemyAIThread = null;
                    }
                }
            } finally {
                base.Dispose( disposing );
            }
        }

        protected override void LoadGraphicsContent( bool loadAllContent ) {
            if( loadAllContent ) {
                // load our enemy model
                enemyModel = this.content.Load<Model>( @"Content\Models\
enemyModel" );
            }
        }

        protected override void UnloadGraphicsContent( bool unloadAllContent ) {
            if( unloadAllContent == true ) {
                content.Unload( );
            }
        }

        /// <summary>
        /// The thread that runs the enemy AI
        /// </summary>
        private void EnemyAIThreadMethod( ) {
            while( !isStopping ) {
                // NOTE: this is just filler code.
                // Real code would analyze user location and if multiple
                // enemies perform flocking, or other behaviours.
                enemyRotation -= 0.01f;
                enemyVelocity += new Vector3( 1, 0, 1 );
                enemyPosition += enemyVelocity;
                // sleeping will probably be in order.
                Thread.Sleep( 10 );
            }
        }

        /// <summary>
        /// The thread that does the drawing from frames
        /// </summary>
        public override void Draw( GameTime gameTime ) {
            // ... draw other aspects of the scene....

            // Copy any parent transforms.
            Matrix[ ] transforms = new Matrix[ enemyModel.Bones.Count ];
            enemyModel.CopyAbsoluteBoneTransformsTo( transforms );

            // draw the enemy model
            foreach( ModelMesh mesh in enemyModel.Meshes ) {
                foreach( BasicEffect effect in mesh.Effects ) {
                    effect.EnableDefaultLighting( );
                    // ... but ensure we draw it at the right location 
                    effect.World = transforms[ mesh.ParentBone.Index ] 
                        * Matrix.CreateRotationY( enemyRotation )
                        * Matrix.CreateTranslation( enemyPosition );
                    effect.View = Matrix.CreateLookAt( 
                        cameraPosition, 
                        Vector3.Zero, 
                        Vector3.Up );
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView( 
                        MathHelper.ToRadians( 45.0f ),
                        aspectRatio, 
                        1.0f, 
                        10000.0f );
                }
                mesh.Draw( );
            }

            // ... draw other aspects of the scene....
            
            base.Draw( gameTime );
        }
    }
}

The Problems with the Sample

The thread method EnemyAIThreadMethod does a slew of calculations while the Draw method uses the results of the calculations to display the model. If you didn't know otherwise the above code looks perfectly fine.

In fact when you run it, more often than not it will behave as you would expect. However, it won't always be right and I don't mean per running of the application. On a frame-by-frame basis you may see things behave in an unexpected fashion, such as an odd warping of the model. There are two main reasons for this.

Uncontrolled Access to Data

Since there are two separate threads using the data, in this case one reading (Draw) and one writing (EnemyAIThreadMethod), it is possible that the Draw thread will use values for enemyPosition, enemyRotation, etc, while they are changing. In other words, in a single frame for a single model some of the meshes in the model could use old values while other meshes for the same model could use newer values. This will result in some rather interesting looking (as in bad) models.

.NET Memory and Synchronization Model

Another reason for odd behaviour is related to how .NET works. .NET is essentially defined such that if nothing in a method or in a member declaration indicates there are multiple threads then .NET assumes it is single threaded code. The simple declaring and starting of a thread, as done in the above sample, isn't sufficient. Your code needs to indicate its awareness of threads.

Why? .NET attempts to optimize the code through a variety of techniques. A common optimization is to cache data in processor or system caches. For example, enemyRotation could be cached separately in the EnemyAIThreadMethod thread and the Draw thread. This means that when you change enemyRotation in the EnemyAIThreadMethod thread the Draw thread may not see the change until something triggers the caches to flush back to main memory. The reason for caching is speed; accessing a cache is much faster than accessing main memory.

Follow-on Posts

This post was just meant to introduce the typical problems encountered with threading. The follow-on posts, which I'll post over the next couple days, will cover ways to correct the problems outlined here.

Developer Diary: XNA Game Loop and Threading

Posted 04/10/2007 @ 06:30:35 AM by Joseph Molnar
Filed under: Developer Diary , Programming , Xbox 360 , XNA

In my previous Developer Diary I talk about the tools and tutorials I used to get some background on XNA. After playing with the tutorials I decided to start digging into the mechanics of what makes an XNA game. The big defining elements are the two main Game methods, Draw and Update. In the process of looking at these method I thought I would give you some insight into .NET threading.

Note: Most of my sample code will be implemented as DrawableGameComponents instead of actual Games. I found this the most convenient way to bring code into an existing project.

Main Game Loops and Threading

Draw, which is intended for drawing frames, and Update, which is intended to run game logic, are called automatically by the XNA framework. When I saw these methods I had assumed they were called via different threads. After all, game developers typically try to achieve 60 frames per second (fps) and therefore try to remove any potential contention.

I was wrong. In .NET you typically get the current thread id using Thread.CurrentThread.GetManagedThreadId. For example, in the following code I retrieve the current thread id in both the Draw and Update methods.

/// <summary>
/// Sample class for checking thread id.
/// </summary>
public class CheckThreadIdClass : DrawableGameComponent {
    /// <summary>
    /// Constructor required for the game component.
    /// </summary>
    /// <param name="theGame">The Game this component will be used by.</param>
    public CheckThreadIdClass( Game theGame )
        : base( theGame ) {
    }

    /// <summary>
    /// The method used to draw frames.
    /// </summary>
    /// <param name="gameTime">GameTime since last call.</param>
    public override void Draw( GameTime gameTime ) {
        base.Draw( gameTime );
        // get the thread that is being called by this thread.
        int currentThreadId = Thread.CurrentThread.ManagedThreadId;
    }

    /// <summary>
    /// The method used to update game location.
    /// </summary>
    /// <param name="gameTime">GameTime since last call.</param>
    public override void Update( GameTime gameTime ) {
        base.Update( gameTime );
        // get the thread that is being called by this thread.
        int currentThreadId = Thread.CurrentThread.ManagedThreadId;
    }
}

Under Windows you will get the same thread id value in both Draw and Update (probably 1). Under the 360 both methods also report the same value, but it is a large negative value, -117440492 (not sure what this value means). From here I added some code to create a new thread to see what its thread id is.

/// <summary>
/// Sample class for creating a thread.
/// </summary>
public class CreateThreadClass : DrawableGameComponent {
    private Thread extraThread = null;
    private volatile bool isStopping = false;

    /// <summary>
    /// Constructor required for the game component.
    /// </summary>
    /// <param name="theGame">The Game this component will be used by.</param>
    public CreateThreadClass( Game theGame )
        : base( theGame ) {
    }

    public override void Initialize( ) {
        base.Initialize( );
        // create and start thread
        extraThread = new Thread( ThreadMethod );
        extraThread.Start( );
    }

    /// <summary>
    /// The method used to draw frames.
    /// </summary>
    /// <param name="gameTime">GameTime since last call.</param>
    public override void Draw( GameTime gameTime ) {
        base.Draw( gameTime );
        // get the thread that is being called by this thread.
        int currentThreadId = Thread.CurrentThread.ManagedThreadId;
    }

    /// <summary>
    /// The method used to update game location.
    /// </summary>
    /// <param name="gameTime">GameTime since last call.</param>
    public override void Update( GameTime gameTime ) {
        base.Update( gameTime );
        if( GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.
Pressed ) {
            ShutDown( );
        } else {
            // get the thread that is being called by this thread.
            int currentThreadId = Thread.CurrentThread.ManagedThreadId;
        }
    }

    /// <summary>
    /// Method called when we are to shutdown the game.
    /// </summary>
    private void ShutDown( ) {
        isStopping = true;
        // wait for the thread to die
        if( this.extraThread != null ) {
            this.extraThread.Join( );
            this.extraThread = null;
        }
        // now exit
        this.Game.Exit( );
    }

    /// <summary>
    /// Sample thread method.
    /// </summary>
    private void ThreadMethod( ) {
        // get the thread that is being called by this thread.
        int currentThreadId = Thread.CurrentThread.ManagedThreadId;

        while( !isStopping ) {
        }
    }
}

On the Xbox 360 the new Thread's thread id returned a similar large negative value, -117440472. At this point I recalled that Microsoft added a special method, Thread.SetProcessorAffinity, for the 360 that allows you to associate a software thread to hardware thread (one of the six the 360 has). So I changed the ThreadMethod method to use a particular hardware thread.

Note: Thread.SetProcessorAffinity must be called within the thread that wishes to run on particular hardware threads.

/// <summary>
/// Sample thread method.
/// </summary>
private void ThreadMethod( ) {
    // get the thread that is being called by this thread.
    int currentThreadId = Thread.CurrentThread.ManagedThreadId;
#if XBOX360
    // set the processor threads to run on
    Thread.CurrentThread.SetProcessorAffinity( new int[ ] { 3 } );
    // re-get the thread id
    currentThreadId = Thread.CurrentThread.ManagedThreadId;
#endif
    while( !isStopping ) {
    }
}

The Xbox 360 returned the same negative value, -117440472. Needless to say, it is a safe assumption that Draw and Update are in the same thread.

The reason for this is simple. Having Draw and Update in the same thread is easier for novices. Writing threaded code when sharing data between threads, as would be the case if Draw and Update were in separate threads, is trickier than it may seem.

Some Threading Notes of Interest

The MSDN Library description for Thread.SetProcessorAffinity has a description of the 360's threading (see the remarks section). Of interest is how many threads are actually listed as either being reserved or partially used by the 360's system software and Dashboard. It seems there is more overhead than I recall from blogs and podcasts on this subject. Perhaps this breakdown is specific to XNA on the 360?

From a tool standpoint, there is one other item of interest. It appears that Visual C# Express does not support the thread debug window, which allows you to view, switch and freeze threads. While some may consider this a mild annoyance there are certain circumstances, like deadlock debugging, that will be difficult to debug without this feature; I would love for Microsoft to allow XNA to be installed into non-Express editions of Visual Studio 2005. Microsoft has stated this may come in a future update to the XNA tools, though most likely not in the update coming this month.

Follow-on Post

I'll continue my discussion on threading, delving into implementation options and difficulties over the next few post:

Recent Developer Diary Articles