1 /* Copyright (C) 2012 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "NetTurnManager.h"
22 #include "network/NetServer.h"
23 #include "network/NetClient.h"
24 #include "network/NetMessage.h"
26 #include "gui/GUIManager.h"
27 #include "maths/MathUtil.h"
28 #include "ps/CLogger.h"
29 #include "ps/Profile.h"
30 #include "ps/Pyrogenesis.h"
31 #include "ps/Replay.h"
32 #include "ps/SavedGame.h"
33 #include "scriptinterface/ScriptInterface.h"
34 #include "simulation2/Simulation2.h"
40 static const int DEFAULT_TURN_LENGTH_MP
= 500;
41 static const int DEFAULT_TURN_LENGTH_SP
= 200;
43 static const int COMMAND_DELAY
= 2;
46 #define NETTURN_LOG(args) debug_printf args
48 #define NETTURN_LOG(args)
51 static std::wstring
Hexify(const std::string
& s
)
53 std::wstringstream str
;
55 for (size_t i
= 0; i
< s
.size(); ++i
)
56 str
<< std::setfill(L
'0') << std::setw(2) << (int)(unsigned char)s
[i
];
60 CNetTurnManager::CNetTurnManager(CSimulation2
& simulation
, u32 defaultTurnLength
, int clientId
, IReplayLogger
& replay
) :
61 m_Simulation2(simulation
), m_CurrentTurn(0), m_ReadyTurn(1), m_TurnLength(defaultTurnLength
), m_DeltaSimTime(0),
62 m_PlayerId(-1), m_ClientId(clientId
), m_HasSyncError(false), m_Replay(replay
),
65 // When we are on turn n, we schedule new commands for n+2.
66 // We know that all other clients have finished scheduling commands for n (else we couldn't have got here).
67 // We know we have not yet finished scheduling commands for n+2.
68 // Hence other clients can be on turn n-1, n, n+1, and no other.
69 // So they can be sending us commands scheduled for n+1, n+2, n+3.
70 // So we need a 3-element buffer:
71 m_QueuedCommands
.resize(COMMAND_DELAY
+ 1);
74 void CNetTurnManager::ResetState(u32 newCurrentTurn
, u32 newReadyTurn
)
76 m_CurrentTurn
= newCurrentTurn
;
77 m_ReadyTurn
= newReadyTurn
;
79 size_t queuedCommandsSize
= m_QueuedCommands
.size();
80 m_QueuedCommands
.clear();
81 m_QueuedCommands
.resize(queuedCommandsSize
);
84 void CNetTurnManager::SetPlayerID(int playerId
)
86 m_PlayerId
= playerId
;
89 bool CNetTurnManager::WillUpdate(float simFrameLength
)
91 // Keep this in sync with the return value of Update()
93 if (m_DeltaSimTime
+ simFrameLength
< 0)
96 if (m_ReadyTurn
<= m_CurrentTurn
)
102 bool CNetTurnManager::Update(float simFrameLength
, size_t maxTurns
)
104 m_DeltaSimTime
+= simFrameLength
;
106 // If we haven't reached the next turn yet, do nothing
107 if (m_DeltaSimTime
< 0)
110 NETTURN_LOG((L
"Update current=%d ready=%d\n", m_CurrentTurn
, m_ReadyTurn
));
112 // Check that the next turn is ready for execution
113 if (m_ReadyTurn
<= m_CurrentTurn
)
115 // Oops, we wanted to start the next turn but it's not ready yet -
116 // there must be too much network lag.
117 // TODO: complain to the user.
118 // TODO: send feedback to the server to increase the turn length.
120 // Reset the next-turn timer to 0 so we try again next update but
121 // so we don't rush to catch up in subsequent turns.
122 // TODO: we should do clever rate adjustment instead of just pausing like this.
128 maxTurns
= std::max((size_t)1, maxTurns
); // always do at least one turn
130 for (size_t i
= 0; i
< maxTurns
; ++i
)
132 // Check that we've reached the i'th next turn
133 if (m_DeltaSimTime
< 0)
136 // Check that the i'th next turn is still ready
137 if (m_ReadyTurn
<= m_CurrentTurn
)
140 NotifyFinishedOwnCommands(m_CurrentTurn
+ COMMAND_DELAY
);
142 m_CurrentTurn
+= 1; // increase the turn number now, so Update can send new commands for a subsequent turn
144 // Clean up any destroyed entities since the last turn (e.g. placement previews
145 // or rally point flags generated by the GUI). (Must do this before the time warp
147 m_Simulation2
.FlushDestroyedEntities();
149 // Save the current state for rewinding, if enabled
150 if (m_TimeWarpNumTurns
&& (m_CurrentTurn
% m_TimeWarpNumTurns
) == 0)
152 PROFILE3("time warp serialization");
153 std::stringstream stream
;
154 m_Simulation2
.SerializeState(stream
);
155 m_TimeWarpStates
.push_back(stream
.str());
158 // Put all the client commands into a single list, in a globally consistent order
159 std::vector
<SimulationCommand
> commands
;
160 for (std::map
<u32
, std::vector
<SimulationCommand
> >::iterator it
= m_QueuedCommands
[0].begin(); it
!= m_QueuedCommands
[0].end(); ++it
)
162 commands
.insert(commands
.end(), it
->second
.begin(), it
->second
.end());
164 m_QueuedCommands
.pop_front();
165 m_QueuedCommands
.resize(m_QueuedCommands
.size() + 1);
167 m_Replay
.Turn(m_CurrentTurn
-1, m_TurnLength
, commands
);
169 NETTURN_LOG((L
"Running %d cmds\n", commands
.size()));
171 m_Simulation2
.Update(m_TurnLength
, commands
);
173 NotifyFinishedUpdate(m_CurrentTurn
);
175 // Set the time for the next turn update
176 m_DeltaSimTime
-= m_TurnLength
/ 1000.f
;
182 bool CNetTurnManager::UpdateFastForward()
186 NETTURN_LOG((L
"UpdateFastForward current=%d ready=%d\n", m_CurrentTurn
, m_ReadyTurn
));
188 // Check that the next turn is ready for execution
189 if (m_ReadyTurn
<= m_CurrentTurn
)
192 while (m_ReadyTurn
> m_CurrentTurn
)
194 // TODO: It would be nice to remove some of the duplication with Update()
195 // (This is similar but doesn't call any Notify functions or update DeltaTime,
196 // it just updates the simulation state)
200 m_Simulation2
.FlushDestroyedEntities();
202 // Put all the client commands into a single list, in a globally consistent order
203 std::vector
<SimulationCommand
> commands
;
204 for (std::map
<u32
, std::vector
<SimulationCommand
> >::iterator it
= m_QueuedCommands
[0].begin(); it
!= m_QueuedCommands
[0].end(); ++it
)
206 commands
.insert(commands
.end(), it
->second
.begin(), it
->second
.end());
208 m_QueuedCommands
.pop_front();
209 m_QueuedCommands
.resize(m_QueuedCommands
.size() + 1);
211 m_Replay
.Turn(m_CurrentTurn
-1, m_TurnLength
, commands
);
213 NETTURN_LOG((L
"Running %d cmds\n", commands
.size()));
215 m_Simulation2
.Update(m_TurnLength
, commands
);
221 void CNetTurnManager::OnSyncError(u32 turn
, const std::string
& expectedHash
)
223 NETTURN_LOG((L
"OnSyncError(%d, %ls)\n", turn
, Hexify(expectedHash
).c_str()));
225 // Only complain the first time
228 m_HasSyncError
= true;
230 bool quick
= !TurnNeedsFullHash(turn
);
232 bool ok
= m_Simulation2
.ComputeStateHash(hash
, quick
);
235 OsPath path
= psLogDir()/"oos_dump.txt";
236 std::ofstream
file (OsString(path
).c_str(), std::ofstream::out
| std::ofstream::trunc
);
237 m_Simulation2
.DumpDebugState(file
);
240 std::wstringstream msg
;
241 msg
<< L
"Out of sync on turn " << turn
<< L
": expected hash " << Hexify(expectedHash
) << L
"\n\n";
242 msg
<< L
"Current state: turn " << m_CurrentTurn
<< L
", hash " << Hexify(hash
) << L
"\n\n";
243 msg
<< L
"Dumping current state to " << path
;
245 g_GUI
->DisplayMessageBox(600, 350, L
"Sync error", msg
.str());
247 LOGERROR(L
"%ls", msg
.str().c_str());
250 void CNetTurnManager::Interpolate(float simFrameLength
, float realFrameLength
)
252 // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
253 // we need to save the previous turn length?
255 float offset
= clamp(m_DeltaSimTime
/ (m_TurnLength
/ 1000.f
) + 1.0, 0.0, 1.0);
256 m_Simulation2
.Interpolate(simFrameLength
, offset
, realFrameLength
);
259 void CNetTurnManager::AddCommand(int client
, int player
, CScriptValRooted data
, u32 turn
)
261 NETTURN_LOG((L
"AddCommand(client=%d player=%d turn=%d)\n", client
, player
, turn
));
263 if (!(m_CurrentTurn
< turn
&& turn
<= m_CurrentTurn
+ COMMAND_DELAY
+ 1))
265 debug_warn(L
"Received command for invalid turn");
269 SimulationCommand cmd
;
272 m_QueuedCommands
[turn
- (m_CurrentTurn
+1)][client
].push_back(cmd
);
275 void CNetTurnManager::FinishedAllCommands(u32 turn
, u32 turnLength
)
277 NETTURN_LOG((L
"FinishedAllCommands(%d, %d)\n", turn
, turnLength
));
279 ENSURE(turn
== m_ReadyTurn
+ 1);
281 m_TurnLength
= turnLength
;
284 bool CNetTurnManager::TurnNeedsFullHash(u32 turn
)
286 // Check immediately for errors caused by e.g. inconsistent game versions
287 // (The hash is computed after the first sim update, so we start at turn == 1)
291 // Otherwise check the full state every ~10 seconds in multiplayer games
292 // (TODO: should probably remove this when we're reasonably sure the game
293 // isn't too buggy, since the full hash is still pretty slow)
300 void CNetTurnManager::EnableTimeWarpRecording(size_t numTurns
)
302 m_TimeWarpStates
.clear();
303 m_TimeWarpNumTurns
= numTurns
;
306 void CNetTurnManager::RewindTimeWarp()
308 if (m_TimeWarpStates
.empty())
311 std::stringstream
stream(m_TimeWarpStates
.back());
312 m_Simulation2
.DeserializeState(stream
);
313 m_TimeWarpStates
.pop_back();
315 // Reset the turn manager state, so we won't execute stray commands and
316 // won't do the next snapshot until the appropriate time.
317 // (Ideally we ought to serialise the turn manager state and restore it
318 // here, but this is simpler for now.)
322 void CNetTurnManager::QuickSave()
326 std::stringstream stream
;
327 bool ok
= m_Simulation2
.SerializeState(stream
);
330 LOGERROR(L
"Failed to quicksave game");
334 m_QuickSaveState
= stream
.str();
336 m_QuickSaveMetadata
= g_GUI
->GetScriptInterface().StringifyJSON(g_GUI
->GetSavedGameData().get(), false);
338 m_QuickSaveMetadata
= std::string();
340 LOGMESSAGERENDER(L
"Quicksaved game");
344 void CNetTurnManager::QuickLoad()
348 if (m_QuickSaveState
.empty())
350 LOGERROR(L
"Cannot quickload game - no game was quicksaved");
354 std::stringstream
stream(m_QuickSaveState
);
355 bool ok
= m_Simulation2
.DeserializeState(stream
);
358 LOGERROR(L
"Failed to quickload game");
362 if (g_GUI
&& !m_QuickSaveMetadata
.empty())
363 g_GUI
->GetScriptInterface().CallFunctionVoid(OBJECT_TO_JSVAL(g_GUI
->GetScriptObject()),
364 "restoreSavedGameData", g_GUI
->GetScriptInterface().ParseJSON(m_QuickSaveMetadata
));
366 LOGMESSAGERENDER(L
"Quickloaded game");
368 // See RewindTimeWarp
373 CNetClientTurnManager::CNetClientTurnManager(CSimulation2
& simulation
, CNetClient
& client
, int clientId
, IReplayLogger
& replay
) :
374 CNetTurnManager(simulation
, DEFAULT_TURN_LENGTH_MP
, clientId
, replay
), m_NetClient(client
)
378 void CNetClientTurnManager::PostCommand(CScriptValRooted data
)
380 NETTURN_LOG((L
"PostCommand()\n"));
382 // Transmit command to server
383 CSimulationMessage
msg(m_Simulation2
.GetScriptInterface(), m_ClientId
, m_PlayerId
, m_CurrentTurn
+ COMMAND_DELAY
, data
.get());
384 m_NetClient
.SendMessage(&msg
);
386 // Add to our local queue
387 //AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
388 // TODO: we should do this when the server stops sending our commands back to us
391 void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn
)
393 NETTURN_LOG((L
"NotifyFinishedOwnCommands(%d)\n", turn
));
395 // Send message to the server
396 CEndCommandBatchMessage msg
;
397 msg
.m_TurnLength
= DEFAULT_TURN_LENGTH_MP
; // TODO: why do we send this?
399 m_NetClient
.SendMessage(&msg
);
402 void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn
)
404 bool quick
= !TurnNeedsFullHash(turn
);
407 PROFILE3("state hash check");
408 bool ok
= m_Simulation2
.ComputeStateHash(hash
, quick
);
412 NETTURN_LOG((L
"NotifyFinishedUpdate(%d, %ls)\n", turn
, Hexify(hash
).c_str()));
414 m_Replay
.Hash(hash
, quick
);
416 // Send message to the server
417 CSyncCheckMessage msg
;
420 m_NetClient
.SendMessage(&msg
);
423 void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage
* msg
)
425 // Command received from the server - store it for later execution
426 AddCommand(msg
->m_Client
, msg
->m_Player
, msg
->m_Data
, msg
->m_Turn
);
430 CNetLocalTurnManager::CNetLocalTurnManager(CSimulation2
& simulation
, IReplayLogger
& replay
) :
431 CNetTurnManager(simulation
, DEFAULT_TURN_LENGTH_SP
, 0, replay
)
435 void CNetLocalTurnManager::PostCommand(CScriptValRooted data
)
437 // Add directly to the next turn, ignoring COMMAND_DELAY,
438 // because we don't need to compensate for network latency
439 AddCommand(m_ClientId
, m_PlayerId
, data
, m_CurrentTurn
+ 1);
442 void CNetLocalTurnManager::NotifyFinishedOwnCommands(u32 turn
)
444 FinishedAllCommands(turn
, m_TurnLength
);
447 void CNetLocalTurnManager::NotifyFinishedUpdate(u32
UNUSED(turn
))
449 #if 0 // this hurts performance and is only useful for verifying log replays
452 PROFILE3("state hash check");
453 bool ok
= m_Simulation2
.ComputeStateHash(hash
);
460 void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage
* UNUSED(msg
))
462 debug_warn(L
"This should never be called");
468 CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker
& server
) :
469 m_NetServer(server
), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP
)
471 // The first turn we will actually execute is number 2,
472 // so store dummy values into the saved lengths list
473 m_SavedTurnLengths
.push_back(0);
474 m_SavedTurnLengths
.push_back(0);
477 void CNetServerTurnManager::NotifyFinishedClientCommands(int client
, u32 turn
)
479 NETTURN_LOG((L
"NotifyFinishedClientCommands(client=%d, turn=%d)\n", client
, turn
));
481 // Must be a client we've already heard of
482 ENSURE(m_ClientsReady
.find(client
) != m_ClientsReady
.end());
484 // Clients must advance one turn at a time
485 ENSURE(turn
== m_ClientsReady
[client
] + 1);
486 m_ClientsReady
[client
] = turn
;
488 // Check whether this was the final client to become ready
492 void CNetServerTurnManager::CheckClientsReady()
494 // See if all clients (including self) are ready for a new turn
495 for (std::map
<int, u32
>::iterator it
= m_ClientsReady
.begin(); it
!= m_ClientsReady
.end(); ++it
)
497 NETTURN_LOG((L
" %d: %d <=? %d\n", it
->first
, it
->second
, m_ReadyTurn
));
498 if (it
->second
<= m_ReadyTurn
)
499 return; // wasn't ready for m_ReadyTurn+1
505 NETTURN_LOG((L
"CheckClientsReady: ready for turn %d\n", m_ReadyTurn
));
507 // Tell all clients that the next turn is ready
508 CEndCommandBatchMessage msg
;
509 msg
.m_TurnLength
= m_TurnLength
;
510 msg
.m_Turn
= m_ReadyTurn
;
511 m_NetServer
.Broadcast(&msg
);
513 // Save the turn length in case it's needed later
514 ENSURE(m_SavedTurnLengths
.size() == m_ReadyTurn
);
515 m_SavedTurnLengths
.push_back(m_TurnLength
);
518 void CNetServerTurnManager::NotifyFinishedClientUpdate(int client
, u32 turn
, const std::string
& hash
)
520 // Clients must advance one turn at a time
521 ENSURE(turn
== m_ClientsSimulated
[client
] + 1);
522 m_ClientsSimulated
[client
] = turn
;
524 m_ClientStateHashes
[turn
][client
] = hash
;
526 // Find the newest turn which we know all clients have simulated
527 u32 newest
= std::numeric_limits
<u32
>::max();
528 for (std::map
<int, u32
>::iterator it
= m_ClientsSimulated
.begin(); it
!= m_ClientsSimulated
.end(); ++it
)
530 if (it
->second
< newest
)
534 // For every set of state hashes that all clients have simulated, check for OOS
535 for (std::map
<u32
, std::map
<int, std::string
> >::iterator it
= m_ClientStateHashes
.begin(); it
!= m_ClientStateHashes
.end(); ++it
)
537 if (it
->first
> newest
)
540 // Assume the host is correct (maybe we should choose the most common instead to help debugging)
541 std::string expected
= it
->second
.begin()->second
;
543 for (std::map
<int, std::string
>::iterator cit
= it
->second
.begin(); cit
!= it
->second
.end(); ++cit
)
545 NETTURN_LOG((L
"sync check %d: %d = %ls\n", it
->first
, cit
->first
, Hexify(cit
->second
).c_str()));
546 if (cit
->second
!= expected
)
548 // Oh no, out of sync
550 // Tell everyone about it
551 CSyncErrorMessage msg
;
552 msg
.m_Turn
= it
->first
;
553 msg
.m_HashExpected
= expected
;
554 m_NetServer
.Broadcast(&msg
);
561 // Delete the saved hashes for all turns that we've already verified
562 m_ClientStateHashes
.erase(m_ClientStateHashes
.begin(), m_ClientStateHashes
.lower_bound(newest
+1));
565 void CNetServerTurnManager::InitialiseClient(int client
, u32 turn
)
567 NETTURN_LOG((L
"InitialiseClient(client=%d, turn=%d)\n", client
, turn
));
569 ENSURE(m_ClientsReady
.find(client
) == m_ClientsReady
.end());
570 m_ClientsReady
[client
] = turn
+ 1;
571 m_ClientsSimulated
[client
] = turn
;
574 void CNetServerTurnManager::UninitialiseClient(int client
)
576 NETTURN_LOG((L
"UninitialiseClient(client=%d)\n", client
));
578 ENSURE(m_ClientsReady
.find(client
) != m_ClientsReady
.end());
579 m_ClientsReady
.erase(client
);
580 m_ClientsSimulated
.erase(client
);
582 // Check whether we're ready for the next turn now that we're not
583 // waiting for this client any more
587 void CNetServerTurnManager::SetTurnLength(u32 msecs
)
589 m_TurnLength
= msecs
;
592 u32
CNetServerTurnManager::GetSavedTurnLength(u32 turn
)
594 ENSURE(turn
<= m_ReadyTurn
);
595 return m_SavedTurnLengths
.at(turn
);