diff --git a/AI/Nullkiller/AIGateway.h b/AI/Nullkiller/AIGateway.h index 00f1b7c2e..b04e76004 100644 --- a/AI/Nullkiller/AIGateway.h +++ b/AI/Nullkiller/AIGateway.h @@ -21,7 +21,6 @@ #include "../../lib/CTownHandler.h" #include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/spells/CSpellHandler.h" -#include "../../lib/CondSh.h" #include "Pathfinding/AIPathfinder.h" #include "Engine/Nullkiller.h" diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index b5625c164..310e4be77 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -23,7 +23,6 @@ #include "../../lib/CTownHandler.h" #include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/spells/CSpellHandler.h" -#include "../../lib/CondSh.h" #include "Pathfinding/AIPathfinder.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index ee155cd8f..e4517e253 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -10,7 +10,7 @@ android { applicationId "is.xyz.vcmi" minSdk 19 targetSdk 33 - versionCode 1513 + versionCode 1515 versionName "1.5.1" setProperty("archivesBaseName", "vcmi") } diff --git a/client/CMT.cpp b/client/CMT.cpp index be8e262d5..fc6885136 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -442,6 +442,8 @@ static void mainLoop() [[noreturn]] static void quitApplication() { + CSH->endNetwork(); + if(!settings["session"]["headless"].Bool()) { if(CSH->client) @@ -450,6 +452,8 @@ static void mainLoop() GH.windows().clear(); } + vstd::clear_pointer(CSH); + CMM.reset(); if(!settings["session"]["headless"].Bool()) @@ -473,7 +477,6 @@ static void mainLoop() vstd::clear_pointer(graphics); } - vstd::clear_pointer(CSH); vstd::clear_pointer(VLC); // sometimes leads to a hang. TODO: investigate diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 0e8b0d562..744fa7a05 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -365,6 +365,7 @@ set(client_HEADERS Client.h ClientCommandManager.h ClientNetPackVisitors.h + ConditionalWait.h HeroMovementController.h GameChatHandler.h LobbyClientNetPackVisitors.h diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 858024fd7..eb18f3393 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -72,7 +72,6 @@ #include "../lib/CStopWatch.h" #include "../lib/CThreadHelper.h" #include "../lib/CTownHandler.h" -#include "../lib/CondSh.h" #include "../lib/GameConstants.h" #include "../lib/RoadHandler.h" #include "../lib/StartInfo.h" @@ -140,7 +139,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player): battleInt = nullptr; castleInt = nullptr; makingTurn = false; - showingDialog = new CondSh(false); + showingDialog = new ConditionalWait(); cingconsole = new CInGameConsole(); firstCall = 1; //if loading will be overwritten in serialize autosaveCount = 0; @@ -1005,7 +1004,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this) { CCS->soundh->playSound(static_cast(soundID)); - showingDialog->set(true); + showingDialog->setBusy(); movementController->requestMovementAbort(); // interrupt movement to show dialog GH.windows().pushWindow(temp); } @@ -1028,7 +1027,7 @@ void CPlayerInterface::showInfoDialogAndWait(std::vector & components void CPlayerInterface::showYesNoDialog(const std::string &text, CFunctionList onYes, CFunctionList onNo, const std::vector> & components) { movementController->requestMovementAbort(); - LOCPLINT->showingDialog->setn(true); + LOCPLINT->showingDialog->setBusy(); CInfoWindow::showYesNoDialog(text, components, onYes, onNo, playerID); } @@ -1217,7 +1216,7 @@ void CPlayerInterface::loadGame( BinaryDeserializer & h ) void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path ) { assert(h); - assert(!showingDialog->get()); + assert(!showingDialog->isBusy()); assert(dialogs.empty()); LOG_TRACE(logGlobal); @@ -1227,7 +1226,7 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path ) return; //can't find hero //It shouldn't be possible to move hero with open dialog (or dialog waiting in bg) - if (showingDialog->get() || !dialogs.empty()) + if (showingDialog->isBusy() || !dialogs.empty()) return; if (localState->isHeroSleeping(h)) @@ -1395,9 +1394,7 @@ void CPlayerInterface::waitWhileDialog() } auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); - boost::unique_lock un(showingDialog->mx); - while(showingDialog->data) - showingDialog->cond.wait(un); + showingDialog->waitWhileBusy(); } void CPlayerInterface::showShipyardDialog(const IShipyard *obj) @@ -1502,9 +1499,9 @@ void CPlayerInterface::update() return; //if there are any waiting dialogs, show them - if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get()) + if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->isBusy()) { - showingDialog->set(true); + showingDialog->setBusy(); GH.windows().pushWindow(dialogs.front()); dialogs.pop_front(); } @@ -1516,6 +1513,11 @@ void CPlayerInterface::update() GH.windows().simpleRedraw(); } +void CPlayerInterface::endNetwork() +{ + showingDialog->requestTermination(); +} + int CPlayerInterface::getLastIndex( std::string namePrefix) { using namespace boost::filesystem; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index e07c5bcb0..fd03f76ed 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -25,7 +25,7 @@ struct CGPath; class CCreatureSet; class CGObjectInstance; struct UpgradeInfo; -template struct CondSh; +class ConditionalWait; struct CPathsInfo; VCMI_LIB_NAMESPACE_END @@ -74,7 +74,7 @@ public: // TODO: make private std::unique_ptr localState; //minor interfaces - CondSh *showingDialog; //indicates if dialog box is displayed + ConditionalWait * showingDialog; //indicates if dialog box is displayed bool makingTurn; //if player is already making his turn @@ -202,6 +202,7 @@ public: // public interface for use by client via LOCPLINT access void proposeLoadingGame(); void performAutosave(); void gamePause(bool pause); + void endNetwork(); ///returns true if all events are processed internally bool capturedAllEvents(); diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 9bfeaae32..2ee59e171 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -29,6 +29,7 @@ #include "../lib/CConfigHandler.h" #include "../lib/CGeneralTextHandler.h" +#include "ConditionalWait.h" #include "../lib/CThreadHelper.h" #include "../lib/StartInfo.h" #include "../lib/TurnTimerInfo.h" @@ -131,6 +132,17 @@ CServerHandler::~CServerHandler() } } +void CServerHandler::endNetwork() +{ + if (client) + client->endNetwork(); + networkHandler->stop(); + { + auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); + threadNetwork.join(); + } +} + CServerHandler::CServerHandler() : networkHandler(INetworkHandler::createHandler()) , lobbyClient(std::make_unique()) @@ -158,7 +170,14 @@ void CServerHandler::threadRunNetwork() { logGlobal->info("Starting network thread"); setThreadName("runNetwork"); - networkHandler->run(); + try { + networkHandler->run(); + } + catch (const TerminationRequestedException & e) + { + logGlobal->info("Terminating network thread"); + return; + } logGlobal->info("Ending network thread"); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index cbf87eaee..85825b2e8 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -13,7 +13,6 @@ #include "../lib/network/NetworkInterface.h" #include "../lib/StartInfo.h" -#include "../lib/CondSh.h" VCMI_LIB_NAMESPACE_BEGIN @@ -208,6 +207,7 @@ public: void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr); void showHighScoresAndEndGameplay(PlayerColor player, bool victory); + void endNetwork(); void endGameplay(); void restartGameplay(); void startCampaignScenario(HighScoreParameter param, std::shared_ptr cs = {}); diff --git a/client/Client.cpp b/client/Client.cpp index 05d495004..9312e228b 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -346,6 +346,22 @@ void CClient::save(const std::string & fname) sendRequest(&save_game, PlayerColor::NEUTRAL); } +void CClient::endNetwork() +{ + if (CGI->mh) + CGI->mh->endNetwork(); + + if (CPlayerInterface::battleInt) + CPlayerInterface::battleInt->endNetwork(); + + for(auto & i : playerint) + { + auto interface = std::dynamic_pointer_cast(i.second); + if (interface) + interface->endNetwork(); + } +} + void CClient::endGame() { #if SCRIPTING_ENABLED diff --git a/client/Client.h b/client/Client.h index 391da0729..a04b9aa71 100644 --- a/client/Client.h +++ b/client/Client.h @@ -135,6 +135,7 @@ public: void serialize(BinaryDeserializer & h); void save(const std::string & fname); + void endNetwork(); void endGame(); void initMapHandler(); diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 39a5aabdf..5bba85706 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -179,11 +179,6 @@ void ClientCommandManager::handleRedrawCommand() GH.windows().totalRedraw(); } -void ClientCommandManager::handleNotDialogCommand() -{ - LOCPLINT->showingDialog->setn(false); -} - void ClientCommandManager::handleTranslateGameCommand() { std::map> textsByMod; @@ -584,9 +579,6 @@ void ClientCommandManager::processCommand(const std::string & message, bool call else if(commandName == "redraw") handleRedrawCommand(); - else if(commandName == "not dialog") - handleNotDialogCommand(); - else if(message=="translate" || message=="translate game") handleTranslateGameCommand(); diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index b146f56fc..56bd4619e 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -45,9 +45,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a // Redraw the current screen void handleRedrawCommand(); - // Set the state indicating if dialog box is active to "no" - void handleNotDialogCommand(); - // Extracts all translateable game texts into Translation directory, separating files on per-mod basis void handleTranslateGameCommand(); diff --git a/client/ConditionalWait.h b/client/ConditionalWait.h new file mode 100644 index 000000000..0df2de789 --- /dev/null +++ b/client/ConditionalWait.h @@ -0,0 +1,77 @@ +/* + * ConditionalWait.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 + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +class TerminationRequestedException : public std::exception +{ +public: + using exception::exception; + + const char* what() const noexcept override + { + return "Thread termination requested"; + } +}; + +class ConditionalWait +{ + bool isBusyValue = false; + bool isTerminating = false; + std::condition_variable cond; + std::mutex mx; + + void set(bool value) + { + boost::unique_lock lock(mx); + isBusyValue = value; + } + +public: + ConditionalWait() = default; + + void setBusy() + { + set(true); + } + + void setFree() + { + set(false); + cond.notify_all(); + } + + void requestTermination() + { + isTerminating = true; + setFree(); + } + + bool isBusy() + { + std::unique_lock lock(mx); + return isBusyValue; + } + + void waitWhileBusy() + { + std::unique_lock un(mx); + while(isBusyValue) + cond.wait(un); + + if (isTerminating) + throw TerminationRequestedException(); + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/client/HeroMovementController.cpp b/client/HeroMovementController.cpp index d69075661..8c3ebe349 100644 --- a/client/HeroMovementController.cpp +++ b/client/HeroMovementController.cpp @@ -22,7 +22,7 @@ #include "../CCallback.h" -#include "../lib/CondSh.h" +#include "ConditionalWait.h" #include "../lib/pathfinder/CGPathNode.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/networkPacks/PacksForClient.h" @@ -237,7 +237,7 @@ void HeroMovementController::onMoveHeroApplied() assert(currentlyMovingHero); const auto * hero = currentlyMovingHero; - bool canMove = LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).nextNode().turns == 0 && !LOCPLINT->showingDialog->get(); + bool canMove = LOCPLINT->localState->hasPath(hero) && LOCPLINT->localState->getPath(hero).nextNode().turns == 0 && !LOCPLINT->showingDialog->isBusy(); bool wantStop = stoppingMovement; bool canStop = !canMove || canHeroStopAtNode(LOCPLINT->localState->getPath(hero).currNode()); diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index faa8f9bf1..3a9067c0d 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -38,7 +38,6 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/CHeroHandler.h" -#include "../../lib/CondSh.h" #include "../../lib/gameState/InfoAboutArmy.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForClientBattle.h" @@ -95,7 +94,7 @@ BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet * obstacleController.reset(new BattleObstacleController(*this)); adventureInt->onAudioPaused(); - ongoingAnimationsState.set(true); + ongoingAnimationsState.setBusy(); GH.windows().pushWindow(windowObject); windowObject->blockUI(true); @@ -744,6 +743,11 @@ void BattleInterface::castThisSpell(SpellID spellID) actionsController->castThisSpell(spellID); } +void BattleInterface::endNetwork() +{ + ongoingAnimationsState.requestTermination(); +} + void BattleInterface::executeStagedAnimations() { EAnimationEvents earliestStage = EAnimationEvents::COUNT; @@ -775,19 +779,19 @@ void BattleInterface::executeAnimationStage(EAnimationEvents event) void BattleInterface::onAnimationsStarted() { - ongoingAnimationsState.setn(true); + ongoingAnimationsState.setBusy(); } void BattleInterface::onAnimationsFinished() { - ongoingAnimationsState.setn(false); + ongoingAnimationsState.setFree(); } void BattleInterface::waitForAnimations() { { auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); - ongoingAnimationsState.waitUntil(false); + ongoingAnimationsState.waitWhileBusy(); } assert(!hasAnimations()); @@ -802,7 +806,7 @@ void BattleInterface::waitForAnimations() bool BattleInterface::hasAnimations() { - return ongoingAnimationsState.get(); + return ongoingAnimationsState.isBusy(); } void BattleInterface::checkForAnimations() diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index a83f50afb..621cf1e84 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -12,7 +12,7 @@ #include "BattleConstants.h" #include "../gui/CIntObject.h" #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation -#include "../../lib/CondSh.h" +#include "../ConditionalWait.h" VCMI_LIB_NAMESPACE_BEGIN @@ -99,7 +99,7 @@ class BattleInterface }; /// Conditional variables that are set depending on ongoing animations on the battlefield - CondSh ongoingAnimationsState; + ConditionalWait ongoingAnimationsState; /// List of events that are waiting to be triggered std::vector awaitingEvents; @@ -186,6 +186,7 @@ public: void setBattleQueueVisibility(bool visible); void setStickyHeroWindowsVisibility(bool visible); + void endNetwork(); void executeStagedAnimations(); void executeAnimationStage( EAnimationEvents event); void onAnimationsStarted(); diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 27de34ff3..ab547e8e8 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -52,7 +52,6 @@ #include "../../lib/CTownHandler.h" #include "../../lib/CHeroHandler.h" #include "../../lib/StartInfo.h" -#include "../../lib/CondSh.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/TextOperations.h" @@ -778,7 +777,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface void BattleResultWindow::activate() { - owner.showingDialog->set(true); + owner.showingDialog->setBusy(); CIntObject::activate(); } @@ -871,7 +870,7 @@ void BattleResultWindow::buttonPressed(int button) //Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle, //so we can be sure that there is no dialogs left on GUI stack. - intTmp.showingDialog->setn(false); + intTmp.showingDialog->setFree(); CCS->videoh->close(); } diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 5528b0bb4..230914047 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -39,7 +39,6 @@ #include "../../lib/battle/BattleHex.h" #include "../../lib/CRandomGenerator.h" #include "../../lib/CStack.h" -#include "../../lib/CondSh.h" #include "../../lib/TextOperations.h" static void onAnimationFinished(const CStack *stack, std::weak_ptr anim) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index a0d513969..9de619179 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -9,7 +9,6 @@ */ #include "StdInc.h" #include "CGuiHandler.h" -#include "../lib/CondSh.h" #include "CIntObject.h" #include "CursorHandler.h" diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index 26160c9a0..c010336e4 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -10,7 +10,6 @@ #pragma once VCMI_LIB_NAMESPACE_BEGIN -template struct CondSh; class Point; class Rect; VCMI_LIB_NAMESPACE_END diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 12b44298f..377eda072 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -58,7 +58,6 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/GameConstants.h" #include "../../lib/CRandomGenerator.h" -#include "../../lib/CondSh.h" std::shared_ptr CMM; ISelectionScreenInfo * SEL; @@ -275,7 +274,8 @@ CMainMenuConfig::CMainMenuConfig() : campaignSets(JsonPath::builtin("config/campaignSets.json")) , config(JsonPath::builtin("config/mainmenu.json")) { - + if (config["game-select"].Vector().empty()) + handleFatalError("Main menu config is invalid or corrupted. Please disable any mods or reinstall VCMI", false); } const CMainMenuConfig & CMainMenuConfig::get() diff --git a/client/mapView/IMapRendererObserver.h b/client/mapView/IMapRendererObserver.h index d1df5d9f1..47b20c2b9 100644 --- a/client/mapView/IMapRendererObserver.h +++ b/client/mapView/IMapRendererObserver.h @@ -25,6 +25,8 @@ public: virtual ~IMapObjectObserver(); virtual bool hasOngoingAnimations() = 0; + virtual void waitForOngoingAnimations(){}; + virtual void endNetwork(){}; /// Plays fade-in animation and adds object to map virtual void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index 7b20d1c71..6cc8ffd08 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -25,6 +25,7 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/StartInfo.h" +#include "../../lib/UnlockGuard.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/pathfinder/CGPathNode.h" @@ -346,6 +347,7 @@ bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 & void MapViewController::fadeOutObject(const CGObjectInstance * obj) { + animationWait.setBusy(); logGlobal->debug("Starting fade out animation"); fadingOutContext = std::make_shared(*state); fadingOutContext->animationTime = adventureContext->animationTime; @@ -366,6 +368,7 @@ void MapViewController::fadeOutObject(const CGObjectInstance * obj) void MapViewController::fadeInObject(const CGObjectInstance * obj) { + animationWait.setBusy(); logGlobal->debug("Starting fade in animation"); fadingInContext = std::make_shared(*state); fadingInContext->animationTime = adventureContext->animationTime; @@ -505,6 +508,7 @@ void MapViewController::onAfterHeroTeleported(const CGHeroInstance * obj, const if(isEventVisible(obj, from, dest)) { + animationWait.setBusy(); logGlobal->debug("Starting teleport animation"); teleportContext = std::make_shared(*state); teleportContext->animationTime = adventureContext->animationTime; @@ -540,6 +544,7 @@ void MapViewController::onHeroMoved(const CGHeroInstance * obj, const int3 & fro if(movementTime > 1) { + animationWait.setBusy(); logGlobal->debug("Starting movement animation"); movementContext = std::make_shared(*state); movementContext->animationTime = adventureContext->animationTime; @@ -577,6 +582,17 @@ bool MapViewController::hasOngoingAnimations() return false; } +void MapViewController::waitForOngoingAnimations() +{ + auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); + animationWait.waitWhileBusy(); +} + +void MapViewController::endNetwork() +{ + animationWait.requestTermination(); +} + void MapViewController::activateAdventureContext(uint32_t animationTime) { resetContext(); @@ -642,6 +658,7 @@ void MapViewController::resetContext() worldViewContext.reset(); spellViewContext.reset(); puzzleMapContext.reset(); + animationWait.setFree(); } void MapViewController::setTerrainVisibility(bool showAllTerrain) diff --git a/client/mapView/MapViewController.h b/client/mapView/MapViewController.h index 151ef6a93..41e92c92f 100644 --- a/client/mapView/MapViewController.h +++ b/client/mapView/MapViewController.h @@ -10,6 +10,7 @@ #pragma once #include "IMapRendererObserver.h" +#include "../ConditionalWait.h" #include "../../lib/Point.h" VCMI_LIB_NAMESPACE_BEGIN @@ -34,6 +35,8 @@ class MapRendererPuzzleMapContext; /// such as its position and any animations class MapViewController : public IMapObjectObserver { + ConditionalWait animationWait; + std::shared_ptr context; std::shared_ptr state; std::shared_ptr model; @@ -68,6 +71,9 @@ private: // IMapObjectObserver impl bool hasOngoingAnimations() override; + void waitForOngoingAnimations() override; + void endNetwork() override; + void onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator) override; void onObjectFadeOut(const CGObjectInstance * obj, const PlayerColor & initiator) override; void onObjectInstantAdd(const CGObjectInstance * obj, const PlayerColor & initiator) override; diff --git a/client/mapView/mapHandler.cpp b/client/mapView/mapHandler.cpp index aac3232e1..f535fa61d 100644 --- a/client/mapView/mapHandler.cpp +++ b/client/mapView/mapHandler.cpp @@ -19,7 +19,6 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/TerrainHandler.h" -#include "../../lib/UnlockGuard.h" #include "../../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/ObjectTemplate.h" @@ -36,13 +35,19 @@ bool CMapHandler::hasOngoingAnimations() void CMapHandler::waitForOngoingAnimations() { - while(CGI->mh->hasOngoingAnimations()) + for(auto * observer : observers) { - auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); - boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); + if (observer->hasOngoingAnimations()) + observer->waitForOngoingAnimations(); } } +void CMapHandler::endNetwork() +{ + for(auto * observer : observers) + observer->endNetwork(); +} + std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) const { const TerrainTile & t = map->getTile(pos); diff --git a/client/mapView/mapHandler.h b/client/mapView/mapHandler.h index becfa51a6..a721ec75a 100644 --- a/client/mapView/mapHandler.h +++ b/client/mapView/mapHandler.h @@ -71,6 +71,7 @@ public: /// blocking wait until all ongoing animatins are over void waitForOngoingAnimations(); + void endNetwork(); static bool compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b); }; diff --git a/client/windows/CTutorialWindow.cpp b/client/windows/CTutorialWindow.cpp index 72f945547..2fc73d16f 100644 --- a/client/windows/CTutorialWindow.cpp +++ b/client/windows/CTutorialWindow.cpp @@ -12,7 +12,7 @@ #include "../eventsSDL/InputHandler.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CondSh.h" +#include "../ConditionalWait.h" #include "../../lib/CGeneralTextHandler.h" #include "../CPlayerInterface.h" #include "../CGameInfo.h" @@ -67,7 +67,7 @@ void CTutorialWindow::openWindowFirstTime(const TutorialMode & m) if(GH.input().hasTouchInputDevice() && !persistentStorage["gui"]["tutorialCompleted" + std::to_string(m)].Bool()) { if(LOCPLINT) - LOCPLINT->showingDialog->set(true); + LOCPLINT->showingDialog->setBusy(); GH.windows().pushWindow(std::make_shared(m)); Settings s = persistentStorage.write["gui"]["tutorialCompleted" + std::to_string(m)]; @@ -78,7 +78,7 @@ void CTutorialWindow::openWindowFirstTime(const TutorialMode & m) void CTutorialWindow::exit() { if(LOCPLINT) - LOCPLINT->showingDialog->setn(false); + LOCPLINT->showingDialog->setFree(); close(); } diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 38d6677b9..ed8a6eb16 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -56,7 +56,7 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/GameSettings.h" -#include "../lib/CondSh.h" +#include "ConditionalWait.h" #include "../lib/CSkillHandler.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/TextOperations.h" @@ -402,7 +402,7 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - LOCPLINT->showingDialog->setn(true); + LOCPLINT->showingDialog->setBusy(); if(!skills.empty()) { @@ -445,7 +445,7 @@ CLevelWindow::~CLevelWindow() if (box && box->selectedIndex() != -1) cb(box->selectedIndex()); - LOCPLINT->showingDialog->setn(false); + LOCPLINT->showingDialog->setFree(); } CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::function & onWindowClosed) diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 6f5fa1ad2..b124c547b 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -29,7 +29,7 @@ #include "../../CCallback.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CondSh.h" +#include "../ConditionalWait.h" #include "../../lib/gameState/InfoAboutArmy.h" #include "../../lib/mapObjects/CGCreature.h" #include "../../lib/mapObjects/CGHeroInstance.h" @@ -140,7 +140,7 @@ void CInfoWindow::close() WindowBase::close(); if(LOCPLINT) - LOCPLINT->showingDialog->setn(false); + LOCPLINT->showingDialog->setFree(); } void CInfoWindow::showAll(Canvas & to) @@ -158,7 +158,7 @@ void CInfoWindow::showInfoDialog(const std::string & text, const TCompsInfo & co void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList & onYes, const CFunctionList & onNo, PlayerColor player) { - assert(!LOCPLINT || LOCPLINT->showingDialog->get()); + assert(!LOCPLINT || LOCPLINT->showingDialog->isBusy()); std::vector>> pom; pom.emplace_back(AnimationPath::builtin("IOKAY.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); diff --git a/docs/players/Cheat_Codes.md b/docs/players/Cheat_Codes.md index 987e8c45f..0b3901819 100644 --- a/docs/players/Cheat_Codes.md +++ b/docs/players/Cheat_Codes.md @@ -142,5 +142,4 @@ Below a list of supported commands, with their arguments wrapped in `<>` `activate <0/1/2>` - activate game windows (no current use, apparently broken long ago) `redraw` - force full graphical redraw `screen` - show value of screenBuf variable, which prints "screen" when adventure map has current focus, "screen2" otherwise, and dumps values of both screen surfaces to .bmp files -`not dialog` - set the state indicating if dialog box is active to "no" `tell hs ` - write what artifact is present on artifact slot with specified ID for hero with specified ID. (must be called during gameplay) diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 00fba6ce8..b2ae0d824 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -648,7 +648,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json JsonNode advMapFile = node["graphics"]["map"]; JsonNode advMapMask = node["graphics"]["mapMask"]; - VLC->identifiers()->requestIdentifier(scope, "object", "monster", [=](si32 index) + VLC->identifiers()->requestIdentifier(scope, "object", "monster", [cre, scope, advMapFile, advMapMask](si32 index) { JsonNode conf; conf.setModScope(scope); @@ -669,7 +669,12 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower) if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->getTemplates().empty()) + { + assert(cre->special); + if (!cre->special) + logMod->error("Creature %s does not have valid map object but is not marked as special!", cre->getJsonKey()); VLC->objtypeh->removeSubObject(Obj::MONSTER, cre->getId().num); + } }); return cre; diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index d2af457cf..cea2b392f 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -23,6 +23,8 @@ VCMI_LIB_NAMESPACE_BEGIN +std::recursive_mutex TextLocalizationContainer::globalTextMutex; + /// Detects language and encoding of H3 text files based on matching against pregenerated footprints of H3 file void CGeneralTextHandler::detectInstallParameters() { @@ -251,6 +253,8 @@ bool CLegacyConfigParser::endLine() void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized) { + std::lock_guard globalLock(globalTextMutex); + assert(!modContext.empty()); assert(!language.empty()); @@ -265,12 +269,16 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container) { + std::lock_guard globalLock(globalTextMutex); + assert(!vstd::contains(subContainers, &container)); subContainers.push_back(&container); } void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container) { + std::lock_guard globalLock(globalTextMutex); + assert(vstd::contains(subContainers, &container)); subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end()); @@ -278,6 +286,8 @@ void TextLocalizationContainer::removeSubContainer(const TextLocalizationContain const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const { + std::lock_guard globalLock(globalTextMutex); + if(stringsLocalizations.count(identifier.get()) == 0) { for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter) @@ -297,6 +307,8 @@ const std::string & TextLocalizationContainer::deserialize(const TextIdentifier void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language) { + std::lock_guard globalLock(globalTextMutex); + assert(!modContext.empty()); assert(!Languages::getLanguageOptions(language).identifier.empty()); assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string @@ -327,6 +339,8 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const { + std::lock_guard globalLock(globalTextMutex); + bool allPresent = true; for(const auto & string : stringsLocalizations) @@ -384,11 +398,15 @@ void TextLocalizationContainer::loadTranslationOverrides(const std::string & lan bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const { + std::lock_guard globalLock(globalTextMutex); + return stringsLocalizations.count(UID.get()); } void TextLocalizationContainer::exportAllTexts(std::map> & storage) const { + std::lock_guard globalLock(globalTextMutex); + for (auto const & subContainer : subContainers) subContainer->exportAllTexts(storage); @@ -418,6 +436,8 @@ std::string TextLocalizationContainer::getModLanguage(const std::string & modCon void TextLocalizationContainer::jsonSerialize(JsonNode & dest) const { + std::lock_guard globalLock(globalTextMutex); + for(auto & s : stringsLocalizations) { dest.Struct()[s.first].String() = s.second.baseValue; @@ -692,6 +712,7 @@ std::string CGeneralTextHandler::getInstalledEncoding() std::vector CGeneralTextHandler::findStringsWithPrefix(const std::string & prefix) { + std::lock_guard globalLock(globalTextMutex); std::vector result; for(const auto & entry : stringsLocalizations) diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 3608b5661..d94385f7e 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -117,6 +117,8 @@ public: class DLL_LINKAGE TextLocalizationContainer { protected: + static std::recursive_mutex globalTextMutex; + struct StringState { /// Human-readable string that was added on registration @@ -153,6 +155,9 @@ protected: std::string getModLanguage(const std::string & modContext); + // returns true if identifier with such name was registered, even if not translated to current language + bool identifierExists(const TextIdentifier & UID) const; + public: /// validates translation of specified language for specified mod /// returns true if localization is valid and complete @@ -163,9 +168,6 @@ public: /// Any entries loaded by this will have priority over texts registered normally void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file); - // returns true if identifier with such name was registered, even if not translated to current language - bool identifierExists(const TextIdentifier & UID) const; - /// add selected string to internal storage void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language); @@ -196,6 +198,8 @@ public: template void serialize(Handler & h) { + std::lock_guard globalLock(globalTextMutex); + std::string key; auto sz = stringsLocalizations.size(); h & sz; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b00c41afe..57561df24 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -649,7 +649,6 @@ set(lib_MAIN_HEADERS CGameInterface.h CGeneralTextHandler.h CHeroHandler.h - CondSh.h ConstTransitivePtr.h Color.h CPlayerState.h diff --git a/lib/CondSh.h b/lib/CondSh.h deleted file mode 100644 index cc3540431..000000000 --- a/lib/CondSh.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * CondSh.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 - -VCMI_LIB_NAMESPACE_BEGIN - -/// Used for multithreading, wraps boost functions -template struct CondSh -{ - T data; - boost::condition_variable cond; - boost::mutex mx; - - CondSh() : data(T()) {} - - CondSh(T t) : data(t) {} - - // set data - void set(T t) - { - boost::unique_lock lock(mx); - data = t; - } - - // set data and notify - void setn(T t) - { - set(t); - cond.notify_all(); - }; - - // get stored value - T get() - { - boost::unique_lock lock(mx); - return data; - } - - // waits until data is set to false - void waitWhileTrue() - { - boost::unique_lock un(mx); - while(data) - cond.wait(un); - } - - // waits while data is set to arg - void waitWhile(const T & t) - { - boost::unique_lock un(mx); - while(data == t) - cond.wait(un); - } - - // waits until data is set to arg - void waitUntil(const T & t) - { - boost::unique_lock un(mx); - while(data != t) - cond.wait(un); - } -}; - -VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index f7593e75c..5aa88b312 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -117,7 +117,9 @@ void NetworkConnection::onPacketReceived(const boost::system::error_code & ec, u if (readBuffer.size() < expectedPacketSize) { - throw std::runtime_error("Failed to read packet! " + std::to_string(readBuffer.size()) + " bytes read, but " + std::to_string(expectedPacketSize) + " bytes expected!"); + // FIXME: figure out what causes this. This should not be possible without error set + std::string errorMessage = "Failed to read packet! " + std::to_string(readBuffer.size()) + " bytes read, but " + std::to_string(expectedPacketSize) + " bytes expected!"; + onError(errorMessage); } std::vector message(expectedPacketSize); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index f908bc434..c5eb7022f 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -986,7 +986,7 @@ void CVCMIServer::multiplayerWelcomeMessage() { int humanPlayer = 0; for (auto & pi : si->playerInfos) - if(gh->getPlayerState(pi.first)->isHuman()) + if(pi.second.isControlledByHuman()) humanPlayer++; if(humanPlayer < 2) // Singleplayer