Jump to content


Can I see your main Game Loop


27 replies to this topic

#1 MarekKnows.com

    Valued Member

  • Members
  • PipPipPip
  • 190 posts
  • LocationOntario, Canada

Posted 10 December 2005 - 06:50 PM

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.
C++, 3D OpenGL and Game Programming video tutorials:
www.MarekKnows.com
Play my free games: Ghost Toast, Zing

#2 Wernaeh

    Senior Member

  • Members
  • PipPipPipPip
  • 368 posts

Posted 10 December 2005 - 06:59 PM

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

#3 Reedbeta

    DevMaster Staff

  • Administrators
  • 5310 posts
  • LocationSanta Clara, CA

Posted 10 December 2005 - 07:23 PM

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.
reedbeta.com - developer blog, OpenGL demos, and other projects

#4 MarekKnows.com

    Valued Member

  • Members
  • PipPipPip
  • 190 posts
  • LocationOntario, Canada

Posted 10 December 2005 - 08:09 PM

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.
C++, 3D OpenGL and Game Programming video tutorials:
www.MarekKnows.com
Play my free games: Ghost Toast, Zing

#5 Reedbeta

    DevMaster Staff

  • Administrators
  • 5310 posts
  • LocationSanta Clara, CA

Posted 10 December 2005 - 08:40 PM

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.
reedbeta.com - developer blog, OpenGL demos, and other projects

#6 Wernaeh

    Senior Member

  • Members
  • PipPipPipPip
  • 368 posts

Posted 10 December 2005 - 09:40 PM

Quote

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

#7 Ed Mack

    Senior Member

  • Members
  • PipPipPipPip
  • 1239 posts

Posted 11 December 2005 - 12:29 AM

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.

#8 elengyel

    Member

  • Members
  • PipPip
  • 50 posts

Posted 11 December 2005 - 07:23 AM

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



#9 Mattias Gustavsson

    Senior Member

  • Members
  • PipPipPipPip
  • 413 posts

Posted 11 December 2005 - 10:10 AM

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

	}



#10 roel

    Senior Member

  • Members
  • PipPipPipPip
  • 698 posts

Posted 11 December 2005 - 10:24 AM

elengyel: shouldn't this:

} while (!quitFlag);

be

} while (!TheQuitFlag);

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

#11 Blaxill

    Member

  • Members
  • PipPip
  • 66 posts

Posted 11 December 2005 - 11:06 AM

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

#12 SpreeTree

    Valued Member

  • Members
  • PipPipPip
  • 265 posts

Posted 11 December 2005 - 11:34 AM


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

#13 Alex

    Valued Member

  • Members
  • PipPipPip
  • 152 posts

Posted 11 December 2005 - 04:15 PM


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

#14 ikk

    Member

  • Members
  • PipPip
  • 87 posts

Posted 11 December 2005 - 05:10 PM

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

		}

	}

}



#15 MarekKnows.com

    Valued Member

  • Members
  • PipPipPip
  • 190 posts
  • LocationOntario, Canada

Posted 11 December 2005 - 06:59 PM

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.
C++, 3D OpenGL and Game Programming video tutorials:
www.MarekKnows.com
Play my free games: Ghost Toast, Zing

#16 ikk

    Member

  • Members
  • PipPip
  • 87 posts

Posted 11 December 2005 - 07:34 PM

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/...game_timing.htm

#17 elengyel

    Member

  • Members
  • PipPip
  • 50 posts

Posted 11 December 2005 - 08:02 PM

roel said:

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.

#18 Alex

    Valued Member

  • Members
  • PipPipPip
  • 152 posts

Posted 11 December 2005 - 08:40 PM

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

#19 MarekKnows.com

    Valued Member

  • Members
  • PipPipPip
  • 190 posts
  • LocationOntario, Canada

Posted 11 December 2005 - 09:47 PM

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.
C++, 3D OpenGL and Game Programming video tutorials:
www.MarekKnows.com
Play my free games: Ghost Toast, Zing

#20 Wernaeh

    Senior Member

  • Members
  • PipPipPipPip
  • 368 posts

Posted 12 December 2005 - 04:59 AM

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





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users