2
0
Эх сурвалжийг харах

Replaced SDL user events list with dispatching of arbitrary functors

Ivan Savenko 2 жил өмнө
parent
commit
0f8d53e978

+ 10 - 2
client/CMT.cpp

@@ -451,8 +451,16 @@ static void mainLoop()
 {
 	SettingsListener resChanged = settings.listen["video"]["resolution"];
 	SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
-	resChanged([](const JsonNode &newState){  GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
-	fsChanged([](const JsonNode &newState){  GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
+
+	auto functor = [](const JsonNode &newState){
+		GH.dispatchMainThread([](){
+			boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+			GH.onScreenResize();
+		});
+	};
+
+	resChanged(functor);
+	fsChanged(functor);
 
 	inGuiThread.reset(new bool(true));
 

+ 0 - 2
client/CMakeLists.txt

@@ -29,7 +29,6 @@ set(client_SRCS
 
 	eventsSDL/NotificationHandler.cpp
 	eventsSDL/InputHandler.cpp
-	eventsSDL/UserEventHandler.cpp
 	eventsSDL/InputSourceKeyboard.cpp
 	eventsSDL/InputSourceMouse.cpp
 	eventsSDL/InputSourceText.cpp
@@ -173,7 +172,6 @@ set(client_HEADERS
 
 	eventsSDL/NotificationHandler.h
 	eventsSDL/InputHandler.h
-	eventsSDL/UserEventHandler.h
 	eventsSDL/InputSourceKeyboard.h
 	eventsSDL/InputSourceMouse.h
 	eventsSDL/InputSourceText.h

+ 26 - 2
client/CPlayerInterface.cpp

@@ -23,6 +23,7 @@
 #include "../CCallback.h"
 #include "windows/CCastleInterface.h"
 #include "eventsSDL/InputHandler.h"
+#include "mainmenu/CMainMenu.h"
 #include "gui/CursorHandler.h"
 #include "windows/CKingdomInterface.h"
 #include "CGameInfo.h"
@@ -1744,7 +1745,16 @@ void CPlayerInterface::requestReturningToMainMenu(bool won)
 	if(won && cb->getStartInfo()->campState)
 		CSH->startCampaignScenario(cb->getStartInfo()->campState);
 	else
-		GH.pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
+	{
+		GH.dispatchMainThread(
+			[]()
+			{
+				CSH->endGameplay();
+				GH.defActionsDef = 63;
+				CMM->menu->switchToTab("main");
+			}
+		);
+	}
 }
 
 void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
@@ -1840,7 +1850,21 @@ void CPlayerInterface::waitForAllDialogs(bool unlockPim)
 
 void CPlayerInterface::proposeLoadingGame()
 {
-	showYesNoDialog(CGI->generaltexth->allTexts[68], [](){ GH.pushUserEvent(EUserEvent::RETURN_TO_MENU_LOAD); }, nullptr);
+	showYesNoDialog(
+		CGI->generaltexth->allTexts[68],
+		[]()
+		{
+			GH.dispatchMainThread(
+				[]()
+				{
+					CSH->endGameplay();
+					GH.defActionsDef = 63;
+					CMM->menu->switchToTab("load");
+				}
+			);
+		},
+		nullptr
+	);
 }
 
 bool CPlayerInterface::capturedAllEvents()

+ 38 - 5
client/CServerHandler.cpp

@@ -21,6 +21,7 @@
 #include "windows/InfoWindows.h"
 
 #include "mainmenu/CMainMenu.h"
+#include "mainmenu/CPrologEpilogVideo.h"
 
 #ifdef VCMI_ANDROID
 #include "../lib/CAndroidVMHelper.h"
@@ -662,10 +663,36 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
 
 void CServerHandler::startCampaignScenario(std::shared_ptr<CampaignState> cs)
 {
-	if(cs)
-		GH.pushUserEvent(EUserEvent::CAMPAIGN_START_SCENARIO, CMemorySerializer::deepCopy(*cs.get()).release());
-	else
-		GH.pushUserEvent(EUserEvent::CAMPAIGN_START_SCENARIO, CMemorySerializer::deepCopy(*si->campState.get()).release());
+	std::shared_ptr<CampaignState> ourCampaign = cs;
+
+	if (!cs)
+		ourCampaign = si->campState;
+
+	GH.dispatchMainThread([ourCampaign]()
+	{
+		CSH->campaignServerRestartLock.set(true);
+		CSH->endGameplay();
+
+		auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
+		auto finisher = [=]()
+		{
+			if(!ourCampaign->isCampaignFinished())
+			{
+				GH.windows().pushWindow(CMM);
+				GH.windows().pushWindow(CMM->menu);
+				CMM->openCampaignLobby(ourCampaign);
+			}
+		};
+		if(epilogue.hasPrologEpilog)
+		{
+			GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
+		}
+		else
+		{
+			CSH->campaignServerRestartLock.waitUntil(false);
+			finisher();
+		}
+	});
 }
 
 void CServerHandler::showServerError(std::string txt)
@@ -842,7 +869,13 @@ void CServerHandler::threadHandleConnection()
 			if(client)
 			{
 				state = EClientState::DISCONNECTING;
-				GH.pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
+
+				GH.dispatchMainThread([]()
+				{
+					CSH->endGameplay();
+					GH.defActionsDef = 63;
+					CMM->menu->switchToTab("main");
+				});
 			}
 			else
 			{

+ 14 - 2
client/adventureMap/AdventureMapShortcuts.cpp

@@ -13,6 +13,7 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
+#include "../CServerHandler.h"
 #include "../PlayerLocalState.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
@@ -293,8 +294,19 @@ void AdventureMapShortcuts::viewPuzzleMap()
 
 void AdventureMapShortcuts::restartGame()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
-		[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
+		[]()
+		{
+			GH.dispatchMainThread(
+				[]()
+				{
+					CSH->sendRestartGame();
+				}
+			);
+		},
+		nullptr
+	);
 }
 
 void AdventureMapShortcuts::visitObject()

+ 14 - 6
client/eventsSDL/InputHandler.cpp

@@ -16,7 +16,6 @@
 #include "InputSourceKeyboard.h"
 #include "InputSourceTouch.h"
 #include "InputSourceText.h"
-#include "UserEventHandler.h"
 
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
@@ -35,7 +34,6 @@ InputHandler::InputHandler()
 	, keyboardHandler(std::make_unique<InputSourceKeyboard>())
 	, fingerHandler(std::make_unique<InputSourceTouch>())
 	, textHandler(std::make_unique<InputSourceText>())
-	, userHandler(std::make_unique<UserEventHandler>())
 {
 }
 
@@ -127,7 +125,7 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
 	}
 	else if(ev.type == SDL_USEREVENT)
 	{
-		userHandler->handleUserEvent(ev.user);
+		handleUserEvent(ev.user);
 
 		return;
 	}
@@ -244,15 +242,25 @@ bool InputHandler::hasTouchInputDevice() const
 	return fingerHandler->hasTouchInputDevice();
 }
 
-void InputHandler::pushUserEvent(EUserEvent usercode, void * userdata)
+void InputHandler::dispatchMainThread(const std::function<void()> & functor)
 {
+	auto heapFunctor = new std::function<void()>(functor);
+
 	SDL_Event event;
 	event.type = SDL_USEREVENT;
-	event.user.code = static_cast<int32_t>(usercode);
-	event.user.data1 = userdata;
+	event.user.code = 0;
+	event.user.data1 = static_cast <void*>(heapFunctor);
+	event.user.data2 = nullptr;
 	SDL_PushEvent(&event);
 }
 
+void InputHandler::handleUserEvent(const SDL_UserEvent & current)
+{
+	auto heapFunctor = static_cast<std::function<void()>*>(current.data1);
+
+	(*heapFunctor)();
+}
+
 const Point & InputHandler::getCursorPosition() const
 {
 	return cursorPosition;

+ 4 - 4
client/eventsSDL/InputHandler.h

@@ -15,12 +15,12 @@
 enum class EUserEvent;
 enum class MouseButton;
 union SDL_Event;
+struct SDL_UserEvent;
 
 class InputSourceMouse;
 class InputSourceKeyboard;
 class InputSourceTouch;
 class InputSourceText;
-class UserEventHandler;
 
 class InputHandler
 {
@@ -31,12 +31,12 @@ class InputHandler
 
 	void preprocessEvent(const SDL_Event & event);
 	void handleCurrentEvent(const SDL_Event & current);
+	void handleUserEvent(const SDL_UserEvent & current);
 
 	std::unique_ptr<InputSourceMouse> mouseHandler;
 	std::unique_ptr<InputSourceKeyboard> keyboardHandler;
 	std::unique_ptr<InputSourceTouch> fingerHandler;
 	std::unique_ptr<InputSourceText> textHandler;
-	std::unique_ptr<UserEventHandler> userHandler;
 
 public:
 	InputHandler();
@@ -66,8 +66,8 @@ public:
 	/// returns true if system has active touchscreen
 	bool hasTouchInputDevice() const;
 
-	/// Generates new user event that will be processed on next frame
-	void pushUserEvent(EUserEvent usercode, void * userdata);
+	/// Calls provided functor in main thread on next execution frame
+	void dispatchMainThread(const std::function<void()> & functor);
 
 	/// Returns current position of cursor, in VCMI logical screen coordinates
 	const Point & getCursorPosition() const;

+ 0 - 93
client/eventsSDL/UserEventHandler.cpp

@@ -1,93 +0,0 @@
-/*
-* EventHandlerSDLUser.cpp, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-
-#include "StdInc.h"
-#include "UserEventHandler.h"
-
-#include "../CMT.h"
-#include "../CPlayerInterface.h"
-#include "../CServerHandler.h"
-#include "../gui/CGuiHandler.h"
-#include "../gui/WindowHandler.h"
-#include "../mainmenu/CMainMenu.h"
-#include "../mainmenu/CPrologEpilogVideo.h"
-#include "../gui/EventDispatcher.h"
-
-#include "../../lib/campaign/CampaignState.h"
-
-#include <SDL_events.h>
-
-void UserEventHandler::handleUserEvent(const SDL_UserEvent & user)
-{
-	switch(static_cast<EUserEvent>(user.code))
-	{
-		case EUserEvent::FORCE_QUIT:
-		{
-			handleQuit(false);
-			return;
-		}
-		break;
-		case EUserEvent::RETURN_TO_MAIN_MENU:
-		{
-			CSH->endGameplay();
-			GH.defActionsDef = 63;
-			CMM->menu->switchToTab("main");
-		}
-		break;
-		case EUserEvent::RESTART_GAME:
-		{
-			CSH->sendRestartGame();
-		}
-		break;
-		case EUserEvent::CAMPAIGN_START_SCENARIO:
-		{
-			CSH->campaignServerRestartLock.set(true);
-			CSH->endGameplay();
-			auto ourCampaign = std::shared_ptr<CampaignState>(reinterpret_cast<CampaignState *>(user.data1));
-			auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
-			auto finisher = [=]()
-			{
-				if(!ourCampaign->isCampaignFinished())
-				{
-					GH.windows().pushWindow(CMM);
-					GH.windows().pushWindow(CMM->menu);
-					CMM->openCampaignLobby(ourCampaign);
-				}
-			};
-			if(epilogue.hasPrologEpilog)
-			{
-				GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
-			}
-			else
-			{
-				CSH->campaignServerRestartLock.waitUntil(false);
-				finisher();
-			}
-		}
-		break;
-		case EUserEvent::RETURN_TO_MENU_LOAD:
-			CSH->endGameplay();
-			GH.defActionsDef = 63;
-			CMM->menu->switchToTab("load");
-			break;
-		case EUserEvent::FULLSCREEN_TOGGLED:
-		{
-			boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-			GH.onScreenResize();
-			break;
-		}
-		case EUserEvent::FAKE_MOUSE_MOVE:
-			GH.events().dispatchMouseMoved(Point(0, 0), GH.getCursorPosition());
-			break;
-		default:
-			logGlobal->error("Unknown user event. Code %d", user.code);
-			break;
-	}
-}

+ 0 - 20
client/eventsSDL/UserEventHandler.h

@@ -1,20 +0,0 @@
-/*
-* EventHandlerSDLUser.h, part of VCMI engine
-*
-* Authors: listed in file AUTHORS in main folder
-*
-* License: GNU General Public License v2.0 or later
-* Full text of license available in license.txt file, in main folder
-*
-*/
-
-#pragma once
-
-struct SDL_UserEvent;
-
-/// Class for handling events of type SDL_UserEvent
-class UserEventHandler
-{
-public:
-	void handleUserEvent(const SDL_UserEvent & current);
-};

+ 5 - 8
client/gui/CGuiHandler.cpp

@@ -87,7 +87,9 @@ void CGuiHandler::handleEvents()
 
 void CGuiHandler::fakeMouseMove()
 {
-	pushUserEvent(EUserEvent::FAKE_MOUSE_MOVE);
+	dispatchMainThread([](){
+		GH.events().dispatchMouseMoved(Point(0, 0), GH.getCursorPosition());
+	});
 }
 
 void CGuiHandler::startTextInput(const Rect & whereInput)
@@ -203,14 +205,9 @@ bool CGuiHandler::amIGuiThread()
 	return inGuiThread.get() && *inGuiThread;
 }
 
-void CGuiHandler::pushUserEvent(EUserEvent usercode)
+void CGuiHandler::dispatchMainThread(const std::function<void()> & functor)
 {
-	inputHandlerInstance->pushUserEvent(usercode, nullptr);
-}
-
-void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
-{
-	inputHandlerInstance->pushUserEvent(usercode, userdata);
+	inputHandlerInstance->dispatchMainThread(functor);
 }
 
 IScreenHandler & CGuiHandler::screenHandler()

+ 3 - 14
client/gui/CGuiHandler.h

@@ -27,18 +27,6 @@ class WindowHandler;
 class EventDispatcher;
 class InputHandler;
 
-// TODO: event handling need refactoring. Perhaps convert into delayed function call?
-enum class EUserEvent
-{
-	RETURN_TO_MAIN_MENU,
-	RESTART_GAME,
-	RETURN_TO_MENU_LOAD,
-	FULLSCREEN_TOGGLED,
-	CAMPAIGN_START_SCENARIO,
-	FORCE_QUIT,
-	FAKE_MOUSE_MOVE,
-};
-
 // Handles GUI logic and drawing
 class CGuiHandler
 {
@@ -108,8 +96,9 @@ public:
 	void drawFPSCounter(); // draws the FPS to the upper left corner of the screen
 
 	bool amIGuiThread();
-	void pushUserEvent(EUserEvent usercode);
-	void pushUserEvent(EUserEvent usercode, void * userdata);
+
+	/// Calls provided functor in main thread on next execution frame
+	void dispatchMainThread(const std::function<void()> & functor);
 
 	CondSh<bool> * terminate_cond; // confirm termination
 };

+ 13 - 4
client/lobby/CBonusSelection.cpp

@@ -427,10 +427,19 @@ void CBonusSelection::startMap()
 void CBonusSelection::restartMap()
 {
 	close();
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
-	{
-		GH.pushUserEvent(EUserEvent::RESTART_GAME);
-	}, 0);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[67],
+		[=]()
+		{
+			GH.dispatchMainThread(
+				[]()
+				{
+					CSH->sendRestartGame();
+				}
+			);
+		},
+		0
+	);
 }
 
 void CBonusSelection::increaseDifficulty()

+ 11 - 5
client/mainmenu/CMainMenu.cpp

@@ -68,7 +68,10 @@ ISelectionScreenInfo * SEL;
 
 static void do_quit()
 {
-	GH.pushUserEvent(EUserEvent::FORCE_QUIT);
+	GH.dispatchMainThread([]()
+	{
+		handleQuit(false);
+	});
 }
 
 CMenuScreen::CMenuScreen(const JsonNode & configNode)
@@ -562,10 +565,13 @@ void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port)
 	else
 		CSH->justConnectToServer(addr, port);
 
-	if(GH.windows().isTopWindow(this))
-	{
-		close();
-	}
+	// async call to prevent thread race
+	GH.dispatchMainThread([this](){
+		if(GH.windows().isTopWindow(this))
+		{
+			close();
+		}
+	});
 }
 
 CLoadingScreen::CLoadingScreen(std::function<void()> loader)

+ 38 - 9
client/windows/settings/SettingsMainWindow.cpp

@@ -16,6 +16,7 @@
 #include "GeneralOptionsTab.h"
 #include "OtherOptionsTab.h"
 
+#include "CMT.h"
 #include "CGameInfo.h"
 #include "CGeneralTextHandler.h"
 #include "CPlayerInterface.h"
@@ -112,7 +113,18 @@ void SettingsMainWindow::close()
 
 void SettingsMainWindow::quitGameButtonCallback()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(EUserEvent::FORCE_QUIT); }, 0);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[578],
