This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

Ticket #4095: split.4.patch

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

    diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
    index 2448e40c8b..6b14ceb221 100644
    a b  
    1 /* Copyright (C) 2016 Wildfire Games.
     1/* Copyright (C) 2017 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
     
    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 aa23532ad0..b33dac382e 100644
    a b  
    1 /* Copyright (C) 2016 Wildfire Games.
     1/* Copyright (C) 2017 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
     
    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 0000000000..7da290cc0e
    - +  
     1/* Copyright (C) 2017 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(...) debug_printf(__VA_ARGS__)
     32#else
     33#define NETCLIENTTURN_LOG(...)
     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("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("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("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("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
     125    std::stringstream msg;
     126    msg << "Out of sync on turn " << turn;
     127
     128    for (size_t i = 0; i < playerNames.size(); ++i)
     129        msg << (i == 0 ? "\nPlayers: " : ", ") << utf8_from_wstring(playerNames[i].m_Name);
     130
     131    msg << "\n\n" << "Your game state is " << (expectedHash == hash ? "identical to" : "different from") << " the hosts game state.";
     132
     133    msg << "\n\n" << "Dumping current state to " << CStr(path.string8()).EscapeToPrintableASCII();
     134
     135    LOGERROR("%s", msg.str());
     136
     137    if (g_GUI)
     138        g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
     139}
  • new file source/network/NetClientTurnManager.h

    diff --git a/source/network/NetClientTurnManager.h b/source/network/NetClientTurnManager.h
    new file mode 100644
    index 0000000000..c2f3d6ddfc
    - +  
     1/* Copyright (C) 2017 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{
     31    NONCOPYABLE(CNetClientTurnManager);
     32public:
     33    CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay);
     34
     35    void OnSimulationMessage(CSimulationMessage* msg) override;
     36
     37    void PostCommand(JS::HandleValue data) override;
     38
     39    /**
     40     * Notify the server that all commands are sent to prepare the connection for termination.
     41     */
     42    void OnDestroyConnection();
     43
     44    void OnSyncError(u32 turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames);
     45
     46private:
     47    void NotifyFinishedOwnCommands(u32 turn) override;
     48
     49    void NotifyFinishedUpdate(u32 turn) override;
     50
     51    CNetClient& m_NetClient;
     52};
     53
     54#endif // INCLUDED_NETCLIENTTURNMANAGER
  • source/network/NetServer.cpp

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

    diff --git a/source/ps/Game.h b/source/ps/Game.h
    index 862ae4deb4..bfccb8d542 100644
    a b  
    1 /* Copyright (C) 2016 Wildfire Games.
     1/* Copyright (C) 2017 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
     
    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 4bf67239ad..d61ce48b97 100644
    a b  
    1 /* Copyright (C) 2016 Wildfire Games.
     1/* Copyright (C) 2017 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
     
    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 0dc91e22e4..90d4d3e5eb 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2017 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{
  • 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 0000000000..f6fe834a17
    - +  
     1/* Copyright (C) 2017 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 0000000000..f7ba298e0c
    - +  
     1/* Copyright (C) 2017 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) override;
     32
     33    void PostCommand(JS::HandleValue data) override;
     34
     35protected:
     36    void NotifyFinishedOwnCommands(u32 turn) override;
     37
     38    virtual void NotifyFinishedUpdate(u32 turn) override;
     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 0000000000..b1ce296896
    - +  
     1/* Copyright (C) 2017 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    std::map<u32, std::pair<std::string, bool>>::iterator turnHashIt = m_ReplayHash.find(turn);
     68    if (m_HasSyncError || turnHashIt == m_ReplayHash.end())
     69        return;
     70
     71    std::string expectedHash = turnHashIt->second.first;
     72    bool quickHash = turnHashIt->second.second;
     73
     74    // Compute hash
     75    std::string hash;
     76    ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash));
     77    hash = Hexify(hash);
     78
     79    if (hash != expectedHash)
     80        OnSyncError(turn);
     81}
     82
     83void CReplayTurnManager::DoTurn(u32 turn)
     84{
     85    debug_printf("Executing turn %u of %u\n", turn, m_FinalTurn);
     86
     87    m_TurnLength = m_ReplayTurnLengths[turn];
     88
     89    JSContext* cx = m_Simulation2.GetScriptInterface().GetContext();
     90    JSAutoRequest rq(cx);
     91
     92    // Simulate commands for that turn
     93    for (const std::pair<player_id_t, std::string>& p : m_ReplayCommands[turn])
     94    {
     95        JS::RootedValue command(cx);
     96        m_Simulation2.GetScriptInterface().ParseJSON(p.second, &command);
     97        AddCommand(m_ClientId, p.first, command, m_CurrentTurn + 1);
     98    }
     99
     100    if (turn == m_FinalTurn)
     101        g_GUI->SendEventToAll("ReplayFinished");
     102}
     103
     104void CReplayTurnManager::OnSyncError(u32 turn)
     105{
     106    m_HasSyncError = true;
     107
     108    std::stringstream msg;
     109    msg << "Out of sync on turn " << turn << "\n\n" << "The current game state is different from the original game state.";
     110
     111    LOGERROR("%s", msg.str());
     112
     113    if (g_GUI)
     114        g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
     115}
  • 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 0000000000..71ad11b7da
    - +  
     1/* Copyright (C) 2017 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
     39private:
     40    void NotifyFinishedUpdate(u32 turn) override;
     41
     42    void DoTurn(u32 turn);
     43
     44    void OnSyncError(u32 turn);
     45
     46    // Contains the commands of every player on each turn
     47    std::map<u32, std::vector<std::pair<player_id_t, std::string>>> m_ReplayCommands;
     48
     49    // Contains the length of every turn
     50    std::map<u32, u32> m_ReplayTurnLengths;
     51
     52    // Contains all replay hash values and weather or not the quick hash method was used
     53    std::map<u32, std::pair<std::string, bool>> m_ReplayHash;
     54};
     55
     56#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 0000000000..9a518d82c8
    - +  
     1/* Copyright (C) 2017 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(...) debug_printf(__VA_ARGS__)
     37#else
     38#define NETTURN_LOG(...)
     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),
     43    m_PlayerId(-1), m_ClientId(clientId), m_DeltaSimTime(0), m_HasSyncError(false), m_Replay(replay),
     44    m_FinalTurn(std::numeric_limits<u32>::max()), m_TimeWarpNumTurns(0)
     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) const
     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("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("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("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("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("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("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) const
     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 51%
    rename from source/network/NetTurnManager.h
    rename to source/simulation2/system/TurnManager.h
    index 806fe77d7c..47695023c6 100644
    old new  
    1 /* Copyright (C) 2016 Wildfire Games.
     1/* Copyright (C) 2017 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
     
    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:  
    9492     * Returns whether Update(simFrameLength, ...) will process at least one new turn.
    9593     * @param simFrameLength Length of the previous frame, in simulation seconds
    9694     */
    97     bool WillUpdate(float simFrameLength);
     95    bool WillUpdate(float simFrameLength) const;
    9896
    9997    /**
    10098     * Advance the graphics by a certain time.
    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;
    protected:  
    166154     * Returns whether we should compute a complete state hash for the given turn,
    167155     * instead of a quick less-complete hash.
    168156     */
    169     bool TurnNeedsFullHash(u32 turn);
     157    bool TurnNeedsFullHash(u32 turn) const;
    170158
    171159    CSimulation2& m_Simulation2;
    172160
    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