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 | |
44 | ScreenManager::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 | |
57 | ScreenManager::~ScreenManager() |
58 | { |
59 | } |
60 | |
61 | void |
62 | ScreenManager::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 | |
73 | void |
74 | ScreenManager::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 | |
84 | void |
85 | ScreenManager::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 | |
93 | void |
94 | ScreenManager::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 | |
103 | void |
104 | ScreenManager::set_speed(float speed) |
105 | { |
106 | m_speed = speed; |
107 | } |
108 | |
109 | float |
110 | ScreenManager::get_speed() const |
111 | { |
112 | return m_speed; |
113 | } |
114 | |
115 | struct 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 | |
181 | private: |
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 | |
192 | void |
193 | ScreenManager::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 | |
222 | void |
223 | ScreenManager::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 | |
238 | void |
239 | ScreenManager::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 | |
271 | void |
272 | ScreenManager::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 | |
293 | void |
294 | ScreenManager::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 | |
367 | bool |
368 | ScreenManager::has_pending_fadeout() const |
369 | { |
370 | return m_screen_fade && !m_screen_fade->done(); |
371 | } |
372 | |
373 | void |
374 | ScreenManager::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 | |
442 | void |
443 | ScreenManager::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 | |