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