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 
CTurnManager(CSimulation2 & simulation,u32 defaultTurnLength,int clientId,IReplayLogger & replay)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 
ResetState(u32 newCurrentTurn,u32 newReadyTurn)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 
SetPlayerID(int playerId)65 void CTurnManager::SetPlayerID(int playerId)
66 {
67 	m_PlayerId = playerId;
68 }
69 
WillUpdate(float simFrameLength) const70 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 
Update(float simFrameLength,size_t maxTurns)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 
UpdateFastForward()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 
Interpolate(float simFrameLength,float realFrameLength)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 
AddCommand(int client,int player,JS::HandleValue data,u32 turn)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 
FinishedAllCommands(u32 turn,u32 turnLength)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 
TurnNeedsFullHash(u32 turn) const253 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 
EnableTimeWarpRecording(size_t numTurns)269 void CTurnManager::EnableTimeWarpRecording(size_t numTurns)
270 {
271 	m_TimeWarpStates.clear();
272 	m_TimeWarpNumTurns = numTurns;
273 }
274 
RewindTimeWarp()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 
QuickSave()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 
QuickLoad()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 }
337