Can I see your main Game Loop

500367065665a05a847242a39a0bc69e
0
mmakrzem 101 Dec 10, 2005 at 18:50

I’m trying to figure out how to structure my main game loop and I’m wondering how other people have done theirs. If you have one that you are willing to share, could you post your code (or pseudo code) here so that I can see it.

27 Replies

Please log in or register to post a reply.

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Dec 10, 2005 at 18:59

Hi there!

The below game loop is the one I currently use, adapted for a client / server based, predicting logics system, and for multithreaded rendering. No audio currently =(
The Tick() method is called in a loop until an exit flag is set (either on processing window messages, or because the user clicked the big shiny exit button).

void CCrescent::Tick()
{
    // Process any window messages
    GetWinSys().MessagePump();

    // Retrieve new local input data
    GetInputSys().Update();

    // Call all game logics:
    // First, let server collect all game events,
    // then, have game state react to all events,
    // finally, let server distribute resulting events
    GetServer().PreTick();
    GetGameState().Tick();
    GetServer().PostTick();

    // Have menu run if currently enabled
    GetMenu().Tick();

    // Start rendering a new frame if the renderer
    // thread currently is idle
    if (GetRenderSys().RenderStart())
    {
        // Make all game modules render as
        // they desire to in proper order
        GetIdleScene().Render();
        GetGameScene().Render();
        GetMenu().Render();

        // Finish rendering frame, reactivate rendering thread
        GetRenderSys().RenderFinish();
    }
}

Cheers,
- Wernaeh

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Dec 10, 2005 at 19:23

In Windows, I use the following:

// Main message pump
g_iFramesRendered = 0;
while (true)
{
    if (g_pRenderEnvironment && g_pRenderEnvironment->IsActive())
    {
        // Process any messages that may have arrived
        while (PeekMessage(&msg, NULL, 0, 0, true))
            DispatchMessage(&msg);
        if (msg.message == WM_QUIT)
            break;

        // Send rendering commands to GPU
        g_pRenderer->Render();

        // Perform game updates
        g_pGame->Update();

        // Finish rendering and display frame
        g_pRenderEnvironment->Display();
        ++g_iFramesRendered;
    }
    else
    {
        // Idle wait for messages
        bool cont = true;
        while (cont && GetMessage(&msg, NULL, 0, 0))
        {
            DispatchMessage(&msg);
            if (g_pRenderEnvironment && g_pRenderEnvironment->IsActive())
                cont = false;   // Go back to the PeekMessage mode
        }
        if (cont)
            // If we reach this point, we received WM_QUIT
            break;
    }
}

The reason for the IsActive() branch is so that I can Alt+Tab away from my game, it will disable the renderer, and idle wait in the background for me to return to it, at which point it re-enables the renderer. In my object hierarchy, a RenderEnvironment means an OpenGL context or a D3D device (currently I use OpenGL only, but it could be extended in future). A Game object controls logic, input, object and camera movement and so forth; a Renderer object does what you’d think. A single game may have multiple Renderers, for instance one for rendering the initial menu and one for rendering the main game. This is single-threaded.

500367065665a05a847242a39a0bc69e
0
mmakrzem 101 Dec 10, 2005 at 20:09

thanks guys for letting me see your code.

It appears that both of you only have one main update routine. I read a while ago that it is best to split up the update into two phases. One phase for updating the game physics/states and another update for the rendering.

The idea is that the physics engine can run at a different rate then the rendering engine.

I’m not quite sure if that’s such a good idea anymore… because the more that I try to separate the two, the more problems that I create.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Dec 10, 2005 at 20:40

I’m not sure what kind of updates you’d need to do that would be render-specific. Generally, you can update based on physics etc. at a higher frequency than your framerate, but whenever you render, you just render the most recent state at which the physics system has arrived.

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Dec 10, 2005 at 21:40

It appears that both of you only have one main update routine. I read a while ago that it is best to split up the update into two phases. One phase for updating the game physics/states and another update for the rendering.

Depends on where and what you exactly split up.

It is indeed a good idea - considering the next generation dual core processors - to split up rendering operations and game state code at some point, so that you use both cores.

In detail, most DX / OpenGl API calls eat up cycles like jellybears, so they feel quite comfortable on a different processor. Also, note that during the actual rendering operations, you usually just require few informations from your entities (such as meshes, textures, transforms), so this process is easy to parallelize.

Finally, splitting up makes sense if you are running fixed step physics, and don’t want rendering to interfere there.

For most simpler applications, f.e. variable time step and lower CPU / graphics load, it is in most cases simpler to just do a single game loop that does rendering and logics in a fixed sequence.

For splitting up to work, however, it is not important to have a seperate rendering state and game state (which easily becomes an utter mess, I tried this one too ;) ). It is by far more important to properly design your renderer so it is easy to parallelize.

