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.
This commit is contained in:
Kovid Goyal 2019-07-18 14:25:11 +05:30
parent 5521d6b623
commit 1cb15dedac
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
17 changed files with 95 additions and 82 deletions

23
glfw/backend_utils.c vendored
View File

@ -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) {

View File

@ -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);

View File

@ -26,6 +26,7 @@
#include "internal.h"
#include <sys/param.h> // For MAXPATHLEN
#include <pthread.h>
// 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) {
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;
}

View File

@ -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);

View File

@ -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];
}

1
glfw/glfw3.h vendored
View File

@ -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);

4
glfw/init.c vendored
View File

@ -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();

1
glfw/internal.h vendored
View File

@ -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);

26
glfw/main_loop.h vendored
View File

@ -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");
}

1
glfw/wl_window.c vendored
View File

@ -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*

1
glfw/x11_window.c vendored
View File

@ -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

View File

@ -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();

View File

@ -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

3
kitty/glfw-wrapper.c generated
View File

@ -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());

4
kitty/glfw-wrapper.h generated
View File

@ -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

View File

@ -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

View File

@ -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();
}