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 : #ifndef INCLUDED_TURNMANAGER
19 : #define INCLUDED_TURNMANAGER
20 :
21 : #include "ps/CStr.h"
22 : #include "simulation2/helpers/SimulationCommand.h"
23 :
24 : #include <list>
25 : #include <map>
26 : #include <vector>
27 : #include <deque>
28 :
29 : class CSimulationMessage;
30 : class CSimulation2;
31 : class IReplayLogger;
32 :
33 : /**
34 : * This file defines the base class of the turn managers for clients, local games and replays.
35 : * The basic idea of our turn managing system across a network is as in this article:
36 : * http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1
37 : *
38 : * Each player performs the simulation for turn N.
39 : * User input is translated into commands scheduled for execution in turn N+2 which are
40 : * distributed to all other clients.
41 : * After a while, a client wants to perform the simulation for turn N+1,
42 : * which first requires that it has all the other clients' commands for turn N+1.
43 : * In that case, it does the simulation and tells all the other clients (via the server)
44 : * it has finished sending commands for turn N+2, and it starts sending commands for turn N+3.
45 : *
46 : * Commands are redistributed immediately by the server.
47 : * To ensure a consistent execution of commands, they are each associated with a
48 : * client session ID (which is globally unique and consistent), which is used to sort them.
49 : */
50 :
51 : /**
52 : * Default turn length in SP & MP.
53 : * This value should be as low as possible, while not introducing un-necessary lag.
54 : */
55 : inline constexpr u32 DEFAULT_TURN_LENGTH = 200;
56 :
57 : /**
58 : * In single-player, commands are directly scheduled for the next turn.
59 : */
60 : inline constexpr u32 COMMAND_DELAY_SP = 1;
61 :
62 : /**
63 : * In multi-player, clients can only compute turn N if all clients have finished sending commands for it,
64 : * i.e. N < CurrentTurn + COMMAND_DELAY for all clients.
65 : * Commands are sent from client to server to client, and both client and network can lag.
66 : * If a client reaches turn CURRENT_TURN + COMMAND_DELAY - 1, it'll freeze while waiting for commands.
67 : * To avoid that, we increase the command-delay to make sure that in general players will have received all commands
68 : * by the time they reach a given turn. Keep in mind the minimum delay is one turn.
69 : * This value should be as low as possible while avoiding 'freezing' in general usage.
70 : * TODO:
71 : * - this command-delay could vary based on server-client pings
72 : * - it ought be possible to send commands in a P2P fashion (with server verification), which would lower the ping.
73 : */
74 : inline constexpr u32 COMMAND_DELAY_MP = 4;
75 :
76 : /**
77 : * Common turn system (used by clients and offline games).
78 : */
79 : class CTurnManager
80 : {
81 : NONCOPYABLE(CTurnManager);
82 : public:
83 : /**
84 : * Construct for a given network session ID.
85 : */
86 : CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, u32 commandDelay, int clientId, IReplayLogger& replay);
87 :
88 0 : virtual ~CTurnManager() { }
89 :
90 : void ResetState(u32 newCurrentTurn, u32 newReadyTurn);
91 :
92 : /**
93 : * Set the current user's player ID, which will be added into command messages.
94 : */
95 : void SetPlayerID(int playerId);
96 :
97 : /**
98 : * Advance the simulation by a certain time. If this brings us past the current
99 : * turn length, the next turns are processed and the function returns true.
100 : * Otherwise, nothing happens and it returns false.
101 : *
102 : * @param simFrameLength Length of the previous frame, in simulation seconds
103 : * @param maxTurns Maximum number of turns to simulate at once
104 : */
105 : bool Update(float simFrameLength, size_t maxTurns);
106 :
107 : /**
108 : * Advance the simulation by as much as possible. Intended for catching up
109 : * over a small number of turns when rejoining a multiplayer match.
110 : * Returns true if it advanced by at least one turn.
111 : */
112 : bool UpdateFastForward();
113 :
114 : /**
115 : * Advance the graphics by a certain time.
116 : * @param simFrameLength Length of the previous frame, in simulation seconds
117 : * @param realFrameLength Length of the previous frame, in real time seconds
118 : */
119 : void Interpolate(float simFrameLength, float realFrameLength);
120 :
121 : /**
122 : * Called by networking code when a simulation message is received.
123 : */
124 : virtual void OnSimulationMessage(CSimulationMessage* msg) = 0;
125 :
126 : /**
127 : * Called by simulation code, to add a new command to be distributed to all clients and executed soon.
128 : */
129 : virtual void PostCommand(JS::HandleValue data) = 0;
130 :
131 : /**
132 : * Called when all commands for a given turn have been received.
133 : * This allows Update to progress to that turn.
134 : */
135 : void FinishedAllCommands(u32 turn, u32 turnLength);
136 :
137 : /**
138 : * Enables the recording of state snapshots every @p numTurns,
139 : * which can be jumped back to via RewindTimeWarp().
140 : * If @p numTurns is 0 then recording is disabled.
141 : */
142 : void EnableTimeWarpRecording(size_t numTurns);
143 :
144 : /**
145 : * Jumps back to the latest recorded state snapshot (if any).
146 : */
147 : void RewindTimeWarp();
148 :
149 : void QuickSave(JS::HandleValue GUIMetadata);
150 : void QuickLoad();
151 :
152 0 : u32 GetCurrentTurn() const { return m_CurrentTurn; }
153 :
154 : /**
155 : * @return how many turns are ready to be computed.
156 : * (used to detect players/observers that fall behind the live game.
157 : */
158 0 : u32 GetPendingTurns() const { return m_ReadyTurn - m_CurrentTurn; }
159 :
160 : protected:
161 : /**
162 : * Store a command to be executed at a given turn.
163 : */
164 : void AddCommand(int client, int player, JS::HandleValue data, u32 turn);
165 :
166 : /**
167 : * Called when this client has finished sending all its commands scheduled for the given turn.
168 : */
169 : virtual void NotifyFinishedOwnCommands(u32 turn) = 0;
170 :
171 : /**
172 : * Called when this client has finished a simulation update.
173 : */
174 : virtual void NotifyFinishedUpdate(u32 turn) = 0;
175 :
176 : /**
177 : * Returns whether we should compute a complete state hash for the given turn,
178 : * instead of a quick less-complete hash.
179 : */
180 : bool TurnNeedsFullHash(u32 turn) const;
181 :
182 : CSimulation2& m_Simulation2;
183 :
184 : /// The turn that we have most recently executed
185 : u32 m_CurrentTurn;
186 :
187 : // Current command delay (commands are scheduled for m_CurrentTurn + m_CommandDelay)
188 : u32 m_CommandDelay;
189 :
190 : /// The latest turn for which we have received all commands from all clients
191 : u32 m_ReadyTurn;
192 :
193 : // Current turn length
194 : u32 m_TurnLength;
195 :
196 : /// Commands queued at each turn (index 0 is for m_CurrentTurn+1)
197 : std::deque<std::map<u32, std::vector<SimulationCommand>>> m_QueuedCommands;
198 :
199 : int m_PlayerId;
200 : uint m_ClientId;
201 :
202 : /// Simulation time remaining until we ought to execute the next turn (as a negative value to
203 : /// add elapsed time increments to until we reach 0).
204 : float m_DeltaSimTime;
205 :
206 : IReplayLogger& m_Replay;
207 :
208 : // The number of the last turn that is allowed to be executed (used for replays)
209 : u32 m_FinalTurn;
210 :
211 : private:
212 : static const CStr EventNameSavegameLoaded;
213 :
214 : size_t m_TimeWarpNumTurns; // 0 if disabled
215 : std::list<std::string> m_TimeWarpStates;
216 : std::string m_QuickSaveState; // TODO: should implement a proper disk-based quicksave system
217 : JS::PersistentRootedValue m_QuickSaveMetadata;
218 : };
219 :
220 : #endif // INCLUDED_TURNMANAGER
|