1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-26 22:57:00 +02:00

Fixed handling of match server crash

This commit is contained in:
Ivan Savenko 2024-02-04 19:56:04 +02:00
parent 7dee24edae
commit d4bedd8d8d
10 changed files with 69 additions and 81 deletions

View File

@ -63,7 +63,6 @@
"vcmi.mainMenu.serverClosing" : "Closing...", "vcmi.mainMenu.serverClosing" : "Closing...",
"vcmi.mainMenu.hostTCP" : "Host TCP/IP game", "vcmi.mainMenu.hostTCP" : "Host TCP/IP game",
"vcmi.mainMenu.joinTCP" : "Join TCP/IP game", "vcmi.mainMenu.joinTCP" : "Join TCP/IP game",
"vcmi.mainMenu.playerName" : "Player",
"vcmi.lobby.filepath" : "File path", "vcmi.lobby.filepath" : "File path",
"vcmi.lobby.creationDate" : "Creation date", "vcmi.lobby.creationDate" : "Creation date",
@ -94,10 +93,10 @@
"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s", "vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
"vcmi.server.errors.disconnected" : "{Network Error}\n\nConnection to game server has been lost!",
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
"vcmi.server.errors.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToEnable" : "{Following mods are required}",
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",
"vcmi.server.confirmReconnect" : "Do you want to reconnect to the last session?",
"vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n", "vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n",
"vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n", "vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n",
"vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!", "vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!",

View File

