screen_manager.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. // SuperTux
  2. // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
  3. // 2014 Ingo Ruhnke <grumbel@gmail.com>
  4. // 2021 A. Semphris <semphris@protonmail.com>
  5. //
  6. // This program is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // This program is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU General Public License
  17. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #include "supertux/screen_manager.hpp"
  19. #include "addon/addon_manager.hpp"
  20. #include "audio/sound_manager.hpp"
  21. #include "editor/editor.hpp"
  22. #include "editor/particle_editor.hpp"
  23. #include "gui/dialog.hpp"
  24. #include "gui/menu_manager.hpp"
  25. #include "gui/mousecursor.hpp"
  26. #include "object/player.hpp"
  27. #include "sdk/integration.hpp"
  28. #include "squirrel/squirrel_virtual_machine.hpp"
  29. #include "supertux/console.hpp"
  30. #include "supertux/constants.hpp"
  31. #include "supertux/controller_hud.hpp"
  32. #include "supertux/debug.hpp"
  33. #include "supertux/game_session.hpp"
  34. #include "supertux/gameconfig.hpp"
  35. #include "supertux/globals.hpp"
  36. #include "supertux/level.hpp"
  37. #include "supertux/menu/menu_storage.hpp"
  38. #include "supertux/resources.hpp"
  39. #include "supertux/screen_fade.hpp"
  40. #include "supertux/sector.hpp"
  41. #include "util/log.hpp"
  42. #include "video/compositor.hpp"
  43. #include "video/drawing_context.hpp"
  44. #include <stdio.h>
  45. #include <chrono>
  46. #include <iostream>
  47. #ifdef __EMSCRIPTEN__
  48. #include <emscripten.h>
  49. #include <emscripten/html5.h>
  50. #endif
  51. struct ScreenManager::FPS_Stats
  52. {
  53. FPS_Stats():
  54. measurements_cnt(0),
  55. acc_us(0),
  56. min_us(1000000),
  57. max_us(0),
  58. last_fps(0),
  59. last_fps_min(0),
  60. last_fps_max(0),
  61. // Use chrono instead of SDL_GetTicks for more precise FPS measurement
  62. time_prev(std::chrono::steady_clock::now())
  63. {
  64. }
  65. void report_frame()
  66. {
  67. auto time_now = std::chrono::steady_clock::now();
  68. int dtime_us = static_cast<int>(std::chrono::duration_cast<
  69. std::chrono::microseconds>(time_now - time_prev).count());
  70. assert(dtime_us >= 0); // Steady clock.
  71. if (dtime_us == 0)
  72. return;
  73. time_prev = time_now;
  74. acc_us += dtime_us;
  75. ++measurements_cnt;
  76. if (min_us > dtime_us)
  77. min_us = dtime_us;
  78. if (max_us < dtime_us)
  79. max_us = dtime_us;
  80. float expired_seconds = static_cast<float>(acc_us) / 1000000.0f;
  81. if (expired_seconds < 0.5f)
  82. return;
  83. // Update values to be printed every 0.5 s
  84. assert(measurements_cnt > 0); // ++measurements_cnt above.
  85. last_fps = static_cast<float>(measurements_cnt) / expired_seconds;
  86. assert(last_fps > 0); // measurements_cnt > 0 and expired_seconds >= 0.5f.
  87. assert(max_us > 0); // dtime_us > 0.
  88. last_fps_min = 1000000.0f / static_cast<float>(max_us);
  89. assert(last_fps_min > 0); // max_us > 0.
  90. assert(min_us > 0); // initialization to 1000000 and dtime_us > 0.
  91. last_fps_max = 1000000.0f / static_cast<float>(min_us);
  92. assert(last_fps_max > 0); // min_us > 0.
  93. measurements_cnt = 0;
  94. acc_us = 0;
  95. min_us = 1000000;
  96. max_us = 0;
  97. }
  98. float get_fps() const { return last_fps; }
  99. float get_fps_min() const { return last_fps_min; }
  100. float get_fps_max() const { return last_fps_max; }
  101. // This returns the highest measured delay between two frames from the
  102. // previous and current 0.5 s measuring intervals
  103. float get_highest_max_ms() const
  104. {
  105. float previous_max_ms = 1000.0f / last_fps_min;
  106. if (measurements_cnt > 0) {
  107. float current_max_ms = static_cast<float>(max_us) / 1000.0f;
  108. return std::max<float>(previous_max_ms, current_max_ms);
  109. }
  110. return previous_max_ms;
  111. }
  112. private:
  113. int measurements_cnt;
  114. int acc_us;
  115. int min_us;
  116. int max_us;
  117. float last_fps;
  118. float last_fps_min;
  119. float last_fps_max;
  120. std::chrono::steady_clock::time_point time_prev;
  121. };
  122. ScreenManager::ScreenManager(VideoSystem& video_system, InputManager& input_manager) :
  123. m_video_system(video_system),
  124. m_input_manager(input_manager),
  125. m_menu_storage(new MenuStorage),
  126. m_menu_manager(new MenuManager()),
  127. m_controller_hud(new ControllerHUD),
  128. m_mobile_controller(),
  129. last_ticks(0),
  130. elapsed_ticks(0),
  131. ms_per_step(static_cast<Uint32>(1000.0f / LOGICAL_FPS)),
  132. seconds_per_step(static_cast<float>(ms_per_step) / 1000.0f),
  133. m_fps_statistics(new FPS_Stats()),
  134. m_speed(1.0),
  135. m_actions(),
  136. m_screen_fade(),
  137. m_screen_stack()
  138. {
  139. }
  140. ScreenManager::~ScreenManager()
  141. {
  142. }
  143. void
  144. ScreenManager::push_screen(std::unique_ptr<Screen> screen, std::unique_ptr<ScreenFade> screen_fade)
  145. {
  146. log_debug << "ScreenManager::push_screen(): " << screen.get() << std::endl;
  147. assert(screen);
  148. set_screen_fade(std::move(screen_fade));
  149. m_actions.emplace_back(Action::PUSH_ACTION, std::move(screen));
  150. }
  151. void
  152. ScreenManager::pop_screen(std::unique_ptr<ScreenFade> screen_fade)
  153. {
  154. log_debug << "ScreenManager::pop_screen(): stack_size: " << m_screen_stack.size() << std::endl;
  155. set_screen_fade(std::move(screen_fade));
  156. m_actions.emplace_back(Action::POP_ACTION);
  157. }
  158. void
  159. ScreenManager::set_screen_fade(std::unique_ptr<ScreenFade> screen_fade)
  160. {
  161. if (g_config->transitions_enabled)
  162. {
  163. m_screen_fade = std::move(screen_fade);
  164. }
  165. }
  166. void
  167. ScreenManager::quit(std::unique_ptr<ScreenFade> screen_fade)
  168. {
  169. Integration::close_all();
  170. #ifdef __EMSCRIPTEN__
  171. g_config->save();
  172. #endif
  173. set_screen_fade(std::move(screen_fade));
  174. m_actions.emplace_back(Action::QUIT_ACTION);
  175. }
  176. void
  177. ScreenManager::set_speed(float speed)
  178. {
  179. m_speed = speed;
  180. }
  181. float
  182. ScreenManager::get_speed() const
  183. {
  184. return m_speed;
  185. }
  186. void
  187. ScreenManager::draw_fps(DrawingContext& context, FPS_Stats& fps_statistics)
  188. {
  189. // The fonts are not monospace, so the numbers need to be drawn separately
  190. Vector pos(context.get_width() - BORDER_X, BORDER_Y + 50);
  191. context.color().draw_text(Resources::small_font, "FPS min / avg / max",
  192. pos, ALIGN_RIGHT, LAYER_HUD);
  193. static const float w2 = Resources::small_font->get_text_width("999.9 /");
  194. static const float w3 = Resources::small_font->get_text_width("999.9");
  195. char str1[60];
  196. char str2[60];
  197. char str3[60];
  198. int str_length = sizeof(str1);
  199. snprintf(str1, str_length, "%3.1f /",
  200. static_cast<double>(fps_statistics.get_fps_min()));
  201. snprintf(str2, str_length, "%3.1f /",
  202. static_cast<double>(fps_statistics.get_fps()));
  203. snprintf(str3, str_length, "%3.1f",
  204. static_cast<double>(fps_statistics.get_fps_max()));
  205. pos.y += 15;
  206. context.color().draw_text(Resources::small_font, str3,
  207. pos, ALIGN_RIGHT, LAYER_HUD);
  208. pos.x -= w3;
  209. context.color().draw_text(Resources::small_font, str2,
  210. pos, ALIGN_RIGHT, LAYER_HUD);
  211. pos.x -= w2;
  212. context.color().draw_text(Resources::small_font, str1,
  213. pos, ALIGN_RIGHT, LAYER_HUD);
  214. }
  215. void
  216. ScreenManager::draw_player_pos(DrawingContext& context)
  217. {
  218. if (auto session = GameSession::current())
  219. {
  220. Sector& sector = session->get_current_sector();
  221. float height = 0;
  222. for (const auto* p : sector.get_players())
  223. {
  224. auto pos = p->get_pos();
  225. auto pos_text = "X:" + std::to_string(int(pos.x)) + " Y:" + std::to_string(int(pos.y));
  226. context.color().draw_text(
  227. Resources::small_font, pos_text,
  228. Vector(context.get_width() - Resources::small_font->get_text_width("99999x99999") - BORDER_X,
  229. BORDER_Y + 60 + height), ALIGN_LEFT, LAYER_HUD);
  230. height += 30;
  231. }
  232. }
  233. }
  234. void
  235. ScreenManager::draw(Compositor& compositor, FPS_Stats& fps_statistics)
  236. {
  237. assert(!m_screen_stack.empty());
  238. // draw the actual screen
  239. m_screen_stack.back()->draw(compositor);
  240. // draw effects and hud
  241. auto& context = compositor.make_context(true);
  242. m_menu_manager->draw(context);
  243. if (m_screen_fade) {
  244. m_screen_fade->draw(context);
  245. }
  246. Console::current()->draw(context);
  247. if (g_config->mobile_controls)
  248. m_mobile_controller.draw(context);
  249. if (g_config->show_fps)
  250. draw_fps(context, fps_statistics);
  251. if (g_config->show_controller) {
  252. m_controller_hud->draw(context);
  253. }
  254. if (g_config->show_player_pos) {
  255. draw_player_pos(context);
  256. }
  257. // render everything
  258. compositor.render();
  259. }
  260. void
  261. ScreenManager::update_gamelogic(float dt_sec)
  262. {
  263. Controller& controller = m_input_manager.get_controller();
  264. if (g_config->mobile_controls)
  265. {
  266. m_mobile_controller.update();
  267. m_mobile_controller.apply(controller);
  268. }
  269. SquirrelVirtualMachine::current()->update(g_game_time);
  270. if (!m_screen_stack.empty())
  271. {
  272. m_screen_stack.back()->update(dt_sec, controller);
  273. }
  274. m_menu_manager->process_input(controller);
  275. if (m_screen_fade)
  276. {
  277. m_screen_fade->update(dt_sec);
  278. }
  279. Console::current()->update(dt_sec);
  280. }
  281. void
  282. ScreenManager::process_events()
  283. {
  284. m_input_manager.update();
  285. SDL_Event event;
  286. auto session = GameSession::current();
  287. while (SDL_PollEvent(&event))
  288. {
  289. switch (event.type)
  290. {
  291. case SDL_FINGERDOWN:
  292. {
  293. SDL_Event old_event = event;
  294. SDL_Event event2;
  295. if (m_mobile_controller.process_finger_down_event(event.tfinger))
  296. break; // Event was processed by touch controls, do not generate mouse event
  297. event2.type = SDL_MOUSEBUTTONDOWN;
  298. event2.button.button = SDL_BUTTON_LEFT;
  299. event2.button.x = Sint32(old_event.tfinger.x * float(m_video_system.get_window_size().width));
  300. event2.button.y = Sint32(old_event.tfinger.y * float(m_video_system.get_window_size().height));
  301. SDL_PushEvent(&event2);
  302. event.type = SDL_MOUSEMOTION;
  303. event.motion.x = event2.button.x;
  304. event.motion.y = event2.button.y;
  305. MouseCursor::current()->set_pos(event.button.x, event.button.y);
  306. break;
  307. }
  308. case SDL_FINGERUP:
  309. {
  310. SDL_Event old_event = event;
  311. // Always generate mouse up event, because the finger can generate mouse click
  312. // and then move to the screen button, and the mouse button will stay pressed
  313. SDL_Event event2;
  314. event2.type = SDL_MOUSEBUTTONUP;
  315. event2.button.button = SDL_BUTTON_LEFT;
  316. event2.button.x = Sint32(old_event.tfinger.x * float(m_video_system.get_window_size().width));
  317. event2.button.y = Sint32(old_event.tfinger.y * float(m_video_system.get_window_size().height));
  318. SDL_PushEvent(&event2);
  319. if (m_mobile_controller.process_finger_up_event(event.tfinger))
  320. break; // Event was processed by touch controls, do not generate mouse event
  321. event.type = SDL_MOUSEMOTION;
  322. event.motion.x = event2.button.x;
  323. event.motion.y = event2.button.y;
  324. MouseCursor::current()->set_pos(event.button.x, event.button.y);
  325. break;
  326. }
  327. case SDL_FINGERMOTION:
  328. SDL_Event old_event = event;
  329. if (m_mobile_controller.process_finger_motion_event(event.tfinger))
  330. break; // Event was processed by touch controls, do not generate mouse event
  331. event.type = SDL_MOUSEMOTION;
  332. event.motion.x = Sint32(old_event.tfinger.x * float(m_video_system.get_window_size().width));
  333. event.motion.y = Sint32(old_event.tfinger.y * float(m_video_system.get_window_size().height));
  334. event.motion.xrel = Sint32(old_event.tfinger.dx * float(m_video_system.get_window_size().width));
  335. event.motion.yrel = Sint32(old_event.tfinger.dy * float(m_video_system.get_window_size().height));
  336. MouseCursor::current()->set_pos(event.motion.x, event.motion.y);
  337. break;
  338. }
  339. m_input_manager.process_event(event);
  340. m_menu_manager->event(event);
  341. if (Editor::is_active()) {
  342. Editor::current()->event(event);
  343. }
  344. if (ParticleEditor::is_active()) {
  345. ParticleEditor::current()->event(event);
  346. }
  347. switch (event.type)
  348. {
  349. case SDL_QUIT:
  350. quit();
  351. break;
  352. case SDL_WINDOWEVENT:
  353. switch (event.window.event)
  354. {
  355. case SDL_WINDOWEVENT_RESIZED:
  356. m_video_system.on_resize(event.window.data1, event.window.data2);
  357. m_menu_manager->on_window_resize();
  358. if (Editor::is_active()) {
  359. Editor::current()->resize();
  360. }
  361. break;
  362. case SDL_WINDOWEVENT_HIDDEN:
  363. case SDL_WINDOWEVENT_FOCUS_LOST:
  364. if (g_config->pause_on_focusloss)
  365. {
  366. if (session != nullptr && session->is_active() && !Level::current()->m_suppress_pause_menu)
  367. {
  368. session->toggle_pause();
  369. }
  370. }
  371. break;
  372. }
  373. break;
  374. case SDL_KEYDOWN:
  375. if (event.key.keysym.sym == SDLK_F10)
  376. {
  377. g_config->show_fps = !g_config->show_fps;
  378. }
  379. #ifndef EMSCRIPTEN // Emscripten builds manage this through JS code
  380. else if (event.key.keysym.sym == SDLK_F11 ||
  381. ((event.key.keysym.mod & KMOD_LALT || event.key.keysym.mod & KMOD_RALT) &&
  382. (event.key.keysym.sym == SDLK_KP_ENTER || event.key.keysym.sym == SDLK_RETURN)))
  383. {
  384. g_config->use_fullscreen = !g_config->use_fullscreen;
  385. m_video_system.apply_config();
  386. m_menu_manager->on_window_resize();
  387. }
  388. #endif
  389. #ifdef STEAM_BUILD
  390. // Shift+Tab opens the overlay; pause the game
  391. else if (event.key.keysym.sym == SDLK_TAB &&
  392. (event.key.keysym.mod & KMOD_LSHIFT || event.key.keysym.mod & KMOD_RSHIFT))
  393. {
  394. if (session != nullptr && session->is_active() && !Level::current()->m_suppress_pause_menu)
  395. {
  396. session->toggle_pause();
  397. }
  398. }
  399. #endif
  400. else if (event.key.keysym.sym == SDLK_PRINTSCREEN ||
  401. event.key.keysym.sym == SDLK_F12)
  402. {
  403. m_video_system.do_take_screenshot();
  404. }
  405. else if (event.key.keysym.sym == SDLK_F2 &&
  406. event.key.keysym.mod & KMOD_CTRL)
  407. {
  408. g_config->developer_mode = !g_config->developer_mode;
  409. log_info << "developer mode: " << g_config->developer_mode << std::endl;
  410. }
  411. break;
  412. // NOTE: Steam recommends leaving this behavior in. If it turns out to be
  413. // impractical for users, please add `#ifdef STEAM_BUILD` code around it.
  414. case SDL_JOYDEVICEREMOVED:
  415. case SDL_CONTROLLERDEVICEREMOVED:
  416. if (session != nullptr && session->is_active() && !Level::current()->m_suppress_pause_menu)
  417. {
  418. session->toggle_pause();
  419. }
  420. break;
  421. }
  422. }
  423. }
  424. bool
  425. ScreenManager::has_pending_fadeout() const
  426. {
  427. return m_screen_fade && !m_screen_fade->done();
  428. }
  429. void
  430. ScreenManager::handle_screen_switch()
  431. {
  432. if (has_pending_fadeout())
  433. {
  434. // wait till the fadeout is completed before switching screens
  435. }
  436. else
  437. {
  438. m_screen_fade.reset();
  439. // Screen::setup() might push more screens, so loop till everything is done
  440. while (!m_actions.empty())
  441. {
  442. // keep track of the current screen, as only that needs a call to Screen::leave()
  443. auto current_screen = m_screen_stack.empty() ? nullptr : m_screen_stack.back().get();
  444. // move actions to a new vector since setup() might modify it
  445. auto actions = std::move(m_actions);
  446. bool quit_action_triggered = false;
  447. for (auto& action : actions)
  448. {
  449. switch (action.type)
  450. {
  451. case Action::POP_ACTION:
  452. assert(!m_screen_stack.empty());
  453. if (current_screen == m_screen_stack.back().get())
  454. {
  455. m_screen_stack.back()->leave();
  456. current_screen = nullptr;
  457. }
  458. m_screen_stack.pop_back();
  459. break;
  460. case Action::PUSH_ACTION:
  461. assert(action.screen);
  462. m_screen_stack.push_back(std::move(action.screen));
  463. break;
  464. case Action::QUIT_ACTION:
  465. m_screen_stack.clear();
  466. current_screen = nullptr;
  467. quit_action_triggered = true;
  468. break;
  469. }
  470. }
  471. if (!quit_action_triggered)
  472. {
  473. if (current_screen != m_screen_stack.back().get())
  474. {
  475. g_debug.set_game_speed_multiplier(1.f);
  476. if (current_screen != nullptr)
  477. {
  478. current_screen->leave();
  479. }
  480. if (!m_screen_stack.empty())
  481. {
  482. m_screen_stack.back()->setup();
  483. m_speed = 1.0;
  484. SquirrelVirtualMachine::current()->wakeup_screenswitch();
  485. }
  486. }
  487. }
  488. }
  489. }
  490. }
  491. void ScreenManager::loop_iter()
  492. {
  493. Uint32 ticks = SDL_GetTicks();
  494. elapsed_ticks += ticks - last_ticks;
  495. last_ticks = ticks;
  496. if (elapsed_ticks > ms_per_step * 8) {
  497. // when the game loads up or levels are switched the
  498. // elapsed_ticks grows extremely large, so we just ignore those
  499. // large time jumps
  500. elapsed_ticks = 0;
  501. }
  502. if (elapsed_ticks < ms_per_step && !g_debug.draw_redundant_frames) {
  503. // Sleep a bit because not enough time has passed since the previous
  504. // logical game step
  505. SDL_Delay(ms_per_step - elapsed_ticks);
  506. return;
  507. }
  508. // Useful if screens edit their status without switching screens
  509. Integration::update_status_all(m_screen_stack.back()->get_status());
  510. Integration::update_all();
  511. g_real_time = static_cast<float>(ticks) / 1000.0f;
  512. float speed_multiplier = g_debug.get_game_speed_multiplier();
  513. int steps = elapsed_ticks / ms_per_step;
  514. // Do not calculate more than a few steps at once
  515. // The maximum number of steps executed before drawing a frame is
  516. // adjusted to the current average frame rate
  517. float fps = m_fps_statistics->get_fps();
  518. if (fps != 0) {
  519. // Skip if fps not ready yet (during first 0.5 seconds of startup).
  520. float seconds_per_frame = 1.0f / fps;
  521. int max_steps_per_frame = static_cast<int>(
  522. ceilf(seconds_per_frame / seconds_per_step));
  523. if (max_steps_per_frame < 2)
  524. // max_steps_per_frame is very negative when the fps value is zero
  525. // Furthermore, the game should always be able to execute
  526. // up to two steps before drawing a frame
  527. max_steps_per_frame = 2;
  528. if (max_steps_per_frame > 4)
  529. // When the game is very laggy, it should slow down instead of
  530. // calculating lots of steps at once so that the player can still
  531. // control Tux reasonably;
  532. // four steps per frame approximately corresponds to a 16 FPS gameplay
  533. max_steps_per_frame = 4;
  534. steps = std::min<int>(steps, max_steps_per_frame);
  535. }
  536. for (int i = 0; i < steps; ++i) {
  537. // Perform a logical game step; seconds_per_step is set to a fixed value
  538. // so that the game is deterministic.
  539. // In cases which don't affect regular gameplay, such as the
  540. // end sequence and debugging, dtime can be changed.
  541. float dtime = seconds_per_step * m_speed * speed_multiplier;
  542. g_game_time += dtime;
  543. process_events();
  544. update_gamelogic(dtime);
  545. elapsed_ticks -= ms_per_step;
  546. }
  547. if ((steps > 0 && !m_screen_stack.empty())
  548. || g_debug.draw_redundant_frames) {
  549. // Draw a frame
  550. Compositor compositor(m_video_system);
  551. draw(compositor, *m_fps_statistics);
  552. m_fps_statistics->report_frame();
  553. }
  554. SoundManager::current()->update();
  555. handle_screen_switch();
  556. #ifdef EMSCRIPTEN
  557. EM_ASM({
  558. supertux2_syncfs();
  559. }, 0); // EM_ASM is a variadic macro and Clang requires at least 1 value for the variadic argument
  560. #endif
  561. }
  562. #ifdef __EMSCRIPTEN__
  563. static void g_loop_iter() {
  564. auto screen_manager = ScreenManager::current();
  565. screen_manager->loop_iter();
  566. }
  567. #endif
  568. void
  569. ScreenManager::run()
  570. {
  571. Integration::init_all();
  572. handle_screen_switch();
  573. #ifdef __EMSCRIPTEN__
  574. emscripten_set_main_loop(g_loop_iter, -1, 1);
  575. #else
  576. while (!m_screen_stack.empty()) {
  577. loop_iter();
  578. }
  579. #endif
  580. }
  581. /* EOF */