This especially means having just few points where your logic needs to wait or test for the renderer mutex object. Adding a mutex to every single of your object’s transforms will most likely kill your performance.

In my main loop, for example, there is just a single non-blocking synchronization request in the main loop (the RenderStart() call). Then, the logics thread commits all rendering commands to the renderer, where they are queued up in an internal buffer. After that, a call to RenderFinish() restarts the rendering main thread. This thread also has a main loop, but it acts as a simple worker thread, and does not do any updating (i.e. it just handles all commands queued while RenderStart() and RenderFinish(), by translating them into API calls, and then sits waiting).

One might perhaps consider adding extrapolation to the renderer (whereas you’d again need some kind of coherent renderer side state, but you might go with the previous command setup, and provide additional infos for extrapolation, i.e. speed, acceleration). Yet, there will hardly ever be a situation where your renderer is faster than your logics system (and thus had time to extrapolate).

Another idea for splitting up would be to seperate updates of graphic objects from updates to their physical state (which however is another, unrelated problem). They did this in battlefields 1942, for example. There, they update the “skeleton” of soldiers far away less often than for nearby enemies. This leads to a few glitches, but improves the framerate. However, I guess this is one thing that may be done in the logic thread whenever it is time to commit rendering operations. For example, before committing an object, see if it should get its geometry updated. It is also certainly not the point where you’d want to go with yet another thread - simply consider how ugly it would be to have to synch three threads (renderer, logics, geo update) trying to access your object’s transforms.

I hope this helps,
and sorry for the long text ;)

Cheers,
- Wernaeh

065f0635a4c94d685583c20132a4559d
0
Ed_Mack 101 Dec 11, 2005 at 00:29

My loop is pretty simple:

Send a tick event
Tell ogre to render a frame
Sleep till next frame

I have event handlers in Ogre so that system events get sent straight through my event system. These probably only get called once frame rendering begins, but that just means the new position is in 1/60 seconds time.

2b43b428f568623be302bd98e15c3686
0
elengyel 101 Dec 11, 2005 at 07:23

Here’s my main loop:

do
{
    #if WINDOWS
    
        // Handle Windows messages
    
    #elif MACOS
    
        // Handle MacOS events
    
    #endif
    
    TheTimeMgr->TimeTask();
    TheFileMgr->FileTask();
    TheMovieMgr->MovieTask();
    TheInterfaceMgr->InterfaceTask();
    TheMessageMgr->ReceiveTask();
    TheInputMgr->InputTask();
    TheApplication->ApplicationTask();
    TheWorldMgr->Move();
    TheMessageMgr->SendTask();
    TheEffectMgr->Update();
    TheWorldMgr->Update();
    TheSoundMgr->SoundTask();
    TheGraphicsMgr->BeginRendering();
    TheWorldMgr->Render();
    TheInterfaceMgr->Render();
    TheGraphicsMgr->EndRendering();

} while (!quitFlag);
8563f7b73aeb34bb8604f1dd8f546c88
0
Mattias_Gustavsson 101 Dec 11, 2005 at 10:10

Here’s mine:

// Main loop
while (engine->IsRunning())
    {   
    engine->ExecuteFrame(); // Have the engine execute update/render
    Sleep(0); // Yield remaining timeslice to other processes
    ProcessMessages(); // Windows message handling
    }
6aa952514ff4e5439df1e9e6d337b864
0
roel 101 Dec 11, 2005 at 10:24

