浏览代码

Fix quit events being dropped if they're processed during a modal draw.

Forward quit data to the next full event processing cycle, and skip love.quit callbacks being called for a second time in that case.
Sasha Szpakowski 1 月之前
父节点
当前提交
0111d70ead

+ 18 - 6
src/modules/event/Event.cpp

@@ -55,10 +55,18 @@ Event::~Event()
 }
 
 void Event::push(Message *msg)
+{
+	push(msg, false);
+}
+
+void Event::push(Message *msg, bool pushFront)
 {
 	Lock lock(mutex);
 	msg->retain();
-	queue.push(msg);
+	if (pushFront)
+		queue.push_front(msg);
+	else
+		queue.push_back(msg);
 }
 
 bool Event::poll(Message *&msg)
@@ -67,7 +75,7 @@ bool Event::poll(Message *&msg)
 	if (queue.empty())
 		return false;
 	msg = queue.front();
-	queue.pop();
+	queue.pop_front();
 	return true;
 }
 
@@ -76,9 +84,8 @@ void Event::clear()
 	Lock lock(mutex);
 	while (!queue.empty())
 	{
-		// std::queue::pop will remove the first (front) element.
 		queue.front()->release();
-		queue.pop();
+		queue.pop_front();
 	}
 }
 
@@ -104,12 +111,17 @@ void Event::modalDraw()
 	if (!deferredExceptionMessage.empty())
 		return;
 
+	// Also skip the draw if a previous one generated a return value that
+	// needs to be passed down as a quit event.
+	if (deferredReturnValues[0].getType() != Variant::NIL)
+		return;
+
 	try
 	{
 		if (modalDrawData.draw != nullptr)
-			modalDrawData.draw(modalDrawData.context);
+			modalDrawData.draw(modalDrawData.context, &deferredReturnValues[0], &deferredReturnValues[1]);
 		else if (defaultModalDrawData.draw != nullptr)
-			defaultModalDrawData.draw(defaultModalDrawData.context);
+			defaultModalDrawData.draw(defaultModalDrawData.context, &deferredReturnValues[0], &deferredReturnValues[1]);
 	}
 	catch (std::exception &e)
 	{

+ 8 - 4
src/modules/event/Event.h

@@ -31,7 +31,7 @@
 #include "thread/threads.h"
 
 // C++
-#include <queue>
+#include <deque>
 #include <vector>
 
 namespace love
@@ -55,12 +55,13 @@ class Event : public Module
 {
 public:
 
-	typedef void (*ModalDrawCallback)(void *context);
+	typedef void (*ModalDrawCallback)(void *context, Variant *returnValue0, Variant *returnValue1);
+	typedef void (*ModalCleanupCallback)(void *context);
 
 	struct ModalDrawData
 	{
 		ModalDrawCallback draw;
-		ModalDrawCallback cleanup;
+		ModalCleanupCallback cleanup;
 		void *context;
 	};
 
@@ -84,12 +85,15 @@ protected:
 
 	Event(const char *name);
 
+	void push(Message *msg, bool pushFront);
+
 	ModalDrawData modalDrawData;
 	ModalDrawData defaultModalDrawData;
 	std::string deferredExceptionMessage;
+	Variant deferredReturnValues[2];
 
 	love::thread::MutexRef mutex;
-	std::queue<Message *> queue;
+	std::deque<Message *> queue;
 
 }; // Event
 

+ 17 - 0
src/modules/event/sdl/Event.cpp

@@ -174,8 +174,25 @@ void Event::pump(float waitTimeout)
 		{
 			std::string exceptionstr = deferredExceptionMessage;
 			deferredExceptionMessage.clear();
+			deferredReturnValues[0] = Variant();
+			deferredReturnValues[1] = Variant();
 			throw love::Exception("%s", exceptionstr.c_str());
 		}
+
+		if (deferredReturnValues[0].getType() != Variant::NIL)
+		{
+			// Third arg being true will tell love.run to skip the love.quit callback,
+			// since the original modal draw function already processed that.
+			std::vector<Variant> args = {deferredReturnValues[0], deferredReturnValues[1], Variant(true)};
+
+			StrongRef<Message> msg(new Message("quit", args), Acquire::NORETAIN);
+
+			// Push to the front of queue so it's dealt with before any other event.
+			push(msg, true);
+
+			deferredReturnValues[0] = Variant();
+			deferredReturnValues[1] = Variant();
+		}
 	}
 
 	if (shouldPoll)

+ 29 - 3
src/modules/event/wrap_Event.cpp

@@ -152,18 +152,44 @@ int w_restart(lua_State *L)
 	return 1;
 }
 
-static void drawCallback(void *context)
+struct DrawCallbackData
+{
+	Variant returnValues[2];
+	Reference *r;
+};
+
+static int drawCallbackInner(lua_State *L)
+{
+	auto data = (DrawCallbackData *)lua_touserdata(L, 1);
+
+	data->r->push(L);
+
+	lua_call(L, 0, 2);
+
+	data->returnValues[0] = luax_checkvariant(L, -2, false);
+	data->returnValues[1] = luax_checkvariant(L, -1, false);
+
+	lua_pop(L, 2);
+	return 0;
+}
+
+static void drawCallback(void *context, Variant *returnVal0, Variant *returnVal1)
 {
 	auto r = (Reference *)context;
 	lua_State *L = r->getPinnedL();
 
-	r->push(L);
+	DrawCallbackData data = {};
+	data.r = r;
 
-	int err = lua_pcall(L, 0, 0, 0);
+	// pcall into C code to catch errors from checkvariant as well as the lua_call.
+	int err = lua_cpcall(L, drawCallbackInner, &data);
 
 	// Unfortunately, this eats the stack trace, too bad.
 	if (err != 0)
 		throw love::Exception("Error in modal draw callback: %s", lua_tostring(L, -1));
+
+	*returnVal0 = data.returnValues[0];
+	*returnVal1 = data.returnValues[1];
 }
 
 static void cleanupCallback(void *context)

+ 1 - 1
src/modules/love/callbacks.lua

@@ -175,7 +175,7 @@ function love.run()
 			love.event.pump()
 			for name, a,b,c,d,e,f,g,h in love.event.poll() do
 				if name == "quit" then
-					if not love.quit or not love.quit() then
+					if c or not love.quit or not love.quit() then
 						return a or 0, b
 					end
 				end