Pumping the Guide

Pumping the Guide

  • Comments 12

I've never been happy with the design of the XNA Framework Guide methods.

I want to write code like this:

    int? button = Guide.ShowMessageBox("Save Game",
                                       "Do you want to save your progress?",
                                       new string[] { "OK", "Cancel" },
                                       0, MessageBoxIcon.None);

    if (button == 0)
    {
        StorageDevice storageDevice = Guide.ShowStorageDeviceSelector();

        if (storageDevice != null)
        {
            using (StorageContainer storageContainer = storageDevice.OpenContainer("foo"))
            {
                ...
            }
        }
    }

But there are no such simple ShowMessageBox or ShowStorageDeviceSelector methods. Even if these methods did exist, the above code would not work. Instead, I have to deal with a tangled mess of Guide.BeginShow*, IAsyncResult, and make a state machine to track when I should call Guide.EndShow*.

Why so complicated?

Back in Game Studio 1.0, we did have just such a simple storage device selector API. It worked fine as long as you called it from a background thread, but if anyone was so foolish as to call it from their main game thread, boom! The Xbox hangs.

People were justifiably surprised by this behavior. The problem is that the Guide UI is displayed over the top of the game, and relies on the game loop calling Present at regular intervals. When the game is blocked inside a ShowStorageDeviceSelector call, it is no longer cycling through the game loop, thus not calling Present, thus the Guide never gets a chance to render itself, thus the user has no way to interact with it, so the Guide call never completes.

It struck us as a bad idea for such a seemingly simple API to behave so rudely, so we spent some time trying to improve it for Game Studio 2.0. The main idea we considered was to make these blocking methods automatically call Present while the Guide was visible. Unfortunately, there are many problems with such a design:

  • Xbox Games are still visible underneath the Guide, partially faded out. If we called Present directly, outside of the normal game loop, this would not be possible, so XNA Framework games would just show black behind the Guide. Ugly.

  • What if we called Game.Draw before each Present, rather than just clearing to black?
    • This only works if Draw is truly independent of Update, and safe to call even while Update is suspended. Sure, a well written game ought to work this way, but can we really assume all games are robust enough?
    • What if the game calls ShowStorageDeviceSelector from inside their Draw method, so we are now calling Draw recursively? No sensible game would do this, but we have to worry about the not-so-sensible ones too :-)
    • To call Draw, the Guide APIs would need access to the Game instance. But Game is defined in the Microsoft.Xna.Framework.Game assembly, which is intended to be optional. We don't want to force anyone to use Game if they prefer to host the framework some other way.

  • To keep the game visible without calling Draw, what if we took a screen grab before activating the Guide, and used that as the background? 
    • The game would no longer animate behind the Guide, but who cares.
    • We'd need extra memory to store the image. Do we really want to take that space away from every game? We can't just allocate it on demand, because we mustn't get in a situation where the Guide can't come up because the game is using too much memory.
    • What if someone calls ShowStorageDeviceSelector from a background thread, in which case the game loop is still running in parallel? Extreme badness would ensure if we tried to call Present at the same time the main thread was drawing something.

  • Ok, so automatically pumping Present is a can of worms. How about if we made this explicit, and had the game pass a delegate into the blocking Guide.Show APIs? We would call this at periodic intervals, so it could do whatever drawing was appropriate.
    • Dang, there goes our nice simple API...
    • This is error prone, and only works if the developer understands enough to specify exactly the right delegate.
    • In fact, what should they specify here? They can't just use Game.Draw, as that would skip the preamble and postamble code which handles lost devices and does the final Present.
    • Oh yeah, lost devices. What should happen if the user locks the desktop while the Guide is up? What if they resize the game window, or drag it to a second monitor, or close it? These actions trigger many crazy events, and there are many things that could go wrong if the game is not expecting them.

When in doubt, play it safe and at least try to do no harm.

Game Studio 2.0 only provides async Begin/End versions of the message box, keyboard input, and storage device Guide calls. These are a pain to use, but at least they explicitly force developers to deal with the resulting state machine, rather than being surprised when crazy stuff happens and unexpected events are raised in the middle of a Guide call. No magic is better than confusing magic that only works half the time, right?

