From 1cb15dedacaf8fa2dae40545323ab31ed7452614 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 18 Jul 2019 14:25:11 +0530 Subject: [PATCH] Simplify the event loop code Also reduce input latency by ignoring repaint_delay when there is actual pending input. Gets rid of request_tick_callback(). Now empty events result in the tick callback being called so there is only a single mechanism for waking up the main loop and getting the tick callback called. --- glfw/backend_utils.c | 23 ++++++++++++++++++++--- glfw/backend_utils.h | 2 ++ glfw/cocoa_init.m | 37 +++++++++++++++++++++++++++++++------ glfw/cocoa_platform.h | 2 +- glfw/cocoa_window.m | 38 ++++++++++++++------------------------ glfw/glfw3.h | 1 - glfw/init.c | 4 ---- glfw/internal.h | 1 - glfw/main_loop.h | 26 ++++++++------------------ glfw/wl_window.c | 1 + glfw/x11_window.c | 1 + kitty/child-monitor.c | 20 +++++++++++++------- kitty/config_data.py | 3 ++- kitty/glfw-wrapper.c | 3 --- kitty/glfw-wrapper.h | 4 ---- kitty/glfw.c | 7 +------ kitty/glfw_tests.c | 4 +--- 17 files changed, 95 insertions(+), 82 deletions(-) diff --git a/glfw/backend_utils.c b/glfw/backend_utils.c index f1ba80612..9058c6c37 100644 --- a/glfw/backend_utils.c +++ b/glfw/backend_utils.c @@ -222,19 +222,25 @@ dispatchTimers(EventLoopData *eld) { } static void -drain_wakeup_fd(int fd, int events UNUSED, void* data UNUSED) { +drain_wakeup_fd(int fd, EventLoopData* eld) { static char drain_buf[64]; + eld->wakeup_data_read = false; while(true) { ssize_t ret = read(fd, drain_buf, sizeof(drain_buf)); if (ret < 0) { if (errno == EINTR) continue; break; } - if (ret > 0) continue; + if (ret > 0) { eld->wakeup_data_read = true; continue; } break; } } +static void +mark_wakep_fd_ready(int fd UNUSED, int events UNUSED, void *data) { + ((EventLoopData*)(data))->wakeup_fd_ready = true; +} + bool initPollData(EventLoopData *eld, int display_fd) { if (!addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL)) return false; @@ -246,10 +252,20 @@ initPollData(EventLoopData *eld, int display_fd) { if (pipe2(eld->wakeupFds, O_CLOEXEC | O_NONBLOCK) != 0) return false; const int wakeup_fd = eld->wakeupFds[0]; #endif - if (!addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, drain_wakeup_fd, NULL)) return false; + if (!addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, mark_wakep_fd_ready, eld)) return false; return true; } +void +check_for_wakeup_events(EventLoopData *eld) { +#ifdef HAS_EVENT_FD + int fd = eld->wakeupFd; +#else + int fd = eld->wakeupFds[0]; +#endif + drain_wakeup_fd(fd, eld); +} + void wakeupEventLoop(EventLoopData *eld) { #ifdef HAS_EVENT_FD @@ -289,6 +305,7 @@ pollForEvents(EventLoopData *eld, double timeout) { EVDBG("pollForEvents final timeout: %.3f", timeout); int result; double end_time = monotonic() + timeout; + eld->wakeup_fd_ready = false; while(1) { if (timeout >= 0) { diff --git a/glfw/backend_utils.h b/glfw/backend_utils.h index 414be39fe..f69fe722d 100644 --- a/glfw/backend_utils.h +++ b/glfw/backend_utils.h @@ -66,12 +66,14 @@ typedef struct { #else int wakeupFds[2]; #endif + bool wakeup_data_read, wakeup_fd_ready; nfds_t watches_count, timers_count; Watch watches[32]; Timer timers[128]; } EventLoopData; +void check_for_wakeup_events(EventLoopData *eld); id_type addWatch(EventLoopData *eld, const char *name, 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); diff --git a/glfw/cocoa_init.m b/glfw/cocoa_init.m index d82e3f7e0..80bcbba32 100644 --- a/glfw/cocoa_init.m +++ b/glfw/cocoa_init.m @@ -26,6 +26,7 @@ #include "internal.h" #include // For MAXPATHLEN +#include // Change to our application bundle's resources directory, if present // @@ -471,32 +472,56 @@ const char* _glfwPlatformGetVersionString(void) static GLFWtickcallback tick_callback = NULL; static void* tick_callback_data = NULL; static bool tick_callback_requested = false; +static pthread_t main_thread; +static NSLock *tick_lock = NULL; void _glfwDispatchTickCallback() { - if (tick_callback) { - tick_callback_requested = false; - tick_callback(tick_callback_data); + if (tick_lock && tick_callback) { + [tick_lock lock]; + while(tick_callback_requested) { + tick_callback_requested = false; + tick_callback(tick_callback_data); + } + [tick_lock unlock]; } } -void _glfwPlatformRequestTickCallback() { +static void +request_tick_callback() { if (!tick_callback_requested) { tick_callback_requested = true; [NSApp performSelectorOnMainThread:@selector(tick_callback) withObject:nil waitUntilDone:NO]; } } +void _glfwPlatformPostEmptyEvent(void) +{ + if (pthread_equal(pthread_self(), main_thread)) { + request_tick_callback(); + } else if (tick_lock) { + [tick_lock lock]; + request_tick_callback(); + [tick_lock unlock]; + } +} + + void _glfwPlatformStopMainLoop(void) { - tick_callback = NULL; [NSApp stop:nil]; - _glfwPlatformPostEmptyEvent(); + _glfwCocoaPostEmptyEvent(); } void _glfwPlatformRunMainLoop(GLFWtickcallback callback, void* data) { + main_thread = pthread_self(); tick_callback = callback; tick_callback_data = data; + tick_lock = [NSLock new]; [NSApp run]; + [tick_lock release]; + tick_lock = NULL; + tick_callback = NULL; + tick_callback_data = NULL; } diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index 62960d746..63994e29d 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -225,7 +225,7 @@ float _glfwTransformYNS(float y); void _glfwClearDisplayLinks(void); void _glfwRestartDisplayLinks(void); -void _glfwCocoaPostEmptyEvent(short subtype, long data1, bool at_start); void _glfwDispatchTickCallback(void); void _glfwDispatchRenderFrame(CGDirectDisplayID); void _glfwShutdownCVDisplayLink(unsigned long long, void*); +void _glfwCocoaPostEmptyEvent(void); diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index e2e6c8ab7..a946f648d 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -579,7 +579,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL; [NSApp stop:nil]; CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL); - _glfwPlatformPostEmptyEvent(); + _glfwCocoaPostEmptyEvent(); } - (void)applicationWillTerminate:(NSNotification *)aNotification @@ -1916,29 +1916,6 @@ _glfwDispatchRenderFrame(CGDirectDisplayID displayID) { } } -void _glfwCocoaPostEmptyEvent(short subtype, long data1, bool at_start) -{ - @autoreleasepool { - - NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined - location:NSMakePoint(0, 0) - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil - subtype:subtype - data1:data1 - data2:0]; - [NSApp postEvent:event atStart:at_start ? YES : NO]; - - } // autoreleasepool -} - -void _glfwPlatformPostEmptyEvent(void) -{ - _glfwCocoaPostEmptyEvent(0, 0, true); -} - void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { const NSRect contentRect = [window->ns.view frame]; @@ -2381,3 +2358,16 @@ float _glfwTransformYNS(float y) { return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; } + +void _glfwCocoaPostEmptyEvent(void) { + NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined + location:NSMakePoint(0, 0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:event atStart:YES]; +} diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 1b0011d60..69105268f 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1628,7 +1628,6 @@ typedef struct GLFWgamepadstate GLFWAPI int glfwInit(void); GLFWAPI void glfwRunMainLoop(GLFWtickcallback callback, void *callback_data); GLFWAPI void glfwStopMainLoop(void); -GLFWAPI void glfwRequestTickCallback(void); GLFWAPI unsigned long long glfwAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void * callback_data, GLFWuserdatafun free_callback); GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, double interval, bool enabled); GLFWAPI void glfwRemoveTimer(unsigned long long); diff --git a/glfw/init.c b/glfw/init.c index 583b85e9e..e5458434b 100644 --- a/glfw/init.c +++ b/glfw/init.c @@ -342,10 +342,6 @@ GLFWAPI void glfwRunMainLoop(GLFWtickcallback callback, void *data) _glfwPlatformRunMainLoop(callback, data); } -GLFWAPI void glfwRequestTickCallback(void) { - _glfwPlatformRequestTickCallback(); -} - GLFWAPI void glfwStopMainLoop(void) { _GLFW_REQUIRE_INIT(); _glfwPlatformStopMainLoop(); diff --git a/glfw/internal.h b/glfw/internal.h index 1000b4da8..1197f8cbe 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -822,7 +822,6 @@ const char* _glfwGetVulkanResultString(VkResult result); _GLFWwindow* _glfwFocusedWindow(void); _GLFWwindow* _glfwWindowForId(GLFWid id); void _glfwPlatformRunMainLoop(GLFWtickcallback, void*); -void _glfwPlatformRequestTickCallback(void); void _glfwPlatformStopMainLoop(void); unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback); void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, bool enabled); diff --git a/glfw/main_loop.h b/glfw/main_loop.h index 993dc1b7f..83f1f7d37 100644 --- a/glfw/main_loop.h +++ b/glfw/main_loop.h @@ -13,36 +13,26 @@ #define GLFW_LOOP_BACKEND x11 #endif -static volatile atomic_int keep_going = 0, tick_callback_requested = 0; +static bool keep_going = false; -void _glfwPlatformRequestTickCallback() { - EVDBG("tick_callback requested"); - tick_callback_requested = 1; -} void _glfwPlatformStopMainLoop(void) { if (keep_going) { - keep_going = 0; + keep_going = false; _glfwPlatformPostEmptyEvent(); } } -static inline void -dispatch_tick_callbacks(GLFWtickcallback tick_callback, void *data) { - while (tick_callback_requested) { - EVDBG("Calling tick callback"); - tick_callback_requested = 0; - tick_callback(data); - } -} - void _glfwPlatformRunMainLoop(GLFWtickcallback tick_callback, void* data) { keep_going = 1; - tick_callback_requested = 0; + EventLoopData *eld = &_glfw.GLFW_LOOP_BACKEND.eventLoopData; while(keep_going) { - EVDBG("loop tick, tick_callback_requested: %d", tick_callback_requested); - dispatch_tick_callbacks(tick_callback, data); _glfwPlatformWaitEvents(); + EVDBG("--------- loop tick, wakeups_happened: %d ----------", eld->wakeup_data_read); + if (eld->wakeup_data_read) { + eld->wakeup_data_read = false; + tick_callback(data); + } } EVDBG("main loop exiting"); } diff --git a/glfw/wl_window.c b/glfw/wl_window.c index ad268ad1e..b7861cccf 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -783,6 +783,7 @@ handleEvents(double timeout) } glfw_ibus_dispatch(&_glfw.wl.xkb.ibus); glfw_dbus_session_bus_dispatch(); + if (_glfw.wl.eventLoopData.wakeup_fd_ready) check_for_wakeup_events(&_glfw.wl.eventLoopData); } static struct wl_cursor* diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 90fc83e55..5646bc494 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -71,6 +71,7 @@ handleEvents(double timeout) { glfw_ibus_dispatch(&_glfw.x11.xkb.ibus); glfw_dbus_session_bus_dispatch(); EVDBG("other dispatch done"); + if (_glfw.x11.eventLoopData.wakeup_fd_ready) check_for_wakeup_events(&_glfw.x11.eventLoopData); } static bool diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 0e0c0fe02..1edab9445 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -295,13 +295,15 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) { Py_RETURN_NONE; } -static inline void +static inline bool do_parse(ChildMonitor *self, Screen *screen, double now) { + bool input_read = false; screen_mutex(lock, read); if (screen->read_buf_sz || screen->pending_mode.used) { double time_since_new_input = now - screen->new_input_at; if (time_since_new_input >= OPT(input_delay)) { bool read_buf_full = screen->read_buf_sz >= READ_BUF_SZ; + input_read = true; parse_func(screen, self->dump_callback, now); if (read_buf_full) wakeup_io_loop(self, false); // Ensure the read fd has POLLIN set screen->new_input_at = 0; @@ -312,12 +314,14 @@ do_parse(ChildMonitor *self, Screen *screen, double now) { } else set_maximum_wait(OPT(input_delay) - time_since_new_input); } screen_mutex(unlock, read); + return input_read; } -static void +static bool parse_input(ChildMonitor *self) { // Parse all available input that was read in the I/O thread. size_t count = 0, remove_count = 0; + bool input_read = false; double now = monotonic(); PyObject *msg = NULL; children_mutex(lock); @@ -372,10 +376,11 @@ parse_input(ChildMonitor *self) { for (size_t i = 0; i < count; i++) { if (!scratch[i].needs_removal) { - do_parse(self, scratch[i].screen, now); + if (do_parse(self, scratch[i].screen, now)) input_read = true; } DECREF_CHILD(scratch[i]); } + return input_read; } static inline void @@ -634,10 +639,11 @@ no_render_frame_received_recently(OSWindow *w, double now, double max_wait) { } static inline void -render(double now) { +render(double now, bool input_read) { + EVDBG("input_read: %d", input_read); static double last_render_at = -DBL_MAX; double time_since_last_render = now - last_render_at; - if (time_since_last_render < OPT(repaint_delay)) { + if (!input_read && time_since_last_render < OPT(repaint_delay)) { set_maximum_wait(OPT(repaint_delay) - time_since_last_render); return; } @@ -905,7 +911,8 @@ process_global_state(void *data) { double now = monotonic(); if (global_state.has_pending_resizes) process_pending_resizes(now); - render(now); + bool input_read = parse_input(self); + render(now, input_read); #ifdef __APPLE__ if (cocoa_pending_actions) { if (cocoa_pending_actions & PREFERENCES_WINDOW) { call_boss(edit_config_file, NULL); } @@ -919,7 +926,6 @@ process_global_state(void *data) { cocoa_pending_actions = 0; } #endif - parse_input(self); if (global_state.terminate) { global_state.terminate = false; close_all_windows(); diff --git a/kitty/config_data.py b/kitty/config_data.py index 18c692b56..76ed0ca3c 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -481,7 +481,8 @@ Delay (in milliseconds) between screen updates. Decreasing it, increases frames-per-second (FPS) at the cost of more CPU usage. The default value yields ~100 FPS which is more than sufficient for most uses. Note that to actually achieve 100 FPS you have to either set :opt:`sync_to_monitor` to no -or use a monitor with a high refresh rate.''')) +or use a monitor with a high refresh rate. Also, to minimize latency +when there is pending input to be processed, repaint_delay is ignored.''')) o('input_delay', 3, option_type=positive_int, long_text=_(''' Delay (in milliseconds) before input from the program running in the terminal diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index b20dcb5c5..98cfba9e8 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -23,9 +23,6 @@ load_glfw(const char* path) { *(void **) (&glfwStopMainLoop_impl) = dlsym(handle, "glfwStopMainLoop"); if (glfwStopMainLoop_impl == NULL) fail("Failed to load glfw function glfwStopMainLoop with error: %s", dlerror()); - *(void **) (&glfwRequestTickCallback_impl) = dlsym(handle, "glfwRequestTickCallback"); - if (glfwRequestTickCallback_impl == NULL) fail("Failed to load glfw function glfwRequestTickCallback with error: %s", dlerror()); - *(void **) (&glfwAddTimer_impl) = dlsym(handle, "glfwAddTimer"); if (glfwAddTimer_impl == NULL) fail("Failed to load glfw function glfwAddTimer with error: %s", dlerror()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 579c39ad5..fdb24c29f 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1403,10 +1403,6 @@ typedef void (*glfwStopMainLoop_func)(void); glfwStopMainLoop_func glfwStopMainLoop_impl; #define glfwStopMainLoop glfwStopMainLoop_impl -typedef void (*glfwRequestTickCallback_func)(void); -glfwRequestTickCallback_func glfwRequestTickCallback_impl; -#define glfwRequestTickCallback glfwRequestTickCallback_impl - typedef unsigned long long (*glfwAddTimer_func)(double, bool, GLFWuserdatafun, void *, GLFWuserdatafun); glfwAddTimer_func glfwAddTimer_impl; #define glfwAddTimer glfwAddTimer_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 5f8e850f9..1944f1765 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -30,7 +30,7 @@ static void set_os_window_dpi(OSWindow *w); void request_tick_callback(void) { - glfwRequestTickCallback(); + glfwPostEmptyEvent(); } static int min_width = 100, min_height = 100; @@ -934,12 +934,7 @@ swap_window_buffers(OSWindow *os_window) { void wakeup_main_loop() { - request_tick_callback(); -#ifndef __APPLE__ - // On Cocoa request_tick_callback() uses performSelectorOnMainLoop which - // wakes up the main loop anyway glfwPostEmptyEvent(); -#endif } void diff --git a/kitty/glfw_tests.c b/kitty/glfw_tests.c index c0f3eed95..399f594dd 100644 --- a/kitty/glfw_tests.c +++ b/kitty/glfw_tests.c @@ -25,8 +25,7 @@ static void* empty_thread_main(void* data UNUSED) while (running) { nanosleep(&time, NULL); - glfwRequestTickCallback(); - glfwPostEmptyEvent(); + wakeup_main_loop(); } return 0; @@ -43,7 +42,6 @@ static void key_callback(GLFWwindow *w UNUSED, int key, int scancode UNUSED, int static void window_close_callback(GLFWwindow* window) { glfwSetWindowShouldClose(window, true); - glfwRequestTickCallback(); wakeup_main_loop(); }