elengyel: shouldn’t this:

} while (!quitFlag);

be

} while (!TheQuitFlag);

instead? :D (interesting naming convention you have ;) )

4c85bbf0fe52dd82315ff56c8797ef91
0
Blaxill 101 Dec 11, 2005 at 11:06
Application = CreateApplication();

    if(Application->Init())
    {
        Application->Execute();

        Application->Exit();
    }

Application->Execute();
works in almost the same way as the kernel system from the Enginuity articles on gamedev.net

F3ff2088fe22d64396b949f149628107
0
SpreeTree 101 Dec 11, 2005 at 11:34
int CIMaryCore::RunCore(void* _window, void (*_perFrameMFCUpdate)(float _frameDelta))
{
#if MPLATFORM == MPLATFORM_WIN32
  MSG    msg;
#endif
  float    frameTime;

  // Init the mary core
  if (!InitMaryCore())
    return 0;
  // Init the game
  if (!GameInit())
    return 0;

  // Run the game loop
  while(1)
  {
#if MPLATFORM == MPLATFORM_WIN32
    // Deal with window messages if we must
    if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
    {
      if(!GetMessage (&msg, NULL, 0, 0)) 
        break;
      TranslateMessage(&msg); 
      DispatchMessage(&msg);
    }
    else
#endif
    {
      // Calculte the frame time
      frameTime = CalculateFrameStatistics();
      // Multiply it by the frame time multiplier
      frameTime *= frameTimeMultiplier;
      // Check for a change in resolution
      if (coreInfo.requestedResolution.vector[W] != 0.0f)
      {
        SuspendCore();
        ResumeCore(0, &coreInfo.requestedResolution);
        coreInfo.requestedResolution.vector[W] = 0.0f;
      }

      // If we are suspended, don't update anything
      if (!IsInterfaceSuspended())
      {
        // Update the core
        if (!UpdateMaryCore(frameTime, 1))
          break;
        // Update the game
        if (!GamePerFrameUpdate(frameTime))
          break;
        // We have updated the core now
        if (!PostCoreUpdate())
          break;
      }
    }
    // Free up processor time
    Sleep(0);
  }

  // Shutdown the game
  if (!GameShutDown())
    return 0;
  // Shutdown the core
  if (!ShutDownMaryCore())
    return 0;
        
  return 1;
}

I suppose a few things do need explaining…

IsInterfaceSuspended() checks if the system has been suspended for any reason - such things like alt-tabbing, alt-entre etc - so that the engine and game is not updated when it is not in use.

// Calculte the frame time
frameTime = CalculateFrameStatistics();
// Multiply it by the frame time multiplier
frameTime *= frameTimeMultiplier;

The above code calculates the current frame delta (for time based movement), fps, etc. The frameTimeMultiplier allows the user to specifcy a value to multiple the frame delta by, which allows them to slow the game down, or speed it up. Great for testing if everything in the game time based, rather than frame based.

Its possible to change the resolution of the game on the fly, which is what the resolution check is.

Spree

Eaa9847123e828897f960de0badf1ffa
0
Alex 101 Dec 11, 2005 at 16:15
App::main()
{

iGraphics2d *mGFX;
iDektop* mDesk;

FATAL(IDualSrv::Query_Interface(&mGFX),LSTR("Failed to obtain iGraphics2d interface.\n"));
FATAL(IDualSrv::Query_Interface(&mDesk),LSTR("Failed to obtain iDesktop interface.\n"));

mDesk->EnableInterfaceEvent(eDesktopShutDown,this);

while(OK(mDesk->Update()))
{
    mGFX->Present(true);
}

mGFX->Release();
mDesk->Release();

}

b8 App::On(eDesktopShutDown* pData)
{
    //do something
    return true;
}

Where both desktop and gfx are os dependant implementations of interfaces that provide you with events and allow you to draw etc etc…
You attach windows/controls to the desktop which are updated automatically (no need to call blah->Update() every frame).
So actual execution of rendering happens somewhere in a control. The implementation of the renderer will be provided by another interface…

