diff --git a/client/CMT.cpp b/client/CMT.cpp index b48a04f74..83ef687e4 100644 --- a/client/CMT.cpp +++ b/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 lock(*CPlayerInterface::pim); + GH.onScreenResize(); + }); + }; + + resChanged(functor); + fsChanged(functor); inGuiThread.reset(new bool(true)); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 59c7eb10f..e41d22705 100644 --- a/client/CMakeLists.txt +++ b/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 diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 8d93acc92..595ade466 100644 --- a/client/CPlayerInterface.cpp +++ b/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() diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 76958778c..67e6321ea 100644 --- a/client/CServerHandler.cpp +++ b/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 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 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(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 { diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 88414af95..ea5a267c7 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/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() diff --git a/client/eventsSDL/InputHandler.cpp b/client/eventsSDL/InputHandler.cpp index aa28f5011..0ed239c2a 100644 --- a/client/eventsSDL/InputHandler.cpp +++ b/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()) , fingerHandler(std::make_unique()) , textHandler(std::make_unique()) - , userHandler(std::make_unique()) { } @@ -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 & functor) { + auto heapFunctor = new std::function(functor); + SDL_Event event; event.type = SDL_USEREVENT; - event.user.code = static_cast(usercode); - event.user.data1 = userdata; + event.user.code = 0; + event.user.data1 = static_cast (heapFunctor); + event.user.data2 = nullptr; SDL_PushEvent(&event); } +void InputHandler::handleUserEvent(const SDL_UserEvent & current) +{ + auto heapFunctor = static_cast*>(current.data1); + + (*heapFunctor)(); +} + const Point & InputHandler::getCursorPosition() const { return cursorPosition; diff --git a/client/eventsSDL/InputHandler.h b/client/eventsSDL/InputHandler.h index f3306f350..c620b3676 100644 --- a/client/eventsSDL/InputHandler.h +++ b/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 mouseHandler; std::unique_ptr keyboardHandler; std::unique_ptr fingerHandler; std::unique_ptr textHandler; - std::unique_ptr 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 & functor); /// Returns current position of cursor, in VCMI logical screen coordinates const Point & getCursorPosition() const; diff --git a/client/eventsSDL/UserEventHandler.cpp b/client/eventsSDL/UserEventHandler.cpp deleted file mode 100644 index 6d9cec47d..000000000 --- a/client/eventsSDL/UserEventHandler.cpp +++ /dev/null @@ -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 - -void UserEventHandler::handleUserEvent(const SDL_UserEvent & user) -{ - switch(static_cast(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(reinterpret_cast(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(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 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; - } -} diff --git a/client/eventsSDL/UserEventHandler.h b/client/eventsSDL/UserEventHandler.h deleted file mode 100644 index 837e0a6e3..000000000 --- a/client/eventsSDL/UserEventHandler.h +++ /dev/null @@ -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); -}; diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index dedbb4756..fdccf1517 100644 --- a/client/gui/CGuiHandler.cpp +++ b/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 & functor) { - inputHandlerInstance->pushUserEvent(usercode, nullptr); -} - -void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata) -{ - inputHandlerInstance->pushUserEvent(usercode, userdata); + inputHandlerInstance->dispatchMainThread(functor); } IScreenHandler & CGuiHandler::screenHandler() diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index 494e99414..b88aaadb0 100644 --- a/client/gui/CGuiHandler.h +++ b/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 & functor); CondSh * terminate_cond; // confirm termination }; diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 8ad35d89f..a37987c7c 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/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() diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 6409ac675..20f0789f9 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/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 loader) diff --git a/client/windows/settings/SettingsMainWindow.cpp b/client/windows/settings/SettingsMainWindow.cpp index 4899ce0af..64b9c8a7c 100644 --- a/client/windows/settings/SettingsMainWindow.cpp +++ b/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) diff --git a/client/windows/settings/SettingsMainWindow.h b/client/windows/settings/SettingsMainWindow.h index 1198ad11c..7b26921df 100644 --- a/client/windows/settings/SettingsMainWindow.h +++ b/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();