I'm still not happy about this. I keep revisiting it, looking at it from different angles, and concluding that it's still a mess. I don't like what we have now, but I also don't like any of the alternatives!

  • What about a non-blocking version of the API?

    The function returns null if the user hasn't selected anything yet and I can just go about my day.  Do you need to take control of program flow here?

    immediate mode 4tw :)

  • > What about a non-blocking version of the API?

    Wouldn't that end up looking basically the same as what we have today with the separate Begin and End calls?

  • I actually like the "screen grab" idea. You wouldn't have to force every game to use it; they still have the Begin/End pattern to follow if they want. It can be explicitly acknowledged that extra memory will be allocated for the screen grab, and it can be documented that "extreme badness" will occur if the blocking call is made in a background thread.

    As long as you provide the normal Begin/End alternative for the games that can't handle these two problems, I think the screen grab blocking call would be a great option.

  • Heck, you could just avoid the badness by throwing the almighty Code 4 if the blocking call is made in a background thread. :)

  • How about you implement the Guide functions as a component?  Then when you call a Guide function you can pass in an implementation of some interface that manages the calls to the Guide and does whatever needs to be done after the gamer has decided what they want to do.

    Alternatively you could just call the function on the Guide component and pass in a delegate that gets called containing the result.  I think that that could be quite good and you could implement it on top of your current implementation.

  • I created a static class to simplify dealing with the async nature of XNA message boxes.  It's kind of a quickly hacked up thing in response to this post, so maybe there's a better way of handling it, but it seems like it  allows you to at least pretend you're coding with blocking message boxes.

    http://www.crappycoding.com/2009/06/simplified-xna-message-boxes/

    I'm far from an expert on this kind of thing, so please feel free to poke holes in it.

  • I actually find the Guide APIs pretty straightforward to work with. I just wrap the Begin/End pairs into Futures and use cooperative threading to suspend my game logic until the Guide operations are complete. I would have had to wrap any blocking form of the APIs in a threadpool work item in order to get an asynchronous version, so I really appreciate that the XNA team decided to offer them as Begin/End pairs instead.

    The C# compiler's support for iterators is perfect for this since you can write your asynchronous logic in the form of an iterator, and 'yield return' an object to suspend execution until it's time to continue. No pesky state machines to write! :)

    Now if only creating and initializing a GamerServicesComponent didn't hang my main thread for like five seconds, I could do all my game's loading smoothly in the background while rendering frames...

  • Well, I think there are a lot of guidelines which you want to stick to. Out of the blue I would go with this one:

    - Disallow all calls to Guide from background threads.

    - int Guide.ShowMessage("Title", "Msg", {"Yea", "Nay"})

    blocks the game, renders a black screen or a provided Texture2D. This one is for all those people who want to go straight forward.

    - If you want to use the async calls to the Guide, you have to call Guide.DoEvents() within your loops (preferably in the Update part). If you use the Game class, this could be taken care of already.

    - Guide.ShowMessage("T", "M", o, delegate(int result) { if(result == 0) doIt(); })

    will then add an delegate(){callback(result);} to an internal list "to be called". Guide.DoEvents() will then invoke all the delegates on this list, so that another call to the Guide will be from the main-thread again, to allow the regular chain of events:

    "Do you want to save"? "Yes" -> "Which device"? "HDD" -> Save

    - Drop all non-blocking calls to the Guide while the Guide is still in focus.

    - While the Guide is in focus, all blocking calls will stop, wait for the guide to close, then their Guide will be opened, processed and then closed.

    I can't really see why you would _need_ to use a background thread to popup the Guide. As I hate applications stealing my focus, background-threads should not open up modal dialogs.

    However you could have a legacy option: Queue class from background threads and wait for a DoEvents from the main-thread to execute them. This would then cause a "black-screened" Guide. That would also be an alternative to the "dropping" of async calls to the Guide while the Guide is in focus.

    Now I'm sure there is much not to like about this idea. But I think it's much easier to use for the common cases and will only cause "black-screen glitches" if you do somethink awkward. But it won't crash that easily.

  • After further consideration, I realized it was rude of me to talk about my Guide techniques without sharing them. So, I wrote an article with example source code that walks people through using my technique, and explains how it works:

    http://www.luminance.org/code/2009/06/13/asynchronous-programming-for-xna-games-with-squaredtask

    Hopefully people find it useful. (My pride is going to be terribly bruised if you respond and tell me how I'm misusing the XNA/.NET APIs, but I can take it, I swear.)

  • I'm working on a custom keyboard input panel, http://keyboardcontroller.codeplex.com/, that might be extended to a full input panel

  • Why not just use a screenshot if there's enough memory, or show black if there's not?

  • When are the samples for XNA 4.0 going to be fixed?  The samples are either impossible to figure out (ok, not impossible, but if I was a newbie, then they would be).  But most of them don't work and use things like Microsoft.Xna.Framework.Storage

    Someone should either fix the samples or pull them off and put up an "Under Construction"

    Appreciate that the samples are preliminary, but these aren't just preliminary, they do not work.

Page 1 of 1 (12 items)
Leave a Comment
  • Please add 6 and 5 and type the answer here:
  • Post