diff --git a/glfw/backend_utils.c b/glfw/backend_utils.c index 770542ec9..58ccb5fa6 100644 --- a/glfw/backend_utils.c +++ b/glfw/backend_utils.c @@ -8,8 +8,10 @@ #define _GNU_SOURCE #include "backend_utils.h" +#include #include #include +#include #ifdef __NetBSD__ #define ppoll pollts @@ -42,24 +44,25 @@ addWatch(EventLoopData *eld, int fd, int events, int enabled, watch_callback_fun return w->id; } +#define removeX(which, item_id, update_func) {\ + for (nfds_t i = 0; i < eld->which##_count; i++) { \ + if (eld->which[i].id == item_id) { \ + eld->which##_count--; \ + if (i < eld->which##_count) { \ + memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \ + } \ + update_func(eld); break; \ +}}} + void removeWatch(EventLoopData *eld, id_type watch_id) { - for (nfds_t i = 0; i < eld->watches_count; i++) { - if (eld->watches[i].id == watch_id) { - eld->watches_count--; - if (i < eld->watches_count) { - memmove(eld->watches + i, eld->watches + i + 1, sizeof(eld->watches[0]) * (eld->watches_count - i)); - } - update_fds(eld); - break; - } - } + removeX(watches, watch_id, update_fds); } void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) { for (nfds_t i = 0; i < eld->watches_count; i++) { - if (eld->watches[i].fd == watch_id) { + if (eld->watches[i].id == watch_id) { if (eld->watches[i].enabled != enabled) { eld->watches[i].enabled = enabled; update_fds(eld); @@ -69,9 +72,72 @@ toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) { } } +static id_type timer_counter = 0; +extern double glfwGetTime(void); + +static int +compare_timers(const void *a_, const void *b_) { + const Timer *a = (const Timer*)a_, *b = (const Timer*)b_; + return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0; +} + +static inline void +update_timers(EventLoopData *eld) { + if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers); +} + +id_type +addTimer(EventLoopData *eld, double interval, int enabled, timer_callback_func cb, void *cb_data) { + if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) return 0; + Timer *t = eld->timers + eld->timers_count++; + t->interval = interval; + t->trigger_at = enabled ? glfwGetTime() + interval : DBL_MAX; + t->callback = cb; + t->callback_data = cb_data; + t->id = ++timer_counter; + update_timers(eld); + return t->id; +} + void -prepareForPoll(EventLoopData *eld) { +removeTimer(EventLoopData *eld, id_type timer_id) { + removeX(timers, timer_id, update_timers); +} + +void +toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) { + for (nfds_t i = 0; i < eld->timers_count; i++) { + if (eld->timers[i].id == timer_id) { + double trigger_at = enabled ? (glfwGetTime() + eld->timers[i].interval) : DBL_MAX; + if (trigger_at != eld->timers[i].trigger_at) { + eld->timers[i].trigger_at = trigger_at; + update_timers(eld); + } + break; + } + } +} + +void +changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) { + for (nfds_t i = 0; i < eld->timers_count; i++) { + if (eld->timers[i].id == timer_id) { + eld->timers[i].interval = interval; + break; + } + } +} + + +double +prepareForPoll(EventLoopData *eld, double timeout) { for (nfds_t i = 0; i < eld->fds_count; i++) eld->fds[i].revents = 0; + if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return timeout; + double now = glfwGetTime(), next_repeat_at = eld->timers[0].trigger_at; + if (timeout < 0 || now + timeout > next_repeat_at) { + timeout = next_repeat_at <= now ? 0 : next_repeat_at - now; + } + return timeout; } int @@ -82,7 +148,7 @@ pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) { return ppoll(fds, nfds, &tv, NULL); } -void +static void dispatchEvents(EventLoopData *eld) { for (unsigned w = 0, f = 0; f < eld->fds_count; f++) { while(eld->watches[w].fd != eld->fds[f].fd) w++; @@ -94,6 +160,29 @@ dispatchEvents(EventLoopData *eld) { } } +unsigned +dispatchTimers(EventLoopData *eld) { + if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0; + static struct { timer_callback_func func; id_type id; void* data; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])]; + unsigned num_dispatches = 0; + double now = glfwGetTime(); + for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at < DBL_MAX; i++) { + if (eld->timers[i].trigger_at <= now) { + eld->timers[i].trigger_at = now + eld->timers[i].interval; + dispatches[num_dispatches].func = eld->timers[i].callback; + dispatches[num_dispatches].id = eld->timers[i].id; + dispatches[num_dispatches].data = eld->timers[i].callback_data; + num_dispatches++; + } + } + // we dispatch separately so that the callbacks can modify timers + for (unsigned i = 0; i < num_dispatches; i++) { + dispatches[i].func(dispatches[i].id, dispatches[i].data); + } + if (num_dispatches) update_timers(eld); + return num_dispatches; +} + static void drain_wakeup_fd(int fd, int events, void* data) { static char drain_buf[64]; @@ -106,6 +195,41 @@ initPollData(EventLoopData *eld, int wakeup_fd, int display_fd) { addWatch(eld, wakeup_fd, POLLIN, 1, drain_wakeup_fd, NULL); } + +int +pollForEvents(EventLoopData *eld, double timeout) { + int read_ok = 0; + timeout = prepareForPoll(eld, timeout); + int result; + double end_time = glfwGetTime() + timeout; + + while(1) { + if (timeout >= 0) { + result = pollWithTimeout(eld->fds, eld->fds_count, timeout); + dispatchTimers(eld); + if (result > 0) { + dispatchEvents(eld); + read_ok = eld->watches[0].ready; + break; + } + timeout = end_time - glfwGetTime(); + if (timeout <= 0) break; + if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue; + break; + } else { + result = poll(eld->fds, eld->fds_count, -1); + dispatchTimers(eld); + if (result > 0) { + dispatchEvents(eld); + read_ok = eld->watches[0].ready; + } + if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue; + break; + } + } + return read_ok; +} + void closeFds(int *fds, size_t count) { while(count--) { diff --git a/glfw/backend_utils.h b/glfw/backend_utils.h index 921146fc0..5ed3604f3 100644 --- a/glfw/backend_utils.h +++ b/glfw/backend_utils.h @@ -28,8 +28,9 @@ #include #include -typedef void(*watch_callback_func)(int, int, void*); typedef unsigned long long id_type; +typedef void(*watch_callback_func)(int, int, void*); +typedef void(*timer_callback_func)(id_type, void*); typedef struct { int fd, events, enabled, ready; @@ -38,19 +39,33 @@ typedef struct { id_type id; } Watch; +typedef struct { + id_type id; + double interval, trigger_at; + timer_callback_func callback; + void *callback_data; +} Timer; + + typedef struct { struct pollfd fds[32]; int wakeupFds[2]; - nfds_t watches_count, fds_count; + nfds_t watches_count, fds_count, timers_count; Watch watches[32]; + Timer timers[128]; } EventLoopData; id_type addWatch(EventLoopData *eld, int fd, int events, int enabled, watch_callback_func cb, void *cb_data); void removeWatch(EventLoopData *eld, id_type watch_id); void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled); -void prepareForPoll(EventLoopData *eld); +id_type addTimer(EventLoopData *eld, double interval, int enabled, timer_callback_func cb, void *cb_data); +void removeTimer(EventLoopData *eld, id_type timer_id); +void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled); +void changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval); +double prepareForPoll(EventLoopData *eld, double timeout); int pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout); -void dispatchEvents(EventLoopData *eld); +int pollForEvents(EventLoopData *eld, double timeout); +unsigned dispatchTimers(EventLoopData *eld); void closeFds(int *fds, size_t count); void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd); diff --git a/glfw/wl_init.c b/glfw/wl_init.c index eeee4f388..32bdd0da2 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -407,6 +407,15 @@ static void keyboardHandleLeave(void* data, _glfwInputWindowFocus(window, GLFW_FALSE); } +static void +dispatchPendingKeyRepeats(id_type timer_id, void *data) { + if (_glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return; + glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT); + changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, ((double)_glfw.wl.keyboardRepeatRate) / 1000.0); + toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); +} + + static void keyboardHandleKey(void* data, struct wl_keyboard* keyboard, uint32_t serial, @@ -419,14 +428,18 @@ static void keyboardHandleKey(void* data, return; int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE; glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action); - _glfw.wl.keyRepeatInfo.nextRepeatAt = 0; + GLFWbool repeatable = GLFW_FALSE; if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key)) { _glfw.wl.keyRepeatInfo.key = key; - _glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0; + repeatable = GLFW_TRUE; _glfw.wl.keyRepeatInfo.keyboardFocus = window; } + if (repeatable) { + changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0); + } + toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0); } static void keyboardHandleModifiers(void* data, @@ -666,6 +679,7 @@ int _glfwPlatformInit(void) } initPollData(&_glfw.wl.eventLoopData, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display)); glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData); + _glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, 0.5, 0, dispatchPendingKeyRepeats, NULL); _glfw.wl.registry = wl_display_get_registry(_glfw.wl.display); wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL); diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index fe5a5f3a5..4f0f5c117 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -205,7 +205,7 @@ typedef struct _GLFWlibraryWayland int32_t keyboardRepeatDelay; struct { uint32_t key; - double nextRepeatAt; + id_type keyRepeatTimer; _GLFWwindow* keyboardFocus; } keyRepeatInfo; _GLFWXKBData xkb; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 39aef9199..1df9be21f 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -688,34 +688,14 @@ static GLFWbool createXdgSurface(_GLFWwindow* window) return GLFW_TRUE; } -static void -dispatchPendingKeyRepeats() { - if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return; - double now = glfwGetTime(); - while (_glfw.wl.keyRepeatInfo.nextRepeatAt <= now) { - glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT); - _glfw.wl.keyRepeatInfo.nextRepeatAt += 1.0 / _glfw.wl.keyboardRepeatRate; - now = glfwGetTime(); - } -} - -static double -adjustTimeoutForKeyRepeat(double timeout) { - if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return timeout; - double now = glfwGetTime(); - if (timeout < 0 || now + timeout > _glfw.wl.keyRepeatInfo.nextRepeatAt) { - timeout = _glfw.wl.keyRepeatInfo.nextRepeatAt <= now ? 0 : ( (_glfw.wl.keyRepeatInfo.nextRepeatAt - now) + 0.001 ); - } - return timeout; -} - static void handleEvents(double timeout) { struct wl_display* display = _glfw.wl.display; - while (wl_display_prepare_read(display) != 0) + while (wl_display_prepare_read(display) != 0) { wl_display_dispatch_pending(display); + } // If an error different from EAGAIN happens, we have likely been // disconnected from the Wayland session, try to handle that the best we @@ -732,24 +712,8 @@ handleEvents(double timeout) return; } - dispatchPendingKeyRepeats(); - timeout = adjustTimeoutForKeyRepeat(timeout); - GLFWbool read_ok = GLFW_FALSE; - prepareForPoll(&_glfw.wl.eventLoopData); - - if (timeout >= 0) { - const int result = pollWithTimeout(_glfw.wl.eventLoopData.fds, _glfw.wl.eventLoopData.fds_count, timeout); - if (result > 0) { - dispatchEvents(&_glfw.wl.eventLoopData); - read_ok = _glfw.wl.eventLoopData.watches[0].ready; - } - } else { - if (poll(_glfw.wl.eventLoopData.fds, _glfw.wl.eventLoopData.fds_count, -1) > 0) { - dispatchEvents(&_glfw.wl.eventLoopData); - read_ok = _glfw.wl.eventLoopData.watches[0].ready; - } - } - if (read_ok) { + GLFWbool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout); + if (display_read_ok) { wl_display_read_events(display); wl_display_dispatch_pending(display); } @@ -757,7 +721,6 @@ handleEvents(double timeout) { wl_display_cancel_read(display); } - dispatchPendingKeyRepeats(); } // Translates a GLFW standard cursor to a theme cursor name diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 1b489e23f..9cbdd3d94 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -54,38 +54,31 @@ // This avoids blocking other threads via the per-display Xlib lock that also // covers GLX functions // -static GLFWbool waitForEvent(double* timeout) -{ - nfds_t count = _glfw.x11.eventLoopData.fds_count; +void _glfwDispatchX11Events(void); - for (;;) - { - prepareForPoll(&_glfw.x11.eventLoopData); - if (timeout) - { - const uint64_t base = _glfwPlatformGetTimerValue(); - const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, count, *timeout); - *timeout -= (_glfwPlatformGetTimerValue() - base) / - (double) _glfwPlatformGetTimerFrequency(); +static void +handleEvents(double timeout) { + int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout); + if (display_read_ok) _glfwDispatchX11Events(); +} - if (result > 0) { - dispatchEvents(&_glfw.x11.eventLoopData); - return GLFW_TRUE; - } - if (result == 0) - return GLFW_FALSE; - if (*timeout > 0 && (errno == EINTR || errno == EAGAIN)) continue; +static GLFWbool +waitForX11Event(double timeout) { + // returns true iff there is X11 data waiting to be read, does not run watches and timers + double end_time = glfwGetTime() + timeout; + while(GLFW_TRUE) { + if (timeout >= 0) { + const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout); + if (result > 0) return GLFW_TRUE; + timeout = end_time - glfwGetTime(); + if (timeout <= 0) return GLFW_FALSE; + if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue; + return GLFW_FALSE; + } else { + const int result = poll(_glfw.x11.eventLoopData.fds, 1, -1); + if (result > 0) return GLFW_TRUE; + if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue; return GLFW_FALSE; - } - else { - const int result = poll(_glfw.x11.eventLoopData.fds, count, -1); - if (result > 0) { - dispatchEvents(&_glfw.x11.eventLoopData); - return GLFW_TRUE; - } - if (result == 0) - return GLFW_FALSE; - if (errno != EINTR && errno != EAGAIN) return GLFW_FALSE; } } } @@ -96,14 +89,13 @@ static GLFWbool waitForEvent(double* timeout) static GLFWbool waitForVisibilityNotify(_GLFWwindow* window) { XEvent dummy; - double timeout = 0.1; while (!XCheckTypedWindowEvent(_glfw.x11.display, window->x11.handle, VisibilityNotify, &dummy)) { - if (!waitForEvent(&timeout)) + if (!waitForX11Event(0.1)) return GLFW_FALSE; } @@ -975,7 +967,7 @@ static const char* getSelectionString(Atom selection) SelectionNotify, ¬ification)) { - waitForEvent(NULL); + waitForX11Event(-1); } if (notification.xselection.property == None) @@ -1011,7 +1003,7 @@ static const char* getSelectionString(Atom selection) isSelPropNewValueNotify, (XPointer) ¬ification)) { - waitForEvent(NULL); + waitForX11Event(-1); } XFree(data); @@ -1843,7 +1835,7 @@ void _glfwPushSelectionToManagerX11(void) } } - waitForEvent(NULL); + waitForX11Event(-1); } } @@ -2133,7 +2125,6 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, _glfw.x11.NET_REQUEST_FRAME_EXTENTS) { XEvent event; - double timeout = 0.5; // Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to // function before the window is mapped @@ -2150,7 +2141,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, isFrameExtentsEvent, (XPointer) window)) { - if (!waitForEvent(&timeout)) + if (!waitForX11Event(0.5)) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue"); @@ -2551,8 +2542,7 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) PropModeReplace, (unsigned char*) &value, 1); } -void _glfwPlatformPollEvents(void) -{ +void _glfwDispatchX11Events(void) { _GLFWwindow* window; #if defined(__linux__) @@ -2585,23 +2575,19 @@ void _glfwPlatformPollEvents(void) XFlush(_glfw.x11.display); } +void _glfwPlatformPollEvents(void) +{ + handleEvents(0); +} + void _glfwPlatformWaitEvents(void) { - while (!XPending(_glfw.x11.display)) - waitForEvent(NULL); - - _glfwPlatformPollEvents(); + handleEvents(-1); } void _glfwPlatformWaitEventsTimeout(double timeout) { - while (!XPending(_glfw.x11.display)) - { - if (!waitForEvent(&timeout)) - break; - } - - _glfwPlatformPollEvents(); + handleEvents(timeout); } void _glfwPlatformPostEmptyEvent(void)