Refactor the game event loop and add menus to the Linux build
Patch from @x-qq Addresses #195
This commit is contained in:
@@ -33,7 +33,6 @@
|
||||
#include "enum_map.hpp"
|
||||
|
||||
bool All_Done = false;
|
||||
sf::Event event;
|
||||
sf::RenderWindow mainPtr;
|
||||
short had_text_freeze = 0,num_fonts;
|
||||
bool first_startup_update = true;
|
||||
@@ -92,6 +91,7 @@ short missile_firer,current_monst_tactic;
|
||||
short store_current_pc = 0;
|
||||
|
||||
sf::Clock animTimer;
|
||||
extern long anim_ticks;
|
||||
|
||||
static void init_boe(int, char*[]);
|
||||
|
||||
@@ -117,26 +117,9 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
menu_activate();
|
||||
restore_cursor();
|
||||
// As of year 2020, the game is causing too high CPU load on modern CPUs (i7-7700HQ), but due
|
||||
// to mistakes in architecture we cannot simply use SFML's native framerate capping
|
||||
// mechanism (setFramerateLimit), so we have to make our own.
|
||||
//
|
||||
// Note that due to the fact that various underlying pieces of code redraw, redisplay and
|
||||
// pause the game as they see fit, this is not exactly framerate capping.
|
||||
// ~xq
|
||||
sf::Clock framerate_clock;
|
||||
const sf::Int64 desired_microseconds_per_frame { 1000000 / 60}; // us / FPS
|
||||
while(!All_Done) {
|
||||
// If this call indicates that it did something expensive by returning true,
|
||||
// we do the performance capping. The logic here is that we do not want to
|
||||
// do expensive things too many times per second.
|
||||
// ~xq
|
||||
if(!Handle_One_Event()) continue;
|
||||
const sf::Int64 remaining_time_budget = desired_microseconds_per_frame - framerate_clock.getElapsedTime().asMicroseconds();
|
||||
if(remaining_time_budget > 0) sf::sleep(sf::microseconds(remaining_time_budget));
|
||||
framerate_clock.restart();
|
||||
}
|
||||
|
||||
|
||||
handle_events();
|
||||
|
||||
close_program();
|
||||
return 0;
|
||||
} catch(std::exception& x) {
|
||||
@@ -169,7 +152,9 @@ static void init_btn(std::shared_ptr<cButton>& btn, eBtnType type) {
|
||||
void init_boe(int argc, char* argv[]) {
|
||||
set_up_apple_events(argc, argv);
|
||||
init_directories(argv[0]);
|
||||
#ifdef __APPLE__
|
||||
init_menubar(); // Do this first of all because otherwise a default File and Window menu will be seen
|
||||
#endif
|
||||
sync_prefs();
|
||||
init_shaders();
|
||||
init_tiling();
|
||||
@@ -210,51 +195,61 @@ void init_boe(int argc, char* argv[]) {
|
||||
showMenuBar();
|
||||
}
|
||||
|
||||
// Return true if we redrew the screen
|
||||
bool Handle_One_Event() {
|
||||
static const long twentyTicks = time_in_ticks(20).asMilliseconds();
|
||||
static const long fortyTicks = time_in_ticks(40).asMilliseconds();
|
||||
|
||||
through_sending();
|
||||
|
||||
if((animTimer.getElapsedTime().asMilliseconds() >= fortyTicks) && (overall_mode != MODE_STARTUP) && (anim_onscreen) && get_bool_pref("DrawTerrainAnimation", true)) {
|
||||
animTimer.restart();
|
||||
draw_terrain();
|
||||
}
|
||||
if((animTimer.getElapsedTime().asMilliseconds() > twentyTicks) && (overall_mode == MODE_STARTUP)) {
|
||||
animTimer.restart();
|
||||
draw_startup_anim(true);
|
||||
}
|
||||
|
||||
clear_sound_memory();
|
||||
|
||||
if(map_visible && mini_map.pollEvent(event)){
|
||||
if(event.type == sf::Event::Closed) {
|
||||
mini_map.setVisible(false);
|
||||
map_visible = false;
|
||||
} else if(event.type == sf::Event::GainedFocus)
|
||||
makeFrontWindow(mainPtr);
|
||||
}
|
||||
if(!mainPtr.pollEvent(event)) {
|
||||
void handle_events() {
|
||||
sf::Event currentEvent;
|
||||
sf::Clock framerate_clock;
|
||||
const sf::Int64 desired_microseconds_per_frame { 1000000 / 60 }; // us / FPS
|
||||
|
||||
while(!All_Done) {
|
||||
while(mainPtr.pollEvent(currentEvent)) handle_one_event(currentEvent);
|
||||
|
||||
// It would be nice to have minimap inside the main game window (we have lots of screen space in fullscreen mode).
|
||||
// Alternatively, minimap could live on its own thread.
|
||||
// But for now we just handle events from both windows on this thread.
|
||||
while(map_visible && mini_map.pollEvent(currentEvent)) handle_one_minimap_event(currentEvent);
|
||||
|
||||
if(changed_display_mode) {
|
||||
changed_display_mode = false;
|
||||
adjust_window_mode();
|
||||
}
|
||||
|
||||
// Still no idea what this does. It's possible that this does not work at all.
|
||||
flushingInput = false;
|
||||
redraw_screen(REFRESH_NONE);
|
||||
return true;
|
||||
|
||||
// Ideally this call should update all of the things that are happening in the world current tick.
|
||||
// NOTE that update does not mean draw.
|
||||
update_everything();
|
||||
|
||||
// Ideally, this should be the only draw call that is done in a cycle.
|
||||
redraw_everything();
|
||||
|
||||
// Prevent the loop from executing too fast.
|
||||
const sf::Int64 remaining_time_budget = desired_microseconds_per_frame - framerate_clock.getElapsedTime().asMicroseconds();
|
||||
if(remaining_time_budget > 0) sf::sleep(sf::microseconds(remaining_time_budget));
|
||||
framerate_clock.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void handle_one_event(const sf::Event& event) {
|
||||
|
||||
// What does this do and should it be here?
|
||||
through_sending();
|
||||
|
||||
// What does this do and should it be here?
|
||||
clear_sound_memory();
|
||||
|
||||
// Check if the menubar wants to handle this event.
|
||||
if(menuBarProcessEvent(event)) return;
|
||||
|
||||
switch(event.type) {
|
||||
case sf::Event::KeyPressed:
|
||||
if(flushingInput) return false;
|
||||
if(!(event.key.*systemKey))
|
||||
handle_keystroke(event);
|
||||
|
||||
if(flushingInput) return;
|
||||
if(!(event.key.*systemKey)) handle_keystroke(event);
|
||||
break;
|
||||
|
||||
case sf::Event::MouseButtonPressed:
|
||||
if(flushingInput) return false;
|
||||
Mouse_Pressed();
|
||||
if(flushingInput) return;
|
||||
Mouse_Pressed(event);
|
||||
break;
|
||||
|
||||
case sf::Event::MouseLeft:
|
||||
@@ -263,18 +258,16 @@ bool Handle_One_Event() {
|
||||
break;
|
||||
|
||||
case sf::Event::GainedFocus:
|
||||
Handle_Update();
|
||||
makeFrontWindow(mainPtr);
|
||||
change_cursor({event.mouseMove.x, event.mouseMove.y});
|
||||
return true;
|
||||
return;
|
||||
|
||||
case sf::Event::MouseMoved:
|
||||
change_cursor({event.mouseMove.x, event.mouseMove.y});
|
||||
flushingInput = false;
|
||||
return false;
|
||||
|
||||
return;
|
||||
|
||||
case sf::Event::MouseWheelMoved:
|
||||
if(flushingInput) return false;
|
||||
if(flushingInput) return;
|
||||
handle_scroll(event);
|
||||
break;
|
||||
|
||||
@@ -308,17 +301,47 @@ bool Handle_One_Event() {
|
||||
default:
|
||||
break; // There's several events we don't need to handle at all
|
||||
}
|
||||
flushingInput = false; // TODO: Could there be a case when the key and mouse input that needs to be flushed has other events interspersed?
|
||||
return true;
|
||||
}
|
||||
|
||||
void handle_one_minimap_event(const sf::Event& event) {
|
||||
if(event.type == sf::Event::Closed) {
|
||||
mini_map.setVisible(false);
|
||||
map_visible = false;
|
||||
} else if(event.type == sf::Event::GainedFocus) {
|
||||
makeFrontWindow(mainPtr);
|
||||
}
|
||||
}
|
||||
|
||||
void Handle_Update() {
|
||||
redraw_screen(REFRESH_NONE);
|
||||
|
||||
void update_terrain_animation() {
|
||||
static const long fortyTicks = time_in_ticks(40).asMilliseconds();
|
||||
|
||||
if(overall_mode == MODE_STARTUP) return;
|
||||
if(!get_bool_pref("DrawTerrainAnimation", true)) return;
|
||||
if(!anim_onscreen) return;
|
||||
if(animTimer.getElapsedTime().asMilliseconds() < fortyTicks) return;
|
||||
|
||||
anim_ticks++;
|
||||
animTimer.restart();
|
||||
}
|
||||
|
||||
void update_startup_animation() {
|
||||
static const long twentyTicks = time_in_ticks(20).asMilliseconds();
|
||||
|
||||
if(overall_mode != MODE_STARTUP) return;
|
||||
if(animTimer.getElapsedTime().asMilliseconds() < twentyTicks) return;
|
||||
|
||||
draw_startup_anim(true);
|
||||
animTimer.restart();
|
||||
}
|
||||
|
||||
void update_everything() {
|
||||
update_terrain_animation();
|
||||
update_startup_animation();
|
||||
}
|
||||
|
||||
void redraw_everything() {
|
||||
redraw_screen(REFRESH_ALL);
|
||||
if(map_visible) draw_map(false);
|
||||
else mini_map.setVisible(false);
|
||||
|
||||
}
|
||||
|
||||
static void handleUpdateWhileScrolling(volatile bool& doneScrolling, int refresh) {
|
||||
@@ -329,8 +352,9 @@ static void handleUpdateWhileScrolling(volatile bool& doneScrolling, int refresh
|
||||
mainPtr.setActive(false);
|
||||
}
|
||||
|
||||
// TODO: Pass the event object around instead of keeping a global one
|
||||
void Mouse_Pressed() {
|
||||
void Mouse_Pressed(sf::Event const & event) {
|
||||
|
||||
// What is this stuff? Why is it here?
|
||||
if(had_text_freeze > 0) {
|
||||
had_text_freeze--;
|
||||
return;
|
||||
@@ -364,6 +388,7 @@ void Mouse_Pressed() {
|
||||
} else All_Done = handle_action(event);
|
||||
} else All_Done = handle_startup_press({event.mouseButton.x, event.mouseButton.y});
|
||||
|
||||
// Why does every mouse click activate a menu?
|
||||
menu_activate();
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user