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"
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"
28 #include "ps/CLogger.h"
29 #include "ps/Loader.h"
31 #include "ps/Profile.h"
32 #include "ps/ProfileViewer.h"
33 #include "ps/Pyrogenesis.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"
44 CReplayLogger::CReplayLogger(const ScriptInterface
& scriptInterface
) :
45 m_ScriptInterface(scriptInterface
), m_Stream(NULL
)
49 CReplayLogger::~CReplayLogger()
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();
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";
84 void CReplayLogger::Hash(const std::string
& hash
, bool quick
)
87 *m_Stream
<< "hash-quick " << Hexify(hash
) << "\n";
89 *m_Stream
<< "hash " << Hexify(hash
) << "\n";
92 OsPath
CReplayLogger::GetDirectory() const
97 ////////////////////////////////////////////////////////////////
99 CReplayPlayer::CReplayPlayer() :
104 CReplayPlayer::~CReplayPlayer()
109 void CReplayPlayer::Load(const OsPath
& path
)
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
)
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
);
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
146 std::vector
<SimulationCommand
> commands
;
151 JSContext
* cx
= g_Game
->GetSimulation2()->GetScriptInterface().GetContext();
152 JSAutoRequest
rq(cx
);
154 while ((*m_Stream
>> type
).good())
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")
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");
209 bool ok
= g_Game
->GetSimulation2()->ComputeStateHash(hash
, quick
);
211 std::string hexHash
= Hexify(hash
);
212 if (hexHash
== replayHash
)
213 debug_printf("hash ok (%s)\n", hexHash
.c_str());
215 debug_printf("HASH MISMATCH (%s != %s)\n", hexHash
.c_str(), replayHash
.c_str());
218 else if (type
== "end")
221 g_Profiler2
.RecordFrameStart();
223 g_Profiler2
.IncrementFrameNumber();
224 PROFILE2_ATTR("%d", g_Profiler2
.GetFrameNumber());
226 g_Game
->GetSimulation2()->Update(turnLength
, commands
);
233 g_ProfileViewer
.SaveToFile();
237 debug_printf("Unrecognised replay token %s\n", type
.c_str());
242 SAFE_DELETE(m_Stream
);
244 g_Profiler2
.SaveToFile();
247 bool ok
= g_Game
->GetSimulation2()->ComputeStateHash(hash
, false);
249 debug_printf("# Final state: %s\n", Hexify(hash
).c_str());
250 timer_DisplayClientTotals();
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();
262 delete &g_ProfileViewer
;
263 SAFE_DELETE(g_ScriptStatsTable
);