main loop trouble

Started by
2 comments, last by Zahlman 15 years, 3 months ago
Hi, I have a problem in my main loop, the the game seems to hang for a second or so after executing a CPU intensive process deep inside the loop. I'll first show the relevant code:

 Console.Print("Main loop...\n");
 unsigned int curtime;
 unsigned int newtime;
 double delta;
 double accum = 0.0;
 curtime = SDL_GetTicks();
 while(!System.exit)
 {
  // measure time
  newtime = SDL_GetTicks();
  delta = (double)(newtime-curtime) / 1000;
  curtime = newtime;
  
  // inner main loop
  if (delta > 0.0) {
   accum += delta;
   while(accum >= TIMESTEP && !System.exit)
   {
    System.Input();
    System.Integrate(TIMESTEP);
    accum -= TIMESTEP;
   }
   System.fps = 1.0 / (float)delta;
  }
  
  // draw
  System.Draw();
 }
 Console.Print("Shutting down...\n");

This code is called in the main() function. The outer loop continuously redraws the screen, the inner loop takes care of all game routines and physics. It is done this way to ensure a fixed time step (we don't interpolate between physics updates, but that's something I may change later). Before the game loop (code not shown), the config files are loaded, display surface/ audio is initialized, the GUI is loaded etc. This works fine. Once the main loop is started, the game sits in idle in the game main menu. At some point during the runtime of the game, the user loads a level. This occurs inside the inner loop, System.Input() to be exact, as the action is triggered by a button click. The load-level routine is then started. The 'load level' function does this each time a level resource (e.g. texture or model) has been loaded from file:

 ComputeProgress(); // computes progress, how far the level has been loaded
 System.Input();    // user can cancel process by pressing ESC
 System.Draw();     // redraw screen to show progress bar updates

Once the level is fully loaded, the 'load level' routine is exited, we return to the inner main loop. We expect the loading screen to disappear and the first frame of the 3d world will be drawn. What I then observe is the following: 1) the first frame of the game is drawn instantly when the progress bar hits 100% (so far so good) 2) after the first frame is drawn (System::Draw, called in outer loop) the game 'hangs' for a few seconds (longer load time seems to cause longer hang) 3) after the second frame there seems to be a second shorter hang, but I'm not entirely sure 4) then the game continues to run smoothly from the 3rd frame on, no noticeable hangs During the hangs, the game is responsive to input, this means that System::Input() is being called. That indicates that during the 'hang', the inner main loop is called for large number of iterations, and the screen never gets redrawn until it exits the inner loop. I *think* the hang happens because the timer is thrown off by the very large time difference that occurs inside System::Input (the 'load level' routine typically takes 30 seconds or so before SDL_GetTicks is called again). The second hang, *i guess*, could be triggered by the first 'hang'. For the same reason, long time elapsed since last SDL_GetTicks, though shorter than the first. As you can tell from the source, the timer code is isolated from the rest of the code, so it cannot be influenced outside the main() function. But I can't wrap my head around the problem, I can't seem to reconstruct what exactly happens. Do you have any idea? Thanks. :)
Advertisement
It sounds like your 'delta' value is getting to be very big causing you to go round that while loop a lot. This takes a while so next frame the delta value is large too. After a few frames catching up it'll get back to normal (unless you have a slow CPU that can't keep up).

The standard solution to this is pick a maximum delta time, and clamp it. This means that if the game is running really slowly (or has frozen completely) you won't get a big jump from one state to the next. 0.1 seconds is probably a reasonable limit - if the game is running below 10 fps something is wrong.

Just in case I've also added a clamp on the miniumum delta time in the code below - negative time values are invalid and should probably be ignored.

  // measure time  newtime = SDL_GetTicks();  delta = (double)(newtime-curtime) / 1000;  // Make sure delta is between 0.0 and 0.1 seconds  delta = std::max(0.0, std::min(delta, 0.1));  curtime = newtime;
Ah, I understand it now. :)

Indeed, when I clamp the delta, there's no hang. I'll leave this code in, but to correctly fix the problem, I should change the inner loop to this:

   while(accum >= TIMESTEP && !System.exit)   {    accum -= TIMESTEP;        double t1 = SDL_GetTicks();    System.Input();    double t2 = SDL_GetTicks();        if ((t2 - t1) > SOMELARGEVALUE) {     // System::Input took a lot of time, get more recent ticks     curtime = SDL_GetTicks();    )        System.Integrate(TIMESTEP);   }


This ensures that the 1st in-game main loop delta never gets so big in the first place, and ensures that the displayed framerate is correct when the first frame is drawn. :)

Thanks!
You could instead unfix the physics timestep when the system needs to catch up. (You do actually respect the time parameter for your physics update, yes?) That way, objects will keep their speed, they just won't be positioned as accurately (in addition to the framerate degradation).

And I don't see a reason to make the *input* locked to a timestep at all :)

One simple heuristic - one update of as many integer number of timesteps as will fit:

// inner main loopSystem.Input();if (delta > 0.0) {  accum += delta;  if (accum >= TIMESTEP && !System.exit) {    int steps = accum / TIMESTEP;    double time = steps * TIMESTEP;    System.Integrate(time);    accum -= time;  }  System.fps = 1.0 / (float)delta;}

This topic is closed to new replies.

Advertisement