Add timeout parameter to waitEvent

This commit is contained in:
vittorioromeo 2024-06-14 13:53:58 +02:00 committed by Vittorio Romeo
parent 51efe50ec4
commit e7b23ffcd1
5 changed files with 114 additions and 44 deletions

View File

@ -33,6 +33,7 @@
#include <SFML/Window/WindowEnums.hpp> #include <SFML/Window/WindowEnums.hpp>
#include <SFML/Window/WindowHandle.hpp> #include <SFML/Window/WindowHandle.hpp>
#include <SFML/System/Time.hpp>
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
#include <memory> #include <memory>
@ -202,11 +203,12 @@ public:
/// \brief Wait for an event and return it /// \brief Wait for an event and return it
/// ///
/// This function is blocking: if there's no pending event then /// This function is blocking: if there's no pending event then
/// it will wait until an event is received. After this function /// it will wait until an event is received or until the provided
/// returns if no error occurred, the returned event will not be /// timeout elapses. After this function returns if no error nor
/// empty. This function is typically used when you have a thread /// timeout occurred, the returned event will not be empty.
/// that is dedicated to events handling: you want to make this /// This function is typically used when you have a thread that is
/// thread sleep as long as no new event is received. /// dedicated to events handling: you want to make this thread sleep
/// as long as no new event is received.
/// \code /// \code
/// if (const auto event = window.waitEvent()) /// if (const auto event = window.waitEvent())
/// { /// {
@ -214,12 +216,14 @@ public:
/// } /// }
/// \endcode /// \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 /// \see pollEvent
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] Event waitEvent(); [[nodiscard]] Event waitEvent(Time timeout = Time::Zero);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the position of the window /// \brief Get the position of the window

View File

@ -149,17 +149,17 @@ bool WindowBase::isOpen() const
Event WindowBase::pollEvent() Event WindowBase::pollEvent()
{ {
Event event; Event event;
if (m_impl && (event = m_impl->popEvent(false))) if (m_impl && (event = m_impl->pollEvent()))
filterEvent(event); filterEvent(event);
return event; return event;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Event WindowBase::waitEvent() Event WindowBase::waitEvent(Time timeout)
{ {
Event event; Event event;
if (m_impl && (event = m_impl->popEvent(true))) if (m_impl && (event = m_impl->waitEvent(timeout)))
filterEvent(event); filterEvent(event);
return event; return event;
} }

View File

@ -35,6 +35,7 @@
#include <SFML/System/Time.hpp> #include <SFML/System/Time.hpp>
#include <array> #include <array>
#include <chrono>
#include <memory> #include <memory>
#include <cmath> #include <cmath>
@ -175,35 +176,46 @@ void WindowImpl::setMaximumSize(const std::optional<Vector2u>& 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 the event queue is empty, let's first check if new events are available from the OS
if (m_events.empty()) if (m_events.empty())
{ populateEventQueue();
// Get events from the system
processJoystickEvents();
processSensorEvents();
processEvents();
// In blocking mode, we must process events until one is triggered return popEvent();
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();
}
}
}
////////////////////////////////////////////////////////////
Event WindowImpl::popEvent()
{
Event event; Event event;
// Pop the first event of the queue, if it is not empty
if (!m_events.empty()) if (!m_events.empty())
{ {
event = m_events.front(); 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, bool WindowImpl::createVulkanSurface([[maybe_unused]] const VkInstance& instance,
[[maybe_unused]] VkSurfaceKHR& surface, [[maybe_unused]] VkSurfaceKHR& surface,

View File

@ -55,6 +55,7 @@
namespace sf namespace sf
{ {
class String; class String;
class Time;
namespace priv namespace priv
{ {
@ -121,21 +122,28 @@ public:
void setJoystickThreshold(float threshold); 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 /// If there's no event available, this function calls the
/// window's internal event processing function. /// 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 /// \brief Get the OS-specific handle of the window
@ -325,6 +333,12 @@ protected:
private: private:
struct JoystickStatesImpl; 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 /// \brief Read the joysticks state and generate the appropriate events
/// ///
@ -337,6 +351,12 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void processSensorEvents(); void processSensorEvents();
////////////////////////////////////////////////////////////
/// \brief Read joystick, sensors, and OS state and populate event queue
///
////////////////////////////////////////////////////////////
void populateEventQueue();
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -9,6 +9,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <WindowUtil.hpp> #include <WindowUtil.hpp>
#include <chrono>
#include <type_traits> #include <type_traits>
TEST_CASE("[Window] sf::WindowBase", runDisplayTests()) TEST_CASE("[Window] sf::WindowBase", runDisplayTests())
@ -103,17 +104,41 @@ TEST_CASE("[Window] sf::WindowBase", runDisplayTests())
} }
SECTION("pollEvent()") SECTION("pollEvent()")
{
SECTION("Uninitialized window")
{ {
sf::WindowBase windowBase; sf::WindowBase windowBase;
CHECK(!windowBase.pollEvent()); CHECK(!windowBase.pollEvent());
} }
}
SECTION("waitEvent()") SECTION("waitEvent()")
{
SECTION("Uninitialized window")
{ {
sf::WindowBase windowBase; sf::WindowBase windowBase;
CHECK(!windowBase.waitEvent()); 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") SECTION("Set/get position")
{ {
sf::WindowBase windowBase; sf::WindowBase windowBase;