From e7b23ffcd1e534316d332f410b332a476c871469 Mon Sep 17 00:00:00 2001 From: vittorioromeo Date: Fri, 14 Jun 2024 13:53:58 +0200 Subject: [PATCH] Add `timeout` parameter to `waitEvent` --- include/SFML/Window/WindowBase.hpp | 18 +++++---- src/SFML/Window/WindowBase.cpp | 6 +-- src/SFML/Window/WindowImpl.cpp | 65 ++++++++++++++++++++---------- src/SFML/Window/WindowImpl.hpp | 36 +++++++++++++---- test/Window/WindowBase.test.cpp | 33 +++++++++++++-- 5 files changed, 114 insertions(+), 44 deletions(-) diff --git a/include/SFML/Window/WindowBase.hpp b/include/SFML/Window/WindowBase.hpp index acb294907..15512cae5 100644 --- a/include/SFML/Window/WindowBase.hpp +++ b/include/SFML/Window/WindowBase.hpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -202,11 +203,12 @@ public: /// \brief Wait for an event and return it /// /// This function is blocking: if there's no pending event then - /// it will wait until an event is received. After this function - /// returns if no error occurred, the returned event will not be - /// empty. This function is typically used when you have a thread - /// that is dedicated to events handling: you want to make this - /// thread sleep as long as no new event is received. + /// it will wait until an event is received or until the provided + /// timeout elapses. After this function returns if no error nor + /// timeout occurred, the returned event will not be empty. + /// This function is typically used when you have a thread that is + /// dedicated to events handling: you want to make this thread sleep + /// as long as no new event is received. /// \code /// if (const auto event = window.waitEvent()) /// { @@ -214,12 +216,14 @@ public: /// } /// \endcode /// - /// \return The event + /// \param timeout Maximum time to wait (`Time::Zero` for infinite) + /// + /// \return The event; will be `Empty` (convertible to `false`) on timeout or if window was closed /// /// \see pollEvent /// //////////////////////////////////////////////////////////// - [[nodiscard]] Event waitEvent(); + [[nodiscard]] Event waitEvent(Time timeout = Time::Zero); //////////////////////////////////////////////////////////// /// \brief Get the position of the window diff --git a/src/SFML/Window/WindowBase.cpp b/src/SFML/Window/WindowBase.cpp index e44124e6f..ab90f6981 100644 --- a/src/SFML/Window/WindowBase.cpp +++ b/src/SFML/Window/WindowBase.cpp @@ -149,17 +149,17 @@ bool WindowBase::isOpen() const Event WindowBase::pollEvent() { Event event; - if (m_impl && (event = m_impl->popEvent(false))) + if (m_impl && (event = m_impl->pollEvent())) filterEvent(event); return event; } //////////////////////////////////////////////////////////// -Event WindowBase::waitEvent() +Event WindowBase::waitEvent(Time timeout) { Event event; - if (m_impl && (event = m_impl->popEvent(true))) + if (m_impl && (event = m_impl->waitEvent(timeout))) filterEvent(event); return event; } diff --git a/src/SFML/Window/WindowImpl.cpp b/src/SFML/Window/WindowImpl.cpp index 007a059d5..6d813cc2d 100644 --- a/src/SFML/Window/WindowImpl.cpp +++ b/src/SFML/Window/WindowImpl.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -175,35 +176,46 @@ void WindowImpl::setMaximumSize(const std::optional& maximumSize) //////////////////////////////////////////////////////////// -Event WindowImpl::popEvent(bool block) +Event WindowImpl::waitEvent(Time timeout) +{ + const auto timedOut = [&, startTime = std::chrono::steady_clock::now()] + { + const bool infiniteTimeout = timeout == Time::Zero; + return !infiniteTimeout && (std::chrono::steady_clock::now() - startTime) >= timeout.toDuration(); + }; + + // If the event queue is empty, let's first check if new events are available from the OS + if (m_events.empty()) + populateEventQueue(); + + // Here we use a manual wait loop instead of the optimized wait-event provided by the OS, + // so that we don't skip joystick events (which require polling) + while (m_events.empty() && !timedOut()) + { + sleep(milliseconds(10)); + populateEventQueue(); + } + + return popEvent(); +} + + +//////////////////////////////////////////////////////////// +Event WindowImpl::pollEvent() { // If the event queue is empty, let's first check if new events are available from the OS if (m_events.empty()) - { - // Get events from the system - processJoystickEvents(); - processSensorEvents(); - processEvents(); + populateEventQueue(); - // In blocking mode, we must process events until one is triggered - if (block) - { - // Here we use a manual wait loop instead of the optimized - // wait-event provided by the OS, so that we don't skip joystick - // events (which require polling) - while (m_events.empty()) - { - sleep(milliseconds(10)); - processJoystickEvents(); - processSensorEvents(); - processEvents(); - } - } - } + return popEvent(); +} + +//////////////////////////////////////////////////////////// +Event WindowImpl::popEvent() +{ Event event; - // Pop the first event of the queue, if it is not empty if (!m_events.empty()) { event = m_events.front(); @@ -311,6 +323,15 @@ void WindowImpl::processSensorEvents() } +//////////////////////////////////////////////////////////// +void WindowImpl::populateEventQueue() +{ + processJoystickEvents(); + processSensorEvents(); + processEvents(); +} + + //////////////////////////////////////////////////////////// bool WindowImpl::createVulkanSurface([[maybe_unused]] const VkInstance& instance, [[maybe_unused]] VkSurfaceKHR& surface, diff --git a/src/SFML/Window/WindowImpl.hpp b/src/SFML/Window/WindowImpl.hpp index 3dbb97c14..a36ea3ce2 100644 --- a/src/SFML/Window/WindowImpl.hpp +++ b/src/SFML/Window/WindowImpl.hpp @@ -55,6 +55,7 @@ namespace sf { class String; +class Time; namespace priv { @@ -121,21 +122,28 @@ public: void setJoystickThreshold(float threshold); //////////////////////////////////////////////////////////// - /// \brief Return the next window event available + /// \brief Wait for and return the next available window event /// /// If there's no event available, this function calls the /// window's internal event processing function. - /// The \a block parameter controls the behavior of the function - /// if no event is available: if it is true then the function - /// doesn't return until a new event is triggered; otherwise it - /// returns an empty event to indicate that no event is available. /// - /// \param block Use true to block the thread until an event arrives + /// \param timeout Maximum time to wait (`Time::Zero` for infinite) /// - /// \return The event; can be `Empty` (convertible to `false`) if not blocking + /// \return The event on success, `Event::Empty` otherwise /// //////////////////////////////////////////////////////////// - Event popEvent(bool block); + [[nodiscard]] Event waitEvent(Time timeout); + + //////////////////////////////////////////////////////////// + /// \brief Return the next window event, if available + /// + /// If there's no event available, this function calls the + /// window's internal event processing function. + /// + /// \return The event if available, `Event::Empty` otherwise + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Event pollEvent(); //////////////////////////////////////////////////////////// /// \brief Get the OS-specific handle of the window @@ -325,6 +333,12 @@ protected: private: struct JoystickStatesImpl; + //////////////////////////////////////////////////////////// + /// \brief Pop the first event of the queue if available, otherwise an empty event + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Event popEvent(); + //////////////////////////////////////////////////////////// /// \brief Read the joysticks state and generate the appropriate events /// @@ -337,6 +351,12 @@ private: //////////////////////////////////////////////////////////// void processSensorEvents(); + //////////////////////////////////////////////////////////// + /// \brief Read joystick, sensors, and OS state and populate event queue + /// + //////////////////////////////////////////////////////////// + void populateEventQueue(); + //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// diff --git a/test/Window/WindowBase.test.cpp b/test/Window/WindowBase.test.cpp index ddb3a5ba6..cf8401d4c 100644 --- a/test/Window/WindowBase.test.cpp +++ b/test/Window/WindowBase.test.cpp @@ -9,6 +9,7 @@ #include #include +#include #include TEST_CASE("[Window] sf::WindowBase", runDisplayTests()) @@ -104,14 +105,38 @@ TEST_CASE("[Window] sf::WindowBase", runDisplayTests()) SECTION("pollEvent()") { - sf::WindowBase windowBase; - CHECK(!windowBase.pollEvent()); + SECTION("Uninitialized window") + { + sf::WindowBase windowBase; + CHECK(!windowBase.pollEvent()); + } } SECTION("waitEvent()") { - sf::WindowBase windowBase; - CHECK(!windowBase.waitEvent()); + SECTION("Uninitialized window") + { + sf::WindowBase windowBase; + CHECK(!windowBase.waitEvent()); + } + + SECTION("Initialized window") + { + sf::WindowBase windowBase(sf::VideoMode({360, 240}), "WindowBase Tests"); + + constexpr auto timeout = sf::milliseconds(100); + + const auto startTime = std::chrono::steady_clock::now(); + const auto event = windowBase.waitEvent(timeout); + const auto elapsed = std::chrono::steady_clock::now() - startTime; + + REQUIRE(elapsed < (timeout + sf::milliseconds(50)).toDuration()); + + if (elapsed <= timeout.toDuration()) + CHECK(event); + else + CHECK(!event); + } } SECTION("Set/get position")