mainLoop.cpp

Engine/source/app/mainLoop.cpp

More...

Namespaces:

namespace

Public Functions

DITTS(F32 , gTimeScale , 1. 0)
DITTS(U32 , gFrameSkip , 0 )
DITTS(U32 , gTimeAdvance , 0 )
processTimeEvent(S32 elapsedTime)

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