aboutsummaryrefslogtreecommitdiffstats
path: root/pugl
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2020-03-15 18:30:24 +0100
committerDavid Robillard <d@drobilla.net>2020-03-15 20:53:37 +0100
commitefc053fe5a38a4928fbfd3780f5665dd43bc7f95 (patch)
treec3e28366d5b57592e82c004ab59a3e364d4ef57f /pugl
parent3b9e8287fd4c1096a2d6244aa07bc28cacb4da8d (diff)
downloadpugl-efc053fe5a38a4928fbfd3780f5665dd43bc7f95.tar.gz
pugl-efc053fe5a38a4928fbfd3780f5665dd43bc7f95.tar.bz2
pugl-efc053fe5a38a4928fbfd3780f5665dd43bc7f95.zip
Unify event loop functions as puglUpdate()
The previous separation between polling and dispatching was a lie, especially on MacOS where it is impossible to only poll for events without dispatching anything. Providing such an API is misleading, and problematic in various other ways. So, merge them into a single puglUpdate() function which can do the right thing on all platforms. This also adds the behaviour of actually processing all events in the given time interval, which is almost always what clients actually want to do when using a positive timeout (naively doing this before caused terrible input lag).
Diffstat (limited to 'pugl')
-rw-r--r--pugl/detail/implementation.c14
-rw-r--r--pugl/detail/mac.m51
-rw-r--r--pugl/detail/win.c39
-rw-r--r--pugl/detail/x11.c54
-rw-r--r--pugl/pugl.h64
5 files changed, 151 insertions, 71 deletions
diff --git a/pugl/detail/implementation.c b/pugl/detail/implementation.c
index b2306c6..d7e7058 100644
--- a/pugl/detail/implementation.c
+++ b/pugl/detail/implementation.c
@@ -290,6 +290,18 @@ puglGetContext(PuglView* view)
#ifndef PUGL_DISABLE_DEPRECATED
PuglStatus
+puglPollEvents(PuglWorld* world, double timeout)
+{
+ return puglUpdate(world, timeout);
+}
+
+PuglStatus
+puglDispatchEvents(PuglWorld* world)
+{
+ return puglUpdate(world, 0.0);
+}
+
+PuglStatus
puglEnterContext(PuglView* view, bool drawing)
{
const PuglEventExpose expose = {
@@ -369,7 +381,7 @@ void
puglDispatchSimpleEvent(PuglView* view, const PuglEventType type)
{
assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP ||
- type == PUGL_UNMAP || type == PUGL_CLOSE);
+ type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE);
const PuglEvent event = {{type, 0}};
puglDispatchEvent(view, &event);
diff --git a/pugl/detail/mac.m b/pugl/detail/mac.m
index ab59b99..22f8088 100644
--- a/pugl/detail/mac.m
+++ b/pugl/detail/mac.m
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
Permission to use, copy, modify, and/or distribute this software for any
@@ -614,6 +614,12 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
[super viewWillStartLiveResize];
}
+- (void) viewWillDraw
+{
+ puglDispatchSimpleEvent(puglview, PUGL_UPDATE);
+ [super viewWillDraw];
+}
+
- (void) resizeTick
{
puglPostRedisplay(puglview);
@@ -915,27 +921,6 @@ puglRequestAttention(PuglView* view)
return PUGL_SUCCESS;
}
-PuglStatus
-puglPollEvents(PuglWorld* world, const double timeout)
-{
- NSDate* date = ((timeout < 0) ? [NSDate distantFuture] :
- (timeout == 0) ? nil :
- [NSDate dateWithTimeIntervalSinceNow:timeout]);
-
- /* Note that dequeue:NO is broken (it blocks forever even when events are
- pending), so we work around this by dequeueing the event then posting it
- back to the front of the queue. */
- NSEvent* event = [world->impl->app
- nextEventMatchingMask:NSAnyEventMask
- untilDate:date
- inMode:NSDefaultRunLoopMode
- dequeue:YES];
-
- [world->impl->app postEvent:event atStart:true];
-
- return PUGL_SUCCESS;
-}
-
PuglStatus puglSendEvent(PuglView* view, const PuglEvent* event)
{
if (event->type == PUGL_CLIENT) {
@@ -990,28 +975,32 @@ dispatchClientEvent(PuglWorld* world, NSEvent* ev)
}
PuglStatus
-puglDispatchEvents(PuglWorld* world)
+puglUpdate(PuglWorld* world, const double timeout)
{
- const NSTimeInterval startTime = [[NSProcessInfo processInfo] systemUptime];
+ NSDate* date = ((timeout < 0)
+ ? [NSDate distantFuture]
+ : [NSDate dateWithTimeIntervalSinceNow:timeout]);
for (NSEvent* ev = NULL;
(ev = [world->impl->app nextEventMatchingMask:NSAnyEventMask
- untilDate:nil
+ untilDate:date
inMode:NSDefaultRunLoopMode
dequeue:YES]);) {
- if ([ev timestamp] > startTime) {
- // Event is later, put it back for the next iteration and return
- [world->impl->app postEvent:ev atStart:true];
- break;
- } else if ([ev type] == NSApplicationDefined &&
- [ev subtype] == PUGL_CLIENT) {
+ if ([ev type] == NSApplicationDefined && [ev subtype] == PUGL_CLIENT) {
dispatchClientEvent(world, ev);
}
[world->impl->app sendEvent: ev];
}
+ for (size_t i = 0; i < world->numViews; ++i) {
+ PuglView* const view = world->views[i];
+
+ puglDispatchSimpleEvent(view, PUGL_UPDATE);
+ [view->impl->drawView displayIfNeeded];
+ }
+
return PUGL_SUCCESS;
}
diff --git a/pugl/detail/win.c b/pugl/detail/win.c
index bbaa872..22a0a25 100644
--- a/pugl/detail/win.c
+++ b/pugl/detail/win.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -145,8 +145,8 @@ puglInitViewInternals(void)
return (PuglInternals*)calloc(1, sizeof(PuglInternals));
}
-PuglStatus
-puglPollEvents(PuglWorld* world, const double timeout)
+static PuglStatus
+puglPollWinEvents(PuglWorld* world, const double timeout)
{
(void)world;
@@ -792,8 +792,8 @@ puglDispatchViewEvents(PuglView* view)
return PUGL_SUCCESS;
}
-PuglStatus
-puglDispatchEvents(PuglWorld* world)
+static PuglStatus
+puglDispatchWinEvents(PuglWorld* world)
{
for (size_t i = 0; i < world->numViews; ++i) {
PostMessage(world->views[i]->impl->hwnd, PUGL_LOCAL_MARK_MSG, 0, 0);
@@ -803,18 +803,43 @@ puglDispatchEvents(PuglWorld* world)
puglDispatchViewEvents(world->views[i]);
}
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglUpdate(PuglWorld* world, double timeout)
+{
+ const double startTime = puglGetTime(world);
+ PuglStatus st = PUGL_SUCCESS;
+
+ if (timeout < 0.0) {
+ st = puglPollWinEvents(world, timeout);
+ st = st ? st : puglDispatchWinEvents(world);
+ } else if (timeout == 0.0) {
+ st = puglDispatchWinEvents(world);
+ } else {
+ const double endTime = startTime + timeout - 0.001;
+ for (double t = startTime; t < endTime; t = puglGetTime(world)) {
+ if ((st = puglPollWinEvents(world, endTime - t)) ||
+ (st = puglDispatchWinEvents(world))) {
+ break;
+ }
+ }
+ }
+
for (size_t i = 0; i < world->numViews; ++i) {
+ puglDispatchSimpleEvent(world->views[i], PUGL_UPDATE);
UpdateWindow(world->views[i]->impl->hwnd);
}
- return PUGL_SUCCESS;
+ return st;
}
#ifndef PUGL_DISABLE_DEPRECATED
PuglStatus
puglProcessEvents(PuglView* view)
{
- return puglDispatchEvents(view->world);
+ return puglUpdate(view->world, 0.0);
}
#endif
diff --git a/pugl/detail/x11.c b/pugl/detail/x11.c
index 780213d..58451e9 100644
--- a/pugl/detail/x11.c
+++ b/pugl/detail/x11.c
@@ -117,8 +117,8 @@ puglInitViewInternals(void)
return (PuglInternals*)calloc(1, sizeof(PuglInternals));
}
-PuglStatus
-puglPollEvents(PuglWorld* world, const double timeout)
+static PuglStatus
+puglPollX11Socket(PuglWorld* world, const double timeout)
{
if (XPending(world->impl->display) > 0) {
return PUGL_SUCCESS;
@@ -140,8 +140,7 @@ puglPollEvents(PuglWorld* world, const double timeout)
ret = select(nfds, &fds, NULL, NULL, &tv);
}
- return ret < 0 ? PUGL_UNKNOWN_ERROR
- : ret == 0 ? PUGL_FAILURE : PUGL_SUCCESS;
+ return ret < 0 ? PUGL_UNKNOWN_ERROR : PUGL_SUCCESS;
}
static PuglView*
@@ -750,6 +749,8 @@ flushExposures(PuglWorld* world)
PuglEvent* const configure = &view->impl->pendingConfigure;
PuglEvent* const expose = &view->impl->pendingExpose;
+ puglDispatchSimpleEvent(view, PUGL_UPDATE);
+
if (configure->type || expose->type) {
view->backend->enter(view, expose->type ? &expose->expose : NULL);
view->eventFunc(view, configure);
@@ -762,8 +763,8 @@ flushExposures(PuglWorld* world)
}
}
-PuglStatus
-puglDispatchEvents(PuglWorld* world)
+static PuglStatus
+puglDispatchX11Events(PuglWorld* world)
{
const PuglX11Atoms* const atoms = &world->impl->atoms;
@@ -771,8 +772,6 @@ puglDispatchEvents(PuglWorld* world)
Display* display = world->impl->display;
XFlush(display);
- world->impl->dispatchingEvents = true;
-
// Process all queued events (without further flushing)
while (XEventsQueued(display, QueuedAfterReading) > 0) {
XEvent xevent;
@@ -827,10 +826,6 @@ puglDispatchEvents(PuglWorld* world)
}
}
- flushExposures(world);
-
- world->impl->dispatchingEvents = false;
-
return PUGL_SUCCESS;
}
@@ -838,10 +833,40 @@ puglDispatchEvents(PuglWorld* world)
PuglStatus
puglProcessEvents(PuglView* view)
{
- return puglDispatchEvents(view->world);
+ return puglUpdate(view->world, 0.0);
}
#endif
+PuglStatus
+puglUpdate(PuglWorld* world, double timeout)
+{
+ const double startTime = puglGetTime(world);
+ PuglStatus st = PUGL_SUCCESS;
+
+ world->impl->dispatchingEvents = true;
+
+ if (timeout < 0.0) {
+ st = puglPollX11Socket(world, timeout);
+ st = st ? st : puglDispatchX11Events(world);
+ } else if (timeout == 0.0) {
+ st = puglDispatchX11Events(world);
+ } else {
+ const double endTime = startTime + timeout - 0.001;
+ for (double t = startTime; t < endTime; t = puglGetTime(world)) {
+ if ((st = puglPollX11Socket(world, endTime - t)) ||
+ (st = puglDispatchX11Events(world))) {
+ break;
+ }
+ }
+ }
+
+ flushExposures(world);
+
+ world->impl->dispatchingEvents = false;
+
+ return st;
+}
+
double
puglGetTime(const PuglWorld* world)
{
@@ -988,8 +1013,7 @@ puglGetClipboard(PuglView* const view,
// Run event loop until data is received
while (!view->clipboard.data) {
- puglPollEvents(view->world, -1);
- puglDispatchEvents(view->world);
+ puglUpdate(view->world, -1.0);
}
}
diff --git a/pugl/pugl.h b/pugl/pugl.h
index d215749..85d0e8f 100644
--- a/pugl/pugl.h
+++ b/pugl/pugl.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -172,6 +172,7 @@ typedef enum {
PUGL_DESTROY, ///< View destroyed, a #PuglEventAny
PUGL_MAP, ///< View made visible, a #PuglEventAny
PUGL_UNMAP, ///< View made invisible, a #PuglEventAny
+ PUGL_UPDATE, ///< View ready to draw, a #PuglEventAny
PUGL_CONFIGURE, ///< View moved/resized, a #PuglEventConfigure
PUGL_EXPOSE, ///< View must be drawn, a #PuglEventExpose
PUGL_CLOSE, ///< View will be closed, a #PuglEventAny
@@ -620,12 +621,21 @@ PUGL_API double
puglGetTime(const PuglWorld* world);
/**
- Poll for events that are ready to be processed.
+ Update by processing events from the window system.
- This polls for events that are ready for any view in the world, potentially
- blocking depending on `timeout`.
+ This function is a single iteration of the main loop, and should be called
+ repeatedly to update all views.
- @param world The world to poll for events.
+ If a positive timeout is given, then events will be processed for that
+ amount of time, starting from when this function was called. For purely
+ event-driven programs, a timeout of -1.0 can be used to block indefinitely
+ until something happens. For continuously animating programs, a timeout
+ that is a reasonable fraction of the ideal frame period should be used, to
+ minimise input latency by ensuring that as many input events are consumed as
+ possible before drawing. Plugins should always use a timeout of 0.0 to
+ avoid blocking the host.
+
+ @param world The world to update.
@param timeout Maximum time to wait, in seconds. If zero, the call returns
immediately, if negative, the call blocks indefinitely.
@@ -633,18 +643,7 @@ puglGetTime(const PuglWorld* world);
@return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error.
*/
PUGL_API PuglStatus
-puglPollEvents(PuglWorld* world, double timeout);
-
-/**
- Dispatch any pending events to views.
-
- This processes all pending events, dispatching them to the appropriate
- views. View event handlers will be called in the scope of this call. This
- function does not block, if no events are pending then it will return
- immediately.
-*/
-PUGL_API PuglStatus
-puglDispatchEvents(PuglWorld* world);
+puglUpdate(PuglWorld* world, double timeout);
/**
@}
@@ -1263,6 +1262,37 @@ PUGL_API PUGL_DEPRECATED_BY("puglDispatchEvents") PuglStatus
puglProcessEvents(PuglView* view);
/**
+ Poll for events that are ready to be processed.
+
+ This polls for events that are ready for any view in the world, potentially
+ blocking depending on `timeout`.
+
+ @param world The world to poll for events.
+
+ @param timeout Maximum time to wait, in seconds. If zero, the call returns
+ immediately, if negative, the call blocks indefinitely.
+
+ @return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error.
+
+ @deprecated Use puglUpdate().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus
+puglPollEvents(PuglWorld* world, double timeout);
+
+/**
+ Dispatch any pending events to views.
+
+ This processes all pending events, dispatching them to the appropriate
+ views. View event handlers will be called in the scope of this call. This
+ function does not block, if no events are pending then it will return
+ immediately.
+
+ @deprecated Use puglUpdate().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus
+puglDispatchEvents(PuglWorld* world);
+
+/**
Enter the graphics context.
Note that, unlike some similar libraries, Pugl automatically enters and