1 //
2 // SuperTuxKart - a fun racing game with go-kart
3 // Copyright (C) 2004-2015 Ingo Ruhnke <grumbel@gmx.de>
4 // Copyright (C) 2006-2015 SuperTuxKart-Team
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 3
9 // of the License, or (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, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 #include "main_loop.hpp"
21
22 #ifdef MOBILE_STK
23 #include "addons/addons_manager.hpp"
24 #endif
25 #include "audio/music_manager.hpp"
26 #include "audio/sfx_manager.hpp"
27 #include "config/player_manager.hpp"
28 #include "config/user_config.hpp"
29 #include "graphics/central_settings.hpp"
30 #include "graphics/irr_driver.hpp"
31 #include "graphics/material_manager.hpp"
32 #include "graphics/sp/sp_texture_manager.hpp"
33 #include "guiengine/engine.hpp"
34 #include "guiengine/message_queue.hpp"
35 #include "guiengine/modaldialog.hpp"
36 #include "guiengine/screen_keyboard.hpp"
37 #include "input/input_manager.hpp"
38 #include "modes/world.hpp"
39 #include "modes/profile_world.hpp"
40 #include "network/network_config.hpp"
41 #include "network/network_timer_synchronizer.hpp"
42 #include "network/protocols/client_lobby.hpp"
43 #include "network/protocols/game_protocol.hpp"
44 #include "network/protocol_manager.hpp"
45 #include "network/race_event_manager.hpp"
46 #include "network/rewind_manager.hpp"
47 #include "network/server.hpp"
48 #include "network/stk_host.hpp"
49 #include "online/request_manager.hpp"
50 #include "race/history.hpp"
51 #include "race/race_manager.hpp"
52 #include "states_screens/dialogs/server_info_dialog.hpp"
53 #include "states_screens/online/server_selection.hpp"
54 #include "states_screens/state_manager.hpp"
55 #include "utils/profiler.hpp"
56 #include "utils/string_utils.hpp"
57 #include "utils/time.hpp"
58 #include "utils/translation.hpp"
59
60 #ifndef WIN32
61 #include <unistd.h>
62 #endif
63
64 MainLoop* main_loop = 0;
65
66 #ifdef WIN32
separateProcessProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ WPARAM wParam,_In_ LPARAM lParam)67 LRESULT CALLBACK separateProcessProc(_In_ HWND hwnd, _In_ UINT uMsg,
68 _In_ WPARAM wParam, _In_ LPARAM lParam)
69 {
70 if (uMsg == WM_DESTROY)
71 {
72 PostQuitMessage(0);
73 return 0;
74 }
75 return DefWindowProc(hwnd, uMsg, wParam, lParam);
76 };
77 #endif
78
79 // ----------------------------------------------------------------------------
MainLoop(unsigned parent_pid,bool download_assets)80 MainLoop::MainLoop(unsigned parent_pid, bool download_assets)
81 : m_abort(false), m_request_abort(false), m_paused(false),
82 m_ticks_adjustment(0), m_parent_pid(parent_pid)
83 {
84 m_curr_time = 0;
85 m_prev_time = 0;
86 m_throttle_fps = true;
87 m_allow_large_dt = false;
88 m_frame_before_loading_world = false;
89 m_download_assets = download_assets;
90 #ifdef WIN32
91 if (parent_pid != 0)
92 {
93 core::stringw class_name = L"separate_process";
94 class_name += StringUtils::toWString(GetCurrentProcessId());
95 WNDCLASSEX wx = {};
96 wx.cbSize = sizeof(WNDCLASSEX);
97 wx.lpfnWndProc = separateProcessProc;
98 wx.hInstance = GetModuleHandle(0);
99 wx.lpszClassName = class_name.c_str();
100 if (RegisterClassEx(&wx))
101 {
102 CreateWindowEx(0, class_name.c_str(), L"stk_server_only",
103 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
104 }
105 }
106 #endif
107 } // MainLoop
108
109 //-----------------------------------------------------------------------------
~MainLoop()110 MainLoop::~MainLoop()
111 {
112 } // ~MainLoop
113
114 #ifdef MOBILE_STK
115 //-----------------------------------------------------------------------------
pause_mainloop()116 extern "C" void pause_mainloop()
117 {
118 if (!main_loop)
119 return;
120 if (music_manager)
121 music_manager->pauseMusic();
122 if (SFXManager::get())
123 SFXManager::get()->pauseAll();
124 PlayerManager::get()->save();
125 if (addons_manager->hasDownloadedIcons())
126 addons_manager->saveInstalled();
127 Online::RequestManager::get()->setPaused(true);
128 IrrlichtDevice* dev = irr_driver->getDevice();
129 if (dev)
130 dev->resetPaused();
131 } // pause_mainloop
132
133 //-----------------------------------------------------------------------------
resume_mainloop()134 extern "C" void resume_mainloop()
135 {
136 if (!main_loop)
137 return;
138 IrrlichtDevice* dev = irr_driver->getDevice();
139 if (dev)
140 dev->resetUnpaused();
141 if (music_manager)
142 music_manager->resumeMusic();
143 if (SFXManager::get())
144 SFXManager::get()->resumeAll();
145 // Improve rubber banding effects of rewinders when going
146 // back to phone, because the smooth timer is paused
147 if (World::getWorld() && RewindManager::isEnabled())
148 RewindManager::get()->resetSmoothNetworkBody();
149 Online::RequestManager::get()->setPaused(false);
150 } // resume_mainloop
151 #endif
152
153 //-----------------------------------------------------------------------------
154 /** Returns the current dt, which guarantees a limited frame rate. If dt is
155 * too low (the frame rate too high), the process will sleep to reach the
156 * maximum frame rate.
157 */
getLimitedDt()158 float MainLoop::getLimitedDt()
159 {
160 m_prev_time = m_curr_time;
161
162 #ifdef IOS_STK
163 if (m_paused.load())
164 {
165 // When iOS apps entering background it should not run any
166 // opengl command from apple document, so we stop here
167 pause_mainloop();
168 while (true)
169 {
170 // iOS will pause this thread completely when fully in background
171 IrrlichtDevice* dev = irr_driver->getDevice();
172 bool quit = false;
173 if (dev)
174 quit = !dev->run();
175 if (quit || !m_paused.load())
176 {
177 if (quit)
178 return 1.0f/60.0f;
179 break;
180 }
181 }
182 resume_mainloop();
183 }
184 #endif
185 float dt = 0;
186
187 // In profile mode without graphics, run with a fixed dt of 1/60
188 if ((ProfileWorld::isProfileMode() && GUIEngine::isNoGraphics()) ||
189 UserConfigParams::m_arena_ai_stats)
190 {
191 return 1.0f/60.0f;
192 }
193
194 while( 1 )
195 {
196 m_curr_time = StkTime::getMonoTimeMs();
197 if (m_prev_time > m_curr_time)
198 {
199 m_prev_time = m_curr_time;
200 // If system time adjusted backwards, return fixed dt and
201 // resynchronize network timer if exists in client
202 if (STKHost::existHost())
203 {
204 #ifndef SERVER_ONLY
205 if (UserConfigParams::m_artist_debug_mode &&
206 !GUIEngine::isNoGraphics())
207 {
208 core::stringw err = L"System clock running backwards in"
209 " networking game.";
210 MessageQueue::add(MessageQueue::MT_ERROR, err);
211 }
212 #endif
213 Log::error("MainLoop", "System clock running backwards in"
214 " networking game.");
215 if (STKHost::get()->getNetworkTimerSynchronizer())
216 {
217 STKHost::get()->getNetworkTimerSynchronizer()
218 ->resynchroniseTimer();
219 }
220 }
221 }
222 dt = (float)(m_curr_time - m_prev_time);
223 // On a server (i.e. without graphics) the frame rate can be under
224 // 1 ms, i.e. dt = 0. Additionally, the resolution of a sleep
225 // statement is not that precise either: if the sleep statement
226 // would be consistent < 1ms, but the stk time would increase by
227 // 1 ms, the stk clock would be desynchronised from real time
228 // (it would go faster), resulting in synchronisation problems
229 // with clients (server time is supposed to be behind client time).
230 // So we play it safe by adding a loop to make sure at least 1ms
231 // (minimum time that can be handled by the integer timer) delay here.
232 while (dt == 0)
233 {
234 StkTime::sleep(1);
235 m_curr_time = StkTime::getMonoTimeMs();
236 if (m_prev_time > m_curr_time)
237 {
238 Log::error("MainLopp", "System clock keeps backwards!");
239 m_prev_time = m_curr_time;
240 }
241 dt = (float)(m_curr_time - m_prev_time);
242 }
243
244 const World* const world = World::getWorld();
245 if (UserConfigParams::m_fps_debug && world)
246 {
247 const LinearWorld *lw = dynamic_cast<const LinearWorld*>(world);
248 if (lw)
249 {
250 Log::verbose("fps", "time %f distance %f dt %f fps %f",
251 lw->getTime(),
252 lw->getDistanceDownTrackForKart(0, true),
253 dt*0.001f, 1000.0f / dt);
254 }
255 else
256 {
257 Log::verbose("fps", "time %f dt %f fps %f",
258 world->getTime(), dt*0.001f, 1000.0f / dt);
259 }
260
261 }
262
263 // Don't allow the game to run slower than a certain amount.
264 // when the computer can't keep it up, slow down the shown time instead
265 // But this can not be done in networking, otherwise the game time on
266 // client and server will not be in synch anymore
267 if ((!NetworkConfig::get()->isNetworking() || !World::getWorld()) &&
268 !m_allow_large_dt)
269 {
270 /* time 3 internal substeps take */
271 const float MAX_ELAPSED_TIME = 3.0f*1.0f / 60.0f*1000.0f;
272 if (dt > MAX_ELAPSED_TIME) dt = MAX_ELAPSED_TIME;
273 }
274 if (!m_throttle_fps || ProfileWorld::isProfileMode()) break;
275
276 // Throttle fps if more than maximum, which can reduce
277 // the noise the fan on a graphics card makes.
278 // When in menus, reduce FPS much, it's not necessary to push to the
279 // maximum for plain menus
280 #ifdef IOS_STK
281 // For iOS devices most at locked at 60, for new iPad Pro supports 120
282 // which is currently m_max_fps
283 const int max_fps =
284 UserConfigParams::m_swap_interval == 2 ? 30 :
285 UserConfigParams::m_swap_interval == 1 ? 60 :
286 UserConfigParams::m_max_fps;
287 #else
288 const int max_fps = (irr_driver->isRecording() &&
289 UserConfigParams::m_limit_game_fps )
290 ? UserConfigParams::m_record_fps
291 : ( StateManager::get()->throttleFPS()
292 ? 60
293 : UserConfigParams::m_max_fps );
294 #endif
295 const int current_fps = (int)(1000.0f / dt);
296 if (!m_throttle_fps || current_fps <= max_fps ||
297 ProfileWorld::isProfileMode() ) break;
298
299 int wait_time = 1000 / max_fps - 1000 / current_fps;
300 if (wait_time < 1) wait_time = 1;
301
302 PROFILER_PUSH_CPU_MARKER("Throttle framerate", 0, 0, 0);
303 StkTime::sleep(wait_time);
304 PROFILER_POP_CPU_MARKER();
305 } // while(1)
306
307 dt *= 0.001f;
308 return dt;
309 } // getLimitedDt
310
311 //-----------------------------------------------------------------------------
312 /** Updates all race related objects.
313 * \param ticks Number of ticks (physics steps) to simulate - should be 1.
314 * \param fast_forward If true, then only rewinders in network will be
315 * updated, but not the physics.
316 */
updateRace(int ticks,bool fast_forward)317 void MainLoop::updateRace(int ticks, bool fast_forward)
318 {
319 if (!World::getWorld()) return; // No race on atm - i.e. we are in menu
320
321 // The race event manager will update world in case of an online race
322 if (RaceEventManager::get() && RaceEventManager::get()->isRunning())
323 RaceEventManager::get()->update(ticks, fast_forward);
324 else
325 World::getWorld()->updateWorld(ticks);
326 } // updateRace
327
328 //-----------------------------------------------------------------------------
329 /** Run the actual main loop.
330 * The sequnce in which various parts of STK are updated is:
331 * - Determine next time step size (`getLimitedDt`). This takes maximum fps
332 * into account (i.e. sleep if the fps would be too high), and will actually
333 * slow down the in-game clock if the fps are too low (if more than 3/60 of
334 * a second have passed, more than 3 physics time steps would be needed,
335 * and physics do at most 3 time steps).
336 * - if a race is taking place (i.e. not only a menu being shown), call
337 * `updateRace()`, which is a thin wrapper around a call to
338 * `World::updateWorld()`:
339 * - Update history manager (which will either set the kart position and/or
340 * controls when replaying, or store the current info for a replay).
341 * This is mostly for debugging only (though available even in release
342 * mode).
343 * - Updates Replays - either storing data when not replaying, or
344 * updating kart positions/control when replaying).
345 * - Calls `WorldStatus::update()`, which updates the race state (e.g.
346 * go from 'ready' to 'set' etc), and clock.
347 * - Updates the physics (`Physics::update()`). This will simulate all
348 * physical objects for the specified time with bullet.
349 * - Updates all karts (`Kart::update()`). Obviously the update function
350 * does a lot more than what is described here, this is only supposed to
351 * be a _very_ high level overview:
352 * - Updates its rewinder (to store potentially changed controls
353 * as events) in `KartRewinder::update()`.
354 * - Calls `Moveable::update()`, which takes the new position from
355 * the physics and saves it (and computes dependent values, like
356 * heading, local velocity).
357 * - Updates its controller. This is either:
358 * - an AI using `SkiddingController::update()` (which then will
359 * compute the new controls), or
360 * - a player controller using `PlayerController::update()`, which will
361 * handle smooth steering (in case of digital input devices steering
362 * is adjusted a bit over time to avoid an instant change from all
363 * left to all right). Input events will be handled when updating
364 * the irrlicht driver later at the end of the main loop.
365 * - Updates kart animation (like rescue, ...) if one is shown atm.
366 * - Update attachments.
367 * - update physics, i.e. taking the current steering and updating
368 * the bullet raycast vehicle with that data. The settings are actually
369 * only used in the next frame when the physics are updated.
370 * - Updates all cameras via `Camera::update()`. The camera position and
371 * rotation is adjusted according to the position etc of the kart (and
372 * special circumstances like rescue, falling).
373 * - Updates all projectiles using the projectile manager. Some of the
374 * projectiles are mostly handled by the physics (e.g. a cake will mainly
375 * check if it's out of bounds), others (like basket ball) do all
376 * their aiming and movement here.
377 * - Updates the rewind manager to store rewind states.
378 * - Updates the music manager.
379 * - Updates the input manager (which only updates internal time, actual
380 * input handling follows late)
381 * - Updates the wiimote manager. This will read the data of all wiimotes
382 * and feed the corresponding events to the irrlicht event system.
383 * - Updates the STK internal gui engine. This updates all widgets, and
384 * e.g. takes care of the rotation of the karts in the KartSelection
385 * screen using the ModelViewWidget.
386 * - Updates STK's irrlicht driver `IrrDriver::update()`:
387 * - Calls Irrlicht's `beginScene()` .
388 * - Renders the scene (several times with different viewport if
389 * split screen is being used)
390 * - Calls `GUIEngine::render()`, which renders all widgets with the
391 * help of Irrlicht's GUIEnvironment (`drawAll()`). This will also
392 * handle all events, i.e. all input is now handled (e.g. steering,
393 * firing etc are all set in the corresponding karts depending on
394 * user input).
395 * - Calls Irrlicht's `endScene()`
396 */
run()397 void MainLoop::run()
398 {
399 m_curr_time = StkTime::getMonoTimeMs();
400 // DT keeps track of the leftover time, since the race update
401 // happens in fixed timesteps
402 float left_over_time = 0;
403
404 #ifdef WIN32
405 HANDLE parent = 0;
406 if (m_parent_pid != 0)
407 {
408 parent = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_parent_pid);
409 if (parent == 0 || parent == INVALID_HANDLE_VALUE)
410 {
411 Log::warn("MainLoop", "Cannot open parent handle, this child "
412 "may not be auto destroyed when parent is terminated");
413 }
414 }
415 #endif
416
417 while (!m_abort)
418 {
419 #ifdef WIN32
420 if (parent != 0 && parent != INVALID_HANDLE_VALUE)
421 {
422 MSG msg;
423 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
424 {
425 TranslateMessage(&msg);
426 DispatchMessage(&msg);
427 if (msg.message == WM_QUIT)
428 {
429 m_request_abort = true;
430 }
431 }
432 // If parent is killed, abort the child main loop too
433 if (WaitForSingleObject(parent, 0) != WAIT_TIMEOUT)
434 {
435 m_request_abort = true;
436 }
437 }
438 #else
439 // POSIX equivalent
440 if (m_parent_pid != 0 && getppid() != (int)m_parent_pid)
441 {
442 m_request_abort = true;
443 }
444 #endif
445
446 PROFILER_PUSH_CPU_MARKER("Main loop", 0xFF, 0x00, 0xF7);
447
448 left_over_time += getLimitedDt();
449 int num_steps = stk_config->time2Ticks(left_over_time);
450 float dt = stk_config->ticks2Time(1);
451 left_over_time -= num_steps * dt ;
452
453 // Shutdown next frame if shutdown request is sent while loading the
454 // world
455 bool was_server = NetworkConfig::get()->isNetworking() &&
456 NetworkConfig::get()->isServer();
457 if ((STKHost::existHost() && STKHost::get()->requestedShutdown()) ||
458 m_request_abort)
459 {
460 bool was_lan = NetworkConfig::get()->isLAN();
461 std::shared_ptr<Server> rejoin_server;
462 if (auto cl = LobbyProtocol::get<ClientLobby>())
463 {
464 if (cl->getJoinedServer()->reconnectWhenQuitLobby())
465 rejoin_server = cl->getJoinedServer();
466 }
467 bool exist_host = STKHost::existHost();
468 core::stringw msg = _("Server connection timed out.");
469
470 if (!m_request_abort)
471 {
472 if (!GUIEngine::isNoGraphics())
473 {
474 SFXManager::get()->quickSound("anvil");
475 if (!STKHost::get()->getErrorMessage().empty())
476 {
477 msg = STKHost::get()->getErrorMessage();
478 }
479 }
480 }
481
482 if (exist_host == true)
483 {
484 STKHost::get()->shutdown();
485 }
486
487 #ifndef SERVER_ONLY
488 if (CVS->isGLSL() && !m_download_assets)
489 {
490 // Flush all command before delete world, avoid later access
491 SP::SPTextureManager::get()
492 ->checkForGLCommand(true/*before_scene*/);
493 // Reset screen in case the minimap was drawn
494 glViewport(0, 0, irr_driver->getActualScreenSize().Width,
495 irr_driver->getActualScreenSize().Height);
496 }
497 #endif
498
499 // In case the user opened a race pause dialog
500 GUIEngine::ModalDialog::dismiss();
501 GUIEngine::ScreenKeyboard::dismiss();
502
503 if (World::getWorld())
504 {
505 RaceManager::get()->clearNetworkGrandPrixResult();
506 RaceManager::get()->exitRace();
507 }
508
509 if (exist_host == true)
510 {
511 if (!GUIEngine::isNoGraphics())
512 {
513 StateManager::get()->resetAndSetStack(
514 NetworkConfig::get()->getResetScreens().data());
515 MessageQueue::add(MessageQueue::MT_ERROR, msg);
516 }
517 if (rejoin_server)
518 {
519 if (was_lan)
520 NetworkConfig::get()->setIsLAN();
521 else
522 NetworkConfig::get()->setIsWAN();
523 NetworkConfig::get()->setIsServer(false);
524 ServerSelection::getInstance()->push();
525 rejoin_server->setReconnectWhenQuitLobby(false);
526 new ServerInfoDialog(rejoin_server);
527 }
528 else
529 NetworkConfig::get()->unsetNetworking();
530 }
531
532 if (m_request_abort)
533 {
534 m_abort = true;
535 }
536 }
537
538 if (was_server && !STKHost::existHost())
539 m_abort = true;
540
541 if (!m_abort)
542 {
543 float frame_duration = num_steps * dt;
544 if (!GUIEngine::isNoGraphics())
545 {
546 PROFILER_PUSH_CPU_MARKER("Update race", 0, 255, 255);
547 if (World::getWorld())
548 World::getWorld()->updateGraphics(frame_duration);
549 PROFILER_POP_CPU_MARKER();
550
551 // Render the previous frame, and also handle all user input.
552 PROFILER_PUSH_CPU_MARKER("IrrDriver update", 0x00, 0x00, 0x7F);
553 irr_driver->update(frame_duration);
554 PROFILER_POP_CPU_MARKER();
555
556 PROFILER_PUSH_CPU_MARKER("Input/GUI", 0x7F, 0x00, 0x00);
557 input_manager->update(frame_duration);
558 GUIEngine::update(frame_duration);
559 PROFILER_POP_CPU_MARKER();
560 if (!m_download_assets)
561 {
562 PROFILER_PUSH_CPU_MARKER("Music", 0x7F, 0x00, 0x00);
563 SFXManager::get()->update();
564 PROFILER_POP_CPU_MARKER();
565 }
566 }
567 // Some protocols in network will use RequestManager
568 if (!m_download_assets)
569 {
570 PROFILER_PUSH_CPU_MARKER("Database polling update", 0x00, 0x7F, 0x7F);
571 Online::RequestManager::get()->update(frame_duration);
572 PROFILER_POP_CPU_MARKER();
573 }
574
575 m_ticks_adjustment.lock();
576 if (m_ticks_adjustment.getData() != 0)
577 {
578 if (m_ticks_adjustment.getData() > 0)
579 {
580 num_steps += m_ticks_adjustment.getData();
581 m_ticks_adjustment.getData() = 0;
582 }
583 else if (m_ticks_adjustment.getData() < 0)
584 {
585 int new_steps = num_steps + m_ticks_adjustment.getData();
586 if (new_steps < 0)
587 {
588 num_steps = 0;
589 m_ticks_adjustment.getData() = new_steps;
590 }
591 else
592 {
593 num_steps = new_steps;
594 m_ticks_adjustment.getData() = 0;
595 }
596 }
597 }
598 m_ticks_adjustment.unlock();
599
600 // Avoid hang when some function in world takes too long time or
601 // when leave / come back from android home button
602 bool fast_forward = NetworkConfig::get()->isNetworking() &&
603 NetworkConfig::get()->isClient() &&
604 num_steps > stk_config->time2Ticks(1.0f);
605 for (int i = 0; i < num_steps; i++)
606 {
607 if (World::getWorld() && history->replayHistory())
608 {
609 history->updateReplay(
610 World::getWorld()->getTicksSinceStart());
611 }
612
613 PROFILER_PUSH_CPU_MARKER("Protocol manager update",
614 0x7F, 0x00, 0x7F);
615 if (auto pm = ProtocolManager::lock())
616 {
617 pm->update(1);
618 }
619 PROFILER_POP_CPU_MARKER();
620
621 PROFILER_PUSH_CPU_MARKER("Update race", 0, 255, 255);
622 if (World::getWorld())
623 {
624 updateRace(1, fast_forward);
625 }
626 PROFILER_POP_CPU_MARKER();
627
628 // We need to check again because update_race may have requested
629 // the main loop to abort; and it's not a good idea to continue
630 // since the GUI engine is no more to be called then.
631 if (m_abort || m_request_abort)
632 break;
633
634 if (m_frame_before_loading_world)
635 {
636 // This will be called when changing introcutscene 1 and 2
637 // in CutsceneWorld::enterRaceOverState
638 // Reset the timer for correct time for cutscene
639 m_frame_before_loading_world = false;
640 m_curr_time = StkTime::getMonoTimeMs();
641 left_over_time = 0.0f;
642 break;
643 }
644
645 if (World::getWorld())
646 {
647 if (World::getWorld()->getPhase()==WorldStatus::SETUP_PHASE)
648 {
649 // Skip the large num steps contributed by loading time
650 World::getWorld()->updateTime(1);
651 break;
652 }
653 World::getWorld()->updateTime(1);
654 }
655 } // for i < num_steps
656
657 // Do it after all pending rewinding is done
658 if (World::getWorld() && RewindManager::isEnabled())
659 RewindManager::get()->handleResetSmoothNetworkBody();
660
661 // Handle controller the last to avoid slow PC sending actions too
662 // late
663 if (!GUIEngine::isNoGraphics())
664 {
665 // User aborted (e.g. closed window)
666 bool abort = !irr_driver->getDevice()->run();
667
668 if (m_frame_before_loading_world)
669 {
670 // irr_driver->getDevice()->run() loads the world
671 m_frame_before_loading_world = false;
672 m_curr_time = StkTime::getMonoTimeMs();
673 left_over_time = 0.0f;
674 }
675
676 if (abort)
677 {
678 m_request_abort = true;
679 }
680 }
681
682 if (auto gp = GameProtocol::lock())
683 {
684 gp->sendActions();
685 }
686 }
687 PROFILER_POP_CPU_MARKER(); // MainLoop pop
688 PROFILER_SYNC_FRAME();
689 } // while !m_abort
690
691 #ifdef WIN32
692 if (parent != 0 && parent != INVALID_HANDLE_VALUE)
693 CloseHandle(parent);
694 #endif
695
696 } // run
697
698 // ----------------------------------------------------------------------------
699 /** Renders the GUI. This function is used during loading a track to get a
700 * responsive GUI, and allow GUI animations (like a progress bar) to be
701 * shown.
702 * \param phase An integer indicated a phase. The maximum number of phases
703 * is used to show a progress bar. The values are between 0 and 8200.
704 * \param loop_index If the call is from a loop, the current loop index.
705 * \param loop_size The number of loop iterations. Used to smooth update
706 * e.g. a progress bar.
707 */
renderGUI(int phase,int loop_index,int loop_size)708 void MainLoop::renderGUI(int phase, int loop_index, int loop_size)
709 {
710 #ifdef SERVER_ONLY
711 return;
712 #else
713 if ((NetworkConfig::get()->isNetworking() &&
714 NetworkConfig::get()->isServer()) ||
715 GUIEngine::isNoGraphics())
716 {
717 return;
718 }
719 // Atm ignore all input when loading only
720 irr_driver->getDevice()->setEventReceiver(NULL);
721 irr_driver->getDevice()->run();
722 irr_driver->getDevice()->setEventReceiver(GUIEngine::EventHandler::get());
723 return;
724 // Rendering past phase 7000 causes the minimap to not work
725 // on higher graphical settings
726 if (phase > 7000)
727 {
728 m_request_abort = !irr_driver->getDevice()->run();
729 return;
730 }
731
732 uint64_t now = StkTime::getMonoTimeMs();
733 float dt = (now - m_curr_time)/1000.0f;
734
735 if (dt < 1.0 / 30.0f) return;
736
737 m_curr_time = now;
738
739 // TODO: remove debug output
740 //Log::verbose("mainloop", "Rendergui t %llu dt %f phase %d index %d / %d",
741 // now, dt, phase, loop_index, loop_size);
742
743 irr_driver->update(dt, /*is_loading*/true);
744 GUIEngine::update(dt);
745 m_request_abort = !irr_driver->getDevice()->run();
746
747 //TODO: remove debug output
748 // uint64_t now2 = StkTime::getMonoTimeMs();
749 // Log::verbose("mainloop", " duration t %llu dt %llu", now, now2-now);
750 #endif
751 } // renderGUI
752 /* EOF */
753
754 #ifdef IOS_STK
755 // For iOS STK we need to make sure no rendering command is executed after pause
756 // so we need a handle_app_event callback
757 #include "SDL_events.h"
handle_app_event(void * userdata,SDL_Event * event)758 extern "C" int handle_app_event(void* userdata, SDL_Event* event)
759 {
760 if (!main_loop)
761 return 1;
762 switch (event->type)
763 {
764 case SDL_APP_WILLENTERBACKGROUND:
765 main_loop->setPaused(true);
766 break;
767 case SDL_APP_DIDENTERFOREGROUND:
768 main_loop->setPaused(false);
769 break;
770 default:
771 break;
772 }
773 return 1;
774 }
775 #endif
776