LCOV - code coverage report
Current view: top level - source/network - NetServerTurnManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 90 0.0 %
Date: 2023-01-19 00:18:29 Functions: 0 8 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "NetMessage.h"
      21             : #include "NetServerTurnManager.h"
      22             : #include "NetServer.h"
      23             : #include "NetSession.h"
      24             : 
      25             : #include "lib/utf8.h"
      26             : #include "ps/CLogger.h"
      27             : #include "ps/ConfigDB.h"
      28             : #include "simulation2/system/TurnManager.h"
      29             : 
      30             : #if 0
      31             : #include "ps/Util.h"
      32             : #define NETSERVERTURN_LOG(...) debug_printf(__VA_ARGS__)
      33             : #else
      34             : #define NETSERVERTURN_LOG(...)
      35             : #endif
      36             : 
      37           0 : CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server)
      38           0 :     : m_NetServer(server), m_ReadyTurn(COMMAND_DELAY_MP - 1), m_TurnLength(DEFAULT_TURN_LENGTH)
      39             : {
      40             :     // Turn 0 is not actually executed, store a dummy value.
      41           0 :     m_SavedTurnLengths.push_back(0);
      42             :     // Turns [1..COMMAND_DELAY - 1] are special: all clients run them without waiting on a server command batch.
      43             :     // Because of this, they are always run with the default MP turn length.
      44           0 :     for (u32 i = 1; i < COMMAND_DELAY_MP; ++i)
      45           0 :         m_SavedTurnLengths.push_back(m_TurnLength);
      46           0 : }
      47             : 
      48           0 : void CNetServerTurnManager::NotifyFinishedClientCommands(CNetServerSession& session, u32 turn)
      49             : {
      50           0 :     int client = session.GetHostID();
      51             : 
      52             :     NETSERVERTURN_LOG("NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn);
      53             : 
      54             :     // Must be a client we've already heard of
      55           0 :     ENSURE(m_ClientsData.find(client) != m_ClientsData.end());
      56             : 
      57             :     // Clients must advance one turn at a time
      58           0 :     if (turn != m_ClientsData[client].readyTurn + 1)
      59             :     {
      60           0 :         LOGERROR("NotifyFinishedClientCommands: Client %d (%s) is ready for turn %d, but expected %d",
      61             :             client,
      62             :             utf8_from_wstring(session.GetUserName()).c_str(),
      63             :             turn,
      64             :             m_ClientsData[client].readyTurn + 1);
      65             : 
      66           0 :         session.Disconnect(NDR_INCORRECT_READY_TURN_COMMANDS);
      67             :     }
      68             : 
      69           0 :     m_ClientsData[client].readyTurn = turn;
      70             : 
      71             :     // Check whether this was the final client to become ready
      72           0 :     CheckClientsReady();
      73           0 : }
      74             : 
      75           0 : void CNetServerTurnManager::CheckClientsReady()
      76             : {
      77           0 :     int max_observer_lag = -1;
      78           0 :     CFG_GET_VAL("network.observermaxlag", max_observer_lag);
      79             :     // Clamp to 0-10000 turns, below/above that is no limit.
      80           0 :     max_observer_lag = max_observer_lag < 0 ? -1 : max_observer_lag > 10000 ? -1 : max_observer_lag;
      81             : 
      82             :     // See if all clients (including self) are ready for a new turn
      83           0 :     for (const std::pair<const int, Client>& clientData : m_ClientsData)
      84             :     {
      85             :         // Observers are allowed to lag more than regular clients.
      86           0 :         if (clientData.second.isObserver && (max_observer_lag == -1 || clientData.second.readyTurn > m_ReadyTurn - max_observer_lag))
      87           0 :             continue;
      88             :         NETSERVERTURN_LOG("  %d: %d <=? %d\n", clientReady.first, clientReady.second, m_ReadyTurn);
      89           0 :         if (clientData.second.readyTurn <= m_ReadyTurn)
      90           0 :             return; // wasn't ready for m_ReadyTurn+1
      91             :     }
      92             : 
      93           0 :     ++m_ReadyTurn;
      94             : 
      95             :     NETSERVERTURN_LOG("CheckClientsReady: ready for turn %d\n", m_ReadyTurn);
      96             : 
      97             :     // Tell all clients that the next turn is ready
      98           0 :     CEndCommandBatchMessage msg;
      99           0 :     msg.m_TurnLength = m_TurnLength;
     100           0 :     msg.m_Turn = m_ReadyTurn;
     101           0 :     m_NetServer.Broadcast(&msg, { NSS_INGAME });
     102             : 
     103           0 :     ENSURE(m_SavedTurnLengths.size() == m_ReadyTurn);
     104           0 :     m_SavedTurnLengths.push_back(m_TurnLength);
     105             : }
     106             : 
     107           0 : void CNetServerTurnManager::NotifyFinishedClientUpdate(CNetServerSession& session, u32 turn, const CStr& hash)
     108             : {
     109             : 
     110           0 :     int client = session.GetHostID();
     111           0 :     const CStrW& playername = session.GetUserName();
     112             : 
     113             :     // Clients must advance one turn at a time
     114           0 :     if (turn != m_ClientsData[client].simulatedTurn + 1)
     115             :     {
     116           0 :         LOGERROR("NotifyFinishedClientUpdate: Client %d (%s) is ready for turn %d, but expected %d",
     117             :             client,
     118             :             utf8_from_wstring(playername).c_str(),
     119             :             turn,
     120             :             m_ClientsData[client].simulatedTurn + 1);
     121             : 
     122           0 :         session.Disconnect(NDR_INCORRECT_READY_TURN_SIMULATED);
     123             :     }
     124             : 
     125           0 :     m_ClientsData[client].simulatedTurn = turn;
     126             : 
     127             :     // Check for OOS only if in sync
     128           0 :     if (m_HasSyncError)
     129           0 :         return;
     130             : 
     131           0 :     m_ClientsData[client].playerName = playername;
     132           0 :     m_ClientStateHashes[turn][client] = hash;
     133             : 
     134             :     // Find the newest turn which we know all clients have simulated
     135           0 :     u32 newest = std::numeric_limits<u32>::max();
     136           0 :     for (const std::pair<const int, Client>& clientData : m_ClientsData)
     137           0 :         if (clientData.second.simulatedTurn < newest)
     138           0 :             newest = clientData.second.simulatedTurn;
     139             : 
     140             :     // For every set of state hashes that all clients have simulated, check for OOS
     141           0 :     for (const std::pair<const u32, std::map<int, std::string>>& clientStateHash : m_ClientStateHashes)
     142             :     {
     143           0 :         if (clientStateHash.first > newest)
     144           0 :             break;
     145             : 
     146             :         // Assume the host is correct (maybe we should choose the most common instead to help debugging)
     147           0 :         std::string expected = clientStateHash.second.begin()->second;
     148             : 
     149             :         // Find all players that are OOS on that turn
     150           0 :         std::vector<CStrW> OOSPlayerNames;
     151           0 :         for (const std::pair<const int, std::string>& hashPair : clientStateHash.second)
     152             :         {
     153             :             NETSERVERTURN_LOG("sync check %d: %d = %hs\n", clientStateHash.first, hashPair.first, Hexify(hashPair.second).c_str());
     154           0 :             if (hashPair.second != expected)
     155             :             {
     156             :                 // Oh no, out of sync
     157           0 :                 m_HasSyncError = true;
     158           0 :                 m_ClientsData[hashPair.first].isOOS = true;
     159           0 :                 OOSPlayerNames.push_back(m_ClientsData[hashPair.first].playerName);
     160             :             }
     161             :         }
     162             : 
     163             :         // Tell everyone about it
     164           0 :         if (m_HasSyncError)
     165             :         {
     166           0 :             CSyncErrorMessage msg;
     167           0 :             msg.m_Turn = clientStateHash.first;
     168           0 :             msg.m_HashExpected = expected;
     169           0 :             for (const CStrW& oosPlayername : OOSPlayerNames)
     170             :             {
     171           0 :                 CSyncErrorMessage::S_m_PlayerNames h;
     172           0 :                 h.m_Name = oosPlayername;
     173           0 :                 msg.m_PlayerNames.push_back(h);
     174             :             }
     175           0 :             m_NetServer.Broadcast(&msg, { NSS_INGAME });
     176           0 :             break;
     177             :         }
     178             :     }
     179             : 
     180             :     // Delete the saved hashes for all turns that we've already verified
     181           0 :     m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1));
     182             : }
     183             : 
     184           0 : void CNetServerTurnManager::InitialiseClient(int client, u32 turn, bool observer)
     185             : {
     186             :     NETSERVERTURN_LOG("InitialiseClient(client=%d, turn=%d)\n", client, turn);
     187             : 
     188           0 :     ENSURE(m_ClientsData.find(client) == m_ClientsData.end());
     189           0 :     Client& data = m_ClientsData[client];
     190           0 :     data.readyTurn = turn + COMMAND_DELAY_MP - 1;
     191           0 :     data.simulatedTurn = turn;
     192           0 :     data.isObserver = observer;
     193           0 : }
     194             : 
     195           0 : void CNetServerTurnManager::UninitialiseClient(int client)
     196             : {
     197             :     NETSERVERTURN_LOG("UninitialiseClient(client=%d)\n", client);
     198             : 
     199           0 :     ENSURE(m_ClientsData.find(client) != m_ClientsData.end());
     200           0 :     bool checkOOS = m_ClientsData[client].isOOS;
     201           0 :     m_ClientsData.erase(client);
     202             : 
     203             :     // Check whether we're ready for the next turn now that we're not
     204             :     // waiting for this client any more
     205           0 :     CheckClientsReady();
     206             : 
     207             :     // Check whether we're still OOS.
     208           0 :     if (checkOOS)
     209             :     {
     210           0 :         for (const std::pair<const int, Client>& clientData : m_ClientsData)
     211           0 :             if (clientData.second.isOOS)
     212           0 :                 return;
     213           0 :         m_HasSyncError = false;
     214             :     }
     215             : }
     216             : 
     217           0 : void CNetServerTurnManager::SetTurnLength(u32 msecs)
     218             : {
     219           0 :     m_TurnLength = msecs;
     220           0 : }
     221             : 
     222           0 : u32 CNetServerTurnManager::GetSavedTurnLength(u32 turn)
     223             : {
     224           0 :     ENSURE(turn <= m_ReadyTurn);
     225           0 :     return m_SavedTurnLengths.at(turn);
     226             : }

Generated by: LCOV version 1.13