Unify duplicate Breadth-First-Search traversing of the LayeredPainter and SmoothEleva...
[0ad.git] / source / ps / Replay.cpp
blobe00b388c288388ab6f2af5610a822c33c59529d8
1 /* Copyright (C) 2017 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 "Replay.h"
22 #include "graphics/TerrainTextureManager.h"
23 #include "lib/timer.h"
24 #include "lib/file/file_system.h"
25 #include "lib/res/h_mgr.h"
26 #include "lib/tex/tex.h"
27 #include "ps/Game.h"
28 #include "ps/CLogger.h"
29 #include "ps/Loader.h"
30 #include "ps/Mod.h"
31 #include "ps/Profile.h"
32 #include "ps/ProfileViewer.h"
33 #include "ps/Pyrogenesis.h"
34 #include "ps/Util.h"
35 #include "ps/VisualReplay.h"
36 #include "scriptinterface/ScriptInterface.h"
37 #include "scriptinterface/ScriptStats.h"
38 #include "simulation2/Simulation2.h"
39 #include "simulation2/helpers/SimulationCommand.h"
41 #include <ctime>
42 #include <fstream>
44 CReplayLogger::CReplayLogger(const ScriptInterface& scriptInterface) :
45 m_ScriptInterface(scriptInterface), m_Stream(NULL)
49 CReplayLogger::~CReplayLogger()
51 delete m_Stream;
54 void CReplayLogger::StartGame(JS::MutableHandleValue attribs)
56 // Add timestamp, since the file-modification-date can change
57 m_ScriptInterface.SetProperty(attribs, "timestamp", (double)std::time(nullptr));
59 // Add engine version and currently loaded mods for sanity checks when replaying
60 m_ScriptInterface.SetProperty(attribs, "engine_version", CStr(engine_version));
61 m_ScriptInterface.SetProperty(attribs, "mods", g_modsLoaded);
63 m_Directory = createDateIndexSubdirectory(VisualReplay::GetDirectoryName());
64 debug_printf("Writing replay to %s\n", m_Directory.string8().c_str());
66 m_Stream = new std::ofstream(OsString(m_Directory / L"commands.txt").c_str(), std::ofstream::out | std::ofstream::trunc);
67 *m_Stream << "start " << m_ScriptInterface.StringifyJSON(attribs, false) << "\n";
70 void CReplayLogger::Turn(u32 n, u32 turnLength, std::vector<SimulationCommand>& commands)
72 JSContext* cx = m_ScriptInterface.GetContext();
73 JSAutoRequest rq(cx);
75 *m_Stream << "turn " << n << " " << turnLength << "\n";
77 for (SimulationCommand& command : commands)
78 *m_Stream << "cmd " << command.player << " " << m_ScriptInterface.StringifyJSON(&command.data, false) << "\n";
80 *m_Stream << "end\n";
81 m_Stream->flush();
84 void CReplayLogger::Hash(const std::string& hash, bool quick)
86 if (quick)
87 *m_Stream << "hash-quick " << Hexify(hash) << "\n";
88 else
89 *m_Stream << "hash " << Hexify(hash) << "\n";
92 OsPath CReplayLogger::GetDirectory() const
94 return m_Directory;
97 ////////////////////////////////////////////////////////////////
99 CReplayPlayer::CReplayPlayer() :
100 m_Stream(NULL)
104 CReplayPlayer::~CReplayPlayer()
106 delete m_Stream;
109 void CReplayPlayer::Load(const OsPath& path)
111 ENSURE(!m_Stream);
113 m_Stream = new std::ifstream(OsString(path).c_str());
114 ENSURE(m_Stream->good());
117 void CReplayPlayer::Replay(bool serializationtest, int rejointestturn, bool ooslog)
119 ENSURE(m_Stream);
121 new CProfileViewer;
122 new CProfileManager;
123 g_ScriptStatsTable = new CScriptStatsTable;
124 g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
126 const int runtimeSize = 384 * 1024 * 1024;
127 const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024;
128 g_ScriptRuntime = ScriptInterface::CreateRuntime(shared_ptr<ScriptRuntime>(), runtimeSize, heapGrowthBytesGCTrigger);
130 g_Game = new CGame(true, false);
131 if (serializationtest)
132 g_Game->GetSimulation2()->EnableSerializationTest();
133 if (rejointestturn > 0)
134 g_Game->GetSimulation2()->EnableRejoinTest(rejointestturn);
135 if (ooslog)
136 g_Game->GetSimulation2()->EnableOOSLog();
138 // Need some stuff for terrain movement costs:
139 // (TODO: this ought to be independent of any graphics code)
140 new CTerrainTextureManager;
141 g_TexMan.LoadTerrainTextures();
143 // Initialise h_mgr so it doesn't crash when emitting sounds
144 h_mgr_init();
146 std::vector<SimulationCommand> commands;
147 u32 turn = 0;
148 u32 turnLength = 0;
151 JSContext* cx = g_Game->GetSimulation2()->GetScriptInterface().GetContext();
152 JSAutoRequest rq(cx);
153 std::string type;
154 while ((*m_Stream >> type).good())
156 if (type == "start")
158 std::string line;
159 std::getline(*m_Stream, line);
160 JS::RootedValue attribs(cx);
161 ENSURE(g_Game->GetSimulation2()->GetScriptInterface().ParseJSON(line, &attribs));
163 std::vector<CStr> replayModList;
164 g_Game->GetSimulation2()->GetScriptInterface().GetProperty(attribs, "mods", replayModList);
166 for (const CStr& mod : replayModList)
167 if (mod != "user" && std::find(g_modsLoaded.begin(), g_modsLoaded.end(), mod) == g_modsLoaded.end())
168 LOGWARNING("The mod '%s' is required by the replay file, but wasn't passed as an argument!", mod);
170 for (const CStr& mod : g_modsLoaded)
171 if (mod != "user" && std::find(replayModList.begin(), replayModList.end(), mod) == replayModList.end())
172 LOGWARNING("The mod '%s' wasn't used when creating this replay file, but was passed as an argument!", mod);
174 g_Game->StartGame(&attribs, "");
176 // TODO: Non progressive load can fail - need a decent way to handle this
177 LDR_NonprogressiveLoad();
179 PSRETURN ret = g_Game->ReallyStartGame();
180 ENSURE(ret == PSRETURN_OK);
182 else if (type == "turn")
184 *m_Stream >> turn >> turnLength;
185 debug_printf("Turn %u (%u)...\n", turn, turnLength);
187 else if (type == "cmd")
189 player_id_t player;
190 *m_Stream >> player;
192 std::string line;
193 std::getline(*m_Stream, line);
194 JS::RootedValue data(cx);
195 g_Game->GetSimulation2()->GetScriptInterface().ParseJSON(line, &data);
196 g_Game->GetSimulation2()->GetScriptInterface().FreezeObject(data, true);
197 commands.emplace_back(SimulationCommand(player, cx, data));
199 else if (type == "hash" || type == "hash-quick")
201 std::string replayHash;
202 *m_Stream >> replayHash;
204 bool quick = (type == "hash-quick");
206 if (turn % 100 == 0)
208 std::string hash;
209 bool ok = g_Game->GetSimulation2()->ComputeStateHash(hash, quick);
210 ENSURE(ok);
211 std::string hexHash = Hexify(hash);
212 if (hexHash == replayHash)
213 debug_printf("hash ok (%s)\n", hexHash.c_str());
214 else
215 debug_printf("HASH MISMATCH (%s != %s)\n", hexHash.c_str(), replayHash.c_str());
218 else if (type == "end")
221 g_Profiler2.RecordFrameStart();
222 PROFILE2("frame");
223 g_Profiler2.IncrementFrameNumber();
224 PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber());
226 g_Game->GetSimulation2()->Update(turnLength, commands);
227 commands.clear();
230 g_Profiler.Frame();
232 if (turn % 20 == 0)
233 g_ProfileViewer.SaveToFile();
235 else
237 debug_printf("Unrecognised replay token %s\n", type.c_str());
242 SAFE_DELETE(m_Stream);
244 g_Profiler2.SaveToFile();
246 std::string hash;
247 bool ok = g_Game->GetSimulation2()->ComputeStateHash(hash, false);
248 ENSURE(ok);
249 debug_printf("# Final state: %s\n", Hexify(hash).c_str());
250 timer_DisplayClientTotals();
252 SAFE_DELETE(g_Game);
254 // Must be explicitly destructed here to avoid callbacks from the JSAPI trying to use g_Profiler2 when
255 // it's already destructed.
256 g_ScriptRuntime.reset();
258 // Clean up
259 delete &g_TexMan;
261 delete &g_Profiler;
262 delete &g_ProfileViewer;
263 SAFE_DELETE(g_ScriptStatsTable);