Engine.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. //
  2. // Copyright (c) 2008-2013 the Urho3D project.
  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 deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  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 FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "Precompiled.h"
  23. #include "Audio.h"
  24. #include "Console.h"
  25. #include "Context.h"
  26. #include "CoreEvents.h"
  27. #include "DebugHud.h"
  28. #include "Engine.h"
  29. #include "FileSystem.h"
  30. #include "Graphics.h"
  31. #include "Input.h"
  32. #include "InputEvents.h"
  33. #include "Log.h"
  34. #include "Navigation.h"
  35. #include "Network.h"
  36. #include "PackageFile.h"
  37. #include "PhysicsWorld.h"
  38. #include "ProcessUtils.h"
  39. #include "Profiler.h"
  40. #include "Renderer.h"
  41. #include "ResourceCache.h"
  42. #include "Scene.h"
  43. #include "SceneEvents.h"
  44. #include "StringUtils.h"
  45. #include "UI.h"
  46. #include "WorkQueue.h"
  47. #include "XMLFile.h"
  48. #include "DebugNew.h"
  49. #if defined(_MSC_VER) && defined(_DEBUG)
  50. // From dbgint.h
  51. #define nNoMansLandSize 4
  52. typedef struct _CrtMemBlockHeader
  53. {
  54. struct _CrtMemBlockHeader* pBlockHeaderNext;
  55. struct _CrtMemBlockHeader* pBlockHeaderPrev;
  56. char* szFileName;
  57. int nLine;
  58. size_t nDataSize;
  59. int nBlockUse;
  60. long lRequest;
  61. unsigned char gap[nNoMansLandSize];
  62. } _CrtMemBlockHeader;
  63. #endif
  64. namespace Urho3D
  65. {
  66. extern const char* logLevelPrefixes[];
  67. Engine::Engine(Context* context) :
  68. Object(context),
  69. timeStep_(0.0f),
  70. minFps_(10),
  71. #if defined(ANDROID) || defined(IOS) || defined(RASPI)
  72. maxFps_(60),
  73. maxInactiveFps_(10),
  74. pauseMinimized_(true),
  75. #else
  76. maxFps_(200),
  77. maxInactiveFps_(60),
  78. pauseMinimized_(false),
  79. #endif
  80. autoExit_(true),
  81. initialized_(false),
  82. exiting_(false),
  83. headless_(false),
  84. audioPaused_(false)
  85. {
  86. SubscribeToEvent(E_EXITREQUESTED, HANDLER(Engine, HandleExitRequested));
  87. }
  88. Engine::~Engine()
  89. {
  90. }
  91. bool Engine::Initialize(const VariantMap& parameters)
  92. {
  93. if (initialized_)
  94. return true;
  95. // Set headless mode
  96. headless_ = GetParameter(parameters, "Headless", false).GetBool();
  97. // Register subsystems and object factories
  98. RegisterSubsystems();
  99. PROFILE(InitEngine);
  100. // Start logging
  101. Log* log = GetSubsystem<Log>();
  102. if (log)
  103. {
  104. if (HasParameter(parameters, "LogLevel"))
  105. log->SetLevel(GetParameter(parameters, "LogLevel").GetInt());
  106. log->SetQuiet(GetParameter(parameters, "LogQuiet", false).GetBool());
  107. log->Open(GetParameter(parameters, "LogName", "Urho3D.log").GetString());
  108. }
  109. // Set maximally accurate low res timer
  110. GetSubsystem<Time>()->SetTimerPeriod(1);
  111. // Configure max FPS
  112. if (GetParameter(parameters, "FrameLimiter", true) == false)
  113. SetMaxFps(0);
  114. // Set amount of worker threads according to the available physical CPU cores. Using also hyperthreaded cores results in
  115. // unpredictable extra synchronization overhead. Also reserve one core for the main thread
  116. unsigned numThreads = GetParameter(parameters, "WorkerThreads", true).GetBool() ? GetNumPhysicalCPUs() - 1 : 0;
  117. if (numThreads)
  118. {
  119. GetSubsystem<WorkQueue>()->CreateThreads(numThreads);
  120. LOGINFO(ToString("Created %u worker thread%s", numThreads, numThreads > 1 ? "s" : ""));
  121. }
  122. // Add resource paths
  123. ResourceCache* cache = GetSubsystem<ResourceCache>();
  124. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  125. String exePath = fileSystem->GetProgramDir();
  126. String cwdPath = fileSystem->GetCurrentDir();
  127. Vector<String> resourcePaths = GetParameter(parameters, "ResourcePaths", "CoreData;Data").GetString().Split(';');
  128. Vector<String> resourcePackages = GetParameter(parameters, "ResourcePackages").GetString().Split(';');
  129. // Prefer to add the resource paths as packages if possible
  130. for (unsigned i = 0; i < resourcePaths.Size(); ++i)
  131. {
  132. bool success = false;
  133. String packageName = exePath + resourcePaths[i] + ".pak";
  134. // Try cwd-relative path if not found next to executable
  135. if (!fileSystem->FileExists(packageName))
  136. packageName = cwdPath + resourcePaths[i] + ".pak";
  137. if (fileSystem->FileExists(packageName))
  138. {
  139. SharedPtr<PackageFile> package(new PackageFile(context_));
  140. if (package->Open(packageName))
  141. {
  142. cache->AddPackageFile(package);
  143. success = true;
  144. }
  145. }
  146. if (!success)
  147. {
  148. String pathName = exePath + resourcePaths[i];
  149. // Try cwd-relative path if not found next to executable
  150. if (!fileSystem->DirExists(pathName))
  151. pathName = cwdPath + resourcePaths[i];
  152. if (fileSystem->DirExists(pathName))
  153. success = cache->AddResourceDir(pathName);
  154. }
  155. if (!success)
  156. {
  157. LOGERROR("Failed to add resource path " + resourcePaths[i]);
  158. return false;
  159. }
  160. }
  161. // Then add specified packages
  162. for (unsigned i = 0; i < resourcePackages.Size(); ++i)
  163. {
  164. bool success = false;
  165. String packageName = exePath + resourcePackages[i];
  166. // Try cwd-relative path if not found next to executable
  167. if (!fileSystem->FileExists(packageName))
  168. packageName = cwdPath + resourcePackages[i];
  169. if (fileSystem->FileExists(packageName))
  170. {
  171. SharedPtr<PackageFile> package(new PackageFile(context_));
  172. if (package->Open(packageName))
  173. {
  174. cache->AddPackageFile(package);
  175. success = true;
  176. }
  177. }
  178. if (!success)
  179. {
  180. LOGERROR("Failed to add resource package " + resourcePackages[i]);
  181. return false;
  182. }
  183. }
  184. // Initialize graphics & audio output
  185. if (!headless_)
  186. {
  187. Graphics* graphics = GetSubsystem<Graphics>();
  188. Renderer* renderer = GetSubsystem<Renderer>();
  189. if (HasParameter(parameters, "ExternalWindow"))
  190. graphics->SetExternalWindow(GetParameter(parameters, "ExternalWindow").GetPtr());
  191. graphics->SetForceSM2(GetParameter(parameters, "ForceSM2", false).GetBool());
  192. graphics->SetWindowTitle(GetParameter(parameters, "WindowTitle", "Urho3D").GetString());
  193. if (!graphics->SetMode(
  194. GetParameter(parameters, "WindowWidth", 0).GetInt(),
  195. GetParameter(parameters, "WindowHeight", 0).GetInt(),
  196. GetParameter(parameters, "FullScreen", true).GetBool(),
  197. GetParameter(parameters, "WindowResizable", false).GetBool(),
  198. GetParameter(parameters, "VSync", false).GetBool(),
  199. GetParameter(parameters, "TripleBuffer", false).GetBool(),
  200. GetParameter(parameters, "MultiSample", 1).GetInt()
  201. ))
  202. return false;
  203. if (HasParameter(parameters, "RenderPath"))
  204. renderer->SetDefaultRenderPath(cache->GetResource<XMLFile>(GetParameter(parameters, "RenderPath").GetString()));
  205. renderer->SetDrawShadows(GetParameter(parameters, "Shadows", true).GetBool());
  206. if (renderer->GetDrawShadows() && GetParameter(parameters, "LowQualityShadows", false).GetBool())
  207. renderer->SetShadowQuality(SHADOWQUALITY_LOW_16BIT);
  208. if (GetParameter(parameters, "Sound", true).GetBool())
  209. {
  210. GetSubsystem<Audio>()->SetMode(
  211. GetParameter(parameters, "SoundBuffer", 100).GetInt(),
  212. GetParameter(parameters, "SoundMixRate", 44100).GetInt(),
  213. GetParameter(parameters, "SoundStereo", true).GetBool(),
  214. GetParameter(parameters, "SoundInterpolation", true).GetBool()
  215. );
  216. }
  217. }
  218. // Init FPU state of main thread
  219. InitFPU();
  220. frameTimer_.Reset();
  221. initialized_ = true;
  222. return true;
  223. }
  224. void Engine::RunFrame()
  225. {
  226. assert(initialized_);
  227. // If graphics subsystem exists, but does not have a window open, assume we should exit
  228. Graphics* graphics = GetSubsystem<Graphics>();
  229. if (graphics && !graphics->IsInitialized())
  230. exiting_ = true;
  231. if (exiting_)
  232. return;
  233. // Note: there is a minimal performance cost to looking up subsystems (uses a hashmap); if they would be looked up several
  234. // times per frame it would be better to cache the pointers
  235. Time* time = GetSubsystem<Time>();
  236. Input* input = GetSubsystem<Input>();
  237. Audio* audio = GetSubsystem<Audio>();
  238. time->BeginFrame(timeStep_);
  239. // If pause when minimized -mode is in use, stop updates and audio as necessary
  240. if (pauseMinimized_ && input->IsMinimized())
  241. {
  242. if (audio->IsPlaying())
  243. {
  244. audio->Stop();
  245. audioPaused_ = true;
  246. }
  247. }
  248. else
  249. {
  250. // Only unpause when it was paused by the engine
  251. if (audioPaused_)
  252. {
  253. audio->Play();
  254. audioPaused_ = false;
  255. }
  256. Update();
  257. }
  258. Render();
  259. ApplyFrameLimit();
  260. time->EndFrame();
  261. }
  262. Console* Engine::CreateConsole()
  263. {
  264. if (headless_ || !initialized_)
  265. return 0;
  266. // Return existing console if possible
  267. Console* console = GetSubsystem<Console>();
  268. if (!console)
  269. {
  270. console = new Console(context_);
  271. context_->RegisterSubsystem(console);
  272. }
  273. return console;
  274. }
  275. DebugHud* Engine::CreateDebugHud()
  276. {
  277. if (headless_ || !initialized_)
  278. return 0;
  279. // Return existing debug HUD if possible
  280. DebugHud* debugHud = GetSubsystem<DebugHud>();
  281. if (!debugHud)
  282. {
  283. debugHud = new DebugHud(context_);
  284. context_->RegisterSubsystem(debugHud);
  285. }
  286. return debugHud;
  287. }
  288. void Engine::SetMinFps(int fps)
  289. {
  290. minFps_ = Max(fps, 0);
  291. }
  292. void Engine::SetMaxFps(int fps)
  293. {
  294. maxFps_ = Max(fps, 0);
  295. }
  296. void Engine::SetMaxInactiveFps(int fps)
  297. {
  298. maxInactiveFps_ = Max(fps, 0);
  299. }
  300. void Engine::SetPauseMinimized(bool enable)
  301. {
  302. pauseMinimized_ = enable;
  303. }
  304. void Engine::SetAutoExit(bool enable)
  305. {
  306. autoExit_ = enable;
  307. }
  308. void Engine::Exit()
  309. {
  310. Graphics* graphics = GetSubsystem<Graphics>();
  311. if (graphics)
  312. graphics->Close();
  313. exiting_ = true;
  314. }
  315. void Engine::DumpProfiler()
  316. {
  317. Profiler* profiler = GetSubsystem<Profiler>();
  318. if (profiler)
  319. LOGRAW(profiler->GetData(true, true) + "\n");
  320. }
  321. void Engine::DumpResources()
  322. {
  323. #ifdef ENABLE_LOGGING
  324. ResourceCache* cache = GetSubsystem<ResourceCache>();
  325. const HashMap<ShortStringHash, ResourceGroup>& resourceGroups = cache->GetAllResources();
  326. LOGRAW("\n");
  327. for (HashMap<ShortStringHash, ResourceGroup>::ConstIterator i = resourceGroups.Begin();
  328. i != resourceGroups.End(); ++i)
  329. {
  330. unsigned num = i->second_.resources_.Size();
  331. unsigned memoryUse = i->second_.memoryUse_;
  332. if (num)
  333. {
  334. LOGRAW("Resource type " + i->second_.resources_.Begin()->second_->GetTypeName() +
  335. ": count " + String(num) + " memory use " + String(memoryUse) + "\n");
  336. }
  337. }
  338. LOGRAW("Total memory use of all resources " + String(cache->GetTotalMemoryUse()) + "\n\n");
  339. #endif
  340. }
  341. void Engine::DumpMemory()
  342. {
  343. #ifdef ENABLE_LOGGING
  344. #if defined(_MSC_VER) && defined(_DEBUG)
  345. _CrtMemState state;
  346. _CrtMemCheckpoint(&state);
  347. _CrtMemBlockHeader* block = state.pBlockHeader;
  348. unsigned total = 0;
  349. unsigned blocks = 0;
  350. for (;;)
  351. {
  352. if (block && block->pBlockHeaderNext)
  353. block = block->pBlockHeaderNext;
  354. else
  355. break;
  356. }
  357. while (block)
  358. {
  359. if (block->nBlockUse > 0)
  360. {
  361. if (block->szFileName)
  362. LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes, file " + String(block->szFileName) + " line " + String(block->nLine) + "\n");
  363. else
  364. LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes\n");
  365. total += block->nDataSize;
  366. ++blocks;
  367. }
  368. block = block->pBlockHeaderPrev;
  369. }
  370. LOGRAW("Total allocated memory " + String(total) + " bytes in " + String(blocks) + " blocks\n\n");
  371. #else
  372. LOGRAW("DumpMemory() supported on MSVC debug mode only\n\n");
  373. #endif
  374. #endif
  375. }
  376. void Engine::Update()
  377. {
  378. PROFILE(Update);
  379. // Logic update event
  380. using namespace Update;
  381. VariantMap eventData;
  382. eventData[P_TIMESTEP] = timeStep_;
  383. SendEvent(E_UPDATE, eventData);
  384. // Logic post-update event
  385. SendEvent(E_POSTUPDATE, eventData);
  386. // Rendering update event
  387. SendEvent(E_RENDERUPDATE, eventData);
  388. // Post-render update event
  389. SendEvent(E_POSTRENDERUPDATE, eventData);
  390. }
  391. void Engine::Render()
  392. {
  393. PROFILE(Render);
  394. // Do not render if device lost
  395. Graphics* graphics = GetSubsystem<Graphics>();
  396. if (!graphics || !graphics->BeginFrame())
  397. return;
  398. GetSubsystem<Renderer>()->Render();
  399. GetSubsystem<UI>()->Render();
  400. graphics->EndFrame();
  401. }
  402. void Engine::ApplyFrameLimit()
  403. {
  404. if (!initialized_)
  405. return;
  406. int maxFps = maxFps_;
  407. Input* input = GetSubsystem<Input>();
  408. if (input && !input->HasFocus())
  409. maxFps = Min(maxInactiveFps_, maxFps);
  410. long long elapsed = 0;
  411. // Perform waiting loop if maximum FPS set
  412. if (maxFps)
  413. {
  414. PROFILE(ApplyFrameLimit);
  415. long long targetMax = 1000000LL / maxFps;
  416. for (;;)
  417. {
  418. elapsed = frameTimer_.GetUSec(false);
  419. if (elapsed >= targetMax)
  420. break;
  421. // Sleep if 1 ms or more off the frame limiting goal
  422. if (targetMax - elapsed >= 1000LL)
  423. {
  424. unsigned sleepTime = (unsigned)((targetMax - elapsed) / 1000LL);
  425. Time::Sleep(sleepTime);
  426. }
  427. }
  428. }
  429. elapsed = frameTimer_.GetUSec(true);
  430. // If FPS lower than minimum, clamp elapsed time
  431. if (minFps_)
  432. {
  433. long long targetMin = 1000000LL / minFps_;
  434. if (elapsed > targetMin)
  435. elapsed = targetMin;
  436. }
  437. timeStep_ = elapsed / 1000000.0f;
  438. }
  439. VariantMap Engine::ParseParameters(const Vector<String>& arguments)
  440. {
  441. VariantMap ret;
  442. for (unsigned i = 0; i < arguments.Size(); ++i)
  443. {
  444. if (arguments[i][0] == '-' && arguments[i].Length() >= 2)
  445. {
  446. String argument = arguments[i].Substring(1).ToLower();
  447. if (argument == "headless")
  448. ret["Headless"] = true;
  449. else if (argument.Substring(0, 3) == "log")
  450. {
  451. argument = argument.Substring(3);
  452. int logLevel = GetStringListIndex(argument.CString(), logLevelPrefixes, -1);
  453. if (logLevel != -1)
  454. ret["LogLevel"] = logLevel;
  455. }
  456. else if (argument == "nolimit")
  457. ret["FrameLimiter"] = false;
  458. else if (argument == "nosound")
  459. ret["Sound"] = false;
  460. else if (argument == "noip")
  461. ret["SoundInterpolation"] = false;
  462. else if (argument == "mono")
  463. ret["SoundStereo"] = false;
  464. else if (argument == "prepass")
  465. ret["RenderPath"] = "RenderPaths/Prepass.xml";
  466. else if (argument == "deferred")
  467. ret["RenderPath"] = "RenderPaths/Deferred.xml";
  468. else if (argument == "noshadows")
  469. ret["Shadows"] = false;
  470. else if (argument == "lqshadows")
  471. ret["LowQualityShadows"] = true;
  472. else if (argument == "nothreads")
  473. ret["WorkerThreads"] = false;
  474. else if (argument == "sm2")
  475. ret["ForceSM2"] = true;
  476. else
  477. {
  478. int value;
  479. if (argument.Length() > 1)
  480. value = ToInt(argument.Substring(1));
  481. switch (tolower(argument[0]))
  482. {
  483. case 'x':
  484. ret["WindowWidth"] = value;
  485. break;
  486. case 'y':
  487. ret["WindowHeight"] = value;
  488. break;
  489. case 'm':
  490. ret["MultiSample"] = value;
  491. break;
  492. case 'b':
  493. ret["SoundBuffer"] = value;
  494. break;
  495. case 'r':
  496. ret["SoundMixRate"] = value;
  497. break;
  498. case 'v':
  499. ret["VSync"] = true;
  500. break;
  501. case 't':
  502. ret["TripleBuffer"] = true;
  503. break;
  504. case 'w':
  505. ret["FullScreen"] = false;
  506. break;
  507. case 's':
  508. ret["WindowResizable"] = true;
  509. break;
  510. case 'q':
  511. ret["LogQuiet"] = true;
  512. break;
  513. }
  514. }
  515. }
  516. }
  517. return ret;
  518. }
  519. bool Engine::HasParameter(const VariantMap& parameters, const String& parameter)
  520. {
  521. ShortStringHash nameHash(parameter);
  522. return parameters.Find(nameHash) != parameters.End();
  523. }
  524. const Variant& Engine::GetParameter(const VariantMap& parameters, const String& parameter, const Variant& defaultValue)
  525. {
  526. ShortStringHash nameHash(parameter);
  527. VariantMap::ConstIterator i = parameters.Find(nameHash);
  528. return i != parameters.End() ? i->second_ : defaultValue;
  529. }
  530. void Engine::RegisterSubsystems()
  531. {
  532. // Register self as a subsystem
  533. context_->RegisterSubsystem(this);
  534. // Create and register the rest of the subsystems. They will register object factories for their own libraries
  535. context_->RegisterSubsystem(new Time(context_));
  536. context_->RegisterSubsystem(new WorkQueue(context_));
  537. #ifdef ENABLE_PROFILING
  538. context_->RegisterSubsystem(new Profiler(context_));
  539. #endif
  540. context_->RegisterSubsystem(new FileSystem(context_));
  541. context_->RegisterSubsystem(new ResourceCache(context_));
  542. context_->RegisterSubsystem(new Network(context_));
  543. if (!headless_)
  544. {
  545. context_->RegisterSubsystem(new Graphics(context_));
  546. context_->RegisterSubsystem(new Renderer(context_));
  547. }
  548. else
  549. {
  550. // Register Graphics library object factories also in headless mode; the objects will function without allocating
  551. // actual GPU resources
  552. RegisterGraphicsLibrary(context_);
  553. }
  554. context_->RegisterSubsystem(new Input(context_));
  555. context_->RegisterSubsystem(new UI(context_));
  556. context_->RegisterSubsystem(new Audio(context_));
  557. #ifdef ENABLE_LOGGING
  558. context_->RegisterSubsystem(new Log(context_));
  559. #endif
  560. // Scene, Physics & Navigation libraries do not have a corresponding subsystem which would register their object factories.
  561. // Register manually now
  562. RegisterSceneLibrary(context_);
  563. RegisterPhysicsLibrary(context_);
  564. RegisterNavigationLibrary(context_);
  565. // In debug mode, check that all factory created objects can be created without crashing
  566. #ifdef _DEBUG
  567. const HashMap<ShortStringHash, SharedPtr<ObjectFactory> >& factories = context_->GetObjectFactories();
  568. for (HashMap<ShortStringHash, SharedPtr<ObjectFactory> >::ConstIterator i = factories.Begin(); i != factories.End(); ++i)
  569. SharedPtr<Object> object = i->second_->CreateObject();
  570. #endif
  571. }
  572. void Engine::HandleExitRequested(StringHash eventType, VariantMap& eventData)
  573. {
  574. if (autoExit_)
  575. Exit();
  576. }
  577. }