E0613df9e198a3ebbad23b02b714eb42
0
ikk 101 Dec 11, 2005 at 17:10

ill post mine too :D, unfortunatelly its windows only :(

VOID CDevice3d::RenderThreadProc()
{
    
    iLastFrameTime = 0;     // this val is not local cos it may be needed to display it somewhere
    for( dwFramesTotal = 0; bWorkFlag; dwFramesTotal++ ){   //counts total number of frames processed
        DWORD dwTimeout = GetTickCount();
        //
        if( TryEnterCriticalSection( &csWorker ) ){
            //
            FrameMove();
            Render();
            //
            LeaveCriticalSection( &csWorker );
        }
        iLastFrameTime = GetTickCount() - dwTimeout;
        CONST DWORD dwMaxFrameTime = 1024/sParamsC.fps;
        DWORD dwSleeptime;
        if( iLastFrameTime >= dwMaxFrameTime ){      //bad, were dropping frames
            dwSleeptime = 0;
        }else{
            dwSleeptime = dwMaxFrameTime - iLastFrameTime;
            Sleep( dwSleeptime );
        }
    }
}
500367065665a05a847242a39a0bc69e
0
mmakrzem 101 Dec 11, 2005 at 18:59

I’m very happy to see so many people sharing their code. I’m mostly interested in determining how people get their main loop timing designed. I’m having problems with my program, because the game does not run at a constant rate. I get a large fluctuation in frame rate.

E0613df9e198a3ebbad23b02b714eb42
0
ikk 101 Dec 11, 2005 at 19:34

To get preety constant frame rate, in my case (post#14), im calculating how much time frame took by calling GetTickCount() function. GetTickCount() returns just how many miliseconds passed since system started, im subtracting current ticks from ticks before frame started to get how many miliseconds curent frame took, it is stored every time into iLastFrameTime variable.

value of desired frame rate i have in sParamsC.fps (it is in example 60) and im calculating how much time ll take one frame in that rate. if ie. sParamsC.fps is 60 then when i divide 1024 by 60 i ll get this time (curently im not sure if one seconds contain 1000 or 1024 miliseconds), result is stored into dwMaxFrameTime.

if condition iLastFrameTime >= dwMaxFrameTime is false then it means that current frame took less than desired so its needed to suspend current thread to not go to next frame too fast, its done by calling Sleep() function.

my code only handles situations when frames are rendered too fast, currently nothing is done if frame tooks more time than desired

EDIT:
here are some articles about timing in games that i found some time ago.

http://www.mvps.org/directx/indexes/game_timing.htm

2b43b428f568623be302bd98e15c3686
0
elengyel 101 Dec 11, 2005 at 20:02

@roel

elengyel: shouldn’t this:

} while (!quitFlag);

be

} while (!TheQuitFlag);

instead? :D (interesting naming convention you have ;) )

Heh. Actually, the loop that I posted was taken from a member function of a singleton instance called TheEngine, and quitFlag is a member variable. The convention is to use “The” in names of pointers to singleton instances to give some kind of explicit indication that there’s only one of them. Those are all globally-accessible pointers to the various managers in the engine.

Eaa9847123e828897f960de0badf1ffa
0
Alex 101 Dec 11, 2005 at 20:40

It’s a bit tricky to get accurate timings without using the slow QueryPerfCount.
My whole system is event based, so things are updated only if required. I have framerates of up to 10*10\^6 when nothing is changing and down to 30 when heavy stuff is going on. The resolution of the rather fast timeGetTime() is less than 6ms so you get pretty inaccurate results for things faster than 6ms…
To solve that for my fps/time per frame counters I did this:

    static u32 FrameCount=0;
    static u32 DeltaCount=0;

    FrameCount++;
    DeltaCount++;

    if(DeltaCount>=20)//we exspect worst case 20fps...so we have a min update frequency of 1s
    {
        //if more than 500ms have elapsed
        u32 CurTime=getTime();
        if(CurTime-mLastFrame>500)//if so..calc time/frame
        {
            mFrameTime=((r32)(CurTime-mLastFrame)*1000.f)/((r32)FrameCount);
            mFPS=(_UWORD)(1000000.f/mFrameTime);
            mLastFrame=CurTime;
            FrameCount=0;
        }
        else
            DeltaCount=0;//not enough time has elapsed
    }

