Ticket #4095: split.patch

File split.patch, 72.5 KB (added by echotangoecho, 7 years ago)
  • source/gui/scripting/ScriptFunctions.cpp

    diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
    index a366c90..bad9492 100644
    a b  
    3737#include "lobby/scripting/JSInterface_Lobby.h"
    3838#include "maths/FixedVector3D.h"
    3939#include "network/NetClient.h"
     40#include "network/NetMessage.h"
    4041#include "network/NetServer.h"
    41 #include "network/NetTurnManager.h"
    4242#include "ps/CConsole.h"
    4343#include "ps/CLogger.h"
    4444#include "ps/Errors.h"
     
    7070#include "simulation2/components/ICmpSelectable.h"
    7171#include "simulation2/components/ICmpTemplateManager.h"
    7272#include "simulation2/helpers/Selection.h"
     73#include "simulation2/system/TurnManager.h"
    7374#include "soundmanager/SoundManager.h"
    7475#include "soundmanager/scripting/JSInterface_Sound.h"
    7576#include "tools/atlas/GameInterface/GameLoop.h"
  • source/network/NetClient.cpp

    diff --git a/source/network/NetClient.cpp b/source/network/NetClient.cpp
    index aa23532..4f35cd4 100644
    a b  
    1919
    2020#include "NetClient.h"
    2121
     22#include "NetClientTurnManager.h"
    2223#include "NetMessage.h"
    2324#include "NetSession.h"
    24 #include "NetTurnManager.h"
    2525
    2626#include "lib/byte_order.h"
    2727#include "lib/sysdep/sysdep.h"
  • new file source/network/NetClientTurnManager.cpp

    diff --git a/source/network/NetClientTurnManager.cpp b/source/network/NetClientTurnManager.cpp
    new file mode 100644
    index 0000000..5e55e2b
    - +  
     1/* Copyright (C) 2016 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 "NetClientTurnManager.h"
     21#include "NetClient.h"
     22
     23#include "gui/GUIManager.h"
     24#include "ps/CLogger.h"
     25#include "ps/Pyrogenesis.h"
     26#include "ps/Replay.h"
     27#include "ps/Util.h"
     28#include "simulation2/Simulation2.h"
     29
     30#if 0
     31#define NETCLIENTTURN_LOG(args) debug_printf args
     32#else
     33#define NETCLIENTTURN_LOG(args)
     34#endif
     35
     36CNetClientTurnManager::CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay) :
     37    CTurnManager(simulation, DEFAULT_TURN_LENGTH_MP, clientId, replay), m_NetClient(client)
     38{
     39}
     40
     41void CNetClientTurnManager::PostCommand(JS::HandleValue data)
     42{
     43    NETCLIENTTURN_LOG((L"PostCommand()\n"));
     44
     45    // Transmit command to server
     46    CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data);
     47    m_NetClient.SendMessage(&msg);
     48
     49    // Add to our local queue
     50    //AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
     51    // TODO: we should do this when the server stops sending our commands back to us
     52}
     53
     54void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
     55{
     56    NETCLIENTTURN_LOG((L"NotifyFinishedOwnCommands(%d)\n", turn));
     57
     58    CEndCommandBatchMessage msg;
     59
     60    msg.m_Turn = turn;
     61
     62    // The turn-length field of the CEndCommandBatchMessage is currently only relevant
     63    // when sending it from the server to the clients.
     64    // It could be used to verify that the client simulated the correct turn length.
     65    msg.m_TurnLength = 0;
     66
     67    m_NetClient.SendMessage(&msg);
     68}
     69
     70void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn)
     71{
     72    bool quick = !TurnNeedsFullHash(turn);
     73    std::string hash;
     74    {
     75        PROFILE3("state hash check");
     76        ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
     77    }
     78
     79    NETCLIENTTURN_LOG((L"NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str()));
     80
     81    m_Replay.Hash(hash, quick);
     82
     83    // Don't send the hash if OOS
     84    if (m_HasSyncError)
     85        return;
     86
     87    // Send message to the server
     88    CSyncCheckMessage msg;
     89    msg.m_Turn = turn;
     90    msg.m_Hash = hash;
     91    m_NetClient.SendMessage(&msg);
     92}
     93
     94void CNetClientTurnManager::OnDestroyConnection()
     95{
     96    NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
     97}
     98
     99void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
     100{
     101    // Command received from the server - store it for later execution
     102    AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
     103}
     104
     105void CNetClientTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames)
     106{
     107    NETCLIENTTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));
     108
     109    // Only complain the first time
     110    if (m_HasSyncError)
     111        return;
     112
     113    m_HasSyncError = true;
     114
     115    std::string hash;
     116    ENSURE(m_Simulation2.ComputeStateHash(hash, !TurnNeedsFullHash(turn)));
     117
     118    OsPath path = psLogDir() / "oos_dump.txt";
     119    std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
     120    m_Simulation2.DumpDebugState(file);
     121    file.close();
     122
     123    hash = Hexify(hash);
     124    const std::string& expectedHashHex = Hexify(expectedHash);
     125
     126    std::stringstream msg;
     127    msg << "Out of sync on turn " << turn;
     128
     129    for (size_t i = 0; i < playerNames.size(); ++i)
     130        msg << (i == 0 ? "\nPlayers: " : ", ") << utf8_from_wstring(playerNames[i].m_Name);
     131
     132    msg << "\n\n" << "Your game state is " << (expectedHash == hash ? "identical to" : "different from") << " the hosts game state.";
     133
     134    msg << "\n\n" << "Dumping current state to " << CStr(path.string8()).EscapeToPrintableASCII();
     135
     136    LOGERROR("%s", msg.str());
     137
     138    if (g_GUI)
     139        g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
     140}
  • new file source/network/NetClientTurnManager.h

    diff --git a/source/network/NetClientTurnManager.h b/source/network/NetClientTurnManager.h
    new file mode 100644
    index 0000000..dbd0226
    - +  
     1/* Copyright (C) 2016 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#ifndef INCLUDED_NETCLIENTTURNMANAGER
     19#define INCLUDED_NETCLIENTTURNMANAGER
     20
     21#include "simulation2/system/TurnManager.h"
     22#include "NetMessage.h"
     23
     24class CNetClient;
     25
     26/**
     27 * Implementation of CTurnManager for network clients.
     28 */
     29class CNetClientTurnManager : public CTurnManager
     30{
     31public:
     32    CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay);
     33
     34    void OnSimulationMessage(CSimulationMessage* msg);
     35
     36    void PostCommand(JS::HandleValue data);
     37
     38    /**
     39     * Notify the server that all commands are sent to prepare the connection for termination.
     40     */
     41    void OnDestroyConnection();
     42
     43    void OnSyncError(u32 turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames);
     44
     45private:
     46    void NotifyFinishedOwnCommands(u32 turn);
     47
     48    void NotifyFinishedUpdate(u32 turn);
     49
     50    CNetClient& m_NetClient;
     51};
     52
     53#endif // INCLUDED_NETCLIENTTURNMANAGER
  • source/network/NetServer.cpp

    diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp
    index 38680d8..9181df4 100644
    a b  
    2222#include "NetClient.h"
    2323#include "NetMessage.h"
    2424#include "NetSession.h"
     25#include "NetServerTurnManager.h"
    2526#include "NetStats.h"
    26 #include "NetTurnManager.h"
    2727
    2828#include "lib/external_libraries/enet.h"
    2929#include "ps/CLogger.h"
     
    3232#include "scriptinterface/ScriptInterface.h"
    3333#include "scriptinterface/ScriptRuntime.h"
    3434#include "simulation2/Simulation2.h"
     35#include "simulation2/system/TurnManager.h"
    3536
    3637#if CONFIG2_MINIUPNPC
    3738#include <miniupnpc/miniwget.h>
  • new file source/network/NetServerTurnManager.cpp

    diff --git a/source/network/NetServerTurnManager.cpp b/source/network/NetServerTurnManager.cpp
    new file mode 100644
    index 0000000..7c2c9c8
    - +  
     1/* Copyright (C) 2016 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
     24#include "simulation2/system/TurnManager.h"
     25
     26#if 0
     27#define NETSERVERTURN_LOG(args) debug_printf args
     28#else
     29#define NETSERVERTURN_LOG(args)
     30#endif
     31
     32CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
     33    m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP), m_HasSyncError(false)
     34{
     35    // The first turn we will actually execute is number 2,
     36    // so store dummy values into the saved lengths list
     37    m_SavedTurnLengths.push_back(0);
     38    m_SavedTurnLengths.push_back(0);
     39}
     40
     41void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
     42{
     43    NETSERVERTURN_LOG((L"NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn));
     44
     45    // Must be a client we've already heard of
     46    ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());
     47
     48    // Clients must advance one turn at a time
     49    ENSURE(turn == m_ClientsReady[client] + 1);
     50    m_ClientsReady[client] = turn;
     51
     52    // Check whether this was the final client to become ready
     53    CheckClientsReady();
     54}
     55
     56void CNetServerTurnManager::CheckClientsReady()
     57{
     58    // See if all clients (including self) are ready for a new turn
     59    for (const std::pair<int, u32>& clientReady : m_ClientsReady)
     60    {
     61        NETSERVERTURN_LOG((L"  %d: %d <=? %d\n", clientReady.first, clientReady.second, m_ReadyTurn));
     62        if (clientReady.second <= m_ReadyTurn)
     63            return; // wasn't ready for m_ReadyTurn+1
     64    }
     65
     66    ++m_ReadyTurn;
     67
     68    NETSERVERTURN_LOG((L"CheckClientsReady: ready for turn %d\n", m_ReadyTurn));
     69
     70    // Tell all clients that the next turn is ready
     71    CEndCommandBatchMessage msg;
     72    msg.m_TurnLength = m_TurnLength;
     73    msg.m_Turn = m_ReadyTurn;
     74    m_NetServer.Broadcast(&msg);
     75
     76    ENSURE(m_SavedTurnLengths.size() == m_ReadyTurn);
     77    m_SavedTurnLengths.push_back(m_TurnLength);
     78}
     79
     80void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, const CStrW& playername, u32 turn, const CStr& hash)
     81{
     82    // Clients must advance one turn at a time
     83    ENSURE(turn == m_ClientsSimulated[client] + 1);
     84    m_ClientsSimulated[client] = turn;
     85
     86    // Check for OOS only if in sync
     87    if (m_HasSyncError)
     88        return;
     89
     90    m_ClientPlayernames[client] = playername;
     91    m_ClientStateHashes[turn][client] = hash;
     92
     93    // Find the newest turn which we know all clients have simulated
     94    u32 newest = std::numeric_limits<u32>::max();
     95    for (const std::pair<int, u32>& clientSimulated : m_ClientsSimulated)
     96        if (clientSimulated.second < newest)
     97            newest = clientSimulated.second;
     98
     99    // For every set of state hashes that all clients have simulated, check for OOS
     100    for (const std::pair<u32, std::map<int, std::string>>& clientStateHash : m_ClientStateHashes)
     101    {
     102        if (clientStateHash.first > newest)
     103            break;
     104
     105        // Assume the host is correct (maybe we should choose the most common instead to help debugging)
     106        std::string expected = clientStateHash.second.begin()->second;
     107
     108        // Find all players that are OOS on that turn
     109        std::vector<CStrW> OOSPlayerNames;
     110        for (const std::pair<int, std::string>& hashPair : clientStateHash.second)
     111        {
     112            NETSERVERTURN_LOG((L"sync check %d: %d = %hs\n", it->first, cit->first, Hexify(cit->second).c_str()));
     113            if (hashPair.second != expected)
     114            {
     115                // Oh no, out of sync
     116                m_HasSyncError = true;
     117                OOSPlayerNames.push_back(m_ClientPlayernames[hashPair.first]);
     118            }
     119        }
     120
     121        // Tell everyone about it
     122        if (m_HasSyncError)
     123        {
     124            CSyncErrorMessage msg;
     125            msg.m_Turn = clientStateHash.first;
     126            msg.m_HashExpected = expected;
     127            for (const CStrW& playername : OOSPlayerNames)
     128            {
     129                CSyncErrorMessage::S_m_PlayerNames h;
     130                h.m_Name = playername;
     131                msg.m_PlayerNames.push_back(h);
     132            }
     133            m_NetServer.Broadcast(&msg);
     134            break;
     135        }
     136    }
     137
     138    // Delete the saved hashes for all turns that we've already verified
     139    m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1));
     140}
     141
     142void CNetServerTurnManager::InitialiseClient(int client, u32 turn)
     143{
     144    NETSERVERTURN_LOG((L"InitialiseClient(client=%d, turn=%d)\n", client, turn));
     145
     146    ENSURE(m_ClientsReady.find(client) == m_ClientsReady.end());
     147    m_ClientsReady[client] = turn + 1;
     148    m_ClientsSimulated[client] = turn;
     149}
     150
     151void CNetServerTurnManager::UninitialiseClient(int client)
     152{
     153    NETSERVERTURN_LOG((L"UninitialiseClient(client=%d)\n", client));
     154
     155    ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());
     156    m_ClientsReady.erase(client);
     157    m_ClientsSimulated.erase(client);
     158
     159    // Check whether we're ready for the next turn now that we're not
     160    // waiting for this client any more
     161    CheckClientsReady();
     162}
     163
     164void CNetServerTurnManager::SetTurnLength(u32 msecs)
     165{
     166    m_TurnLength = msecs;
     167}
     168
     169u32 CNetServerTurnManager::GetSavedTurnLength(u32 turn)
     170{
     171    ENSURE(turn <= m_ReadyTurn);
     172    return m_SavedTurnLengths.at(turn);
     173}
  • new file source/network/NetServerTurnManager.h

    diff --git a/source/network/NetServerTurnManager.h b/source/network/NetServerTurnManager.h
    new file mode 100644
    index 0000000..d5786df
    - +  
     1/* Copyright (C) 2016 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#ifndef INCLUDED_NETSERVERTURNMANAGER
     19#define INCLUDED_NETSERVERTURNMANAGER
     20
     21class CNetServerWorker;
     22
     23/**
     24 * The server-side counterpart to CNetClientTurnManager.
     25 * Records the turn state of each client, and sends turn advancement messages
     26 * when all clients are ready.
     27 *
     28 * Thread-safety:
     29 * - This is constructed and used by CNetServerWorker in the network server thread.
     30 */
     31class CNetServerTurnManager
     32{
     33    NONCOPYABLE(CNetServerTurnManager);
     34public:
     35    CNetServerTurnManager(CNetServerWorker& server);
     36
     37    void NotifyFinishedClientCommands(int client, u32 turn);
     38
     39    void NotifyFinishedClientUpdate(int client, const CStrW& playername, u32 turn, const CStr& hash);
     40
     41    /**
     42     * Inform the turn manager of a new client who will be sending commands.
     43     */
     44    void InitialiseClient(int client, u32 turn);
     45
     46    /**
     47     * Inform the turn manager that a previously-initialised client has left the game
     48     * and will no longer be sending commands.
     49     */
     50    void UninitialiseClient(int client);
     51
     52    void SetTurnLength(u32 msecs);
     53
     54    /**
     55     * Returns the latest turn for which all clients are ready;
     56     * they will have already been told to execute this turn.
     57     */
     58    u32 GetReadyTurn() { return m_ReadyTurn; }
     59
     60    /**
     61     * Returns the turn length that was used for the given turn.
     62     * Requires turn <= GetReadyTurn().
     63     */
     64    u32 GetSavedTurnLength(u32 turn);
     65
     66private:
     67    void CheckClientsReady();
     68
     69    /// The latest turn for which we have received all commands from all clients
     70    u32 m_ReadyTurn;
     71
     72    // Client ID -> ready turn number (the latest turn for which all commands have been received from that client)
     73    std::map<int, u32> m_ClientsReady;
     74
     75    // Client ID -> last known simulated turn number (for which we have the state hash)
     76    // (the client has reached the start of this turn, not done the update for it yet)
     77    std::map<int, u32> m_ClientsSimulated;
     78
     79    // Map of turn -> {Client ID -> state hash}; old indexes <= min(m_ClientsSimulated) are deleted
     80    std::map<u32, std::map<int, std::string>> m_ClientStateHashes;
     81
     82    // Map of client ID -> playername
     83    std::map<u32, CStrW> m_ClientPlayernames;
     84
     85    // Current turn length
     86    u32 m_TurnLength;
     87
     88    // Turn lengths for all previously executed turns
     89    std::vector<u32> m_SavedTurnLengths;
     90
     91    CNetServerWorker& m_NetServer;
     92
     93    bool m_HasSyncError;
     94};
     95
     96#endif // INCLUDED_NETSERVERTURNMANAGER
  • deleted file source/network/NetTurnManager.cpp

    diff --git a/source/network/NetTurnManager.cpp b/source/network/NetTurnManager.cpp
    deleted file mode 100644
    index 19bfb98..0000000
    + -  
    1 /* Copyright (C) 2016 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 "NetTurnManager.h"
    21 #include "NetMessage.h"
    22 
    23 #include "network/NetServer.h"
    24 #include "network/NetClient.h"
    25 #include "network/NetMessage.h"
    26 
    27 #include "gui/GUIManager.h"
    28 #include "maths/MathUtil.h"
    29 #include "ps/CLogger.h"
    30 #include "ps/Profile.h"
    31 #include "ps/Pyrogenesis.h"
    32 #include "ps/Replay.h"
    33 #include "ps/SavedGame.h"
    34 #include "ps/Util.h"
    35 #include "scriptinterface/ScriptInterface.h"
    36 #include "simulation2/Simulation2.h"
    37 
    38 #include <fstream>
    39 
    40 const u32 DEFAULT_TURN_LENGTH_MP = 500;
    41 const u32 DEFAULT_TURN_LENGTH_SP = 200;
    42 
    43 static const int COMMAND_DELAY = 2;
    44 
    45 #if 0
    46 #define NETTURN_LOG(args) debug_printf args
    47 #else
    48 #define NETTURN_LOG(args)
    49 #endif
    50 
    51 CNetTurnManager::CNetTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay) :
    52     m_Simulation2(simulation), m_CurrentTurn(0), m_ReadyTurn(1), m_TurnLength(defaultTurnLength), m_DeltaSimTime(0),
    53     m_PlayerId(-1), m_ClientId(clientId), m_HasSyncError(false), m_Replay(replay),
    54     m_TimeWarpNumTurns(0), m_FinalTurn(std::numeric_limits<u32>::max())
    55 {
    56     // When we are on turn n, we schedule new commands for n+2.
    57     // We know that all other clients have finished scheduling commands for n (else we couldn't have got here).
    58     // We know we have not yet finished scheduling commands for n+2.
    59     // Hence other clients can be on turn n-1, n, n+1, and no other.
    60     // So they can be sending us commands scheduled for n+1, n+2, n+3.
    61     // So we need a 3-element buffer:
    62     m_QueuedCommands.resize(COMMAND_DELAY + 1);
    63 }
    64 
    65 void CNetTurnManager::ResetState(u32 newCurrentTurn, u32 newReadyTurn)
    66 {
    67     m_CurrentTurn = newCurrentTurn;
    68     m_ReadyTurn = newReadyTurn;
    69     m_DeltaSimTime = 0;
    70     size_t queuedCommandsSize = m_QueuedCommands.size();
    71     m_QueuedCommands.clear();
    72     m_QueuedCommands.resize(queuedCommandsSize);
    73 }
    74 
    75 void CNetTurnManager::SetPlayerID(int playerId)
    76 {
    77     m_PlayerId = playerId;
    78 }
    79 
    80 bool CNetTurnManager::WillUpdate(float simFrameLength)
    81 {
    82     // Keep this in sync with the return value of Update()
    83 
    84     if (m_CurrentTurn > m_FinalTurn)
    85         return false;
    86 
    87     if (m_DeltaSimTime + simFrameLength < 0)
    88         return false;
    89 
    90     if (m_ReadyTurn <= m_CurrentTurn)
    91         return false;
    92 
    93     return true;
    94 }
    95 
    96 bool CNetTurnManager::Update(float simFrameLength, size_t maxTurns)
    97 {
    98     if (m_CurrentTurn > m_FinalTurn)
    99         return false;
    100 
    101     m_DeltaSimTime += simFrameLength;
    102 
    103     // If the game becomes laggy, m_DeltaSimTime increases progressively.
    104     // The engine will fast forward accordingly to catch up.
    105     // To keep the game playable, stop fast forwarding after 2 turn lengths.
    106     m_DeltaSimTime = std::min(m_DeltaSimTime, 2.0f * m_TurnLength / 1000.0f);
    107 
    108     // If we haven't reached the next turn yet, do nothing
    109     if (m_DeltaSimTime < 0)
    110         return false;
    111 
    112     NETTURN_LOG((L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
    113 
    114     // Check that the next turn is ready for execution
    115     if (m_ReadyTurn <= m_CurrentTurn)
    116     {
    117         // Oops, we wanted to start the next turn but it's not ready yet -
    118         // there must be too much network lag.
    119         // TODO: complain to the user.
    120         // TODO: send feedback to the server to increase the turn length.
    121 
    122         // Reset the next-turn timer to 0 so we try again next update but
    123         // so we don't rush to catch up in subsequent turns.
    124         // TODO: we should do clever rate adjustment instead of just pausing like this.
    125         m_DeltaSimTime = 0;
    126 
    127         return false;
    128     }
    129 
    130     maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn
    131 
    132     for (size_t i = 0; i < maxTurns; ++i)
    133     {
    134         // Check that we've reached the i'th next turn
    135         if (m_DeltaSimTime < 0)
    136             break;
    137 
    138         // Check that the i'th next turn is still ready
    139         if (m_ReadyTurn <= m_CurrentTurn)
    140             break;
    141 
    142         NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
    143 
    144         // Increase now, so Update can send new commands for a subsequent turn
    145         ++m_CurrentTurn;
    146 
    147         // Clean up any destroyed entities since the last turn (e.g. placement previews
    148         // or rally point flags generated by the GUI). (Must do this before the time warp
    149         // serialization.)
    150         m_Simulation2.FlushDestroyedEntities();
    151 
    152         // Save the current state for rewinding, if enabled
    153         if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0)
    154         {
    155             PROFILE3("time warp serialization");
    156             std::stringstream stream;
    157             m_Simulation2.SerializeState(stream);
    158             m_TimeWarpStates.push_back(stream.str());
    159         }
    160 
    161         // Put all the client commands into a single list, in a globally consistent order
    162         std::vector<SimulationCommand> commands;
    163         for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
    164             commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
    165 
    166         m_QueuedCommands.pop_front();
    167         m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
    168 
    169         m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
    170 
    171         NETTURN_LOG((L"Running %d cmds\n", commands.size()));
    172 
    173         m_Simulation2.Update(m_TurnLength, commands);
    174 
    175         NotifyFinishedUpdate(m_CurrentTurn);
    176 
    177         // Set the time for the next turn update
    178         m_DeltaSimTime -= m_TurnLength / 1000.f;
    179     }
    180 
    181     return true;
    182 }
    183 
    184 bool CNetTurnManager::UpdateFastForward()
    185 {
    186     m_DeltaSimTime = 0;
    187 
    188     NETTURN_LOG((L"UpdateFastForward current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
    189 
    190     // Check that the next turn is ready for execution
    191     if (m_ReadyTurn <= m_CurrentTurn)
    192         return false;
    193 
    194     while (m_ReadyTurn > m_CurrentTurn)
    195     {
    196         // TODO: It would be nice to remove some of the duplication with Update()
    197         // (This is similar but doesn't call any Notify functions or update DeltaTime,
    198         // it just updates the simulation state)
    199 
    200         ++m_CurrentTurn;
    201 
    202         m_Simulation2.FlushDestroyedEntities();
    203 
    204         // Put all the client commands into a single list, in a globally consistent order
    205         std::vector<SimulationCommand> commands;
    206         for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
    207             commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
    208 
    209         m_QueuedCommands.pop_front();
    210         m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
    211 
    212         m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
    213 
    214         NETTURN_LOG((L"Running %d cmds\n", commands.size()));
    215 
    216         m_Simulation2.Update(m_TurnLength, commands);
    217     }
    218 
    219     return true;
    220 }
    221 
    222 void CNetTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames)
    223 {
    224     NETTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));
    225 
    226     // Only complain the first time
    227     if (m_HasSyncError)
    228         return;
    229 
    230     bool quick = !TurnNeedsFullHash(turn);
    231     std::string hash;
    232     ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
    233 
    234     OsPath path = psLogDir() / "oos_dump.txt";
    235     std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
    236     m_Simulation2.DumpDebugState(file);
    237     file.close();
    238 
    239     hash = Hexify(hash);
    240     const std::string& expectedHashHex = Hexify(expectedHash);
    241 
    242     DisplayOOSError(turn, hash, expectedHashHex, false, &playerNames, &path);
    243 }
    244 
    245 void CNetTurnManager::DisplayOOSError(u32 turn, const CStr& hash, const CStr& expectedHash, bool isReplay, std::vector<CSyncErrorMessage::S_m_PlayerNames>* playerNames = NULL, OsPath* path = NULL)
    246 {
    247     m_HasSyncError = true;
    248 
    249     std::stringstream msg;
    250     msg << "Out of sync on turn " << turn;
    251 
    252     if (playerNames)
    253         for (size_t i = 0; i < playerNames->size(); ++i)
    254             msg << (i == 0 ? "\nPlayers: " : ", ") << utf8_from_wstring((*playerNames)[i].m_Name);
    255 
    256     if (isReplay)
    257         msg << "\n\n" << "The current game state is different from the original game state.";
    258     else
    259         msg << "\n\n" << "Your game state is " << (expectedHash == hash ? "identical to" : "different from") << " the hosts game state.";
    260 
    261     if (path)
    262         msg << "\n\n" << "Dumping current state to " << CStr(path->string8()).EscapeToPrintableASCII();
    263 
    264     LOGERROR("%s", msg.str());
    265 
    266     if (g_GUI)
    267         g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
    268 }
    269 
    270 void CNetTurnManager::Interpolate(float simFrameLength, float realFrameLength)
    271 {
    272     // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
    273     // we need to save the previous turn length?
    274 
    275     float offset = clamp(m_DeltaSimTime / (m_TurnLength / 1000.f) + 1.0, 0.0, 1.0);
    276 
    277     // Stop animations while still updating the selection highlight
    278     if (m_CurrentTurn > m_FinalTurn)
    279         simFrameLength = 0;
    280 
    281     m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength);
    282 }
    283 
    284 void CNetTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn)
    285 {
    286     NETTURN_LOG((L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn));
    287 
    288     if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
    289     {
    290         debug_warn(L"Received command for invalid turn");
    291         return;
    292     }
    293 
    294     m_Simulation2.GetScriptInterface().FreezeObject(data, true);
    295 
    296     JSContext* cx = m_Simulation2.GetScriptInterface().GetContext();
    297     JSAutoRequest rq(cx);
    298 
    299     m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, cx, data);
    300 }
    301 
    302 void CNetTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
    303 {
    304     NETTURN_LOG((L"FinishedAllCommands(%d, %d)\n", turn, turnLength));
    305 
    306     ENSURE(turn == m_ReadyTurn + 1);
    307     m_ReadyTurn = turn;
    308     m_TurnLength = turnLength;
    309 }
    310 
    311 bool CNetTurnManager::TurnNeedsFullHash(u32 turn)
    312 {
    313     // Check immediately for errors caused by e.g. inconsistent game versions
    314     // (The hash is computed after the first sim update, so we start at turn == 1)
    315     if (turn == 1)
    316         return true;
    317 
    318     // Otherwise check the full state every ~10 seconds in multiplayer games
    319     // (TODO: should probably remove this when we're reasonably sure the game
    320     // isn't too buggy, since the full hash is still pretty slow)
    321     if (turn % 20 == 0)
    322         return true;
    323 
    324     return false;
    325 }
    326 
    327 void CNetTurnManager::EnableTimeWarpRecording(size_t numTurns)
    328 {
    329     m_TimeWarpStates.clear();
    330     m_TimeWarpNumTurns = numTurns;
    331 }
    332 
    333 void CNetTurnManager::RewindTimeWarp()
    334 {
    335     if (m_TimeWarpStates.empty())
    336         return;
    337 
    338     std::stringstream stream(m_TimeWarpStates.back());
    339     m_Simulation2.DeserializeState(stream);
    340     m_TimeWarpStates.pop_back();
    341 
    342     // Reset the turn manager state, so we won't execute stray commands and
    343     // won't do the next snapshot until the appropriate time.
    344     // (Ideally we ought to serialise the turn manager state and restore it
    345     // here, but this is simpler for now.)
    346     ResetState(0, 1);
    347 }
    348 
    349 void CNetTurnManager::QuickSave()
    350 {
    351     TIMER(L"QuickSave");
    352 
    353     std::stringstream stream;
    354     if (!m_Simulation2.SerializeState(stream))
    355     {
    356         LOGERROR("Failed to quicksave game");
    357         return;
    358     }
    359 
    360     m_QuickSaveState = stream.str();
    361     if (g_GUI)
    362         m_QuickSaveMetadata = g_GUI->GetSavedGameData();
    363     else
    364         m_QuickSaveMetadata = std::string();
    365 
    366     LOGMESSAGERENDER("Quicksaved game");
    367 
    368 }
    369 
    370 void CNetTurnManager::QuickLoad()
    371 {
    372     TIMER(L"QuickLoad");
    373 
    374     if (m_QuickSaveState.empty())
    375     {
    376         LOGERROR("Cannot quickload game - no game was quicksaved");
    377         return;
    378     }
    379 
    380     std::stringstream stream(m_QuickSaveState);
    381     if (!m_Simulation2.DeserializeState(stream))
    382     {
    383         LOGERROR("Failed to quickload game");
    384         return;
    385     }
    386 
    387     if (g_GUI && !m_QuickSaveMetadata.empty())
    388         g_GUI->RestoreSavedGameData(m_QuickSaveMetadata);
    389 
    390     LOGMESSAGERENDER("Quickloaded game");
    391 
    392     // See RewindTimeWarp
    393     ResetState(0, 1);
    394 }
    395 
    396 
    397 CNetClientTurnManager::CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay) :
    398     CNetTurnManager(simulation, DEFAULT_TURN_LENGTH_MP, clientId, replay), m_NetClient(client)
    399 {
    400 }
    401 
    402 void CNetClientTurnManager::PostCommand(JS::HandleValue data)
    403 {
    404     NETTURN_LOG((L"PostCommand()\n"));
    405 
    406     // Transmit command to server
    407     CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data);
    408     m_NetClient.SendMessage(&msg);
    409 
    410     // Add to our local queue
    411     //AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
    412     // TODO: we should do this when the server stops sending our commands back to us
    413 }
    414 
    415 void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
    416 {
    417     NETTURN_LOG((L"NotifyFinishedOwnCommands(%d)\n", turn));
    418 
    419     CEndCommandBatchMessage msg;
    420 
    421     msg.m_Turn = turn;
    422 
    423     // The turn-length field of the CEndCommandBatchMessage is currently only relevant
    424     // when sending it from the server to the clients.
    425     // It could be used to verify that the client simulated the correct turn length.
    426     msg.m_TurnLength = 0;
    427 
    428     m_NetClient.SendMessage(&msg);
    429 }
    430 
    431 void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn)
    432 {
    433     bool quick = !TurnNeedsFullHash(turn);
    434     std::string hash;
    435     {
    436         PROFILE3("state hash check");
    437         ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
    438     }
    439 
    440     NETTURN_LOG((L"NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str()));
    441 
    442     m_Replay.Hash(hash, quick);
    443 
    444     // Don't send the hash if OOS
    445     if (m_HasSyncError)
    446         return;
    447 
    448     // Send message to the server
    449     CSyncCheckMessage msg;
    450     msg.m_Turn = turn;
    451     msg.m_Hash = hash;
    452     m_NetClient.SendMessage(&msg);
    453 }
    454 
    455 void CNetClientTurnManager::OnDestroyConnection()
    456 {
    457     NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
    458 }
    459 
    460 void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
    461 {
    462     // Command received from the server - store it for later execution
    463     AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
    464 }
    465 
    466 
    467 CNetLocalTurnManager::CNetLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
    468     CNetTurnManager(simulation, DEFAULT_TURN_LENGTH_SP, 0, replay)
    469 {
    470 }
    471 
    472 void CNetLocalTurnManager::PostCommand(JS::HandleValue data)
    473 {
    474     // Add directly to the next turn, ignoring COMMAND_DELAY,
    475     // because we don't need to compensate for network latency
    476     AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + 1);
    477 }
    478 
    479 void CNetLocalTurnManager::NotifyFinishedOwnCommands(u32 turn)
    480 {
    481     FinishedAllCommands(turn, m_TurnLength);
    482 }
    483 
    484 void CNetLocalTurnManager::NotifyFinishedUpdate(u32 UNUSED(turn))
    485 {
    486 #if 0 // this hurts performance and is only useful for verifying log replays
    487     std::string hash;
    488     {
    489         PROFILE3("state hash check");
    490         ENSURE(m_Simulation2.ComputeStateHash(hash));
    491     }
    492     m_Replay.Hash(hash);
    493 #endif
    494 }
    495 
    496 void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
    497 {
    498     debug_warn(L"This should never be called");
    499 }
    500 
    501 CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
    502     CNetLocalTurnManager(simulation, replay)
    503 {
    504 }
    505 
    506 void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
    507 {
    508     // Using the pair we make sure that commands per turn will be processed in the correct order
    509     m_ReplayCommands[turn].emplace_back(player, command);
    510 }
    511 
    512 void CNetReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick)
    513 {
    514     m_ReplayHash[turn] = std::make_pair(hash, quick);
    515 }
    516 
    517 void CNetReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
    518 {
    519     m_ReplayTurnLengths[turn] = turnLength;
    520 
    521     // Initialize turn length
    522     if (turn == 0)
    523         m_TurnLength = m_ReplayTurnLengths[0];
    524 }
    525 
    526 void CNetReplayTurnManager::StoreFinalReplayTurn(u32 turn)
    527 {
    528     m_FinalTurn = turn;
    529 }
    530 
    531 void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
    532 {
    533     if (turn == 1 && m_FinalTurn == 0)
    534         g_GUI->SendEventToAll("ReplayFinished");
    535 
    536     if (turn > m_FinalTurn)
    537         return;
    538 
    539     DoTurn(turn);
    540 
    541     // Compare hash if it exists in the replay and if we didn't have an OOS already
    542     if (m_HasSyncError || m_ReplayHash.find(turn) == m_ReplayHash.end())
    543         return;
    544 
    545     std::string expectedHash = m_ReplayHash[turn].first;
    546     bool quickHash = m_ReplayHash[turn].second;
    547 
    548     // Compute hash
    549     std::string hash;
    550     ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash));
    551     hash = Hexify(hash);
    552 
    553     if (hash != expectedHash)
    554         DisplayOOSError(turn, hash, expectedHash, true);
    555 }
    556 
    557 void CNetReplayTurnManager::DoTurn(u32 turn)
    558 {
    559     debug_printf("Executing turn %u of %u\n", turn, m_FinalTurn);
    560 
    561     m_TurnLength = m_ReplayTurnLengths[turn];
    562 
    563     JSContext* cx = m_Simulation2.GetScriptInterface().GetContext();
    564     JSAutoRequest rq(cx);
    565 
    566     // Simulate commands for that turn
    567     for (const std::pair<player_id_t, std::string>& p : m_ReplayCommands[turn])
    568     {
    569         JS::RootedValue command(cx);
    570         m_Simulation2.GetScriptInterface().ParseJSON(p.second, &command);
    571         AddCommand(m_ClientId, p.first, command, m_CurrentTurn + 1);
    572     }
    573 
    574     if (turn == m_FinalTurn)
    575         g_GUI->SendEventToAll("ReplayFinished");
    576 }
    577 
    578 CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
    579     m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP), m_HasSyncError(false)
    580 {
    581     // The first turn we will actually execute is number 2,
    582     // so store dummy values into the saved lengths list
    583     m_SavedTurnLengths.push_back(0);
    584     m_SavedTurnLengths.push_back(0);
    585 }
    586 
    587 void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
    588 {
    589     NETTURN_LOG((L"NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn));
    590 
    591     // Must be a client we've already heard of
    592     ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());
    593 
    594     // Clients must advance one turn at a time
    595     ENSURE(turn == m_ClientsReady[client] + 1);
    596     m_ClientsReady[client] = turn;
    597 
    598     // Check whether this was the final client to become ready
    599     CheckClientsReady();
    600 }
    601 
    602 void CNetServerTurnManager::CheckClientsReady()
    603 {
    604     // See if all clients (including self) are ready for a new turn
    605     for (const std::pair<int, u32>& clientReady : m_ClientsReady)
    606     {
    607         NETTURN_LOG((L"  %d: %d <=? %d\n", clientReady.first, clientReady.second, m_ReadyTurn));
    608         if (clientReady.second <= m_ReadyTurn)
    609             return; // wasn't ready for m_ReadyTurn+1
    610     }
    611 
    612     ++m_ReadyTurn;
    613 
    614     NETTURN_LOG((L"CheckClientsReady: ready for turn %d\n", m_ReadyTurn));
    615 
    616     // Tell all clients that the next turn is ready
    617     CEndCommandBatchMessage msg;
    618     msg.m_TurnLength = m_TurnLength;
    619     msg.m_Turn = m_ReadyTurn;
    620     m_NetServer.Broadcast(&msg);
    621 
    622     ENSURE(m_SavedTurnLengths.size() == m_ReadyTurn);
    623     m_SavedTurnLengths.push_back(m_TurnLength);
    624 }
    625 
    626 void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, const CStrW& playername, u32 turn, const CStr& hash)
    627 {
    628     // Clients must advance one turn at a time
    629     ENSURE(turn == m_ClientsSimulated[client] + 1);
    630     m_ClientsSimulated[client] = turn;
    631 
    632     // Check for OOS only if in sync
    633     if (m_HasSyncError)
    634         return;
    635 
    636     m_ClientPlayernames[client] = playername;
    637     m_ClientStateHashes[turn][client] = hash;
    638 
    639     // Find the newest turn which we know all clients have simulated
    640     u32 newest = std::numeric_limits<u32>::max();
    641     for (const std::pair<int, u32>& clientSimulated : m_ClientsSimulated)
    642         if (clientSimulated.second < newest)
    643             newest = clientSimulated.second;
    644 
    645     // For every set of state hashes that all clients have simulated, check for OOS
    646     for (const std::pair<u32, std::map<int, std::string>>& clientStateHash : m_ClientStateHashes)
    647     {
    648         if (clientStateHash.first > newest)
    649             break;
    650 
    651         // Assume the host is correct (maybe we should choose the most common instead to help debugging)
    652         std::string expected = clientStateHash.second.begin()->second;
    653 
    654         // Find all players that are OOS on that turn
    655         std::vector<CStrW> OOSPlayerNames;
    656         for (const std::pair<int, std::string>& hashPair : clientStateHash.second)
    657         {
    658             NETTURN_LOG((L"sync check %d: %d = %hs\n", it->first, cit->first, Hexify(cit->second).c_str()));
    659             if (hashPair.second != expected)
    660             {
    661                 // Oh no, out of sync
    662                 m_HasSyncError = true;
    663                 OOSPlayerNames.push_back(m_ClientPlayernames[hashPair.first]);
    664             }
    665         }
    666 
    667         // Tell everyone about it
    668         if (m_HasSyncError)
    669         {
    670             CSyncErrorMessage msg;
    671             msg.m_Turn = clientStateHash.first;
    672             msg.m_HashExpected = expected;
    673             for (const CStrW& playername : OOSPlayerNames)
    674             {
    675                 CSyncErrorMessage::S_m_PlayerNames h;
    676                 h.m_Name = playername;
    677                 msg.m_PlayerNames.push_back(h);
    678             }
    679             m_NetServer.Broadcast(&msg);
    680             break;
    681         }
    682     }
    683 
    684     // Delete the saved hashes for all turns that we've already verified
    685     m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1));
    686 }
    687 
    688 void CNetServerTurnManager::InitialiseClient(int client, u32 turn)
    689 {
    690     NETTURN_LOG((L"InitialiseClient(client=%d, turn=%d)\n", client, turn));
    691 
    692     ENSURE(m_ClientsReady.find(client) == m_ClientsReady.end());
    693     m_ClientsReady[client] = turn + 1;
    694     m_ClientsSimulated[client] = turn;
    695 }
    696 
    697 void CNetServerTurnManager::UninitialiseClient(int client)
    698 {
    699     NETTURN_LOG((L"UninitialiseClient(client=%d)\n", client));
    700 
    701     ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());
    702     m_ClientsReady.erase(client);
    703     m_ClientsSimulated.erase(client);
    704 
    705     // Check whether we're ready for the next turn now that we're not
    706     // waiting for this client any more
    707     CheckClientsReady();
    708 }
    709 
    710 void CNetServerTurnManager::SetTurnLength(u32 msecs)
    711 {
    712     m_TurnLength = msecs;
    713 }
    714 
    715 u32 CNetServerTurnManager::GetSavedTurnLength(u32 turn)
    716 {
    717     ENSURE(turn <= m_ReadyTurn);
    718     return m_SavedTurnLengths.at(turn);
    719 }
  • source/network/tests/test_Net.h

    diff --git a/source/network/tests/test_Net.h b/source/network/tests/test_Net.h
    index 8f6c58a..51c7d81 100644
    a b  
    2323#include "lib/tex/tex.h"
    2424#include "network/NetServer.h"
    2525#include "network/NetClient.h"
    26 #include "network/NetTurnManager.h"
    2726#include "network/NetMessage.h"
    2827#include "network/NetMessages.h"
    2928#include "ps/CLogger.h"
     
    3332#include "ps/XML/Xeromyces.h"
    3433#include "scriptinterface/ScriptInterface.h"
    3534#include "simulation2/Simulation2.h"
     35#include "simulation2/system/TurnManager.h"
    3636
    3737class TestNetComms : public CxxTest::TestSuite
    3838{
    public:  
    139139        ScriptInterface scriptInterface("Engine", "Test", g_ScriptRuntime);
    140140        JSContext* cx = scriptInterface.GetContext();
    141141        JSAutoRequest rq(cx);
    142        
     142
    143143        TestStdoutLogger logger;
    144144
    145145        std::vector<CNetClient*> clients;
    public:  
    204204        ScriptInterface scriptInterface("Engine", "Test", g_ScriptRuntime);
    205205        JSContext* cx = scriptInterface.GetContext();
    206206        JSAutoRequest rq(cx);
    207        
     207
    208208        TestStdoutLogger logger;
    209209
    210210        std::vector<CNetClient*> clients;
    public:  
    310310        wait(clients, 100);
    311311
    312312        // (This SetTurnLength thing doesn't actually detect errors unless you change
    313         // CNetTurnManager::TurnNeedsFullHash to always return true)
     313        // CTurnManager::TurnNeedsFullHash to always return true)
    314314
    315315        {
    316316            JS::RootedValue cmd(cx);
  • source/ps/Game.cpp

    diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp
    index 48e7930..e0783ae 100644
    a b  
    2929#include "lib/timer.h"
    3030#include "network/NetClient.h"
    3131#include "network/NetServer.h"
    32 #include "network/NetTurnManager.h"
    3332#include "ps/CConsole.h"
    3433#include "ps/CLogger.h"
    3534#include "ps/CStr.h"
     
    4746#include "simulation2/Simulation2.h"
    4847#include "simulation2/components/ICmpPlayer.h"
    4948#include "simulation2/components/ICmpPlayerManager.h"
     49#include "simulation2/system/ReplayTurnManager.h"
    5050#include "soundmanager/ISoundManager.h"
    5151
    5252#include "tools/atlas/GameInterface/GameLoop.h"
    CGame::CGame(bool disableGraphics, bool replayLog):  
    8787    if (m_GameView)
    8888        m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager());
    8989
    90     m_TurnManager = new CNetLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client
     90    m_TurnManager = new CLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client
    9191
    9292    m_Simulation2->LoadDefaultScripts();
    9393}
    CGame::~CGame()  
    110110    delete m_ReplayStream;
    111111}
    112112
    113 void CGame::SetTurnManager(CNetTurnManager* turnManager)
     113void CGame::SetTurnManager(CTurnManager* turnManager)
    114114{
    115115    if (m_TurnManager)
    116116        delete m_TurnManager;
    int CGame::LoadVisualReplayData()  
    127127    ENSURE(!m_ReplayPath.empty());
    128128    ENSURE(m_ReplayStream);
    129129
    130     CNetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager());
     130    CReplayTurnManager* replayTurnMgr = static_cast<CReplayTurnManager*>(GetTurnManager());
    131131
    132132    u32 currentTurn = 0;
    133133    std::string type;
    bool CGame::StartVisualReplay(const std::string& replayPath)  
    175175    m_IsVisualReplay = true;
    176176    ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
    177177
    178     SetTurnManager(new CNetReplayTurnManager(*m_Simulation2, GetReplayLogger()));
     178    SetTurnManager(new CReplayTurnManager(*m_Simulation2, GetReplayLogger()));
    179179
    180180    m_ReplayPath = replayPath;
    181181    m_ReplayStream = new std::ifstream(m_ReplayPath.c_str());
    void CGame::RegisterInit(const JS::HandleValue attribs, const std::string& saved  
    206206    ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
    207207    JSContext* cx = scriptInterface.GetContext();
    208208    JSAutoRequest rq(cx);
    209    
     209
    210210    m_InitialSavedState = savedState;
    211211    m_IsSavedGame = !savedState.empty();
    212212
    PSRETURN CGame::ReallyStartGame()  
    292292{
    293293    JSContext* cx = m_Simulation2->GetScriptInterface().GetContext();
    294294    JSAutoRequest rq(cx);
    295    
     295
    296296    // Call the script function InitGame only for new games, not saved games
    297297    if (!m_IsSavedGame)
    298298    {
    299         // Perform some simulation initializations (replace skirmish entities, explore territories, etc.) 
     299        // Perform some simulation initializations (replace skirmish entities, explore territories, etc.)
    300300        // that needs to be done before setting up the AI and shouldn't be done in Atlas
    301301        if (!g_AtlasGameLoop->running)
    302302            m_Simulation2->PreInitGame();
    PSRETURN CGame::ReallyStartGame()  
    314314    Interpolate(0, 0);
    315315
    316316    m_GameStarted=true;
    317    
     317
    318318    // Render a frame to begin loading assets
    319319    if (CRenderer::IsInitialised())
    320320        Render();
    void CGame::Update(const double deltaRealTime, bool doInterpolate)  
    384384        return;
    385385
    386386    const double deltaSimTime = deltaRealTime * m_SimRate;
    387    
     387
    388388    if (deltaSimTime)
    389389    {
    390390        // To avoid confusing the profiler, we need to trigger the new turn
  • source/ps/Game.h

    diff --git a/source/ps/Game.h b/source/ps/Game.h
    index 862ae4d..58d8e37 100644
    a b  
    2727class CWorld;
    2828class CSimulation2;
    2929class CGameView;
    30 class CNetTurnManager;
     30class CTurnManager;
    3131class IReplayLogger;
    3232struct CColor;
    3333
    class CGame  
    7777     */
    7878    player_id_t m_ViewedPlayerID;
    7979
    80     CNetTurnManager* m_TurnManager;
     80    CTurnManager* m_TurnManager;
    8181
    8282public:
    8383    CGame(bool disableGraphics = false, bool replayLog = true);
    public:  
    185185     * Replace the current turn manager.
    186186     * This class will take ownership of the pointer.
    187187     */
    188     void SetTurnManager(CNetTurnManager* turnManager);
     188    void SetTurnManager(CTurnManager* turnManager);
    189189
    190     CNetTurnManager* GetTurnManager() const
     190    CTurnManager* GetTurnManager() const
    191191    {   return m_TurnManager; }
    192192
    193193    IReplayLogger& GetReplayLogger() const
  • source/ps/Util.h

    diff --git a/source/ps/Util.h b/source/ps/Util.h
    index 4bf6723..96aeea7 100644
    a b  
    1818#ifndef PS_UTIL_H
    1919#define PS_UTIL_H
    2020
     21#include "lib/os_path.h"
    2122#include "lib/file/vfs/vfs_path.h"
    2223
    2324struct Tex;
  • source/simulation2/components/CCmpCommandQueue.cpp

    diff --git a/source/simulation2/components/CCmpCommandQueue.cpp b/source/simulation2/components/CCmpCommandQueue.cpp
    index 18cd4b9..0385695 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2016 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    2323#include "ps/CLogger.h"
    2424#include "ps/Game.h"
    2525#include "ps/Profile.h"
    26 #include "network/NetTurnManager.h"
     26#include "simulation2/system/TurnManager.h"
    2727
    2828class CCmpCommandQueue : public ICmpCommandQueue
    2929{
    public:  
    6666    {
    6767        JSContext* cx = GetSimContext().GetScriptInterface().GetContext();
    6868        JSAutoRequest rq(cx);
    69    
     69
    7070        u32 numCmds;
    7171        deserialize.NumberU32_Unbounded("num commands", numCmds);
    7272        for (size_t i = 0; i < numCmds; ++i)
    public:  
    9191    {
    9292        JSContext* cx = GetSimContext().GetScriptInterface().GetContext();
    9393        JSAutoRequest rq(cx);
    94        
     94
    9595        // TODO: This is a workaround because we need to pass a MutableHandle to StringifyJSON.
    9696        JS::RootedValue cmd(cx, cmd1.get());
    9797
    public:  
    108108        ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
    109109        JSContext* cx = scriptInterface.GetContext();
    110110        JSAutoRequest rq(cx);
    111        
     111
    112112        JS::RootedValue global(cx, scriptInterface.GetGlobalObject());
    113113        std::vector<SimulationCommand> localCommands;
    114114        m_LocalQueue.swap(localCommands);
  • new file source/simulation2/system/LocalTurnManager.cpp

    diff --git a/source/simulation2/system/LocalTurnManager.cpp b/source/simulation2/system/LocalTurnManager.cpp
    new file mode 100644
    index 0000000..e2630a8
    - +  
     1/* Copyright (C) 2016 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 "LocalTurnManager.h"
     21
     22CLocalTurnManager::CLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
     23    CTurnManager(simulation, DEFAULT_TURN_LENGTH_SP, 0, replay)
     24{
     25}
     26
     27void CLocalTurnManager::PostCommand(JS::HandleValue data)
     28{
     29    // Add directly to the next turn, ignoring COMMAND_DELAY,
     30    // because we don't need to compensate for network latency
     31    AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + 1);
     32}
     33
     34void CLocalTurnManager::NotifyFinishedOwnCommands(u32 turn)
     35{
     36    FinishedAllCommands(turn, m_TurnLength);
     37}
     38
     39void CLocalTurnManager::NotifyFinishedUpdate(u32 UNUSED(turn))
     40{
     41#if 0 // this hurts performance and is only useful for verifying log replays
     42    std::string hash;
     43    {
     44        PROFILE3("state hash check");
     45        ENSURE(m_Simulation2.ComputeStateHash(hash));
     46    }
     47    m_Replay.Hash(hash);
     48#endif
     49}
     50
     51void CLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
     52{
     53    debug_warn(L"This should never be called");
     54}
  • new file source/simulation2/system/LocalTurnManager.h

    diff --git a/source/simulation2/system/LocalTurnManager.h b/source/simulation2/system/LocalTurnManager.h
    new file mode 100644
    index 0000000..66c5b74
    - +  
     1/* Copyright (C) 2016 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#ifndef INCLUDED_LOCALTURNMANAGER
     19#define INCLUDED_LOCALTURNMANAGER
     20
     21#include "TurnManager.h"
     22
     23/**
     24 * Implementation of CTurnManager for offline games.
     25 */
     26class CLocalTurnManager : public CTurnManager
     27{
     28public:
     29    CLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay);
     30
     31    void OnSimulationMessage(CSimulationMessage* msg);
     32
     33    void PostCommand(JS::HandleValue data);
     34
     35protected:
     36    void NotifyFinishedOwnCommands(u32 turn);
     37
     38    virtual void NotifyFinishedUpdate(u32 turn);
     39};
     40
     41#endif // INCLUDED_LOCALTURNMANAGER
  • new file source/simulation2/system/ReplayTurnManager.cpp

    diff --git a/source/simulation2/system/ReplayTurnManager.cpp b/source/simulation2/system/ReplayTurnManager.cpp
    new file mode 100644
    index 0000000..e1e13c0
    - +  
     1/* Copyright (C) 2016 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 "ReplayTurnManager.h"
     21
     22#include "gui/GUIManager.h"
     23#include "ps/Util.h"
     24#include "simulation2/Simulation2.h"
     25
     26CReplayTurnManager::CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
     27    CLocalTurnManager(simulation, replay)
     28{
     29}
     30
     31void CReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
     32{
     33    // Using the pair we make sure that commands per turn will be processed in the correct order
     34    m_ReplayCommands[turn].emplace_back(player, command);
     35}
     36
     37void CReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick)
     38{
     39    m_ReplayHash[turn] = std::make_pair(hash, quick);
     40}
     41
     42void CReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
     43{
     44    m_ReplayTurnLengths[turn] = turnLength;
     45
     46    // Initialize turn length
     47    if (turn == 0)
     48        m_TurnLength = m_ReplayTurnLengths[0];
     49}
     50
     51void CReplayTurnManager::StoreFinalReplayTurn(u32 turn)
     52{
     53    m_FinalTurn = turn;
     54}
     55
     56void CReplayTurnManager::NotifyFinishedUpdate(u32 turn)
     57{
     58    if (turn == 1 && m_FinalTurn == 0)
     59        g_GUI->SendEventToAll("ReplayFinished");
     60
     61    if (turn > m_FinalTurn)
     62        return;
     63
     64    DoTurn(turn);
     65
     66    // Compare hash if it exists in the replay and if we didn't have an OOS already
     67    if (m_HasSyncError || m_ReplayHash.find(turn) == m_ReplayHash.end())
     68        return;
     69
     70    std::string expectedHash = m_ReplayHash[turn].first;
     71    bool quickHash = m_ReplayHash[turn].second;
     72
     73    // Compute hash
     74    std::string hash;
     75    ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash));
     76    hash = Hexify(hash);
     77
     78    if (hash != expectedHash)
     79        OnSyncError(turn);
     80}
     81
     82void CReplayTurnManager::DoTurn(u32 turn)
     83{
     84    debug_printf("Executing turn %u of %u\n", turn, m_FinalTurn);
     85
     86    m_TurnLength = m_ReplayTurnLengths[turn];
     87
     88    JSContext* cx = m_Simulation2.GetScriptInterface().GetContext();
     89    JSAutoRequest rq(cx);
     90
     91    // Simulate commands for that turn
     92    for (const std::pair<player_id_t, std::string>& p : m_ReplayCommands[turn])
     93    {
     94        JS::RootedValue command(cx);
     95        m_Simulation2.GetScriptInterface().ParseJSON(p.second, &command);
     96        AddCommand(m_ClientId, p.first, command, m_CurrentTurn + 1);
     97    }
     98
     99    if (turn == m_FinalTurn)
     100        g_GUI->SendEventToAll("ReplayFinished");
     101}
     102
     103void CReplayTurnManager::OnSyncError(u32 turn)
     104{
     105    m_HasSyncError = true;
     106
     107    std::stringstream msg;
     108    msg << "Out of sync on turn " << turn << "\n\n" << "The current game state is different from the original game state.";
     109
     110    LOGERROR("%s", msg.str());
     111
     112    if (g_GUI)
     113        g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
     114}
  • new file source/simulation2/system/ReplayTurnManager.h

    diff --git a/source/simulation2/system/ReplayTurnManager.h b/source/simulation2/system/ReplayTurnManager.h
    new file mode 100644
    index 0000000..ab5886b
    - +  
     1/* Copyright (C) 2016 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#ifndef INCLUDED_REPLAYTURNMANAGER
     19#define INCLUDED_REPLAYTURNMANAGER
     20
     21#include "LocalTurnManager.h"
     22
     23/**
     24 * Implementation of CLocalTurnManager for replay games.
     25 */
     26class CReplayTurnManager : public CLocalTurnManager
     27{
     28public:
     29    CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
     30
     31    void StoreReplayCommand(u32 turn, int player, const std::string& command);
     32
     33    void StoreReplayTurnLength(u32 turn, u32 turnLength);
     34
     35    void StoreReplayHash(u32 turn, const std::string& hash, bool quick);
     36
     37    void StoreFinalReplayTurn(u32 turn);
     38
     39
     40private:
     41    void NotifyFinishedUpdate(u32 turn);
     42
     43    void DoTurn(u32 turn);
     44
     45    void OnSyncError(u32 turn);
     46
     47    // Contains the commands of every player on each turn
     48    std::map<u32, std::vector<std::pair<player_id_t, std::string>>> m_ReplayCommands;
     49
     50    // Contains the length of every turn
     51    std::map<u32, u32> m_ReplayTurnLengths;
     52
     53    // Contains all replay hash values and weather or not the quick hash method was used
     54    std::map<u32, std::pair<std::string, bool>> m_ReplayHash;
     55};
     56
     57#endif // INCLUDED_REPLAYTURNMANAGER
  • new file source/simulation2/system/TurnManager.cpp

    diff --git a/source/simulation2/system/TurnManager.cpp b/source/simulation2/system/TurnManager.cpp
    new file mode 100644
    index 0000000..cd8779f
    - +  
     1/* Copyright (C) 2016 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 "TurnManager.h"
     21
     22#include "gui/GUIManager.h"
     23#include "maths/MathUtil.h"
     24#include "ps/Pyrogenesis.h"
     25#include "ps/Replay.h"
     26#include "ps/Util.h"
     27#include "scriptinterface/ScriptInterface.h"
     28#include "simulation2/Simulation2.h"
     29
     30const u32 DEFAULT_TURN_LENGTH_MP = 500;
     31const u32 DEFAULT_TURN_LENGTH_SP = 200;
     32
     33const int COMMAND_DELAY = 2;
     34
     35#if 0
     36#define NETTURN_LOG(args) debug_printf args
     37#else
     38#define NETTURN_LOG(args)
     39#endif
     40
     41CTurnManager::CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay) :
     42    m_Simulation2(simulation), m_CurrentTurn(0), m_ReadyTurn(1), m_TurnLength(defaultTurnLength), m_DeltaSimTime(0),
     43    m_PlayerId(-1), m_ClientId(clientId), m_HasSyncError(false), m_Replay(replay),
     44    m_TimeWarpNumTurns(0), m_FinalTurn(std::numeric_limits<u32>::max())
     45{
     46    // When we are on turn n, we schedule new commands for n+2.
     47    // We know that all other clients have finished scheduling commands for n (else we couldn't have got here).
     48    // We know we have not yet finished scheduling commands for n+2.
     49    // Hence other clients can be on turn n-1, n, n+1, and no other.
     50    // So they can be sending us commands scheduled for n+1, n+2, n+3.
     51    // So we need a 3-element buffer:
     52    m_QueuedCommands.resize(COMMAND_DELAY + 1);
     53}
     54
     55void CTurnManager::ResetState(u32 newCurrentTurn, u32 newReadyTurn)
     56{
     57    m_CurrentTurn = newCurrentTurn;
     58    m_ReadyTurn = newReadyTurn;
     59    m_DeltaSimTime = 0;
     60    size_t queuedCommandsSize = m_QueuedCommands.size();
     61    m_QueuedCommands.clear();
     62    m_QueuedCommands.resize(queuedCommandsSize);
     63}
     64
     65void CTurnManager::SetPlayerID(int playerId)
     66{
     67    m_PlayerId = playerId;
     68}
     69
     70bool CTurnManager::WillUpdate(float simFrameLength)
     71{
     72    // Keep this in sync with the return value of Update()
     73
     74    if (m_CurrentTurn > m_FinalTurn)
     75        return false;
     76
     77    if (m_DeltaSimTime + simFrameLength < 0)
     78        return false;
     79
     80    if (m_ReadyTurn <= m_CurrentTurn)
     81        return false;
     82
     83    return true;
     84}
     85
     86bool CTurnManager::Update(float simFrameLength, size_t maxTurns)
     87{
     88    if (m_CurrentTurn > m_FinalTurn)
     89        return false;
     90
     91    m_DeltaSimTime += simFrameLength;
     92
     93    // If the game becomes laggy, m_DeltaSimTime increases progressively.
     94    // The engine will fast forward accordingly to catch up.
     95    // To keep the game playable, stop fast forwarding after 2 turn lengths.
     96    m_DeltaSimTime = std::min(m_DeltaSimTime, 2.0f * m_TurnLength / 1000.0f);
     97
     98    // If we haven't reached the next turn yet, do nothing
     99    if (m_DeltaSimTime < 0)
     100        return false;
     101
     102    NETTURN_LOG((L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
     103
     104    // Check that the next turn is ready for execution
     105    if (m_ReadyTurn <= m_CurrentTurn)
     106    {
     107        // Oops, we wanted to start the next turn but it's not ready yet -
     108        // there must be too much network lag.
     109        // TODO: complain to the user.
     110        // TODO: send feedback to the server to increase the turn length.
     111
     112        // Reset the next-turn timer to 0 so we try again next update but
     113        // so we don't rush to catch up in subsequent turns.
     114        // TODO: we should do clever rate adjustment instead of just pausing like this.
     115        m_DeltaSimTime = 0;
     116
     117        return false;
     118    }
     119
     120    maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn
     121
     122    for (size_t i = 0; i < maxTurns; ++i)
     123    {
     124        // Check that we've reached the i'th next turn
     125        if (m_DeltaSimTime < 0)
     126            break;
     127
     128        // Check that the i'th next turn is still ready
     129        if (m_ReadyTurn <= m_CurrentTurn)
     130            break;
     131
     132        NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
     133
     134        // Increase now, so Update can send new commands for a subsequent turn
     135        ++m_CurrentTurn;
     136
     137        // Clean up any destroyed entities since the last turn (e.g. placement previews
     138        // or rally point flags generated by the GUI). (Must do this before the time warp
     139        // serialization.)
     140        m_Simulation2.FlushDestroyedEntities();
     141
     142        // Save the current state for rewinding, if enabled
     143        if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0)
     144        {
     145            PROFILE3("time warp serialization");
     146            std::stringstream stream;
     147            m_Simulation2.SerializeState(stream);
     148            m_TimeWarpStates.push_back(stream.str());
     149        }
     150
     151        // Put all the client commands into a single list, in a globally consistent order
     152        std::vector<SimulationCommand> commands;
     153        for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
     154            commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
     155
     156        m_QueuedCommands.pop_front();
     157        m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
     158
     159        m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
     160
     161        NETTURN_LOG((L"Running %d cmds\n", commands.size()));
     162
     163        m_Simulation2.Update(m_TurnLength, commands);
     164
     165        NotifyFinishedUpdate(m_CurrentTurn);
     166
     167        // Set the time for the next turn update
     168        m_DeltaSimTime -= m_TurnLength / 1000.f;
     169    }
     170
     171    return true;
     172}
     173
     174bool CTurnManager::UpdateFastForward()
     175{
     176    m_DeltaSimTime = 0;
     177
     178    NETTURN_LOG((L"UpdateFastForward current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
     179
     180    // Check that the next turn is ready for execution
     181    if (m_ReadyTurn <= m_CurrentTurn)
     182        return false;
     183
     184    while (m_ReadyTurn > m_CurrentTurn)
     185    {
     186        // TODO: It would be nice to remove some of the duplication with Update()
     187        // (This is similar but doesn't call any Notify functions or update DeltaTime,
     188        // it just updates the simulation state)
     189
     190        ++m_CurrentTurn;
     191
     192        m_Simulation2.FlushDestroyedEntities();
     193
     194        // Put all the client commands into a single list, in a globally consistent order
     195        std::vector<SimulationCommand> commands;
     196        for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
     197            commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
     198
     199        m_QueuedCommands.pop_front();
     200        m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
     201
     202        m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
     203
     204        NETTURN_LOG((L"Running %d cmds\n", commands.size()));
     205
     206        m_Simulation2.Update(m_TurnLength, commands);
     207    }
     208
     209    return true;
     210}
     211
     212void CTurnManager::Interpolate(float simFrameLength, float realFrameLength)
     213{
     214    // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
     215    // we need to save the previous turn length?
     216
     217    float offset = clamp(m_DeltaSimTime / (m_TurnLength / 1000.f) + 1.0, 0.0, 1.0);
     218
     219    // Stop animations while still updating the selection highlight
     220    if (m_CurrentTurn > m_FinalTurn)
     221        simFrameLength = 0;
     222
     223    m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength);
     224}
     225
     226void CTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn)
     227{
     228    NETTURN_LOG((L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn));
     229
     230    if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
     231    {
     232        debug_warn(L"Received command for invalid turn");
     233        return;
     234    }
     235
     236    m_Simulation2.GetScriptInterface().FreezeObject(data, true);
     237
     238    JSContext* cx = m_Simulation2.GetScriptInterface().GetContext();
     239    JSAutoRequest rq(cx);
     240
     241    m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, cx, data);
     242}
     243
     244void CTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
     245{
     246    NETTURN_LOG((L"FinishedAllCommands(%d, %d)\n", turn, turnLength));
     247
     248    ENSURE(turn == m_ReadyTurn + 1);
     249    m_ReadyTurn = turn;
     250    m_TurnLength = turnLength;
     251}
     252
     253bool CTurnManager::TurnNeedsFullHash(u32 turn)
     254{
     255    // Check immediately for errors caused by e.g. inconsistent game versions
     256    // (The hash is computed after the first sim update, so we start at turn == 1)
     257    if (turn == 1)
     258        return true;
     259
     260    // Otherwise check the full state every ~10 seconds in multiplayer games
     261    // (TODO: should probably remove this when we're reasonably sure the game
     262    // isn't too buggy, since the full hash is still pretty slow)
     263    if (turn % 20 == 0)
     264        return true;
     265
     266    return false;
     267}
     268
     269void CTurnManager::EnableTimeWarpRecording(size_t numTurns)
     270{
     271    m_TimeWarpStates.clear();
     272    m_TimeWarpNumTurns = numTurns;
     273}
     274
     275void CTurnManager::RewindTimeWarp()
     276{
     277    if (m_TimeWarpStates.empty())
     278        return;
     279
     280    std::stringstream stream(m_TimeWarpStates.back());
     281    m_Simulation2.DeserializeState(stream);
     282    m_TimeWarpStates.pop_back();
     283
     284    // Reset the turn manager state, so we won't execute stray commands and
     285    // won't do the next snapshot until the appropriate time.
     286    // (Ideally we ought to serialise the turn manager state and restore it
     287    // here, but this is simpler for now.)
     288    ResetState(0, 1);
     289}
     290
     291void CTurnManager::QuickSave()
     292{
     293    TIMER(L"QuickSave");
     294
     295    std::stringstream stream;
     296    if (!m_Simulation2.SerializeState(stream))
     297    {
     298        LOGERROR("Failed to quicksave game");
     299        return;
     300    }
     301
     302    m_QuickSaveState = stream.str();
     303    if (g_GUI)
     304        m_QuickSaveMetadata = g_GUI->GetSavedGameData();
     305    else
     306        m_QuickSaveMetadata = std::string();
     307
     308    LOGMESSAGERENDER("Quicksaved game");
     309
     310}
     311
     312void CTurnManager::QuickLoad()
     313{
     314    TIMER(L"QuickLoad");
     315
     316    if (m_QuickSaveState.empty())
     317    {
     318        LOGERROR("Cannot quickload game - no game was quicksaved");
     319        return;
     320    }
     321
     322    std::stringstream stream(m_QuickSaveState);
     323    if (!m_Simulation2.DeserializeState(stream))
     324    {
     325        LOGERROR("Failed to quickload game");
     326        return;
     327    }
     328
     329    if (g_GUI && !m_QuickSaveMetadata.empty())
     330        g_GUI->RestoreSavedGameData(m_QuickSaveMetadata);
     331
     332    LOGMESSAGERENDER("Quickloaded game");
     333
     334    // See RewindTimeWarp
     335    ResetState(0, 1);
     336}
  • .h

    diff --git a/source/network/NetTurnManager.h b/source/simulation2/system/TurnManager.h
    similarity index 52%
    rename from source/network/NetTurnManager.h
    rename to source/simulation2/system/TurnManager.h
    index 806fe77..0116738 100644
    old new  
    1515 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    1616 */
    1717
    18 #ifndef INCLUDED_NETTURNMANAGER
    19 #define INCLUDED_NETTURNMANAGER
     18#ifndef INCLUDED_TURNMANAGER
     19#define INCLUDED_TURNMANAGER
    2020
    2121#include "simulation2/helpers/SimulationCommand.h"
    22 #include "lib/os_path.h"
    23 #include "NetMessage.h"
    2422
    2523#include <list>
    2624#include <map>
    2725#include <vector>
    2826
    29 extern const u32 DEFAULT_TURN_LENGTH_MP;
    3027extern const u32 DEFAULT_TURN_LENGTH_SP;
     28extern const u32 DEFAULT_TURN_LENGTH_MP;
     29
     30extern const int COMMAND_DELAY;
    3131
    32 class CNetServerWorker;
    33 class CNetClient;
    3432class CSimulationMessage;
    3533class CSimulation2;
    3634class IReplayLogger;
    3735
    3836/*
    39  * This file deals with the logic of the network turn system. The basic idea is as in
     37 * This file deals with the logic of the turn system. The basic idea is as in
    4038 * http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1
    4139 *
    4240 * Each player performs the simulation for turn N.
    class IReplayLogger;  
    5351 */
    5452
    5553/**
    56  * Common network turn system (used by clients and offline games).
     54 * Common turn system (used by clients and offline games).
    5755 */
    58 class CNetTurnManager
     56class CTurnManager
    5957{
    60     NONCOPYABLE(CNetTurnManager);
     58    NONCOPYABLE(CTurnManager);
    6159public:
    6260    /**
    6361     * Construct for a given network session ID.
    6462     */
    65     CNetTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay);
     63    CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay);
    6664
    67     virtual ~CNetTurnManager() { }
     65    virtual ~CTurnManager() { }
    6866
    6967    void ResetState(u32 newCurrentTurn, u32 newReadyTurn);
    7068
    public:  
    109107    virtual void OnSimulationMessage(CSimulationMessage* msg) = 0;
    110108
    111109    /**
    112      * Called when there has been an out-of-sync error.
    113      */
    114     virtual void OnSyncError(u32 turn, const CStr& expectedHash, std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames);
    115 
    116     /**
    117      * Shows a message box when an out of sync error has been detected in the session or visual replay.
    118      */
    119     virtual void DisplayOOSError(u32 turn, const CStr& hash, const CStr& expectedHash, bool isReplay, std::vector<CSyncErrorMessage::S_m_PlayerNames>* playerNames, OsPath* path);
    120 
    121     /**
    122110     * Called by simulation code, to add a new command to be distributed to all clients and executed soon.
    123111     */
    124112    virtual void PostCommand(JS::HandleValue data) = 0;
    private:  
    203191    std::string m_QuickSaveMetadata;
    204192};
    205193
    206 
    207 /**
    208  * Implementation of CNetTurnManager for network clients.
    209  */
    210 class CNetClientTurnManager : public CNetTurnManager
    211 {
    212 public:
    213     CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay);
    214 
    215     virtual void OnSimulationMessage(CSimulationMessage* msg);
    216 
    217     virtual void PostCommand(JS::HandleValue data);
    218 
    219     /**
    220      * Notifiy the server that all commands are sent to prepare the connection for termination.
    221      */
    222     void OnDestroyConnection();
    223 
    224 protected:
    225     virtual void NotifyFinishedOwnCommands(u32 turn);
    226 
    227     virtual void NotifyFinishedUpdate(u32 turn);
    228 
    229     CNetClient& m_NetClient;
    230 };
    231 
    232 /**
    233  * Implementation of CNetTurnManager for offline games.
    234  */
    235 class CNetLocalTurnManager : public CNetTurnManager
    236 {
    237 public:
    238     CNetLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay);
    239 
    240     virtual void OnSimulationMessage(CSimulationMessage* msg);
    241 
    242     virtual void PostCommand(JS::HandleValue data);
    243 
    244 protected:
    245     virtual void NotifyFinishedOwnCommands(u32 turn);
    246 
    247     virtual void NotifyFinishedUpdate(u32 turn);
    248 };
    249 
    250 
    251 
    252 /**
    253  * Implementation of CNetTurnManager for replay games.
    254  */
    255 class CNetReplayTurnManager : public CNetLocalTurnManager
    256 {
    257 public:
    258     CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
    259 
    260     void StoreReplayCommand(u32 turn, int player, const std::string& command);
    261 
    262     void StoreReplayTurnLength(u32 turn, u32 turnLength);
    263 
    264     void StoreReplayHash(u32 turn, const std::string& hash, bool quick);
    265 
    266     void StoreFinalReplayTurn(u32 turn);
    267 
    268 
    269 protected:
    270     virtual void NotifyFinishedUpdate(u32 turn);
    271 
    272     void DoTurn(u32 turn);
    273 
    274     // Contains the commands of every player on each turn
    275     std::map<u32, std::vector<std::pair<player_id_t, std::string>>> m_ReplayCommands;
    276 
    277     // Contains the length of every turn
    278     std::map<u32, u32> m_ReplayTurnLengths;
    279 
    280     // Contains all replay hash values and weather or not the quick hash method was used
    281     std::map<u32, std::pair<std::string, bool>> m_ReplayHash;
    282 };
    283 /**
    284  * The server-side counterpart to CNetClientTurnManager.
    285  * Records the turn state of each client, and sends turn advancement messages
    286  * when all clients are ready.
    287  *
    288  * Thread-safety:
    289  * - This is constructed and used by CNetServerWorker in the network server thread.
    290  */
    291 class CNetServerTurnManager
    292 {
    293     NONCOPYABLE(CNetServerTurnManager);
    294 public:
    295     CNetServerTurnManager(CNetServerWorker& server);
    296 
    297     void NotifyFinishedClientCommands(int client, u32 turn);
    298 
    299     void NotifyFinishedClientUpdate(int client, const CStrW& playername, u32 turn, const CStr& hash);
    300 
    301     /**
    302      * Inform the turn manager of a new client who will be sending commands.
    303      */
    304     void InitialiseClient(int client, u32 turn);
    305 
    306     /**
    307      * Inform the turn manager that a previously-initialised client has left the game
    308      * and will no longer be sending commands.
    309      */
    310     void UninitialiseClient(int client);
    311 
    312     void SetTurnLength(u32 msecs);
    313 
    314     /**
    315      * Returns the latest turn for which all clients are ready;
    316      * they will have already been told to execute this turn.
    317      */
    318     u32 GetReadyTurn() { return m_ReadyTurn; }
    319 
    320     /**
    321      * Returns the turn length that was used for the given turn.
    322      * Requires turn <= GetReadyTurn().
    323      */
    324     u32 GetSavedTurnLength(u32 turn);
    325 
    326 protected:
    327     void CheckClientsReady();
    328 
    329     /// The latest turn for which we have received all commands from all clients
    330     u32 m_ReadyTurn;
    331 
    332     // Client ID -> ready turn number (the latest turn for which all commands have been received from that client)
    333     std::map<int, u32> m_ClientsReady;
    334 
    335     // Client ID -> last known simulated turn number (for which we have the state hash)
    336     // (the client has reached the start of this turn, not done the update for it yet)
    337     std::map<int, u32> m_ClientsSimulated;
    338 
    339     // Map of turn -> {Client ID -> state hash}; old indexes <= min(m_ClientsSimulated) are deleted
    340     std::map<u32, std::map<int, std::string>> m_ClientStateHashes;
    341 
    342     // Map of client ID -> playername
    343     std::map<u32, CStrW> m_ClientPlayernames;
    344 
    345     // Current turn length
    346     u32 m_TurnLength;
    347 
    348     // Turn lengths for all previously executed turns
    349     std::vector<u32> m_SavedTurnLengths;
    350 
    351     CNetServerWorker& m_NetServer;
    352 
    353     bool m_HasSyncError;
    354 };
    355 
    356 #endif // INCLUDED_NETTURNMANAGER
     194#endif // INCLUDED_TURNMANAGER