diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/common/loop.h | 5 | ||||
| -rw-r--r-- | src/platform/cocoa/window.m | 102 | ||||
| -rw-r--r-- | src/platform/posix/loop.c | 219 |
3 files changed, 313 insertions, 13 deletions
diff --git a/src/common/loop.h b/src/common/loop.h index 663417a..60c5b64 100644 --- a/src/common/loop.h +++ b/src/common/loop.h @@ -3,7 +3,12 @@ #include "yasl/loop.h" +struct pollfd; + int yasl_loop_next_timeout_ms(const struct yasl_loop *l); +/* The loop's current poll set, valid until the next watch change. Backends + * that wait on native events use it to wake on loop readiness. */ +int yasl_loop_poll_set(const struct yasl_loop *l, const struct pollfd **fds); /* Whether id is armed: added, not deleted, and not retired by its callback. */ bool yasl_loop_timer_armed(const struct yasl_loop *l, yasl_timer_id id); diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index f1f0f03..811a7c6 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -13,11 +13,18 @@ #include <assert.h> #include <math.h> +#include <poll.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/event.h> +#include <unistd.h> #define LINK_IDLE_FRAMES 8 +/* Application-defined event subtype that only wakes a waiting dispatch. */ +#define LOOP_WAKE_SUBTYPE 0x5953 +/* Wait bound while some loop descriptor cannot be watched with kqueue. */ +#define LOOP_FALLBACK_MS 10 #define SCROLL_POINTS_PER_LINE 10.0 enum { @@ -1119,6 +1126,54 @@ static void pump_window(struct cocoa_surface *w) @autoreleasepool { render_and_commit(w); } } +static void loop_ready(CFFileDescriptorRef fd, CFOptionFlags flags, void *info) +{ + (void)fd; + (void)flags; + (void)info; + NSEvent *wake = [NSEvent otherEventWithType:NSEventTypeApplicationDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:LOOP_WAKE_SUBTYPE + data1:0 + data2:0]; + [NSApp postEvent:wake atStart:YES]; +} + +/* A kqueue mirroring the loop's poll set, or -1. complete is false when some + * descriptor could not be registered. */ +static int loop_kqueue(struct yasl_loop *loop, bool *complete) +{ + *complete = true; + int kq = kqueue(); + if (kq < 0) { + *complete = false; + return -1; + } + const struct pollfd *fds; + int n = yasl_loop_poll_set(loop, &fds); + for (int i = 0; i < n; i++) { + struct kevent change; + if (fds[i].events & POLLIN) { + EV_SET(&change, fds[i].fd, EVFILT_READ, EV_ADD, 0, 0, NULL); + if (kevent(kq, &change, 1, NULL, 0, NULL) < 0) *complete = false; + } + if (fds[i].events & POLLOUT) { + EV_SET(&change, fds[i].fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL); + if (kevent(kq, &change, 1, NULL, 0, NULL) < 0) *complete = false; + } + } + return kq; +} + +static bool is_loop_wake(NSEvent *ev) +{ + return ev.type == NSEventTypeApplicationDefined && ev.subtype == LOOP_WAKE_SUBTYPE; +} + int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) { assert([NSThread isMainThread]); @@ -1143,6 +1198,30 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) } } + /* AppKit waits on Mach ports, not on loop descriptors. While waiting, + * a run loop source on a kqueue of the loop's descriptors posts a wake + * event, so kicks, posts, signals, and watches end the wait. */ + CFFileDescriptorRef wake_fd = NULL; + CFRunLoopSourceRef wake_source = NULL; + if (timeout_ms != 0 && d->loop) { + bool complete; + int kq = loop_kqueue(d->loop, &complete); + if (kq >= 0) { + wake_fd = CFFileDescriptorCreate(kCFAllocatorDefault, kq, true, loop_ready, NULL); + if (!wake_fd) close(kq); + } + if (wake_fd) { + CFFileDescriptorEnableCallBacks(wake_fd, kCFFileDescriptorReadCallBack); + wake_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, wake_fd, 0); + } + if (wake_source) + CFRunLoopAddSource(CFRunLoopGetCurrent(), wake_source, kCFRunLoopDefaultMode); + else + complete = false; + if (!complete && (timeout_ms < 0 || timeout_ms > LOOP_FALLBACK_MS)) + timeout_ms = LOOP_FALLBACK_MS; + } + NSDate *limitDate; if (timeout_ms == 0) limitDate = [NSDate distantPast]; @@ -1156,19 +1235,24 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) untilDate:limitDate inMode:NSDefaultRunLoopMode dequeue:YES]; - if (ev) { - count++; - [NSApp sendEvent:ev]; - [NSApp updateWindows]; - - while ((ev = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES])) { + if (wake_source) { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), wake_source, kCFRunLoopDefaultMode); + CFRelease(wake_source); + } + if (wake_fd) { + CFFileDescriptorInvalidate(wake_fd); + CFRelease(wake_fd); + } + while (ev) { + if (!is_loop_wake(ev)) { count++; [NSApp sendEvent:ev]; [NSApp updateWindows]; } + ev = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; } } diff --git a/src/platform/posix/loop.c b/src/platform/posix/loop.c index 88454d8..3c3d730 100644 --- a/src/platform/posix/loop.c +++ b/src/platform/posix/loop.c @@ -1,10 +1,14 @@ #define _GNU_SOURCE +#define _DARWIN_C_SOURCE #define _POSIX_C_SOURCE 200809L #include "common/loop.h" +#include "platform/sync.h" #include <errno.h> #include <fcntl.h> #include <poll.h> +#include <signal.h> +#include <stdatomic.h> #include <stdlib.h> #include <string.h> #include <time.h> @@ -31,6 +35,21 @@ struct yasl_timer_source { bool deleted; }; +struct yasl_post { + void (*fn)(void *ud); + void *ud; + struct yasl_post *next; +}; + +/* Signals this loop watches; the previous actions are restored on unwatch. */ +struct yasl_signal_watch { + void (*cb)(void *ud, int signo); + void *ud; + int count; + int signals[NSIG]; + struct sigaction old[NSIG]; +}; + struct yasl_loop { struct yasl_fd_source *fds; struct pollfd *pollfds; @@ -47,8 +66,21 @@ struct yasl_loop { int timer_capacity; uint32_t next_timer_id; int timer_firing_depth; + + yasl_mutex post_mutex; + struct yasl_post *post_head; + struct yasl_post *post_tail; + + struct yasl_signal_watch *signals; }; +/* Handlers run on any thread, so they only touch lock-free atomics: they mark + * the signal pending and write to the owning loop's wake pipe. The pipe is + * stored as fd + 1 so that the zero initializer means no pipe. */ +static atomic_int signal_wake_fd[NSIG]; +static atomic_bool signal_pending[NSIG]; +static struct yasl_loop *signal_owner[NSIG]; + uint64_t yasl_loop_now_ns(void) { struct timespec ts; @@ -56,13 +88,41 @@ uint64_t yasl_loop_now_ns(void) return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; } +static void run_posts(struct yasl_loop *l) +{ + yasl_mutex_lock(&l->post_mutex); + struct yasl_post *p = l->post_head; + l->post_head = l->post_tail = NULL; + yasl_mutex_unlock(&l->post_mutex); + while (p) { + struct yasl_post *next = p->next; + p->fn(p->ud); + free(p); + p = next; + } +} + +static void run_signals(struct yasl_loop *l) +{ + /* A callback may unwatch, so re-check the watch before each delivery. */ + for (int signo = 1; signo < NSIG; signo++) { + struct yasl_signal_watch *w = l->signals; + if (!w) return; + if (signal_owner[signo] != l || !atomic_exchange(&signal_pending[signo], false)) + continue; + w->cb(w->ud, signo); + } +} + static int loop_wake_cb(int fd, short revents, void *ud) { (void)revents; - (void)ud; char buf[128]; ssize_t n; while ((n = read(fd, buf, sizeof(buf))) > 0 || (n < 0 && errno == EINTR)) {} + struct yasl_loop *l = ud; + run_posts(l); + run_signals(l); return 0; } @@ -81,6 +141,13 @@ struct yasl_loop *yasl_loop_create(void) return NULL; } + if (yasl_mutex_init(&l->post_mutex) != 0) { + free(l->fds); + free(l->pollfds); + free(l); + return NULL; + } + l->next_id = 1; l->next_timer_id = 1; l->wake_rfd = -1; @@ -114,6 +181,13 @@ struct yasl_loop *yasl_loop_create(void) void yasl_loop_destroy(struct yasl_loop *l) { if (!l) return; + yasl_loop_unwatch_signals(l); + for (struct yasl_post *p = l->post_head; p;) { + struct yasl_post *next = p->next; + free(p); + p = next; + } + yasl_mutex_destroy(&l->post_mutex); if (l->wake_watch > 0) yasl_loop_del_fd(l, l->wake_watch); if (l->wake_rfd >= 0) @@ -184,14 +258,141 @@ int yasl_loop_set_fd_events(struct yasl_loop *l, int id, short events) return -1; } -void yasl_loop_kick(struct yasl_loop *l) +static void wake_write(int fd) { - if (!l || l->wake_wfd < 0) return; + /* A full pipe already wakes the loop, so EAGAIN needs no retry. */ + int saved = errno; char b = 1; ssize_t n; do { - n = write(l->wake_wfd, &b, 1); + n = write(fd, &b, 1); } while (n < 0 && errno == EINTR); + errno = saved; +} + +void yasl_loop_kick(struct yasl_loop *l) +{ + if (!l || l->wake_wfd < 0) return; + wake_write(l->wake_wfd); +} + +bool yasl_loop_post(struct yasl_loop *l, void (*fn)(void *ud), void *ud) +{ + if (!l || !fn || l->wake_wfd < 0) return false; + struct yasl_post *p = malloc(sizeof(*p)); + if (!p) return false; + *p = (struct yasl_post){.fn = fn, .ud = ud}; + yasl_mutex_lock(&l->post_mutex); + if (l->post_tail) + l->post_tail->next = p; + else + l->post_head = p; + l->post_tail = p; + yasl_mutex_unlock(&l->post_mutex); + wake_write(l->wake_wfd); + return true; +} + +static void signal_handler(int signo) +{ + atomic_store(&signal_pending[signo], true); + int fd = atomic_load(&signal_wake_fd[signo]) - 1; + if (fd >= 0) wake_write(fd); +} + +static bool signal_valid(int signo) +{ + return signo > 0 && signo < NSIG && signo != SIGKILL && signo != SIGSTOP; +} + +bool yasl_loop_watch_signals(struct yasl_loop *l, const int *signals, int n, + void (*cb)(void *ud, int signo), void *ud) +{ + if (!l || !signals || n <= 0 || !cb || l->wake_wfd < 0) { + errno = EINVAL; + return false; + } + bool wanted[NSIG] = {0}; + for (int i = 0; i < n; i++) { + if (!signal_valid(signals[i])) { + errno = EINVAL; + return false; + } + if (signal_owner[signals[i]] && signal_owner[signals[i]] != l) { + errno = EBUSY; + return false; + } + wanted[signals[i]] = true; + } + struct yasl_signal_watch *w = calloc(1, sizeof(*w)); + if (!w) return false; + w->cb = cb; + w->ud = ud; + + /* Keep the old watch's saved actions for signals that stay watched, so + * that unwatch restores what the application had before any watch. */ + struct yasl_signal_watch *old = l->signals; + struct sigaction sa = {.sa_handler = signal_handler, .sa_flags = SA_RESTART}; + sigemptyset(&sa.sa_mask); + for (int signo = 1; signo < NSIG; signo++) { + if (!wanted[signo]) continue; + int prev = -1; + for (int i = 0; old && i < old->count; i++) + if (old->signals[i] == signo) prev = i; + if (prev >= 0) { + w->old[w->count] = old->old[prev]; + } else { + atomic_store(&signal_pending[signo], false); + atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); + if (sigaction(signo, &sa, &w->old[w->count]) != 0) { + int err = errno; + atomic_store(&signal_wake_fd[signo], 0); + for (int i = 0; i < w->count; i++) { + int done = w->signals[i]; + bool kept = false; + for (int j = 0; old && j < old->count; j++) + if (old->signals[j] == done) kept = true; + if (kept) continue; + sigaction(done, &w->old[i], NULL); + atomic_store(&signal_wake_fd[done], 0); + } + free(w); + errno = err; + return false; + } + } + w->signals[w->count++] = signo; + } + + /* Restore signals the old watch had that the new one drops. */ + for (int i = 0; old && i < old->count; i++) { + int signo = old->signals[i]; + if (wanted[signo]) continue; + sigaction(signo, &old->old[i], NULL); + atomic_store(&signal_wake_fd[signo], 0); + atomic_store(&signal_pending[signo], false); + signal_owner[signo] = NULL; + } + for (int i = 0; i < w->count; i++) + signal_owner[w->signals[i]] = l; + free(old); + l->signals = w; + return true; +} + +void yasl_loop_unwatch_signals(struct yasl_loop *l) +{ + if (!l || !l->signals) return; + struct yasl_signal_watch *w = l->signals; + l->signals = NULL; + for (int i = 0; i < w->count; i++) { + int signo = w->signals[i]; + sigaction(signo, &w->old[i], NULL); + atomic_store(&signal_wake_fd[signo], 0); + atomic_store(&signal_pending[signo], false); + signal_owner[signo] = NULL; + } + free(w); } static void timer_cleanup_deleted(struct yasl_loop *l) @@ -215,6 +416,16 @@ bool yasl_loop_timer_armed(const struct yasl_loop *l, yasl_timer_id id) return false; } +int yasl_loop_poll_set(const struct yasl_loop *l, const struct pollfd **fds) +{ + if (!l) { + *fds = NULL; + return 0; + } + *fds = l->pollfds; + return l->count; +} + int yasl_loop_next_timeout_ms(const struct yasl_loop *l) { if (!l || l->timer_count <= 0) return -1; |
