Engine.cpp 34 KB

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