2018-07-09 10:58:51 +03:00
|
|
|
/*
|
|
|
|
* backend_utils.c
|
|
|
|
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
*
|
|
|
|
* Distributed under terms of the GPL3 license.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include "backend_utils.h"
|
|
|
|
|
2018-07-09 17:43:05 +03:00
|
|
|
#include <stdlib.h>
|
2018-07-09 10:58:51 +03:00
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
2018-07-09 17:43:05 +03:00
|
|
|
#include <float.h>
|
2018-07-09 10:58:51 +03:00
|
|
|
|
|
|
|
#ifdef __NetBSD__
|
|
|
|
#define ppoll pollts
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void
|
|
|
|
update_fds(EventLoopData *eld) {
|
|
|
|
eld->fds_count = 0;
|
|
|
|
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
|
|
|
Watch *w = eld->watches + i;
|
|
|
|
if (w->enabled) {
|
|
|
|
eld->fds[eld->fds_count].fd = w->fd;
|
|
|
|
eld->fds[eld->fds_count].events = w->events;
|
|
|
|
eld->fds_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 12:55:19 +03:00
|
|
|
static id_type watch_counter = 0;
|
|
|
|
|
|
|
|
id_type
|
2018-07-09 10:58:51 +03:00
|
|
|
addWatch(EventLoopData *eld, int fd, int events, int enabled, watch_callback_func cb, void *cb_data) {
|
2018-07-09 11:59:34 +03:00
|
|
|
if (eld->watches_count >= sizeof(eld->watches)/sizeof(eld->watches[0])) return 0;
|
2018-07-09 10:58:51 +03:00
|
|
|
Watch *w = eld->watches + eld->watches_count++;
|
|
|
|
w->fd = fd; w->events = events; w->enabled = enabled;
|
|
|
|
w->callback = cb;
|
|
|
|
w->callback_data = cb_data;
|
2018-07-09 12:55:19 +03:00
|
|
|
w->id = ++watch_counter;
|
2018-07-09 10:58:51 +03:00
|
|
|
update_fds(eld);
|
2018-07-09 12:55:19 +03:00
|
|
|
return w->id;
|
2018-07-09 10:58:51 +03:00
|
|
|
}
|
|
|
|
|
2018-07-09 17:43:05 +03:00
|
|
|
#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; \
|
|
|
|
}}}
|
|
|
|
|
2018-07-09 10:58:51 +03:00
|
|
|
void
|
2018-07-09 12:55:19 +03:00
|
|
|
removeWatch(EventLoopData *eld, id_type watch_id) {
|
2018-07-09 17:43:05 +03:00
|
|
|
removeX(watches, watch_id, update_fds);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) {
|
2018-07-09 10:58:51 +03:00
|
|
|
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
2018-07-09 12:55:19 +03:00
|
|
|
if (eld->watches[i].id == watch_id) {
|
2018-07-09 17:43:05 +03:00
|
|
|
if (eld->watches[i].enabled != enabled) {
|
|
|
|
eld->watches[i].enabled = enabled;
|
|
|
|
update_fds(eld);
|
2018-07-09 10:58:51 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 17:43:05 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-09 10:58:51 +03:00
|
|
|
void
|
2018-07-09 17:43:05 +03:00
|
|
|
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);
|
2018-07-09 10:58:51 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2018-07-09 17:43:05 +03:00
|
|
|
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) {
|
2018-07-09 10:58:51 +03:00
|
|
|
for (nfds_t i = 0; i < eld->fds_count; i++) eld->fds[i].revents = 0;
|
2018-07-09 17:43:05 +03:00
|
|
|
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;
|
2018-07-09 10:58:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
|
|
|
|
const long seconds = (long) timeout;
|
|
|
|
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
|
|
|
|
struct timespec tv = { seconds, nanoseconds };
|
|
|
|
return ppoll(fds, nfds, &tv, NULL);
|
|
|
|
}
|
|
|
|
|
2018-07-09 17:43:05 +03:00
|
|
|
static void
|
2018-07-09 10:58:51 +03:00
|
|
|
dispatchEvents(EventLoopData *eld) {
|
|
|
|
for (unsigned w = 0, f = 0; f < eld->fds_count; f++) {
|
|
|
|
while(eld->watches[w].fd != eld->fds[f].fd) w++;
|
|
|
|
Watch *ww = eld->watches + w;
|
|
|
|
if (eld->fds[f].revents & ww->events) {
|
|
|
|
ww->ready = 1;
|
|
|
|
if (ww->callback) ww->callback(ww->fd, eld->fds[f].revents, ww->callback_data);
|
|
|
|
} else ww->ready = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 17:43:05 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-09 10:58:51 +03:00
|
|
|
static void
|
|
|
|
drain_wakeup_fd(int fd, int events, void* data) {
|
|
|
|
static char drain_buf[64];
|
|
|
|
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
initPollData(EventLoopData *eld, int wakeup_fd, int display_fd) {
|
|
|
|
addWatch(eld, display_fd, POLLIN, 1, NULL, NULL);
|
|
|
|
addWatch(eld, wakeup_fd, POLLIN, 1, drain_wakeup_fd, NULL);
|
|
|
|
}
|
|
|
|
|
2018-07-09 17:43:05 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-09 10:58:51 +03:00
|
|
|
void
|
|
|
|
closeFds(int *fds, size_t count) {
|
|
|
|
while(count--) {
|
|
|
|
if (*fds > 0) {
|
|
|
|
close(*fds);
|
|
|
|
*fds = -1;
|
|
|
|
}
|
|
|
|
fds++;
|
|
|
|
}
|
|
|
|
}
|