This guaranties that a decent amount of time compared to the timer’s resolution has passed before calculating the time per frame.
The timer is not queried every frame to safe some cycles.
It doesn’t make much of a difference here but when profiling using the HighPerfCounter I use something similar to avoid calling the timer every frame (which is damn slow). Normally it is enough to profile every n-th run of a function (as you will not sample the data faster then your fps when displaying it realtime).

My 2 cents on timing…

Alex

500367065665a05a847242a39a0bc69e
0
mmakrzem 101 Dec 11, 2005 at 21:47

This is my main game loop, but I’m finding that it doesn’t work for me.

void Game::Frame() {

    //if window is in focus, run the game
    if (m_GameState == GS_PLAY) {
        m_timeNow   = GetTickCount();
        m_timeDelta = m_timeNow - m_timePrev;

        //if game hung for a long time, prevent the user from 
        //having to wait a long time for the game to catch
        //up in time.
        if (m_timeDelta > 250) {
            m_timeDelta = 250;
        }

        m_timePrev  = m_timeNow;
        m_timeAccumulated += m_timeDelta;
        while (m_timeAccumulated >= m_PHYSICS_TIMESTEP) {

            m_LastError = UpdateGame();
            if ( m_LastError != EC_NoError ) {
                m_bQuit = true;
                return;
            }

            m_timeGame      += m_PHYSICS_TIMESTEP;
            m_timeAccumulated   -= m_PHYSICS_TIMESTEP;
        }
    }

    m_timeNowRender = GetTickCount();
    if ( (m_timeNowRender - m_timePrevRender) > (DWORD)(1000 / m_iRefreshRate) ) {
        MainWindow::CountFPS();
        MainWindow::DisplayFPSinTitle(m_iNumActiveTerrain);

        m_LastError = m_pGraphicsEngine->Render(m_iNumActiveTerrain);
        if ( m_LastError != EC_NoError ) {
            m_bQuit = true;
            return;
        }

        m_timePrevRender = m_timeNowRender;
    }

    Sleep (1);  //ensures that m_timeDelta != 0

    return;
}

I think it is because the physics stuff takes a long time to process, and then I render …. I’m now trying to modify this so that I will render while I update at the same time to see if it helps to speed things up.

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Dec 12, 2005 at 04:59

Hmm I guess the problem is that - even with the fixed timestep you are using, and the delta you accumulated - if your physics are too slow to guarantee a fixed timestep on your machine, you won’t be able to do anything about it.

Think about it. If your physics already take say 30 ms in a 20 ms frame on the average, you just accumulate overwork, a little more each frame, until you have so much work that you have to wait a long time for the next frame. In this case, it doesn’t matter what you render, or where else in code you lose time, since you already missed the timeline after your physics.

This also holds true for physics that come close to the desired framerate - where then rendering breaks over your desired fixed step time.

In your case, there are just few workarounds I can suggest:

First one would be to remove the unnecessary fixed step buffering, and just have the entire physics run a bit slower on slow machines.
Another one would be to use a variable timestep, which leads to numerical robustness problems with some physics algorithms.

Finally, optimize your physics. Your program should never actually be CPU bound unless you are sitting on minimum spec hardware.

Cheers,
- Wernaeh

Eaa9847123e828897f960de0badf1ffa
0
Alex 101 Dec 12, 2005 at 13:57

I’d really run a profiler on a release build before optimizing anything…you’re chances are high that you optimize at the wrong spot…
Also unless you’re running on console only you’ll do hard to get a constant frame rate..systems are so different and often you fight with different bottomlecks depending on the hw etc…as long as things are precached (few jerks when something new is processed) and your code is happy to handle variable frame times I wouldn’t worry too much. Eventually the user can fine tune the settings to get a decent frame rate…

