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