From efc053fe5a38a4928fbfd3780f5665dd43bc7f95 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 15 Mar 2020 18:30:24 +0100 Subject: 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). --- .clang-tidy | 1 + COPYING | 2 +- examples/pugl_cairo_demo.c | 14 ++--- examples/pugl_embed_demo.c | 21 ++++---- examples/pugl_gl3_demo.c | 8 +-- examples/pugl_print_events.c | 5 +- examples/pugl_window_demo.c | 13 ++--- pugl/detail/implementation.c | 14 ++++- pugl/detail/mac.m | 51 +++++++----------- pugl/detail/win.c | 39 +++++++++++--- pugl/detail/x11.c | 54 +++++++++++++------ pugl/pugl.h | 64 ++++++++++++++++------ test/test_redisplay.c | 17 +++--- test/test_show_hide.c | 6 +-- test/test_update.c | 124 +++++++++++++++++++++++++++++++++++++++++++ test/test_utils.h | 4 +- wscript | 2 +- 17 files changed, 321 insertions(+), 118 deletions(-) create mode 100644 test/test_update.c diff --git a/.clang-tidy b/.clang-tidy index 0f46134..ee84a8a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,6 +5,7 @@ Checks: > -android-cloexec-fopen, -bugprone-suspicious-string-compare, -clang-analyzer-alpha.*, + -clang-analyzer-security.FloatLoopCounter, -hicpp-multiway-paths-covered, -hicpp-signed-bitwise, -llvm-header-guard, diff --git a/COPYING b/COPYING index b16a139..4a287b9 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright 2011-2019 David Robillard +Copyright 2011-2020 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/examples/pugl_cairo_demo.c b/examples/pugl_cairo_demo.c index d2e57f8..a1423ae 100644 --- a/examples/pugl_cairo_demo.c +++ b/examples/pugl_cairo_demo.c @@ -203,6 +203,11 @@ onEvent(PuglView* view, const PuglEvent* event) app->entered = false; puglPostRedisplay(view); break; + case PUGL_UPDATE: + if (app->opts.continuous) { + puglPostRedisplay(view); + } + break; case PUGL_EXPOSE: onDisplay(app, view, &event->expose); break; @@ -249,14 +254,9 @@ main(int argc, char** argv) puglShowWindow(view); PuglFpsPrinter fpsPrinter = { puglGetTime(app.world) }; + const double timeout = app.opts.continuous ? (1 / 60.0) : -1.0; while (!app.quit) { - if (app.opts.continuous) { - postButtonRedisplay(view); - } else { - puglPollEvents(app.world, -1); - } - - puglDispatchEvents(app.world); + puglUpdate(app.world, timeout); if (app.opts.continuous) { puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn); diff --git a/examples/pugl_embed_demo.c b/examples/pugl_embed_demo.c index 6bafae5..174fd36 100644 --- a/examples/pugl_embed_demo.c +++ b/examples/pugl_embed_demo.c @@ -1,5 +1,5 @@ /* - Copyright 2012-2019 David Robillard + Copyright 2012-2020 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -164,6 +164,11 @@ onParentEvent(PuglView* view, const PuglEvent* event) reshapeCube((int)event->configure.width, (int)event->configure.height); puglSetFrame(app->child, getChildFrame(parentFrame)); break; + case PUGL_UPDATE: + if (app->continuous) { + puglPostRedisplay(view); + } + break; case PUGL_EXPOSE: if (puglHasFocus(app->parent)) { glMatrixMode(GL_MODELVIEW); @@ -208,6 +213,11 @@ onEvent(PuglView* view, const PuglEvent* event) case PUGL_CONFIGURE: reshapeCube((int)event->configure.width, (int)event->configure.height); break; + case PUGL_UPDATE: + if (app->continuous) { + puglPostRedisplay(view); + } + break; case PUGL_EXPOSE: onDisplay(view); break; @@ -317,14 +327,7 @@ main(int argc, char** argv) while (!app.quit) { const double thisTime = puglGetTime(app.world); - if (app.continuous) { - puglPostRedisplay(app.parent); - puglPostRedisplay(app.child); - } else { - puglPollEvents(app.world, -1); - } - - puglDispatchEvents(app.world); + puglUpdate(app.world, app.continuous ? 0.0 : -1.0); ++framesDrawn; if (!requestedAttention && thisTime > 5.0) { diff --git a/examples/pugl_gl3_demo.c b/examples/pugl_gl3_demo.c index e0c63ca..26db852 100644 --- a/examples/pugl_gl3_demo.c +++ b/examples/pugl_gl3_demo.c @@ -1,5 +1,5 @@ /* - Copyright 2012-2019 David Robillard + Copyright 2012-2020 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -177,6 +177,9 @@ onEvent(PuglView* view, const PuglEvent* event) case PUGL_CONFIGURE: onConfigure(view, event->configure.width, event->configure.height); break; + case PUGL_UPDATE: + puglPostRedisplay(view); + break; case PUGL_EXPOSE: onExpose(view); break; case PUGL_CLOSE: app->quit = 1; break; case PUGL_KEY_PRESS: @@ -411,8 +414,7 @@ main(int argc, char** argv) // Grind away, drawing continuously PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)}; while (!app.quit) { - puglPostRedisplay(app.view); - puglDispatchEvents(app.world); + puglUpdate(app.world, 0.0); puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn); } diff --git a/examples/pugl_print_events.c b/examples/pugl_print_events.c index c662117..9c81033 100644 --- a/examples/pugl_print_events.c +++ b/examples/pugl_print_events.c @@ -1,5 +1,5 @@ /* - Copyright 2012-2019 David Robillard + Copyright 2012-2020 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -68,8 +68,7 @@ main(void) puglShowWindow(app.view); while (!app.quit) { - puglPollEvents(app.world, -1); - puglDispatchEvents(app.world); + puglUpdate(app.world, -1.0); } puglFreeView(app.view); diff --git a/examples/pugl_window_demo.c b/examples/pugl_window_demo.c index 394b959..248d44e 100644 --- a/examples/pugl_window_demo.c +++ b/examples/pugl_window_demo.c @@ -128,6 +128,9 @@ onEvent(PuglView* view, const PuglEvent* event) case PUGL_CONFIGURE: reshapeCube((int)event->configure.width, (int)event->configure.height); break; + case PUGL_UPDATE: + puglPostRedisplay(view); + break; case PUGL_EXPOSE: onDisplay(view); break; @@ -224,15 +227,7 @@ main(int argc, char** argv) PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)}; unsigned framesDrawn = 0; while (!app.quit) { - if (app.continuous) { - for (size_t i = 0; i < 2; ++i) { - puglPostRedisplay(app.cubes[i].view); - } - } else { - puglPollEvents(app.world, -1); - } - - puglDispatchEvents(app.world); + puglUpdate(app.world, app.continuous ? 0.0 : -1.0); ++framesDrawn; if (app.continuous) { 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 @@ -289,6 +289,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) { @@ -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 + Copyright 2012-2020 David Robillard Copyright 2017 Hanspeter Portner 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 + Copyright 2012-2020 David Robillard 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 + Copyright 2012-2020 David Robillard 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); /** @} @@ -1262,6 +1261,37 @@ puglWaitForEvent(PuglView* view); 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. diff --git a/test/test_redisplay.c b/test/test_redisplay.c index 4470053..6ec5bb8 100644 --- a/test/test_redisplay.c +++ b/test/test_redisplay.c @@ -31,6 +31,12 @@ #include #include +#ifdef __APPLE__ +static const double timeout = 1 / 60.0; +#else +static const double timeout = -1.0; +#endif + typedef enum { START, EXPOSED, @@ -85,13 +91,6 @@ onEvent(PuglView* view, const PuglEvent* event) return PUGL_SUCCESS; } -static void -tick(PuglWorld* world) -{ - assert(!puglPollEvents(world, -1)); - assert(!puglDispatchEvents(world)); -} - int main(int argc, char** argv) { @@ -111,7 +110,7 @@ main(int argc, char** argv) assert(!puglCreateWindow(app.view, "Pugl Test")); assert(!puglShowWindow(app.view)); while (app.state != EXPOSED) { - tick(app.world); + assert(!puglUpdate(app.world, timeout)); } // Send a custom event to trigger a redisplay in the event loop @@ -121,7 +120,7 @@ main(int argc, char** argv) // Loop until an expose happens in the same iteration as the redisplay app.state = SHOULD_REDISPLAY; while (app.state != REDISPLAYED) { - tick(app.world); + assert(!puglUpdate(app.world, timeout)); assert(app.state != POSTED_REDISPLAY); } diff --git a/test/test_show_hide.c b/test/test_show_hide.c index b942f45..ed5ede7 100644 --- a/test/test_show_hide.c +++ b/test/test_show_hide.c @@ -95,12 +95,10 @@ tick(PuglWorld* world) #ifdef __APPLE__ // FIXME: Expose events are not events on MacOS, so we can't block // indefinitely here since it will block forever - assert(!puglPollEvents(world, 1 / 30.0)); + assert(!puglUpdate(world, 1 / 30.0)); #else - assert(!puglPollEvents(world, -1)); + assert(!puglUpdate(world, -1)); #endif - - assert(!puglDispatchEvents(world)); } int diff --git a/test/test_update.c b/test/test_update.c new file mode 100644 index 0000000..bbda94a --- /dev/null +++ b/test/test_update.c @@ -0,0 +1,124 @@ +/* + Copyright 2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* + Tests that redisplays posted in the event handler are dispatched at the end + of the same event loop iteration. +*/ + +#undef NDEBUG + +#include "test_utils.h" + +#include "pugl/pugl.h" +#include "pugl/pugl_stub.h" + +#include +#include +#include + +#ifdef __APPLE__ +static const double timeout = 1 / 60.0; +#else +static const double timeout = -1.0; +#endif + +typedef enum { + START, + EXPOSED1, + UPDATED, + EXPOSED2, +} State; + +typedef struct { + PuglTestOptions opts; + PuglWorld* world; + PuglView* view; + State state; +} PuglTest; + +static PuglStatus +onEvent(PuglView* view, const PuglEvent* event) +{ + PuglTest* test = (PuglTest*)puglGetHandle(view); + + if (test->opts.verbose) { + printEvent(event, "Event: ", true); + } + + switch (event->type) { + case PUGL_EXPOSE: + switch (test->state) { + case START: + test->state = EXPOSED1; + break; + case UPDATED: + test->state = EXPOSED2; + break; + default: + break; + } + break; + + case PUGL_UPDATE: + if (test->state == EXPOSED1) { + puglPostRedisplay(view); + test->state = UPDATED; + } + break; + + default: + break; + } + + return PUGL_SUCCESS; +} + +int +main(int argc, char** argv) +{ + PuglTest app = {puglParseTestOptions(&argc, &argv), + puglNewWorld(PUGL_PROGRAM, 0), + NULL, + START}; + + // Set up view + app.view = puglNewView(app.world); + puglSetClassName(app.world, "Pugl Test"); + puglSetBackend(app.view, puglStubBackend()); + puglSetHandle(app.view, &app); + puglSetEventFunc(app.view, onEvent); + + // Create and show window + assert(!puglCreateWindow(app.view, "Pugl Test")); + assert(!puglShowWindow(app.view)); + + // Tick until an expose happens + while (app.state <= EXPOSED1) { + assert(!puglUpdate(app.world, timeout)); + assert(app.state != UPDATED); + } + + // Tick once and ensure the update and the expose it posted both happened + assert(!puglUpdate(app.world, 0.0)); + assert(app.state == EXPOSED2); + + // Tear down + puglFreeView(app.view); + puglFreeWorld(app.world); + + return 0; +} diff --git a/test/test_utils.h b/test/test_utils.h index 7dc6e6e..3e62714 100644 --- a/test/test_utils.h +++ b/test/test_utils.h @@ -1,5 +1,5 @@ /* - Copyright 2012-2019 David Robillard + Copyright 2012-2020 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -140,6 +140,8 @@ printEvent(const PuglEvent* event, const char* prefix, const bool verbose) return fprintf(stderr, "%sMap\n", prefix); case PUGL_UNMAP: return fprintf(stderr, "%sUnmap\n", prefix); + case PUGL_UPDATE: + return fprintf(stderr, "%sUpdate\n", prefix); case PUGL_CONFIGURE: return PRINT("%sConfigure " PFMT " " PFMT "\n", prefix, diff --git a/wscript b/wscript index 7d6d36c..5e2b833 100644 --- a/wscript +++ b/wscript @@ -172,7 +172,7 @@ def _build_pc_file(bld, name, desc, target, libname, deps={}, requires=[]): LIBS=' '.join(link_flags)) -tests = ['redisplay', 'show_hide'] +tests = ['redisplay', 'show_hide', 'update'] def build(bld): -- cgit v1.2.1