I don’t quite agree with wernaeh about never being cpu bound. AFAIK you are almost always cpu bound unless you have a pure gfx demo (no ai, physiks etc etc). Even with pure gfx you’re likely to get cpu bound unless you do something wrong to the hardware OR everything you render fit’s the gfx card’s vram and is static (only vertex/pixel shader processing required). Even then the cost of a dip call is huge. So unless your data requires few dips/frame (eg. less than about 100/frame in a 1ghz cpu at 30Hz) you will still be cpu limited.

Still it’s a good idea to optimize the physics and maybe decouple it from the rendering.
If you can decouple the physics then you can run it at a frequency that fits the system’s capabilities and you can run it in a spereate thread to utilize multicore or hyperthreaded systems. I don’t know how viable/complicated it is to decouple physics as I’ve never done that. So maybe I’m jsut talking rubbish :)

Alex

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Dec 12, 2005 at 14:42

Reedbeta: I see a bug in your code, specifically when WM_QUIT is not the last message in the queue :wacko:

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Dec 12, 2005 at 16:23

.oisyn: WM_QUIT is always the last message in the queue :wacko:

6f0a333c785da81d479a0f58c2ccb203
0
monjardin 102 Dec 12, 2005 at 16:55

I assume .oisyn is talking about this part:

while (PeekMessage(&msg, NULL, 0, 0, true))
    DispatchMessage(&msg);
if (msg.message == WM_QUIT)
    break;

Are you sure that WM_QUIT is always last? It seems like you should still be able to retrieve messages after a quit is posted. I know I’ve written MFC programs that had some user interaction after a quit request to clean-up and save data.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Dec 12, 2005 at 17:12

I stand corrected :)
Couldn’t find any documentation about it in the msdn library though, but WM_QUIT seems to function like a WM_PAINT (it is held in the queue until there are no more messages left)

But what if another message is posted (from another thread) just before PeekMessage returns it’s fetched WM_QUIT?

This outputs WM_USER, WM_QUIT, WM_USER, WM_USER:

#include <windows.h>
#include <iostream>

int main()
{
    PostQuitMessage(0);
    PostMessage(0, WM_USER, 0, 0);

    MSG msg;
    while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        std::cout << msg.message << std::endl;

    PostMessage(0, WM_USER, 0, 0);
    PostMessage(0, WM_USER, 0, 0);

    while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        std::cout << msg.message << std::endl;
}

So I guess it’s technically still possible to miss the WM_QUIT in your loop, albeit under specific circumstances (do I hear raceconditions? :wacko:)

500367065665a05a847242a39a0bc69e
0
mmakrzem 101 Dec 12, 2005 at 18:21

In my game loop i’ve been running some simple tests and I noticed that if I have <4000 particles on the screen then I get about 32fps. However when I bump up the # of particles beyone 4000 the fps drops very quickly to 8…. when I get to 7000particles, my game runs at 3fps.

I am now trying to re-work my game so that I don’t get such a big performance hit.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Dec 14, 2005 at 09:15

@.oisyn

So I guess it’s technically still possible to miss the WM_QUIT in your loop, albeit under specific circumstances (do I hear raceconditions? :wacko:)

At the risk of hijacking this thread completely, I hadn’t considered what could happen if multiple threads were posting to the main window’s message queue. (I did mention that my application is currently single-threaded.) Nevertheless, I stand by my code: IMHO, the WM_QUIT message should only be posted when the application is truly ready to quit, i.e. after resources have been freed, graphics contexts deleted, worker threads stopped, and the main window about to be destroyed. For instance, when the user wants to quit I call DestroyWindow() on the main window; then I do all cleanup in the message handler for WM_DESTROY, and only then (just before returning from the handler) do I post WM_QUIT. In other words, WM_QUIT is not a signal for the application to begin its shutdown process; it’s a signal that the shutdown process is done. :)

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Dec 14, 2005 at 10:52

Sure, but you’re not in control of other processes that are able to send your thread messages :wacko:. But I agree that is an unlikely situation, and the reason I pointed it out was because I didn’t know the WM_QUIT messages were held in the queue up until the last message. But personally, from what I know now, I would be defensive and put the “if(WM_QUIT)” inside the loop, the “optimization” of pulling it outside the loop (I’m not saying that was your reason) is of course quite pointless :)