Merge pull request #1846 from CouleeApps/background-fix
[Torque-3d.git] / Engine / source / app / mainLoop.cpp
blobc77c335527bb24115c978e1854216a81851beb5e
1 //-----------------------------------------------------------------------------
2 // Copyright (c) 2012 GarageGames, LLC
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to
6 // deal in the Software without restriction, including without limitation the
7 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 // sell copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 // IN THE SOFTWARE.
21 //-----------------------------------------------------------------------------
23 #include "app/mainLoop.h"
24 #include "app/game.h"
26 #include "platform/platformTimer.h"
27 #include "platform/platformRedBook.h"
28 #include "platform/platformVolume.h"
29 #include "platform/platformMemory.h"
30 #include "platform/platformTimer.h"
31 #include "platform/platformNet.h"
32 #include "platform/nativeDialogs/fileDialog.h"
33 #include "platform/threads/thread.h"
35 #include "core/module.h"
36 #include "core/threadStatic.h"
37 #include "core/iTickable.h"
38 #include "core/stream/fileStream.h"
40 #include "windowManager/platformWindowMgr.h"
42 #include "core/util/journal/process.h"
43 #include "util/fpsTracker.h"
45 #include "console/debugOutputConsumer.h"
46 #include "console/consoleTypes.h"
47 #include "console/engineAPI.h"
49 #include "gfx/bitmap/gBitmap.h"
50 #include "gfx/gFont.h"
51 #include "gfx/video/videoCapture.h"
52 #include "gfx/gfxTextureManager.h"
54 #include "sim/netStringTable.h"
55 #include "sim/actionMap.h"
56 #include "sim/netInterface.h"
58 #include "util/sampler.h"
59 #include "platform/threads/threadPool.h"
61 // For the TickMs define... fix this for T2D...
62 #include "T3D/gameBase/processList.h"
64 #ifdef TORQUE_ENABLE_VFS
65 #include "platform/platformVFS.h"
66 #endif
68 #ifndef _MODULE_MANAGER_H
69 #include "module/moduleManager.h"
70 #endif
72 #ifndef _ASSET_MANAGER_H_
73 #include "assets/assetManager.h"
74 #endif
76 DITTS( F32, gTimeScale, 1.0 );
77 DITTS( U32, gTimeAdvance, 0 );
78 DITTS( U32, gFrameSkip, 0 );
80 extern S32 sgBackgroundProcessSleepTime;
81 extern S32 sgTimeManagerProcessInterval;
83 extern FPSTracker gFPS;
85 TimeManager* tm = NULL;
87 static bool gRequiresRestart = false;
89 #ifdef TORQUE_DEBUG
91 /// Temporary timer used to time startup times.
92 static PlatformTimer* gStartupTimer;
94 #endif
96 #if defined( TORQUE_MINIDUMP ) && defined( TORQUE_RELEASE )
97 StringTableEntry gMiniDumpDir;
98 StringTableEntry gMiniDumpExec;
99 StringTableEntry gMiniDumpParams;
100 StringTableEntry gMiniDumpExecDir;
101 #endif
104 namespace engineAPI
106 // This is the magic switch for deciding which interop the engine
107 // should use. It will go away when we drop the console system
108 // entirely but for now it is necessary for several behaviors that
109 // differ between the interops to decide what to do.
110 bool gUseConsoleInterop = true;
112 bool gIsInitialized = false;
117 // The following are some tricks to make the memory leak checker run after global
118 // dtors have executed by placing some code in the termination segments.
120 #if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER )
122 #ifdef TORQUE_COMPILER_VISUALC
123 # pragma data_seg( ".CRT$XTU" )
125 static void* sCheckMemBeforeTermination = &Memory::ensureAllFreed;
127 # pragma data_seg()
128 #elif defined( TORQUE_COMPILER_GCC )
130 __attribute__ ( ( destructor ) ) static void _ensureAllFreed()
132 Memory::ensureAllFreed();
135 #endif
137 #endif
139 // Process a time event and update all sub-processes
140 void processTimeEvent(S32 elapsedTime)
142 PROFILE_START(ProcessTimeEvent);
144 // If recording a video and not playinb back a journal, override the elapsedTime
145 if (VIDCAP->isRecording() && !Journal::IsPlaying())
146 elapsedTime = VIDCAP->getMsPerFrame();
148 // cap the elapsed time to one second
149 // if it's more than that we're probably in a bad catch-up situation
150 if(elapsedTime > 1024)
151 elapsedTime = 1024;
153 U32 timeDelta;
154 if(ATTS(gTimeAdvance))
155 timeDelta = ATTS(gTimeAdvance);
156 else
157 timeDelta = (U32) (elapsedTime * ATTS(gTimeScale));
159 Platform::advanceTime(elapsedTime);
161 // Don't build up more time than a single tick... this makes the sim
162 // frame rate dependent but is a useful hack for singleplayer.
163 if ( ATTS(gFrameSkip) )
164 if ( timeDelta > TickMs )
165 timeDelta = TickMs;
167 bool tickPass;
169 PROFILE_START(ServerProcess);
170 tickPass = serverProcess(timeDelta);
171 PROFILE_END();
173 PROFILE_START(ServerNetProcess);
174 // only send packets if a tick happened
175 if(tickPass)
176 GNet->processServer();
177 // Used to indicate if server was just ticked.
178 Con::setBoolVariable( "$pref::hasServerTicked", tickPass );
179 PROFILE_END();
182 PROFILE_START(SimAdvanceTime);
183 Sim::advanceTime(timeDelta);
184 PROFILE_END();
186 PROFILE_START(ClientProcess);
187 tickPass = clientProcess(timeDelta);
188 // Used to indicate if client was just ticked.
189 Con::setBoolVariable( "$pref::hasClientTicked", tickPass );
190 PROFILE_END_NAMED(ClientProcess);
192 PROFILE_START(ClientNetProcess);
193 if(tickPass)
194 GNet->processClient();
195 PROFILE_END();
197 GNet->checkTimeouts();
199 gFPS.update();
201 // Give the texture manager a chance to cleanup any
202 // textures that haven't been referenced for a bit.
203 if( GFX )
204 TEXMGR->cleanupCache( 5 );
206 PROFILE_END();
208 // Update the console time
209 Con::setFloatVariable("Sim::Time",F32(Platform::getVirtualMilliseconds()) / 1000);
212 void StandardMainLoop::init()
214 #ifdef TORQUE_DEBUG
215 gStartupTimer = PlatformTimer::create();
216 #endif
218 #ifdef TORQUE_DEBUG_GUARD
219 Memory::flagCurrentAllocs( Memory::FLAG_Global );
220 #endif
222 Platform::setMathControlStateKnown();
224 // Asserts should be created FIRST
225 PlatformAssert::create();
227 ManagedSingleton< ThreadManager >::createSingleton();
228 FrameAllocator::init(TORQUE_FRAME_SIZE); // See comments in torqueConfig.h
230 // Yell if we can't initialize the network.
231 if(!Net::init())
233 AssertISV(false, "StandardMainLoop::initCore - could not initialize networking!");
236 _StringTable::create();
238 // Set up the resource manager and get some basic file types in it.
239 Con::init();
240 Platform::initConsole();
241 NetStringTable::create();
243 // Use debug output logging on the Xbox and OSX builds
244 #if defined( _XBOX ) || defined( TORQUE_OS_MAC )
245 DebugOutputConsumer::init();
246 #endif
248 // init Filesystem first, so we can actually log errors for all components that follow
249 Platform::FS::InstallFileSystems(); // install all drives for now until we have everything using the volume stuff
250 Platform::FS::MountDefaults();
252 // Set our working directory.
253 Torque::FS::SetCwd( "game:/" );
255 // Set our working directory.
256 Platform::setCurrentDirectory( Platform::getMainDotCsDir() );
258 Processor::init();
259 Math::init();
260 Platform::init(); // platform specific initialization
261 RedBook::init();
262 Platform::initConsole();
264 ThreadPool::GlobalThreadPool::createSingleton();
266 // Initialize modules.
268 EngineModuleManager::initializeSystem();
270 // Initialise ITickable.
271 #ifdef TORQUE_TGB_ONLY
272 ITickable::init( 4 );
273 #endif
275 #ifdef TORQUE_ENABLE_VFS
276 // [tom, 10/28/2006] Load the VFS here so that it stays loaded
277 Zip::ZipArchive *vfs = openEmbeddedVFSArchive();
278 gResourceManager->addVFSRoot(vfs);
279 #endif
281 Con::addVariable("timeScale", TypeF32, &ATTS(gTimeScale), "Animation time scale.\n"
282 "@ingroup platform");
283 Con::addVariable("timeAdvance", TypeS32, &ATTS(gTimeAdvance), "The speed at which system processing time advances.\n"
284 "@ingroup platform");
285 Con::addVariable("frameSkip", TypeS32, &ATTS(gFrameSkip), "Sets the number of frames to skip while rendering the scene.\n"
286 "@ingroup platform");
288 Con::setVariable( "defaultGame", StringTable->insert("scripts") );
290 Con::addVariable( "_forceAllMainThread", TypeBool, &ThreadPool::getForceAllMainThread(), "Force all work items to execute on main thread. turns this into a single-threaded system. Primarily useful to find whether malfunctions are caused by parallel execution or not.\n"
291 "@ingroup platform" );
293 #if defined( TORQUE_MINIDUMP ) && defined( TORQUE_RELEASE )
294 Con::addVariable("MiniDump::Dir", TypeString, &gMiniDumpDir);
295 Con::addVariable("MiniDump::Exec", TypeString, &gMiniDumpExec);
296 Con::addVariable("MiniDump::Params", TypeString, &gMiniDumpParams);
297 Con::addVariable("MiniDump::ExecDir", TypeString, &gMiniDumpExecDir);
298 #endif
300 // Register the module manager.
301 ModuleDatabase.registerObject("ModuleDatabase");
303 // Register the asset database.
304 AssetDatabase.registerObject("AssetDatabase");
306 // Register the asset database as a module listener.
307 ModuleDatabase.addListener(&AssetDatabase);
309 ActionMap* globalMap = new ActionMap;
310 globalMap->registerObject("GlobalActionMap");
311 Sim::getActiveActionMapSet()->pushObject(globalMap);
313 // Do this before we init the process so that process notifiees can get the time manager
314 tm = new TimeManager;
315 tm->timeEvent.notify(&::processTimeEvent);
317 // Start up the Input Event Manager
318 INPUTMGR->start();
320 Sampler::init();
322 // Hook in for UDP notification
323 Net::getPacketReceiveEvent().notify(GNet, &NetInterface::processPacketReceiveEvent);
325 #ifdef TORQUE_DEBUG_GUARD
326 Memory::flagCurrentAllocs( Memory::FLAG_Static );
327 #endif
330 void StandardMainLoop::shutdown()
332 // Stop the Input Event Manager
333 INPUTMGR->stop();
335 delete tm;
336 preShutdown();
338 // Unregister the module database.
339 ModuleDatabase.unregisterObject();
341 // Unregister the asset database.
342 AssetDatabase.unregisterObject();
344 // Shut down modules.
346 EngineModuleManager::shutdownSystem();
348 ThreadPool::GlobalThreadPool::deleteSingleton();
350 #ifdef TORQUE_ENABLE_VFS
351 closeEmbeddedVFSArchive();
352 #endif
354 RedBook::destroy();
356 Platform::shutdown();
358 #if defined( _XBOX ) || defined( TORQUE_OS_MAC )
359 DebugOutputConsumer::destroy();
360 #endif
362 NetStringTable::destroy();
363 Con::shutdown();
365 _StringTable::destroy();
366 FrameAllocator::destroy();
367 Net::shutdown();
368 Sampler::destroy();
370 ManagedSingleton< ThreadManager >::deleteSingleton();
372 // asserts should be destroyed LAST
373 PlatformAssert::destroy();
375 #if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER )
376 Memory::validate();
377 #endif
380 void StandardMainLoop::preShutdown()
382 #ifdef TORQUE_TOOLS
383 // Tools are given a chance to do pre-quit processing
384 // - This is because for tools we like to do things such
385 // as prompting to save changes before shutting down
386 // and onExit is packaged which means we can't be sure
387 // where in the shutdown namespace chain we are when using
388 // onExit since some components of the tools may already be
389 // destroyed that may be vital to saving changes to avoid
390 // loss of work [1/5/2007 justind]
391 if( Con::isFunction("onPreExit") )
392 Con::executef( "onPreExit");
393 #endif
395 //exec the script onExit() function
396 if ( Con::isFunction( "onExit" ) )
397 Con::executef("onExit");
400 bool StandardMainLoop::handleCommandLine( S32 argc, const char **argv )
402 // Allow the window manager to process command line inputs; this is
403 // done to let web plugin functionality happen in a fairly transparent way.
404 PlatformWindowManager::get()->processCmdLineArgs(argc, argv);
406 Process::handleCommandLine( argc, argv );
408 // Set up the command line args for the console scripts...
409 Con::setIntVariable("Game::argc", argc);
410 U32 i;
411 for (i = 0; i < argc; i++)
412 Con::setVariable(avar("Game::argv%d", i), argv[i]);
414 #ifdef TORQUE_PLAYER
415 if(argc > 2 && dStricmp(argv[1], "-project") == 0)
417 char playerPath[1024];
418 Platform::makeFullPathName(argv[2], playerPath, sizeof(playerPath));
419 Platform::setCurrentDirectory(playerPath);
421 argv += 2;
422 argc -= 2;
424 // Re-locate the game:/ asset mount.
426 Torque::FS::Unmount( "game" );
427 Torque::FS::Mount( "game", Platform::FS::createNativeFS( playerPath ) );
429 #endif
431 // Executes an entry script file. This is "main.cs"
432 // by default, but any file name (with no whitespace
433 // in it) may be run if it is specified as the first
434 // command-line parameter. The script used, default
435 // or otherwise, is not compiled and is loaded here
436 // directly because the resource system restricts
437 // access to the "root" directory.
439 #ifdef TORQUE_ENABLE_VFS
440 Zip::ZipArchive *vfs = openEmbeddedVFSArchive();
441 bool useVFS = vfs != NULL;
442 #endif
444 Stream *mainCsStream = NULL;
446 // The working filestream.
447 FileStream str;
449 const char *defaultScriptName = "main.cs";
450 bool useDefaultScript = true;
452 // Check if any command-line parameters were passed (the first is just the app name).
453 if (argc > 1)
455 // If so, check if the first parameter is a file to open.
456 if ( (dStrcmp(argv[1], "") != 0 ) && (str.open(argv[1], Torque::FS::File::Read)) )
458 // If it opens, we assume it is the script to run.
459 useDefaultScript = false;
460 #ifdef TORQUE_ENABLE_VFS
461 useVFS = false;
462 #endif
463 mainCsStream = &str;
467 if (useDefaultScript)
469 bool success = false;
471 #ifdef TORQUE_ENABLE_VFS
472 if(useVFS)
473 success = (mainCsStream = vfs->openFile(defaultScriptName, Zip::ZipArchive::Read)) != NULL;
474 else
475 #endif
476 success = str.open(defaultScriptName, Torque::FS::File::Read);
478 #if defined( TORQUE_DEBUG ) && defined (TORQUE_TOOLS) && !defined(TORQUE_DEDICATED) && !defined( _XBOX )
479 if (!success)
481 OpenFileDialog ofd;
482 FileDialogData &fdd = ofd.getData();
483 fdd.mFilters = StringTable->insert("Main Entry Script (main.cs)|main.cs|");
484 fdd.mTitle = StringTable->insert("Locate Game Entry Script");
486 // Get the user's selection
487 if( !ofd.Execute() )
488 return false;
490 // Process and update CWD so we can run the selected main.cs
491 S32 pathLen = dStrlen( fdd.mFile );
492 FrameTemp<char> szPathCopy( pathLen + 1);
494 dStrcpy( szPathCopy, fdd.mFile );
495 //forwardslash( szPathCopy );
497 const char *path = dStrrchr(szPathCopy, '/');
498 if(path)
500 U32 len = path - (const char*)szPathCopy;
501 szPathCopy[len+1] = 0;
503 Platform::setCurrentDirectory(szPathCopy);
505 // Re-locate the game:/ asset mount.
507 Torque::FS::Unmount( "game" );
508 Torque::FS::Mount( "game", Platform::FS::createNativeFS( ( const char* ) szPathCopy ) );
510 success = str.open(fdd.mFile, Torque::FS::File::Read);
511 if(success)
512 defaultScriptName = fdd.mFile;
515 #endif
516 if( !success )
518 char msg[1024];
519 dSprintf(msg, sizeof(msg), "Failed to open \"%s\".", defaultScriptName);
520 Platform::AlertOK("Error", msg);
521 #ifdef TORQUE_ENABLE_VFS
522 closeEmbeddedVFSArchive();
523 #endif
525 return false;
528 #ifdef TORQUE_ENABLE_VFS
529 if(! useVFS)
530 #endif
531 mainCsStream = &str;
534 // This should rarely happen, but lets deal with
535 // it gracefully if it does.
536 if ( mainCsStream == NULL )
537 return false;
539 U32 size = mainCsStream->getStreamSize();
540 char *script = new char[size + 1];
541 mainCsStream->read(size, script);
543 #ifdef TORQUE_ENABLE_VFS
544 if(useVFS)
545 vfs->closeFile(mainCsStream);
546 else
547 #endif
548 str.close();
550 script[size] = 0;
552 char buffer[1024], *ptr;
553 Platform::makeFullPathName(useDefaultScript ? defaultScriptName : argv[1], buffer, sizeof(buffer), Platform::getCurrentDirectory());
554 ptr = dStrrchr(buffer, '/');
555 if(ptr != NULL)
556 *ptr = 0;
557 Platform::setMainDotCsDir(buffer);
558 Platform::setCurrentDirectory(buffer);
560 Con::evaluate(script, false, useDefaultScript ? defaultScriptName : argv[1]);
561 delete[] script;
563 #ifdef TORQUE_ENABLE_VFS
564 closeEmbeddedVFSArchive();
565 #endif
567 return true;
570 bool StandardMainLoop::doMainLoop()
572 #ifdef TORQUE_DEBUG
573 if( gStartupTimer )
575 Con::printf( "Started up in %.2f seconds...",
576 F32( gStartupTimer->getElapsedMs() ) / 1000.f );
577 SAFE_DELETE( gStartupTimer );
579 #endif
581 bool keepRunning = true;
582 // while(keepRunning)
584 tm->setBackgroundThreshold(mClamp(sgBackgroundProcessSleepTime, 1, 200));
585 tm->setForegroundThreshold(mClamp(sgTimeManagerProcessInterval, 1, 200));
586 // update foreground/background status
587 if(WindowManager->getFirstWindow())
589 static bool lastFocus = false;
590 bool newFocus = ( WindowManager->getFocusedWindow() != NULL );
591 if(lastFocus != newFocus)
593 #ifndef TORQUE_SHIPPING
594 Con::printf("Window focus status changed: focus: %d", newFocus);
595 if (!newFocus)
596 Con::printf(" Using background sleep time: %u", Platform::getBackgroundSleepTime());
597 #endif
599 #ifdef TORQUE_OS_MAC
600 if (newFocus)
601 WindowManager->getFirstWindow()->show();
603 #endif
604 lastFocus = newFocus;
607 // under the web plugin do not sleep the process when the child window loses focus as this will cripple the browser perfomance
608 if (!Platform::getWebDeployment())
609 tm->setBackground(!newFocus);
610 else
611 tm->setBackground(false);
613 else
615 tm->setBackground(false);
618 PROFILE_START(MainLoop);
619 Sampler::beginFrame();
621 if(!Process::processEvents())
622 keepRunning = false;
624 ThreadPool::processMainThreadWorkItems();
625 Sampler::endFrame();
626 PROFILE_END_NAMED(MainLoop);
629 return keepRunning;
632 S32 StandardMainLoop::getReturnStatus()
634 return Process::getReturnStatus();
637 void StandardMainLoop::setRestart(bool restart )
639 gRequiresRestart = restart;
642 bool StandardMainLoop::requiresRestart()
644 return gRequiresRestart;