From 8900a72ac4639ccbbef29bdafe3f0352878ae3ae Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sat, 8 Feb 2025 16:32:30 -0400 Subject: [PATCH] The main loop runs while resizing or moving the window, on Windows. Add love.event.setModalDrawCallback to override the function that's called during those resize/move operations. Fixes #2148. --- src/modules/event/Event.cpp | 31 +++++++++++++ src/modules/event/Event.h | 19 ++++++++ src/modules/event/sdl/Event.cpp | 79 ++++++++++++++++++++++++-------- src/modules/event/sdl/Event.h | 2 + src/modules/event/wrap_Event.cpp | 68 +++++++++++++++++++++++++++ src/modules/love/boot.lua | 7 +++ 6 files changed, 186 insertions(+), 20 deletions(-) diff --git a/src/modules/event/Event.cpp b/src/modules/event/Event.cpp index 45b7c2c2b..b9cbb1a16 100644 --- a/src/modules/event/Event.cpp +++ b/src/modules/event/Event.cpp @@ -40,11 +40,18 @@ Message::~Message() Event::Event(const char *name) : Module(M_EVENT, name) + , modalDrawData() + , defaultModalDrawData() { } Event::~Event() { + if (modalDrawData.cleanup != nullptr) + modalDrawData.cleanup(modalDrawData.context); + + if (defaultModalDrawData.cleanup != nullptr) + defaultModalDrawData.cleanup(defaultModalDrawData.context); } void Event::push(Message *msg) @@ -75,5 +82,29 @@ void Event::clear() } } +void Event::setModalDrawData(const ModalDrawData &data) +{ + if (modalDrawData.cleanup != nullptr) + modalDrawData.cleanup(modalDrawData.context); + + modalDrawData = data; +} + +void Event::setDefaultModalDrawData(const ModalDrawData &data) +{ + if (defaultModalDrawData.cleanup != nullptr) + defaultModalDrawData.cleanup(defaultModalDrawData.context); + + defaultModalDrawData = data; +} + +void Event::modalDraw() +{ + if (modalDrawData.draw != nullptr) + modalDrawData.draw(modalDrawData.context); + else if (defaultModalDrawData.draw != nullptr) + defaultModalDrawData.draw(defaultModalDrawData.context); +} + } // event } // love diff --git a/src/modules/event/Event.h b/src/modules/event/Event.h index e07a64e47..1b1660c0c 100644 --- a/src/modules/event/Event.h +++ b/src/modules/event/Event.h @@ -55,6 +55,15 @@ class Event : public Module { public: + typedef void (*ModalDrawCallback)(void *context); + + struct ModalDrawData + { + ModalDrawCallback draw; + ModalDrawCallback cleanup; + void *context; + }; + virtual ~Event(); void push(Message *msg); @@ -64,10 +73,20 @@ public: virtual void pump(float waitTimeout = 0.0f) = 0; virtual Message *wait() = 0; + void setModalDrawData(const ModalDrawData &data); + const ModalDrawData &getModalDrawData() const { return modalDrawData; } + + void setDefaultModalDrawData(const ModalDrawData &data); + + void modalDraw(); + protected: Event(const char *name); + ModalDrawData modalDrawData; + ModalDrawData defaultModalDrawData; + love::thread::MutexRef mutex; std::queue queue; diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index c90ef5c31..0dd588535 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -79,19 +79,27 @@ static void normalizedToDPICoords(love::window::Window *window, double *x, doubl // SDL's event watch callbacks trigger when the event is actually posted inside // SDL, unlike with SDL_PollEvents. This is useful for some events which require // handling inside the function which triggered them on some backends. -static bool SDLCALL watchAppEvents(void * /*udata*/, SDL_Event *event) +// Note: this may run on non-main threads on some platforms (Android?) +static bool SDLCALL watchAppEvents(void *udata, SDL_Event *event) { - auto gfx = Module::getInstance(Module::M_GRAPHICS); + auto eventModule = (Event *)udata; switch (event->type) { - // On iOS, calling any OpenGL ES function after the function which triggers - // SDL_APP_DIDENTERBACKGROUND is called will kill the app, so we handle it - // with an event watch callback, which will be called inside that function. case SDL_EVENT_DID_ENTER_BACKGROUND: case SDL_EVENT_WILL_ENTER_FOREGROUND: - if (gfx) - gfx->setActive(event->type == SDL_EVENT_WILL_ENTER_FOREGROUND); + // On iOS, calling any OpenGL ES function after the function which triggers + // SDL_APP_DIDENTERBACKGROUND is called will kill the app, so we handle it + // with an event watch callback, which will be called inside that function. + { + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx && SDL_IsMainThread()) + gfx->setActive(event->type == SDL_EVENT_WILL_ENTER_FOREGROUND); + } + break; + case SDL_EVENT_WINDOW_EXPOSED: + if (eventModule != nullptr && SDL_IsMainThread()) + eventModule->modalDraw(); break; default: break; @@ -119,24 +127,55 @@ void Event::pump(float waitTimeout) { exceptionIfInRenderPass("love.event.pump"); - int waitTimeoutMS = 0; - if (std::isinf(waitTimeout) || waitTimeout < 0.0f) - waitTimeoutMS = -1; // Wait forever. - else if (waitTimeout > 0.0f) - waitTimeoutMS = (int)std::min(LOVE_INT32_MAX, 1000LL * waitTimeout); + bool shouldPoll = false; - // Wait for the first event, if requested. - SDL_Event e; - if (SDL_WaitEventTimeout(&e, waitTimeoutMS)) + if (insideEventPump) { - StrongRef msg(convert(e), Acquire::NORETAIN); - if (msg) - push(msg); + // Don't pump if we're inside the event pump already, but do allow + // polling what's in the SDL queue. + shouldPoll = true; + } + else + { + int waitTimeoutMS = 0; + if (std::isinf(waitTimeout) || waitTimeout < 0.0f) + waitTimeoutMS = -1; // Wait forever. + else if (waitTimeout > 0.0f) + waitTimeoutMS = (int)std::min(LOVE_INT32_MAX, 1000LL * waitTimeout); - // Fetch any extra events that came in during WaitEvent. + try + { + // Wait for the first event, if requested. + SDL_Event e; + insideEventPump = true; + if (SDL_WaitEventTimeout(&e, waitTimeoutMS)) + { + insideEventPump = false; + StrongRef msg(convert(e), Acquire::NORETAIN); + if (msg) + push(msg); + + // Fetch any extra events that came in during WaitEvent. + shouldPoll = true; + } + else + { + insideEventPump = false; + } + } + catch (std::exception &) + { + insideEventPump = false; + throw; + } + } + + if (shouldPoll) + { + SDL_Event e; while (SDL_PollEvent(&e)) { - msg.set(convert(e), Acquire::NORETAIN); + StrongRef msg(convert(e), Acquire::NORETAIN); if (msg) push(msg); } diff --git a/src/modules/event/sdl/Event.h b/src/modules/event/sdl/Event.h index e6a28de01..5ed1dcd88 100644 --- a/src/modules/event/sdl/Event.h +++ b/src/modules/event/sdl/Event.h @@ -70,6 +70,8 @@ private: Message *convertJoystickEvent(const SDL_Event &e) const; Message *convertWindowEvent(const SDL_Event &e, love::window::Window *win); + bool insideEventPump = false; + }; // Event } // sdl diff --git a/src/modules/event/wrap_Event.cpp b/src/modules/event/wrap_Event.cpp index 175add59d..b165e3d2c 100644 --- a/src/modules/event/wrap_Event.cpp +++ b/src/modules/event/wrap_Event.cpp @@ -22,6 +22,7 @@ // LOVE #include "common/runtime.h" +#include "common/Reference.h" #include "sdl/Event.h" #include @@ -151,6 +152,71 @@ int w_restart(lua_State *L) return 1; } +static void drawCallback(void *context) +{ + auto r = (Reference *)context; + lua_State *L = r->getPinnedL(); + + r->push(L); + + int err = lua_pcall(L, 0, 0, 0); + + // Unfortunately, this eats the stack trace, too bad. + if (err != 0) + throw love::Exception("Error in modal draw callback: %s", lua_tostring(L, -1)); +} + +static void cleanupCallback(void *context) +{ + auto r = (Reference *)context; + delete r; +} + +int w_setModalDrawCallback(lua_State *L) +{ + Event::ModalDrawData data = {}; + + if (!lua_isnoneornil(L, 1)) + { + luaL_checktype(L, 1, LUA_TFUNCTION); + + // Save the callback function as a Reference. + lua_pushvalue(L, 1); + Reference *r = new Reference(L); + lua_pop(L, 1); + + data.draw = drawCallback; + data.cleanup = cleanupCallback; + data.context = r; + } + + luax_catchexcept(L, [&]() { instance()->setModalDrawData(data); }); + return 0; +} + +int w__setDefaultModalDrawCallback(lua_State *L) +{ + Event::ModalDrawData data = {}; + + if (!lua_isnoneornil(L, 1)) + { + luaL_checktype(L, 1, LUA_TFUNCTION); + + // Save the callback function as a Reference. + lua_pushvalue(L, 1); + Reference *r = new Reference(L); + lua_pop(L, 1); + + Event::ModalDrawData data = {}; + data.draw = drawCallback; + data.cleanup = cleanupCallback; + data.context = r; + } + + luax_catchexcept(L, [&]() { instance()->setDefaultModalDrawData(data); }); + return 0; +} + // List of functions to wrap. static const luaL_Reg functions[] = { @@ -161,6 +227,8 @@ static const luaL_Reg functions[] = { "clear", w_clear }, { "quit", w_quit }, { "restart", w_restart }, + { "setModalDrawCallback", w_setModalDrawCallback }, + { "_setDefaultModalDrawCallback", w__setDefaultModalDrawCallback }, { 0, 0 } }; diff --git a/src/modules/love/boot.lua b/src/modules/love/boot.lua index 65833c9b3..5c71fd50f 100644 --- a/src/modules/love/boot.lua +++ b/src/modules/love/boot.lua @@ -453,6 +453,7 @@ end return function() local func + local setModalDrawFunc = false local inerror = false local function deferErrhand(...) @@ -481,14 +482,20 @@ return function() result, main = xpcall(love.run, deferErrhand) if result then func = main + setModalDrawFunc = true elseif inerror then -- Error in error handler print("Error: " .. tostring(main)) end end func = earlyinit + local prevFunc = nil while func do + if setModalDrawFunc and love.event and func ~= prevFunc then + prevFunc = func + love.event._setDefaultModalDrawCallback(func) + end local _, retval, restartvalue = xpcall(func, deferErrhand) if retval then return retval, restartvalue end coroutine.yield()