+		[this]()
+		{
+			close();
+			GH.dispatchMainThread( []()
+			{
+				handleQuit(false);
+			});
+		},
+		0
+	);
 }
 
 void SettingsMainWindow::backButtonCallback()
@@ -122,7 +134,20 @@ void SettingsMainWindow::backButtonCallback()
 
 void SettingsMainWindow::mainMenuButtonCallback()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(EUserEvent::RETURN_TO_MAIN_MENU); }, 0);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[578],
+		[this]()
+		{
+			close();
+			GH.dispatchMainThread( []()
+			{
+				CSH->endGameplay();
+				GH.defActionsDef = 63;
+				CMM->menu->switchToTab("main");
+			});
+		},
+		0
+	);
 }
 
 void SettingsMainWindow::loadGameButtonCallback()
@@ -139,13 +164,17 @@ void SettingsMainWindow::saveGameButtonCallback()
 
 void SettingsMainWindow::restartGameButtonCallback()
 {
-	LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this](){ closeAndPushEvent(EUserEvent::RESTART_GAME); }, 0);
-}
-
-void SettingsMainWindow::closeAndPushEvent(EUserEvent code)
-{
-	close();
-	GH.pushUserEvent(code);
+	LOCPLINT->showYesNoDialog(
+		CGI->generaltexth->allTexts[67],
+		[this]()
+		{
+			close();
+			GH.dispatchMainThread([](){
+				CSH->sendRestartGame();
+			});
+		},
+		0
+	);
 }
 
 void SettingsMainWindow::showAll(Canvas & to)

+ 0 - 1
client/windows/settings/SettingsMainWindow.h

@@ -30,7 +30,6 @@ private:
 	void openTab(size_t index);
 
 	void close(); //TODO: copypaste of WindowBase::close(), consider changing Windowbase to IWindowbase with default close() implementation and changing WindowBase inheritance to CIntObject + IWindowBase
-	void closeAndPushEvent(EUserEvent code);
 
 	void loadGameButtonCallback();
 	void saveGameButtonCallback();