From 8244f7cd58be2a0b7f7fe078eed8e0effd4678c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Jul 2019 21:29:51 +0530 Subject: [PATCH] Linux: Only process global state when something interesting happens This matches behavior on macOS. Had initially set the code to process on every loop tick in an attmept to workaround the issue of the event loop freezing on X11 until an X event is delivered. However, in light of #1782 that workaround was incorrect anyway. Better to have similar behavior across platforms. This also has the advantage of reducing CPU consumption. Also add a simple program to test event loop wakeups. --- glfw/main_loop.h | 25 +++++++--- kitty/child-monitor.c | 10 ++-- kitty/glfw.c | 18 +++++-- kitty/glfw_tests.c | 112 ++++++++++++++++++++++++++++++++++++++++++ kitty/glfw_tests.h | 11 +++++ kitty/state.h | 1 + 6 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 kitty/glfw_tests.c create mode 100644 kitty/glfw_tests.h diff --git a/glfw/main_loop.h b/glfw/main_loop.h index ee98c39b5..d4d850ef1 100644 --- a/glfw/main_loop.h +++ b/glfw/main_loop.h @@ -12,9 +12,11 @@ #define GLFW_LOOP_BACKEND x11 #endif -static bool keep_going = false; +static bool keep_going = false, tick_callback_requested = false; void _glfwPlatformRequestTickCallback() { + EVDBG("tick_callback requested"); + tick_callback_requested = true; } void _glfwPlatformStopMainLoop(void) { @@ -24,15 +26,26 @@ void _glfwPlatformStopMainLoop(void) { } } -void _glfwPlatformRunMainLoop(GLFWtickcallback tick_callback, void* data) { - keep_going = true; - while(keep_going) { - _glfwPlatformWaitEvents(); - EVDBG("loop tick"); +static inline void +dispatch_tick_callbacks(GLFWtickcallback tick_callback, void *data) { + while (tick_callback_requested) { + EVDBG("Calling tick callback"); + tick_callback_requested = false; tick_callback(data); } } +void _glfwPlatformRunMainLoop(GLFWtickcallback tick_callback, void* data) { + keep_going = true; + tick_callback_requested = false; + while(keep_going) { + EVDBG("loop tick, tick_callback_requested: %d", tick_callback_requested); + dispatch_tick_callbacks(tick_callback, data); + _glfwPlatformWaitEvents(); + } + EVDBG("main loop exiting"); +} + unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafreefun callback, void *callback_data, GLFWuserdatafreefun free_callback) { return addTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, "user timer", interval, 1, repeats, callback, callback_data, free_callback); } diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index f4f7ef91b..0e0c0fe02 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -889,14 +889,9 @@ set_cocoa_pending_action(CocoaPendingAction action, const char *wd) { static void process_global_state(void *data); static void -do_state_check(id_type timer_id UNUSED, void *data UNUSED) { +do_state_check(id_type timer_id UNUSED, void *data) { EVDBG("State check timer fired"); -#ifdef __APPLE__ process_global_state(data); -#endif - // We don't actually do anything here as process_global_state - // will be called when the loop ticks on Linux - } static id_type state_check_timer = 0; @@ -937,7 +932,8 @@ process_global_state(void *data) { if (global_state.has_pending_closes) has_open_windows = process_pending_closes(self); if (has_open_windows) { if (maximum_wait >= 0) { - state_check_timer_enabled = true; + if (maximum_wait == 0) request_tick_callback(); + else state_check_timer_enabled = true; } } else { stop_main_loop(); diff --git a/kitty/glfw.c b/kitty/glfw.c index 5cac45655..805dce7ef 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -5,6 +5,7 @@ */ #include "state.h" +#include "glfw_tests.h" #include "fonts.h" #include #include "glfw-wrapper.h" @@ -27,11 +28,9 @@ static GLFWcursor *standard_cursor = NULL, *click_cursor = NULL, *arrow_cursor = static void set_os_window_dpi(OSWindow *w); -static void +void request_tick_callback(void) { -#ifdef __APPLE__ glfwRequestTickCallback(); -#endif } static int min_width = 100, min_height = 100; @@ -1150,6 +1149,18 @@ stop_main_loop(void) { glfwStopMainLoop(); } + +static PyObject* +test_empty_event(PYNOARG) { + + int ret = empty_main(); + if (ret != EXIT_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "Empty test returned failure code: %d", ret); + return NULL; + } + Py_RETURN_NONE; +} + // Boilerplate {{{ static PyMethodDef module_methods[] = { @@ -1178,6 +1189,7 @@ static PyMethodDef module_methods[] = { {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, {"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""}, {"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""}, + {"glfw_test_empty_event", (PyCFunction)test_empty_event, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/glfw_tests.c b/kitty/glfw_tests.c new file mode 100644 index 000000000..c0f3eed95 --- /dev/null +++ b/kitty/glfw_tests.c @@ -0,0 +1,112 @@ +/* + * glfw_tests.c + * Copyright (C) 2019 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "glfw_tests.h" + + +#include "glfw-wrapper.h" +#include "gl.h" + +#include +#include +#include +#include + +static volatile bool running = true; + +static void* empty_thread_main(void* data UNUSED) +{ + struct timespec time = { .tv_sec = 1 }; + + while (running) + { + nanosleep(&time, NULL); + glfwRequestTickCallback(); + glfwPostEmptyEvent(); + } + + return 0; +} + +static void key_callback(GLFWwindow *w UNUSED, int key, int scancode UNUSED, int action, int mods UNUSED, const char* text UNUSED, int state UNUSED) +{ + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { + glfwSetWindowShouldClose(w, true); + wakeup_main_loop(); + } +} + +static void +window_close_callback(GLFWwindow* window) { + glfwSetWindowShouldClose(window, true); + glfwRequestTickCallback(); + wakeup_main_loop(); +} + + +static float nrand(void) +{ + return (float) rand() / (float) RAND_MAX; +} + +static void +empty_main_tick(void *data) { + GLFWwindow *window = data; + if (glfwWindowShouldClose(window)) { + running = false; + glfwStopMainLoop(); + return; + } + int width, height; + float r = nrand(), g = nrand(), b = nrand(); + float l = (float) sqrt(r * r + g * g + b * b); + + glfwGetFramebufferSize(window, &width, &height); + + glViewport(0, 0, width, height); + glClearColor(r / l, g / l, b / l, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + glfwSwapBuffers(window); +} + +int empty_main(void) +{ + pthread_t thread; + GLFWwindow* window; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); + + + srand((unsigned int) time(NULL)); + + window = glfwCreateWindow(640, 480, "Empty Event Test", NULL, NULL); + if (!window) + { + return (EXIT_FAILURE); + } + + glfwMakeContextCurrent(window); + gl_init(); + glfwSetKeyboardCallback(window, key_callback); + glfwSetWindowCloseCallback(window, window_close_callback); + + if (pthread_create(&thread, NULL, empty_thread_main, NULL) != 0) + { + fprintf(stderr, "Failed to create secondary thread\n"); + return (EXIT_FAILURE); + } + + glfwRunMainLoop(empty_main_tick, window); + + glfwHideWindow(window); + pthread_join(thread, NULL); + glfwDestroyWindow(window); + + return (EXIT_SUCCESS); +} diff --git a/kitty/glfw_tests.h b/kitty/glfw_tests.h new file mode 100644 index 000000000..4f44e0869 --- /dev/null +++ b/kitty/glfw_tests.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2019 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +#include "state.h" + +int empty_main(void); diff --git a/kitty/state.h b/kitty/state.h index 6e992b859..f7f4ebf7a 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -234,6 +234,7 @@ bool application_quit_requested(void); void request_application_quit(void); #endif void request_frame_render(OSWindow *w); +void request_tick_callback(void); typedef void (* timer_callback_fun)(id_type, void*); typedef void (* tick_callback_fun)(void*); id_type add_main_loop_timer(double interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback);