Engine.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996
  1. //
  2. // Copyright (c) 2008-2016 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/Audio.h"
  24. #include "../Core/Context.h"
  25. #include "../Core/CoreEvents.h"
  26. #include "../Core/EventProfiler.h"
  27. #include "../Core/ProcessUtils.h"
  28. #include "../Core/WorkQueue.h"
  29. #include "../Engine/Engine.h"
  30. #include "../Graphics/Graphics.h"
  31. #include "../Graphics/Renderer.h"
  32. #include "../Input/Input.h"
  33. #include "../IO/FileSystem.h"
  34. #include "../IO/Log.h"
  35. #include "../IO/PackageFile.h"
  36. // ATOMIC BEGIN
  37. #include "../Resource/XMLFile.h"
  38. // ATOMIC END
  39. #ifdef ATOMIC_NAVIGATION
  40. #include "../Navigation/NavigationMesh.h"
  41. #endif
  42. #ifdef ATOMIC_NETWORK
  43. #include "../Network/Network.h"
  44. #endif
  45. #ifdef ATOMIC_DATABASE
  46. #include "../Database/Database.h"
  47. #endif
  48. #ifdef ATOMIC_PHYSICS
  49. #include "../Physics/PhysicsWorld.h"
  50. #endif
  51. #include "../Resource/ResourceCache.h"
  52. #include "../Resource/Localization.h"
  53. #include "../Scene/Scene.h"
  54. #include "../Scene/SceneEvents.h"
  55. #include "../UI/UI.h"
  56. #ifdef ATOMIC_ATOMIC2D
  57. #include "../Atomic2D/Atomic2D.h"
  58. #endif
  59. #if defined(__EMSCRIPTEN__) && defined(ATOMIC_TESTING)
  60. #include <emscripten/emscripten.h>
  61. #endif
  62. #include "../DebugNew.h"
  63. #if defined(_MSC_VER) && defined(_DEBUG)
  64. // From dbgint.h
  65. #define nNoMansLandSize 4
  66. typedef struct _CrtMemBlockHeader
  67. {
  68. struct _CrtMemBlockHeader* pBlockHeaderNext;
  69. struct _CrtMemBlockHeader* pBlockHeaderPrev;
  70. char* szFileName;
  71. int nLine;
  72. size_t nDataSize;
  73. int nBlockUse;
  74. long lRequest;
  75. unsigned char gap[nNoMansLandSize];
  76. } _CrtMemBlockHeader;
  77. #endif
  78. namespace Atomic
  79. {
  80. extern const char* logLevelPrefixes[];
  81. Engine::Engine(Context* context) :
  82. Object(context),
  83. timeStep_(0.0f),
  84. timeStepSmoothing_(2),
  85. minFps_(10),
  86. #if defined(IOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
  87. maxFps_(60),
  88. maxInactiveFps_(10),
  89. pauseMinimized_(true),
  90. #else
  91. maxFps_(200),
  92. maxInactiveFps_(60),
  93. pauseMinimized_(false),
  94. #endif
  95. #ifdef ATOMIC_TESTING
  96. timeOut_(0),
  97. #endif
  98. autoExit_(true),
  99. initialized_(false),
  100. exiting_(false),
  101. headless_(false),
  102. audioPaused_(false)
  103. {
  104. // Register self as a subsystem
  105. context_->RegisterSubsystem(this);
  106. // Create subsystems which do not depend on engine initialization or startup parameters
  107. context_->RegisterSubsystem(new Time(context_));
  108. context_->RegisterSubsystem(new WorkQueue(context_));
  109. #ifdef ATOMIC_PROFILING
  110. context_->RegisterSubsystem(new Profiler(context_));
  111. #endif
  112. context_->RegisterSubsystem(new FileSystem(context_));
  113. #ifdef ATOMIC_LOGGING
  114. context_->RegisterSubsystem(new Log(context_));
  115. #endif
  116. context_->RegisterSubsystem(new ResourceCache(context_));
  117. context_->RegisterSubsystem(new Localization(context_));
  118. #ifdef ATOMIC_NETWORK
  119. context_->RegisterSubsystem(new Network(context_));
  120. #endif
  121. #ifdef ATOMIC_DATABASE
  122. context_->RegisterSubsystem(new Database(context_));
  123. #endif
  124. context_->RegisterSubsystem(new Input(context_));
  125. context_->RegisterSubsystem(new Audio(context_));
  126. context_->RegisterSubsystem(new UI(context_));
  127. // Register object factories for libraries which are not automatically registered along with subsystem creation
  128. RegisterSceneLibrary(context_);
  129. #ifdef ATOMIC_PHYSICS
  130. RegisterPhysicsLibrary(context_);
  131. #endif
  132. #ifdef ATOMIC_NAVIGATION
  133. RegisterNavigationLibrary(context_);
  134. #endif
  135. SubscribeToEvent(E_EXITREQUESTED, ATOMIC_HANDLER(Engine, HandleExitRequested));
  136. }
  137. Engine::~Engine()
  138. {
  139. }
  140. bool Engine::Initialize(const VariantMap& parameters)
  141. {
  142. if (initialized_)
  143. return true;
  144. ATOMIC_PROFILE(InitEngine);
  145. // Set headless mode
  146. headless_ = GetParameter(parameters, "Headless", false).GetBool();
  147. // Register the rest of the subsystems
  148. if (!headless_)
  149. {
  150. context_->RegisterSubsystem(new Graphics(context_));
  151. context_->RegisterSubsystem(new Renderer(context_));
  152. }
  153. else
  154. {
  155. // Register graphics library objects explicitly in headless mode to allow them to work without using actual GPU resources
  156. RegisterGraphicsLibrary(context_);
  157. }
  158. #ifdef ATOMIC_ATOMIC2D
  159. // 2D graphics library is dependent on 3D graphics library
  160. RegisterAtomic2DLibrary(context_);
  161. #endif
  162. // Start logging
  163. Log* log = GetSubsystem<Log>();
  164. if (log)
  165. {
  166. if (HasParameter(parameters, "LogLevel"))
  167. log->SetLevel(GetParameter(parameters, "LogLevel").GetInt());
  168. log->SetQuiet(GetParameter(parameters, "LogQuiet", false).GetBool());
  169. log->Open(GetParameter(parameters, "LogName", "Atomic.log").GetString());
  170. }
  171. // Set maximally accurate low res timer
  172. GetSubsystem<Time>()->SetTimerPeriod(1);
  173. // Configure max FPS
  174. if (GetParameter(parameters, "FrameLimiter", true) == false)
  175. SetMaxFps(0);
  176. // Set amount of worker threads according to the available physical CPU cores. Using also hyperthreaded cores results in
  177. // unpredictable extra synchronization overhead. Also reserve one core for the main thread
  178. #ifdef ATOMIC_THREADING
  179. unsigned numThreads = GetParameter(parameters, "WorkerThreads", true).GetBool() ? GetNumPhysicalCPUs() - 1 : 0;
  180. if (numThreads)
  181. {
  182. GetSubsystem<WorkQueue>()->CreateThreads(numThreads);
  183. ATOMIC_LOGINFOF("Created %u worker thread%s", numThreads, numThreads > 1 ? "s" : "");
  184. }
  185. #endif
  186. // Add resource paths
  187. ResourceCache* cache = GetSubsystem<ResourceCache>();
  188. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  189. Vector<String> resourcePrefixPaths = GetParameter(parameters, "ResourcePrefixPaths", String::EMPTY).GetString().Split(';', true);
  190. for (unsigned i = 0; i < resourcePrefixPaths.Size(); ++i)
  191. resourcePrefixPaths[i] = AddTrailingSlash(
  192. IsAbsolutePath(resourcePrefixPaths[i]) ? resourcePrefixPaths[i] : fileSystem->GetProgramDir() + resourcePrefixPaths[i]);
  193. Vector<String> resourcePaths = GetParameter(parameters, "ResourcePaths", "Data;CoreData").GetString().Split(';');
  194. Vector<String> resourcePackages = GetParameter(parameters, "ResourcePackages").GetString().Split(';');
  195. Vector<String> autoLoadPaths = GetParameter(parameters, "AutoloadPaths", "Autoload").GetString().Split(';');
  196. for (unsigned i = 0; i < resourcePaths.Size(); ++i)
  197. {
  198. // If path is not absolute, prefer to add it as a package if possible
  199. if (!IsAbsolutePath(resourcePaths[i]))
  200. {
  201. unsigned j = 0;
  202. for (; j < resourcePrefixPaths.Size(); ++j)
  203. {
  204. // ATOMIC BEGIN
  205. String packageName = resourcePrefixPaths[j] + resourcePaths[i] + PAK_EXTENSION;
  206. // ATOMIC END
  207. if (fileSystem->FileExists(packageName))
  208. {
  209. if (cache->AddPackageFile(packageName))
  210. break;
  211. else
  212. return false; // The root cause of the error should have already been logged
  213. }
  214. String pathName = resourcePrefixPaths[j] + resourcePaths[i];
  215. if (fileSystem->DirExists(pathName))
  216. {
  217. if (cache->AddResourceDir(pathName))
  218. break;
  219. else
  220. return false;
  221. }
  222. }
  223. // ATOMIC: Only fail when CoreData can't be opened and not headless
  224. if (j == resourcePrefixPaths.Size() && !headless_)
  225. {
  226. ATOMIC_LOGERRORF(
  227. "Failed to add resource path '%s', check the documentation on how to set the 'resource prefix path'",
  228. resourcePaths[i].CString());
  229. return false;
  230. }
  231. }
  232. else
  233. {
  234. String pathName = resourcePaths[i];
  235. if (fileSystem->DirExists(pathName))
  236. if (!cache->AddResourceDir(pathName))
  237. return false;
  238. }
  239. }
  240. // Then add specified packages
  241. for (unsigned i = 0; i < resourcePackages.Size(); ++i)
  242. {
  243. unsigned j = 0;
  244. for (; j < resourcePrefixPaths.Size(); ++j)
  245. {
  246. String packageName = resourcePrefixPaths[j] + resourcePackages[i];
  247. if (fileSystem->FileExists(packageName))
  248. {
  249. if (cache->AddPackageFile(packageName))
  250. break;
  251. else
  252. return false;
  253. }
  254. }
  255. // ATOMIC: Only fail when CoreData can't be opened and not headless
  256. if (j == resourcePrefixPaths.Size() && !headless_)
  257. {
  258. ATOMIC_LOGERRORF(
  259. "Failed to add resource package '%s', check the documentation on how to set the 'resource prefix path'",
  260. resourcePackages[i].CString());
  261. return false;
  262. }
  263. }
  264. // Add auto load folders. Prioritize these (if exist) before the default folders
  265. for (unsigned i = 0; i < autoLoadPaths.Size(); ++i)
  266. {
  267. bool autoLoadPathExist = false;
  268. for (unsigned j = 0; j < resourcePrefixPaths.Size(); ++j)
  269. {
  270. String autoLoadPath(autoLoadPaths[i]);
  271. if (!IsAbsolutePath(autoLoadPath))
  272. autoLoadPath = resourcePrefixPaths[j] + autoLoadPath;
  273. if (fileSystem->DirExists(autoLoadPath))
  274. {
  275. autoLoadPathExist = true;
  276. // Add all the subdirs (non-recursive) as resource directory
  277. Vector<String> subdirs;
  278. fileSystem->ScanDir(subdirs, autoLoadPath, "*", SCAN_DIRS, false);
  279. for (unsigned y = 0; y < subdirs.Size(); ++y)
  280. {
  281. String dir = subdirs[y];
  282. if (dir.StartsWith("."))
  283. continue;
  284. String autoResourceDir = autoLoadPath + "/" + dir;
  285. if (!cache->AddResourceDir(autoResourceDir, 0))
  286. return false;
  287. }
  288. // Add all the found package files (non-recursive)
  289. Vector<String> paks;
  290. // ATOMIC BEGIN
  291. fileSystem->ScanDir(paks, autoLoadPath, ToString("*.%s", PAK_EXTENSION), SCAN_FILES, false);
  292. // ATOMIC END
  293. for (unsigned y = 0; y < paks.Size(); ++y)
  294. {
  295. String pak = paks[y];
  296. if (pak.StartsWith("."))
  297. continue;
  298. String autoPackageName = autoLoadPath + "/" + pak;
  299. if (!cache->AddPackageFile(autoPackageName, 0))
  300. return false;
  301. }
  302. }
  303. }
  304. // The following debug message is confusing when user is not aware of the autoload feature
  305. // Especially because the autoload feature is enabled by default without user intervention
  306. // The following extra conditional check below is to suppress unnecessary debug log entry under such default situation
  307. // The cleaner approach is to not enable the autoload by default, i.e. do not use 'Autoload' as default value for 'AutoloadPaths' engine parameter
  308. // However, doing so will break the existing applications that rely on this
  309. if (!autoLoadPathExist && (autoLoadPaths.Size() > 1 || autoLoadPaths[0] != "Autoload"))
  310. ATOMIC_LOGDEBUGF(
  311. "Skipped autoload path '%s' as it does not exist, check the documentation on how to set the 'resource prefix path'",
  312. autoLoadPaths[i].CString());
  313. }
  314. // Initialize graphics & audio output
  315. if (!headless_)
  316. {
  317. Graphics* graphics = GetSubsystem<Graphics>();
  318. Renderer* renderer = GetSubsystem<Renderer>();
  319. if (HasParameter(parameters, "ExternalWindow"))
  320. graphics->SetExternalWindow(GetParameter(parameters, "ExternalWindow").GetVoidPtr());
  321. graphics->SetWindowTitle(GetParameter(parameters, "WindowTitle", "Atomic").GetString());
  322. graphics->SetWindowIcon(cache->GetResource<Image>(GetParameter(parameters, "WindowIcon", String::EMPTY).GetString()));
  323. graphics->SetFlushGPU(GetParameter(parameters, "FlushGPU", false).GetBool());
  324. graphics->SetOrientations(GetParameter(parameters, "Orientations", "LandscapeLeft LandscapeRight").GetString());
  325. if (HasParameter(parameters, "WindowPositionX") && HasParameter(parameters, "WindowPositionY"))
  326. graphics->SetWindowPosition(GetParameter(parameters, "WindowPositionX").GetInt(),
  327. GetParameter(parameters, "WindowPositionY").GetInt());
  328. #ifdef ATOMIC_OPENGL
  329. if (HasParameter(parameters, "ForceGL2"))
  330. graphics->SetForceGL2(GetParameter(parameters, "ForceGL2").GetBool());
  331. #endif
  332. if (!graphics->SetMode(
  333. // ATOMIC BEGIN
  334. GetParameter(parameters, "WindowMaximized", false).GetBool() ? 0 : GetParameter(parameters, "WindowWidth", 0).GetInt(),
  335. GetParameter(parameters, "WindowMaximized", false).GetBool() ? 0 : GetParameter(parameters, "WindowHeight", 0).GetInt(),
  336. // ATOMIC END
  337. GetParameter(parameters, "FullScreen", true).GetBool(),
  338. GetParameter(parameters, "Borderless", false).GetBool(),
  339. GetParameter(parameters, "WindowResizable", false).GetBool(),
  340. GetParameter(parameters, "HighDPI", false).GetBool(),
  341. GetParameter(parameters, "VSync", false).GetBool(),
  342. GetParameter(parameters, "TripleBuffer", false).GetBool(),
  343. GetParameter(parameters, "MultiSample", 1).GetInt()
  344. ))
  345. return false;
  346. if (HasParameter(parameters, "DumpShaders"))
  347. graphics->BeginDumpShaders(GetParameter(parameters, "DumpShaders", String::EMPTY).GetString());
  348. if (HasParameter(parameters, "RenderPath"))
  349. renderer->SetDefaultRenderPath(cache->GetResource<XMLFile>(GetParameter(parameters, "RenderPath").GetString()));
  350. renderer->SetDrawShadows(GetParameter(parameters, "Shadows", true).GetBool());
  351. if (renderer->GetDrawShadows() && GetParameter(parameters, "LowQualityShadows", false).GetBool())
  352. renderer->SetShadowQuality(SHADOWQUALITY_SIMPLE_16BIT);
  353. renderer->SetMaterialQuality(GetParameter(parameters, "MaterialQuality", QUALITY_HIGH).GetInt());
  354. renderer->SetTextureQuality(GetParameter(parameters, "TextureQuality", QUALITY_HIGH).GetInt());
  355. renderer->SetTextureFilterMode((TextureFilterMode)GetParameter(parameters, "TextureFilterMode", FILTER_TRILINEAR).GetInt());
  356. renderer->SetTextureAnisotropy(GetParameter(parameters, "TextureAnisotropy", 4).GetInt());
  357. if (GetParameter(parameters, "Sound", true).GetBool())
  358. {
  359. GetSubsystem<Audio>()->SetMode(
  360. GetParameter(parameters, "SoundBuffer", 100).GetInt(),
  361. GetParameter(parameters, "SoundMixRate", 44100).GetInt(),
  362. GetParameter(parameters, "SoundStereo", true).GetBool(),
  363. GetParameter(parameters, "SoundInterpolation", true).GetBool()
  364. );
  365. }
  366. }
  367. // Init FPU state of main thread
  368. InitFPU();
  369. // Initialize input
  370. if (HasParameter(parameters, "TouchEmulation"))
  371. GetSubsystem<Input>()->SetTouchEmulation(GetParameter(parameters, "TouchEmulation").GetBool());
  372. #ifdef ATOMIC_TESTING
  373. if (HasParameter(parameters, "TimeOut"))
  374. timeOut_ = GetParameter(parameters, "TimeOut", 0).GetInt() * 1000000LL;
  375. #endif
  376. #ifdef ATOMIC_PROFILING
  377. if (GetParameter(parameters, "EventProfiler", true).GetBool())
  378. {
  379. context_->RegisterSubsystem(new EventProfiler(context_));
  380. EventProfiler::SetActive(true);
  381. }
  382. #endif
  383. frameTimer_.Reset();
  384. ATOMIC_LOGINFO("Initialized engine");
  385. initialized_ = true;
  386. return true;
  387. }
  388. void Engine::RunFrame()
  389. {
  390. assert(initialized_);
  391. // If not headless, and the graphics subsystem no longer has a window open, assume we should exit
  392. if (!headless_ && !GetSubsystem<Graphics>()->IsInitialized())
  393. exiting_ = true;
  394. if (exiting_)
  395. return;
  396. // Note: there is a minimal performance cost to looking up subsystems (uses a hashmap); if they would be looked up several
  397. // times per frame it would be better to cache the pointers
  398. Time* time = GetSubsystem<Time>();
  399. Input* input = GetSubsystem<Input>();
  400. Audio* audio = GetSubsystem<Audio>();
  401. #ifdef ATOMIC_PROFILING
  402. if (EventProfiler::IsActive())
  403. {
  404. EventProfiler* eventProfiler = GetSubsystem<EventProfiler>();
  405. if (eventProfiler)
  406. eventProfiler->BeginFrame();
  407. }
  408. #endif
  409. time->BeginFrame(timeStep_);
  410. // If pause when minimized -mode is in use, stop updates and audio as necessary
  411. if (pauseMinimized_ && input->IsMinimized())
  412. {
  413. if (audio->IsPlaying())
  414. {
  415. audio->Stop();
  416. audioPaused_ = true;
  417. }
  418. }
  419. else
  420. {
  421. // Only unpause when it was paused by the engine
  422. if (audioPaused_)
  423. {
  424. audio->Play();
  425. audioPaused_ = false;
  426. }
  427. Update();
  428. }
  429. Render();
  430. ApplyFrameLimit();
  431. time->EndFrame();
  432. }
  433. Console* Engine::CreateConsole()
  434. {
  435. // ATOMIC BEGIN
  436. /*
  437. if (headless_ || !initialized_)
  438. return 0;
  439. // Return existing console if possible
  440. Console* console = GetSubsystem<Console>();
  441. if (!console)
  442. {
  443. console = new Console(context_);
  444. context_->RegisterSubsystem(console);
  445. }
  446. return console;
  447. */
  448. // ATOMIC END
  449. return 0;
  450. }
  451. DebugHud* Engine::CreateDebugHud()
  452. {
  453. // ATOMIC BEGIN
  454. /*
  455. if (headless_ || !initialized_)
  456. return 0;
  457. // Return existing debug HUD if possible
  458. DebugHud* debugHud = GetSubsystem<DebugHud>();
  459. if (!debugHud)
  460. {
  461. debugHud = new DebugHud(context_);
  462. context_->RegisterSubsystem(debugHud);
  463. }
  464. return debugHud;
  465. */
  466. // ATOMIC END
  467. return 0;
  468. }
  469. void Engine::SetTimeStepSmoothing(int frames)
  470. {
  471. timeStepSmoothing_ = (unsigned)Clamp(frames, 1, 20);
  472. }
  473. void Engine::SetMinFps(int fps)
  474. {
  475. minFps_ = (unsigned)Max(fps, 0);
  476. }
  477. void Engine::SetMaxFps(int fps)
  478. {
  479. maxFps_ = (unsigned)Max(fps, 0);
  480. }
  481. void Engine::SetMaxInactiveFps(int fps)
  482. {
  483. maxInactiveFps_ = (unsigned)Max(fps, 0);
  484. }
  485. void Engine::SetPauseMinimized(bool enable)
  486. {
  487. pauseMinimized_ = enable;
  488. }
  489. void Engine::SetAutoExit(bool enable)
  490. {
  491. // On mobile platforms exit is mandatory if requested by the platform itself and should not be attempted to be disabled
  492. #if defined(__ANDROID__) || defined(IOS)
  493. enable = true;
  494. #endif
  495. autoExit_ = enable;
  496. }
  497. void Engine::SetNextTimeStep(float seconds)
  498. {
  499. timeStep_ = Max(seconds, 0.0f);
  500. }
  501. void Engine::Exit()
  502. {
  503. #if defined(IOS)
  504. // On iOS it's not legal for the application to exit on its own, instead it will be minimized with the home key
  505. #else
  506. DoExit();
  507. #endif
  508. }
  509. void Engine::DumpProfiler()
  510. {
  511. #ifdef ATOMIC_LOGGING
  512. if (!Thread::IsMainThread())
  513. return;
  514. Profiler* profiler = GetSubsystem<Profiler>();
  515. if (profiler)
  516. ATOMIC_LOGRAW(profiler->PrintData(true, true) + "\n");
  517. #endif
  518. }
  519. void Engine::DumpResources(bool dumpFileName)
  520. {
  521. #ifdef ATOMIC_LOGGING
  522. if (!Thread::IsMainThread())
  523. return;
  524. ResourceCache* cache = GetSubsystem<ResourceCache>();
  525. const HashMap<StringHash, ResourceGroup>& resourceGroups = cache->GetAllResources();
  526. if (dumpFileName)
  527. {
  528. ATOMIC_LOGRAW("Used resources:\n");
  529. for (HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups.Begin(); i != resourceGroups.End(); ++i)
  530. {
  531. const HashMap<StringHash, SharedPtr<Resource> >& resources = i->second_.resources_;
  532. if (dumpFileName)
  533. {
  534. for (HashMap<StringHash, SharedPtr<Resource> >::ConstIterator j = resources.Begin(); j != resources.End(); ++j)
  535. ATOMIC_LOGRAW(j->second_->GetName() + "\n");
  536. }
  537. }
  538. }
  539. else
  540. ATOMIC_LOGRAW(cache->PrintMemoryUsage() + "\n");
  541. #endif
  542. }
  543. void Engine::DumpMemory()
  544. {
  545. #ifdef ATOMIC_LOGGING
  546. #if defined(_MSC_VER) && defined(_DEBUG)
  547. _CrtMemState state;
  548. _CrtMemCheckpoint(&state);
  549. _CrtMemBlockHeader* block = state.pBlockHeader;
  550. unsigned total = 0;
  551. unsigned blocks = 0;
  552. for (;;)
  553. {
  554. if (block && block->pBlockHeaderNext)
  555. block = block->pBlockHeaderNext;
  556. else
  557. break;
  558. }
  559. while (block)
  560. {
  561. if (block->nBlockUse > 0)
  562. {
  563. if (block->szFileName)
  564. ATOMIC_LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes, file " + String(block->szFileName) + " line " + String(block->nLine) + "\n");
  565. else
  566. ATOMIC_LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes\n");
  567. total += block->nDataSize;
  568. ++blocks;
  569. }
  570. block = block->pBlockHeaderPrev;
  571. }
  572. ATOMIC_LOGRAW("Total allocated memory " + String(total) + " bytes in " + String(blocks) + " blocks\n\n");
  573. #else
  574. ATOMIC_LOGRAW("DumpMemory() supported on MSVC debug mode only\n\n");
  575. #endif
  576. #endif
  577. }
  578. void Engine::Update()
  579. {
  580. ATOMIC_PROFILE(Update);
  581. // Logic update event
  582. using namespace Update;
  583. VariantMap& eventData = GetEventDataMap();
  584. eventData[P_TIMESTEP] = timeStep_;
  585. SendEvent(E_UPDATE, eventData);
  586. // Logic post-update event
  587. SendEvent(E_POSTUPDATE, eventData);
  588. // Rendering update event
  589. SendEvent(E_RENDERUPDATE, eventData);
  590. // Post-render update event
  591. SendEvent(E_POSTRENDERUPDATE, eventData);
  592. }
  593. void Engine::Render()
  594. {
  595. if (headless_)
  596. return;
  597. ATOMIC_PROFILE(Render);
  598. // If device is lost, BeginFrame will fail and we skip rendering
  599. Graphics* graphics = GetSubsystem<Graphics>();
  600. if (!graphics->BeginFrame())
  601. return;
  602. GetSubsystem<Renderer>()->Render();
  603. GetSubsystem<UI>()->Render();
  604. graphics->EndFrame();
  605. }
  606. void Engine::ApplyFrameLimit()
  607. {
  608. if (!initialized_)
  609. return;
  610. unsigned maxFps = maxFps_;
  611. Input* input = GetSubsystem<Input>();
  612. if (input && !input->HasFocus())
  613. maxFps = Min(maxInactiveFps_, maxFps);
  614. long long elapsed = 0;
  615. #ifndef __EMSCRIPTEN__
  616. // Perform waiting loop if maximum FPS set
  617. #ifndef IOS
  618. if (maxFps)
  619. #else
  620. // If on iOS and target framerate is 60 or above, just let the animation callback handle frame timing
  621. // instead of waiting ourselves
  622. if (maxFps < 60)
  623. #endif
  624. {
  625. ATOMIC_PROFILE(ApplyFrameLimit);
  626. long long targetMax = 1000000LL / maxFps;
  627. for (;;)
  628. {
  629. elapsed = frameTimer_.GetUSec(false);
  630. if (elapsed >= targetMax)
  631. break;
  632. // Sleep if 1 ms or more off the frame limiting goal
  633. if (targetMax - elapsed >= 1000LL)
  634. {
  635. unsigned sleepTime = (unsigned)((targetMax - elapsed) / 1000LL);
  636. Time::Sleep(sleepTime);
  637. }
  638. }
  639. }
  640. #endif
  641. elapsed = frameTimer_.GetUSec(true);
  642. #ifdef ATOMIC_TESTING
  643. if (timeOut_ > 0)
  644. {
  645. timeOut_ -= elapsed;
  646. if (timeOut_ <= 0)
  647. Exit();
  648. }
  649. #endif
  650. // If FPS lower than minimum, clamp elapsed time
  651. if (minFps_)
  652. {
  653. long long targetMin = 1000000LL / minFps_;
  654. if (elapsed > targetMin)
  655. elapsed = targetMin;
  656. }
  657. // Perform timestep smoothing
  658. timeStep_ = 0.0f;
  659. lastTimeSteps_.Push(elapsed / 1000000.0f);
  660. if (lastTimeSteps_.Size() > timeStepSmoothing_)
  661. {
  662. // If the smoothing configuration was changed, ensure correct amount of samples
  663. lastTimeSteps_.Erase(0, lastTimeSteps_.Size() - timeStepSmoothing_);
  664. for (unsigned i = 0; i < lastTimeSteps_.Size(); ++i)
  665. timeStep_ += lastTimeSteps_[i];
  666. timeStep_ /= lastTimeSteps_.Size();
  667. }
  668. else
  669. timeStep_ = lastTimeSteps_.Back();
  670. }
  671. VariantMap Engine::ParseParameters(const Vector<String>& arguments)
  672. {
  673. VariantMap ret;
  674. // Pre-initialize the parameters with environment variable values when they are set
  675. if (const char* paths = getenv("ATOMIC_PREFIX_PATH"))
  676. ret["ResourcePrefixPaths"] = paths;
  677. for (unsigned i = 0; i < arguments.Size(); ++i)
  678. {
  679. if (arguments[i].Length() > 1 && arguments[i][0] == '-')
  680. {
  681. String argument = arguments[i].Substring(1).ToLower();
  682. String value = i + 1 < arguments.Size() ? arguments[i + 1] : String::EMPTY;
  683. if (argument == "headless")
  684. ret["Headless"] = true;
  685. else if (argument == "nolimit")
  686. ret["FrameLimiter"] = false;
  687. else if (argument == "flushgpu")
  688. ret["FlushGPU"] = true;
  689. else if (argument == "gl2")
  690. ret["ForceGL2"] = true;
  691. else if (argument == "landscape")
  692. ret["Orientations"] = "LandscapeLeft LandscapeRight " + ret["Orientations"].GetString();
  693. else if (argument == "portrait")
  694. ret["Orientations"] = "Portrait PortraitUpsideDown " + ret["Orientations"].GetString();
  695. else if (argument == "nosound")
  696. ret["Sound"] = false;
  697. else if (argument == "noip")
  698. ret["SoundInterpolation"] = false;
  699. else if (argument == "mono")
  700. ret["SoundStereo"] = false;
  701. else if (argument == "prepass")
  702. ret["RenderPath"] = "RenderPaths/Prepass.xml";
  703. else if (argument == "deferred")
  704. ret["RenderPath"] = "RenderPaths/Deferred.xml";
  705. else if (argument == "renderpath" && !value.Empty())
  706. {
  707. ret["RenderPath"] = value;
  708. ++i;
  709. }
  710. else if (argument == "noshadows")
  711. ret["Shadows"] = false;
  712. else if (argument == "lqshadows")
  713. ret["LowQualityShadows"] = true;
  714. else if (argument == "nothreads")
  715. ret["WorkerThreads"] = false;
  716. else if (argument == "v")
  717. ret["VSync"] = true;
  718. else if (argument == "t")
  719. ret["TripleBuffer"] = true;
  720. else if (argument == "w")
  721. ret["FullScreen"] = false;
  722. else if (argument == "borderless")
  723. ret["Borderless"] = true;
  724. else if (argument == "s")
  725. ret["WindowResizable"] = true;
  726. else if (argument == "hd")
  727. ret["HighDPI"] = true;
  728. else if (argument == "q")
  729. ret["LogQuiet"] = true;
  730. else if (argument == "log" && !value.Empty())
  731. {
  732. unsigned logLevel = GetStringListIndex(value.CString(), logLevelPrefixes, M_MAX_UNSIGNED);
  733. if (logLevel != M_MAX_UNSIGNED)
  734. {
  735. ret["LogLevel"] = logLevel;
  736. ++i;
  737. }
  738. }
  739. else if (argument == "x" && !value.Empty())
  740. {
  741. ret["WindowWidth"] = ToInt(value);
  742. ++i;
  743. }
  744. else if (argument == "y" && !value.Empty())
  745. {
  746. ret["WindowHeight"] = ToInt(value);
  747. ++i;
  748. }
  749. else if (argument == "m" && !value.Empty())
  750. {
  751. ret["MultiSample"] = ToInt(value);
  752. ++i;
  753. }
  754. else if (argument == "b" && !value.Empty())
  755. {
  756. ret["SoundBuffer"] = ToInt(value);
  757. ++i;
  758. }
  759. else if (argument == "r" && !value.Empty())
  760. {
  761. ret["SoundMixRate"] = ToInt(value);
  762. ++i;
  763. }
  764. else if (argument == "pp" && !value.Empty())
  765. {
  766. ret["ResourcePrefixPaths"] = value;
  767. ++i;
  768. }
  769. else if (argument == "p" && !value.Empty())
  770. {
  771. ret["ResourcePaths"] = value;
  772. ++i;
  773. }
  774. else if (argument == "pf" && !value.Empty())
  775. {
  776. ret["ResourcePackages"] = value;
  777. ++i;
  778. }
  779. else if (argument == "ap" && !value.Empty())
  780. {
  781. ret["AutoloadPaths"] = value;
  782. ++i;
  783. }
  784. else if (argument == "ds" && !value.Empty())
  785. {
  786. ret["DumpShaders"] = value;
  787. ++i;
  788. }
  789. else if (argument == "mq" && !value.Empty())
  790. {
  791. ret["MaterialQuality"] = ToInt(value);
  792. ++i;
  793. }
  794. else if (argument == "tq" && !value.Empty())
  795. {
  796. ret["TextureQuality"] = ToInt(value);
  797. ++i;
  798. }
  799. else if (argument == "tf" && !value.Empty())
  800. {
  801. ret["TextureFilterMode"] = ToInt(value);
  802. ++i;
  803. }
  804. else if (argument == "af" && !value.Empty())
  805. {
  806. ret["TextureFilterMode"] = FILTER_ANISOTROPIC;
  807. ret["TextureAnisotropy"] = ToInt(value);
  808. ++i;
  809. }
  810. else if (argument == "touch")
  811. ret["TouchEmulation"] = true;
  812. // ATOMIC BEGIN
  813. else if (argument == "logname" && !value.Empty())
  814. {
  815. ret["LogName"] = value;
  816. ++i;
  817. }
  818. // ATOMIC END
  819. #ifdef ATOMIC_TESTING
  820. else if (argument == "timeout" && !value.Empty())
  821. {
  822. ret["TimeOut"] = ToInt(value);
  823. ++i;
  824. }
  825. #endif
  826. }
  827. }
  828. return ret;
  829. }
  830. bool Engine::HasParameter(const VariantMap& parameters, const String& parameter)
  831. {
  832. StringHash nameHash(parameter);
  833. return parameters.Find(nameHash) != parameters.End();
  834. }
  835. const Variant& Engine::GetParameter(const VariantMap& parameters, const String& parameter, const Variant& defaultValue)
  836. {
  837. StringHash nameHash(parameter);
  838. VariantMap::ConstIterator i = parameters.Find(nameHash);
  839. return i != parameters.End() ? i->second_ : defaultValue;
  840. }
  841. void Engine::HandleExitRequested(StringHash eventType, VariantMap& eventData)
  842. {
  843. if (autoExit_)
  844. {
  845. // Do not call Exit() here, as it contains mobile platform -specific tests to not exit.
  846. // If we do receive an exit request from the system on those platforms, we must comply
  847. DoExit();
  848. }
  849. }
  850. void Engine::DoExit()
  851. {
  852. Graphics* graphics = GetSubsystem<Graphics>();
  853. if (graphics)
  854. graphics->Close();
  855. exiting_ = true;
  856. #if defined(__EMSCRIPTEN__) && defined(ATOMIC_TESTING)
  857. emscripten_force_exit(EXIT_SUCCESS); // Some how this is required to signal emrun to stop
  858. #endif
  859. }
  860. // ATOMIC BEGIN
  861. bool Engine::GetDebugBuild() const
  862. {
  863. #ifdef ATOMIC_DEBUG
  864. return true;
  865. #else
  866. return false;
  867. #endif
  868. }
  869. // ATOMIC END
  870. }