From 1f7b88f1cf6dd80d5d216e18c8917f227183c57e Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Tue, 7 Feb 2023 03:49:15 +0800 Subject: [PATCH] Make input manager handle quit events. Improve command-line option parsing --- src/app/sdl/sdl-input-manager.cpp | 10 ++- src/app/sdl/sdl-window-manager.cpp | 4 +- src/event/queue.hpp | 14 ++-- src/game/context.hpp | 51 ++++++++----- src/game/state/boot.cpp | 111 ++++++++++++++++++++++------- src/game/state/boot.hpp | 13 +--- src/input/event.hpp | 86 +--------------------- 7 files changed, 139 insertions(+), 150 deletions(-) diff --git a/src/app/sdl/sdl-input-manager.cpp b/src/app/sdl/sdl-input-manager.cpp index d63ff64..08e0868 100644 --- a/src/app/sdl/sdl-input-manager.cpp +++ b/src/app/sdl/sdl-input-manager.cpp @@ -82,9 +82,15 @@ void sdl_input_manager::update() throw std::runtime_error("Failed to peep SDL events"); } - if (event.type == SDL_QUIT) + switch (event.type) { - //... + case SDL_QUIT: + debug::log::debug("Application quit requested"); + this->event_queue.enqueue({}); + break; + + default: + break; } } diff --git a/src/app/sdl/sdl-window-manager.cpp b/src/app/sdl/sdl-window-manager.cpp index 8205409..e122679 100644 --- a/src/app/sdl/sdl-window-manager.cpp +++ b/src/app/sdl/sdl-window-manager.cpp @@ -154,7 +154,7 @@ void sdl_window_manager::update() // Update window state window->size = {event.window.data1, event.window.data2}; const auto window_flags = SDL_GetWindowFlags(internal_window); - if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & SDL_WINDOW_FULLSCREEN)) + if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP))) { window->windowed_size = window->size; } @@ -178,7 +178,7 @@ void sdl_window_manager::update() // Update window state window->position = {event.window.data1, event.window.data2}; const auto window_flags = SDL_GetWindowFlags(internal_window); - if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & SDL_WINDOW_FULLSCREEN)) + if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP))) { window->windowed_position = window->position; } diff --git a/src/event/queue.hpp b/src/event/queue.hpp index 53b90ba..b2cd36d 100644 --- a/src/event/queue.hpp +++ b/src/event/queue.hpp @@ -33,7 +33,7 @@ namespace event { /** - * Collects messages from publishers to be distributed to subscribers when desired. + * Collects messages from publishers to be forwarded to subscribers when desired. */ class queue { @@ -46,6 +46,8 @@ public: * @param subscriber Function object to subscribe. * * @return Shared subscription object which will unsubscribe the subscriber on destruction. + * + * @TODO This function should be available through an interface class which does not expose the queue's message-sending functions, such as event::channel for publishers. */ template [[nodiscard]] std::shared_ptr subscribe(subscriber&& subscriber) @@ -81,13 +83,13 @@ public: ( [this, message]() { - this->distribute(message); + this->forward(message); } ); } /** - * Distributes queued messages in FIFO order to subscribers. + * Forwards queued messages, in FIFO order, to subscribers. */ void flush() { @@ -116,14 +118,14 @@ public: private: /** - * Distributes a message. + * Forwards a message to subscribers of the message type. * * @tparam T Message type. * - * @param message Message to distribute. + * @param message Message to forward. */ template - void distribute(const T& message) const + void forward(const T& message) const { // For each subscriber of the given message type const auto range = subscribers.equal_range(std::type_index(typeid(T))); diff --git a/src/game/context.hpp b/src/game/context.hpp index 9a30fba..48f6500 100644 --- a/src/game/context.hpp +++ b/src/game/context.hpp @@ -122,17 +122,46 @@ namespace game { /// Container for data shared between game states. struct context { - // Configuration + // Command-line options + std::optional option_continue; + std::optional option_data; + std::optional option_fullscreen; + std::optional option_new_game; + std::optional option_quick_start; + std::optional option_reset; + std::optional option_v_sync; + std::optional option_windowed; + + // Resource management and paths + resource_manager* resource_manager; + + // Persistent settings dict* settings; - // Window creation, events, and management + // Window management and event handling app::window_manager* window_manager; app::window* window; bool closed; std::shared_ptr<::event::subscription> window_closed_subscription; - // Input devices and events + // Input management and event handling app::input_manager* input_manager; + std::shared_ptr<::event::subscription> application_quit_subscription; + + // Localization and internationalization + std::uint16_t language_index; + std::uint16_t language_count; + i18n::string_table* string_table; + std::vector string_maps; + + // Fonts + std::unordered_map typefaces; + type::bitmap_font debug_font; + type::bitmap_font menu_font; + type::bitmap_font title_font; + render::material debug_font_material; + render::material menu_font_material; + render::material title_font_material; // Hierarchichal state machine hsm::state_machine state_machine; @@ -180,22 +209,6 @@ struct context std::filesystem::path controls_path; std::filesystem::path data_package_path; - // Resources - resource_manager* resource_manager; - - // Localization - std::uint16_t language_index; - std::uint16_t language_count; - i18n::string_table* string_table; - std::vector string_maps; - std::unordered_map typefaces; - type::bitmap_font debug_font; - type::bitmap_font menu_font; - type::bitmap_font title_font; - render::material debug_font_material; - render::material menu_font_material; - render::material title_font_material; - // Framebuffers gl::texture_2d* hdr_color_texture; gl::texture_2d* hdr_depth_texture; diff --git a/src/game/state/boot.cpp b/src/game/state/boot.cpp index e70c448..3cefa66 100644 --- a/src/game/state/boot.cpp +++ b/src/game/state/boot.cpp @@ -89,7 +89,6 @@ #include "utility/dict.hpp" #include "utility/hash/fnv1a.hpp" #include -#include #include #include #include @@ -97,6 +96,10 @@ #include #include +// Prevent cxxopts from using RTTI +#define CXXOPTS_NO_RTTI +#include + using namespace hash::literals; namespace game { @@ -108,7 +111,7 @@ boot::boot(game::context& ctx, int argc, char** argv): // Boot process debug::log::trace("Booting up..."); - parse_arguments(argc, argv); + parse_options(argc, argv); setup_resources(); load_settings(); setup_window(); @@ -153,8 +156,11 @@ boot::~boot() (*ctx.settings)["maximized"_fnv1a32] = maximized; (*ctx.settings)["fullscreen"_fnv1a32] = fullscreen; + // Destruct window + delete ctx.window; + // Save settings - ctx.resource_manager->save>(ctx.settings, "settings.cfg"); + ctx.resource_manager->save(ctx.settings, "settings.cfg"); // Destruct input and window managers delete ctx.input_manager; @@ -166,61 +172,78 @@ boot::~boot() debug::log::trace("Boot down complete"); } -void boot::parse_arguments(int argc, char** argv) +void boot::parse_options(int argc, char** argv) { - debug::log::trace("Parsing {} command line arguments...", argc); + debug::log::trace("Parsing command-line options..."); + // Parse command-line options with cxxopts try { - cxxopts::Options options("Antkeeper", "Ant colony simulation game"); + cxxopts::Options options(config::application_name, "Ant colony simulation game"); options.add_options() ("c,continue", "Continues from the last save") ("d,data", "Sets the data package path", cxxopts::value()) ("f,fullscreen", "Starts in fullscreen mode") ("n,new-game", "Starts a new game") ("q,quick-start", "Skips to the main menu") - ("r,reset", "Restores all settings to default") - ("v,v_sync", "Enables or disables v-sync", cxxopts::value()) - ("w,window", "Starts in window mode"); + ("r,reset", "Resets all settings to default") + ("v,vsync", "Enables or disables v-sync", cxxopts::value()) + ("w,windowed", "Starts in windowed mode"); auto result = options.parse(argc, argv); // --continue if (result.count("continue")) - option_continue = true; + { + ctx.option_continue = true; + } // --data if (result.count("data")) - option_data = result["data"].as(); + { + ctx.option_data = result["data"].as(); + } // --fullscreen if (result.count("fullscreen")) - option_fullscreen = true; + { + ctx.option_fullscreen = true; + } // --new-game if (result.count("new-game")) - option_new_game = true; + { + ctx.option_new_game = true; + } // --quick-start if (result.count("quick-start")) - option_quick_start = true; + { + ctx.option_quick_start = true; + } // --reset if (result.count("reset")) - option_reset = true; + { + ctx.option_reset = true; + } // --v_sync - if (result.count("v_sync")) - option_v_sync = (result["v_sync"].as()) ? true : false; + if (result.count("vsync")) + { + ctx.option_v_sync = result["vsync"].as(); + } // --window - if (result.count("window")) - option_windowed = true; + if (result.count("windowed")) + { + ctx.option_windowed = true; + } - debug::log::trace("Parsed {} command line arguments", argc); + debug::log::info("Parsed {} command-line options", argc); } catch (const std::exception& e) { - debug::log::warning("Exception caught while parsing command line arguments: {}", e.what()); + debug::log::error("An error occurred while parsing command-line options: {}", e.what()); } } @@ -287,9 +310,9 @@ void boot::setup_resources() } // Determine data package path - if (option_data.has_value()) + if (ctx.option_data) { - ctx.data_package_path = std::filesystem::path(option_data.value()); + ctx.data_package_path = std::filesystem::path(ctx.option_data.value()); if (ctx.data_package_path.is_relative()) ctx.data_package_path = ctx.data_path / ctx.data_package_path; } @@ -318,11 +341,21 @@ void boot::setup_resources() void boot::load_settings() { - ctx.settings = ctx.resource_manager->load>("settings.cfg"); - if (!ctx.settings) + if (ctx.option_reset) { - debug::log::info("Settings not found"); + // Command-line reset option found, reset settings ctx.settings = new dict(); + ctx.resource_manager->save(ctx.settings, "settings.cfg"); + debug::log::info("Settings reset"); + } + else + { + ctx.settings = ctx.resource_manager->load>("settings.cfg"); + if (!ctx.settings) + { + debug::log::info("Settings not found"); + ctx.settings = new dict(); + } } } @@ -370,6 +403,23 @@ void boot::setup_window() window_y = usable_bounds_center.y() - window_h / 2; } + // Handle window-related command-line options + if (ctx.option_windowed) + { + // Start in windowed mode + maximized = false; + fullscreen = false; + } + if (ctx.option_fullscreen) + { + // Start in fullscreen mode + fullscreen = true; + } + if (ctx.option_v_sync) + { + v_sync = ctx.option_v_sync.value(); + } + // Construct window ctx.window = ctx.window_manager->create_window ( @@ -398,6 +448,15 @@ void boot::setup_input() { // Construct input manager ctx.input_manager = app::input_manager::instance(); + + // Setup application quit callback + ctx.application_quit_subscription = ctx.input_manager->get_event_queue().subscribe + ( + [&](const auto& event) + { + ctx.closed = true; + } + ); } void boot::load_strings() diff --git a/src/game/state/boot.hpp b/src/game/state/boot.hpp index 6523b43..c57e1c5 100644 --- a/src/game/state/boot.hpp +++ b/src/game/state/boot.hpp @@ -48,7 +48,7 @@ public: virtual ~boot(); private: - void parse_arguments(int argc, char** argv); + void parse_options(int argc, char** argv); void setup_resources(); void load_settings(); void setup_window(); @@ -64,19 +64,10 @@ private: void setup_ui(); void setup_debugging(); void setup_loop(); + void loop(); void shutdown_audio(); - - // Command line options - std::optional option_continue; - std::optional option_data; - std::optional option_fullscreen; - std::optional option_new_game; - std::optional option_quick_start; - std::optional option_reset; - std::optional option_v_sync; - std::optional option_windowed; }; } // namespace state diff --git a/src/input/event.hpp b/src/input/event.hpp index c555841..9a89a69 100644 --- a/src/input/event.hpp +++ b/src/input/event.hpp @@ -237,91 +237,9 @@ struct mouse_scrolled }; /** - * Event generated when a window has been requested to close. + * Event generated when the application has been requested to quit. */ -struct window_closed -{ - /// Pointer to the window that has been requested to close. - void* window; -}; - -/** - * Event generated when a window has gained or lost focus. - */ -struct window_focus_changed -{ - /// Pointer to the window that has gained or lost focus. - void* window; - - /// `true` if the window is in focus, `false` otherwise. - bool in_focus; -}; - -/** - * Event generated when a window has been moved. - */ -struct window_moved -{ - /// Pointer to the window that has been moved. - void* window; - - /// Position of the window, in pixels. - math::vector position; - - /// `true` if the window is maximized, `false` otherwise. - bool maximized; - - /// `true` if the window is fullscreen, `false` otherwise. - bool fullscreen; -}; - -/** - * Event generated when a window has been maximized. - */ -struct window_maximized -{ - /// Pointer to the window that has been maximized. - void* window; -}; - -/** - * Event generated when a window has been minimized. - */ -struct window_minimized -{ - /// Pointer to the window that has been minimized. - void* window; -}; - -/** - * Event generated when a window has been restored. - */ -struct window_restored -{ - /// Pointer to the window that has been restored. - void* window; -}; - -/** - * Event generated when a window has been resized. - */ -struct window_resized -{ - /// Pointer to the window that has been resized. - void* window; - - /// Window size, in display units. - math::vector size; - - /// `true` if the window is maximized, `false` otherwise. - bool maximized; - - /// `true` if the window is fullscreen, `false` otherwise. - bool fullscreen; - - /// Window viewport size, in pixels. - math::vector viewport_size; -}; +struct application_quit {}; } // namespace event } // namespace input