From 849043dd5c6b538f2d42f0252383fc0a3df199a3 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Tue, 17 Jan 2023 00:39:50 +0800 Subject: [PATCH] Add signal and connection-based event handling --- src/application.cpp | 31 ++++- src/application.hpp | 47 +++++++ src/event/signal.cpp | 47 +++++++ src/event/signal.hpp | 214 +++++++++++++++++++++++++++++ src/game/state/main-menu.cpp | 51 +++++++ src/game/state/main-menu.hpp | 6 + src/render/stage.cpp | 28 ++++ src/render/stage.hpp | 29 +++- src/render/stage/culling-stage.cpp | 2 +- src/render/stage/culling-stage.hpp | 2 +- 10 files changed, 448 insertions(+), 9 deletions(-) create mode 100644 src/event/signal.cpp create mode 100644 src/event/signal.hpp create mode 100644 src/render/stage.cpp diff --git a/src/application.cpp b/src/application.cpp index 7e52636..1e3b27a 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -258,8 +258,6 @@ void application::resize_window(int width, int height) // Resize and center window SDL_SetWindowPosition(sdl_window, x, y); SDL_SetWindowSize(sdl_window, width, height); - - window_resized(); } void application::set_fullscreen(bool fullscreen) @@ -539,11 +537,31 @@ void application::process_events() case SDL_WINDOWEVENT: { - if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED) + switch (sdl_event.window.event) { - window_resized(); + case SDL_WINDOWEVENT_SIZE_CHANGED: + window_resized(); + break; + + case SDL_WINDOWEVENT_FOCUS_GAINED: + window_focus_signal.emit(true); + break; + + case SDL_WINDOWEVENT_FOCUS_LOST: + window_focus_signal.emit(false); + break; + + case SDL_WINDOWEVENT_MOVED: + window_motion_signal.emit(sdl_event.window.data1, sdl_event.window.data2); + break; + + case SDL_WINDOWEVENT_CLOSE: + window_close_signal.emit(); + break; + + default: + break; } - break; } @@ -575,4 +593,7 @@ void application::window_resized() event.h = window_dimensions[1]; event_dispatcher->queue(event); + + window_size_signal.emit(window_dimensions[0], window_dimensions[1]); + viewport_size_signal.emit(viewport_dimensions[0], viewport_dimensions[1]); } diff --git a/src/application.hpp b/src/application.hpp index 537fedf..c9248ca 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -29,6 +29,7 @@ #include "input/gamepad.hpp" #include "utility/fundamental-types.hpp" #include "debug/logger.hpp" +#include "event/signal.hpp" // Forward declarations typedef struct SDL_Window SDL_Window; @@ -149,6 +150,21 @@ public: void process_events(); bool was_closed() const; + + /// Returns a connector for the signal emitted when the window is requested to close. + connector& get_window_close_signal() noexcept; + + /// Returns a connector for the signal emitted each time the window gains or loses focus. + connector& get_window_focus_signal() noexcept; + + /// Returns a connector for the signal emitted each time the window is moved. + connector& get_window_motion_signal() noexcept; + + /// Returns a connector for the signal emitted each time the window is resized. + connector& get_window_size_signal() noexcept; + + /// Returns a connector for the signal emitted each time the window viewport is resized. + connector& get_viewport_size_signal() noexcept; private: void window_resized(); @@ -177,6 +193,12 @@ private: input::mouse* mouse; std::list gamepads; std::unordered_map gamepad_map; + + signal window_close_signal; + signal window_focus_signal; + signal window_motion_signal; + signal window_size_signal; + signal viewport_size_signal; }; inline debug::logger* application::get_logger() @@ -244,4 +266,29 @@ inline bool application::was_closed() const return closed; } +inline connector& application::get_window_close_signal() noexcept +{ + return window_close_signal.connector(); +} + +inline connector& application::get_window_focus_signal() noexcept +{ + return window_focus_signal.connector(); +} + +inline connector& application::get_window_motion_signal() noexcept +{ + return window_motion_signal.connector(); +} + +inline connector& application::get_window_size_signal() noexcept +{ + return window_size_signal.connector(); +} + +inline connector& application::get_viewport_size_signal() noexcept +{ + return viewport_size_signal.connector(); +} + #endif // ANTKEEPER_APPLICATION_HPP diff --git a/src/event/signal.cpp b/src/event/signal.cpp new file mode 100644 index 0000000..bcad1c9 --- /dev/null +++ b/src/event/signal.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "event/signal.hpp" + +//namespace event { + +connection::connection(std::weak_ptr handler, disconnector_type disconnector): + handler(handler), + disconnector(disconnector) +{} + +connection::~connection() +{ + disconnect(); +} + +bool connection::connected() const noexcept +{ + return !handler.expired(); +} + +void connection::disconnect() +{ + if (connected()) + { + disconnector(handler); + } +} + +//} // namespace event diff --git a/src/event/signal.hpp b/src/event/signal.hpp new file mode 100644 index 0000000..b7d91ac --- /dev/null +++ b/src/event/signal.hpp @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_EVENT_SIGNAL_HPP +#define ANTKEEPER_EVENT_SIGNAL_HPP + +#include +#include +#include +#include +#include + +//namespace event { + +/** + * Manages a connection between a signal and handler. A signal will be disconnected from a handler when the connection is destructed or is disconnected manually via connection::disconnect(). + */ +class connection +{ +public: + /// Signal handler disconnect function type. + typedef std::function)> disconnector_type; + + /** + * Constructs a connection between a signal and a handler. + * + * @param handler Weak pointer to a signal handler. + * @param disconnector Signal handler disconnect function. + */ + connection(std::weak_ptr handler, disconnector_type disconnector); + + /** + * Destructs a connection between a signal and a handler. + */ + ~connection(); + + /** + * Returns `true` if the signal and handler are connected, `false` otherwise. + */ + bool connected() const noexcept; + + /** + * Disconnects the signal from the handler. + */ + void disconnect(); + +private: + std::weak_ptr handler; + disconnector_type disconnector; +}; + +template +class signal; + +template +class connector; + +/** + * Creates connections between a signal and signal handlers. + * + * @tparam T Signal handler return type. + * @tparam Args Signal handler argument types. + */ +template +class connector +{ +public: + /// Signal type. + typedef signal signal_type; + + /** + * Constructs a signal connector. + * + * @param signal Signal to which handlers may be connected. + */ + connector(signal_type& signal): + signal(&signal) + {} + + /// @copydoc signal::connect(handler_type) + std::shared_ptr connect(typename signal_type::handler_type handler) + { + return signal->connect(handler); + } + +private: + signal_type* signal; +}; + +/** + * Emits signals to connected handlers. + * + * @tparam T Signal handler return type. + * @tparam Args Signal handler argument types. + */ +template +class signal +{ +public: + /// Signal handler type. + typedef std::function handler_type; + + /// Signal connector type. + typedef connector connector_type; + + /** + * Constructs a signal. + */ + signal(): + signal_connector(*this) + {} + + /** + * Returns the connector for this signal. + */ + connector_type& connector() noexcept + { + return signal_connector; + } + + /** + * Connects the signal to a handler. + * + * @param handler Signal handler to connect. + * + * @return Connection between the signal and handler. + */ + std::shared_ptr connect(handler_type handler) + { + // Allocate shared pointer to handler + std::shared_ptr shared_handler = std::make_shared(handler); + + // Add handler to list of connected handlers + handlers.push_back(shared_handler); + + // Return a shared pointer to the connection between the signal and handler + return std::make_shared + ( + std::static_pointer_cast(shared_handler), + [this](std::weak_ptr handler) + { + this->handlers.remove(std::static_pointer_cast(handler.lock())); + } + ); + } + + /** + * Disconnects the signal from all connected handlers. + */ + void disconnect() + { + handlers.clear(); + } + + /** + * Emits a signal to all connected handlers. + * + * @tparam ExecutionPolicy Execution policy type. + * + * @param policy Execution policy to use. + * @param args Signal arguments. + */ + template + void emit(ExecutionPolicy&& policy, Args... args) const + { + std::for_each + ( + policy, + std::begin(handlers), + std::end(handlers), + [&](const auto& handler) + { + (*handler)(args...); + } + ); + } + + /** + * Emits a signal to all connected handlers. + * + * @param args Signal arguments. + */ + void emit(Args... args) const + { + emit(std::execution::seq, args...); + } + +private: + /// Signal connector. + connector_type signal_connector; + + /// List of connected signal handlers. + std::list> handlers; +}; + +//} // namespace event + +#endif // ANTKEEPER_EVENT_SIGNAL_HPP diff --git a/src/game/state/main-menu.cpp b/src/game/state/main-menu.cpp index 81573ed..ccfdabc 100644 --- a/src/game/state/main-menu.cpp +++ b/src/game/state/main-menu.cpp @@ -43,6 +43,9 @@ #include "game/component/transform.hpp" #include "math/projection.hpp" #include +#include + +#include "event/signal.hpp" namespace game { namespace state { @@ -52,6 +55,54 @@ main_menu::main_menu(game::context& ctx, bool fade_in): { ctx.logger->push_task("Entering main menu state"); + auto listener1 = [](int x, int y) + { + std::cout << "listener1 received " << x << std::endl; + }; + auto listener2 = [](int x, int y) + { + std::cout << "listener2 received " << x << std::endl; + }; + auto listener3 = [](int x, int y) + { + std::cout << "listener3 received " << x << std::endl; + }; + + viewport_size_connection = ctx.app->get_viewport_size_signal().connect + ( + [](int w, int h) + { + std::cout << "viewport resized " << w << "x" << h << std::endl; + } + ); + + window_motion_connection = ctx.app->get_window_motion_signal().connect + ( + [](int x, int y) + { + std::cout << "window moved to " << x << ", " << y << std::endl; + } + ); + + window_focus_connection = ctx.app->get_window_focus_signal().connect + ( + [](bool focus) + { + if (focus) + std::cout << "focus gained" << std::endl; + else + std::cout << "focus lost" << std::endl; + } + ); + + window_close_connection = ctx.app->get_window_close_signal().connect + ( + []() + { + std::cout << "window closed" << std::endl; + } + ); + ctx.ui_clear_pass->set_cleared_buffers(true, true, false); // Construct title text diff --git a/src/game/state/main-menu.hpp b/src/game/state/main-menu.hpp index b41a5b2..b7f30a5 100644 --- a/src/game/state/main-menu.hpp +++ b/src/game/state/main-menu.hpp @@ -24,6 +24,7 @@ #include "scene/text.hpp" #include "animation/animation.hpp" #include "entity/id.hpp" +#include "event/signal.hpp" namespace game { namespace state { @@ -42,6 +43,11 @@ private: animation title_fade_animation; entity::id swarm_eid; + + std::shared_ptr window_close_connection; + std::shared_ptr window_motion_connection; + std::shared_ptr window_focus_connection; + std::shared_ptr viewport_size_connection; }; } // namespace state diff --git a/src/render/stage.cpp b/src/render/stage.cpp new file mode 100644 index 0000000..74ec45b --- /dev/null +++ b/src/render/stage.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "render/stage.hpp" + +namespace render { + +stage::stage(): + priority(0) +{} + +} // namespace render diff --git a/src/render/stage.hpp b/src/render/stage.hpp index 9a4c2cb..4c3248b 100644 --- a/src/render/stage.hpp +++ b/src/render/stage.hpp @@ -30,8 +30,15 @@ namespace render { class stage { public: + /** + * Constructs a render stage and sets it priority. + * + * @param priority Stage execution order priority. + */ + stage(int priority); + /// Constructs a render stage. - stage() = default; + stage(); /// Destructs a render stage. virtual ~stage() = default; @@ -41,9 +48,27 @@ public: * * @param ctx Render context. */ - virtual void execute(render::context& ctx) = 0; + virtual void execute(render::context& ctx) const = 0; + + /** + * Sets the priority of the stage's execution order in the render pipeline. + * + * @param priority Stage execution order priority. Stages with lower priorities are executed first. + */ + void set_priority(int priority); + + /// Returns the priority of the stage's execution order in the render pipeline. + int get_priority() const noexcept; + +private: + int priority; }; +inline int stage::get_priority() const noexcept +{ + return priority; +} + } // namespace render #endif // ANTKEEPER_RENDER_STAGE_HPP diff --git a/src/render/stage/culling-stage.cpp b/src/render/stage/culling-stage.cpp index b08811a..8e55ee6 100644 --- a/src/render/stage/culling-stage.cpp +++ b/src/render/stage/culling-stage.cpp @@ -26,7 +26,7 @@ namespace render { -void culling_stage::execute(render::context& ctx) +void culling_stage::execute(render::context& ctx) const { // Get list of all objects in the collection const std::list& objects = *(ctx.collection->get_objects()); diff --git a/src/render/stage/culling-stage.hpp b/src/render/stage/culling-stage.hpp index 9f616f4..3a95ed8 100644 --- a/src/render/stage/culling-stage.hpp +++ b/src/render/stage/culling-stage.hpp @@ -37,7 +37,7 @@ public: virtual ~culling_stage() = default; /// @copydoc render::stage::execute(render::context&) - virtual void execute(render::context& ctx) final; + virtual void execute(render::context& ctx) const final; }; } // namespace render