Line data Source code
1 : /* Copyright (C) 2021 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/Profile.h"
26 : #include "ps/CLogger.h"
27 : #include "ps/Replay.h"
28 : #include "ps/Util.h"
29 : #include "scriptinterface/Object.h"
30 : #include "simulation2/Simulation2.h"
31 :
32 : #if 0
33 : #define NETTURN_LOG(...) debug_printf(__VA_ARGS__)
34 : #else
35 : #define NETTURN_LOG(...)
36 : #endif
37 :
38 1 : const CStr CTurnManager::EventNameSavegameLoaded = "SavegameLoaded";
39 :
40 0 : CTurnManager::CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, u32 commandDelay, int clientId, IReplayLogger& replay)
41 0 : : m_Simulation2(simulation), m_CurrentTurn(0), m_CommandDelay(commandDelay), m_ReadyTurn(commandDelay - 1), m_TurnLength(defaultTurnLength),
42 : m_PlayerId(-1), m_ClientId(clientId), m_DeltaSimTime(0), m_Replay(replay),
43 0 : m_FinalTurn(std::numeric_limits<u32>::max()), m_TimeWarpNumTurns(0)
44 : {
45 0 : ScriptRequest rq(m_Simulation2.GetScriptInterface());
46 0 : m_QuickSaveMetadata.init(rq.cx);
47 0 : m_QueuedCommands.resize(1);
48 0 : }
49 :
50 0 : void CTurnManager::ResetState(u32 newCurrentTurn, u32 newReadyTurn)
51 : {
52 0 : m_CurrentTurn = newCurrentTurn;
53 0 : m_ReadyTurn = newReadyTurn;
54 0 : m_DeltaSimTime = 0;
55 0 : size_t queuedCommandsSize = m_QueuedCommands.size();
56 0 : m_QueuedCommands.clear();
57 0 : m_QueuedCommands.resize(queuedCommandsSize);
58 0 : }
59 :
60 0 : void CTurnManager::SetPlayerID(int playerId)
61 : {
62 0 : m_PlayerId = playerId;
63 0 : }
64 :
65 0 : bool CTurnManager::Update(float simFrameLength, size_t maxTurns)
66 : {
67 0 : if (m_CurrentTurn > m_FinalTurn)
68 0 : return false;
69 :
70 0 : m_DeltaSimTime += simFrameLength;
71 :
72 : // If the game becomes laggy, m_DeltaSimTime increases progressively.
73 : // The engine will fast forward accordingly to catch up.
74 : // To keep the game playable, stop fast forwarding after 2 turn lengths.
75 0 : m_DeltaSimTime = std::min(m_DeltaSimTime, 2.0f * m_TurnLength / 1000.0f);
76 :
77 : // If we haven't reached the next turn yet, do nothing
78 0 : if (m_DeltaSimTime < 0)
79 0 : return false;
80 :
81 : NETTURN_LOG("Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn);
82 :
83 : // Check that the next turn is ready for execution
84 0 : if (m_ReadyTurn <= m_CurrentTurn && m_CommandDelay > 1)
85 : {
86 : // Oops, we wanted to start the next turn but it's not ready yet -
87 : // there must be too much network lag.
88 : // TODO: complain to the user.
89 : // TODO: send feedback to the server to increase the turn length.
90 :
91 : // Reset the next-turn timer to 0 so we try again next update but
92 : // so we don't rush to catch up in subsequent turns.
93 : // TODO: we should do clever rate adjustment instead of just pausing like this.
94 0 : m_DeltaSimTime = 0;
95 :
96 0 : return false;
97 : }
98 :
99 0 : maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn
100 :
101 0 : for (size_t i = 0; i < maxTurns; ++i)
102 : {
103 : // Check that we've reached the i'th next turn
104 0 : if (m_DeltaSimTime < 0)
105 0 : break;
106 :
107 : // Check that the i'th next turn is still ready
108 0 : if (m_ReadyTurn <= m_CurrentTurn && m_CommandDelay > 1)
109 0 : break;
110 :
111 : // To avoid confusing the profiler, we need to trigger the new turn
112 : // while we're not nested inside any PROFILE blocks
113 0 : g_Profiler.Turn();
114 :
115 0 : NotifyFinishedOwnCommands(m_CurrentTurn + m_CommandDelay);
116 :
117 : // Increase now, so Update can send new commands for a subsequent turn
118 0 : ++m_CurrentTurn;
119 :
120 : // Clean up any destroyed entities since the last turn (e.g. placement previews
121 : // or rally point flags generated by the GUI). (Must do this before the time warp
122 : // serialization.)
123 0 : m_Simulation2.FlushDestroyedEntities();
124 :
125 : // Save the current state for rewinding, if enabled
126 0 : if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0)
127 : {
128 0 : PROFILE3("time warp serialization");
129 0 : std::stringstream stream;
130 0 : m_Simulation2.SerializeState(stream);
131 0 : m_TimeWarpStates.push_back(stream.str());
132 : }
133 :
134 : // Put all the client commands into a single list, in a globally consistent order
135 0 : std::vector<SimulationCommand> commands;
136 0 : for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
137 0 : commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
138 :
139 0 : m_QueuedCommands.pop_front();
140 0 : m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
141 :
142 0 : m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
143 :
144 : NETTURN_LOG("Running %d cmds\n", commands.size());
145 :
146 0 : m_Simulation2.Update(m_TurnLength, commands);
147 :
148 0 : NotifyFinishedUpdate(m_CurrentTurn);
149 :
150 : // Set the time for the next turn update
151 0 : m_DeltaSimTime -= m_TurnLength / 1000.f;
152 : }
153 :
154 0 : return true;
155 : }
156 :
157 0 : bool CTurnManager::UpdateFastForward()
158 : {
159 0 : m_DeltaSimTime = 0;
160 :
161 : NETTURN_LOG("UpdateFastForward current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn);
162 :
163 : // Check that the next turn is ready for execution
164 0 : if (m_ReadyTurn <= m_CurrentTurn)
165 0 : return false;
166 :
167 0 : while (m_ReadyTurn > m_CurrentTurn)
168 : {
169 : // TODO: It would be nice to remove some of the duplication with Update()
170 : // (This is similar but doesn't call any Notify functions or update DeltaTime,
171 : // it just updates the simulation state)
172 :
173 0 : ++m_CurrentTurn;
174 :
175 0 : m_Simulation2.FlushDestroyedEntities();
176 :
177 : // Put all the client commands into a single list, in a globally consistent order
178 0 : std::vector<SimulationCommand> commands;
179 0 : for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
180 0 : commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
181 :
182 0 : m_QueuedCommands.pop_front();
183 0 : m_QueuedCommands.resize(m_QueuedCommands.size() + 1);
184 :
185 0 : m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
186 :
187 : NETTURN_LOG("Running %d cmds\n", commands.size());
188 :
189 0 : m_Simulation2.Update(m_TurnLength, commands);
190 : }
191 :
192 0 : return true;
193 : }
194 :
195 0 : void CTurnManager::Interpolate(float simFrameLength, float realFrameLength)
196 : {
197 : // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
198 : // we need to save the previous turn length?
199 :
200 0 : float offset = Clamp(m_DeltaSimTime / (m_TurnLength / 1000.f) + 1.0, 0.0, 1.0);
201 :
202 : // Stop animations while still updating the selection highlight
203 0 : if (m_CurrentTurn > m_FinalTurn)
204 0 : simFrameLength = 0;
205 :
206 0 : m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength);
207 0 : }
208 :
209 0 : void CTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn)
210 : {
211 : NETTURN_LOG("AddCommand(client=%d player=%d turn=%d current=%d, ready=%d)\n", client, player, turn, m_CurrentTurn, m_ReadyTurn);
212 :
213 : // Reject commands for turns that we should not be able to compute (in the past).
214 0 : if (m_CurrentTurn >= turn)
215 : {
216 : // The most likely explanation is that an observer that's lagging behind is sending commands,
217 : // which is possible when cheats are enabled. Report & ignore.
218 : // It seems a bad idea to error out too badly here:
219 : // nefarious clients could try and send broken commands to DOS.
220 0 : LOGWARNING("Received command for invalid turn %i (current turn is %i)", turn, m_CurrentTurn);
221 0 : return;
222 : }
223 :
224 0 : ScriptRequest rq(m_Simulation2.GetScriptInterface());
225 :
226 0 : Script::FreezeObject(rq, data, true);
227 :
228 0 : size_t command_in_turns = turn - (m_CurrentTurn+1);
229 0 : if (m_QueuedCommands.size() <= command_in_turns)
230 0 : m_QueuedCommands.resize(command_in_turns+1);
231 0 : m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, rq.cx, data);
232 : }
233 :
234 0 : void CTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
235 : {
236 : NETTURN_LOG("FinishedAllCommands(%d, %d)\n", turn, turnLength);
237 :
238 0 : ENSURE(turn == m_ReadyTurn + 1);
239 0 : m_ReadyTurn = turn;
240 0 : m_TurnLength = turnLength;
241 0 : }
242 :
243 0 : bool CTurnManager::TurnNeedsFullHash(u32 turn) const
244 : {
245 : // Check immediately for errors caused by e.g. inconsistent game versions
246 : // (The hash is computed after the first sim update, so we start at turn == 1)
247 0 : if (turn == 1)
248 0 : return true;
249 :
250 : // Otherwise check the full state every ~10 seconds in multiplayer games
251 : // (TODO: should probably remove this when we're reasonably sure the game
252 : // isn't too buggy, since the full hash is still pretty slow)
253 0 : if (turn % 20 == 0)
254 0 : return true;
255 :
256 0 : return false;
257 : }
258 :
259 0 : void CTurnManager::EnableTimeWarpRecording(size_t numTurns)
260 : {
261 0 : m_TimeWarpStates.clear();
262 0 : m_TimeWarpNumTurns = numTurns;
263 0 : }
264 :
265 0 : void CTurnManager::RewindTimeWarp()
266 : {
267 0 : if (m_TimeWarpStates.empty())
268 0 : return;
269 :
270 0 : std::stringstream stream(m_TimeWarpStates.back());
271 0 : m_Simulation2.DeserializeState(stream);
272 0 : m_TimeWarpStates.pop_back();
273 :
274 : // Reset the turn manager state, so we won't execute stray commands and
275 : // won't do the next snapshot until the appropriate time.
276 : // (Ideally we ought to serialise the turn manager state and restore it
277 : // here, but this is simpler for now.)
278 0 : ResetState(1, m_CommandDelay);
279 : }
280 :
281 0 : void CTurnManager::QuickSave(JS::HandleValue GUIMetadata)
282 : {
283 0 : TIMER(L"QuickSave");
284 :
285 0 : std::stringstream stream;
286 0 : if (!m_Simulation2.SerializeState(stream))
287 : {
288 0 : LOGERROR("Failed to quicksave game");
289 0 : return;
290 : }
291 :
292 0 : m_QuickSaveState = stream.str();
293 :
294 0 : ScriptRequest rq(m_Simulation2.GetScriptInterface());
295 :
296 0 : m_QuickSaveMetadata.set(Script::DeepCopy(rq, GUIMetadata));
297 : // Freeze state to ensure that consectuvie loads don't modify the state
298 0 : Script::FreezeObject(rq, m_QuickSaveMetadata, true);
299 :
300 0 : LOGMESSAGERENDER("Quicksaved game");
301 : }
302 :
303 0 : void CTurnManager::QuickLoad()
304 : {
305 0 : TIMER(L"QuickLoad");
306 :
307 0 : if (m_QuickSaveState.empty())
308 : {
309 0 : LOGERROR("Cannot quickload game - no game was quicksaved");
310 0 : return;
311 : }
312 :
313 0 : std::stringstream stream(m_QuickSaveState);
314 0 : if (!m_Simulation2.DeserializeState(stream))
315 : {
316 0 : LOGERROR("Failed to quickload game");
317 0 : return;
318 : }
319 :
320 : // See RewindTimeWarp
321 0 : ResetState(1, m_CommandDelay);
322 :
323 0 : if (!g_GUI)
324 0 : return;
325 :
326 0 : ScriptRequest rq(m_Simulation2.GetScriptInterface());
327 :
328 : // Provide a copy, so that GUI components don't have to clone to get mutable objects
329 0 : JS::RootedValue quickSaveMetadataClone(rq.cx, Script::DeepCopy(rq, m_QuickSaveMetadata));
330 :
331 0 : JS::RootedValueArray<1> paramData(rq.cx);
332 0 : paramData[0].set(quickSaveMetadataClone);
333 0 : g_GUI->SendEventToAll(EventNameSavegameLoaded, paramData);
334 :
335 0 : LOGMESSAGERENDER("Quickloaded game");
336 3 : }
|