0
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

0
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

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

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


Cheers,
- Wernaeh

0
165 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();

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.

0
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.

0
165 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.

0
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

0
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.

0
101 Dec 11, 2005 at 07:23

Here’s my main loop:

do
{
#if WINDOWS

// Handle Windows messages

#elif MACOS

// Handle MacOS events

#endif

TheWorldMgr->Move();
TheEffectMgr->Update();
TheWorldMgr->Update();
TheGraphicsMgr->BeginRendering();
TheWorldMgr->Render();
TheInterfaceMgr->Render();
TheGraphicsMgr->EndRendering();

} while (!quitFlag);

0
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
}

0
101 Dec 11, 2005 at 10:24

elengyel: shouldn’t this:

} while (!quitFlag);


be

} while (!TheQuitFlag);


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

0
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

0
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

0
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…

0
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 );
}
}
}

0
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.

0
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.

0
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.

0
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

0
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.

0
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

0
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

0
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:

0
165 Dec 12, 2005 at 16:23

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

0
102 Dec 12, 2005 at 16:55

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.

0
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:)

0
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.

0
165 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. :)

0
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 :)