Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

XNA stops calling Update and Draw while the game window is being resized or moved.

Why? Is there a way to prevent this behaviour?

(It causes my network code to desynchronise, because network messages aren't being pumped.)

share|improve this question

1 Answer 1

up vote 15 down vote accepted

Yes. It involves a small amount of messing with XNA's internals. I've got a demonstration of a fix in the second half of this video.

Background:

The reason this happens is because XNA suspends its game clock when Game's underlying Form is resized (or moved, the events are the same). This, in turn, is because it is driving its game loop from Application.Idle. When a window is resized, Windows does some crazy win32 message loop stuff that prevents Idle from firing.

So, if Game didn't suspend its clock, after a resize it will have accumulated an enormous amount of time that it would then have to update through in a single burst - clearly not desirable.

How to fix it:

There is a long chain of events in XNA (if you use Game, this doesn't apply to custom windows) that basically connects the resize event of the underlying Form, to Suspend and Resume methods for the game clock.

These are private, so you'll need to use reflection to unhook the events (where this is a Game):

this.host.Suspend = null;
this.host.Resume = null;

Here is the necessary incantation:

object host = typeof(Game).GetField("host", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this);
host.GetType().BaseType.GetField("Suspend", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(host, null);
host.GetType().BaseType.GetField("Resume", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(host, null);

(You could disconnect the chain elsewhere, you can use ILSpy to figure it out. But there's nothing else besides the window resize that suspends the timer - so these events are as good as any.)

Then you need to provide your own tick source, for when XNA isn't ticking from Application.Idle. I simply used a System.Windows.Forms.Timer:

timer = new Timer();
timer.Interval = (int)(this.TargetElapsedTime.TotalMilliseconds);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();

Now, timer_Tick will eventually call Game.Tick. Now that the clock isn't suspended, we're free to just keep using XNA's own timing code. Easy! Not quite: we only want our manual ticking to happen when XNA's built-in ticking doesn't happen. Here is some simple code to do that:

bool manualTick;
int manualTickCount = 0;
void timer_Tick(object sender, EventArgs e)
{
    if(manualTickCount > 2)
    {
        manualTick = true;
        this.Tick();
        manualTick = false;
    }

    manualTickCount++;
}

And then, in Update, put this code to reset the counter if XNA is ticking normally.

if(!manualTick)
    manualTickCount = 0;

Note that this ticking is considerably less accurate that XNA's. However it should remain more-or-less lined up with real time.


There is still one small flaw in this code. Win32, bless its stupid ugly face, stops essentially all messages for a few hundred milliseconds when you click the title-bar of a window, before the mouse actually moves (see that same link again). This prevents our Timer (which is message-based) from ticking.

Because we now don't suspend XNA's game clock, when our timer eventually starts ticking again, you'll get a burst of updates. And, sadly, XNA caps the amount of accumulatable time to an amount slightly lower than the length of time Windows stops messages when you click the title bar. So if a user happens to click and hold your title-bar, without moving it, you'll get a burst of updates and fall a few frames short of real time.

(But this should still be good enough for most cases. XNA's timer already sacrifices accuracy to prevent stuttering, so if you need perfect timing, you should do something else.)

share|improve this answer
2  
Nice comprehensive answer. –  Byte56 Mar 20 '13 at 3:03
    
Why exactly did you ask the question, then instantly answer it? –  Alastair Pitts Mar 25 '13 at 22:33
3  
Fair question. It's explicitly encouraged. The question UI even has an "Answer your own question" box. Originally I was going to make it an answer to this question, but that would just be an edit of an old answer, and my solution doesn't solve part of that question. This way it gets better visibility (being new and being XNA on GDSE instead of SO). And the info is a better fit for here, rather than my blog. –  Andrew Russell Mar 26 '13 at 6:37

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.