Engine.cpp 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  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. // If paused, or pause when minimized -mode is in use, stop updates and audio as necessary
  436. if ((paused_ && !runNextPausedFrame_) ||
  437. (pauseMinimized_ && input->IsMinimized()))
  438. {
  439. if (audio->IsPlaying())
  440. {
  441. audio->Stop();
  442. audioPaused_ = true;
  443. }
  444. }
  445. else
  446. {
  447. // Only unpause when it was paused by the engine
  448. if (audioPaused_)
  449. {
  450. audio->Play();
  451. audioPaused_ = false;
  452. }
  453. // Only run one frame when stepping
  454. runNextPausedFrame_ = false;
  455. Update();
  456. }
  457. fpsTimeSinceUpdate_ += timeStep_;
  458. ++fpsFramesSinceUpdate_;
  459. if (fpsTimeSinceUpdate_ > ENGINE_FPS_UPDATE_INTERVAL)
  460. {
  461. fps_ = (int)(fpsFramesSinceUpdate_ / fpsTimeSinceUpdate_);
  462. fpsFramesSinceUpdate_ = 0;
  463. fpsTimeSinceUpdate_ = 0;
  464. }
  465. // ATOMIC END
  466. Render();
  467. ApplyFrameLimit();
  468. time->EndFrame();
  469. }
  470. Console* Engine::CreateConsole()
  471. {
  472. // ATOMIC BEGIN
  473. /*
  474. if (headless_ || !initialized_)
  475. return 0;
  476. // Return existing console if possible
  477. Console* console = GetSubsystem<Console>();
  478. if (!console)
  479. {
  480. console = new Console(context_);
  481. context_->RegisterSubsystem(console);
  482. }
  483. return console;
  484. */
  485. // ATOMIC END
  486. return 0;
  487. }
  488. DebugHud* Engine::CreateDebugHud()
  489. {
  490. // ATOMIC BEGIN
  491. /*
  492. if (headless_ || !initialized_)
  493. return 0;
  494. // Return existing debug HUD if possible
  495. DebugHud* debugHud = GetSubsystem<DebugHud>();
  496. if (!debugHud)
  497. {
  498. debugHud = new DebugHud(context_);
  499. context_->RegisterSubsystem(debugHud);
  500. }
  501. return debugHud;
  502. */
  503. // ATOMIC END
  504. return 0;
  505. }
  506. void Engine::SetTimeStepSmoothing(int frames)
  507. {
  508. timeStepSmoothing_ = (unsigned)Clamp(frames, 1, 20);
  509. }
  510. void Engine::SetMinFps(int fps)
  511. {
  512. minFps_ = (unsigned)Max(fps, 0);
  513. }
  514. void Engine::SetMaxFps(int fps)
  515. {
  516. maxFps_ = (unsigned)Max(fps, 0);
  517. }
  518. void Engine::SetMaxInactiveFps(int fps)
  519. {
  520. maxInactiveFps_ = (unsigned)Max(fps, 0);
  521. }
  522. void Engine::SetPauseMinimized(bool enable)
  523. {
  524. pauseMinimized_ = enable;
  525. }
  526. void Engine::SetAutoExit(bool enable)
  527. {
  528. // On mobile platforms exit is mandatory if requested by the platform itself and should not be attempted to be disabled
  529. #if defined(__ANDROID__) || defined(IOS)
  530. enable = true;
  531. #endif
  532. autoExit_ = enable;
  533. }
  534. void Engine::SetNextTimeStep(float seconds)
  535. {
  536. timeStep_ = Max(seconds, 0.0f);
  537. }
  538. void Engine::Exit()
  539. {
  540. #if defined(IOS)
  541. // On iOS it's not legal for the application to exit on its own, instead it will be minimized with the home key
  542. #else
  543. DoExit();
  544. #endif
  545. }
  546. void Engine::DumpProfiler()
  547. {
  548. #ifdef ATOMIC_LOGGING
  549. if (!Thread::IsMainThread())
  550. return;
  551. Profiler* profiler = GetSubsystem<Profiler>();
  552. if (profiler)
  553. ATOMIC_LOGRAW(profiler->PrintData(true, true) + "\n");
  554. #endif
  555. }
  556. void Engine::DumpResources(bool dumpFileName)
  557. {
  558. #ifdef ATOMIC_LOGGING
  559. if (!Thread::IsMainThread())
  560. return;
  561. ResourceCache* cache = GetSubsystem<ResourceCache>();
  562. const HashMap<StringHash, ResourceGroup>& resourceGroups = cache->GetAllResources();
  563. if (dumpFileName)
  564. {
  565. ATOMIC_LOGRAW("Used resources:\n");
  566. for (HashMap<StringHash, ResourceGroup>::ConstIterator i = resourceGroups.Begin(); i != resourceGroups.End(); ++i)
  567. {
  568. const HashMap<StringHash, SharedPtr<Resource> >& resources = i->second_.resources_;
  569. if (dumpFileName)
  570. {
  571. for (HashMap<StringHash, SharedPtr<Resource> >::ConstIterator j = resources.Begin(); j != resources.End(); ++j)
  572. ATOMIC_LOGRAW(j->second_->GetName() + "\n");
  573. }
  574. }
  575. }
  576. else
  577. ATOMIC_LOGRAW(cache->PrintMemoryUsage() + "\n");
  578. #endif
  579. }
  580. void Engine::DumpMemory()
  581. {
  582. #ifdef ATOMIC_LOGGING
  583. #if defined(_MSC_VER) && defined(_DEBUG)
  584. _CrtMemState state;
  585. _CrtMemCheckpoint(&state);
  586. _CrtMemBlockHeader* block = state.pBlockHeader;
  587. unsigned total = 0;
  588. unsigned blocks = 0;
  589. for (;;)
  590. {
  591. if (block && block->pBlockHeaderNext)
  592. block = block->pBlockHeaderNext;
  593. else
  594. break;
  595. }
  596. while (block)
  597. {
  598. if (block->nBlockUse > 0)
  599. {
  600. if (block->szFileName)
  601. ATOMIC_LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes, file " + String(block->szFileName) + " line " + String(block->nLine) + "\n");
  602. else
  603. ATOMIC_LOGRAW("Block " + String((int)block->lRequest) + ": " + String(block->nDataSize) + " bytes\n");
  604. total += block->nDataSize;
  605. ++blocks;
  606. }
  607. block = block->pBlockHeaderPrev;
  608. }
  609. ATOMIC_LOGRAW("Total allocated memory " + String(total) + " bytes in " + String(blocks) + " blocks\n\n");
  610. #else
  611. ATOMIC_LOGRAW("DumpMemory() supported on MSVC debug mode only\n\n");
  612. #endif
  613. #endif
  614. }
  615. void Engine::Update()
  616. {
  617. if (exiting_)
  618. return;
  619. ATOMIC_PROFILE(Update);
  620. // Logic update event
  621. using namespace Update;
  622. VariantMap& eventData = GetEventDataMap();
  623. eventData[P_TIMESTEP] = timeStep_;
  624. SendEvent(E_UPDATE, eventData);
  625. // Logic post-update event
  626. SendEvent(E_POSTUPDATE, eventData);
  627. // Rendering update event
  628. SendEvent(E_RENDERUPDATE, eventData);
  629. // Post-render update event
  630. SendEvent(E_POSTRENDERUPDATE, eventData);
  631. }
  632. void Engine::Render()
  633. {
  634. if ( exiting_ )
  635. return;
  636. if (headless_)
  637. return;
  638. ATOMIC_PROFILE(Render);
  639. // If device is lost, BeginFrame will fail and we skip rendering
  640. Graphics* graphics = GetSubsystem<Graphics>();
  641. if (!graphics->BeginFrame())
  642. return;
  643. GetSubsystem<Renderer>()->Render();
  644. GetSubsystem<UI>()->Render();
  645. graphics->EndFrame();
  646. }
  647. void Engine::ApplyFrameLimit()
  648. {
  649. if (!initialized_)
  650. return;
  651. unsigned maxFps = maxFps_;
  652. Input* input = GetSubsystem<Input>();
  653. if (input && !input->HasFocus())
  654. maxFps = Min(maxInactiveFps_, maxFps);
  655. long long elapsed = 0;
  656. #ifndef __EMSCRIPTEN__
  657. // Perform waiting loop if maximum FPS set
  658. #ifndef IOS
  659. if (maxFps)
  660. #else
  661. // If on iOS and target framerate is 60 or above, just let the animation callback handle frame timing
  662. // instead of waiting ourselves
  663. if (maxFps < 60)
  664. #endif
  665. {
  666. ATOMIC_PROFILE(ApplyFrameLimit);
  667. long long targetMax = 1000000LL / maxFps;
  668. for (;;)
  669. {
  670. elapsed = frameTimer_.GetUSec(false);
  671. if (elapsed >= targetMax)
  672. break;
  673. // Sleep if 1 ms or more off the frame limiting goal
  674. if (targetMax - elapsed >= 1000LL)
  675. {
  676. unsigned sleepTime = (unsigned)((targetMax - elapsed) / 1000LL);
  677. Time::Sleep(sleepTime);
  678. }
  679. }
  680. }
  681. #endif
  682. elapsed = frameTimer_.GetUSec(true);
  683. #ifdef ATOMIC_TESTING
  684. if (timeOut_ > 0)
  685. {
  686. timeOut_ -= elapsed;
  687. if (timeOut_ <= 0)
  688. Exit();
  689. }
  690. #endif
  691. // If FPS lower than minimum, clamp elapsed time
  692. if (minFps_)
  693. {
  694. long long targetMin = 1000000LL / minFps_;
  695. if (elapsed > targetMin)
  696. elapsed = targetMin;
  697. }
  698. // Perform timestep smoothing
  699. timeStep_ = 0.0f;
  700. lastTimeSteps_.Push(elapsed / 1000000.0f);
  701. if (lastTimeSteps_.Size() > timeStepSmoothing_)
  702. {
  703. // If the smoothing configuration was changed, ensure correct amount of samples
  704. lastTimeSteps_.Erase(0, lastTimeSteps_.Size() - timeStepSmoothing_);
  705. for (unsigned i = 0; i < lastTimeSteps_.Size(); ++i)
  706. timeStep_ += lastTimeSteps_[i];
  707. timeStep_ /= lastTimeSteps_.Size();
  708. }
  709. else
  710. timeStep_ = lastTimeSteps_.Back();
  711. }
  712. VariantMap Engine::ParseParameters(const Vector<String>& arguments)
  713. {
  714. VariantMap ret;
  715. // Pre-initialize the parameters with environment variable values when they are set
  716. if (const char* paths = getenv("ATOMIC_PREFIX_PATH"))
  717. ret["ResourcePrefixPaths"] = paths;
  718. for (unsigned i = 0; i < arguments.Size(); ++i)
  719. {
  720. if (arguments[i].Length() > 1 && arguments[i][0] == '-')
  721. {
  722. String argument = arguments[i].Substring(1).ToLower();
  723. String value = i + 1 < arguments.Size() ? arguments[i + 1] : String::EMPTY;
  724. if (argument == "headless")
  725. ret["Headless"] = true;
  726. else if (argument == "nolimit")
  727. ret["FrameLimiter"] = false;
  728. else if (argument == "flushgpu")
  729. ret["FlushGPU"] = true;
  730. else if (argument == "gl2")
  731. ret["ForceGL2"] = true;
  732. else if (argument == "landscape")
  733. ret["Orientations"] = "LandscapeLeft LandscapeRight " + ret["Orientations"].GetString();
  734. else if (argument == "portrait")
  735. ret["Orientations"] = "Portrait PortraitUpsideDown " + ret["Orientations"].GetString();
  736. else if (argument == "nosound")
  737. ret["Sound"] = false;
  738. else if (argument == "noip")
  739. ret["SoundInterpolation"] = false;
  740. else if (argument == "mono")
  741. ret["SoundStereo"] = false;
  742. else if (argument == "prepass")
  743. ret["RenderPath"] = "RenderPaths/Prepass.xml";
  744. else if (argument == "deferred")
  745. ret["RenderPath"] = "RenderPaths/Deferred.xml";
  746. else if (argument == "renderpath" && !value.Empty())
  747. {
  748. ret["RenderPath"] = value;
  749. ++i;
  750. }
  751. else if (argument == "noshadows")
  752. ret["Shadows"] = false;
  753. else if (argument == "lqshadows")
  754. ret["LowQualityShadows"] = true;
  755. else if (argument == "nothreads")
  756. ret["WorkerThreads"] = false;
  757. else if (argument == "v")
  758. ret["VSync"] = true;
  759. else if (argument == "t")
  760. ret["TripleBuffer"] = true;
  761. else if (argument == "w")
  762. ret["FullScreen"] = false;
  763. else if (argument == "borderless")
  764. ret["Borderless"] = true;
  765. else if (argument == "s")
  766. ret["WindowResizable"] = true;
  767. else if (argument == "hd")
  768. ret["HighDPI"] = true;
  769. else if (argument == "q")
  770. ret["LogQuiet"] = true;
  771. else if (argument == "log" && !value.Empty())
  772. {
  773. unsigned logLevel = GetStringListIndex(value.CString(), logLevelPrefixes, M_MAX_UNSIGNED);
  774. if (logLevel != M_MAX_UNSIGNED)
  775. {
  776. ret["LogLevel"] = logLevel;
  777. ++i;
  778. }
  779. }
  780. else if (argument == "x" && !value.Empty())
  781. {
  782. ret["WindowWidth"] = ToInt(value);
  783. ++i;
  784. }
  785. else if (argument == "y" && !value.Empty())
  786. {
  787. ret["WindowHeight"] = ToInt(value);
  788. ++i;
  789. }
  790. else if (argument == "m" && !value.Empty())
  791. {
  792. ret["MultiSample"] = ToInt(value);
  793. ++i;
  794. }
  795. else if (argument == "b" && !value.Empty())
  796. {
  797. ret["SoundBuffer"] = ToInt(value);
  798. ++i;
  799. }
  800. else if (argument == "r" && !value.Empty())
  801. {
  802. ret["SoundMixRate"] = ToInt(value);
  803. ++i;
  804. }
  805. else if (argument == "pp" && !value.Empty())
  806. {
  807. ret["ResourcePrefixPaths"] = value;
  808. ++i;
  809. }
  810. else if (argument == "p" && !value.Empty())
  811. {
  812. ret["ResourcePaths"] = value;
  813. ++i;
  814. }
  815. else if (argument == "pf" && !value.Empty())
  816. {
  817. ret["ResourcePackages"] = value;
  818. ++i;
  819. }
  820. else if (argument == "ap" && !value.Empty())
  821. {
  822. ret["AutoloadPaths"] = value;
  823. ++i;
  824. }
  825. else if (argument == "ds" && !value.Empty())
  826. {
  827. ret["DumpShaders"] = value;
  828. ++i;
  829. }
  830. else if (argument == "mq" && !value.Empty())
  831. {
  832. ret["MaterialQuality"] = ToInt(value);
  833. ++i;
  834. }
  835. else if (argument == "tq" && !value.Empty())
  836. {
  837. ret["TextureQuality"] = ToInt(value);
  838. ++i;
  839. }
  840. else if (argument == "tf" && !value.Empty())
  841. {
  842. ret["TextureFilterMode"] = ToInt(value);
  843. ++i;
  844. }
  845. else if (argument == "af" && !value.Empty())
  846. {
  847. ret["TextureFilterMode"] = FILTER_ANISOTROPIC;
  848. ret["TextureAnisotropy"] = ToInt(value);
  849. ++i;
  850. }
  851. else if (argument == "touch")
  852. ret["TouchEmulation"] = true;
  853. // ATOMIC BEGIN
  854. else if (argument == "logname" && !value.Empty())
  855. {
  856. ret["LogName"] = value;
  857. ++i;
  858. }
  859. // ATOMIC END
  860. #ifdef ATOMIC_TESTING
  861. else if (argument == "timeout" && !value.Empty())
  862. {
  863. ret["TimeOut"] = ToInt(value);
  864. ++i;
  865. }
  866. #endif
  867. }
  868. }
  869. return ret;
  870. }
  871. bool Engine::HasParameter(const VariantMap& parameters, const String& parameter)
  872. {
  873. StringHash nameHash(parameter);
  874. return parameters.Find(nameHash) != parameters.End();
  875. }
  876. const Variant& Engine::GetParameter(const VariantMap& parameters, const String& parameter, const Variant& defaultValue)
  877. {
  878. StringHash nameHash(parameter);
  879. VariantMap::ConstIterator i = parameters.Find(nameHash);
  880. return i != parameters.End() ? i->second_ : defaultValue;
  881. }
  882. void Engine::HandleExitRequested(StringHash eventType, VariantMap& eventData)
  883. {
  884. if (autoExit_)
  885. {
  886. // Do not call Exit() here, as it contains mobile platform -specific tests to not exit.
  887. // If we do receive an exit request from the system on those platforms, we must comply
  888. DoExit();
  889. }
  890. }
  891. void Engine::DoExit()
  892. {
  893. Graphics* graphics = GetSubsystem<Graphics>();
  894. if (graphics)
  895. graphics->Close();
  896. exiting_ = true;
  897. #if defined(__EMSCRIPTEN__) && defined(ATOMIC_TESTING)
  898. emscripten_force_exit(EXIT_SUCCESS); // Some how this is required to signal emrun to stop
  899. #endif
  900. }
  901. // ATOMIC BEGIN
  902. void Engine::SetPaused(bool paused)
  903. {
  904. paused_ = paused;
  905. using namespace UpdatesPaused;
  906. // Updates paused event
  907. VariantMap& eventData = GetEventDataMap();
  908. eventData[P_PAUSED] = paused_;
  909. SendEvent(E_UPDATESPAUSEDRESUMED, eventData);
  910. }
  911. void Engine::SetRunNextPausedFrame(bool run)
  912. {
  913. runNextPausedFrame_ = run;
  914. }
  915. void Engine::HandlePauseResumeRequested(StringHash eventType, VariantMap& eventData)
  916. {
  917. SetPaused(!IsPaused());
  918. }
  919. void Engine::HandlePauseStepRequested(StringHash eventType, VariantMap& eventData)
  920. {
  921. if (IsPaused())
  922. {
  923. SetRunNextPausedFrame(true);
  924. }
  925. }
  926. bool Engine::GetDebugBuild() const
  927. {
  928. #ifdef ATOMIC_DEBUG
  929. return true;
  930. #else
  931. return false;
  932. #endif
  933. }
  934. // ATOMIC END
  935. }