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