Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg +++ ps/trunk/binaries/data/config/default.cfg @@ -445,6 +445,7 @@ duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join. lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled. observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached +gamestarttimeout = 60000 ; Don't disconnect clients timing out in the loading screen and rejoin process before exceeding this timeout. [overlay] fps = "false" ; Show frames per second in top right corner Index: ps/trunk/source/network/NetClient.cpp =================================================================== --- ps/trunk/source/network/NetClient.cpp +++ ps/trunk/source/network/NetClient.cpp @@ -672,6 +672,8 @@ return true; } +// This is called either when the host clicks the StartGame button or +// if this client rejoins and finishes the download of the simstate. bool CNetClient::OnGameStart(void* context, CFsmEvent* event) { ENSURE(event->GetType() == (uint)NMT_GAME_START); @@ -680,6 +682,8 @@ JSContext* cx = client->GetScriptInterface().GetContext(); JSAutoRequest rq(cx); + client->m_Session->SetLongTimeout(true); + // Find the player assigned to our GUID int player = -1; if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end()) @@ -820,15 +824,26 @@ CClientsLoadingMessage* message = (CClientsLoadingMessage*)event->GetParamRef(); - std::vector guids; - guids.reserve(message->m_Clients.size()); - for (const CClientsLoadingMessage::S_m_Clients& client : message->m_Clients) - guids.push_back(client.m_GUID); - CNetClient* client = (CNetClient*)context; JSContext* cx = client->GetScriptInterface().GetContext(); JSAutoRequest rq(cx); + bool finished = true; + std::vector guids; + guids.reserve(message->m_Clients.size()); + for (const CClientsLoadingMessage::S_m_Clients& mClient : message->m_Clients) + { + if (client->m_GUID == mClient.m_GUID) + finished = false; + + guids.push_back(mClient.m_GUID); + } + + // Disable the timeout here after processing the enet message, so as to ensure that the connection isn't currently + // timing out (as it is when just leaving the loading screen in LoadFinished). + if (finished) + client->m_Session->SetLongTimeout(false); + JS::RootedValue msg(cx); client->GetScriptInterface().Eval("({ 'type':'clients-loading' })", &msg); client->GetScriptInterface().SetProperty(msg, "guids", guids); @@ -875,6 +890,9 @@ if (client->m_Rejoin) client->SendRejoinedMessage(); + // The last client to leave the loading screen didn't receive the CClientsLoadingMessage, so disable here. + client->m_Session->SetLongTimeout(false); + return true; } Index: ps/trunk/source/network/NetServer.cpp =================================================================== --- ps/trunk/source/network/NetServer.cpp +++ ps/trunk/source/network/NetServer.cpp @@ -1095,6 +1095,9 @@ // Assume session 0 is most likely the local player, so they're // the most efficient client to request a copy from CNetServerSession* sourceSession = server.m_Sessions.at(0); + + session->SetLongTimeout(true); + sourceSession->GetFileTransferer().StartTask( shared_ptr(new CNetFileReceiveTask_ServerRejoin(server, newHostID)) ); @@ -1263,6 +1266,8 @@ CNetServerSession* loadedSession = (CNetServerSession*)context; CNetServerWorker& server = loadedSession->GetServer(); + loadedSession->SetLongTimeout(false); + // We're in the loading state, so wait until every client has loaded // before starting the game ENSURE(server.m_State == SERVER_STATE_LOADING); @@ -1360,6 +1365,8 @@ session->SendMessage(&pausedMessage); } + session->SetLongTimeout(false); + return true; } @@ -1450,8 +1457,11 @@ { m_ServerTurnManager = new CNetServerTurnManager(*this); - for (const CNetServerSession* session : m_Sessions) + for (CNetServerSession* session : m_Sessions) + { m_ServerTurnManager->InitialiseClient(session->GetHostID(), 0); // TODO: only for non-observers + session->SetLongTimeout(true); + } m_State = SERVER_STATE_LOADING; Index: ps/trunk/source/network/NetSession.h =================================================================== --- ps/trunk/source/network/NetSession.h +++ ps/trunk/source/network/NetSession.h @@ -105,6 +105,12 @@ */ u32 GetMeanRTT() const; + /** + * Allows increasing the timeout to prevent drops during an expensive operation, + * and decreasing it back to normal afterwards. + */ + void SetLongTimeout(bool longTimeout); + CNetFileTransferer& GetFileTransferer() { return m_FileTransferer; } private: @@ -184,6 +190,12 @@ void SetLocalClient(bool isLocalClient); /** + * Allows increasing the timeout to prevent drops during an expensive operation, + * and decreasing it back to normal afterwards. + */ + void SetLongTimeout(bool longTimeout); + + /** * Send a message to the client. */ virtual bool SendMessage(const CNetMessage* message); Index: ps/trunk/source/network/NetSession.cpp =================================================================== --- ps/trunk/source/network/NetSession.cpp +++ ps/trunk/source/network/NetSession.cpp @@ -23,6 +23,7 @@ #include "NetStats.h" #include "lib/external_libraries/enet.h" #include "ps/CLogger.h" +#include "ps/ConfigDB.h" #include "ps/Profile.h" #include "scriptinterface/ScriptInterface.h" @@ -32,6 +33,25 @@ static const int CHANNEL_COUNT = 1; +// Only disable long timeouts after a packet from the remote enet peer has been processed. +// Otherwise a long timeout can still be in progress when disabling it here. +void SetEnetLongTimeout(ENetPeer* peer, bool isLocalClient, bool enabled) +{ +#if (ENET_VERSION >= ENET_VERSION_CREATE(1, 3, 4)) + if (!peer || isLocalClient) + return; + + if (enabled) + { + u32 timeout; + CFG_GET_VAL("network.gamestarttimeout", timeout); + enet_peer_timeout(peer, 0, timeout, timeout); + } + else + enet_peer_timeout(peer, 0, 0, 0); +#endif +} + CNetClientSession::CNetClientSession(CNetClient& client) : m_Client(client), m_FileTransferer(this), m_Host(nullptr), m_Server(nullptr), m_Stats(nullptr), m_IsLocalClient(false) { @@ -201,6 +221,11 @@ return m_Server->roundTripTime; } +void CNetClientSession::SetLongTimeout(bool enabled) +{ + SetEnetLongTimeout(m_Server, m_IsLocalClient, enabled); +} + CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) : m_Server(server), m_FileTransferer(this), m_Peer(peer), m_IsLocalClient(false), m_HostID(0), m_GUID(), m_UserName() { @@ -261,3 +286,8 @@ enet_peer_timeout(m_Peer, 0, MAXIMUM_HOST_TIMEOUT, MAXIMUM_HOST_TIMEOUT); #endif } + +void CNetServerSession::SetLongTimeout(bool enabled) +{ + SetEnetLongTimeout(m_Peer, m_IsLocalClient, enabled); +}