Engine.cpp 34 KB

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