Can I see your main Game Loop
#1
Posted 10 December 2005 - 06:50 PM
www.MarekKnows.com
Play my free games: Ghost Toast, Zing
#2
Posted 10 December 2005 - 06:59 PM
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
Posted 10 December 2005 - 07:23 PM
// 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.
#4
Posted 10 December 2005 - 08:09 PM
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.
www.MarekKnows.com
Play my free games: Ghost Toast, Zing
#5
Posted 10 December 2005 - 08:40 PM
#6
Posted 10 December 2005 - 09:40 PM
Quote
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
Posted 11 December 2005 - 12:29 AM
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
Posted 11 December 2005 - 07:23 AM
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
Posted 11 December 2005 - 10:10 AM
// 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
Posted 11 December 2005 - 10:24 AM
} while (!quitFlag);
be
} while (!TheQuitFlag);
instead? :D (interesting naming convention you have ;) )
#11
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
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
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
Posted 11 December 2005 - 05:10 PM
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
Posted 11 December 2005 - 06:59 PM
www.MarekKnows.com
Play my free games: Ghost Toast, Zing
#16
Posted 11 December 2005 - 07:34 PM
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
Posted 11 December 2005 - 08:02 PM
roel said:
} 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
Posted 11 December 2005 - 08:40 PM
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
Posted 11 December 2005 - 09:47 PM
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.
www.MarekKnows.com
Play my free games: Ghost Toast, Zing
#20
Posted 12 December 2005 - 04:59 AM
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
2 user(s) are reading this topic
0 members, 2 guests, 0 anonymous users