@ -1868,16 +1868,11 @@ void CPlayerInterface::proposeLoadingGame()
{ {
showYesNoDialog( showYesNoDialog(
CGI->generaltexth->allTexts[68], CGI->generaltexth->allTexts[68],
[]()
{
GH.dispatchMainThread(
[]() []()
{ {
CSH->endGameplay(); CSH->endGameplay();
GH.defActionsDef = 63; GH.defActionsDef = 63;
CMM->menu->switchToTab("load"); CMM->menu->switchToTab("load");
}
);
}, },
nullptr nullptr
); );

View File

@ -144,6 +144,7 @@ CServerHandler::CServerHandler()
, applier(std::make_unique<CApplier<CBaseForLobbyApply>>()) , applier(std::make_unique<CApplier<CBaseForLobbyApply>>())
, threadNetwork(&CServerHandler::threadRunNetwork, this) , threadNetwork(&CServerHandler::threadRunNetwork, this)
, state(EClientState::NONE) , state(EClientState::NONE)
, serverPort(0)
, campaignStateToSend(nullptr) , campaignStateToSend(nullptr)
, screenType(ESelectionScreen::unknown) , screenType(ESelectionScreen::unknown)
, serverMode(EServerMode::NONE) , serverMode(EServerMode::NONE)
@ -165,7 +166,7 @@ void CServerHandler::threadRunNetwork()
void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector<std::string> & names) void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector<std::string> & names)
{ {
hostClientId = -1; hostClientId = -1;
state = EClientState::NONE; setState(EClientState::NONE);
serverMode = newServerMode; serverMode = newServerMode;
mapToStart = nullptr; mapToStart = nullptr;
th = std::make_unique<CStopWatch>(); th = std::make_unique<CStopWatch>();
@ -263,7 +264,7 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby)
void CServerHandler::connectToServer(const std::string & addr, const ui16 port) void CServerHandler::connectToServer(const std::string & addr, const ui16 port)
{ {
logNetwork->info("Establishing connection to %s:%d...", addr, port); logNetwork->info("Establishing connection to %s:%d...", addr, port);
state = EClientState::CONNECTING; setState(EClientState::CONNECTING);
serverHostname = addr; serverHostname = addr;
serverPort = port; serverPort = port;
@ -281,7 +282,7 @@ void CServerHandler::connectToServer(const std::string & addr, const ui16 port)
void CServerHandler::onConnectionFailed(const std::string & errorMessage) void CServerHandler::onConnectionFailed(const std::string & errorMessage)
{ {
assert(state == EClientState::CONNECTING); assert(getState() == EClientState::CONNECTING);
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
if (isServerLocal()) if (isServerLocal())
@ -293,7 +294,7 @@ void CServerHandler::onConnectionFailed(const std::string & errorMessage)
else else
{ {
// remote server refused connection - show error message // remote server refused connection - show error message
state = EClientState::CONNECTION_FAILED; setState(EClientState::NONE);
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {}); CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {});
} }
} }
@ -302,7 +303,7 @@ void CServerHandler::onTimer()
{ {
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
if(state == EClientState::CONNECTION_CANCELLED) if(getState() == EClientState::CONNECTION_CANCELLED)
{ {
logNetwork->info("Connection aborted by player!"); logNetwork->info("Connection aborted by player!");
return; return;
@ -314,7 +315,7 @@ void CServerHandler::onTimer()
void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection) void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection)
{ {
assert(state == EClientState::CONNECTING); assert(getState() == EClientState::CONNECTING);
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
@ -361,6 +362,16 @@ ui8 CServerHandler::myFirstId() const
return clientFirstId(c->connectionID); return clientFirstId(c->connectionID);
} }
EClientState CServerHandler::getState() const
{
return state;
}
void CServerHandler::setState(EClientState newState)
{
state = newState;
}
bool CServerHandler::isServerLocal() const bool CServerHandler::isServerLocal() const
{ {
return threadRunLocalServer.joinable(); return threadRunLocalServer.joinable();
@ -418,13 +429,13 @@ void CServerHandler::sendClientConnecting() const
void CServerHandler::sendClientDisconnecting() void CServerHandler::sendClientDisconnecting()
{ {
// FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server // FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server
if(state == EClientState::DISCONNECTING) if(getState() == EClientState::DISCONNECTING)
{ {
assert(0); assert(0);
return; return;
} }
state = EClientState::DISCONNECTING; setState(EClientState::DISCONNECTING);
mapToStart = nullptr; mapToStart = nullptr;
LobbyClientDisconnected lcd; LobbyClientDisconnected lcd;
lcd.clientId = c->connectionID; lcd.clientId = c->connectionID;
@ -439,13 +450,14 @@ void CServerHandler::sendClientDisconnecting()
logNetwork->info("Sent leaving signal to the server"); logNetwork->info("Sent leaving signal to the server");
} }
sendLobbyPack(lcd); sendLobbyPack(lcd);
networkConnection->close();
networkConnection.reset(); networkConnection.reset();
c.reset(); c.reset();
} }
void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign) void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign)
{ {
state = EClientState::LOBBY_CAMPAIGN; setState(EClientState::LOBBY_CAMPAIGN);
LobbySetCampaign lsc; LobbySetCampaign lsc;
lsc.ourCampaign = newCampaign; lsc.ourCampaign = newCampaign;
sendLobbyPack(lsc); sendLobbyPack(lsc);
@ -453,7 +465,7 @@ void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign
void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
{ {
if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
return; return;
LobbySetCampaignMap lscm; LobbySetCampaignMap lscm;
@ -463,7 +475,7 @@ void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
void CServerHandler::setCampaignBonus(int bonusId) const void CServerHandler::setCampaignBonus(int bonusId) const
{ {
if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
return; return;
LobbySetCampaignBonus lscb; LobbySetCampaignBonus lscb;
@ -673,7 +685,7 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
} }
// After everything initialized we can accept CPackToClient netpacks // After everything initialized we can accept CPackToClient netpacks
c->enterGameplayConnectionMode(client->gameState()); c->enterGameplayConnectionMode(client->gameState());
state = EClientState::GAMEPLAY; setState(EClientState::GAMEPLAY);
} }
void CServerHandler::endGameplay() void CServerHandler::endGameplay()
@ -780,7 +792,7 @@ int CServerHandler::howManyPlayerInterfaces()
ELoadMode CServerHandler::getLoadMode() ELoadMode CServerHandler::getLoadMode()
{ {
if(loadMode != ELoadMode::TUTORIAL && state == EClientState::GAMEPLAY) if(loadMode != ELoadMode::TUTORIAL && getState() == EClientState::GAMEPLAY)
{ {
if(si->campState) if(si->campState)
return ELoadMode::CAMPAIGN; return ELoadMode::CAMPAIGN;
@ -874,7 +886,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr<INetworkConnection>
{ {
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
if(state == EClientState::DISCONNECTING) if(getState() == EClientState::DISCONNECTING)
{ {
assert(0); //Should not be possible - socket must be closed at this point assert(0); //Should not be possible - socket must be closed at this point
return; return;
@ -887,7 +899,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr<INetworkConnection>
void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage) void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
{ {
if(state == EClientState::DISCONNECTING) if(getState() == EClientState::DISCONNECTING)
{ {
assert(networkConnection == nullptr); assert(networkConnection == nullptr);
// Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction // Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction
@ -898,18 +910,13 @@ void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> &
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
logNetwork->error("Lost connection to server! Connection has been closed"); logNetwork->error("Lost connection to server! Connection has been closed");
networkConnection.reset();
if(client) if(client)
{
state = EClientState::DISCONNECTING;
GH.dispatchMainThread([]()
{ {
CSH->endGameplay(); CSH->endGameplay();
GH.defActionsDef = 63; GH.defActionsDef = 63;
CMM->menu->switchToTab("main"); CMM->menu->switchToTab("main");
}); CSH->showServerError(CGI->generaltexth->translate("vcmi.server.errors.disconnected"));
} }
else else
{ {
@ -917,6 +924,8 @@ void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> &
lcd.clientId = c->connectionID; lcd.clientId = c->connectionID;
applyPackOnLobbyScreen(lcd); applyPackOnLobbyScreen(lcd);
} }
networkConnection.reset();
} }
void CServerHandler::visitForLobby(CPackForLobby & lobbyPack) void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
@ -970,15 +979,13 @@ void CServerHandler::threadRunServer(bool connectToLobby)
} }
else else
{ {
if (state != EClientState::DISCONNECTING) boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
{
if (state == EClientState::CONNECTING)
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.server.errors.existingProcess"), {});
else
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.server.errors.serverCrashed"), {});
}
state = EClientState::CONNECTION_CANCELLED; // stop attempts to reconnect if (getState() == EClientState::CONNECTING)
{
showServerError(CGI->generaltexth->translate("vcmi.server.errors.existingProcess"));
setState(EClientState::CONNECTION_CANCELLED); // stop attempts to reconnect
}
logNetwork->error("Error: server failed to close correctly or crashed!"); logNetwork->error("Error: server failed to close correctly or crashed!");
logNetwork->error("Check %s for more info", logName); logNetwork->error("Check %s for more info", logName);
} }
@ -987,6 +994,6 @@ void CServerHandler::threadRunServer(bool connectToLobby)
void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
{ {
if(state != EClientState::STARTING) if(getState() != EClientState::STARTING)
c->sendPack(&pack); c->sendPack(&pack);
} }

View File

@ -54,7 +54,6 @@ enum class EClientState : ui8
STARTING, // Gameplay interfaces being created, we pause netpacks retrieving STARTING, // Gameplay interfaces being created, we pause netpacks retrieving
GAMEPLAY, // In-game, used by some UI GAMEPLAY, // In-game, used by some UI
DISCONNECTING, // We disconnecting, drop all netpacks DISCONNECTING, // We disconnecting, drop all netpacks
CONNECTION_FAILED // We could not connect to server
}; };
enum class EServerMode : uint8_t enum class EServerMode : uint8_t
@ -108,6 +107,8 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
boost::thread threadRunLocalServer; boost::thread threadRunLocalServer;
boost::thread threadNetwork; boost::thread threadNetwork;
std::atomic<EClientState> state;
void threadRunNetwork(); void threadRunNetwork();
void threadRunServer(bool connectToLobby); void threadRunServer(bool connectToLobby);
@ -129,7 +130,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
public: public:
std::shared_ptr<CConnection> c; std::shared_ptr<CConnection> c;
std::atomic<EClientState> state;
//////////////////// ////////////////////
// FIXME: Bunch of crutches to glue it all together // FIXME: Bunch of crutches to glue it all together
@ -160,6 +160,9 @@ public:
bool isMyColor(PlayerColor color) const; bool isMyColor(PlayerColor color) const;
ui8 myFirstId() const; // Used by chat only! ui8 myFirstId() const; // Used by chat only!
EClientState getState() const;
void setState(EClientState newState);
bool isHost() const; bool isHost() const;
bool isGuest() const; bool isGuest() const;

View File

@ -73,7 +73,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
GH.windows().createAndPushWindow<CLobbyScreen>(handler.screenType); GH.windows().createAndPushWindow<CLobbyScreen>(handler.screenType);
} }
handler.state = EClientState::LOBBY; handler.setState(EClientState::LOBBY);
} }
} }
@ -136,12 +136,9 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack) void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyRestartGame(LobbyRestartGame & pack)
{ {
if(handler.state == EClientState::GAMEPLAY) assert(handler.getState() == EClientState::GAMEPLAY);
{
handler.restartGameplay();
}
if (handler.validateGameStart()) handler.restartGameplay();
handler.sendStartGame(); handler.sendStartGame();
} }
@ -160,7 +157,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pac
return; return;
} }
handler.state = EClientState::STARTING; handler.setState(EClientState::STARTING);
if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.c->connectionID) if(handler.si->mode != EStartMode::LOAD_GAME || pack.clientId == handler.c->connectionID)
{ {
auto modeBackup = handler.si->mode; auto modeBackup = handler.si->mode;
@ -206,7 +203,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
if(!lobby) //stub: ignore message for game mode if(!lobby) //stub: ignore message for game mode
return; return;
if(!lobby->bonusSel && handler.si->campState && handler.state == EClientState::LOBBY_CAMPAIGN) if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
{ {
lobby->bonusSel = std::make_shared<CBonusSelection>(); lobby->bonusSel = std::make_shared<CBonusSelection>();
GH.windows().pushWindow(lobby->bonusSel); GH.windows().pushWindow(lobby->bonusSel);

View File

@ -307,15 +307,12 @@ void CBonusSelection::createBonusesIcons()
void CBonusSelection::updateAfterStateChange() void CBonusSelection::updateAfterStateChange()
{ {
if(CSH->state != EClientState::GAMEPLAY) if(CSH->getState() != EClientState::GAMEPLAY)
{ {
buttonRestart->disable(); buttonRestart->disable();
buttonVideo->disable(); buttonVideo->disable();
buttonStart->enable(); buttonStart->enable();
if(!getCampaign()->conqueredScenarios().empty()) buttonBack->block(!getCampaign()->conqueredScenarios().empty());
buttonBack->block(true);
else
buttonBack->block(false);
} }
else else
{ {
@ -358,7 +355,7 @@ void CBonusSelection::updateAfterStateChange()
void CBonusSelection::goBack() void CBonusSelection::goBack()
{ {
if(CSH->state != EClientState::GAMEPLAY) if(CSH->getState() != EClientState::GAMEPLAY)
{ {
GH.windows().popWindows(2); GH.windows().popWindows(2);
} }

View File

@ -112,7 +112,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
CLobbyScreen::~CLobbyScreen() CLobbyScreen::~CLobbyScreen()
{ {
// TODO: For now we always destroy whole lobby when leaving bonus selection screen // TODO: For now we always destroy whole lobby when leaving bonus selection screen
if(CSH->state == EClientState::LOBBY_CAMPAIGN) if(CSH->getState() == EClientState::LOBBY_CAMPAIGN)
CSH->sendClientDisconnecting(); CSH->sendClientDisconnecting();
} }

View File

@ -490,7 +490,7 @@ std::string CMultiMode::getPlayerName()
{ {
std::string name = settings["general"]["playerName"].String(); std::string name = settings["general"]["playerName"].String();
if(name == "Player") if(name == "Player")
name = CGI->generaltexth->translate("vcmi.mainMenu.playerName"); name = CGI->generaltexth->translate("core.genrltxt.434");
return name; return name;
} }
@ -587,16 +587,9 @@ void CSimpleJoinScreen::connectToServer()
} }
void CSimpleJoinScreen::leaveScreen() void CSimpleJoinScreen::leaveScreen()
{
if(CSH->state == EClientState::CONNECTING)
{ {
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverClosing")); textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverClosing"));
CSH->state = EClientState::CONNECTION_CANCELLED; CSH->setState(EClientState::CONNECTION_CANCELLED);
}
else if(GH.windows().isTopWindow(this))
{
close();
}
} }
void CSimpleJoinScreen::onChange(const std::string & newText) void CSimpleJoinScreen::onChange(const std::string & newText)

View File

@ -143,12 +143,9 @@ void SettingsMainWindow::mainMenuButtonCallback()
[this]() [this]()
{ {
close(); close();
GH.dispatchMainThread( []()
{
CSH->endGameplay(); CSH->endGameplay();
GH.defActionsDef = 63; GH.defActionsDef = 63;
CMM->menu->switchToTab("main"); CMM->menu->switchToTab("main");
});
}, },
0 0
); );

View File

@ -79,13 +79,13 @@ void NetworkConnection::sendPacket(const std::vector<std::byte> & message)
{ {
boost::system::error_code ec; boost::system::error_code ec;
// create array with single element - boost::asio::buffer can be constructed from containers, but not from plain integer
std::array<uint32_t, 1> messageSize{static_cast<uint32_t>(message.size())}; std::array<uint32_t, 1> messageSize{static_cast<uint32_t>(message.size())};
boost::asio::write(*socket, boost::asio::buffer(messageSize), ec ); boost::asio::write(*socket, boost::asio::buffer(messageSize), ec );
boost::asio::write(*socket, boost::asio::buffer(message), ec ); boost::asio::write(*socket, boost::asio::buffer(message), ec );
if (ec) //Note: ignoring error code, intended
listener.onDisconnected(shared_from_this(), ec.message());
} }
void NetworkConnection::close() void NetworkConnection::close()
@ -93,7 +93,7 @@ void NetworkConnection::close()
boost::system::error_code ec; boost::system::error_code ec;
socket->close(ec); socket->close(ec);
//NOTE: ignoring error code //NOTE: ignoring error code, intended
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END