summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/backend.c136
-rw-r--r--src/common/backend.h28
-rw-r--r--src/common/events.c2
-rw-r--r--src/common/loop.h6
-rw-r--r--src/common/private_loop.h225
-rw-r--r--src/common/surface.c122
-rw-r--r--src/common/types.h25
7 files changed, 287 insertions, 257 deletions
diff --git a/src/common/backend.c b/src/common/backend.c
index ad9e218..9910e08 100644
--- a/src/common/backend.c
+++ b/src/common/backend.c
@@ -22,8 +22,38 @@ static const struct yasl_private_backend_ops *backend_ops(enum yasl_private_back
}
}
+/* The display drives the loop it runs on; see struct yasl_private_loop_driver. */
+static bool driver_prepare(void *ud)
+{
+ struct yasl_private_display *d = ud;
+ bool ready = d->ops->display_prepare(d);
+ /* Readiness the backends cannot see: events already queued, an error to
+ * report, or an owed REFRESH. */
+ return ready || yasl_private_events_pending(d) || yasl_private_events_error(d) ||
+ (d->pending && d->pending(d));
+}
+
+static int driver_wait(void *ud, int timeout_ms)
+{
+ struct yasl_private_display *d = ud;
+ return d->ops->display_wait(d, timeout_ms);
+}
+
+static void driver_check(void *ud)
+{
+ struct yasl_private_display *d = ud;
+ d->ops->display_check(d);
+}
+
+static void driver_deliver(void *ud)
+{
+ struct yasl_private_display *d = ud;
+ if (d->deliver)
+ d->deliver(d);
+}
+
static struct yasl_private_display *open_backend(enum yasl_private_backend backend,
- struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec)
+ struct yasl_loop *loop, const struct yasl_private_display_spec *spec)
{
const struct yasl_private_backend_ops *ops = backend_ops(backend);
if (!ops) {
@@ -33,18 +63,26 @@ static struct yasl_private_display *open_backend(enum yasl_private_backend backe
errno = 0;
struct yasl_private_display *d = ops->display_open(loop, spec);
if (!d && !errno) errno = ENODEV;
- if (d) {
- if (!yasl_private_events_open(d)) {
- int error = errno;
- ops->display_close(d);
- errno = error;
- return NULL;
- }
+ if (!d)
+ return NULL;
+ struct yasl_private_loop_driver driver = {
+ .prepare = driver_prepare,
+ .wait = ops->display_wait ? driver_wait : NULL,
+ .check = driver_check,
+ .deliver = driver_deliver,
+ .ud = d,
+ };
+ if (!yasl_private_events_open(d) || !yasl_private_loop_set_driver(d->loop, &driver)) {
+ int error = errno;
+ yasl_private_events_close(d);
+ ops->display_close(d);
+ errno = error;
+ return NULL;
}
return d;
}
-struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop,
+struct yasl_private_display *yasl_private_display_open(struct yasl_loop *loop,
const struct yasl_private_display_spec *spec)
{
enum yasl_private_backend backend = spec ? spec->backend : YASL_PRIVATE_BACKEND_AUTO;
@@ -78,6 +116,7 @@ void yasl_private_display_close(struct yasl_private_display *d)
{
if (!d || !d->ops->display_close)
return;
+ yasl_private_loop_set_driver(d->loop, NULL);
yasl_private_events_close(d);
d->ops->display_close(d);
}
@@ -89,27 +128,6 @@ uint32_t yasl_private_display_caps(const struct yasl_private_display *d)
return d->ops->display_caps(d);
}
-int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms)
-{
- if (!d) {
- errno = EINVAL;
- return -1;
- }
- if (!d->ops->display_dispatch_timeout) {
- errno = ENOTSUP;
- return -1;
- }
- int queue_error = yasl_private_events_error(d);
- if (queue_error) { errno = queue_error; return -1; }
- if (yasl_private_events_pending(d)) timeout_ms = 0;
- errno = 0;
- int result = d->ops->display_dispatch_timeout(d, timeout_ms);
- queue_error = yasl_private_events_error(d);
- if (queue_error) { errno = queue_error; return -1; }
- if (result < 0 && !errno) errno = EIO;
- return result;
-}
-
void yasl_private_display_flush(struct yasl_private_display *d)
{
if (!d || !d->ops->display_flush)
@@ -317,7 +335,10 @@ bool yasl_private_surface_present(struct yasl_surface *s, const struct yasl_priv
};
}
}
- return s->display->ops->surface_present(s, fb, &clipped);
+ if (!s->display->ops->surface_present(s, fb, &clipped))
+ return false;
+ atomic_store(&s->presented_ns, yasl_private_loop_now_ns());
+ return true;
}
bool yasl_private_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap,
@@ -473,10 +494,10 @@ bool yasl_private_surface_place_under_pointer(struct yasl_surface *s, struct yas
void yasl_private_surface_clear_timers(struct yasl_surface *s)
{
if (!s || !s->timers) return;
- struct yasl_private_loop *loop = s->display ? s->display->loop : NULL;
+ struct yasl_loop *loop = s->display ? s->display->loop : NULL;
for (int i = 0; i < s->timer_count; i++) {
if (loop && s->timers[i]) {
- yasl_private_loop_del_timer(loop, s->timers[i]);
+ yasl_loop_del_timer(loop, s->timers[i]);
}
}
free(s->timers);
@@ -491,7 +512,7 @@ yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uin
if (!s || !s->display || !s->display->loop || !cb || interval_ns == 0)
return 0;
- yasl_private_timer_id id = yasl_private_loop_add_timer(s->display->loop, interval_ns, cb, ud);
+ yasl_private_timer_id id = yasl_loop_add_timer(s->display->loop, interval_ns, cb, ud);
if (!id) return 0;
/* A one-shot timer retires itself in the loop. Drop retired ids before
@@ -507,7 +528,7 @@ yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uin
int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4;
yasl_private_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt));
if (!nt) {
- yasl_private_loop_del_timer(s->display->loop, id);
+ yasl_loop_del_timer(s->display->loop, id);
return 0;
}
s->timers = nt;
@@ -523,13 +544,13 @@ bool yasl_private_surface_del_timer(struct yasl_surface *s, yasl_private_timer_i
for (int i = 0; i < s->timer_count; i++) {
if (s->timers[i] == id) {
s->timers[i] = s->timers[--s->timer_count];
- return s->display && yasl_private_loop_del_timer(s->display->loop, id);
+ return s->display && yasl_loop_del_timer(s->display->loop, id);
}
}
return false;
}
-struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d)
+struct yasl_loop *yasl_private_display_loop(struct yasl_private_display *d)
{
return d ? d->loop : NULL;
}
@@ -560,7 +581,46 @@ void yasl_private_surface_request_frame(struct yasl_surface *s)
{
if (!s) return;
yasl_private_surface_schedule_redraw(s);
- if (s->display->loop) yasl_private_loop_kick(s->display->loop);
+ yasl_private_loop_wake(s->display->loop);
+}
+
+void yasl_private_surface_notify_frame_shown(struct yasl_surface *s)
+{
+ if (!s || !s->frame_requested)
+ return;
+ s->frame_requested = false;
+ s->refresh_owed = true;
+}
+
+static uint64_t frame_interval_done(void *ud, yasl_private_timer_id id, uint64_t interval_ns)
+{
+ (void)id; (void)interval_ns;
+ yasl_private_surface_notify_frame_shown(ud);
+ return 0;
+}
+
+bool yasl_private_surface_request_shown_frame(struct yasl_surface *s)
+{
+ s->frame_requested = true;
+ const struct yasl_private_backend_ops *ops = s->display->ops;
+ if (ops->surface_frame_in_flight) {
+ if (!ops->surface_frame_in_flight(s))
+ yasl_private_surface_notify_frame_shown(s);
+ return true;
+ }
+ /* No frame signal: one frame per refresh interval since the last present. */
+ if (yasl_private_loop_timer_armed(s->display->loop, s->frame_timer))
+ return true;
+ double refresh = yasl_private_surface_timing(s).refresh_ns;
+ uint64_t interval = refresh >= 1000000.0 && refresh <= 1000000000.0 ? (uint64_t)refresh : 16666667;
+ uint64_t presented = atomic_load(&s->presented_ns);
+ uint64_t since = yasl_private_loop_now_ns() - presented;
+ if (!presented || since >= interval) {
+ yasl_private_surface_notify_frame_shown(s);
+ return true;
+ }
+ s->frame_timer = yasl_private_surface_add_timer(s, interval - since, frame_interval_done, s);
+ return s->frame_timer != 0;
}
static uint64_t pacing_tick(void *ud, yasl_private_timer_id id, uint64_t interval)
diff --git a/src/common/backend.h b/src/common/backend.h
index b111d3e..7633609 100644
--- a/src/common/backend.h
+++ b/src/common/backend.h
@@ -23,11 +23,18 @@ struct yasl_private_display {
/* Set by the backend while a native modal loop (macOS live resize) blocks the
* event loop; refresh callbacks run only then. */
bool modal;
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
/* Backend's copy of yasl_private_display_spec.app_id, or NULL; for desktop services. */
const char *app_id;
struct yasl_private_event_state *events;
struct yasl_surface *surfaces;
+ /* Set by the backend: a lost connection comes back (Wayland). */
+ bool reconnects;
+ /* Set by the public layer, or NULL. pending: an event is ready that the
+ * queue does not hold, such as an owed REFRESH. deliver: hand queued events
+ * to the application during the loop step. */
+ bool (*pending)(struct yasl_private_display *d);
+ void (*deliver)(struct yasl_private_display *d);
};
struct yasl_surface {
@@ -43,6 +50,11 @@ struct yasl_surface {
struct yasl_private_vector2i reported_size;
uint32_t reported_scale_120, reported_state;
bool refresh_owed;
+ /* yasl_surface_request_frame waits for the frame in flight. */
+ bool frame_requested;
+ /* Written by GL presents on a rendering thread. */
+ _Atomic uint64_t presented_ns;
+ yasl_private_timer_id frame_timer;
void (*refresh)(struct yasl_surface *);
bool text_input_active;
unsigned text_purpose;
@@ -89,6 +101,8 @@ bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surfac
const struct yasl_private_event *event);
void yasl_private_surface_notify_configure(struct yasl_surface *s, struct yasl_private_geometry geometry);
void yasl_private_surface_notify_refresh(struct yasl_surface *s);
+/* The system showed the surface's last frame; answers a pending frame request. */
+void yasl_private_surface_notify_frame_shown(struct yasl_surface *s);
void yasl_private_surface_notify_close(struct yasl_surface *s);
void yasl_private_surface_notify_state(struct yasl_surface *s, uint32_t state);
void yasl_private_surface_notify_drag(struct yasl_surface *s, enum yasl_private_drag_event ev, struct yasl_private_vector2i pos);
@@ -132,10 +146,15 @@ struct yasl_private_backend_ops {
bool (*surface_update_text_input)(struct yasl_surface *);
enum yasl_private_backend type;
- struct yasl_private_display *(*display_open)(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec);
+ struct yasl_private_display *(*display_open)(struct yasl_loop *loop, const struct yasl_private_display_spec *spec);
void (*display_close)(struct yasl_private_display *d);
uint32_t (*display_caps)(const struct yasl_private_display *d);
- int (*display_dispatch_timeout)(struct yasl_private_display *d, int timeout_ms);
+ /* The loop driver hooks; see struct yasl_private_loop_driver. prepare and
+ * check are required. wait is optional, for platforms whose events do not
+ * arrive on a descriptor. */
+ bool (*display_prepare)(struct yasl_private_display *d);
+ int (*display_wait)(struct yasl_private_display *d, int timeout_ms);
+ void (*display_check)(struct yasl_private_display *d);
void (*display_flush)(struct yasl_private_display *d);
int (*display_modifiers)(const struct yasl_private_display *d);
/* Optional; returns zero or less when the system reports no interval. */
@@ -161,6 +180,9 @@ struct yasl_private_backend_ops {
void (*surface_set_callbacks)(struct yasl_surface *s, const struct yasl_private_window_cbs *cbs, void *ud);
void (*surface_set_title)(struct yasl_surface *s, const char *title);
void (*surface_schedule_redraw)(struct yasl_surface *s);
+ /* Optional; true while the system has not yet shown the last presented
+ * frame. Without it, frame requests are paced by the refresh interval. */
+ bool (*surface_frame_in_flight)(const struct yasl_surface *s);
bool (*surface_present)(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage);
/* Optional; NULL means only YASL_PRIVATE_POINTER_NORMAL is supported. Backends that
* implement RELATIVE push POINTER_MOTION with delta set and pos frozen. */
diff --git a/src/common/events.c b/src/common/events.c
index b318d0c..83baf11 100644
--- a/src/common/events.c
+++ b/src/common/events.c
@@ -200,6 +200,8 @@ bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surfac
if (!ok) d->events->error = errno;
yasl_private_mutex_unlock(&d->events->mutex);
if (!ok) dispose(node);
+ /* The owner's next step sees the queue; another thread must wake it. */
+ yasl_private_loop_wake(d->loop);
return ok;
failed:
yasl_private_mutex_lock(&d->events->mutex);
diff --git a/src/common/loop.h b/src/common/loop.h
index 3a1c542..437466b 100644
--- a/src/common/loop.h
+++ b/src/common/loop.h
@@ -5,11 +5,9 @@
struct pollfd;
-int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l);
+int yasl_private_loop_next_timeout_ms(const 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_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds);
-/* Whether id is armed: added, not deleted, and not retired by its callback. */
-bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id);
+int yasl_private_loop_poll_set(const yasl_loop *l, const struct pollfd **fds);
#endif /* YASL_PRIVATE_COMMON_LOOP_H */
diff --git a/src/common/private_loop.h b/src/common/private_loop.h
index 432bbc3..a70997a 100644
--- a/src/common/private_loop.h
+++ b/src/common/private_loop.h
@@ -1,168 +1,22 @@
/**
* @file
- * Optional event loop, file watches, and timers.
- *
- * Use the loop's owning thread for all calls and callbacks, except
- * yasl_private_loop_now_ns(), yasl_private_loop_kick(), and yasl_private_loop_post(), which any thread
- * may call. A display borrows a supplied loop. For window applications,
- * drive yasl_private_display_dispatch(), drain input, then yasl_private_display_flush(); the
- * standalone loop_run() function does not replace this cycle. Timer intervals are
- * nanoseconds; timeout_ms arguments are milliseconds. Do not recursively step/run
- * or destroy a loop from a callback.
+ * The internal parts of the loop. The loop itself is public: see yasl_loop_* in
+ * yasl.h. Use the loop's owning thread for all calls and callbacks, except
+ * yasl_private_loop_now_ns(), yasl_private_loop_kick(), yasl_private_loop_wake()
+ * and yasl_private_loop_post(), which any thread may call.
*/
#ifndef YASL_PRIVATE_LOOP_H
#define YASL_PRIVATE_LOOP_H
#include "common/types.h"
+#include "yasl/yasl.h"
#ifdef __cplusplus
extern "C" {
#endif
-/* Optional event loop. Use the display's loop when adding application timers.
- * A step waits at most timeout_ms: zero polls, a negative value waits indefinitely.
- * Calls and callbacks use the loop's owning thread. */
-/**
- * Create an owned standalone file-descriptor and timer loop.
- * @return A loop to destroy with yasl_private_loop_destroy(), or NULL on allocation failure.
- * Use on one owning thread. Passing it to yasl_private_display_open() only lends it to
- * the display; the caller retains ownership.
- */
-struct yasl_private_loop *yasl_private_loop_create(void);
-/**
- * Release a loop, its watches, its timers, and its signal watch.
- * @param l Loop, or NULL for no action.
- * Close displays borrowing this loop first. Must not run during a loop callback
- * or an active step/run. Watched file descriptors are not closed. Posted
- * functions that have not run are dropped; no thread may still post or kick.
- */
-void yasl_private_loop_destroy(struct yasl_private_loop *l);
-/**
- * Step a standalone loop until yasl_private_loop_quit() is called.
- * @param l Loop.
- * @return Zero after quit, or -1 on failure.
- * For a window application, use a dispatch, input-poll, flush cycle instead;
- * loop_run alone does not perform that full cycle on every backend.
- */
-int yasl_private_loop_run(struct yasl_private_loop *l);
-/**
- * Wait for file-descriptor readiness or a timer deadline and run callbacks.
- * @param l Loop.
- * @param timeout_ms Maximum wait: zero polls, negative waits indefinitely.
- * @return Number of callbacks handled, or -1 on failure.
- * Timer deadlines shorten the wait. Callbacks can add/delete sources, including
- * themselves. Do not recursively step/run this loop.
- */
-int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms);
-/**
- * Ask the currently running yasl_private_loop_run() to return after this step.
- * @param l Loop, or NULL for no action.
- * Call on the owning thread, usually from a callback.
- */
-void yasl_private_loop_quit(struct yasl_private_loop *l);
-/**
- * Wake a loop that is waiting, without running any callback.
- * @param l Loop, or NULL for no action.
- * May be called from any thread and from a signal handler. The loop must stay
- * alive until the call returns. A waiting yasl_private_loop_step(), yasl_private_loop_run(), or
- * yasl_private_display_dispatch() on this loop returns early; a loop that is not
- * waiting returns early from its next wait instead. Kicks coalesce. This does
- * not schedule a redraw or enqueue an event; use yasl_private_loop_post() to run code
- * on the owning thread.
- */
-void yasl_private_loop_kick(struct yasl_private_loop *l);
-/**
- * Run a function on the loop's owning thread.
- * @param l Loop.
- * @param fn Required function, called once with ud during a later step.
- * @param ud Borrowed userdata until fn runs.
- * @return true if queued; false for NULL arguments, a loop without a wake
- * pipe, or allocation failure.
- * May be called from any thread, but not from a signal handler. Wakes the loop
- * like yasl_private_loop_kick(). Functions run in posting order, during a
- * yasl_private_loop_step() or display dispatch on the owning thread. They may post
- * again; a function posted while posts are running waits for a later step.
- * Stop posting before yasl_private_loop_destroy(), which drops functions that have
- * not run.
- */
-bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud);
-/**
- * Deliver POSIX signals to a callback on the loop's owning thread.
- * @param l Loop.
- * @param signals Signal numbers to watch; the array is read during the call.
- * @param n Number of entries, at least one.
- * @param cb Required callback, given ud and one signal number per delivery.
- * @param ud Borrowed userdata until the watch is removed.
- * @return true if every signal is watched; false with errno set and no
- * dispositions changed. EINVAL means a NULL or invalid argument, including
- * SIGKILL and SIGSTOP. EBUSY means another loop watches one of the signals.
- * Replaces this loop's previous watch, if any. Installs one process-wide
- * handler per signal with SA_RESTART and an empty handler mask, and saves the
- * previous action. The handler only records the signal and wakes the loop,
- * so it may run on any thread. YASL does not change any thread's signal mask:
- * a signal blocked in every thread stays pending and is not delivered. Caught
- * signals reset to their default action across exec, so spawned programs
- * do not inherit the handler. Repeated deliveries of one signal before the
- * callback runs coalesce into one callback. SIGCHLD reports stopped and
- * continued children as well as exits. Callbacks run during yasl_private_loop_step()
- * or display dispatch, and may remove the watch.
- */
-bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n,
- void (*cb)(void *ud, int signo), void *ud);
-/**
- * Stop watching signals and restore their previous actions.
- * @param l Loop, or NULL for no action.
- * Does nothing if the loop watches no signals. Signals that arrived but have
- * not reached the callback are dropped. yasl_private_loop_destroy() also unwatches.
- */
-void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l);
-/**
- * Read the monotonic clock used by YASL timers and timing results.
- * @return Nanoseconds from CLOCK_MONOTONIC, unrelated to wall-clock time.
- * May be called from any thread.
- */
-uint64_t yasl_private_loop_now_ns(void);
-/**
- * Get the loop used for a display's file watches and application timers.
- * @param d Display, or NULL.
- * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself.
- */
-struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d);
-/**
- * Register a borrowed file descriptor for poll-style readiness.
- * @param l Loop.
- * @param fd Open file descriptor; it remains owned by the caller.
- * @param events Bitmask from <poll.h>, for example POLLIN or POLLOUT.
- * @param fn Callback receiving poll revents (including errors/hangup); return value is ignored.
- * @param ud Borrowed userdata until watch removal or loop destruction.
- * @return Positive watch ID, or -1 on failure.
- * Delete the watch before closing fd. The callback must consume readiness or
- * change/remove its watch to avoid repeated immediate wakeups.
- */
-int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events,
- int (*fn)(int fd, short revents, void *ud), void *ud);
-/**
- * Remove a file watch without closing its descriptor.
- * @param l Loop.
- * @param id Watch ID returned by yasl_private_loop_add_fd().
- * @return Zero if removed, or -1 for an invalid/missing watch.
- */
-int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id);
-/**
- * Replace a watch's poll event mask.
- * @param l Loop.
- * @param id Watch ID.
- * @param events New <poll.h> mask; zero disables requested readiness (errors may still arrive).
- * @return Zero if updated, or -1 for an invalid/missing watch.
- */
-int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events);
-
-/* Timers use nanoseconds. Return the next interval to repeat/reschedule, or zero
- * to remove the timer. A zero ID indicates failure. Loop timers live until they
- * stop, are deleted, or the loop is destroyed. Surface timers are also canceled
- * when their surface is destroyed. */
-/** Timer identity scoped to its loop. Zero is invalid; do not reuse a retired ID. */
-typedef uint32_t yasl_private_timer_id;
+/** Timer identity scoped to its loop. Zero is invalid; IDs are never reissued. */
+typedef uint64_t yasl_private_timer_id;
/** Timer callback. interval_ns is the last scheduled delay; return the next delay or zero to stop. */
typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, uint64_t interval_ns);
@@ -170,29 +24,60 @@ typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, ui
#define YASL_PRIVATE_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull)
/**
- * Schedule a timer on a loop.
- * @param l Loop.
- * @param interval_ns Nonzero delay in monotonic nanoseconds before first firing.
- * @param cb Required callback; returns a new nonzero interval to repeat, or zero to stop.
- * @param ud Borrowed userdata until the timer stops, is deleted, or the loop is destroyed.
- * @return Nonzero timer ID, or zero for invalid arguments or allocation failure.
- * Repeating timers coalesce missed deadlines; do not use them to count elapsed
- * wall time. The callback receives the last scheduled interval, not elapsed time.
+ * How a display drives the loop it runs on. Each step runs prepare, the wait,
+ * check, deliver, then descriptor callbacks and timers.
+ * prepare: dispatch what the backend already holds, draw due surfaces, flush
+ * requests, and arm the read. True when progress is possible now, so the
+ * wait must not block.
+ * wait: replaces poll(2) for platforms whose events do not arrive on a
+ * descriptor; NULL uses poll. The loop reads descriptor readiness after it.
+ * check: runs after every wait, failed or not, before any callback; it closes
+ * what prepare opened.
+ * deliver: optional; hands queued events to the application.
+ */
+struct yasl_private_loop_driver {
+ bool (*prepare)(void *ud);
+ int (*wait)(void *ud, int timeout_ms);
+ void (*check)(void *ud);
+ void (*deliver)(void *ud);
+ void *ud;
+};
+/** Install a driver, or remove it with NULL. False with EBUSY when one is set. */
+bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver);
+/** True on the thread that created the loop. */
+bool yasl_private_loop_owned(const yasl_loop *l);
+/** True while a step runs. */
+bool yasl_private_loop_stepping(const yasl_loop *l);
+/** The revents the last wait reported for watch id, until its callback runs. */
+short yasl_private_loop_revents(const yasl_loop *l, int id);
+
+/** Wake a waiting loop from any thread or a signal handler. Kicks coalesce. */
+void yasl_private_loop_kick(yasl_loop *l);
+/** Kick, but only from another thread: the owner's next step sees its own work. */
+void yasl_private_loop_wake(yasl_loop *l);
+/**
+ * Run fn on the loop's owning thread during a later step. Any thread, not a
+ * signal handler. False for NULL arguments or allocation failure. Functions run
+ * in posting order; one posted while posts run waits for a later step.
*/
-yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns,
- yasl_private_timer_cb cb, void *ud);
+bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud);
+/** CLOCK_MONOTONIC nanoseconds. Any thread. */
+uint64_t yasl_private_loop_now_ns(void);
+
/**
- * Cancel a loop timer, including from its own callback.
- * @param l Loop.
- * @param id Timer ID.
- * @return true if an armed timer was removed, false if invalid or already retired.
+ * Get the loop used for a display's file watches and application timers.
+ * @param d Display, or NULL.
+ * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself.
*/
-bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id);
+yasl_loop *yasl_private_display_loop(struct yasl_private_display *d);
+
+/** Whether id is armed: added, not deleted, and not retired by its callback. */
+bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id);
/**
* Schedule a timer canceled automatically on surface destruction.
* @param s Surface whose display loop will run the timer.
- * @param interval_ns Nonzero initial delay in nanoseconds.
- * @param cb Required timer callback, with the same rules as yasl_private_loop_add_timer().
+ * @param interval_ns Initial delay in nanoseconds.
+ * @param cb Required timer callback, with the same rules as yasl_loop_add_timer().
* @param ud Borrowed userdata until the timer ends or the surface is destroyed.
* @return Nonzero timer ID, or zero on failure.
* The surface does not own userdata. Cancel with yasl_private_surface_del_timer().
diff --git a/src/common/surface.c b/src/common/surface.c
index aeca3d8..5c29a10 100644
--- a/src/common/surface.c
+++ b/src/common/surface.c
@@ -13,9 +13,12 @@ static struct yasl_private_display *display;
static pthread_t owner;
static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER;
static atomic_bool wake_requested;
-static bool pumping;
static unsigned refresh_depth;
static char *delivered_text; /* Text of the last returned event; freed by the next wait. */
+/* The options of the open display; on_event set means callback mode. */
+static struct yasl_options active;
+static char *active_app_id;
+static bool queue_failure_reported;
bool yasl_public_fail(int error)
{
@@ -62,20 +65,74 @@ bool yasl_public_connected(yasl_surface *s)
/* Runs a private call that reports failure through errno. */
#define STATUS(call) (errno = 0, yasl_public_status((call) || yasl_public_fail(errno)))
-int yasl_init(enum yasl_backend backend, const char *app_id)
+static bool refresh_ready(yasl_surface *s);
+static int take_event(yasl_event *out);
+/* An owed REFRESH is an event the queue does not hold. */
+static bool public_pending(struct yasl_private_display *d)
+{
+ for (yasl_surface *s = d->surfaces; s; s = s->event_next)
+ if (s->refresh_owed && refresh_ready(s))
+ return true;
+ return false;
+}
+/* Callback mode: runs in each loop step, after the display is read and before
+ * descriptor callbacks. Bounded, so a handler that keeps making events cannot
+ * starve the loop's other sources; the rest go in the next step, which does
+ * not wait while they are pending. */
+static void deliver_events(struct yasl_private_display *d)
{
- if (display)
- return yasl_public_owner() ? yasl_public_error(EALREADY) : -errno;
- if ((unsigned)backend > YASL_BACKEND_COCOA)
+ if (yasl_private_events_error(d) && !queue_failure_reported) {
+ queue_failure_reported = true;
+ active.on_event(active.userdata, &(yasl_event){
+ .type = YASL_EVENT_DISPLAY_LOST, .action = YASL_LOST_FINAL, .time_ns = yasl_time_ns()});
+ }
+ for (int i = 0; i < 256; i++) {
+ yasl_event event;
+ if (take_event(&event) <= 0)
+ break;
+ active.on_event(active.userdata, &event);
+ free(delivered_text);
+ delivered_text = NULL;
+ }
+}
+static bool same_options(const struct yasl_options *o)
+{
+ const char *app_id = o->app_id ? o->app_id : "";
+ return o->backend == active.backend && o->loop == active.loop &&
+ o->on_event == active.on_event && o->userdata == active.userdata &&
+ !strcmp(app_id, active_app_id ? active_app_id : "");
+}
+int yasl_init(const struct yasl_options *options)
+{
+ struct yasl_options o = options ? *options : (struct yasl_options){0};
+ if (display) {
+ if (!yasl_public_owner())
+ return -errno;
+ return yasl_public_error(same_options(&o) ? EALREADY : EBUSY);
+ }
+ if ((unsigned)o.backend > YASL_BACKEND_COCOA || !o.loop != !o.on_event)
return yasl_public_error(EINVAL);
+ if (o.loop && !yasl_private_loop_owned(o.loop))
+ return yasl_public_error(EPERM);
+ char *app_id = o.app_id ? strdup(o.app_id) : NULL;
+ if (o.app_id && !app_id)
+ return yasl_public_error(ENOMEM);
struct yasl_private_display_spec spec = {
- .app_id = app_id,
- .backend = (enum yasl_private_backend)backend,
+ .app_id = o.app_id,
+ .backend = (enum yasl_private_backend)o.backend,
};
errno = 0;
- struct yasl_private_display *d = yasl_private_display_open(NULL, &spec);
- if (!d)
+ struct yasl_private_display *d = yasl_private_display_open(o.loop, &spec);
+ if (!d) {
+ free(app_id);
return yasl_public_error(errno);
+ }
+ d->pending = public_pending;
+ d->deliver = o.on_event ? deliver_events : NULL;
+ active = o;
+ active.app_id = NULL;
+ active_app_id = app_id;
+ queue_failure_reported = false;
pthread_mutex_lock(&lifecycle_mutex);
owner = pthread_self();
display = d;
@@ -84,7 +141,7 @@ int yasl_init(enum yasl_backend backend, const char *app_id)
}
bool yasl_public_initialize(void)
{
- return display ? yasl_public_owner() : yasl_init(YASL_BACKEND_AUTO, NULL) == 0;
+ return display ? yasl_public_owner() : yasl_init(NULL) == 0;
}
static void close_display(void)
{
@@ -98,7 +155,7 @@ int yasl_shutdown(void)
{
if (display && !yasl_public_owner())
return -errno;
- if (pumping || refresh_depth)
+ if (refresh_depth || (display && yasl_private_loop_stepping(display->loop)))
return yasl_public_error(EBUSY);
if (display) {
for (yasl_surface *s = display->surfaces; s; s = s->event_next)
@@ -110,6 +167,9 @@ int yasl_shutdown(void)
}
free(delivered_text);
delivered_text = NULL;
+ free(active_app_id);
+ active_app_id = NULL;
+ active = (struct yasl_options){0};
atomic_store(&wake_requested, false);
#if YASL_HAS_VULKAN
extern void yasl_private_vulkan_reset(void);
@@ -260,7 +320,7 @@ yasl_surface *yasl_surface_create(const struct yasl_surface_options *o)
{
if (!o || (unsigned)o->type > YASL_SURFACE_NATIVE || !size_valid(o->width, o->height))
return yasl_public_fail(EINVAL), NULL;
- if (pumping || refresh_depth)
+ if (refresh_depth)
return yasl_public_fail(EBUSY), NULL;
unsigned flags = (o->type == YASL_SURFACE_CPU ? YASL_PRIVATE_SURFACE_CPU : 0u) |
(o->undecorated ? YASL_PRIVATE_SURFACE_UNDECORATED : 0u) |
@@ -387,6 +447,12 @@ int yasl_surface_request_size(yasl_surface *s, int w, int h)
return yasl_public_error(EINVAL);
return STATUS(yasl_private_surface_set_size(s, (struct yasl_private_vector2i){w, h}));
}
+int yasl_surface_request_frame(yasl_surface *s)
+{
+ if (!yasl_public_connected(s))
+ return -errno;
+ return STATUS(yasl_private_surface_request_shown_frame(s));
+}
int yasl_surface_set_cursor(yasl_surface *s, enum yasl_cursor cursor)
{
if (!yasl_public_surface(s, false))
@@ -409,7 +475,8 @@ void yasl_private_surface_notify_refresh(yasl_surface *s)
{
if (!s || !s->display)
return;
- if (s->refresh && s->display->modal && pumping && !refresh_depth) {
+ /* modal holds only inside the system's own loop, within the wait. */
+ if (s->refresh && s->display->modal && !refresh_depth) {
++refresh_depth;
s->refresh(s);
--refresh_depth;
@@ -703,6 +770,7 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e)
return true;
case YASL_PRIVATE_EVENT_DISPLAY_LOST:
e->type = YASL_EVENT_DISPLAY_LOST;
+ e->action = display->reconnects ? YASL_LOST_RECONNECTING : YASL_LOST_FINAL;
return true;
case YASL_PRIVATE_EVENT_DISPLAY_RESTORED:
e->type = YASL_EVENT_DISPLAY_RESTORED;
@@ -869,11 +937,14 @@ static int pump(yasl_event *out, int timeout)
int n = take_event(out);
if (n)
return n;
- yasl_private_display_flush(display);
- if ((n = take_event(out)))
- return n;
+ int error = yasl_private_events_error(display);
+ if (error)
+ return yasl_public_error(error);
if (atomic_exchange(&wake_requested, false))
return 0;
+ /* A loss that does not reconnect ends the waits once its event is out. */
+ if (atomic_load(&display->offline) && !display->reconnects)
+ return yasl_public_error(ENOTCONN);
int wait = timeout;
if (timeout > 0) {
uint64_t now = yasl_time_ns();
@@ -881,11 +952,11 @@ static int pump(yasl_event *out, int timeout)
return 0;
wait = (int)((deadline - now + 999999u) / 1000000u);
}
- if (yasl_private_display_dispatch(display, wait) < 0)
- return yasl_public_error(errno);
- yasl_private_display_flush(display);
- if ((n = take_event(out)) || timeout == 0)
- return n;
+ int stepped = yasl_loop_step(display->loop, wait);
+ if (stepped < 0)
+ return yasl_public_error(-stepped);
+ if (timeout == 0)
+ return take_event(out);
}
}
int yasl_wait_event(yasl_event *out, int timeout)
@@ -895,13 +966,11 @@ int yasl_wait_event(yasl_event *out, int timeout)
*out = (yasl_event){.type = YASL_EVENT_NONE};
if (!yasl_public_owner())
return -errno;
- if (pumping || refresh_depth)
+ if (active.on_event || refresh_depth || yasl_private_loop_stepping(display->loop))
return yasl_public_error(EBUSY);
free(delivered_text);
delivered_text = NULL;
- pumping = true;
int n = pump(out, timeout);
- pumping = false;
if (n < 0)
*out = (yasl_event){.type = YASL_EVENT_NONE};
return n;
@@ -1014,5 +1083,8 @@ int yasl_gl_present(yasl_surface *s)
return -errno;
if (!display->ops->gl_swap_buffers)
return yasl_public_error(ENOTSUP);
- return STATUS(display->ops->gl_swap_buffers(display, s));
+ int status = STATUS(display->ops->gl_swap_buffers(display, s));
+ if (status == 0)
+ atomic_store(&s->presented_ns, yasl_private_loop_now_ns());
+ return status;
}
diff --git a/src/common/types.h b/src/common/types.h
index 3fffd47..8d00d0b 100644
--- a/src/common/types.h
+++ b/src/common/types.h
@@ -4,8 +4,9 @@
*
* All calls and callbacks use the display's opening thread (the main thread on
* Cocoa), unless explicitly documented otherwise. Userdata is borrowed; callback
- * tables and creation specs are copied. Do not recursively dispatch/flush or
- * destroy a display/loop while dispatch or a callback is active. Destroy child
+ * tables and creation specs are copied. The display drives its loop: each
+ * yasl_loop_step dispatches it. Do not recursively step or flush, or destroy a
+ * display/loop while a step or a callback is active. Destroy child
* popups before parents, surfaces before displays, displays before borrowed loops.
* Surface destruction from its own callback is supported; stop using it immediately.
*
@@ -43,7 +44,7 @@ extern "C" {
struct yasl_private_display;
struct yasl_surface;
-struct yasl_private_loop;
+struct yasl_loop;
/* Geometry */
/** A two-dimensional integer size, position, or offset; units depend on the caller. */
@@ -569,7 +570,7 @@ struct yasl_private_display_spec {
* opening fails without a more specific platform error. Explicit choices never
* fall back. Close with yasl_private_display_close() after destroying its surfaces.
*/
-struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop,
+struct yasl_private_display *yasl_private_display_open(struct yasl_loop *loop,
const struct yasl_private_display_spec *spec);
/**
* Read the selected backend.
@@ -592,19 +593,6 @@ void yasl_private_display_close(struct yasl_private_display *d);
* permission to perform an operation at any arbitrary moment.
*/
uint32_t yasl_private_display_caps(const struct yasl_private_display *d);
-/* Process events, waiting at most timeout_ms: zero polls, negative waits.
- * Pending input or redraw work returns immediately. Returns -1 on failure. */
-/**
- * Receive events and run ready loop sources.
- * @param d Display to dispatch.
- * @param timeout_ms Maximum wait in milliseconds: zero polls, negative waits indefinitely.
- * @return A nonnegative backend-specific work count on success, or -1 with errno
- * set on failure (EINVAL for NULL, EIO if no specific backend error is available).
- * Do not interpret the count as the number of queued input events. Pending input
- * or runnable redraw work prevents waiting. Drain input and call
- * yasl_private_display_flush() after updating application state. Do not dispatch recursively.
- */
-int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms);
/**
* Draw scheduled surfaces and send pending display requests.
* @param d Display, or NULL for no action.
@@ -886,6 +874,9 @@ bool yasl_private_frame_present(struct yasl_private_frame *frame,
const struct yasl_private_rect2i *rects, size_t count);
/** Coalesce a redraw request; use on the display thread. NULL is ignored. */
void yasl_private_surface_request_frame(struct yasl_surface *s);
+/** Owe s a REFRESH once the system shows its frame in flight, or at once when
+ * none is. False with errno when the fallback timer cannot start. */
+bool yasl_private_surface_request_shown_frame(struct yasl_surface *s);
/** Set drawing policy for either loop style. Returns false with errno on failure. */
bool yasl_private_surface_set_pacing(struct yasl_surface *s, yasl_private_pacing_mode mode);