Implement timers for the linux event loops

Needed for dbus integration. Also cleanup the event handling code.
X11 and Wayland now share most of their event polling and dispatch logic.
This commit is contained in:
Kovid Goyal
2018-07-09 20:13:05 +05:30
parent b0bfa2b2fc
commit 99ea6c08a7
6 changed files with 212 additions and 110 deletions

150
glfw/backend_utils.c vendored
View File

@@ -8,8 +8,10 @@
#define _GNU_SOURCE
#include "backend_utils.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <float.h>
#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--) {