diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index 2448e40c8b..6b14ceb221 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2016 Wildfire Games.
+/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -37,8 +37,8 @@
#include "lobby/scripting/JSInterface_Lobby.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
+#include "network/NetMessage.h"
#include "network/NetServer.h"
-#include "network/NetTurnManager.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/Errors.h"
@@ -70,6 +70,7 @@
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/helpers/Selection.h"
+#include "simulation2/system/TurnManager.h"
#include "soundmanager/SoundManager.h"
#include "soundmanager/scripting/JSInterface_Sound.h"
#include "tools/atlas/GameInterface/GameLoop.h"
diff --git a/source/network/NetClient.cpp b/source/network/NetClient.cpp
index aa23532ad0..b33dac382e 100644
--- a/source/network/NetClient.cpp
+++ b/source/network/NetClient.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2016 Wildfire Games.
+/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,9 +19,9 @@
#include "NetClient.h"
+#include "NetClientTurnManager.h"
#include "NetMessage.h"
#include "NetSession.h"
-#include "NetTurnManager.h"
#include "lib/byte_order.h"
#include "lib/sysdep/sysdep.h"
diff --git a/source/network/NetClientTurnManager.cpp b/source/network/NetClientTurnManager.cpp
new file mode 100644
index 0000000000..7da290cc0e
--- /dev/null
+++ b/source/network/NetClientTurnManager.cpp
@@ -0,0 +1,139 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "NetClientTurnManager.h"
+#include "NetClient.h"
+
+#include "gui/GUIManager.h"
+#include "ps/CLogger.h"
+#include "ps/Pyrogenesis.h"
+#include "ps/Replay.h"
+#include "ps/Util.h"
+#include "simulation2/Simulation2.h"
+
+#if 0
+#define NETCLIENTTURN_LOG(...) debug_printf(__VA_ARGS__)
+#else
+#define NETCLIENTTURN_LOG(...)
+#endif
+
+CNetClientTurnManager::CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay)
+ : CTurnManager(simulation, DEFAULT_TURN_LENGTH_MP, clientId, replay), m_NetClient(client)
+{
+}
+
+void CNetClientTurnManager::PostCommand(JS::HandleValue data)
+{
+ NETCLIENTTURN_LOG("PostCommand()\n");
+
+ // Transmit command to server
+ CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data);
+ m_NetClient.SendMessage(&msg);
+
+ // Add to our local queue
+ //AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
+ // TODO: we should do this when the server stops sending our commands back to us
+}
+
+void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
+{
+ NETCLIENTTURN_LOG("NotifyFinishedOwnCommands(%d)\n", turn);
+
+ CEndCommandBatchMessage msg;
+
+ msg.m_Turn = turn;
+
+ // The turn-length field of the CEndCommandBatchMessage is currently only relevant
+ // when sending it from the server to the clients.
+ // It could be used to verify that the client simulated the correct turn length.
+ msg.m_TurnLength = 0;
+
+ m_NetClient.SendMessage(&msg);
+}
+
+void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn)
+{
+ bool quick = !TurnNeedsFullHash(turn);
+ std::string hash;
+ {
+ PROFILE3("state hash check");
+ ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
+ }
+
+ NETCLIENTTURN_LOG("NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str());
+
+ m_Replay.Hash(hash, quick);
+
+ // Don't send the hash if OOS
+ if (m_HasSyncError)
+ return;
+
+ // Send message to the server
+ CSyncCheckMessage msg;
+ msg.m_Turn = turn;
+ msg.m_Hash = hash;
+ m_NetClient.SendMessage(&msg);
+}
+
+void CNetClientTurnManager::OnDestroyConnection()
+{
+ NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);
+}
+
+void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
+{
+ // Command received from the server - store it for later execution
+ AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn);
+}
+
+void CNetClientTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, const std::vector& playerNames)
+{
+ NETCLIENTTURN_LOG("OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str());
+
+ // Only complain the first time
+ if (m_HasSyncError)
+ return;
+
+ m_HasSyncError = true;
+
+ std::string hash;
+ ENSURE(m_Simulation2.ComputeStateHash(hash, !TurnNeedsFullHash(turn)));
+
+ OsPath path = psLogDir() / "oos_dump.txt";
+ std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
+ m_Simulation2.DumpDebugState(file);
+ file.close();
+
+ hash = Hexify(hash);
+
+ std::stringstream msg;
+ msg << "Out of sync on turn " << turn;
+
+ for (size_t i = 0; i < playerNames.size(); ++i)
+ msg << (i == 0 ? "\nPlayers: " : ", ") << utf8_from_wstring(playerNames[i].m_Name);
+
+ msg << "\n\n" << "Your game state is " << (expectedHash == hash ? "identical to" : "different from") << " the hosts game state.";
+
+ msg << "\n\n" << "Dumping current state to " << CStr(path.string8()).EscapeToPrintableASCII();
+
+ LOGERROR("%s", msg.str());
+
+ if (g_GUI)
+ g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
+}
diff --git a/source/network/NetClientTurnManager.h b/source/network/NetClientTurnManager.h
new file mode 100644
index 0000000000..c2f3d6ddfc
--- /dev/null
+++ b/source/network/NetClientTurnManager.h
@@ -0,0 +1,54 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_NETCLIENTTURNMANAGER
+#define INCLUDED_NETCLIENTTURNMANAGER
+
+#include "simulation2/system/TurnManager.h"
+#include "NetMessage.h"
+
+class CNetClient;
+
+/**
+ * Implementation of CTurnManager for network clients.
+ */
+class CNetClientTurnManager : public CTurnManager
+{
+ NONCOPYABLE(CNetClientTurnManager);
+public:
+ CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay);
+
+ void OnSimulationMessage(CSimulationMessage* msg) override;
+
+ void PostCommand(JS::HandleValue data) override;
+
+ /**
+ * Notify the server that all commands are sent to prepare the connection for termination.
+ */
+ void OnDestroyConnection();
+
+ void OnSyncError(u32 turn, const CStr& expectedHash, const std::vector& playerNames);
+
+private:
+ void NotifyFinishedOwnCommands(u32 turn) override;
+
+ void NotifyFinishedUpdate(u32 turn) override;
+
+ CNetClient& m_NetClient;
+};
+
+#endif // INCLUDED_NETCLIENTTURNMANAGER
diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp
index 38680d8efd..941b31ef01 100644
--- a/source/network/NetServer.cpp
+++ b/source/network/NetServer.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2016 Wildfire Games.
+/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -22,8 +22,8 @@
#include "NetClient.h"
#include "NetMessage.h"
#include "NetSession.h"
+#include "NetServerTurnManager.h"
#include "NetStats.h"
-#include "NetTurnManager.h"
#include "lib/external_libraries/enet.h"
#include "ps/CLogger.h"
@@ -32,6 +32,7 @@
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptRuntime.h"
#include "simulation2/Simulation2.h"
+#include "simulation2/system/TurnManager.h"
#if CONFIG2_MINIUPNPC
#include
diff --git a/source/network/NetServerTurnManager.cpp b/source/network/NetServerTurnManager.cpp
new file mode 100644
index 0000000000..1fc4114b51
--- /dev/null
+++ b/source/network/NetServerTurnManager.cpp
@@ -0,0 +1,173 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "NetMessage.h"
+#include "NetServerTurnManager.h"
+#include "NetServer.h"
+
+#include "simulation2/system/TurnManager.h"
+
+#if 0
+#define NETSERVERTURN_LOG(...) debug_printf(__VA_ARGS__)
+#else
+#define NETSERVERTURN_LOG(...)
+#endif
+
+CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server)
+ : m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP), m_HasSyncError(false)
+{
+ // The first turn we will actually execute is number 2,
+ // so store dummy values into the saved lengths list
+ m_SavedTurnLengths.push_back(0);
+ m_SavedTurnLengths.push_back(0);
+}
+
+void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
+{
+ NETSERVERTURN_LOG("NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn);
+
+ // Must be a client we've already heard of
+ ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());
+
+ // Clients must advance one turn at a time
+ ENSURE(turn == m_ClientsReady[client] + 1);
+ m_ClientsReady[client] = turn;
+
+ // Check whether this was the final client to become ready
+ CheckClientsReady();
+}
+
+void CNetServerTurnManager::CheckClientsReady()
+{
+ // See if all clients (including self) are ready for a new turn
+ for (const std::pair& clientReady : m_ClientsReady)
+ {
+ NETSERVERTURN_LOG(" %d: %d <=? %d\n", clientReady.first, clientReady.second, m_ReadyTurn);
+ if (clientReady.second <= m_ReadyTurn)
+ return; // wasn't ready for m_ReadyTurn+1
+ }
+
+ ++m_ReadyTurn;
+
+ NETSERVERTURN_LOG("CheckClientsReady: ready for turn %d\n", m_ReadyTurn);
+
+ // Tell all clients that the next turn is ready
+ CEndCommandBatchMessage msg;
+ msg.m_TurnLength = m_TurnLength;
+ msg.m_Turn = m_ReadyTurn;
+ m_NetServer.Broadcast(&msg);
+
+ ENSURE(m_SavedTurnLengths.size() == m_ReadyTurn);
+ m_SavedTurnLengths.push_back(m_TurnLength);
+}
+
+void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, const CStrW& playername, u32 turn, const CStr& hash)
+{
+ // Clients must advance one turn at a time
+ ENSURE(turn == m_ClientsSimulated[client] + 1);
+ m_ClientsSimulated[client] = turn;
+
+ // Check for OOS only if in sync
+ if (m_HasSyncError)
+ return;
+
+ m_ClientPlayernames[client] = playername;
+ m_ClientStateHashes[turn][client] = hash;
+
+ // Find the newest turn which we know all clients have simulated
+ u32 newest = std::numeric_limits::max();
+ for (const std::pair& clientSimulated : m_ClientsSimulated)
+ if (clientSimulated.second < newest)
+ newest = clientSimulated.second;
+
+ // For every set of state hashes that all clients have simulated, check for OOS
+ for (const std::pair>& clientStateHash : m_ClientStateHashes)
+ {
+ if (clientStateHash.first > newest)
+ break;
+
+ // Assume the host is correct (maybe we should choose the most common instead to help debugging)
+ std::string expected = clientStateHash.second.begin()->second;
+
+ // Find all players that are OOS on that turn
+ std::vector OOSPlayerNames;
+ for (const std::pair& hashPair : clientStateHash.second)
+ {
+ NETSERVERTURN_LOG("sync check %d: %d = %hs\n", it->first, cit->first, Hexify(cit->second).c_str());
+ if (hashPair.second != expected)
+ {
+ // Oh no, out of sync
+ m_HasSyncError = true;
+ OOSPlayerNames.push_back(m_ClientPlayernames[hashPair.first]);
+ }
+ }
+
+ // Tell everyone about it
+ if (m_HasSyncError)
+ {
+ CSyncErrorMessage msg;
+ msg.m_Turn = clientStateHash.first;
+ msg.m_HashExpected = expected;
+ for (const CStrW& playername : OOSPlayerNames)
+ {
+ CSyncErrorMessage::S_m_PlayerNames h;
+ h.m_Name = playername;
+ msg.m_PlayerNames.push_back(h);
+ }
+ m_NetServer.Broadcast(&msg);
+ break;
+ }
+ }
+
+ // Delete the saved hashes for all turns that we've already verified
+ m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1));
+}
+
+void CNetServerTurnManager::InitialiseClient(int client, u32 turn)
+{
+ NETSERVERTURN_LOG("InitialiseClient(client=%d, turn=%d)\n", client, turn);
+
+ ENSURE(m_ClientsReady.find(client) == m_ClientsReady.end());
+ m_ClientsReady[client] = turn + 1;
+ m_ClientsSimulated[client] = turn;
+}
+
+void CNetServerTurnManager::UninitialiseClient(int client)
+{
+ NETSERVERTURN_LOG("UninitialiseClient(client=%d)\n", client);
+
+ ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());
+ m_ClientsReady.erase(client);
+ m_ClientsSimulated.erase(client);
+
+ // Check whether we're ready for the next turn now that we're not
+ // waiting for this client any more
+ CheckClientsReady();
+}
+
+void CNetServerTurnManager::SetTurnLength(u32 msecs)
+{
+ m_TurnLength = msecs;
+}
+
+u32 CNetServerTurnManager::GetSavedTurnLength(u32 turn)
+{
+ ENSURE(turn <= m_ReadyTurn);
+ return m_SavedTurnLengths.at(turn);
+}
diff --git a/source/network/NetServerTurnManager.h b/source/network/NetServerTurnManager.h
new file mode 100644
index 0000000000..670e830f21
--- /dev/null
+++ b/source/network/NetServerTurnManager.h
@@ -0,0 +1,99 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_NETSERVERTURNMANAGER
+#define INCLUDED_NETSERVERTURNMANAGER
+
+#include