Ticket #4095: split.4.patch
File split.4.patch, 72.2 KB (added by , 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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 37 37 #include "lobby/scripting/JSInterface_Lobby.h" 38 38 #include "maths/FixedVector3D.h" 39 39 #include "network/NetClient.h" 40 #include "network/NetMessage.h" 40 41 #include "network/NetServer.h" 41 #include "network/NetTurnManager.h"42 42 #include "ps/CConsole.h" 43 43 #include "ps/CLogger.h" 44 44 #include "ps/Errors.h" … … 70 70 #include "simulation2/components/ICmpSelectable.h" 71 71 #include "simulation2/components/ICmpTemplateManager.h" 72 72 #include "simulation2/helpers/Selection.h" 73 #include "simulation2/system/TurnManager.h" 73 74 #include "soundmanager/SoundManager.h" 74 75 #include "soundmanager/scripting/JSInterface_Sound.h" 75 76 #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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 19 19 20 20 #include "NetClient.h" 21 21 22 #include "NetClientTurnManager.h" 22 23 #include "NetMessage.h" 23 24 #include "NetSession.h" 24 #include "NetTurnManager.h"25 25 26 26 #include "lib/byte_order.h" 27 27 #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 36 CNetClientTurnManager::CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay) 37 : CTurnManager(simulation, DEFAULT_TURN_LENGTH_MP, clientId, replay), m_NetClient(client) 38 { 39 } 40 41 void 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 54 void 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 70 void 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 94 void CNetClientTurnManager::OnDestroyConnection() 95 { 96 NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY); 97 } 98 99 void 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 105 void 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 24 class CNetClient; 25 26 /** 27 * Implementation of CTurnManager for network clients. 28 */ 29 class CNetClientTurnManager : public CTurnManager 30 { 31 NONCOPYABLE(CNetClientTurnManager); 32 public: 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 46 private: 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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 22 22 #include "NetClient.h" 23 23 #include "NetMessage.h" 24 24 #include "NetSession.h" 25 #include "NetServerTurnManager.h" 25 26 #include "NetStats.h" 26 #include "NetTurnManager.h"27 27 28 28 #include "lib/external_libraries/enet.h" 29 29 #include "ps/CLogger.h" … … 32 32 #include "scriptinterface/ScriptInterface.h" 33 33 #include "scriptinterface/ScriptRuntime.h" 34 34 #include "simulation2/Simulation2.h" 35 #include "simulation2/system/TurnManager.h" 35 36 36 37 #if CONFIG2_MINIUPNPC 37 38 #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 32 CNetServerTurnManager::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 41 void 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 56 void 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 80 void 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 142 void 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 151 void 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 164 void CNetServerTurnManager::SetTurnLength(u32 msecs) 165 { 166 m_TurnLength = msecs; 167 } 168 169 u32 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 24 class 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 */ 34 class CNetServerTurnManager 35 { 36 NONCOPYABLE(CNetServerTurnManager); 37 public: 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 69 private: 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 modify5 * it under the terms of the GNU General Public License as published by6 * the Free Software Foundation, either version 2 of the License, or7 * (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 of11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12 * GNU General Public License for more details.13 *14 * You should have received a copy of the GNU General Public License15 * 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 046 #define NETTURN_LOG(args) debug_printf args47 #else48 #define NETTURN_LOG(args)49 #endif50 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 nothing109 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 execution115 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 but123 // 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 turn131 132 for (size_t i = 0; i < maxTurns; ++i)133 {134 // Check that we've reached the i'th next turn135 if (m_DeltaSimTime < 0)136 break;137 138 // Check that the i'th next turn is still ready139 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 turn145 ++m_CurrentTurn;146 147 // Clean up any destroyed entities since the last turn (e.g. placement previews148 // or rally point flags generated by the GUI). (Must do this before the time warp149 // serialization.)150 m_Simulation2.FlushDestroyedEntities();151 152 // Save the current state for rewinding, if enabled153 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 order162 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 update178 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 execution191 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 order205 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 time227 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 else259 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 - maybe273 // 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 highlight278 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 versions314 // (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 games319 // (TODO: should probably remove this when we're reasonably sure the game320 // 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 and343 // won't do the next snapshot until the appropriate time.344 // (Ideally we ought to serialise the turn manager state and restore it345 // 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 else364 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 RewindTimeWarp393 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 server407 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 queue411 //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 us413 }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 relevant424 // 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 OOS445 if (m_HasSyncError)446 return;447 448 // Send message to the server449 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 execution463 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 latency476 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 replays487 std::string hash;488 {489 PROFILE3("state hash check");490 ENSURE(m_Simulation2.ComputeStateHash(hash));491 }492 m_Replay.Hash(hash);493 #endif494 }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 order509 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 length522 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 already542 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 hash549 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 turn567 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 list583 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 of592 ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());593 594 // Clients must advance one turn at a time595 ENSURE(turn == m_ClientsReady[client] + 1);596 m_ClientsReady[client] = turn;597 598 // Check whether this was the final client to become ready599 CheckClientsReady();600 }601 602 void CNetServerTurnManager::CheckClientsReady()603 {604 // See if all clients (including self) are ready for a new turn605 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+1610 }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 ready617 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 time629 ENSURE(turn == m_ClientsSimulated[client] + 1);630 m_ClientsSimulated[client] = turn;631 632 // Check for OOS only if in sync633 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 simulated640 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 OOS646 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 turn655 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 sync662 m_HasSyncError = true;663 OOSPlayerNames.push_back(m_ClientPlayernames[hashPair.first]);664 }665 }666 667 // Tell everyone about it668 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 verified685 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 not706 // waiting for this client any more707 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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 23 23 #include "lib/tex/tex.h" 24 24 #include "network/NetServer.h" 25 25 #include "network/NetClient.h" 26 #include "network/NetTurnManager.h"27 26 #include "network/NetMessage.h" 28 27 #include "network/NetMessages.h" 29 28 #include "ps/CLogger.h" … … 33 32 #include "ps/XML/Xeromyces.h" 34 33 #include "scriptinterface/ScriptInterface.h" 35 34 #include "simulation2/Simulation2.h" 35 #include "simulation2/system/TurnManager.h" 36 36 37 37 class TestNetComms : public CxxTest::TestSuite 38 38 { … … public: 310 310 wait(clients, 100); 311 311 312 312 // (This SetTurnLength thing doesn't actually detect errors unless you change 313 // C NetTurnManager::TurnNeedsFullHash to always return true)313 // CTurnManager::TurnNeedsFullHash to always return true) 314 314 315 315 { 316 316 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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 29 29 #include "lib/timer.h" 30 30 #include "network/NetClient.h" 31 31 #include "network/NetServer.h" 32 #include "network/NetTurnManager.h"33 32 #include "ps/CConsole.h" 34 33 #include "ps/CLogger.h" 35 34 #include "ps/CStr.h" … … 47 46 #include "simulation2/Simulation2.h" 48 47 #include "simulation2/components/ICmpPlayer.h" 49 48 #include "simulation2/components/ICmpPlayerManager.h" 49 #include "simulation2/system/ReplayTurnManager.h" 50 50 #include "soundmanager/ISoundManager.h" 51 51 52 52 #include "tools/atlas/GameInterface/GameLoop.h" … … CGame::CGame(bool disableGraphics, bool replayLog): 87 87 if (m_GameView) 88 88 m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager()); 89 89 90 m_TurnManager = new C NetLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client90 m_TurnManager = new CLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client 91 91 92 92 m_Simulation2->LoadDefaultScripts(); 93 93 } … … CGame::~CGame() 110 110 delete m_ReplayStream; 111 111 } 112 112 113 void CGame::SetTurnManager(C NetTurnManager* turnManager)113 void CGame::SetTurnManager(CTurnManager* turnManager) 114 114 { 115 115 if (m_TurnManager) 116 116 delete m_TurnManager; … … int CGame::LoadVisualReplayData() 127 127 ENSURE(!m_ReplayPath.empty()); 128 128 ENSURE(m_ReplayStream); 129 129 130 C NetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager());130 CReplayTurnManager* replayTurnMgr = static_cast<CReplayTurnManager*>(GetTurnManager()); 131 131 132 132 u32 currentTurn = 0; 133 133 std::string type; … … bool CGame::StartVisualReplay(const std::string& replayPath) 175 175 m_IsVisualReplay = true; 176 176 ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface(); 177 177 178 SetTurnManager(new C NetReplayTurnManager(*m_Simulation2, GetReplayLogger()));178 SetTurnManager(new CReplayTurnManager(*m_Simulation2, GetReplayLogger())); 179 179 180 180 m_ReplayPath = replayPath; 181 181 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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 27 27 class CWorld; 28 28 class CSimulation2; 29 29 class CGameView; 30 class C NetTurnManager;30 class CTurnManager; 31 31 class IReplayLogger; 32 32 struct CColor; 33 33 … … class CGame 77 77 */ 78 78 player_id_t m_ViewedPlayerID; 79 79 80 C NetTurnManager* m_TurnManager;80 CTurnManager* m_TurnManager; 81 81 82 82 public: 83 83 CGame(bool disableGraphics = false, bool replayLog = true); … … public: 185 185 * Replace the current turn manager. 186 186 * This class will take ownership of the pointer. 187 187 */ 188 void SetTurnManager(C NetTurnManager* turnManager);188 void SetTurnManager(CTurnManager* turnManager); 189 189 190 C NetTurnManager* GetTurnManager() const190 CTurnManager* GetTurnManager() const 191 191 { return m_TurnManager; } 192 192 193 193 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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 18 18 #ifndef PS_UTIL_H 19 19 #define PS_UTIL_H 20 20 21 #include "lib/os_path.h" 21 22 #include "lib/file/vfs/vfs_path.h" 22 23 23 24 struct 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) 201 0Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 23 23 #include "ps/CLogger.h" 24 24 #include "ps/Game.h" 25 25 #include "ps/Profile.h" 26 #include " network/NetTurnManager.h"26 #include "simulation2/system/TurnManager.h" 27 27 28 28 class CCmpCommandQueue : public ICmpCommandQueue 29 29 { -
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 22 CLocalTurnManager::CLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay) 23 : CTurnManager(simulation, DEFAULT_TURN_LENGTH_SP, 0, replay) 24 { 25 } 26 27 void 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 34 void CLocalTurnManager::NotifyFinishedOwnCommands(u32 turn) 35 { 36 FinishedAllCommands(turn, m_TurnLength); 37 } 38 39 void 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 51 void 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 */ 26 class CLocalTurnManager : public CTurnManager 27 { 28 public: 29 CLocalTurnManager(CSimulation2& simulation, IReplayLogger& replay); 30 31 void OnSimulationMessage(CSimulationMessage* msg) override; 32 33 void PostCommand(JS::HandleValue data) override; 34 35 protected: 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 26 CReplayTurnManager::CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) 27 : CLocalTurnManager(simulation, replay) 28 { 29 } 30 31 void 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 37 void CReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick) 38 { 39 m_ReplayHash[turn] = std::make_pair(hash, quick); 40 } 41 42 void 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 51 void CReplayTurnManager::StoreFinalReplayTurn(u32 turn) 52 { 53 m_FinalTurn = turn; 54 } 55 56 void 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 83 void 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 104 void 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 */ 26 class CReplayTurnManager : public CLocalTurnManager 27 { 28 public: 29 CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay); 30 31 void StoreReplayCommand(u32 turn, int player, const std::string& command); 32 33 void StoreReplayTurnLength(u32 turn, u32 turnLength); 34 35 void StoreReplayHash(u32 turn, const std::string& hash, bool quick); 36 37 void StoreFinalReplayTurn(u32 turn); 38 39 private: 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 30 const u32 DEFAULT_TURN_LENGTH_MP = 500; 31 const u32 DEFAULT_TURN_LENGTH_SP = 200; 32 33 const 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 41 CTurnManager::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 55 void 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 65 void CTurnManager::SetPlayerID(int playerId) 66 { 67 m_PlayerId = playerId; 68 } 69 70 bool 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 86 bool 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 174 bool 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 212 void 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 226 void 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 244 void 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 253 bool 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 269 void CTurnManager::EnableTimeWarpRecording(size_t numTurns) 270 { 271 m_TimeWarpStates.clear(); 272 m_TimeWarpNumTurns = numTurns; 273 } 274 275 void 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 291 void 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 312 void 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) 201 6Wildfire Games.1 /* Copyright (C) 2017 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 15 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 16 */ 17 17 18 #ifndef INCLUDED_ NETTURNMANAGER19 #define INCLUDED_ NETTURNMANAGER18 #ifndef INCLUDED_TURNMANAGER 19 #define INCLUDED_TURNMANAGER 20 20 21 21 #include "simulation2/helpers/SimulationCommand.h" 22 #include "lib/os_path.h"23 #include "NetMessage.h"24 22 25 23 #include <list> 26 24 #include <map> 27 25 #include <vector> 28 26 29 extern const u32 DEFAULT_TURN_LENGTH_MP;30 27 extern const u32 DEFAULT_TURN_LENGTH_SP; 28 extern const u32 DEFAULT_TURN_LENGTH_MP; 29 30 extern const int COMMAND_DELAY; 31 31 32 class CNetServerWorker;33 class CNetClient;34 32 class CSimulationMessage; 35 33 class CSimulation2; 36 34 class IReplayLogger; 37 35 38 36 /* 39 * This file deals with the logic of the networkturn system. The basic idea is as in37 * This file deals with the logic of the turn system. The basic idea is as in 40 38 * http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1 41 39 * 42 40 * Each player performs the simulation for turn N. … … class IReplayLogger; 53 51 */ 54 52 55 53 /** 56 * Common networkturn system (used by clients and offline games).54 * Common turn system (used by clients and offline games). 57 55 */ 58 class C NetTurnManager56 class CTurnManager 59 57 { 60 NONCOPYABLE(C NetTurnManager);58 NONCOPYABLE(CTurnManager); 61 59 public: 62 60 /** 63 61 * Construct for a given network session ID. 64 62 */ 65 C NetTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay);63 CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, int clientId, IReplayLogger& replay); 66 64 67 virtual ~C NetTurnManager() { }65 virtual ~CTurnManager() { } 68 66 69 67 void ResetState(u32 newCurrentTurn, u32 newReadyTurn); 70 68 … … public: 94 92 * Returns whether Update(simFrameLength, ...) will process at least one new turn. 95 93 * @param simFrameLength Length of the previous frame, in simulation seconds 96 94 */ 97 bool WillUpdate(float simFrameLength) ;95 bool WillUpdate(float simFrameLength) const; 98 96 99 97 /** 100 98 * Advance the graphics by a certain time. … … public: 109 107 virtual void OnSimulationMessage(CSimulationMessage* msg) = 0; 110 108 111 109 /** 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 /**122 110 * Called by simulation code, to add a new command to be distributed to all clients and executed soon. 123 111 */ 124 112 virtual void PostCommand(JS::HandleValue data) = 0; … … protected: 166 154 * Returns whether we should compute a complete state hash for the given turn, 167 155 * instead of a quick less-complete hash. 168 156 */ 169 bool TurnNeedsFullHash(u32 turn) ;157 bool TurnNeedsFullHash(u32 turn) const; 170 158 171 159 CSimulation2& m_Simulation2; 172 160 … … private: 203 191 std::string m_QuickSaveMetadata; 204 192 }; 205 193 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