diff --git a/README.md b/README.md index 3362aa07..257068cd 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ does not find Doxygen, the documentation will not be generated. absolute and relative window size limits - Added `glfwGetKeyName` for querying the layout-specific name of printable keys + - Added `glfwWaitEventsTimeout` for waiting for events for a set amount of time - Added `glfwGetTimerValue` and `glfwGetTimerFrequency` for raw timer access - Added `GLFWuint64` for platform-independent 64-bit unsigned values - Added `GLFW_NO_API` for creating window without contexts diff --git a/docs/input.dox b/docs/input.dox index b0fc9f29..89587d3d 100644 --- a/docs/input.dox +++ b/docs/input.dox @@ -58,6 +58,17 @@ processes all received events. This saves a great deal of CPU cycles and is useful for, for example, editing tools. There must be at least one GLFW window for this function to sleep. +If you want to wait for events but have UI elements that need periodic updates, +call @ref glfwWaitEventsTimeout. + +@code +glfwWaitEventsTimeout(0.7); +@endcode + +It puts the thread to sleep until at least one event has been received, or until +the specified number of seconds have elapsed. It then processes any received +events. + If the main thread is sleeping in @ref glfwWaitEvents, you can wake it from another thread by posting an empty event to the event queue with @ref glfwPostEmptyEvent. diff --git a/docs/news.dox b/docs/news.dox index 87fd3964..b1d0e30d 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -43,6 +43,12 @@ GLFW now supports raw timer values with @ref glfwGetTimerValue and @ref glfwGetTimerFrequency. +@subsection news_32_waittimeout Wait for events with timeout + +GLFW now supports waiting for events for a set amount of time with @ref +glfwWaitEventsTimeout. + + @section news_31 New features in 3.1 These are the release highlights. For a full list of changes see the diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 7199da8c..8abe5f3b 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -2633,6 +2633,7 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window * * @sa @ref events * @sa glfwWaitEvents + * @sa glfwWaitEventsTimeout * * @since Added in version 1.0. * @@ -2677,6 +2678,7 @@ GLFWAPI void glfwPollEvents(void); * * @sa @ref events * @sa glfwPollEvents + * @sa glfwWaitEventsTimeout * * @since Added in version 2.5. * @@ -2684,6 +2686,52 @@ GLFWAPI void glfwPollEvents(void); */ GLFWAPI void glfwWaitEvents(void); +/*! @brief Waits with timeout until events are queued and processes them. + * + * This function puts the calling thread to sleep until at least one event is + * available in the event queue, or until the specified timeout is reached. If + * one or more events are available, it behaves exactly like @ref + * glfwPollEvents, i.e. the events in the queue are processed and the function + * then returns immediately. Processing events will cause the window and input + * callbacks associated with those events to be called. + * + * The timeout value must be a positive finite number. + * + * Since not all events are associated with callbacks, this function may return + * without a callback having been called even if you are monitoring all + * callbacks. + * + * On some platforms, a window move, resize or menu operation will cause event + * processing to block. This is due to how event processing is designed on + * those platforms. You can use the + * [window refresh callback](@ref window_refresh) to redraw the contents of + * your window when necessary during such operations. + * + * On some platforms, certain callbacks may be called outside of a call to one + * of the event processing functions. + * + * If no windows exist, this function returns immediately. For synchronization + * of threads in applications that do not create windows, use your threading + * library of choice. + * + * Event processing is not required for joystick input to work. + * + * @param[in] timeout The maximum amount of time, in seconds, to wait. + * + * @reentrancy This function must not be called from a callback. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref events + * @sa glfwPollEvents + * @sa glfwWaitEvents + * + * @since Added in version 3.2. + * + * @ingroup window + */ +GLFWAPI void glfwWaitEventsTimeout(double timeout); + /*! @brief Posts an empty event to the event queue. * * This function posts an empty event from the current thread to the event diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 0086e8d0..7c3f5606 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1214,6 +1214,19 @@ void _glfwPlatformWaitEvents(void) _glfwPlatformPollEvents(); } +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp sendEvent:event]; + + _glfwPlatformPollEvents(); +} + void _glfwPlatformPostEmptyEvent(void) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; diff --git a/src/internal.h b/src/internal.h index 68e2d7d1..c9ad097c 100644 --- a/src/internal.h +++ b/src/internal.h @@ -717,6 +717,11 @@ void _glfwPlatformPollEvents(void); */ void _glfwPlatformWaitEvents(void); +/*! @copydoc glfwWaitEventsTimeout + * @ingroup platform + */ +void _glfwPlatformWaitEventsTimeout(double timeout); + /*! @copydoc glfwPostEmptyEvent * @ingroup platform */ diff --git a/src/mir_window.c b/src/mir_window.c index 9d8f1fd3..b3c3acf2 100644 --- a/src/mir_window.c +++ b/src/mir_window.c @@ -550,6 +550,24 @@ void _glfwPlatformWaitEvents(void) _glfwPlatformPollEvents(); } +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + pthread_mutex_lock(&_glfw.mir.event_mutex); + + if (emptyEventQueue(_glfw.mir.event_queue)) + { + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + time.tv_sec += (long) timeout; + time.tv_nsec += (long) ((timeout - (long) timeout) * 1e9); + pthread_cond_timedwait(&_glfw.mir.event_cond, &_glfw.mir.event_mutex, &time); + } + + pthread_mutex_unlock(&_glfw.mir.event_mutex); + + _glfwPlatformPollEvents(); +} + void _glfwPlatformPostEmptyEvent(void) { } diff --git a/src/win32_window.c b/src/win32_window.c index 85a72ad9..7ee1c8a1 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -1142,6 +1142,13 @@ void _glfwPlatformWaitEvents(void) _glfwPlatformPollEvents(); } +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD) (timeout * 1e3), QS_ALLEVENTS); + + _glfwPlatformPollEvents(); +} + void _glfwPlatformPostEmptyEvent(void) { _GLFWwindow* window = _glfw.windowListHead; diff --git a/src/window.c b/src/window.c index 2dbec86d..8e055b3b 100644 --- a/src/window.c +++ b/src/window.c @@ -31,6 +31,7 @@ #include #include #include +#include ////////////////////////////////////////////////////////////////////////// @@ -807,6 +808,19 @@ GLFWAPI void glfwWaitEvents(void) _glfwPlatformWaitEvents(); } +GLFWAPI void glfwWaitEventsTimeout(double timeout) +{ + _GLFW_REQUIRE_INIT(); + + if (timeout != timeout || timeout < 0.0 || timeout > DBL_MAX) + { + _glfwInputError(GLFW_INVALID_VALUE, "Invalid time"); + return; + } + + _glfwPlatformWaitEventsTimeout(timeout); +} + GLFWAPI void glfwPostEmptyEvent(void) { _GLFW_REQUIRE_INIT(); diff --git a/src/wl_window.c b/src/wl_window.c index 1fe5b86a..f1d5b520 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -516,6 +516,11 @@ void _glfwPlatformWaitEvents(void) handleEvents(-1); } +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + handleEvents((int) (timeout * 1e3)); +} + void _glfwPlatformPostEmptyEvent(void) { wl_display_sync(_glfw.wl.display); diff --git a/src/x11_window.c b/src/x11_window.c index 5c347694..c4bbf9f1 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1912,6 +1912,28 @@ void _glfwPlatformWaitEvents(void) _glfwPlatformPollEvents(); } +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + const double deadline = timeout + _glfwPlatformGetTimerValue() / + (double) _glfwPlatformGetTimerFrequency(); + + while (!XPending(_glfw.x11.display)) + { + const double remaining = deadline - _glfwPlatformGetTimerValue() / + (double) _glfwPlatformGetTimerFrequency(); + if (remaining <= 0.0) + return; + + const long seconds = (long) remaining; + const long microseconds = (long) ((remaining - seconds) * 1e6); + struct timeval tv = { seconds, microseconds }; + + selectDisplayConnection(&tv); + } + + _glfwPlatformPollEvents(); +} + void _glfwPlatformPostEmptyEvent(void) { XEvent event;