forked from 0ad/0ad
Yves
4b1297b328
Each GUI Page gets its own compartment and all ScriptInterfaces in the same thread should now use the same JS Runtime. This is required for the SpiderMonkey upgrade. Check the ticket for details. Closes #2241 Refs #1886 Refs #1966 This was SVN commit r14496.
256 lines
6.3 KiB
C++
256 lines
6.3 KiB
C++
/* Copyright (C) 2013 Wildfire Games.
|
|
* This file is part of 0 A.D.
|
|
*
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "Replay.h"
|
|
|
|
#include "graphics/TerrainTextureManager.h"
|
|
#include "lib/timer.h"
|
|
#include "lib/file/file_system.h"
|
|
#include "lib/res/h_mgr.h"
|
|
#include "lib/tex/tex.h"
|
|
#include "ps/Game.h"
|
|
#include "ps/Loader.h"
|
|
#include "ps/Profile.h"
|
|
#include "ps/ProfileViewer.h"
|
|
#include "scriptinterface/ScriptInterface.h"
|
|
#include "scriptinterface/ScriptStats.h"
|
|
#include "simulation2/Simulation2.h"
|
|
#include "simulation2/helpers/SimulationCommand.h"
|
|
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
|
|
#if MSC_VERSION
|
|
#include <process.h>
|
|
#define getpid _getpid // use the non-deprecated function name
|
|
#endif
|
|
|
|
static std::string Hexify(const std::string& s)
|
|
{
|
|
std::stringstream str;
|
|
str << std::hex;
|
|
for (size_t i = 0; i < s.size(); ++i)
|
|
str << std::setfill('0') << std::setw(2) << (int)(unsigned char)s[i];
|
|
return str.str();
|
|
}
|
|
|
|
CReplayLogger::CReplayLogger(ScriptInterface& scriptInterface) :
|
|
m_ScriptInterface(scriptInterface)
|
|
{
|
|
// Construct the directory name based on the PID, to be relatively unique.
|
|
// Append "-1", "-2" etc if we run multiple matches in a single session,
|
|
// to avoid accidentally overwriting earlier logs.
|
|
|
|
std::wstringstream name;
|
|
name << getpid();
|
|
|
|
static int run = -1;
|
|
if (++run)
|
|
name << "-" << run;
|
|
|
|
OsPath path = psLogDir() / L"sim_log" / name.str() / L"commands.txt";
|
|
CreateDirectories(path.Parent(), 0700);
|
|
m_Stream = new std::ofstream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
|
|
}
|
|
|
|
CReplayLogger::~CReplayLogger()
|
|
{
|
|
delete m_Stream;
|
|
}
|
|
|
|
void CReplayLogger::StartGame(const CScriptValRooted& attribs)
|
|
{
|
|
*m_Stream << "start " << m_ScriptInterface.StringifyJSON(attribs.get(), false) << "\n";
|
|
}
|
|
|
|
void CReplayLogger::Turn(u32 n, u32 turnLength, const std::vector<SimulationCommand>& commands)
|
|
{
|
|
*m_Stream << "turn " << n << " " << turnLength << "\n";
|
|
for (size_t i = 0; i < commands.size(); ++i)
|
|
{
|
|
*m_Stream << "cmd " << commands[i].player << " " << m_ScriptInterface.StringifyJSON(commands[i].data.get(), false) << "\n";
|
|
}
|
|
*m_Stream << "end\n";
|
|
m_Stream->flush();
|
|
}
|
|
|
|
void CReplayLogger::Hash(const std::string& hash, bool quick)
|
|
{
|
|
if (quick)
|
|
*m_Stream << "hash-quick " << Hexify(hash) << "\n";
|
|
else
|
|
*m_Stream << "hash " << Hexify(hash) << "\n";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
CReplayPlayer::CReplayPlayer() :
|
|
m_Stream(NULL)
|
|
{
|
|
}
|
|
|
|
CReplayPlayer::~CReplayPlayer()
|
|
{
|
|
delete m_Stream;
|
|
}
|
|
|
|
void CReplayPlayer::Load(const std::string& path)
|
|
{
|
|
ENSURE(!m_Stream);
|
|
|
|
m_Stream = new std::ifstream(path.c_str());
|
|
ENSURE(m_Stream->good());
|
|
}
|
|
|
|
void CReplayPlayer::Replay()
|
|
{
|
|
ENSURE(m_Stream);
|
|
|
|
new CProfileViewer;
|
|
new CProfileManager;
|
|
g_ScriptStatsTable = new CScriptStatsTable;
|
|
g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
|
|
g_ScriptRuntime = ScriptInterface::CreateRuntime(128 * 1024 * 1024);
|
|
|
|
CGame game(true);
|
|
g_Game = &game;
|
|
|
|
// Need some stuff for terrain movement costs:
|
|
// (TODO: this ought to be independent of any graphics code)
|
|
tex_codec_register_all();
|
|
new CTerrainTextureManager;
|
|
g_TexMan.LoadTerrainTextures();
|
|
|
|
// Initialise h_mgr so it doesn't crash when emitting sounds
|
|
h_mgr_init();
|
|
|
|
std::vector<SimulationCommand> commands;
|
|
u32 turn = 0;
|
|
u32 turnLength = 0;
|
|
|
|
std::string type;
|
|
while ((*m_Stream >> type).good())
|
|
{
|
|
// if (turn >= 1400) break;
|
|
|
|
if (type == "start")
|
|
{
|
|
std::string line;
|
|
std::getline(*m_Stream, line);
|
|
CScriptValRooted attribs = game.GetSimulation2()->GetScriptInterface().ParseJSON(line);
|
|
|
|
game.StartGame(attribs, "");
|
|
|
|
// TODO: Non progressive load can fail - need a decent way to handle this
|
|
LDR_NonprogressiveLoad();
|
|
|
|
PSRETURN ret = game.ReallyStartGame();
|
|
ENSURE(ret == PSRETURN_OK);
|
|
}
|
|
else if (type == "turn")
|
|
{
|
|
*m_Stream >> turn >> turnLength;
|
|
debug_printf(L"Turn %u (%u)... ", turn, turnLength);
|
|
}
|
|
else if (type == "cmd")
|
|
{
|
|
player_id_t player;
|
|
*m_Stream >> player;
|
|
|
|
std::string line;
|
|
std::getline(*m_Stream, line);
|
|
CScriptValRooted data = game.GetSimulation2()->GetScriptInterface().ParseJSON(line);
|
|
|
|
SimulationCommand cmd = { player, data };
|
|
commands.push_back(cmd);
|
|
}
|
|
else if (type == "hash" || type == "hash-quick")
|
|
{
|
|
std::string replayHash;
|
|
*m_Stream >> replayHash;
|
|
|
|
bool quick = (type == "hash-quick");
|
|
|
|
// if (turn >= 1300)
|
|
// if (turn >= 0)
|
|
if (turn % 100 == 0)
|
|
{
|
|
std::string hash;
|
|
bool ok = game.GetSimulation2()->ComputeStateHash(hash, quick);
|
|
ENSURE(ok);
|
|
std::string hexHash = Hexify(hash);
|
|
if (hexHash == replayHash)
|
|
debug_printf(L"hash ok (%hs)", hexHash.c_str());
|
|
else
|
|
debug_printf(L"HASH MISMATCH (%hs != %hs)", hexHash.c_str(), replayHash.c_str());
|
|
}
|
|
}
|
|
else if (type == "end")
|
|
{
|
|
{
|
|
g_Profiler2.RecordFrameStart();
|
|
PROFILE2("frame");
|
|
g_Profiler2.IncrementFrameNumber();
|
|
PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber());
|
|
|
|
game.GetSimulation2()->Update(turnLength, commands);
|
|
commands.clear();
|
|
}
|
|
|
|
// std::string hash;
|
|
// bool ok = game.GetSimulation2()->ComputeStateHash(hash, true);
|
|
// ENSURE(ok);
|
|
// debug_printf(L"%hs", Hexify(hash).c_str());
|
|
|
|
debug_printf(L"\n");
|
|
|
|
g_Profiler.Frame();
|
|
|
|
// if (turn % 1000 == 0)
|
|
// JS_GC(game.GetSimulation2()->GetScriptInterface().GetContext());
|
|
|
|
if (turn % 20 == 0)
|
|
g_ProfileViewer.SaveToFile();
|
|
}
|
|
else
|
|
{
|
|
debug_printf(L"Unrecognised replay token %hs\n", type.c_str());
|
|
}
|
|
}
|
|
|
|
g_Profiler2.SaveToFile();
|
|
|
|
std::string hash;
|
|
bool ok = game.GetSimulation2()->ComputeStateHash(hash, false);
|
|
ENSURE(ok);
|
|
debug_printf(L"# Final state: %hs\n", Hexify(hash).c_str());
|
|
|
|
timer_DisplayClientTotals();
|
|
|
|
// Clean up
|
|
delete &g_TexMan;
|
|
tex_codec_unregister_all();
|
|
|
|
delete &g_Profiler;
|
|
delete &g_ProfileViewer;
|
|
|
|
g_Game = NULL;
|
|
}
|